Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RNMobile] Auto-scroll upon block insertion #57273

Merged
merged 15 commits into from
Jan 2, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
import Animated, {
runOnJS,
runOnUI,
useAnimatedRef,
useAnimatedStyle,
useSharedValue,
withDelay,
Expand Down Expand Up @@ -39,7 +38,6 @@ import RCTAztecView from '@wordpress/react-native-aztec';
import useScrollWhenDragging from './use-scroll-when-dragging';
import DraggableChip from './draggable-chip';
import { store as blockEditorStore } from '../../store';
import { useBlockListContext } from '../block-list/block-list-context';
import DroppingInsertionPoint from './dropping-insertion-point';
import useBlockDropZone from '../use-block-drop-zone';
import styles from './style.scss';
Expand Down Expand Up @@ -74,13 +72,10 @@ const BlockDraggableWrapper = ( { children, isRTL } ) => {
const { selectBlock, startDraggingBlocks, stopDraggingBlocks } =
useDispatch( blockEditorStore );

const { scrollRef } = useBlockListContext();
const animatedScrollRef = useAnimatedRef();
const { left, right } = useSafeAreaInsets();
const { width } = useSafeAreaFrame();
const safeAreaOffset = left + right;
const contentWidth = width - safeAreaOffset;
animatedScrollRef( scrollRef );

const scroll = {
offsetY: useSharedValue( 0 ),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
*/
import {
act,
advanceAnimationByFrames,
fireEvent,
initializeEditor,
screen,
waitForStoreResolvers,
within,
advanceAnimationByFrames,
} from 'test/helpers';
import { fireGestureHandler } from 'react-native-gesture-handler/jest-utils';
import { State } from 'react-native-gesture-handler';
Expand Down Expand Up @@ -52,15 +53,15 @@ const DEFAULT_TOUCH_EVENTS = [
export const initializeWithBlocksLayouts = async ( blocks ) => {
const initialHtml = blocks.map( ( block ) => block.html ).join( '\n' );

const screen = await initializeEditor( { initialHtml } );
const { getAllByLabelText } = screen;
await initializeEditor( { initialHtml } );

const waitPromises = [];
const blockListItems = screen.getAllByTestId( 'block-list-item-cell' );
// Check that rendered block list items match expected block count.
expect( blockListItems.length ).toBe( blocks.length );

blocks.forEach( ( block, index ) => {
const a11yLabel = new RegExp(
`${ block.name } Block\\. Row ${ index + 1 }`
);
const [ element ] = getAllByLabelText( a11yLabel );
const element = blockListItems[ index ];
// "onLayout" event will populate the blocks layouts data.
fireEvent( element, 'layout', {
nativeEvent: { layout: block.layout },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const VELOCITY_MULTIPLIER = 5000;
export default function useScrollWhenDragging() {
const { scrollRef } = useBlockListContext();
const animatedScrollRef = useAnimatedRef();
animatedScrollRef( scrollRef );
animatedScrollRef( scrollRef?.scrollViewRef );

const { height: windowHeight } = useWindowDimensions();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ function BlockListItemCell( { children, item: clientId, onLayout } ) {
[ clientId, rootClientId, updateBlocksLayouts, onLayout ]
);

return <View onLayout={ onCellLayout }>{ children }</View>;
return (
<View testID="block-list-item-cell" onLayout={ onCellLayout }>
{ children }
</View>
);
}

export default BlockListItemCell;
17 changes: 16 additions & 1 deletion packages/block-editor/src/components/block-list/block.native.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import classnames from 'classnames';
/**
* WordPress dependencies
*/
import { useCallback, useMemo, useState } from '@wordpress/element';
import { useCallback, useMemo, useState, useRef } from '@wordpress/element';
import {
GlobalStylesContext,
getMergedGlobalStyles,
Expand Down Expand Up @@ -40,6 +40,7 @@ import BlockInvalidWarning from './block-invalid-warning';
import BlockOutline from './block-outline';
import { store as blockEditorStore } from '../../store';
import { useLayout } from './layout';
import useScrollUponInsertion from './use-scroll-upon-insertion';
import { useSettings } from '../use-settings';

const EMPTY_ARRAY = [];
Expand Down Expand Up @@ -103,6 +104,18 @@ function BlockWrapper( {
];
const accessible = ! ( isSelected || isDescendentBlockSelected );

const ref = useRef();
const [ isLayoutCalculated, setIsLayoutCalculated ] = useState();
useScrollUponInsertion( {
clientId,
isSelected,
isLayoutCalculated,
elementRef: ref,
} );
const onLayout = useCallback( () => {
setIsLayoutCalculated( true );
}, [] );

return (
<Pressable
accessibilityLabel={ accessibilityLabel }
Expand All @@ -111,6 +124,8 @@ function BlockWrapper( {
disabled={ ! isTouchable }
onPress={ onFocus }
style={ blockWrapperStyle }
ref={ ref }
onLayout={ onLayout }
>
<BlockOutline
blockCategory={ blockCategory }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export default function BlockList( {
insertBlock( newBlock, blockCount );
};

const scrollViewRef = useRef( null );
const scrollRef = useRef( null );

const shouldFlatListPreventAutomaticScroll = () =>
blockInsertionPointIsVisible;
Expand Down Expand Up @@ -244,7 +244,7 @@ export default function BlockList( {
<BlockListProvider
value={ {
...DEFAULT_BLOCK_LIST_CONTEXT,
scrollRef: scrollViewRef.current,
scrollRef: scrollRef.current,
} }
>
<BlockDraggableWrapper isRTL={ isRTL }>
Expand All @@ -254,9 +254,7 @@ export default function BlockList( {
? { removeClippedSubviews: false }
: {} ) } // Disable clipping on Android to fix focus losing. See https://github.com/wordpress-mobile/gutenberg-mobile/pull/741#issuecomment-472746541
accessibilityLabel="block-list"
innerRef={ ( ref ) => {
scrollViewRef.current = ref;
} }
ref={ scrollRef }
extraScrollHeight={ extraScrollHeight }
keyboardShouldPersistTaps="always"
scrollViewStyle={ { flex: 1 } }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* WordPress dependencies
*/
import { useEffect, useRef } from '@wordpress/element';
import { useSelect } from '@wordpress/data';

/**
* Internal dependencies
*/
import { useBlockListContext } from './block-list-context';
import { store as blockEditorStore } from '../../store';

const useScrollUponInsertion = ( {
clientId,
isSelected,
isLayoutCalculated,
elementRef,
} ) => {
const isAlreadyInserted = useRef( false );
const { scrollRef } = useBlockListContext();
const wasBlockJustInserted = useSelect(
( select ) =>
!! select( blockEditorStore ).wasBlockJustInserted(
clientId,
'inserter_menu'
),
[ clientId ]
);
useEffect( () => {
const blockJustInserted =
! isAlreadyInserted.current && wasBlockJustInserted;
if (
! isSelected ||
! scrollRef ||
! blockJustInserted ||
! isLayoutCalculated
) {
return;
}
scrollRef.scrollToElement( elementRef );
isAlreadyInserted.current = wasBlockJustInserted;
}, [
isSelected,
scrollRef,
wasBlockJustInserted,
elementRef,
isLayoutCalculated,
] );
};

export default useScrollUponInsertion;
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,69 @@
* External dependencies
*/
import { FlatList } from 'react-native';
import Animated, { useAnimatedScrollHandler } from 'react-native-reanimated';
import Animated from 'react-native-reanimated';

/**
* WordPress dependencies
*/
import {
forwardRef,
useCallback,
useImperativeHandle,
} from '@wordpress/element';

/**
* Internal dependencies
*/
import useScroll from './use-scroll';
import KeyboardAvoidingView from '../keyboard-avoiding-view';

const AnimatedFlatList = Animated.createAnimatedComponent( FlatList );

export const KeyboardAwareFlatList = ( { innerRef, onScroll, ...props } ) => {
const scrollHandler = useAnimatedScrollHandler( { onScroll } );
export const KeyboardAwareFlatList = ( { onScroll, ...props }, ref ) => {
const { extraScrollHeight, scrollEnabled, shouldPreventAutomaticScroll } =
props;

const {
scrollViewRef,
scrollHandler,
scrollToSection,
scrollToElement,
onContentSizeChange,
} = useScroll( {
scrollEnabled,
shouldPreventAutomaticScroll,
extraScrollHeight,
onScroll,
} );

const getFlatListRef = useCallback(
( flatListRef ) => {
// On Android, we get the ref of the associated scroll
// view to the FlatList.
scrollViewRef.current = flatListRef?.getNativeScrollRef();
},
[ scrollViewRef ]
);

useImperativeHandle( ref, () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Love this! 😍

return {
scrollViewRef: scrollViewRef.current,
scrollToSection,
scrollToElement,
};
} );

return (
<KeyboardAvoidingView style={ { flex: 1 } }>
<AnimatedFlatList
ref={ innerRef }
ref={ getFlatListRef }
onScroll={ scrollHandler }
onContentSizeChange={ onContentSizeChange }
{ ...props }
/>
</KeyboardAvoidingView>
);
};

export default KeyboardAwareFlatList;
export default forwardRef( KeyboardAwareFlatList );
Loading
Loading