@wordpress/block-editor
Version:
136 lines (118 loc) • 3.67 kB
JavaScript
/**
* External dependencies
*/
import { useWindowDimensions } from 'react-native';
import {
useSharedValue,
useAnimatedRef,
scrollTo,
useAnimatedReaction,
withTiming,
withRepeat,
cancelAnimation,
Easing,
} from 'react-native-reanimated';
/**
* Internal dependencies
*/
import { useBlockListContext } from '../block-list/block-list-context';
const SCROLL_INACTIVE_DISTANCE_PX = 50;
const SCROLL_INTERVAL_MS = 1000;
const VELOCITY_MULTIPLIER = 5000;
/**
* React hook that scrolls the scroll container when a block is being dragged.
*
* @return {Function[]} `startScrolling`, `scrollOnDragOver`, `stopScrolling`
* functions to be called in `onDragStart`, `onDragOver`
* and `onDragEnd` events respectively. Additionally,
* `scrollHandler` function is returned which should be
* called in the `onScroll` event of the block list.
*/
export default function useScrollWhenDragging() {
const { scrollRef } = useBlockListContext();
const animatedScrollRef = useAnimatedRef();
animatedScrollRef( scrollRef?.scrollViewRef );
const { height: windowHeight } = useWindowDimensions();
const velocityY = useSharedValue( 0 );
const offsetY = useSharedValue( 0 );
const dragStartY = useSharedValue( 0 );
const animationTimer = useSharedValue( 0 );
const isAnimationTimerActive = useSharedValue( false );
const isScrollActive = useSharedValue( false );
const scroll = {
offsetY: useSharedValue( 0 ),
maxOffsetY: useSharedValue( 0 ),
};
const scrollHandler = ( event ) => {
'worklet';
const { contentSize, contentOffset, layoutMeasurement } = event;
scroll.offsetY.value = contentOffset.y;
scroll.maxOffsetY.value = contentSize.height - layoutMeasurement.height;
};
const stopScrolling = () => {
'worklet';
cancelAnimation( animationTimer );
isAnimationTimerActive.value = false;
isScrollActive.value = false;
velocityY.value = 0;
};
const startScrolling = ( y ) => {
'worklet';
stopScrolling();
offsetY.value = scroll.offsetY.value;
dragStartY.value = y;
animationTimer.value = 0;
animationTimer.value = withRepeat(
withTiming( 1, {
duration: SCROLL_INTERVAL_MS,
easing: Easing.linear,
} ),
-1,
true
);
isAnimationTimerActive.value = true;
};
const scrollOnDragOver = ( y ) => {
'worklet';
const dragDistance = Math.max(
Math.abs( y - dragStartY.value ) - SCROLL_INACTIVE_DISTANCE_PX,
0
);
const distancePercentage = dragDistance / windowHeight;
if ( ! isScrollActive.value ) {
isScrollActive.value = dragDistance > 0;
} else if ( y > dragStartY.value ) {
// User is dragging downwards.
velocityY.value = VELOCITY_MULTIPLIER * distancePercentage;
} else if ( y < dragStartY.value ) {
// User is dragging upwards.
velocityY.value = -VELOCITY_MULTIPLIER * distancePercentage;
} else {
velocityY.value = 0;
}
};
useAnimatedReaction(
() => animationTimer.value,
( value, previous ) => {
if ( velocityY.value === 0 ) {
return;
}
const delta = Math.abs( value - previous );
let newOffset = offsetY.value + delta * velocityY.value;
if ( scroll.maxOffsetY.value !== 0 ) {
newOffset = Math.max(
0,
Math.min( scroll.maxOffsetY.value, newOffset )
);
} else {
// Scroll values are empty until receiving the first scroll event.
// In that case, the max offset is unknown and we can't clamp the
// new offset value.
newOffset = Math.max( 0, newOffset );
}
offsetY.value = newOffset;
scrollTo( animatedScrollRef, 0, offsetY.value, false );
}
);
return [ startScrolling, scrollOnDragOver, stopScrolling, scrollHandler ];
}