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 da367ecc0ebea6..810e23e4c1442a 100644
--- a/packages/block-editor/src/components/block-list/index.native.js
+++ b/packages/block-editor/src/components/block-list/index.native.js
@@ -1,12 +1,12 @@
/**
* External dependencies
*/
-import { View, Platform, TouchableWithoutFeedback } from 'react-native';
+import { View, Platform, Pressable } from 'react-native';
/**
* WordPress dependencies
*/
-import { useRef, useState } from '@wordpress/element';
+import { useRef, useState, useCallback } from '@wordpress/element';
import { useSelect, useDispatch } from '@wordpress/data';
import { createBlock } from '@wordpress/blocks';
import {
@@ -372,20 +372,20 @@ function Footer( {
renderFooterAppender,
withFooter,
} ) {
+ const onAddParagraphBlock = useCallback( () => {
+ const paragraphBlock = createBlock( 'core/paragraph' );
+ addBlockToEndOfPost( paragraphBlock );
+ }, [ addBlockToEndOfPost ] );
+
if ( ! isReadOnly && withFooter ) {
return (
- <>
- {
- const paragraphBlock = createBlock( 'core/paragraph' );
- addBlockToEndOfPost( paragraphBlock );
- } }
- >
-
-
- >
+
+
+
);
} else if ( renderFooterAppender ) {
return { renderFooterAppender() };
diff --git a/packages/block-editor/src/components/default-block-appender/index.native.js b/packages/block-editor/src/components/default-block-appender/index.native.js
index dae0750b8e30d4..82ff8b7c8d4029 100644
--- a/packages/block-editor/src/components/default-block-appender/index.native.js
+++ b/packages/block-editor/src/components/default-block-appender/index.native.js
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import { TouchableWithoutFeedback, View } from 'react-native';
+import { Pressable, View } from 'react-native';
/**
* WordPress dependencies
@@ -20,6 +20,9 @@ import BlockInsertionPoint from '../block-list/insertion-point';
import styles from './style.scss';
import { store as blockEditorStore } from '../../store';
+const hitSlop = { top: 22, bottom: 22, left: 22, right: 22 };
+const noop = () => {};
+
export function DefaultBlockAppender( {
isLocked,
isVisible,
@@ -37,22 +40,21 @@ export function DefaultBlockAppender( {
? decodeEntities( placeholder )
: __( 'Start writing…' );
+ const appenderStyles = [
+ styles.blockHolder,
+ showSeparator && containerStyle,
+ ];
+
return (
-
-
+
+
{ showSeparator ? (
) : (
- {} } />
+
) }
-
+
);
}
diff --git a/packages/react-native-aztec/src/AztecInputState.js b/packages/react-native-aztec/src/AztecInputState.js
index 8a1737118d1d13..5f0a1dd7596284 100644
--- a/packages/react-native-aztec/src/AztecInputState.js
+++ b/packages/react-native-aztec/src/AztecInputState.js
@@ -116,6 +116,15 @@ export const notifyInputChange = () => {
}
};
+/**
+ * Sets the current focused element ref held within TextInputState.
+ *
+ * @param {RefObject} element Element to be set as the focused element.
+ */
+export const focusInput = ( element ) => {
+ TextInputState.focusInput( element );
+};
+
/**
* Focuses the specified element.
*
diff --git a/packages/react-native-aztec/src/AztecView.js b/packages/react-native-aztec/src/AztecView.js
index 4d90d13974c8ec..e05737f5fb0f99 100644
--- a/packages/react-native-aztec/src/AztecView.js
+++ b/packages/react-native-aztec/src/AztecView.js
@@ -237,14 +237,49 @@ class AztecView extends Component {
}
_onAztecFocus( event ) {
- // IMPORTANT: the onFocus events from Aztec are thrown away on Android as these are handled by onPress() in the upper level.
- // It's necessary to do this otherwise onFocus may be set by `{...otherProps}` and thus the onPress + onFocus
- // combination generate an infinite loop as described in https://github.com/wordpress-mobile/gutenberg-mobile/issues/302
- // For iOS, this is necessary to let the system know when Aztec was focused programatically.
- if ( Platform.OS === 'ios' ) {
+ // IMPORTANT: This function serves two purposes:
+ //
+ // Android: This intentional no-op function prevents focus loops originating
+ // when the native Aztec module programmatically focuses the instance. The
+ // no-op is explicitly passed as an `onFocus` prop to avoid future prop
+ // spreading from inadvertently introducing focus loops. The user-facing
+ // focus of the element is handled by `onPress` instead.
+ //
+ // See: https://github.com/wordpress-mobile/gutenberg-mobile/issues/302
+ //
+ // iOS: Programmatic focus from the native Aztec module is required to
+ // ensure the React-based `TextStateInput` ref is properly set when focus
+ // is *returned* to an instance, e.g. dismissing a bottom sheet. If the ref
+ // is not updated, attempts to dismiss the keyboard via the `ToolbarButton`
+ // will fail.
+ //
+ // See: https://github.com/wordpress-mobile/gutenberg-mobile/issues/702
+ if (
+ // The Android keyboard is, likely erroneously, already dismissed in the
+ // contexts where programmatic focus may be required on iOS.
+ //
+ // - https://github.com/WordPress/gutenberg/issues/28748
+ // - https://github.com/WordPress/gutenberg/issues/29048
+ // - https://github.com/wordpress-mobile/WordPress-Android/issues/16167
+ Platform.OS === 'ios'
+ ) {
this.updateCaretData( event );
- this._onPress( event );
+ if ( ! this.isFocused() ) {
+ // Programmatically swapping input focus creates an infinite loop if the
+ // user taps a different input in between the programmatic focus and
+ // the resulting update to the React Native TextInputState focused element
+ // ref. To mitigate this, the below updates the focused element ref, but
+ // does not call the native focus methods.
+ //
+ // See: https://github.com/wordpress-mobile/WordPress-iOS/issues/18783
+ AztecInputState.focusInput( this.aztecViewRef.current );
+
+ // Calling _onFocus is needed to trigger provided onFocus callbacks
+ // which are needed to prevent undesired results like having a focused
+ // TextInput when another element has the focus.
+ this._onFocus( event );
+ }
}
}
@@ -285,9 +320,7 @@ class AztecView extends Component {
onBackspace={ this.props.onKeyDown && this._onBackspace }
onKeyDown={ this.props.onKeyDown && this._onKeyDown }
deleteEnter={ this.props.deleteEnter }
- // IMPORTANT: the onFocus events are thrown away as these are handled by onPress() in the upper level.
- // It's necessary to do this otherwise onFocus may be set by `{...otherProps}` and thus the onPress + onFocus
- // combination generate an infinite loop as described in https://github.com/wordpress-mobile/gutenberg-mobile/issues/302
+ // IMPORTANT: Do not remove the `onFocus` prop, see `_onAztecFocus`
onFocus={ this._onAztecFocus }
onBlur={ this._onBlur }
ref={ this.aztecViewRef }
diff --git a/packages/react-native-editor/CHANGELOG.md b/packages/react-native-editor/CHANGELOG.md
index 4ddfda16c8c78d..e757ad02bd62d5 100644
--- a/packages/react-native-editor/CHANGELOG.md
+++ b/packages/react-native-editor/CHANGELOG.md
@@ -11,6 +11,9 @@ For each user feature we should also add a importance categorization label to i
## Unreleased
+## 1.100.2
+- [**] Fix iOS Focus loop for RichText components [#53217]
+
## 1.100.1
- [**] Add WP hook for registering non-core blocks [#52791]