@hh.ru/magritte-ui-scrollable-container 6.5.7 → 6.5.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ScrollStateProvider.d.ts +14 -0
- package/ScrollStateProvider.js +3 -1
- package/ScrollStateProvider.js.map +1 -1
- package/ScrollableContainer.js +2 -2
- package/ScrollableContainerDefaultLayout.js +1 -1
- package/ScrollableContainerDotPages.js +5 -8
- package/ScrollableContainerDotPages.js.map +1 -1
- package/ScrollableContainerGallery.js +4 -5
- package/ScrollableContainerGallery.js.map +1 -1
- package/ScrollableItem.js +1 -1
- package/index.css +13 -13
- package/index.js +2 -2
- package/index.mock.js +1 -1
- package/package.json +3 -3
- package/scrollableContainer-CQ8v8sxD.js +5 -0
- package/scrollableContainer-CQ8v8sxD.js.map +1 -0
- package/useScrollContainer.js +4 -5
- package/useScrollContainer.js.map +1 -1
- package/useScrollableControls.js +14 -11
- package/useScrollableControls.js.map +1 -1
- package/utils.d.ts +30 -27
- package/utils.js +61 -81
- package/utils.js.map +1 -1
- package/scrollableContainer-DMHjenAM.js +0 -5
- package/scrollableContainer-DMHjenAM.js.map +0 -1
package/ScrollStateProvider.d.ts
CHANGED
|
@@ -28,6 +28,20 @@ export interface ScrollState {
|
|
|
28
28
|
indexForDeferScroll: number;
|
|
29
29
|
/** тип поведения для отложенного скролла */
|
|
30
30
|
behaviorScrollTypeDeferScroll: BehaviorScrollType;
|
|
31
|
+
/** версия раскладки: инкрементируется при каждом пересчёте метрик элементов */
|
|
32
|
+
layoutVersion: number;
|
|
33
|
+
/** кэш snap-позиций, см. computeSnapPositions */
|
|
34
|
+
snapPositionsCache: SnapPositionsCache | null;
|
|
35
|
+
}
|
|
36
|
+
/** входы, при которых посчитаны snap-позиции; сравниваются по полям, чтобы не аллоцировать ключ на каждый кадр */
|
|
37
|
+
export interface SnapPositionsCache {
|
|
38
|
+
layoutVersion: number;
|
|
39
|
+
itemsCount: number;
|
|
40
|
+
viewportWidth: number;
|
|
41
|
+
minPosition: number;
|
|
42
|
+
correction: number;
|
|
43
|
+
align: ScrollSnapAlign;
|
|
44
|
+
positions: number[];
|
|
31
45
|
}
|
|
32
46
|
export declare const createScrollState: () => ScrollState;
|
|
33
47
|
export declare const ScrollStateContext: import("react").Context<ScrollState>;
|
package/ScrollStateProvider.js
CHANGED
|
@@ -14,7 +14,9 @@ const createScrollState = () => ({
|
|
|
14
14
|
rightCorrection: new ObservableValue(0),
|
|
15
15
|
indexForDeferScroll: -1,
|
|
16
16
|
behaviorScrollTypeDeferScroll: "animate",
|
|
17
|
-
scrollSnapAlign: "none"
|
|
17
|
+
scrollSnapAlign: "none",
|
|
18
|
+
layoutVersion: 0,
|
|
19
|
+
snapPositionsCache: null
|
|
18
20
|
});
|
|
19
21
|
const ScrollStateContext = createContext(createScrollState());
|
|
20
22
|
const useScrollState = () => useContext(ScrollStateContext);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScrollStateProvider.js","sources":["src/ScrollStateProvider.tsx"],"sourcesContent":["import { type RefObject, createContext, useContext } from 'react';\nimport { MotionValue } from 'motion/react';\n\nimport { ObservableValue } from '@hh.ru/magritte-ui-scrollable-container/ObservableValue';\nimport { type ItemWithOffset, type ScrollSnapAlign } from '@hh.ru/magritte-ui-scrollable-container/types';\n\nexport type BehaviorScrollType = 'animate' | 'instant';\n\nexport interface ScrollState {\n /** ширина контента, которая складывается из ширины элементов галереи */\n contentWidth: ObservableValue<number>;\n /** ширина вьюпорта для просмотра галереи */\n viewportWidth: ObservableValue<number>;\n /** элементы галереи */\n items: ObservableValue<RefObject<HTMLElement & ItemWithOffset>[]>;\n /** текущая позиция */\n scrollPosition: MotionValue<number>;\n /** текущая позиция, обернется в spring */\n springScrollPosition: MotionValue<number> | null;\n /** Определяет, как ближайший элемент будет размещён во вьюпорте при автоматическом скролле */\n scrollSnapAlign: ScrollSnapAlign;\n /**\n * флаг коррекции \"по умолчанию\"\n * возникает при использовании \"стандартных\" (по спеке) кнопок в fade\n */\n defaultButtonsCorrection: ObservableValue<number>;\n /** значения коррекции */\n leftCorrection: ObservableValue<number>;\n rightCorrection: ObservableValue<number>;\n /** индекс элемента для отложенного скролла */\n indexForDeferScroll: number;\n /** тип поведения для отложенного скролла */\n behaviorScrollTypeDeferScroll: BehaviorScrollType;\n}\n\nexport const createScrollState = (): ScrollState => ({\n contentWidth: new ObservableValue(0),\n viewportWidth: new ObservableValue(0),\n items: new ObservableValue<RefObject<HTMLElement & ItemWithOffset>[]>([]),\n scrollPosition: new MotionValue(0),\n springScrollPosition: null,\n defaultButtonsCorrection: new ObservableValue(0),\n leftCorrection: new ObservableValue(0),\n rightCorrection: new ObservableValue(0),\n indexForDeferScroll: -1,\n behaviorScrollTypeDeferScroll: 'animate',\n scrollSnapAlign: 'none',\n});\n\nexport const ScrollStateContext = createContext<ScrollState>(createScrollState());\n\nexport const useScrollState = (): ScrollState => useContext(ScrollStateContext);\n"],"names":[],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"ScrollStateProvider.js","sources":["src/ScrollStateProvider.tsx"],"sourcesContent":["import { type RefObject, createContext, useContext } from 'react';\nimport { MotionValue } from 'motion/react';\n\nimport { ObservableValue } from '@hh.ru/magritte-ui-scrollable-container/ObservableValue';\nimport { type ItemWithOffset, type ScrollSnapAlign } from '@hh.ru/magritte-ui-scrollable-container/types';\n\nexport type BehaviorScrollType = 'animate' | 'instant';\n\nexport interface ScrollState {\n /** ширина контента, которая складывается из ширины элементов галереи */\n contentWidth: ObservableValue<number>;\n /** ширина вьюпорта для просмотра галереи */\n viewportWidth: ObservableValue<number>;\n /** элементы галереи */\n items: ObservableValue<RefObject<HTMLElement & ItemWithOffset>[]>;\n /** текущая позиция */\n scrollPosition: MotionValue<number>;\n /** текущая позиция, обернется в spring */\n springScrollPosition: MotionValue<number> | null;\n /** Определяет, как ближайший элемент будет размещён во вьюпорте при автоматическом скролле */\n scrollSnapAlign: ScrollSnapAlign;\n /**\n * флаг коррекции \"по умолчанию\"\n * возникает при использовании \"стандартных\" (по спеке) кнопок в fade\n */\n defaultButtonsCorrection: ObservableValue<number>;\n /** значения коррекции */\n leftCorrection: ObservableValue<number>;\n rightCorrection: ObservableValue<number>;\n /** индекс элемента для отложенного скролла */\n indexForDeferScroll: number;\n /** тип поведения для отложенного скролла */\n behaviorScrollTypeDeferScroll: BehaviorScrollType;\n /** версия раскладки: инкрементируется при каждом пересчёте метрик элементов */\n layoutVersion: number;\n /** кэш snap-позиций, см. computeSnapPositions */\n snapPositionsCache: SnapPositionsCache | null;\n}\n\n/** входы, при которых посчитаны snap-позиции; сравниваются по полям, чтобы не аллоцировать ключ на каждый кадр */\nexport interface SnapPositionsCache {\n layoutVersion: number;\n itemsCount: number;\n viewportWidth: number;\n minPosition: number;\n correction: number;\n align: ScrollSnapAlign;\n positions: number[];\n}\n\nexport const createScrollState = (): ScrollState => ({\n contentWidth: new ObservableValue(0),\n viewportWidth: new ObservableValue(0),\n items: new ObservableValue<RefObject<HTMLElement & ItemWithOffset>[]>([]),\n scrollPosition: new MotionValue(0),\n springScrollPosition: null,\n defaultButtonsCorrection: new ObservableValue(0),\n leftCorrection: new ObservableValue(0),\n rightCorrection: new ObservableValue(0),\n indexForDeferScroll: -1,\n behaviorScrollTypeDeferScroll: 'animate',\n scrollSnapAlign: 'none',\n layoutVersion: 0,\n snapPositionsCache: null,\n});\n\nexport const ScrollStateContext = createContext<ScrollState>(createScrollState());\n\nexport const useScrollState = (): ScrollState => useContext(ScrollStateContext);\n"],"names":[],"mappings":";;;;AAkDO,MAAM,oBAAoB,OAAoB;AAAA,EACjD,YAAA,EAAc,IAAI,eAAA,CAAgB,CAAC,CAAA;AAAA,EACnC,aAAA,EAAe,IAAI,eAAA,CAAgB,CAAC,CAAA;AAAA,EACpC,KAAO,EAAA,IAAI,eAA2D,CAAA,EAAE,CAAA;AAAA,EACxE,cAAA,EAAgB,IAAI,WAAA,CAAY,CAAC,CAAA;AAAA,EACjC,oBAAsB,EAAA,IAAA;AAAA,EACtB,wBAAA,EAA0B,IAAI,eAAA,CAAgB,CAAC,CAAA;AAAA,EAC/C,cAAA,EAAgB,IAAI,eAAA,CAAgB,CAAC,CAAA;AAAA,EACrC,eAAA,EAAiB,IAAI,eAAA,CAAgB,CAAC,CAAA;AAAA,EACtC,mBAAqB,EAAA,CAAA,CAAA;AAAA,EACrB,6BAA+B,EAAA,SAAA;AAAA,EAC/B,eAAiB,EAAA,MAAA;AAAA,EACjB,aAAe,EAAA,CAAA;AAAA,EACf,kBAAoB,EAAA,IAAA;AACxB,CAAA,EAAA;AAEa,MAAA,kBAAA,GAAqB,aAA2B,CAAA,iBAAA,EAAmB,EAAA;AAEnE,MAAA,cAAA,GAAiB,MAAmB,UAAA,CAAW,kBAAkB;;;;"}
|
package/ScrollableContainer.js
CHANGED
|
@@ -11,18 +11,18 @@ import { ScrollableContainerGallery } from './ScrollableContainerGallery.js';
|
|
|
11
11
|
import { ScrollableItem } from './ScrollableItem.js';
|
|
12
12
|
import { useScrollableControlsWithState } from './useScrollableControls.js';
|
|
13
13
|
import { getActualCorrection, useInitOnce } from './utils.js';
|
|
14
|
-
import { s as styles } from './scrollableContainer-
|
|
14
|
+
import { s as styles } from './scrollableContainer-CQ8v8sxD.js';
|
|
15
15
|
import 'motion/react';
|
|
16
16
|
import './ObservableValue.js';
|
|
17
17
|
import '@hh.ru/magritte-common-use-translation';
|
|
18
18
|
import '@hh.ru/magritte-ui-icon/icon';
|
|
19
19
|
import '@hh.ru/magritte-internal-custom-scroll/initScrollHandlers';
|
|
20
20
|
import '@hh.ru/magritte-ui-dot-pages';
|
|
21
|
-
import './types.js';
|
|
22
21
|
import '@hh.ru/magritte-ui-spacing';
|
|
23
22
|
import './useScrollContainer.js';
|
|
24
23
|
import '@hh.ru/magritte-common-func-utils';
|
|
25
24
|
import '@hh.ru/magritte-internal-custom-scroll';
|
|
25
|
+
import './types.js';
|
|
26
26
|
|
|
27
27
|
const ScrollableContainerComponent = forwardRef(
|
|
28
28
|
({
|
|
@@ -9,7 +9,7 @@ import { ChevronLeftOutlinedSize24, ChevronRightOutlinedSize24 } from '@hh.ru/ma
|
|
|
9
9
|
import { useScrollState } from './ScrollStateProvider.js';
|
|
10
10
|
import { ScrollableContainerLeft, ScrollableContainerRight } from './ScrollableContainerButton.js';
|
|
11
11
|
import { BREAKPOINTS_LIST, NO_BUTTONS_START_FROM_BREAKPOINT, useInitOnce } from './utils.js';
|
|
12
|
-
import { s as styles } from './scrollableContainer-
|
|
12
|
+
import { s as styles } from './scrollableContainer-CQ8v8sxD.js';
|
|
13
13
|
import './ObservableValue.js';
|
|
14
14
|
import './useScrollableControls.js';
|
|
15
15
|
import '@hh.ru/magritte-internal-custom-scroll';
|
|
@@ -4,14 +4,14 @@ import { useState, useRef, useEffect } from 'react';
|
|
|
4
4
|
import { clamp } from '@hh.ru/magritte-internal-custom-scroll/initScrollHandlers';
|
|
5
5
|
import { DotPages } from '@hh.ru/magritte-ui-dot-pages';
|
|
6
6
|
import { useScrollState } from './ScrollStateProvider.js';
|
|
7
|
-
import {
|
|
8
|
-
import { useInitOnce, findIndexNearestToSnapPoint } from './utils.js';
|
|
7
|
+
import { useInitOnce, findNearestSnapIndex } from './utils.js';
|
|
9
8
|
import { VSpacing } from '@hh.ru/magritte-ui-spacing';
|
|
10
|
-
import { s as styles } from './scrollableContainer-
|
|
9
|
+
import { s as styles } from './scrollableContainer-CQ8v8sxD.js';
|
|
11
10
|
import 'motion/react';
|
|
12
11
|
import './ObservableValue.js';
|
|
13
12
|
import '@hh.ru/magritte-internal-custom-scroll';
|
|
14
13
|
import '@hh.ru/magritte-ui-breakpoint';
|
|
14
|
+
import './types.js';
|
|
15
15
|
|
|
16
16
|
const DEFAULT_DOT = 1;
|
|
17
17
|
const ScrollableContainerDotPages = ({
|
|
@@ -34,13 +34,10 @@ const ScrollableContainerDotPages = ({
|
|
|
34
34
|
activeDotRef.current = dot;
|
|
35
35
|
});
|
|
36
36
|
const updateActiveDot = useInitOnce(() => () => {
|
|
37
|
-
|
|
38
|
-
const center = viewportWidth.get() / 2 + Math.abs(scrollPosition.get());
|
|
39
|
-
const activeDotItem = items.get()[activeDotRef.current - 1]?.current;
|
|
40
|
-
if (!activeDotItem || !activeDotItem[OFFSET_ATTR] || activeDotItem[OFFSET_ATTR].left <= center && activeDotItem[OFFSET_ATTR].right >= center) {
|
|
37
|
+
if (scrollState.items.get().length === 0) {
|
|
41
38
|
return;
|
|
42
39
|
}
|
|
43
|
-
setDotAndEmitOnChange(
|
|
40
|
+
setDotAndEmitOnChange(findNearestSnapIndex(scrollState) + 1);
|
|
44
41
|
});
|
|
45
42
|
useEffect(() => {
|
|
46
43
|
const unsubscribe = [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScrollableContainerDotPages.js","sources":["src/ScrollableContainerDotPages.tsx"],"sourcesContent":["import { type FC, useState, useEffect, useRef } from 'react';\n\nimport { clamp } from '@hh.ru/magritte-internal-custom-scroll/initScrollHandlers';\nimport { DotPages } from '@hh.ru/magritte-ui-dot-pages';\nimport { useScrollState } from '@hh.ru/magritte-ui-scrollable-container/ScrollStateProvider';\nimport {
|
|
1
|
+
{"version":3,"file":"ScrollableContainerDotPages.js","sources":["src/ScrollableContainerDotPages.tsx"],"sourcesContent":["import { type FC, useState, useEffect, useRef } from 'react';\n\nimport { clamp } from '@hh.ru/magritte-internal-custom-scroll/initScrollHandlers';\nimport { DotPages } from '@hh.ru/magritte-ui-dot-pages';\nimport { useScrollState } from '@hh.ru/magritte-ui-scrollable-container/ScrollStateProvider';\nimport { ScrollableContainerDotPagesProps } from '@hh.ru/magritte-ui-scrollable-container/types';\nimport { useInitOnce, findNearestSnapIndex } from '@hh.ru/magritte-ui-scrollable-container/utils';\nimport { VSpacing } from '@hh.ru/magritte-ui-spacing';\n\nimport styles from './scrollableContainer.less';\n\nconst DEFAULT_DOT = 1;\n\nexport const ScrollableContainerDotPages: FC<ScrollableContainerDotPagesProps> = ({\n onChangeDot,\n dotPagesProps,\n offsetTop = 8,\n}) => {\n const scrollState = useScrollState();\n const [activeDot, setActiveDot] = useState(DEFAULT_DOT);\n const [itemsCount, setItemsCount] = useState(0);\n const activeDotRef = useRef(DEFAULT_DOT);\n const onChangeDotRef = useRef(onChangeDot);\n onChangeDotRef.current = onChangeDot;\n\n const setDotAndEmitOnChange = useInitOnce(() => (dot: number) => {\n if (dot === activeDotRef.current) {\n return;\n }\n setActiveDot(dot);\n onChangeDotRef.current?.(dot);\n activeDotRef.current = dot;\n });\n\n const updateActiveDot = useInitOnce(() => () => {\n if (scrollState.items.get().length === 0) {\n return;\n }\n setDotAndEmitOnChange(findNearestSnapIndex(scrollState) + 1);\n });\n\n useEffect(() => {\n const unsubscribe = [\n scrollState.scrollPosition.on('change', updateActiveDot),\n scrollState.items.on('change', (items) => {\n setItemsCount(items.length);\n // удалили последний элемент и он был активный\n if (activeDotRef.current > items.length && items.length !== 0) {\n setDotAndEmitOnChange(clamp(activeDotRef.current, 0, items.length - 1));\n return;\n }\n updateActiveDot();\n }),\n ];\n return () => {\n unsubscribe.forEach((fn) => fn());\n };\n }, [scrollState, updateActiveDot, setDotAndEmitOnChange]);\n\n if (itemsCount === 0) {\n return null;\n }\n\n return (\n <div>\n <VSpacing default={offsetTop} />\n <div className={styles.dotContainer}>\n <DotPages\n active={activeDot}\n count={itemsCount}\n data-qa={dotPagesProps?.['data-qa'] || 'scrollable-container__dot-pages'}\n {...dotPagesProps}\n />\n </div>\n </div>\n );\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -5,8 +5,8 @@ import { useSpring, motion, animate } from 'motion/react';
|
|
|
5
5
|
import { clamp } from '@hh.ru/magritte-internal-custom-scroll/initScrollHandlers';
|
|
6
6
|
import { useScrollState } from './ScrollStateProvider.js';
|
|
7
7
|
import { useScrollContainer } from './useScrollContainer.js';
|
|
8
|
-
import { getDisableState,
|
|
9
|
-
import { s as styles } from './scrollableContainer-
|
|
8
|
+
import { getDisableState, findNearestSnapIndex, computeTargetPositionForIndex } from './utils.js';
|
|
9
|
+
import { s as styles } from './scrollableContainer-CQ8v8sxD.js';
|
|
10
10
|
import './ObservableValue.js';
|
|
11
11
|
import '@hh.ru/magritte-common-func-utils';
|
|
12
12
|
import '@hh.ru/magritte-internal-custom-scroll';
|
|
@@ -37,9 +37,8 @@ const ScrollableContainerGallery = ({
|
|
|
37
37
|
useEffect(() => {
|
|
38
38
|
const { scrollPosition, viewportWidth, contentWidth } = scrollState;
|
|
39
39
|
const updateScrollPositionByResize = () => {
|
|
40
|
-
const index =
|
|
41
|
-
const
|
|
42
|
-
const target = scrollSnapBehavior(scrollState, index);
|
|
40
|
+
const index = findNearestSnapIndex(scrollState);
|
|
41
|
+
const target = computeTargetPositionForIndex(scrollState, index);
|
|
43
42
|
if (target === null) {
|
|
44
43
|
return;
|
|
45
44
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScrollableContainerGallery.js","sources":["src/ScrollableContainerGallery.tsx"],"sourcesContent":["import { type FC, createContext, useContext, useEffect, useRef } from 'react';\nimport { animate, motion, useSpring } from 'motion/react';\n\nimport { clamp } from '@hh.ru/magritte-internal-custom-scroll/initScrollHandlers';\nimport { useScrollState } from '@hh.ru/magritte-ui-scrollable-container/ScrollStateProvider';\nimport type {\n ScrollableContainerGalleryProps,\n RegisterAndRemoveItem,\n} from '@hh.ru/magritte-ui-scrollable-container/types';\nimport { useScrollContainer } from '@hh.ru/magritte-ui-scrollable-container/useScrollContainer';\nimport {\n getDisableState,\n
|
|
1
|
+
{"version":3,"file":"ScrollableContainerGallery.js","sources":["src/ScrollableContainerGallery.tsx"],"sourcesContent":["import { type FC, createContext, useContext, useEffect, useRef } from 'react';\nimport { animate, motion, useSpring } from 'motion/react';\n\nimport { clamp } from '@hh.ru/magritte-internal-custom-scroll/initScrollHandlers';\nimport { useScrollState } from '@hh.ru/magritte-ui-scrollable-container/ScrollStateProvider';\nimport type {\n ScrollableContainerGalleryProps,\n RegisterAndRemoveItem,\n} from '@hh.ru/magritte-ui-scrollable-container/types';\nimport { useScrollContainer } from '@hh.ru/magritte-ui-scrollable-container/useScrollContainer';\nimport {\n getDisableState,\n computeTargetPositionForIndex,\n findNearestSnapIndex,\n} from '@hh.ru/magritte-ui-scrollable-container/utils';\n\nimport styles from './scrollableContainer.less';\n\nconst GalleryContext = createContext<RegisterAndRemoveItem>({\n registerItem: () => null,\n removeItem: () => null,\n});\n\nexport const useGalleryContext = (): RegisterAndRemoveItem => useContext(GalleryContext);\n\nexport const ScrollableContainerGallery: FC<ScrollableContainerGalleryProps> = ({\n children,\n onScroll,\n onReady,\n onChangeSize,\n ...props\n}) => {\n const scrollState = useScrollState();\n const onReadyRef = useRef(onReady);\n const onScrollRef = useRef(onScroll);\n const onChangeSizeRef = useRef(onChangeSize);\n onReadyRef.current = onReady;\n onScrollRef.current = onScroll;\n onChangeSizeRef.current = onChangeSize;\n\n const { registerItem, removeItem, initScrollHandlers, containerRef, positionContainerRef, isTouch } =\n useScrollContainer();\n\n useEffect(() => {\n const { scrollPosition, viewportWidth, contentWidth } = scrollState;\n const updateScrollPositionByResize = () => {\n const index = findNearestSnapIndex(scrollState);\n const target = computeTargetPositionForIndex(scrollState, index);\n if (target === null) {\n return;\n }\n const position = clamp(target, viewportWidth.get() - contentWidth.get(), 0);\n onChangeSizeRef.current?.(getDisableState(scrollState));\n if (scrollPosition.get() === position) {\n return;\n }\n animate(scrollPosition, position, {\n velocity: Math.max(scrollPosition.getVelocity(), 1),\n });\n };\n const unsubscribe = [\n scrollPosition.on('change', () => {\n onScrollRef.current?.(getDisableState(scrollState));\n }),\n viewportWidth.on('change', updateScrollPositionByResize),\n contentWidth.on('change', updateScrollPositionByResize),\n ];\n return () => {\n unsubscribe.forEach((fn) => fn());\n };\n }, [scrollState]);\n\n useEffect(() => {\n onReadyRef.current?.(getDisableState(scrollState));\n return initScrollHandlers();\n }, [initScrollHandlers, scrollState]);\n\n const springScrollPosition = useSpring(scrollState.scrollPosition, {\n stiffness: 1800,\n damping: 75,\n mass: 0.5,\n });\n scrollState.springScrollPosition = springScrollPosition;\n\n return (\n <GalleryContext.Provider\n value={{\n registerItem,\n removeItem,\n }}\n >\n <div\n ref={containerRef}\n role=\"list\"\n className={styles.scrollableContainer}\n data-qa=\"scrollable-container__overflow-container\"\n {...props}\n >\n <motion.div\n ref={positionContainerRef}\n data-qa=\"scrollable-container__position-container\"\n className={styles.positionContainer}\n style={{ x: isTouch ? scrollState.scrollPosition : springScrollPosition }}\n >\n {children}\n </motion.div>\n </div>\n </GalleryContext.Provider>\n );\n};\n"],"names":[],"mappings":"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"}
|
package/ScrollableItem.js
CHANGED
|
@@ -13,7 +13,7 @@ import '@hh.ru/magritte-internal-custom-scroll';
|
|
|
13
13
|
import './types.js';
|
|
14
14
|
import './utils.js';
|
|
15
15
|
import '@hh.ru/magritte-ui-breakpoint';
|
|
16
|
-
import './scrollableContainer-
|
|
16
|
+
import './scrollableContainer-CQ8v8sxD.js';
|
|
17
17
|
|
|
18
18
|
const ScrollableItem = ({ children, scrollToItemOnFocus, ...props }) => {
|
|
19
19
|
const ref = useRef(null);
|
package/index.css
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
.magritte-night-theme{
|
|
10
10
|
--magritte-gradient-component-chat-input-background-fade-v26-0-2:#1b1b1b00 0%, #1b1b1b 100%;
|
|
11
11
|
}
|
|
12
|
-
.magritte-scroll-wrapper___vTdZl_6-5-
|
|
12
|
+
.magritte-scroll-wrapper___vTdZl_6-5-9{
|
|
13
13
|
position:relative;
|
|
14
14
|
touch-action:pan-y pinch-zoom;
|
|
15
15
|
contain:layout;
|
|
16
16
|
}
|
|
17
|
-
.magritte-scrollable-container___AKIUp_6-5-
|
|
17
|
+
.magritte-scrollable-container___AKIUp_6-5-9{
|
|
18
18
|
display:block;
|
|
19
19
|
isolation:isolate;
|
|
20
20
|
overflow-x:hidden;
|
|
@@ -31,25 +31,25 @@
|
|
|
31
31
|
margin-left:var(--magritte-ui-scrollabe-container-ml, -4px);
|
|
32
32
|
margin-right:var(--magritte-ui-scrollabe-container-mr, -4px);
|
|
33
33
|
}
|
|
34
|
-
.magritte-scroll-buttons-container___ZeI5a_6-5-
|
|
34
|
+
.magritte-scroll-buttons-container___ZeI5a_6-5-9{
|
|
35
35
|
position:relative;
|
|
36
36
|
}
|
|
37
|
-
.magritte-scroll-buttons-container-on-hover___PFUvV_6-5-
|
|
37
|
+
.magritte-scroll-buttons-container-on-hover___PFUvV_6-5-9 .magritte-scroll-button___4GQu3_6-5-9{
|
|
38
38
|
opacity:0;
|
|
39
39
|
pointer-events:none;
|
|
40
40
|
transition:opacity var(--magritte-semantic-animation-time-s-duration-v26-0-2) ease-in-out;
|
|
41
41
|
}
|
|
42
|
-
.magritte-scroll-buttons-container-on-hover___PFUvV_6-5-
|
|
42
|
+
.magritte-scroll-buttons-container-on-hover___PFUvV_6-5-9 .magritte-scroll-button___4GQu3_6-5-9:focus-within{
|
|
43
43
|
opacity:1;
|
|
44
44
|
pointer-events:auto;
|
|
45
45
|
}
|
|
46
46
|
@media (hover: hover){
|
|
47
|
-
.magritte-scroll-buttons-container-on-hover___PFUvV_6-5-
|
|
47
|
+
.magritte-scroll-buttons-container-on-hover___PFUvV_6-5-9:hover .magritte-scroll-button___4GQu3_6-5-9{
|
|
48
48
|
opacity:1;
|
|
49
49
|
pointer-events:auto;
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
|
-
.magritte-scroll-buttons___0SjoR_6-5-
|
|
52
|
+
.magritte-scroll-buttons___0SjoR_6-5-9{
|
|
53
53
|
--fade-left:linear-gradient(90deg, var(--magritte-gradient-component-chat-input-background-fade-v26-0-2));
|
|
54
54
|
--fade-right:linear-gradient(270deg, var(--magritte-gradient-component-chat-input-background-fade-v26-0-2));
|
|
55
55
|
--left-fade-mask:var(--fade-left) 30px 0 / 128px 100% no-repeat, linear-gradient(#000, #000) right / calc(100% - 158px) 100% no-repeat;
|
|
@@ -58,29 +58,29 @@
|
|
|
58
58
|
-webkit-mask:none;
|
|
59
59
|
mask:none;
|
|
60
60
|
}
|
|
61
|
-
.magritte-scroll-buttons-small___xG3MS_6-5-
|
|
61
|
+
.magritte-scroll-buttons-small___xG3MS_6-5-9{
|
|
62
62
|
--left-fade-mask:var(--fade-left) 0px 0 / 44px 100% no-repeat, linear-gradient(#000, #000) right / calc(100% - 44px) 100% no-repeat;
|
|
63
63
|
--right-fade-mask:var(--fade-right) calc(100% - 0px) 0 / 44px 100% no-repeat, linear-gradient(#000, #000) left / calc(100% - 44px) 100% no-repeat;
|
|
64
64
|
--both-sides-mask:var(--fade-left) 0px 0 / 44px 100% no-repeat, var(--fade-right) calc(100% - 0px) 0 / 44px 100% no-repeat, linear-gradient(#000, #000) center / calc(100% - 44px - 44px) 100% no-repeat;
|
|
65
65
|
}
|
|
66
|
-
.magritte-scroll-button___4GQu3_6-5-
|
|
66
|
+
.magritte-scroll-button___4GQu3_6-5-9{
|
|
67
67
|
display:none;
|
|
68
68
|
position:absolute;
|
|
69
69
|
top:50%;
|
|
70
70
|
transform:translateY(-50%);
|
|
71
71
|
}
|
|
72
|
-
.magritte-scroll-button-left___UueEY_6-5-
|
|
72
|
+
.magritte-scroll-button-left___UueEY_6-5-9{
|
|
73
73
|
z-index:1;
|
|
74
74
|
left:var(--magritte-ui-scrollable-container-buttons-offset, 0);
|
|
75
75
|
}
|
|
76
|
-
.magritte-scroll-button-right___wZOn-_6-5-
|
|
76
|
+
.magritte-scroll-button-right___wZOn-_6-5-9{
|
|
77
77
|
right:var(--magritte-ui-scrollable-container-buttons-offset, 0);
|
|
78
78
|
}
|
|
79
|
-
.magritte-position-container___1A4Bv_6-5-
|
|
79
|
+
.magritte-position-container___1A4Bv_6-5-9{
|
|
80
80
|
box-sizing:border-box;
|
|
81
81
|
width:100%;
|
|
82
82
|
}
|
|
83
|
-
.magritte-dot-container___ahtlG_6-5-
|
|
83
|
+
.magritte-dot-container___ahtlG_6-5-9{
|
|
84
84
|
display:flex;
|
|
85
85
|
justify-content:center;
|
|
86
86
|
}
|
package/index.js
CHANGED
|
@@ -13,15 +13,15 @@ import './ScrollableContainerDefaultLayout.js';
|
|
|
13
13
|
import 'motion/react';
|
|
14
14
|
import '@hh.ru/magritte-common-use-translation';
|
|
15
15
|
import '@hh.ru/magritte-ui-icon/icon';
|
|
16
|
-
import './scrollableContainer-
|
|
16
|
+
import './scrollableContainer-CQ8v8sxD.js';
|
|
17
17
|
import './ScrollableContainerDotPages.js';
|
|
18
18
|
import '@hh.ru/magritte-internal-custom-scroll/initScrollHandlers';
|
|
19
19
|
import '@hh.ru/magritte-ui-dot-pages';
|
|
20
|
-
import './types.js';
|
|
21
20
|
import '@hh.ru/magritte-ui-spacing';
|
|
22
21
|
import './ScrollableItem.js';
|
|
23
22
|
import '@hh.ru/magritte-internal-custom-scroll';
|
|
24
23
|
import './useScrollContainer.js';
|
|
25
24
|
import '@hh.ru/magritte-common-func-utils';
|
|
25
|
+
import './types.js';
|
|
26
26
|
import './ObservableValue.js';
|
|
27
27
|
//# sourceMappingURL=index.js.map
|
package/index.mock.js
CHANGED
|
@@ -13,7 +13,7 @@ import 'react/jsx-runtime';
|
|
|
13
13
|
import '@hh.ru/magritte-internal-custom-scroll/initScrollHandlers';
|
|
14
14
|
import './useScrollContainer.js';
|
|
15
15
|
import '@hh.ru/magritte-common-func-utils';
|
|
16
|
-
import './scrollableContainer-
|
|
16
|
+
import './scrollableContainer-CQ8v8sxD.js';
|
|
17
17
|
import './ObservableValue.js';
|
|
18
18
|
|
|
19
19
|
const staticProps = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hh.ru/magritte-ui-scrollable-container",
|
|
3
|
-
"version": "6.5.
|
|
3
|
+
"version": "6.5.9",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"types": "index.d.ts",
|
|
6
6
|
"sideEffects": [
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@hh.ru/magritte-common-func-utils": "1.3.17",
|
|
24
|
-
"@hh.ru/magritte-common-use-translation": "3.
|
|
24
|
+
"@hh.ru/magritte-common-use-translation": "3.8.0",
|
|
25
25
|
"@hh.ru/magritte-design-tokens": "26.0.2",
|
|
26
26
|
"@hh.ru/magritte-internal-custom-scroll": "2.1.3",
|
|
27
27
|
"@hh.ru/magritte-ui-breakpoint": "6.1.3",
|
|
@@ -38,5 +38,5 @@
|
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
|
-
"gitHead": "
|
|
41
|
+
"gitHead": "cf2f64cd7ddcc1cdc569e66eb3eb88e54e1df97a"
|
|
42
42
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import './index.css';
|
|
2
|
+
var styles = {"scroll-wrapper":"magritte-scroll-wrapper___vTdZl_6-5-9","scrollWrapper":"magritte-scroll-wrapper___vTdZl_6-5-9","scrollable-container":"magritte-scrollable-container___AKIUp_6-5-9","scrollableContainer":"magritte-scrollable-container___AKIUp_6-5-9","scroll-buttons-container":"magritte-scroll-buttons-container___ZeI5a_6-5-9","scrollButtonsContainer":"magritte-scroll-buttons-container___ZeI5a_6-5-9","scroll-buttons-container-on-hover":"magritte-scroll-buttons-container-on-hover___PFUvV_6-5-9","scrollButtonsContainerOnHover":"magritte-scroll-buttons-container-on-hover___PFUvV_6-5-9","scroll-button":"magritte-scroll-button___4GQu3_6-5-9","scrollButton":"magritte-scroll-button___4GQu3_6-5-9","scroll-buttons":"magritte-scroll-buttons___0SjoR_6-5-9","scrollButtons":"magritte-scroll-buttons___0SjoR_6-5-9","scroll-buttons-small":"magritte-scroll-buttons-small___xG3MS_6-5-9","scrollButtonsSmall":"magritte-scroll-buttons-small___xG3MS_6-5-9","scroll-button-left":"magritte-scroll-button-left___UueEY_6-5-9","scrollButtonLeft":"magritte-scroll-button-left___UueEY_6-5-9","scroll-button-right":"magritte-scroll-button-right___wZOn-_6-5-9","scrollButtonRight":"magritte-scroll-button-right___wZOn-_6-5-9","position-container":"magritte-position-container___1A4Bv_6-5-9","positionContainer":"magritte-position-container___1A4Bv_6-5-9","dot-container":"magritte-dot-container___ahtlG_6-5-9","dotContainer":"magritte-dot-container___ahtlG_6-5-9"};
|
|
3
|
+
|
|
4
|
+
export { styles as s };
|
|
5
|
+
//# sourceMappingURL=scrollableContainer-CQ8v8sxD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrollableContainer-CQ8v8sxD.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|
package/useScrollContainer.js
CHANGED
|
@@ -5,7 +5,7 @@ import { clamp, initScrollHandlers } from '@hh.ru/magritte-internal-custom-scrol
|
|
|
5
5
|
import { useScrollState } from './ScrollStateProvider.js';
|
|
6
6
|
import { OFFSET_ATTR } from './types.js';
|
|
7
7
|
import { useScrollableControls } from './useScrollableControls.js';
|
|
8
|
-
import { useInitOnce,
|
|
8
|
+
import { useInitOnce, findNearestSnapIndex, animateScrollToPosition, computeTargetPositionForIndex } from './utils.js';
|
|
9
9
|
import 'motion/react';
|
|
10
10
|
import './ObservableValue.js';
|
|
11
11
|
import '@hh.ru/magritte-ui-breakpoint';
|
|
@@ -49,6 +49,7 @@ const useScrollContainer = () => {
|
|
|
49
49
|
(pos, itemRef) => Math.max(pos, itemRef.current?.[OFFSET_ATTR]?.right || 0),
|
|
50
50
|
0
|
|
51
51
|
);
|
|
52
|
+
scrollState.layoutVersion += 1;
|
|
52
53
|
scrollState.viewportWidth.set(viewportWidth);
|
|
53
54
|
scrollState.contentWidth.set(contentWidth);
|
|
54
55
|
scrollState.items.set(items);
|
|
@@ -70,10 +71,8 @@ const useScrollContainer = () => {
|
|
|
70
71
|
if (scrollState.scrollSnapAlign === "none") {
|
|
71
72
|
return;
|
|
72
73
|
}
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
const target = scrollSnapBehavior(scrollState, targetItemIndex);
|
|
76
|
-
animateScrollToPosition(scrollState, target);
|
|
74
|
+
const index = findNearestSnapIndex(scrollState);
|
|
75
|
+
animateScrollToPosition(scrollState, computeTargetPositionForIndex(scrollState, index));
|
|
77
76
|
}, 100)
|
|
78
77
|
);
|
|
79
78
|
useEffect(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useScrollContainer.js","sources":["src/useScrollContainer.ts"],"sourcesContent":["import { useRef, useEffect, type RefObject, useCallback, useState } from 'react';\n\nimport { requestAnimation, debounce } from '@hh.ru/magritte-common-func-utils';\nimport { clamp, type ScrollGestureEvent, initScrollHandlers } from '@hh.ru/magritte-internal-custom-scroll';\nimport { useScrollState } from '@hh.ru/magritte-ui-scrollable-container/ScrollStateProvider';\nimport { type ItemWithOffset, OFFSET_ATTR } from '@hh.ru/magritte-ui-scrollable-container/types';\nimport { useScrollableControls } from '@hh.ru/magritte-ui-scrollable-container/useScrollableControls';\nimport {\n useInitOnce,\n findIndexNearestToSnapPoint,\n animateScrollToPosition,\n computeScrollSnapBehavior,\n} from '@hh.ru/magritte-ui-scrollable-container/utils';\n\nconst EPSILON = 0.5;\nconst MIN_DELTA_FOR_TOUCH = 5;\nconst MAX_OFFSET = { [OFFSET_ATTR]: { left: Number.MAX_SAFE_INTEGER } };\n\n/**\n * Переменные полностью описывающие состояние скролла, хранятся в одном объекте, чтобы не плодить\n * кучу ref'ов и передавать всё состояние в функции одним аргументом, а не длинным списком переменных\n */\nexport interface ScrollInternalState {\n /** контейнер, в котором лежит positionContainer (определяет видимую область) */\n containerRef: RefObject<HTMLDivElement>;\n /** контейнер, с помощью изменения положения которого обеспечивается скролл */\n positionContainerRef: RefObject<HTMLDivElement>;\n /** размер коррекции отступов */\n correction: { leftCorrection: number; rightCorrection: number };\n /* Было ли взаимодействие с компонентом - скролл или прокрутка к элементу */\n wasInteracted: boolean;\n}\n\ninterface UseScrollContainer {\n registerItem: (ref: RefObject<HTMLElement>) => void;\n removeItem: (ref: RefObject<HTMLElement>) => void;\n initScrollHandlers: () => VoidFunction;\n containerRef: RefObject<HTMLDivElement>;\n positionContainerRef: RefObject<HTMLDivElement>;\n isTouch: boolean;\n}\n\nexport const useScrollContainer = (): UseScrollContainer => {\n const scrollState = useScrollState();\n const { scrollToIndex, jumpToIndex } = useScrollableControls();\n const [isTouch, setIsTouch] = useState(false);\n const leftCorrection = scrollState.leftCorrection.get();\n const rightCorrection = scrollState.rightCorrection.get();\n const scrollStateRef = useRef<ScrollInternalState>({\n containerRef: useRef<HTMLDivElement>(null),\n positionContainerRef: useRef<HTMLDivElement>(null),\n correction: { leftCorrection, rightCorrection },\n wasInteracted: false,\n });\n scrollStateRef.current.correction = { leftCorrection, rightCorrection };\n const resizeOberverRef = useRef<ResizeObserver | null>(null);\n\n // recalc positions + report widths into store\n const recalculateItemPositions = useCallback(() => {\n const state = scrollStateRef.current;\n const { containerRef, correction, positionContainerRef } = state;\n const { leftCorrection, rightCorrection } = correction;\n const items = scrollState.items.get();\n if (!containerRef.current || !positionContainerRef.current) {\n return;\n }\n const offsetLeft = positionContainerRef.current?.getBoundingClientRect().left ?? 0;\n items.forEach((itemRef) => {\n if (!itemRef.current) {\n return;\n }\n const rect = itemRef.current.getBoundingClientRect();\n itemRef.current[OFFSET_ATTR] = { left: rect.left - offsetLeft, right: rect.right - offsetLeft };\n });\n items.sort((a, b) => (a.current || MAX_OFFSET)[OFFSET_ATTR].left - (b.current || MAX_OFFSET)[OFFSET_ATTR].left);\n const viewportWidth = containerRef.current.offsetWidth - leftCorrection - rightCorrection;\n const contentWidth = items.reduce(\n (pos, itemRef) => Math.max(pos, itemRef.current?.[OFFSET_ATTR]?.right || 0),\n 0\n );\n scrollState.viewportWidth.set(viewportWidth);\n scrollState.contentWidth.set(contentWidth);\n scrollState.items.set(items);\n if (scrollState.indexForDeferScroll !== -1) {\n if (scrollState.behaviorScrollTypeDeferScroll === 'instant') {\n jumpToIndex(scrollState.indexForDeferScroll);\n } else {\n scrollToIndex(scrollState.indexForDeferScroll);\n }\n scrollState.behaviorScrollTypeDeferScroll = 'animate';\n scrollState.indexForDeferScroll = -1;\n }\n }, [scrollState, scrollToIndex, jumpToIndex]);\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const recalculateItemPositionsDebounced = useCallback(debounce(recalculateItemPositions, 10), [\n recalculateItemPositions,\n ]);\n\n const scrollToItemDebounced = useInitOnce(() =>\n debounce(() => {\n if (scrollState.scrollSnapAlign === 'none') {\n return;\n }\n const targetItemIndex = findIndexNearestToSnapPoint(scrollState);\n const scrollSnapBehavior = computeScrollSnapBehavior(scrollState);\n const target = scrollSnapBehavior(scrollState, targetItemIndex);\n animateScrollToPosition(scrollState, target);\n }, 100)\n );\n\n useEffect(() => {\n const state = scrollStateRef.current;\n const observer = new ResizeObserver(\n requestAnimation(() => {\n recalculateItemPositions();\n })\n );\n\n scrollState.items.get().forEach((item) => item.current && observer.observe(item.current));\n state.containerRef.current && observer.observe(state.containerRef.current);\n resizeOberverRef.current = observer;\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Слушатель нужен, чтобы не вызывать скролл в родительском контейнере (например в bs)\n useEffect(() => {\n const wrapperRef = scrollStateRef.current.containerRef.current;\n if (!wrapperRef) {\n return void 0;\n }\n const handleTouchStartHandler = (event: TouchEvent) => {\n event.stopPropagation();\n };\n wrapperRef.addEventListener('touchstart', handleTouchStartHandler);\n return () => {\n wrapperRef.removeEventListener('touchstart', handleTouchStartHandler);\n };\n }, []);\n\n // события тача\n const didContainerMove = useRef(false);\n const onTouchStart = useCallback(() => {\n setIsTouch(true);\n didContainerMove.current = false;\n scrollState.scrollPosition.stop();\n }, [scrollState]);\n\n const onTouchMove = useCallback(\n (event: ScrollGestureEvent) => {\n if (!didContainerMove.current) {\n // надо отличить тач от свайпа\n didContainerMove.current = Math.abs(event.delta) > MIN_DELTA_FOR_TOUCH;\n }\n scrollState.scrollPosition.set(\n clamp(\n scrollState.scrollPosition.get() + event.delta,\n scrollState.viewportWidth.get() - scrollState.contentWidth.get(),\n 0\n )\n );\n },\n [scrollState]\n );\n\n const onTouchEnd = useCallback(() => {\n if (didContainerMove.current) {\n scrollToItemDebounced();\n }\n }, [scrollToItemDebounced]);\n\n // события мыши\n const didContainerHovered = useRef(false);\n const onMouseEnter = useCallback(() => {\n didContainerHovered.current = true;\n }, []);\n\n const onWheelMove = useCallback(\n (event: ScrollGestureEvent) => {\n setIsTouch(false);\n if (!didContainerHovered.current) {\n return;\n }\n const currentPosition = scrollState.scrollPosition.get();\n const nextPosition = clamp(\n currentPosition + event.delta,\n scrollState.viewportWidth.get() - scrollState.contentWidth.get(),\n 0\n );\n if (Math.abs(nextPosition - currentPosition) > EPSILON) {\n scrollState.scrollPosition.set(nextPosition);\n scrollToItemDebounced();\n event.preventDefault();\n }\n },\n [scrollToItemDebounced, scrollState]\n );\n\n const onMouseLeave = useCallback(() => {\n didContainerHovered.current = false;\n }, []);\n\n const _initScrollHandlers = useCallback(\n () =>\n initScrollHandlers({\n axis: 'horizontal',\n wrapperRef: scrollStateRef.current.containerRef,\n onTouchStart,\n onTouchMove,\n onTouchEnd,\n onMouseEnter,\n onWheelMove,\n onMouseLeave,\n }),\n [onTouchMove, onTouchEnd, onWheelMove, onTouchStart, onMouseEnter, onMouseLeave]\n );\n\n const registerItem = useCallback(\n (ref: RefObject<HTMLElement>) => {\n if (!ref.current) {\n return;\n }\n scrollState.items.get().push(ref as unknown as RefObject<HTMLElement & ItemWithOffset>);\n scrollState.items.set(scrollState.items.get());\n resizeOberverRef.current?.observe(ref.current);\n recalculateItemPositions();\n },\n [recalculateItemPositions, scrollState]\n );\n\n const removeItem = useCallback(\n (ref: RefObject<HTMLElement>) => {\n if (!ref.current) {\n return;\n }\n scrollState.items.set(scrollState.items.get().filter((stored) => stored.current !== ref.current));\n resizeOberverRef.current?.unobserve(ref.current);\n recalculateItemPositionsDebounced();\n },\n [recalculateItemPositionsDebounced, scrollState]\n );\n\n return {\n registerItem,\n removeItem,\n initScrollHandlers: _initScrollHandlers,\n containerRef: scrollStateRef.current.containerRef,\n positionContainerRef: scrollStateRef.current.positionContainerRef,\n isTouch,\n };\n};\n"],"names":["leftCorrection","rightCorrection"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useScrollContainer.js","sources":["src/useScrollContainer.ts"],"sourcesContent":["import { useRef, useEffect, type RefObject, useCallback, useState } from 'react';\n\nimport { requestAnimation, debounce } from '@hh.ru/magritte-common-func-utils';\nimport { clamp, type ScrollGestureEvent, initScrollHandlers } from '@hh.ru/magritte-internal-custom-scroll';\nimport { useScrollState } from '@hh.ru/magritte-ui-scrollable-container/ScrollStateProvider';\nimport { type ItemWithOffset, OFFSET_ATTR } from '@hh.ru/magritte-ui-scrollable-container/types';\nimport { useScrollableControls } from '@hh.ru/magritte-ui-scrollable-container/useScrollableControls';\nimport {\n useInitOnce,\n animateScrollToPosition,\n computeTargetPositionForIndex,\n findNearestSnapIndex,\n} from '@hh.ru/magritte-ui-scrollable-container/utils';\n\nconst EPSILON = 0.5;\nconst MIN_DELTA_FOR_TOUCH = 5;\nconst MAX_OFFSET = { [OFFSET_ATTR]: { left: Number.MAX_SAFE_INTEGER } };\n\n/**\n * Переменные полностью описывающие состояние скролла, хранятся в одном объекте, чтобы не плодить\n * кучу ref'ов и передавать всё состояние в функции одним аргументом, а не длинным списком переменных\n */\nexport interface ScrollInternalState {\n /** контейнер, в котором лежит positionContainer (определяет видимую область) */\n containerRef: RefObject<HTMLDivElement>;\n /** контейнер, с помощью изменения положения которого обеспечивается скролл */\n positionContainerRef: RefObject<HTMLDivElement>;\n /** размер коррекции отступов */\n correction: { leftCorrection: number; rightCorrection: number };\n /* Было ли взаимодействие с компонентом - скролл или прокрутка к элементу */\n wasInteracted: boolean;\n}\n\ninterface UseScrollContainer {\n registerItem: (ref: RefObject<HTMLElement>) => void;\n removeItem: (ref: RefObject<HTMLElement>) => void;\n initScrollHandlers: () => VoidFunction;\n containerRef: RefObject<HTMLDivElement>;\n positionContainerRef: RefObject<HTMLDivElement>;\n isTouch: boolean;\n}\n\nexport const useScrollContainer = (): UseScrollContainer => {\n const scrollState = useScrollState();\n const { scrollToIndex, jumpToIndex } = useScrollableControls();\n const [isTouch, setIsTouch] = useState(false);\n const leftCorrection = scrollState.leftCorrection.get();\n const rightCorrection = scrollState.rightCorrection.get();\n const scrollStateRef = useRef<ScrollInternalState>({\n containerRef: useRef<HTMLDivElement>(null),\n positionContainerRef: useRef<HTMLDivElement>(null),\n correction: { leftCorrection, rightCorrection },\n wasInteracted: false,\n });\n scrollStateRef.current.correction = { leftCorrection, rightCorrection };\n const resizeOberverRef = useRef<ResizeObserver | null>(null);\n\n // recalc positions + report widths into store\n const recalculateItemPositions = useCallback(() => {\n const state = scrollStateRef.current;\n const { containerRef, correction, positionContainerRef } = state;\n const { leftCorrection, rightCorrection } = correction;\n const items = scrollState.items.get();\n if (!containerRef.current || !positionContainerRef.current) {\n return;\n }\n const offsetLeft = positionContainerRef.current?.getBoundingClientRect().left ?? 0;\n items.forEach((itemRef) => {\n if (!itemRef.current) {\n return;\n }\n const rect = itemRef.current.getBoundingClientRect();\n itemRef.current[OFFSET_ATTR] = { left: rect.left - offsetLeft, right: rect.right - offsetLeft };\n });\n items.sort((a, b) => (a.current || MAX_OFFSET)[OFFSET_ATTR].left - (b.current || MAX_OFFSET)[OFFSET_ATTR].left);\n const viewportWidth = containerRef.current.offsetWidth - leftCorrection - rightCorrection;\n const contentWidth = items.reduce(\n (pos, itemRef) => Math.max(pos, itemRef.current?.[OFFSET_ATTR]?.right || 0),\n 0\n );\n scrollState.layoutVersion += 1;\n scrollState.viewportWidth.set(viewportWidth);\n scrollState.contentWidth.set(contentWidth);\n scrollState.items.set(items);\n if (scrollState.indexForDeferScroll !== -1) {\n if (scrollState.behaviorScrollTypeDeferScroll === 'instant') {\n jumpToIndex(scrollState.indexForDeferScroll);\n } else {\n scrollToIndex(scrollState.indexForDeferScroll);\n }\n scrollState.behaviorScrollTypeDeferScroll = 'animate';\n scrollState.indexForDeferScroll = -1;\n }\n }, [scrollState, scrollToIndex, jumpToIndex]);\n\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const recalculateItemPositionsDebounced = useCallback(debounce(recalculateItemPositions, 10), [\n recalculateItemPositions,\n ]);\n\n const scrollToItemDebounced = useInitOnce(() =>\n debounce(() => {\n if (scrollState.scrollSnapAlign === 'none') {\n return;\n }\n const index = findNearestSnapIndex(scrollState);\n animateScrollToPosition(scrollState, computeTargetPositionForIndex(scrollState, index));\n }, 100)\n );\n\n useEffect(() => {\n const state = scrollStateRef.current;\n const observer = new ResizeObserver(\n requestAnimation(() => {\n recalculateItemPositions();\n })\n );\n\n scrollState.items.get().forEach((item) => item.current && observer.observe(item.current));\n state.containerRef.current && observer.observe(state.containerRef.current);\n resizeOberverRef.current = observer;\n return () => observer.disconnect();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Слушатель нужен, чтобы не вызывать скролл в родительском контейнере (например в bs)\n useEffect(() => {\n const wrapperRef = scrollStateRef.current.containerRef.current;\n if (!wrapperRef) {\n return void 0;\n }\n const handleTouchStartHandler = (event: TouchEvent) => {\n event.stopPropagation();\n };\n wrapperRef.addEventListener('touchstart', handleTouchStartHandler);\n return () => {\n wrapperRef.removeEventListener('touchstart', handleTouchStartHandler);\n };\n }, []);\n\n // события тача\n const didContainerMove = useRef(false);\n const onTouchStart = useCallback(() => {\n setIsTouch(true);\n didContainerMove.current = false;\n scrollState.scrollPosition.stop();\n }, [scrollState]);\n\n const onTouchMove = useCallback(\n (event: ScrollGestureEvent) => {\n if (!didContainerMove.current) {\n // надо отличить тач от свайпа\n didContainerMove.current = Math.abs(event.delta) > MIN_DELTA_FOR_TOUCH;\n }\n scrollState.scrollPosition.set(\n clamp(\n scrollState.scrollPosition.get() + event.delta,\n scrollState.viewportWidth.get() - scrollState.contentWidth.get(),\n 0\n )\n );\n },\n [scrollState]\n );\n\n const onTouchEnd = useCallback(() => {\n if (didContainerMove.current) {\n scrollToItemDebounced();\n }\n }, [scrollToItemDebounced]);\n\n // события мыши\n const didContainerHovered = useRef(false);\n const onMouseEnter = useCallback(() => {\n didContainerHovered.current = true;\n }, []);\n\n const onWheelMove = useCallback(\n (event: ScrollGestureEvent) => {\n setIsTouch(false);\n if (!didContainerHovered.current) {\n return;\n }\n const currentPosition = scrollState.scrollPosition.get();\n const nextPosition = clamp(\n currentPosition + event.delta,\n scrollState.viewportWidth.get() - scrollState.contentWidth.get(),\n 0\n );\n if (Math.abs(nextPosition - currentPosition) > EPSILON) {\n scrollState.scrollPosition.set(nextPosition);\n scrollToItemDebounced();\n event.preventDefault();\n }\n },\n [scrollToItemDebounced, scrollState]\n );\n\n const onMouseLeave = useCallback(() => {\n didContainerHovered.current = false;\n }, []);\n\n const _initScrollHandlers = useCallback(\n () =>\n initScrollHandlers({\n axis: 'horizontal',\n wrapperRef: scrollStateRef.current.containerRef,\n onTouchStart,\n onTouchMove,\n onTouchEnd,\n onMouseEnter,\n onWheelMove,\n onMouseLeave,\n }),\n [onTouchMove, onTouchEnd, onWheelMove, onTouchStart, onMouseEnter, onMouseLeave]\n );\n\n const registerItem = useCallback(\n (ref: RefObject<HTMLElement>) => {\n if (!ref.current) {\n return;\n }\n scrollState.items.get().push(ref as unknown as RefObject<HTMLElement & ItemWithOffset>);\n scrollState.items.set(scrollState.items.get());\n resizeOberverRef.current?.observe(ref.current);\n recalculateItemPositions();\n },\n [recalculateItemPositions, scrollState]\n );\n\n const removeItem = useCallback(\n (ref: RefObject<HTMLElement>) => {\n if (!ref.current) {\n return;\n }\n scrollState.items.set(scrollState.items.get().filter((stored) => stored.current !== ref.current));\n resizeOberverRef.current?.unobserve(ref.current);\n recalculateItemPositionsDebounced();\n },\n [recalculateItemPositionsDebounced, scrollState]\n );\n\n return {\n registerItem,\n removeItem,\n initScrollHandlers: _initScrollHandlers,\n containerRef: scrollStateRef.current.containerRef,\n positionContainerRef: scrollStateRef.current.positionContainerRef,\n isTouch,\n };\n};\n"],"names":["leftCorrection","rightCorrection"],"mappings":"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"}
|
package/useScrollableControls.js
CHANGED
|
@@ -2,7 +2,7 @@ import './index.css';
|
|
|
2
2
|
import { useCallback } from 'react';
|
|
3
3
|
import { clamp } from '@hh.ru/magritte-internal-custom-scroll';
|
|
4
4
|
import { useScrollState } from './ScrollStateProvider.js';
|
|
5
|
-
import { animateScrollToPosition,
|
|
5
|
+
import { animateScrollToPosition, computeSnapPositions, findNearestSnapIndex, ensureItemReadyForScroll, computeTargetPositionForIndex } from './utils.js';
|
|
6
6
|
import 'motion/react';
|
|
7
7
|
import './ObservableValue.js';
|
|
8
8
|
import '@hh.ru/magritte-ui-breakpoint';
|
|
@@ -17,14 +17,20 @@ const useScrollableControlsWithState = (scrollState) => {
|
|
|
17
17
|
animateScrollToPosition(scrollState, position);
|
|
18
18
|
return;
|
|
19
19
|
}
|
|
20
|
-
const
|
|
21
|
-
const
|
|
20
|
+
const positions = computeSnapPositions(scrollState);
|
|
21
|
+
const index = findNearestSnapIndex(scrollState);
|
|
22
|
+
const step = toRight ? 1 : -1;
|
|
23
|
+
let nextIndex = index;
|
|
24
|
+
while (nextIndex + step >= 0 && nextIndex + step < positions.length) {
|
|
25
|
+
nextIndex += step;
|
|
26
|
+
if (positions[nextIndex] !== positions[index]) {
|
|
27
|
+
break;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
22
30
|
if (!ensureItemReadyForScroll(scrollState, nextIndex)) {
|
|
23
31
|
return;
|
|
24
32
|
}
|
|
25
|
-
|
|
26
|
-
const target = scrollSnapBehavior(scrollState, nextIndex);
|
|
27
|
-
animateScrollToPosition(scrollState, target);
|
|
33
|
+
animateScrollToPosition(scrollState, positions[nextIndex]);
|
|
28
34
|
},
|
|
29
35
|
[scrollState]
|
|
30
36
|
);
|
|
@@ -33,9 +39,7 @@ const useScrollableControlsWithState = (scrollState) => {
|
|
|
33
39
|
if (!ensureItemReadyForScroll(scrollState, index)) {
|
|
34
40
|
return;
|
|
35
41
|
}
|
|
36
|
-
|
|
37
|
-
const target = scrollSnapBehavior(scrollState, index);
|
|
38
|
-
animateScrollToPosition(scrollState, target);
|
|
42
|
+
animateScrollToPosition(scrollState, computeTargetPositionForIndex(scrollState, index));
|
|
39
43
|
},
|
|
40
44
|
[scrollState]
|
|
41
45
|
);
|
|
@@ -56,8 +60,7 @@ const useScrollableControlsWithState = (scrollState) => {
|
|
|
56
60
|
if (!ensureItemReadyForScroll(scrollState, index, "instant")) {
|
|
57
61
|
return;
|
|
58
62
|
}
|
|
59
|
-
const
|
|
60
|
-
const target = scrollSnapBehavior(scrollState, index);
|
|
63
|
+
const target = computeTargetPositionForIndex(scrollState, index);
|
|
61
64
|
const { scrollPosition, springScrollPosition, viewportWidth, contentWidth } = scrollState;
|
|
62
65
|
if (target === null) {
|
|
63
66
|
return;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useScrollableControls.js","sources":["src/useScrollableControls.tsx"],"sourcesContent":["import { useCallback, type RefObject } from 'react';\n\nimport { clamp } from '@hh.ru/magritte-internal-custom-scroll';\nimport { useScrollState, type ScrollState } from '@hh.ru/magritte-ui-scrollable-container/ScrollStateProvider';\nimport type { ScrollableControls } from '@hh.ru/magritte-ui-scrollable-container/types';\nimport {\n
|
|
1
|
+
{"version":3,"file":"useScrollableControls.js","sources":["src/useScrollableControls.tsx"],"sourcesContent":["import { useCallback, type RefObject } from 'react';\n\nimport { clamp } from '@hh.ru/magritte-internal-custom-scroll';\nimport { useScrollState, type ScrollState } from '@hh.ru/magritte-ui-scrollable-container/ScrollStateProvider';\nimport type { ScrollableControls } from '@hh.ru/magritte-ui-scrollable-container/types';\nimport {\n animateScrollToPosition,\n ensureItemReadyForScroll,\n computeSnapPositions,\n computeTargetPositionForIndex,\n findNearestSnapIndex,\n} from '@hh.ru/magritte-ui-scrollable-container/utils';\n\nexport const useScrollableControlsWithState: (scrollState: ScrollState) => ScrollableControls = (scrollState) => {\n const scrollSide = useCallback(\n (toRight: boolean = false) => {\n if (scrollState.scrollSnapAlign === 'none') {\n const delta = toRight ? -scrollState.viewportWidth.get() : scrollState.viewportWidth.get();\n const position = scrollState.scrollPosition.get() + delta;\n animateScrollToPosition(scrollState, position);\n return;\n }\n const positions = computeSnapPositions(scrollState);\n const index = findNearestSnapIndex(scrollState);\n const step = toRight ? 1 : -1;\n let nextIndex = index;\n // соседние элементы могут делить одну позицию (у края нет места) — ищем ближайшую отличающуюся\n while (nextIndex + step >= 0 && nextIndex + step < positions.length) {\n nextIndex += step;\n if (positions[nextIndex] !== positions[index]) {\n break;\n }\n }\n if (!ensureItemReadyForScroll(scrollState, nextIndex)) {\n return;\n }\n animateScrollToPosition(scrollState, positions[nextIndex]);\n },\n [scrollState]\n );\n\n const scrollToIndex = useCallback(\n (index: number) => {\n if (!ensureItemReadyForScroll(scrollState, index)) {\n return;\n }\n animateScrollToPosition(scrollState, computeTargetPositionForIndex(scrollState, index));\n },\n [scrollState]\n );\n\n const scrollToFirstItem = useCallback(() => scrollToIndex(0), [scrollToIndex]);\n\n const scrollToLastItem = useCallback(\n () => scrollToIndex(scrollState.items.get().length - 1),\n [scrollState, scrollToIndex]\n );\n\n const scrollToItem = useCallback(\n (ref: RefObject<HTMLElement>) => {\n const index = scrollState.items.get().findIndex((item) => item.current && item.current === ref.current);\n index !== -1 && scrollToIndex(index);\n },\n [scrollState, scrollToIndex]\n );\n\n const jumpToIndex = useCallback(\n (index: number) => {\n if (!ensureItemReadyForScroll(scrollState, index, 'instant')) {\n return;\n }\n const target = computeTargetPositionForIndex(scrollState, index);\n const { scrollPosition, springScrollPosition, viewportWidth, contentWidth } = scrollState;\n if (target === null) {\n return;\n }\n const clampedTarget = clamp(target, viewportWidth.get() - contentWidth.get(), 0);\n const isScrollPositionReached = scrollPosition.get() === clampedTarget;\n const isSpringScrollPositionReached = !springScrollPosition || springScrollPosition.get() === clampedTarget;\n if (isScrollPositionReached && isSpringScrollPositionReached) {\n return;\n }\n scrollPosition.jump(clampedTarget);\n springScrollPosition?.jump(clampedTarget);\n },\n [scrollState]\n );\n\n const jumpBy = useCallback(\n (delta: number) => {\n const { scrollPosition, springScrollPosition, viewportWidth, contentWidth } = scrollState;\n const target = clamp(scrollPosition.get() + delta, viewportWidth.get() - contentWidth.get(), 0);\n const clampedDelta = target - scrollPosition.get();\n if (clampedDelta === 0) {\n return;\n }\n scrollPosition.jump(target);\n springScrollPosition?.jump(springScrollPosition.get() + clampedDelta);\n },\n [scrollState]\n );\n\n return {\n scrollLeft: () => scrollSide(),\n scrollRight: () => scrollSide(true),\n scrollToFirstItem,\n scrollToLastItem,\n scrollToIndex,\n scrollToItem,\n jumpToIndex,\n jumpBy,\n };\n};\n\nexport const useScrollableControls: () => ScrollableControls = () => {\n const scrollState = useScrollState();\n const controls = useScrollableControlsWithState(scrollState);\n return controls;\n};\n"],"names":[],"mappings":"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"}
|
package/utils.d.ts
CHANGED
|
@@ -43,35 +43,36 @@ export declare const animateScrollToPosition: (scrollState: ScrollState, positio
|
|
|
43
43
|
*/
|
|
44
44
|
export declare const ensureItemReadyForScroll: (scrollState: ScrollState, index: number, behaviorScrollType?: BehaviorScrollType) => boolean;
|
|
45
45
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
46
|
+
* Список snap-позиций — по одной на элемент, в порядке элементов.
|
|
47
|
+
*
|
|
48
|
+
* Если элемент можно выровнять согласно scrollSnapAlign, берётся честная позиция.
|
|
49
|
+
* У краёв контента честное выравнивание недостижимо (например, центр узкого последнего элемента
|
|
50
|
+
* нельзя подвести к центру вьюпорта) — тогда элемент доводится до полной видимости у ближнего края.
|
|
51
|
+
* Позиции не возрастают по индексу. Совпасть у соседних элементов они могут только когда в диапазоне
|
|
52
|
+
* прокрутки физически нет места для отдельных состояний (контент выступает за вьюпорт меньше,
|
|
53
|
+
* чем на ширину хвостовых элементов).
|
|
54
|
+
*
|
|
55
|
+
* В режиме `none` позиции считаются как для `start` без коррекции на кнопки
|
|
56
|
+
* и используются только индикатором (DotPages).
|
|
57
|
+
*
|
|
58
|
+
* Результат кэшируется на стейте: DotPages вызывает расчёт на каждое изменение scrollPosition,
|
|
59
|
+
* а позиции меняются только вместе с раскладкой. Возвращаемый массив нельзя мутировать.
|
|
60
|
+
*
|
|
61
|
+
* @param state стейт скролла
|
|
62
|
+
* @returns массив позиций
|
|
63
|
+
*/
|
|
64
|
+
export declare const computeSnapPositions: (state: ScrollState) => number[];
|
|
65
|
+
/**
|
|
66
|
+
* Индекс элемента, чья snap-позиция ближе всего к текущей позиции скролла.
|
|
67
|
+
* Если несколько элементов делят позицию у правого края, выбирается последний («доехали до конца»),
|
|
68
|
+
* в остальных случаях — первый из них.
|
|
69
|
+
* @param state стейт скролла
|
|
48
70
|
* @returns индекс найденого элемента
|
|
49
71
|
*/
|
|
50
|
-
export declare const
|
|
72
|
+
export declare const findNearestSnapIndex: (state: ScrollState) => number;
|
|
51
73
|
interface ScrollBehavior {
|
|
52
74
|
(scrollState: ScrollState, index: number): number | null;
|
|
53
75
|
}
|
|
54
|
-
/**
|
|
55
|
-
* Определят позицию, при которой элемент будет находится на левой границе viewport
|
|
56
|
-
* @param scrollState стейт скролла
|
|
57
|
-
* @param index индекс элемента
|
|
58
|
-
* @returns новая позиция
|
|
59
|
-
*/
|
|
60
|
-
export declare const computeStartPositionForIndex: ScrollBehavior;
|
|
61
|
-
/**
|
|
62
|
-
* Определят позицию, при которой элемент будет находится на правой границе viewport
|
|
63
|
-
* @param scrollState стейт скролла
|
|
64
|
-
* @param index индекс элемента
|
|
65
|
-
* @returns новая позиция
|
|
66
|
-
*/
|
|
67
|
-
export declare const computeEndPositionForIndex: ScrollBehavior;
|
|
68
|
-
/**
|
|
69
|
-
* Определят позицию, при которой элемент будет в центре viewport
|
|
70
|
-
* @param scrollState стейт скролла
|
|
71
|
-
* @param index индекс элемента
|
|
72
|
-
* @returns новая позиция
|
|
73
|
-
*/
|
|
74
|
-
export declare const computeCenterPositionForIndex: ScrollBehavior;
|
|
75
76
|
/**
|
|
76
77
|
* Определят позицию, при которой элемент будет находится во viewport (с учетом боковых кнопок в fade)
|
|
77
78
|
* @param scrollState стейт скролла
|
|
@@ -80,9 +81,11 @@ export declare const computeCenterPositionForIndex: ScrollBehavior;
|
|
|
80
81
|
*/
|
|
81
82
|
export declare const computePositionForIndex: ScrollBehavior;
|
|
82
83
|
/**
|
|
83
|
-
*
|
|
84
|
+
* Позиция, к которой скроллить при переходе к элементу по индексу:
|
|
85
|
+
* в режиме `none` — минимальный сдвиг до полной видимости, иначе — snap-позиция элемента
|
|
84
86
|
* @param scrollState стейт скролла
|
|
85
|
-
* @
|
|
87
|
+
* @param index индекс элемента
|
|
88
|
+
* @returns новая позиция, либо null (если элемента нет)
|
|
86
89
|
*/
|
|
87
|
-
export declare const
|
|
90
|
+
export declare const computeTargetPositionForIndex: ScrollBehavior;
|
|
88
91
|
export {};
|
package/utils.js
CHANGED
|
@@ -82,88 +82,75 @@ const ensureItemReadyForScroll = (scrollState, index, behaviorScrollType = "anim
|
|
|
82
82
|
}
|
|
83
83
|
return true;
|
|
84
84
|
};
|
|
85
|
-
const
|
|
86
|
-
let point = 0;
|
|
85
|
+
const getAlignedPosition = (offset, align, viewportWidth, correction) => {
|
|
87
86
|
switch (align) {
|
|
88
87
|
case "center":
|
|
89
|
-
|
|
90
|
-
break;
|
|
88
|
+
return viewportWidth / 2 - (offset.left + offset.right) / 2;
|
|
91
89
|
case "end":
|
|
92
|
-
|
|
93
|
-
break;
|
|
90
|
+
return viewportWidth - offset.right - correction;
|
|
94
91
|
case "start":
|
|
95
92
|
default:
|
|
96
|
-
|
|
93
|
+
return -offset.left + correction;
|
|
97
94
|
}
|
|
98
|
-
return point;
|
|
99
95
|
};
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
default:
|
|
112
|
-
point = scrollLeft;
|
|
96
|
+
const computeSnapPositions = (state) => {
|
|
97
|
+
const { items, viewportWidth, contentWidth, defaultButtonsCorrection, scrollSnapAlign } = state;
|
|
98
|
+
const viewport = viewportWidth.get();
|
|
99
|
+
const minPosition = Math.min(viewport - contentWidth.get(), 0);
|
|
100
|
+
const isSnap = scrollSnapAlign !== "none";
|
|
101
|
+
const align = isSnap ? scrollSnapAlign : "start";
|
|
102
|
+
const correction = isSnap ? defaultButtonsCorrection.get() : 0;
|
|
103
|
+
const itemsCount = items.get().length;
|
|
104
|
+
const cache = state.snapPositionsCache;
|
|
105
|
+
if (cache && cache.layoutVersion === state.layoutVersion && cache.itemsCount === itemsCount && cache.viewportWidth === viewport && cache.minPosition === minPosition && cache.correction === correction && cache.align === align) {
|
|
106
|
+
return cache.positions;
|
|
113
107
|
}
|
|
114
|
-
|
|
108
|
+
let previous = 0;
|
|
109
|
+
const positions = items.get().map((itemRef) => {
|
|
110
|
+
const offset = itemRef.current?.[OFFSET_ATTR];
|
|
111
|
+
if (!offset) {
|
|
112
|
+
return previous;
|
|
113
|
+
}
|
|
114
|
+
let position = getAlignedPosition(offset, align, viewport, correction);
|
|
115
|
+
if (position > 0) {
|
|
116
|
+
position = getAlignedPosition(offset, "start", viewport, correction);
|
|
117
|
+
} else if (position < minPosition) {
|
|
118
|
+
position = getAlignedPosition(offset, "end", viewport, correction);
|
|
119
|
+
}
|
|
120
|
+
previous = Math.min(clamp(position, minPosition, 0), previous);
|
|
121
|
+
return previous;
|
|
122
|
+
});
|
|
123
|
+
state.snapPositionsCache = {
|
|
124
|
+
layoutVersion: state.layoutVersion,
|
|
125
|
+
itemsCount,
|
|
126
|
+
viewportWidth: viewport,
|
|
127
|
+
minPosition,
|
|
128
|
+
correction,
|
|
129
|
+
align,
|
|
130
|
+
positions
|
|
131
|
+
};
|
|
132
|
+
return positions;
|
|
115
133
|
};
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
if (
|
|
134
|
+
const findNearestSnapIndex = (state) => {
|
|
135
|
+
const positions = computeSnapPositions(state);
|
|
136
|
+
if (positions.length === 0) {
|
|
119
137
|
return 0;
|
|
120
138
|
}
|
|
121
|
-
const
|
|
122
|
-
let
|
|
123
|
-
let
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
if (
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const dist = Math.abs(targetPoint - anchor);
|
|
130
|
-
if (dist < minDist) {
|
|
131
|
-
minDist = dist;
|
|
132
|
-
idx = key;
|
|
133
|
-
}
|
|
139
|
+
const current = state.scrollPosition.get();
|
|
140
|
+
let index = 0;
|
|
141
|
+
let minDistance = Infinity;
|
|
142
|
+
positions.forEach((position, i) => {
|
|
143
|
+
const distance = Math.abs(position - current);
|
|
144
|
+
if (distance < minDistance) {
|
|
145
|
+
minDistance = distance;
|
|
146
|
+
index = i;
|
|
134
147
|
}
|
|
148
|
+
});
|
|
149
|
+
const lastPosition = positions[positions.length - 1];
|
|
150
|
+
if (lastPosition < 0 && positions[index] === lastPosition) {
|
|
151
|
+
return positions.length - 1;
|
|
135
152
|
}
|
|
136
|
-
return
|
|
137
|
-
};
|
|
138
|
-
const computeStartPositionForIndex = (scrollState, index) => {
|
|
139
|
-
const { items, defaultButtonsCorrection } = scrollState;
|
|
140
|
-
const itemRef = items.get()[index];
|
|
141
|
-
if (!itemRef?.current) {
|
|
142
|
-
return null;
|
|
143
|
-
}
|
|
144
|
-
const offset = itemRef.current[OFFSET_ATTR];
|
|
145
|
-
return -offset.left + defaultButtonsCorrection.get();
|
|
146
|
-
};
|
|
147
|
-
const computeEndPositionForIndex = (scrollState, index) => {
|
|
148
|
-
const { items, defaultButtonsCorrection, viewportWidth } = scrollState;
|
|
149
|
-
const itemRef = items.get()[index];
|
|
150
|
-
if (!itemRef?.current) {
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
const offset = itemRef.current[OFFSET_ATTR];
|
|
154
|
-
return -offset.right + viewportWidth.get() - defaultButtonsCorrection.get();
|
|
155
|
-
};
|
|
156
|
-
const computeCenterPositionForIndex = (scrollState, index) => {
|
|
157
|
-
const { items, viewportWidth } = scrollState;
|
|
158
|
-
const itemRef = items.get()[index];
|
|
159
|
-
if (!itemRef?.current) {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
const offset = itemRef.current[OFFSET_ATTR];
|
|
163
|
-
const itemWidth = offset.right - offset.left;
|
|
164
|
-
const itemCenter = offset.left + itemWidth / 2;
|
|
165
|
-
const viewportCenter = viewportWidth.get() / 2;
|
|
166
|
-
return viewportCenter - itemCenter;
|
|
153
|
+
return index;
|
|
167
154
|
};
|
|
168
155
|
const computePositionForIndex = (scrollState, index) => {
|
|
169
156
|
const { items, viewportWidth, scrollPosition, defaultButtonsCorrection } = scrollState;
|
|
@@ -182,19 +169,12 @@ const computePositionForIndex = (scrollState, index) => {
|
|
|
182
169
|
}
|
|
183
170
|
return viewportWidth.get() - offset.right - defaultButtonsCorrection.get();
|
|
184
171
|
};
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return computeCenterPositionForIndex;
|
|
189
|
-
case "start":
|
|
190
|
-
return computeStartPositionForIndex;
|
|
191
|
-
case "end":
|
|
192
|
-
return computeEndPositionForIndex;
|
|
193
|
-
case "none":
|
|
194
|
-
default:
|
|
195
|
-
return computePositionForIndex;
|
|
172
|
+
const computeTargetPositionForIndex = (scrollState, index) => {
|
|
173
|
+
if (scrollState.scrollSnapAlign === "none") {
|
|
174
|
+
return computePositionForIndex(scrollState, index);
|
|
196
175
|
}
|
|
176
|
+
return computeSnapPositions(scrollState)[index] ?? null;
|
|
197
177
|
};
|
|
198
178
|
|
|
199
|
-
export { BREAKPOINTS_LIST, NO_BUTTONS_START_FROM_BREAKPOINT, animateScrollToPosition,
|
|
179
|
+
export { BREAKPOINTS_LIST, NO_BUTTONS_START_FROM_BREAKPOINT, animateScrollToPosition, computePositionForIndex, computeSnapPositions, computeTargetPositionForIndex, debounce, ensureItemReadyForScroll, findNearestSnapIndex, getActualCorrection, getDisableState, getItemOffset, useInitOnce };
|
|
200
180
|
//# sourceMappingURL=utils.js.map
|
package/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sources":["src/utils.ts"],"sourcesContent":["import { useRef } from 'react';\nimport { animate } from 'motion/react';\n\nimport { clamp } from '@hh.ru/magritte-internal-custom-scroll';\nimport { Breakpoint } from '@hh.ru/magritte-ui-breakpoint';\nimport { type ScrollState, type BehaviorScrollType } from '@hh.ru/magritte-ui-scrollable-container/ScrollStateProvider';\nimport {\n type SpacingCorrectionProps,\n type ScrollDisableState,\n type ScrollSnapAlign,\n OFFSET_ATTR,\n type ItemOffset,\n} from '@hh.ru/magritte-ui-scrollable-container/types';\n\nexport const BREAKPOINTS_LIST = [\n Breakpoint.XS,\n Breakpoint.S,\n Breakpoint.M,\n Breakpoint.L,\n Breakpoint.XL,\n Breakpoint.XXL,\n];\nexport const NO_BUTTONS_START_FROM_BREAKPOINT = Breakpoint.S;\n\nexport function debounce<T extends unknown[]>(callback: (...args: T) => void, waitMs: number): typeof callback {\n let timeout = 0;\n return (...args: T) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => {\n callback(...args);\n }, waitMs) as unknown as number;\n };\n}\n\ninterface ActualCorrection {\n leftCorrection: number;\n rightCorrection: number;\n}\n\n/**\n * По переданным параметрам коррекции возвращает значения отступов для текущего брейкпоинта\n * @param props Параметры коррекции\n * @param breakpoint Текуший брейкпоинт\n * @returns Объект содержащий значения отступов\n */\nexport const getActualCorrection = (props: SpacingCorrectionProps, breakpoint: Breakpoint): ActualCorrection => {\n const breakpointIndex = BREAKPOINTS_LIST.indexOf(breakpoint);\n let leftCorrection: number | undefined;\n let rightCorrection: number | undefined;\n // Проходимся по всем брейкпоинтам, начиная с текущего в сторону меньших разрешений\n for (let i = breakpointIndex; i >= 0; i--) {\n const breakpointName = BREAKPOINTS_LIST[i].toUpperCase();\n // Ключи, которые могут быть в параметрах коррекции (SpacingCorrectionProps)\n const leftCorrectionKey = `correctionLeft${breakpointName}` as keyof SpacingCorrectionProps;\n const rightCorrectionKey = `correctionRight${breakpointName}` as keyof SpacingCorrectionProps;\n const commonCorrectionKey = `correction${breakpointName}` as keyof SpacingCorrectionProps;\n // Если для текущего брейкпоинта есть параметры коррекции для левой/правой стороны, то берем их\n props[leftCorrectionKey] !== undefined &&\n leftCorrection === undefined &&\n (leftCorrection = props[leftCorrectionKey]);\n props[rightCorrectionKey] !== undefined &&\n rightCorrection === undefined &&\n (rightCorrection = props[rightCorrectionKey]);\n\n // Если для левой или правой стороны не найден параметр коррекции, то пробуем взять параметр correction для\n // брейкпоинта который сейчас обрабатываем в цикле, например correctionM\n if (props[commonCorrectionKey] !== undefined) {\n leftCorrection === undefined && (leftCorrection = props[commonCorrectionKey]);\n rightCorrection === undefined && (rightCorrection = props[commonCorrectionKey]);\n }\n\n // Если нашли значения для обеих сторон - возращаем\n if (leftCorrection !== undefined && rightCorrection !== undefined) {\n return { leftCorrection, rightCorrection };\n }\n }\n // Если не нашли значение для какой то из сторон, то берем значение из correction, без уточнения брейкпоинта\n return {\n leftCorrection: leftCorrection === undefined ? props.correction || 0 : leftCorrection,\n rightCorrection: rightCorrection === undefined ? props.correction || 0 : rightCorrection,\n };\n};\n\nexport const useInitOnce = <T>(func: () => T): T => {\n const initializedRef = useRef<T | undefined>(undefined);\n if (initializedRef.current === undefined) {\n initializedRef.current = func();\n }\n return initializedRef.current;\n};\n\n/**\n * Отдает метрики элемента галереи\n * @param scrollState стейт скролла\n * @param index индекс элемента\n * @returns метрики элемента, либо null (если элемента нет или его размеры еще не посчитаны)\n */\nexport const getItemOffset = (scrollState: ScrollState, index: number): ItemOffset | null =>\n scrollState.items.get()[index]?.current?.[OFFSET_ATTR] ?? null;\n\n/**\n * На основе стейта расчитывает флаги скрола\n * @param scrollState стейт скролла\n * @returns cостояние блокировки скролла\n */\nexport const getDisableState = (scrollState: ScrollState): ScrollDisableState => {\n const { scrollPosition, viewportWidth, contentWidth } = scrollState;\n return {\n isScrollLeftDisabled: scrollPosition.get() >= 0,\n isScrollRightDisabled: Math.abs(scrollPosition.get()) + viewportWidth.get() >= contentWidth.get(),\n };\n};\n\n/**\n * Анимирует scrollPosition с учетом границ контенера\n * @param scrollState стейт скролла\n * @param position новая позиция\n * @returns void\n */\nexport const animateScrollToPosition = (scrollState: ScrollState, position: number | null): void => {\n const { scrollPosition, viewportWidth, contentWidth } = scrollState;\n if (position === null || scrollPosition.get() === position) {\n return;\n }\n const clampedPosition = clamp(position, viewportWidth.get() - contentWidth.get(), 0);\n animate(scrollPosition, clampedPosition, {\n velocity: Math.max(scrollPosition.getVelocity(), 1),\n });\n};\n\n/**\n * scrollTo* может быть вызвана раньше чем появяться метрики элементов\n * например, scrollToIndex функция может вызвана в useEffect в tabs\n * запоминаем индекс и вызовем скролл, когда метрики будут посчитаны\n */\nexport const ensureItemReadyForScroll = (\n scrollState: ScrollState,\n index: number,\n behaviorScrollType: BehaviorScrollType = 'animate'\n): boolean => {\n const { items } = scrollState;\n const item = items.get()[index]?.current;\n if (!item || !item[OFFSET_ATTR]) {\n scrollState.indexForDeferScroll = index;\n scrollState.behaviorScrollTypeDeferScroll = behaviorScrollType;\n return false;\n }\n return true;\n};\n\n/**\n * Возвращает якорную точку отрезка в зависимости от scrollSnapAlign.\n *\n * start — левая граница элемента\n * center — центр элемента\n * end — правая граница элемента\n *\n * @param left левая граница элемента\n * @param right правая граница элемента\n * @param align режим выравнивания (scroll-snap-align)\n * @returns координата якорной точки элемента\n */\nconst getAnchorPoint = (left: number, right: number, align: ScrollSnapAlign): number => {\n let point = 0;\n switch (align) {\n case 'center':\n point = (left + right) / 2;\n break;\n case 'end':\n point = right;\n break;\n case 'start':\n default:\n point = left;\n }\n return point;\n};\n\n/**\n * Вычисляет целевую точку прокрутки в зависимости от режима scroll-snap выравнивания.\n *\n * В качестве базовой позиции используется текущее значение scrollLeft,\n * к которому добавляется смещение, зависящее от `scrollSnapAlign`:\n * - start — начало области просмотра\n * - center — центр области просмотра\n * - end — конец области просмотра\n *\n * @param state Текущее состояние скролл\n * @returns Координата целевой точки прокрутки\n */\nconst getTargetPoint = (state: ScrollState): number => {\n const scrollLeft = Math.abs(state.scrollPosition.get());\n let point = 0;\n switch (state.scrollSnapAlign) {\n case 'center':\n point = scrollLeft + state.viewportWidth.get() / 2;\n break;\n case 'end':\n point = scrollLeft + state.viewportWidth.get();\n break;\n case 'start':\n default:\n point = scrollLeft;\n }\n return point;\n};\n\n/**\n * На основе позиции, находит ближайший элемент к центру viewport\n * @param scrollState стейт скролла\n * @returns индекс найденого элемента\n */\nexport const findIndexNearestToSnapPoint = (state: ScrollState): number => {\n const items = state.items.get();\n if (items.length === 0) {\n return 0;\n }\n const targetPoint = getTargetPoint(state);\n let idx = 0;\n let minDist = Infinity;\n for (let key = 0; key < items.length; key++) {\n const element = items[key]?.current;\n if (element && element[OFFSET_ATTR]) {\n const { left, right } = element[OFFSET_ATTR];\n const anchor = getAnchorPoint(left, right, state.scrollSnapAlign);\n const dist = Math.abs(targetPoint - anchor);\n if (dist < minDist) {\n minDist = dist;\n idx = key;\n }\n }\n }\n return idx;\n};\n\ninterface ScrollBehavior {\n (scrollState: ScrollState, index: number): number | null;\n}\n\n/**\n * Определят позицию, при которой элемент будет находится на левой границе viewport\n * @param scrollState стейт скролла\n * @param index индекс элемента\n * @returns новая позиция\n */\nexport const computeStartPositionForIndex: ScrollBehavior = (scrollState, index) => {\n const { items, defaultButtonsCorrection } = scrollState;\n const itemRef = items.get()[index];\n if (!itemRef?.current) {\n return null;\n }\n const offset = itemRef.current[OFFSET_ATTR];\n return -offset.left + defaultButtonsCorrection.get();\n};\n\n/**\n * Определят позицию, при которой элемент будет находится на правой границе viewport\n * @param scrollState стейт скролла\n * @param index индекс элемента\n * @returns новая позиция\n */\nexport const computeEndPositionForIndex: ScrollBehavior = (scrollState, index) => {\n const { items, defaultButtonsCorrection, viewportWidth } = scrollState;\n const itemRef = items.get()[index];\n if (!itemRef?.current) {\n return null;\n }\n const offset = itemRef.current[OFFSET_ATTR];\n return -offset.right + viewportWidth.get() - defaultButtonsCorrection.get();\n};\n\n/**\n * Определят позицию, при которой элемент будет в центре viewport\n * @param scrollState стейт скролла\n * @param index индекс элемента\n * @returns новая позиция\n */\nexport const computeCenterPositionForIndex: ScrollBehavior = (scrollState, index) => {\n const { items, viewportWidth } = scrollState;\n const itemRef = items.get()[index];\n if (!itemRef?.current) {\n return null;\n }\n const offset = itemRef.current[OFFSET_ATTR];\n const itemWidth = offset.right - offset.left;\n const itemCenter = offset.left + itemWidth / 2;\n const viewportCenter = viewportWidth.get() / 2;\n return viewportCenter - itemCenter;\n};\n\n/**\n * Определят позицию, при которой элемент будет находится во viewport (с учетом боковых кнопок в fade)\n * @param scrollState стейт скролла\n * @param index индекс элемента\n * @returns новая позиция\n */\nexport const computePositionForIndex: ScrollBehavior = (scrollState, index) => {\n const { items, viewportWidth, scrollPosition, defaultButtonsCorrection } = scrollState;\n const itemRef = items.get()[index];\n if (!itemRef?.current) {\n return null;\n }\n const offset = itemRef.current[OFFSET_ATTR];\n const viewportLeft = -scrollPosition.get() + defaultButtonsCorrection.get();\n const viewportRight = viewportLeft + viewportWidth.get() - defaultButtonsCorrection.get();\n if (offset.left >= viewportLeft && offset.right <= viewportRight) {\n return scrollPosition.get();\n }\n if (offset.left < viewportLeft) {\n return -(offset.left - defaultButtonsCorrection.get());\n }\n return viewportWidth.get() - offset.right - defaultButtonsCorrection.get();\n};\n\n/**\n * Определят поведение \"подскрола\" ближайшего элемента\n * @param scrollState стейт скролла\n * @returns StrategyPosition\n */\nexport const computeScrollSnapBehavior = (scrollState: ScrollState): ScrollBehavior => {\n switch (scrollState.scrollSnapAlign) {\n case 'center':\n return computeCenterPositionForIndex;\n case 'start':\n return computeStartPositionForIndex;\n case 'end':\n return computeEndPositionForIndex;\n case 'none':\n default:\n return computePositionForIndex;\n }\n};\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"utils.js","sources":["src/utils.ts"],"sourcesContent":["import { useRef } from 'react';\nimport { animate } from 'motion/react';\n\nimport { clamp } from '@hh.ru/magritte-internal-custom-scroll';\nimport { Breakpoint } from '@hh.ru/magritte-ui-breakpoint';\nimport { type ScrollState, type BehaviorScrollType } from '@hh.ru/magritte-ui-scrollable-container/ScrollStateProvider';\nimport {\n type SpacingCorrectionProps,\n type ScrollDisableState,\n type ScrollSnapAlign,\n OFFSET_ATTR,\n type ItemOffset,\n} from '@hh.ru/magritte-ui-scrollable-container/types';\n\nexport const BREAKPOINTS_LIST = [\n Breakpoint.XS,\n Breakpoint.S,\n Breakpoint.M,\n Breakpoint.L,\n Breakpoint.XL,\n Breakpoint.XXL,\n];\nexport const NO_BUTTONS_START_FROM_BREAKPOINT = Breakpoint.S;\n\nexport function debounce<T extends unknown[]>(callback: (...args: T) => void, waitMs: number): typeof callback {\n let timeout = 0;\n return (...args: T) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => {\n callback(...args);\n }, waitMs) as unknown as number;\n };\n}\n\ninterface ActualCorrection {\n leftCorrection: number;\n rightCorrection: number;\n}\n\n/**\n * По переданным параметрам коррекции возвращает значения отступов для текущего брейкпоинта\n * @param props Параметры коррекции\n * @param breakpoint Текуший брейкпоинт\n * @returns Объект содержащий значения отступов\n */\nexport const getActualCorrection = (props: SpacingCorrectionProps, breakpoint: Breakpoint): ActualCorrection => {\n const breakpointIndex = BREAKPOINTS_LIST.indexOf(breakpoint);\n let leftCorrection: number | undefined;\n let rightCorrection: number | undefined;\n // Проходимся по всем брейкпоинтам, начиная с текущего в сторону меньших разрешений\n for (let i = breakpointIndex; i >= 0; i--) {\n const breakpointName = BREAKPOINTS_LIST[i].toUpperCase();\n // Ключи, которые могут быть в параметрах коррекции (SpacingCorrectionProps)\n const leftCorrectionKey = `correctionLeft${breakpointName}` as keyof SpacingCorrectionProps;\n const rightCorrectionKey = `correctionRight${breakpointName}` as keyof SpacingCorrectionProps;\n const commonCorrectionKey = `correction${breakpointName}` as keyof SpacingCorrectionProps;\n // Если для текущего брейкпоинта есть параметры коррекции для левой/правой стороны, то берем их\n props[leftCorrectionKey] !== undefined &&\n leftCorrection === undefined &&\n (leftCorrection = props[leftCorrectionKey]);\n props[rightCorrectionKey] !== undefined &&\n rightCorrection === undefined &&\n (rightCorrection = props[rightCorrectionKey]);\n\n // Если для левой или правой стороны не найден параметр коррекции, то пробуем взять параметр correction для\n // брейкпоинта который сейчас обрабатываем в цикле, например correctionM\n if (props[commonCorrectionKey] !== undefined) {\n leftCorrection === undefined && (leftCorrection = props[commonCorrectionKey]);\n rightCorrection === undefined && (rightCorrection = props[commonCorrectionKey]);\n }\n\n // Если нашли значения для обеих сторон - возращаем\n if (leftCorrection !== undefined && rightCorrection !== undefined) {\n return { leftCorrection, rightCorrection };\n }\n }\n // Если не нашли значение для какой то из сторон, то берем значение из correction, без уточнения брейкпоинта\n return {\n leftCorrection: leftCorrection === undefined ? props.correction || 0 : leftCorrection,\n rightCorrection: rightCorrection === undefined ? props.correction || 0 : rightCorrection,\n };\n};\n\nexport const useInitOnce = <T>(func: () => T): T => {\n const initializedRef = useRef<T | undefined>(undefined);\n if (initializedRef.current === undefined) {\n initializedRef.current = func();\n }\n return initializedRef.current;\n};\n\n/**\n * Отдает метрики элемента галереи\n * @param scrollState стейт скролла\n * @param index индекс элемента\n * @returns метрики элемента, либо null (если элемента нет или его размеры еще не посчитаны)\n */\nexport const getItemOffset = (scrollState: ScrollState, index: number): ItemOffset | null =>\n scrollState.items.get()[index]?.current?.[OFFSET_ATTR] ?? null;\n\n/**\n * На основе стейта расчитывает флаги скрола\n * @param scrollState стейт скролла\n * @returns cостояние блокировки скролла\n */\nexport const getDisableState = (scrollState: ScrollState): ScrollDisableState => {\n const { scrollPosition, viewportWidth, contentWidth } = scrollState;\n return {\n isScrollLeftDisabled: scrollPosition.get() >= 0,\n isScrollRightDisabled: Math.abs(scrollPosition.get()) + viewportWidth.get() >= contentWidth.get(),\n };\n};\n\n/**\n * Анимирует scrollPosition с учетом границ контенера\n * @param scrollState стейт скролла\n * @param position новая позиция\n * @returns void\n */\nexport const animateScrollToPosition = (scrollState: ScrollState, position: number | null): void => {\n const { scrollPosition, viewportWidth, contentWidth } = scrollState;\n if (position === null || scrollPosition.get() === position) {\n return;\n }\n const clampedPosition = clamp(position, viewportWidth.get() - contentWidth.get(), 0);\n animate(scrollPosition, clampedPosition, {\n velocity: Math.max(scrollPosition.getVelocity(), 1),\n });\n};\n\n/**\n * scrollTo* может быть вызвана раньше чем появяться метрики элементов\n * например, scrollToIndex функция может вызвана в useEffect в tabs\n * запоминаем индекс и вызовем скролл, когда метрики будут посчитаны\n */\nexport const ensureItemReadyForScroll = (\n scrollState: ScrollState,\n index: number,\n behaviorScrollType: BehaviorScrollType = 'animate'\n): boolean => {\n const { items } = scrollState;\n const item = items.get()[index]?.current;\n if (!item || !item[OFFSET_ATTR]) {\n scrollState.indexForDeferScroll = index;\n scrollState.behaviorScrollTypeDeferScroll = behaviorScrollType;\n return false;\n }\n return true;\n};\n\n/**\n * Позиция скролла, при которой элемент выровнен во вьюпорте согласно align\n * @param offset метрики элемента\n * @param align режим выравнивания\n * @param viewportWidth ширина вьюпорта\n * @param correction коррекция на кнопки по краям\n * @returns позиция скролла (0 — начало контента, отрицательные значения — прокрутка вправо)\n */\nconst getAlignedPosition = (\n offset: ItemOffset,\n align: ScrollSnapAlign,\n viewportWidth: number,\n correction: number\n): number => {\n switch (align) {\n case 'center':\n return viewportWidth / 2 - (offset.left + offset.right) / 2;\n case 'end':\n return viewportWidth - offset.right - correction;\n case 'start':\n default:\n return -offset.left + correction;\n }\n};\n\n/**\n * Список snap-позиций — по одной на элемент, в порядке элементов.\n *\n * Если элемент можно выровнять согласно scrollSnapAlign, берётся честная позиция.\n * У краёв контента честное выравнивание недостижимо (например, центр узкого последнего элемента\n * нельзя подвести к центру вьюпорта) — тогда элемент доводится до полной видимости у ближнего края.\n * Позиции не возрастают по индексу. Совпасть у соседних элементов они могут только когда в диапазоне\n * прокрутки физически нет места для отдельных состояний (контент выступает за вьюпорт меньше,\n * чем на ширину хвостовых элементов).\n *\n * В режиме `none` позиции считаются как для `start` без коррекции на кнопки\n * и используются только индикатором (DotPages).\n *\n * Результат кэшируется на стейте: DotPages вызывает расчёт на каждое изменение scrollPosition,\n * а позиции меняются только вместе с раскладкой. Возвращаемый массив нельзя мутировать.\n *\n * @param state стейт скролла\n * @returns массив позиций\n */\nexport const computeSnapPositions = (state: ScrollState): number[] => {\n const { items, viewportWidth, contentWidth, defaultButtonsCorrection, scrollSnapAlign } = state;\n const viewport = viewportWidth.get();\n const minPosition = Math.min(viewport - contentWidth.get(), 0);\n const isSnap = scrollSnapAlign !== 'none';\n const align = isSnap ? scrollSnapAlign : 'start';\n const correction = isSnap ? defaultButtonsCorrection.get() : 0;\n const itemsCount = items.get().length;\n // метрики элементов переписываются только в recalculateItemPositions (layoutVersion),\n // остальные входы — размеры, коррекция и режим — сравниваются явно\n const cache = state.snapPositionsCache;\n if (\n cache &&\n cache.layoutVersion === state.layoutVersion &&\n cache.itemsCount === itemsCount &&\n cache.viewportWidth === viewport &&\n cache.minPosition === minPosition &&\n cache.correction === correction &&\n cache.align === align\n ) {\n return cache.positions;\n }\n let previous = 0;\n const positions = items.get().map((itemRef) => {\n const offset = itemRef.current?.[OFFSET_ATTR];\n if (!offset) {\n return previous;\n }\n let position = getAlignedPosition(offset, align, viewport, correction);\n if (position > 0) {\n position = getAlignedPosition(offset, 'start', viewport, correction);\n } else if (position < minPosition) {\n position = getAlignedPosition(offset, 'end', viewport, correction);\n }\n previous = Math.min(clamp(position, minPosition, 0), previous);\n return previous;\n });\n state.snapPositionsCache = {\n layoutVersion: state.layoutVersion,\n itemsCount,\n viewportWidth: viewport,\n minPosition,\n correction,\n align,\n positions,\n };\n return positions;\n};\n\n/**\n * Индекс элемента, чья snap-позиция ближе всего к текущей позиции скролла.\n * Если несколько элементов делят позицию у правого края, выбирается последний («доехали до конца»),\n * в остальных случаях — первый из них.\n * @param state стейт скролла\n * @returns индекс найденого элемента\n */\nexport const findNearestSnapIndex = (state: ScrollState): number => {\n const positions = computeSnapPositions(state);\n if (positions.length === 0) {\n return 0;\n }\n const current = state.scrollPosition.get();\n let index = 0;\n let minDistance = Infinity;\n positions.forEach((position, i) => {\n const distance = Math.abs(position - current);\n if (distance < minDistance) {\n minDistance = distance;\n index = i;\n }\n });\n const lastPosition = positions[positions.length - 1];\n if (lastPosition < 0 && positions[index] === lastPosition) {\n return positions.length - 1;\n }\n return index;\n};\n\ninterface ScrollBehavior {\n (scrollState: ScrollState, index: number): number | null;\n}\n\n/**\n * Определят позицию, при которой элемент будет находится во viewport (с учетом боковых кнопок в fade)\n * @param scrollState стейт скролла\n * @param index индекс элемента\n * @returns новая позиция\n */\nexport const computePositionForIndex: ScrollBehavior = (scrollState, index) => {\n const { items, viewportWidth, scrollPosition, defaultButtonsCorrection } = scrollState;\n const itemRef = items.get()[index];\n if (!itemRef?.current) {\n return null;\n }\n const offset = itemRef.current[OFFSET_ATTR];\n const viewportLeft = -scrollPosition.get() + defaultButtonsCorrection.get();\n const viewportRight = viewportLeft + viewportWidth.get() - defaultButtonsCorrection.get();\n if (offset.left >= viewportLeft && offset.right <= viewportRight) {\n return scrollPosition.get();\n }\n if (offset.left < viewportLeft) {\n return -(offset.left - defaultButtonsCorrection.get());\n }\n return viewportWidth.get() - offset.right - defaultButtonsCorrection.get();\n};\n\n/**\n * Позиция, к которой скроллить при переходе к элементу по индексу:\n * в режиме `none` — минимальный сдвиг до полной видимости, иначе — snap-позиция элемента\n * @param scrollState стейт скролла\n * @param index индекс элемента\n * @returns новая позиция, либо null (если элемента нет)\n */\nexport const computeTargetPositionForIndex: ScrollBehavior = (scrollState, index) => {\n if (scrollState.scrollSnapAlign === 'none') {\n return computePositionForIndex(scrollState, index);\n }\n return computeSnapPositions(scrollState)[index] ?? null;\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
import './index.css';
|
|
2
|
-
var styles = {"scroll-wrapper":"magritte-scroll-wrapper___vTdZl_6-5-7","scrollWrapper":"magritte-scroll-wrapper___vTdZl_6-5-7","scrollable-container":"magritte-scrollable-container___AKIUp_6-5-7","scrollableContainer":"magritte-scrollable-container___AKIUp_6-5-7","scroll-buttons-container":"magritte-scroll-buttons-container___ZeI5a_6-5-7","scrollButtonsContainer":"magritte-scroll-buttons-container___ZeI5a_6-5-7","scroll-buttons-container-on-hover":"magritte-scroll-buttons-container-on-hover___PFUvV_6-5-7","scrollButtonsContainerOnHover":"magritte-scroll-buttons-container-on-hover___PFUvV_6-5-7","scroll-button":"magritte-scroll-button___4GQu3_6-5-7","scrollButton":"magritte-scroll-button___4GQu3_6-5-7","scroll-buttons":"magritte-scroll-buttons___0SjoR_6-5-7","scrollButtons":"magritte-scroll-buttons___0SjoR_6-5-7","scroll-buttons-small":"magritte-scroll-buttons-small___xG3MS_6-5-7","scrollButtonsSmall":"magritte-scroll-buttons-small___xG3MS_6-5-7","scroll-button-left":"magritte-scroll-button-left___UueEY_6-5-7","scrollButtonLeft":"magritte-scroll-button-left___UueEY_6-5-7","scroll-button-right":"magritte-scroll-button-right___wZOn-_6-5-7","scrollButtonRight":"magritte-scroll-button-right___wZOn-_6-5-7","position-container":"magritte-position-container___1A4Bv_6-5-7","positionContainer":"magritte-position-container___1A4Bv_6-5-7","dot-container":"magritte-dot-container___ahtlG_6-5-7","dotContainer":"magritte-dot-container___ahtlG_6-5-7"};
|
|
3
|
-
|
|
4
|
-
export { styles as s };
|
|
5
|
-
//# sourceMappingURL=scrollableContainer-DMHjenAM.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"scrollableContainer-DMHjenAM.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;"}
|