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] Fix issues related to editing text and dragging gesture #40480

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,22 @@ import Animated, {
ZoomInEasyDown,
ZoomOutEasyDown,
} from 'react-native-reanimated';
import TextInputState from 'react-native/Libraries/Components/TextInput/TextInputState';

/**
* WordPress dependencies
*/
import { Draggable, DraggableTrigger } from '@wordpress/components';
import { select, useSelect, useDispatch } from '@wordpress/data';
import { useEffect, useRef, useState, Platform } from '@wordpress/element';
import {
useCallback,
useEffect,
useRef,
useState,
Platform,
} from '@wordpress/element';
import { getBlockType } from '@wordpress/blocks';
import { generateHapticFeedback } from '@wordpress/react-native-bridge';
import RCTAztecView from '@wordpress/react-native-aztec';

/**
* Internal dependencies
Expand Down Expand Up @@ -256,6 +262,9 @@ const BlockDraggableWrapper = ( { children } ) => {
*/
const BlockDraggable = ( { clientId, children, enabled = true } ) => {
const wasBeingDragged = useRef( false );
const [ isEditingText, setIsEditingText ] = useState(
RCTAztecView.InputState.isFocused()
);

const draggingAnimation = {
opacity: useSharedValue( 1 ),
Expand All @@ -275,27 +284,28 @@ const BlockDraggable = ( { clientId, children, enabled = true } ) => {
);
};

const { isDraggable, isBeingDragged, canDragBlock } = useSelect(
const { isDraggable, isBeingDragged, isBlockSelected } = useSelect(
( _select ) => {
const {
getBlockRootClientId,
getTemplateLock,
isBlockBeingDragged,
hasSelectedBlock,
getSelectedBlockClientId,
hasSelectedInnerBlock,
} = _select( blockEditorStore );
const rootClientId = getBlockRootClientId( clientId );
const templateLock = rootClientId
? getTemplateLock( rootClientId )
: null;
const isAnyTextInputFocused =
TextInputState.currentlyFocusedInput() !== null;
const selectedBlockClientId = getSelectedBlockClientId();

return {
isBeingDragged: isBlockBeingDragged( clientId ),
isDraggable: 'all' !== templateLock,
canDragBlock: hasSelectedBlock()
? ! isAnyTextInputFocused
: true,
isBlockSelected:
selectedBlockClientId &&
( selectedBlockClientId === clientId ||
hasSelectedInnerBlock( clientId, true ) ),
};
},
[ clientId ]
Expand All @@ -312,6 +322,24 @@ const BlockDraggable = ( { clientId, children, enabled = true } ) => {
wasBeingDragged.current = isBeingDragged;
}, [ isBeingDragged ] );

const onFocusChangeAztec = useCallback( ( { isFocused } ) => {
setIsEditingText( isFocused );
}, [] );

useEffect( () => {
RCTAztecView.InputState.addFocusChangeListener( onFocusChangeAztec );
return () => {
RCTAztecView.InputState.removeFocusChangeListener(
onFocusChangeAztec
);
};
}, [] );

const onLongPressDraggable = useCallback( () => {
// Ensure that no text input is focused when starting the dragging gesture in order to prevent conflicts with text editing.
RCTAztecView.InputState.blurCurrentFocusedElement();
}, [] );

const animatedWrapperStyles = useAnimatedStyle( () => {
return {
opacity: draggingAnimation.opacity.value,
Expand All @@ -322,6 +350,8 @@ const BlockDraggable = ( { clientId, children, enabled = true } ) => {
styles[ 'draggable-wrapper__container' ],
];

const canDragBlock = enabled && ( ! isBlockSelected || ! isEditingText );

if ( ! isDraggable ) {
return children( { isDraggable: false } );
}
Expand All @@ -340,6 +370,7 @@ const BlockDraggable = ( { clientId, children, enabled = true } ) => {
: DEFAULT_LONG_PRESS_MIN_DURATION,
android: DEFAULT_LONG_PRESS_MIN_DURATION,
} ) }
onLongPress={ onLongPressDraggable }
>
<Animated.View style={ wrapperStyles }>
{ children( { isDraggable: true } ) }
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-aztec/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ For each user feature we should also add a importance categorization label to i

## Unreleased
- [*] Bump Aztec-Android version to v1.5.1 [#36861]
- [*] Add text input state to Aztec view [#40480]

## 1.50.0
- [*] Block split/merge fix for a (never shipped) regression (Android only) [#29683]
Expand Down
111 changes: 111 additions & 0 deletions packages/react-native-aztec/src/AztecInputState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/**
* External dependencies
*/
import TextInputState from 'react-native/Libraries/Components/TextInput/TextInputState';

/** @typedef {import('@wordpress/element').RefObject} RefObject */

const focusChangeListeners = [];

let currentFocusedElement = null;

/**
* Adds a listener that will be called in the following cases:
*
* - An Aztec view is being focused and all were previously unfocused.
* - An Aztec view is being unfocused and none will be focused.
*
* Note that this listener won't be called when switching focus between Aztec views.
*
* @param {Function} listener
*/
export const addFocusChangeListener = ( listener ) => {
focusChangeListeners.push( listener );
};

/**
* Removes a listener from the focus change listeners list.
*
* @param {Function} listener
*/
export const removeFocusChangeListener = ( listener ) => {
const itemIndex = focusChangeListeners.indexOf( listener );
if ( itemIndex !== -1 ) {
focusChangeListeners.splice( itemIndex, 1 );
}
};

/**
* Notifies listeners about changes in focus.
*
* @param {Object} event Event data to be notified to listeners.
* @param {boolean} event.isFocused True if any Aztec view is currently focused.
*/
const notifyListeners = ( { isFocused } ) => {
fluiddot marked this conversation as resolved.
Show resolved Hide resolved
focusChangeListeners.forEach( ( listener ) => {
listener( { isFocused } );
} );
};

/**
* Determines if any Aztec view is focused.
*
* @return {boolean} True if focused.
*/
export const isFocused = () => {
return currentFocusedElement !== null;
};

/**
* Returns the current focused element.
*
* @return {RefObject} Ref of the current focused element or `null` otherwise.
*/
export const getCurrentFocusedElement = () => {
return currentFocusedElement;
};

/**
* Notifies that an Aztec view is being focused or unfocused.
*/
export const notifyInputChange = () => {
const focusedInput = TextInputState.currentlyFocusedInput();
const hasAnyFocusedInput = focusedInput !== null;

if ( hasAnyFocusedInput ) {
if ( ! currentFocusedElement ) {
notifyListeners( { isFocused: true } );
}
currentFocusedElement = focusedInput;
} else if ( currentFocusedElement ) {
notifyListeners( { isFocused: false } );
currentFocusedElement = null;
}
};

/**
* Focuses the specified element.
*
* @param {RefObject} element Element to be focused.
*/
export const focus = ( element ) => {
TextInputState.focusTextInput( element );
};

/**
* Unfocuses the specified element.
*
* @param {RefObject} element Element to be unfocused.
*/
export const blur = ( element ) => {
TextInputState.blurTextInput( element );
};

/**
* Unfocuses the current focused element.
*/
export const blurCurrentFocusedElement = () => {
if ( isFocused() ) {
blur( getCurrentFocusedElement() );
}
};
23 changes: 17 additions & 6 deletions packages/react-native-aztec/src/AztecView.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,18 @@ import {
TouchableWithoutFeedback,
Platform,
} from 'react-native';
import TextInputState from 'react-native/Libraries/Components/TextInput/TextInputState';

/**
* WordPress dependencies
*/
import { Component, createRef } from '@wordpress/element';
import { ENTER, BACKSPACE } from '@wordpress/keycodes';

/**
* Internal dependencies
*/
import * as AztecInputState from './AztecInputState';

const AztecManager = UIManager.getViewManagerConfig( 'RCTAztecView' );

class AztecView extends Component {
Expand Down Expand Up @@ -117,6 +122,8 @@ class AztecView extends Component {
}

_onFocus( event ) {
AztecInputState.notifyInputChange();

if ( ! this.props.onFocus ) {
return;
}
Expand All @@ -127,7 +134,9 @@ class AztecView extends Component {

_onBlur( event ) {
this.selectionEndCaretY = null;
TextInputState.blurTextInput( this.aztecViewRef.current );

AztecInputState.blur( this.aztecViewRef.current );
AztecInputState.notifyInputChange();

if ( ! this.props.onBlur ) {
return;
Expand Down Expand Up @@ -179,16 +188,16 @@ class AztecView extends Component {
}

blur() {
TextInputState.blurTextInput( this.aztecViewRef.current );
AztecInputState.blur( this.aztecViewRef.current );
}

focus() {
TextInputState.focusTextInput( this.aztecViewRef.current );
AztecInputState.focus( this.aztecViewRef.current );
}

isFocused() {
const focusedField = TextInputState.currentlyFocusedInput();
return focusedField && focusedField === this.aztecViewRef.current;
const focusedElement = AztecInputState.getCurrentFocusedElement();
return focusedElement && focusedElement === this.aztecViewRef.current;
}

_onPress( event ) {
Expand Down Expand Up @@ -251,4 +260,6 @@ class AztecView extends Component {

const RCTAztecView = requireNativeComponent( 'RCTAztecView', AztecView );

AztecView.InputState = AztecInputState;

export default AztecView;
Loading