diff --git a/packages/block-editor/src/components/block-list/index.native.js b/packages/block-editor/src/components/block-list/index.native.js index c531ea8db4893d..830b7ffec02f4d 100644 --- a/packages/block-editor/src/components/block-list/index.native.js +++ b/packages/block-editor/src/components/block-list/index.native.js @@ -211,7 +211,7 @@ export default function BlockList( { ); }; - const { blockToolbar, headerToolbar, floatingToolbar } = styles; + const { blockToolbar, floatingToolbar } = styles; const containerStyle = { flex: isRootList ? 1 : 0, @@ -224,7 +224,6 @@ export default function BlockList( { const isMultiBlocks = blockClientIds.length > 1; const { isWider } = alignmentHelpers; const extraScrollHeight = - headerToolbar.height + blockToolbar.height + ( isFloatingToolbarVisible ? floatingToolbar.height : 0 ); @@ -245,14 +244,10 @@ export default function BlockList( { { ( { onScroll } ) => ( { + if ( getSelectedBlockClientId() === clientId ) { + clearSelectedBlock(); + } + }, [ clearSelectedBlock, clientId, getSelectedBlockClientId ] ); + const onDelete = useCallback( ( { value, isReverse } ) => { if ( onMerge ) { @@ -590,6 +600,7 @@ export function RichTextWrapper( disableSuggestions={ disableSuggestions } disableAutocorrection={ disableAutocorrection } containerWidth={ containerWidth } + clearCurrentSelectionOnUnmount={ clearCurrentSelectionOnUnmount } // Props to be set on the editable container are destructured on the // element itself for web (see below), but passed through rich text // for native. diff --git a/packages/block-editor/src/components/rich-text/native/index.native.js b/packages/block-editor/src/components/rich-text/native/index.native.js index 8b4c871bda7bbf..26d39a0c6058b4 100644 --- a/packages/block-editor/src/components/rich-text/native/index.native.js +++ b/packages/block-editor/src/components/rich-text/native/index.native.js @@ -873,6 +873,17 @@ export class RichText extends Component { } } + componentWillUnmount() { + const { clearCurrentSelectionOnUnmount } = this.props; + + // There are cases when the component is unmounted e.g. scrolling in a + // long post due to virtualization, so the block selection needs to be cleared + // so it doesn't auto-focus when it's added back. + if ( this._editor?.isFocused() ) { + clearCurrentSelectionOnUnmount?.(); + } + } + getHtmlToRender( record, tagName ) { // Save back to HTML from React tree. let value = this.valueToFormat( record ); diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js index e66a0ffc28b542..0e92bef7df25f2 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.android.js @@ -18,8 +18,10 @@ import { */ import useScroll from './use-scroll'; import KeyboardAvoidingView from '../keyboard-avoiding-view'; +import { OPTIMIZATION_ITEMS_THRESHOLD, OPTIMIZATION_PROPS } from './shared'; const AnimatedFlatList = Animated.createAnimatedComponent( FlatList ); +const EMPTY_OBJECT = {}; export const KeyboardAwareFlatList = ( { onScroll, ...props }, ref ) => { const { extraScrollHeight, scrollEnabled, shouldPreventAutomaticScroll } = @@ -41,8 +43,6 @@ export const KeyboardAwareFlatList = ( { onScroll, ...props }, ref ) => { const getFlatListRef = useCallback( ( flatListRef ) => { - // On Android, we get the ref of the associated scroll - // view to the FlatList. scrollViewRef.current = flatListRef?.getNativeScrollRef(); }, [ scrollViewRef ] @@ -57,12 +57,21 @@ export const KeyboardAwareFlatList = ( { onScroll, ...props }, ref ) => { }; } ); + const optimizationProps = + props.data?.length > OPTIMIZATION_ITEMS_THRESHOLD + ? OPTIMIZATION_PROPS + : EMPTY_OBJECT; + return ( diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js index ac2c89188cbf68..9c224405a14a82 100644 --- a/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js +++ b/packages/components/src/mobile/keyboard-aware-flat-list/index.ios.js @@ -1,8 +1,7 @@ /** * External dependencies */ - -import { ScrollView, FlatList } from 'react-native'; +import { FlatList, View } from 'react-native'; import Animated from 'react-native-reanimated'; /** @@ -22,9 +21,12 @@ import { useThrottle } from '@wordpress/compose'; import useScroll from './use-scroll'; import useTextInputOffset from './use-text-input-offset'; import useTextInputCaretPosition from './use-text-input-caret-position'; +import { OPTIMIZATION_ITEMS_THRESHOLD, OPTIMIZATION_PROPS } from './shared'; +import styles from './styles.scss'; const DEFAULT_FONT_SIZE = 16; -const AnimatedScrollView = Animated.createAnimatedComponent( ScrollView ); +const AnimatedFlatList = Animated.createAnimatedComponent( FlatList ); +const EMPTY_OBJECT = {}; /** @typedef {import('@wordpress/element').RefObject} RefObject */ /** @@ -35,7 +37,6 @@ const AnimatedScrollView = Animated.createAnimatedComponent( ScrollView ); * @param {number} props.extraScrollHeight Extra scroll height for the content. * @param {Function} props.onScroll Function to be called when the list is scrolled. * @param {boolean} props.scrollEnabled Whether the list can be scrolled. - * @param {Object} props.scrollViewStyle Additional style for the ScrollView component. * @param {boolean} props.shouldPreventAutomaticScroll Whether to prevent scrolling when there's a Keyboard offset set. * @param {Object} props... Other props to pass to the FlatList component. * @param {RefObject} ref @@ -46,7 +47,6 @@ export const KeyboardAwareFlatList = ( extraScrollHeight, onScroll, scrollEnabled, - scrollViewStyle, shouldPreventAutomaticScroll, ...props }, @@ -105,7 +105,12 @@ export const KeyboardAwareFlatList = ( // extra padding at the bottom. const contentInset = { bottom: keyboardOffset }; - const style = [ { flex: 1 }, scrollViewStyle ]; + const getFlatListRef = useCallback( + ( flatListRef ) => { + scrollViewRef.current = flatListRef?.getNativeScrollRef(); + }, + [ scrollViewRef ] + ); useImperativeHandle( ref, () => { return { @@ -116,20 +121,26 @@ export const KeyboardAwareFlatList = ( }; } ); + const optimizationProps = + props.data?.length > OPTIMIZATION_ITEMS_THRESHOLD + ? OPTIMIZATION_PROPS + : EMPTY_OBJECT; + return ( - - - + + + ); }; diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/shared.native.js b/packages/components/src/mobile/keyboard-aware-flat-list/shared.native.js new file mode 100644 index 00000000000000..170ea77773ee1d --- /dev/null +++ b/packages/components/src/mobile/keyboard-aware-flat-list/shared.native.js @@ -0,0 +1,26 @@ +/** + * Optimization properties for FlatList. + * @typedef {Object} OptimizationProps + * @property {number} maxToRenderPerBatch - Controls the amount of items rendered per batch during scrolling. + * Increasing this number reduces visual blank areas but may affect responsiveness. + * Default: 10 + * @property {number} windowSize - Measurement unit representing viewport height. + * Default: 21 (10 viewports above, 10 below, and 1 in between). + * Larger values reduce chances of seeing blank spaces while scrolling but increase memory consumption. + * Smaller values save memory but increase chances of seeing blank areas. + */ + +/** + * Threshold for applying optimization settings. + * @type {number} + */ +export const OPTIMIZATION_ITEMS_THRESHOLD = 30; + +/** + * Optimization properties for FlatList. + * @type {OptimizationProps} + */ +export const OPTIMIZATION_PROPS = { + maxToRenderPerBatch: 15, + windowSize: 17, +}; diff --git a/packages/components/src/mobile/keyboard-aware-flat-list/styles.native.scss b/packages/components/src/mobile/keyboard-aware-flat-list/styles.native.scss new file mode 100644 index 00000000000000..bbde37bac1c37b --- /dev/null +++ b/packages/components/src/mobile/keyboard-aware-flat-list/styles.native.scss @@ -0,0 +1,8 @@ +.list__container { + flex-grow: 1; + align-items: stretch; +} + +.list__content { + margin-bottom: $mobile-block-toolbar-height; +} diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md index 8f4fcc63edd0fa..8381d09b04eb9d 100644 --- a/packages/react-native-editor/CHANGELOG.md +++ b/packages/react-native-editor/CHANGELOG.md @@ -12,6 +12,7 @@ For each user feature we should also add a importance categorization label to i ## Unreleased - [*] Fix a crash when pasting file images and special comment markup [#60476] - [*] Update Aztec to v2.1.2 [#61007] +- [*] KeyboardAwareFlatList - Enable FlatList virtualization for iOS [#59833] ## 1.117.0 - [*] Add empty fallback option for the BottomSheetSelectControl component [#60333]