diff --git a/__tests__/scrolling-threshold.html b/__tests__/scrolling-threshold.html
new file mode 100644
index 0000000..33dd71e
--- /dev/null
+++ b/__tests__/scrolling-threshold.html
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/__tests__/scrolling-threshold.js b/__tests__/scrolling-threshold.js
new file mode 100644
index 0000000..277b03e
--- /dev/null
+++ b/__tests__/scrolling-threshold.js
@@ -0,0 +1,3 @@
+/** @jest-environment ./packages/test-harness/JestEnvironment */
+
+test('autoScrollThreshold should keep stickiness when within threshold', () => runHTML('scrolling-threshold.html'));
diff --git a/packages/component/src/BasicScrollToBottom.js b/packages/component/src/BasicScrollToBottom.js
index ac0e29b..b96f961 100644
--- a/packages/component/src/BasicScrollToBottom.js
+++ b/packages/component/src/BasicScrollToBottom.js
@@ -1,6 +1,7 @@
+import React from 'react';
+
import classNames from 'classnames';
import PropTypes from 'prop-types';
-import React from 'react';
import AutoHideFollowButton from './ScrollToBottom/AutoHideFollowButton';
import Composer from './ScrollToBottom/Composer';
@@ -48,9 +49,11 @@ const BasicScrollToBottom = ({
nonce,
scroller,
scrollViewClassName,
- styleOptions
+ styleOptions,
+ autoScrollThreshold
}) => (
Infinity;
const MIN_CHECK_INTERVAL = 17; // 1 frame
const MODE_BOTTOM = 'bottom';
const MODE_TOP = 'top';
-const NEAR_END_THRESHOLD = 1;
+
const SCROLL_DECISION_DURATION = 34; // 2 frames
function setImmediateInterval(fn, ms) {
@@ -26,9 +26,9 @@ function setImmediateInterval(fn, ms) {
return setInterval(fn, ms);
}
-function computeViewState({ mode, target: { offsetHeight, scrollHeight, scrollTop } }) {
- const atBottom = scrollHeight - scrollTop - offsetHeight < NEAR_END_THRESHOLD;
- const atTop = scrollTop < NEAR_END_THRESHOLD;
+function computeViewState({ mode, autoScrollThreshold, target: { offsetHeight, scrollHeight, scrollTop } }) {
+ const atBottom = scrollHeight - scrollTop - offsetHeight < autoScrollThreshold;
+ const atTop = scrollTop < autoScrollThreshold;
const atEnd = mode === MODE_TOP ? atTop : atBottom;
const atStart = mode !== MODE_TOP ? atTop : atBottom;
@@ -46,6 +46,7 @@ function isEnd(animateTo, mode) {
}
const Composer = ({
+ autoScrollThreshold,
checkInterval,
children,
debounce,
@@ -325,7 +326,7 @@ const Composer = ({
return;
}
- const { atBottom, atEnd, atStart, atTop } = computeViewState({ mode, target });
+ const { atBottom, atEnd, atStart, atTop } = computeViewState({ mode, autoScrollThreshold, target });
setAtBottom(atBottom);
setAtEnd(atEnd);
@@ -416,6 +417,7 @@ const Composer = ({
},
[
animateToRef,
+ autoScrollThreshold,
debug,
ignoreScrollEventBeforeRef,
mode,
@@ -442,7 +444,7 @@ const Composer = ({
const animating = animateToRef.current !== null;
if (stickyRef.current) {
- if (!computeViewState({ mode, target }).atEnd) {
+ if (!computeViewState({ mode, autoScrollThreshold, target }).atEnd) {
if (!stickyButNotAtEndSince) {
stickyButNotAtEndSince = Date.now();
} else if (Date.now() - stickyButNotAtEndSince > SCROLL_DECISION_DURATION) {
@@ -495,7 +497,18 @@ const Composer = ({
return () => clearInterval(timeout);
}
- }, [animateToRef, checkInterval, debug, mode, scrollToSticky, setSticky, stickyRef, target, targetRef]);
+ }, [
+ animateToRef,
+ autoScrollThreshold,
+ checkInterval,
+ debug,
+ mode,
+ scrollToSticky,
+ setSticky,
+ stickyRef,
+ target,
+ targetRef
+ ]);
const emotion = useEmotion(nonce, styleOptions?.stylesRoot);
const styleToClassName = useCallback(style => emotion.css(style) + '', [emotion]);
@@ -610,6 +623,7 @@ const Composer = ({
};
Composer.defaultProps = {
+ autoScrollThreshold: 1,
checkInterval: 100,
children: undefined,
debounce: 17,
@@ -622,6 +636,7 @@ Composer.defaultProps = {
};
Composer.propTypes = {
+ autoScrollThreshold: PropTypes.number,
checkInterval: PropTypes.number,
children: PropTypes.any,
debounce: PropTypes.number,