@hh.ru/magritte-ui-scrollable-container 6.5.2 → 6.5.3

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.
Files changed (44) hide show
  1. package/ObservableValue.js +17 -17
  2. package/ObservableValue.js.map +1 -1
  3. package/ScrollStateProvider.d.ts +2 -2
  4. package/ScrollStateProvider.js +11 -11
  5. package/ScrollStateProvider.js.map +1 -1
  6. package/ScrollableContainer.d.ts +8 -8
  7. package/ScrollableContainer.js +99 -60
  8. package/ScrollableContainer.js.map +1 -1
  9. package/ScrollableContainerButton.d.ts +1 -1
  10. package/ScrollableContainerButton.js +15 -12
  11. package/ScrollableContainerButton.js.map +1 -1
  12. package/ScrollableContainerDefaultLayout.d.ts +1 -1
  13. package/ScrollableContainerDefaultLayout.js +130 -68
  14. package/ScrollableContainerDefaultLayout.js.map +1 -1
  15. package/ScrollableContainerDotPages.d.ts +1 -1
  16. package/ScrollableContainerDotPages.js +59 -47
  17. package/ScrollableContainerDotPages.js.map +1 -1
  18. package/ScrollableContainerGallery.d.ts +1 -1
  19. package/ScrollableContainerGallery.js +85 -54
  20. package/ScrollableContainerGallery.js.map +1 -1
  21. package/ScrollableItem.d.ts +1 -1
  22. package/ScrollableItem.js +22 -12
  23. package/ScrollableItem.js.map +1 -1
  24. package/index.css +19 -19
  25. package/index.d.ts +6 -6
  26. package/index.js +1 -1
  27. package/index.mock.d.ts +4 -4
  28. package/index.mock.js +21 -21
  29. package/index.mock.js.map +1 -1
  30. package/package.json +6 -6
  31. package/scrollableContainer-CWsF-84M.js +5 -0
  32. package/scrollableContainer-CWsF-84M.js.map +1 -0
  33. package/types.js +1 -1
  34. package/types.js.map +1 -1
  35. package/useScrollContainer.js +181 -157
  36. package/useScrollContainer.js.map +1 -1
  37. package/useScrollableControls.d.ts +2 -2
  38. package/useScrollableControls.js +90 -72
  39. package/useScrollableControls.js.map +1 -1
  40. package/utils.d.ts +2 -2
  41. package/utils.js +157 -254
  42. package/utils.js.map +1 -1
  43. package/scrollableContainer-C0a8z9uL.js +0 -5
  44. package/scrollableContainer-C0a8z9uL.js.map +0 -1
@@ -14,166 +14,190 @@ const EPSILON = 0.5;
14
14
  const MIN_DELTA_FOR_TOUCH = 5;
15
15
  const MAX_OFFSET = { [OFFSET_ATTR]: { left: Number.MAX_SAFE_INTEGER } };
16
16
  const useScrollContainer = () => {
17
- const scrollState = useScrollState();
18
- const { scrollToIndex, jumpToIndex } = useScrollableControls();
19
- const [isTouch, setIsTouch] = useState(false);
20
- const leftCorrection = scrollState.leftCorrection.get();
21
- const rightCorrection = scrollState.rightCorrection.get();
22
- const scrollStateRef = useRef({
23
- containerRef: useRef(null),
24
- positionContainerRef: useRef(null),
25
- correction: { leftCorrection, rightCorrection },
26
- wasInteracted: false,
17
+ const scrollState = useScrollState();
18
+ const { scrollToIndex, jumpToIndex } = useScrollableControls();
19
+ const [isTouch, setIsTouch] = useState(false);
20
+ const leftCorrection = scrollState.leftCorrection.get();
21
+ const rightCorrection = scrollState.rightCorrection.get();
22
+ const scrollStateRef = useRef({
23
+ containerRef: useRef(null),
24
+ positionContainerRef: useRef(null),
25
+ correction: { leftCorrection, rightCorrection },
26
+ wasInteracted: false
27
+ });
28
+ scrollStateRef.current.correction = { leftCorrection, rightCorrection };
29
+ const resizeOberverRef = useRef(null);
30
+ const recalculateItemPositions = useCallback(() => {
31
+ const state = scrollStateRef.current;
32
+ const { containerRef, correction, positionContainerRef } = state;
33
+ const { leftCorrection: leftCorrection2, rightCorrection: rightCorrection2 } = correction;
34
+ const items = scrollState.items.get();
35
+ if (!containerRef.current || !positionContainerRef.current) {
36
+ return;
37
+ }
38
+ const offsetLeft = positionContainerRef.current?.getBoundingClientRect().left ?? 0;
39
+ items.forEach((itemRef) => {
40
+ if (!itemRef.current) {
41
+ return;
42
+ }
43
+ const rect = itemRef.current.getBoundingClientRect();
44
+ itemRef.current[OFFSET_ATTR] = { left: rect.left - offsetLeft, right: rect.right - offsetLeft };
27
45
  });
28
- scrollStateRef.current.correction = { leftCorrection, rightCorrection };
29
- const resizeOberverRef = useRef(null);
30
- // recalc positions + report widths into store
31
- const recalculateItemPositions = useCallback(() => {
32
- const state = scrollStateRef.current;
33
- const { containerRef, correction, positionContainerRef } = state;
34
- const { leftCorrection, rightCorrection } = correction;
35
- const items = scrollState.items.get();
36
- if (!containerRef.current || !positionContainerRef.current) {
37
- return;
38
- }
39
- const offsetLeft = positionContainerRef.current?.getBoundingClientRect().left ?? 0;
40
- items.forEach((itemRef) => {
41
- if (!itemRef.current) {
42
- return;
43
- }
44
- const rect = itemRef.current.getBoundingClientRect();
45
- itemRef.current[OFFSET_ATTR] = { left: rect.left - offsetLeft, right: rect.right - offsetLeft };
46
- });
47
- items.sort((a, b) => (a.current || MAX_OFFSET)[OFFSET_ATTR].left - (b.current || MAX_OFFSET)[OFFSET_ATTR].left);
48
- const viewportWidth = containerRef.current.offsetWidth - leftCorrection - rightCorrection;
49
- const contentWidth = items.reduce((pos, itemRef) => Math.max(pos, itemRef.current?.[OFFSET_ATTR]?.right || 0), 0);
50
- scrollState.viewportWidth.set(viewportWidth);
51
- scrollState.contentWidth.set(contentWidth);
52
- scrollState.items.set(items);
53
- if (scrollState.indexForDeferScroll !== -1) {
54
- if (scrollState.behaviorScrollTypeDeferScroll === 'instant') {
55
- jumpToIndex(scrollState.indexForDeferScroll);
56
- }
57
- else {
58
- scrollToIndex(scrollState.indexForDeferScroll);
59
- }
60
- scrollState.behaviorScrollTypeDeferScroll = 'animate';
61
- scrollState.indexForDeferScroll = -1;
62
- }
63
- }, [scrollState, scrollToIndex, jumpToIndex]);
64
- // eslint-disable-next-line react-hooks/exhaustive-deps
65
- const recalculateItemPositionsDebounced = useCallback(debounce(recalculateItemPositions, 10), [
66
- recalculateItemPositions,
67
- ]);
68
- const scrollToItemDebounced = useInitOnce(() => debounce(() => {
69
- if (scrollState.scrollSnapAlign === 'none') {
70
- return;
71
- }
72
- const targetItemIndex = findIndexNearestToSnapPoint(scrollState);
73
- const scrollSnapBehavior = computeScrollSnapBehavior(scrollState);
74
- const target = scrollSnapBehavior(scrollState, targetItemIndex);
75
- animateScrollToPosition(scrollState, target);
76
- }, 100));
77
- useEffect(() => {
78
- const state = scrollStateRef.current;
79
- const observer = new ResizeObserver(requestAnimation(() => {
80
- recalculateItemPositions();
81
- }));
82
- scrollState.items.get().forEach((item) => item.current && observer.observe(item.current));
83
- state.containerRef.current && observer.observe(state.containerRef.current);
84
- resizeOberverRef.current = observer;
85
- return () => observer.disconnect();
86
- // eslint-disable-next-line react-hooks/exhaustive-deps
87
- }, []);
88
- // Слушатель нужен, чтобы не вызывать скролл в родительском контейнере (например в bs)
89
- useEffect(() => {
90
- const wrapperRef = scrollStateRef.current.containerRef.current;
91
- if (!wrapperRef) {
92
- return void 0;
93
- }
94
- const handleTouchStartHandler = (event) => {
95
- event.stopPropagation();
96
- };
97
- wrapperRef.addEventListener('touchstart', handleTouchStartHandler);
98
- return () => {
99
- wrapperRef.removeEventListener('touchstart', handleTouchStartHandler);
100
- };
101
- }, []);
102
- // события тача
103
- const didContainerMove = useRef(false);
104
- const onTouchStart = useCallback(() => {
105
- setIsTouch(true);
106
- didContainerMove.current = false;
107
- scrollState.scrollPosition.stop();
108
- }, [scrollState]);
109
- const onTouchMove = useCallback((event) => {
110
- if (!didContainerMove.current) {
111
- // надо отличить тач от свайпа
112
- didContainerMove.current = Math.abs(event.delta) > MIN_DELTA_FOR_TOUCH;
113
- }
114
- scrollState.scrollPosition.set(clamp(scrollState.scrollPosition.get() + event.delta, scrollState.viewportWidth.get() - scrollState.contentWidth.get(), 0));
115
- }, [scrollState]);
116
- const onTouchEnd = useCallback(() => {
117
- if (didContainerMove.current) {
118
- scrollToItemDebounced();
119
- }
120
- }, [scrollToItemDebounced]);
121
- // события мыши
122
- const didContainerHovered = useRef(false);
123
- const onMouseEnter = useCallback(() => {
124
- didContainerHovered.current = true;
125
- }, []);
126
- const onWheelMove = useCallback((event) => {
127
- setIsTouch(false);
128
- if (!didContainerHovered.current) {
129
- return;
130
- }
131
- const currentPosition = scrollState.scrollPosition.get();
132
- const nextPosition = clamp(currentPosition + event.delta, scrollState.viewportWidth.get() - scrollState.contentWidth.get(), 0);
133
- if (Math.abs(nextPosition - currentPosition) > EPSILON) {
134
- scrollState.scrollPosition.set(nextPosition);
135
- scrollToItemDebounced();
136
- event.preventDefault();
137
- }
138
- }, [scrollToItemDebounced, scrollState]);
139
- const onMouseLeave = useCallback(() => {
140
- didContainerHovered.current = false;
141
- }, []);
142
- const _initScrollHandlers = useCallback(() => initScrollHandlers({
143
- axis: 'horizontal',
144
- wrapperRef: scrollStateRef.current.containerRef,
145
- onTouchStart,
146
- onTouchMove,
147
- onTouchEnd,
148
- onMouseEnter,
149
- onWheelMove,
150
- onMouseLeave,
151
- }), [onTouchMove, onTouchEnd, onWheelMove, onTouchStart, onMouseEnter, onMouseLeave]);
152
- const registerItem = useCallback((ref) => {
153
- if (!ref.current) {
154
- return;
155
- }
156
- scrollState.items.get().push(ref);
157
- scrollState.items.set(scrollState.items.get());
158
- resizeOberverRef.current?.observe(ref.current);
46
+ items.sort((a, b) => (a.current || MAX_OFFSET)[OFFSET_ATTR].left - (b.current || MAX_OFFSET)[OFFSET_ATTR].left);
47
+ const viewportWidth = containerRef.current.offsetWidth - leftCorrection2 - rightCorrection2;
48
+ const contentWidth = items.reduce(
49
+ (pos, itemRef) => Math.max(pos, itemRef.current?.[OFFSET_ATTR]?.right || 0),
50
+ 0
51
+ );
52
+ scrollState.viewportWidth.set(viewportWidth);
53
+ scrollState.contentWidth.set(contentWidth);
54
+ scrollState.items.set(items);
55
+ if (scrollState.indexForDeferScroll !== -1) {
56
+ if (scrollState.behaviorScrollTypeDeferScroll === "instant") {
57
+ jumpToIndex(scrollState.indexForDeferScroll);
58
+ } else {
59
+ scrollToIndex(scrollState.indexForDeferScroll);
60
+ }
61
+ scrollState.behaviorScrollTypeDeferScroll = "animate";
62
+ scrollState.indexForDeferScroll = -1;
63
+ }
64
+ }, [scrollState, scrollToIndex, jumpToIndex]);
65
+ const recalculateItemPositionsDebounced = useCallback(debounce(recalculateItemPositions, 10), [
66
+ recalculateItemPositions
67
+ ]);
68
+ const scrollToItemDebounced = useInitOnce(
69
+ () => debounce(() => {
70
+ if (scrollState.scrollSnapAlign === "none") {
71
+ return;
72
+ }
73
+ const targetItemIndex = findIndexNearestToSnapPoint(scrollState);
74
+ const scrollSnapBehavior = computeScrollSnapBehavior(scrollState);
75
+ const target = scrollSnapBehavior(scrollState, targetItemIndex);
76
+ animateScrollToPosition(scrollState, target);
77
+ }, 100)
78
+ );
79
+ useEffect(() => {
80
+ const state = scrollStateRef.current;
81
+ const observer = new ResizeObserver(
82
+ requestAnimation(() => {
159
83
  recalculateItemPositions();
160
- }, [recalculateItemPositions, scrollState]);
161
- const removeItem = useCallback((ref) => {
162
- if (!ref.current) {
163
- return;
164
- }
165
- scrollState.items.set(scrollState.items.get().filter((stored) => stored.current !== ref.current));
166
- resizeOberverRef.current?.unobserve(ref.current);
167
- recalculateItemPositionsDebounced();
168
- }, [recalculateItemPositionsDebounced, scrollState]);
169
- return {
170
- registerItem,
171
- removeItem,
172
- initScrollHandlers: _initScrollHandlers,
173
- containerRef: scrollStateRef.current.containerRef,
174
- positionContainerRef: scrollStateRef.current.positionContainerRef,
175
- isTouch,
84
+ })
85
+ );
86
+ scrollState.items.get().forEach((item) => item.current && observer.observe(item.current));
87
+ state.containerRef.current && observer.observe(state.containerRef.current);
88
+ resizeOberverRef.current = observer;
89
+ return () => observer.disconnect();
90
+ }, []);
91
+ useEffect(() => {
92
+ const wrapperRef = scrollStateRef.current.containerRef.current;
93
+ if (!wrapperRef) {
94
+ return void 0;
95
+ }
96
+ const handleTouchStartHandler = (event) => {
97
+ event.stopPropagation();
176
98
  };
99
+ wrapperRef.addEventListener("touchstart", handleTouchStartHandler);
100
+ return () => {
101
+ wrapperRef.removeEventListener("touchstart", handleTouchStartHandler);
102
+ };
103
+ }, []);
104
+ const didContainerMove = useRef(false);
105
+ const onTouchStart = useCallback(() => {
106
+ setIsTouch(true);
107
+ didContainerMove.current = false;
108
+ scrollState.scrollPosition.stop();
109
+ }, [scrollState]);
110
+ const onTouchMove = useCallback(
111
+ (event) => {
112
+ if (!didContainerMove.current) {
113
+ didContainerMove.current = Math.abs(event.delta) > MIN_DELTA_FOR_TOUCH;
114
+ }
115
+ scrollState.scrollPosition.set(
116
+ clamp(
117
+ scrollState.scrollPosition.get() + event.delta,
118
+ scrollState.viewportWidth.get() - scrollState.contentWidth.get(),
119
+ 0
120
+ )
121
+ );
122
+ },
123
+ [scrollState]
124
+ );
125
+ const onTouchEnd = useCallback(() => {
126
+ if (didContainerMove.current) {
127
+ scrollToItemDebounced();
128
+ }
129
+ }, [scrollToItemDebounced]);
130
+ const didContainerHovered = useRef(false);
131
+ const onMouseEnter = useCallback(() => {
132
+ didContainerHovered.current = true;
133
+ }, []);
134
+ const onWheelMove = useCallback(
135
+ (event) => {
136
+ setIsTouch(false);
137
+ if (!didContainerHovered.current) {
138
+ return;
139
+ }
140
+ const currentPosition = scrollState.scrollPosition.get();
141
+ const nextPosition = clamp(
142
+ currentPosition + event.delta,
143
+ scrollState.viewportWidth.get() - scrollState.contentWidth.get(),
144
+ 0
145
+ );
146
+ if (Math.abs(nextPosition - currentPosition) > EPSILON) {
147
+ scrollState.scrollPosition.set(nextPosition);
148
+ scrollToItemDebounced();
149
+ event.preventDefault();
150
+ }
151
+ },
152
+ [scrollToItemDebounced, scrollState]
153
+ );
154
+ const onMouseLeave = useCallback(() => {
155
+ didContainerHovered.current = false;
156
+ }, []);
157
+ const _initScrollHandlers = useCallback(
158
+ () => initScrollHandlers({
159
+ axis: "horizontal",
160
+ wrapperRef: scrollStateRef.current.containerRef,
161
+ onTouchStart,
162
+ onTouchMove,
163
+ onTouchEnd,
164
+ onMouseEnter,
165
+ onWheelMove,
166
+ onMouseLeave
167
+ }),
168
+ [onTouchMove, onTouchEnd, onWheelMove, onTouchStart, onMouseEnter, onMouseLeave]
169
+ );
170
+ const registerItem = useCallback(
171
+ (ref) => {
172
+ if (!ref.current) {
173
+ return;
174
+ }
175
+ scrollState.items.get().push(ref);
176
+ scrollState.items.set(scrollState.items.get());
177
+ resizeOberverRef.current?.observe(ref.current);
178
+ recalculateItemPositions();
179
+ },
180
+ [recalculateItemPositions, scrollState]
181
+ );
182
+ const removeItem = useCallback(
183
+ (ref) => {
184
+ if (!ref.current) {
185
+ return;
186
+ }
187
+ scrollState.items.set(scrollState.items.get().filter((stored) => stored.current !== ref.current));
188
+ resizeOberverRef.current?.unobserve(ref.current);
189
+ recalculateItemPositionsDebounced();
190
+ },
191
+ [recalculateItemPositionsDebounced, scrollState]
192
+ );
193
+ return {
194
+ registerItem,
195
+ removeItem,
196
+ initScrollHandlers: _initScrollHandlers,
197
+ containerRef: scrollStateRef.current.containerRef,
198
+ positionContainerRef: scrollStateRef.current.positionContainerRef,
199
+ isTouch
200
+ };
177
201
  };
178
202
 
179
203
  export { useScrollContainer };
@@ -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":[],"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 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,4 +1,4 @@
1
- import { type ScrollState } from '@hh.ru/magritte-ui-scrollable-container/ScrollStateProvider';
2
- import type { ScrollableControls } from '@hh.ru/magritte-ui-scrollable-container/types';
1
+ import { type ScrollState } from './ScrollStateProvider';
2
+ import type { ScrollableControls } from './types';
3
3
  export declare const useScrollableControlsWithState: (scrollState: ScrollState) => ScrollableControls;
4
4
  export declare const useScrollableControls: () => ScrollableControls;
@@ -9,80 +9,98 @@ import '@hh.ru/magritte-ui-breakpoint';
9
9
  import './types.js';
10
10
 
11
11
  const useScrollableControlsWithState = (scrollState) => {
12
- const scrollSide = useCallback((toRight = false) => {
13
- if (scrollState.scrollSnapAlign === 'none') {
14
- const delta = toRight ? -scrollState.viewportWidth.get() : scrollState.viewportWidth.get();
15
- const position = scrollState.scrollPosition.get() + delta;
16
- animateScrollToPosition(scrollState, position);
17
- return;
18
- }
19
- const index = findIndexNearestToSnapPoint(scrollState);
20
- const nextIndex = clamp(toRight ? index + 1 : index - 1, 0, scrollState.items.get().length - 1);
21
- if (!ensureItemReadyForScroll(scrollState, nextIndex)) {
22
- return;
23
- }
24
- const scrollSnapBehavior = computeScrollSnapBehavior(scrollState);
25
- const target = scrollSnapBehavior(scrollState, nextIndex);
26
- animateScrollToPosition(scrollState, target);
27
- }, [scrollState]);
28
- const scrollToIndex = useCallback((index) => {
29
- if (!ensureItemReadyForScroll(scrollState, index)) {
30
- return;
31
- }
32
- const scrollSnapBehavior = computeScrollSnapBehavior(scrollState);
33
- const target = scrollSnapBehavior(scrollState, index);
34
- animateScrollToPosition(scrollState, target);
35
- }, [scrollState]);
36
- const scrollToFirstItem = useCallback(() => scrollToIndex(0), [scrollToIndex]);
37
- const scrollToLastItem = useCallback(() => scrollToIndex(scrollState.items.get().length - 1), [scrollState, scrollToIndex]);
38
- const scrollToItem = useCallback((ref) => {
39
- const index = scrollState.items.get().findIndex((item) => item.current && item.current === ref.current);
40
- index !== -1 && scrollToIndex(index);
41
- }, [scrollState, scrollToIndex]);
42
- const jumpToIndex = useCallback((index) => {
43
- if (!ensureItemReadyForScroll(scrollState, index, 'instant')) {
44
- return;
45
- }
46
- const scrollSnapBehavior = computeScrollSnapBehavior(scrollState);
47
- const target = scrollSnapBehavior(scrollState, index);
48
- const { scrollPosition, springScrollPosition, viewportWidth, contentWidth } = scrollState;
49
- if (target === null) {
50
- return;
51
- }
52
- const clampedTarget = clamp(target, viewportWidth.get() - contentWidth.get(), 0);
53
- const isScrollPositionReached = scrollPosition.get() === clampedTarget;
54
- const isSpringScrollPositionReached = !springScrollPosition || springScrollPosition.get() === clampedTarget;
55
- if (isScrollPositionReached && isSpringScrollPositionReached) {
56
- return;
57
- }
58
- scrollPosition.jump(clampedTarget);
59
- springScrollPosition?.jump(clampedTarget);
60
- }, [scrollState]);
61
- const jumpBy = useCallback((delta) => {
62
- const { scrollPosition, springScrollPosition, viewportWidth, contentWidth } = scrollState;
63
- const target = clamp(scrollPosition.get() + delta, viewportWidth.get() - contentWidth.get(), 0);
64
- const clampedDelta = target - scrollPosition.get();
65
- if (clampedDelta === 0) {
66
- return;
67
- }
68
- scrollPosition.jump(target);
69
- springScrollPosition?.jump(springScrollPosition.get() + clampedDelta);
70
- }, [scrollState]);
71
- return {
72
- scrollLeft: () => scrollSide(),
73
- scrollRight: () => scrollSide(true),
74
- scrollToFirstItem,
75
- scrollToLastItem,
76
- scrollToIndex,
77
- scrollToItem,
78
- jumpToIndex,
79
- jumpBy,
80
- };
12
+ const scrollSide = useCallback(
13
+ (toRight = false) => {
14
+ if (scrollState.scrollSnapAlign === "none") {
15
+ const delta = toRight ? -scrollState.viewportWidth.get() : scrollState.viewportWidth.get();
16
+ const position = scrollState.scrollPosition.get() + delta;
17
+ animateScrollToPosition(scrollState, position);
18
+ return;
19
+ }
20
+ const index = findIndexNearestToSnapPoint(scrollState);
21
+ const nextIndex = clamp(toRight ? index + 1 : index - 1, 0, scrollState.items.get().length - 1);
22
+ if (!ensureItemReadyForScroll(scrollState, nextIndex)) {
23
+ return;
24
+ }
25
+ const scrollSnapBehavior = computeScrollSnapBehavior(scrollState);
26
+ const target = scrollSnapBehavior(scrollState, nextIndex);
27
+ animateScrollToPosition(scrollState, target);
28
+ },
29
+ [scrollState]
30
+ );
31
+ const scrollToIndex = useCallback(
32
+ (index) => {
33
+ if (!ensureItemReadyForScroll(scrollState, index)) {
34
+ return;
35
+ }
36
+ const scrollSnapBehavior = computeScrollSnapBehavior(scrollState);
37
+ const target = scrollSnapBehavior(scrollState, index);
38
+ animateScrollToPosition(scrollState, target);
39
+ },
40
+ [scrollState]
41
+ );
42
+ const scrollToFirstItem = useCallback(() => scrollToIndex(0), [scrollToIndex]);
43
+ const scrollToLastItem = useCallback(
44
+ () => scrollToIndex(scrollState.items.get().length - 1),
45
+ [scrollState, scrollToIndex]
46
+ );
47
+ const scrollToItem = useCallback(
48
+ (ref) => {
49
+ const index = scrollState.items.get().findIndex((item) => item.current && item.current === ref.current);
50
+ index !== -1 && scrollToIndex(index);
51
+ },
52
+ [scrollState, scrollToIndex]
53
+ );
54
+ const jumpToIndex = useCallback(
55
+ (index) => {
56
+ if (!ensureItemReadyForScroll(scrollState, index, "instant")) {
57
+ return;
58
+ }
59
+ const scrollSnapBehavior = computeScrollSnapBehavior(scrollState);
60
+ const target = scrollSnapBehavior(scrollState, index);
61
+ const { scrollPosition, springScrollPosition, viewportWidth, contentWidth } = scrollState;
62
+ if (target === null) {
63
+ return;
64
+ }
65
+ const clampedTarget = clamp(target, viewportWidth.get() - contentWidth.get(), 0);
66
+ const isScrollPositionReached = scrollPosition.get() === clampedTarget;
67
+ const isSpringScrollPositionReached = !springScrollPosition || springScrollPosition.get() === clampedTarget;
68
+ if (isScrollPositionReached && isSpringScrollPositionReached) {
69
+ return;
70
+ }
71
+ scrollPosition.jump(clampedTarget);
72
+ springScrollPosition?.jump(clampedTarget);
73
+ },
74
+ [scrollState]
75
+ );
76
+ const jumpBy = useCallback(
77
+ (delta) => {
78
+ const { scrollPosition, springScrollPosition, viewportWidth, contentWidth } = scrollState;
79
+ const target = clamp(scrollPosition.get() + delta, viewportWidth.get() - contentWidth.get(), 0);
80
+ const clampedDelta = target - scrollPosition.get();
81
+ if (clampedDelta === 0) {
82
+ return;
83
+ }
84
+ scrollPosition.jump(target);
85
+ springScrollPosition?.jump(springScrollPosition.get() + clampedDelta);
86
+ },
87
+ [scrollState]
88
+ );
89
+ return {
90
+ scrollLeft: () => scrollSide(),
91
+ scrollRight: () => scrollSide(true),
92
+ scrollToFirstItem,
93
+ scrollToLastItem,
94
+ scrollToIndex,
95
+ scrollToItem,
96
+ jumpToIndex,
97
+ jumpBy
98
+ };
81
99
  };
82
100
  const useScrollableControls = () => {
83
- const scrollState = useScrollState();
84
- const controls = useScrollableControlsWithState(scrollState);
85
- return controls;
101
+ const scrollState = useScrollState();
102
+ const controls = useScrollableControlsWithState(scrollState);
103
+ return controls;
86
104
  };
87
105
 
88
106
  export { useScrollableControls, useScrollableControlsWithState };