diff --git a/native/chat/chat-input-bar.react.js b/native/chat/chat-input-bar.react.js --- a/native/chat/chat-input-bar.react.js +++ b/native/chat/chat-input-bar.react.js @@ -69,6 +69,8 @@ import Button from '../components/button.react'; import ClearableTextInput from '../components/clearable-text-input.react'; +import type { SyncedSelectionData } from '../components/selectable-text-input'; +import SelectableTextInput from '../components/selectable-text-input.react'; import SWMansionIcon from '../components/swmansion-icon.react'; import { type InputState, InputStateContext } from '../input/input-state'; import { getKeyboardHeight } from '../keyboard/keyboard'; @@ -89,7 +91,7 @@ } from '../navigation/route-names'; import { useSelector } from '../redux/redux-utils'; import { type Colors, useStyles, useColors } from '../themes/colors'; -import type { LayoutEvent, SelectionChangeEvent } from '../types/react-native'; +import type { LayoutEvent } from '../types/react-native'; import { type AnimatedViewStyle, AnimatedView } from '../types/styles'; import { runTiming } from '../utils/animation-utils'; import { nativeTypeaheadRegex } from '../utils/typeahead-utils'; @@ -155,11 +157,12 @@ +text: string, +textEdited: boolean, +buttonsExpanded: boolean, - +selection: Selection, + +selectionState: SyncedSelectionData, }; class ChatInputBar extends React.PureComponent { textInput: ?React.ElementRef; clearableTextInput: ?ClearableTextInput; + selectableTextInput: ?React.ElementRef; expandoButtonsOpen: Value; targetExpandoButtonsOpen: Value; @@ -178,7 +181,7 @@ text: props.draft, textEdited: false, buttonsExpanded: true, - selection: { start: 0, end: 0 }, + selectionState: { text: props.draft, selection: { start: 0, end: 0 } }, }; this.setUpActionIconAnimations(); @@ -471,8 +474,8 @@ } const typeaheadRegexMatches = getTypeaheadRegexMatches( - this.state.text, - this.state.selection, + this.state.selectionState.text, + this.state.selectionState.selection, nativeTypeaheadRegex, ); @@ -607,18 +610,19 @@ {this.state.buttonsExpanded ? null : expandoButton} - @@ -649,13 +653,19 @@ this.clearableTextInput = clearableTextInput; }; + selectableTextInputRef = ( + selectableTextInput: ?React.ElementRef, + ) => { + this.selectableTextInput = selectableTextInput; + }; + updateText = (text: string) => { this.setState({ text, textEdited: true }); this.saveDraft(text); }; - updateSelection: (event: SelectionChangeEvent) => void = event => { - this.setState({ selection: event.nativeEvent.selection }); + updateSelectionState: (data: SyncedSelectionData) => void = data => { + this.setState({ selectionState: data }); }; saveDraft = _throttle(text => { @@ -669,10 +679,11 @@ }, 400); focusAndUpdateTextAndSelection = (text: string, selection: Selection) => { + this.selectableTextInput?.prepareForSelectionMutation(text, selection); this.setState({ text, textEdited: true, - selection, + selectionState: { text, selection }, }); this.saveDraft(text); diff --git a/native/components/selectable-text-input.js b/native/components/selectable-text-input.js new file mode 100644 --- /dev/null +++ b/native/components/selectable-text-input.js @@ -0,0 +1,25 @@ +// @flow + +import * as React from 'react'; + +import type { Selection } from 'lib/shared/typeahead-utils'; + +import type { ClearableTextInputProps } from './clearable-text-input'; +import ClearableTextInput from './clearable-text-input.react'; + +export type SyncedSelectionData = { + +text: string, + +selection: Selection, +}; + +export type SelectableTextInputProps = { + ...ClearableTextInputProps, + +clearableTextInputRef: ( + clearableTextInput: ?React.ElementRef, + ) => mixed, + +onUpdateSyncedSelectionData: (data: SyncedSelectionData) => mixed, +}; + +export type SelectableTextInputRef = { + +prepareForSelectionMutation: (text: string, selection: Selection) => void, +}; diff --git a/native/components/selectable-text-input.react.ios.js b/native/components/selectable-text-input.react.ios.js new file mode 100644 --- /dev/null +++ b/native/components/selectable-text-input.react.ios.js @@ -0,0 +1,44 @@ +// @flow + +import * as React from 'react'; + +import ClearableTextInput from './clearable-text-input.react'; +import type { + SelectableTextInputProps, + SelectableTextInputRef, +} from './selectable-text-input'; + +const SelectableTextInput = React.forwardRef(function BaseSelectableTextInput( + props, + ref, +): React.Node { + const { clearableTextInputRef, onUpdateSyncedSelectionData, ...rest } = props; + + const clearableTextInputRefCallback = React.useCallback( + (clearableTextInput: ?React.ElementRef) => { + clearableTextInputRef(clearableTextInput); + }, + [clearableTextInputRef], + ); + + const prepareForSelectionMutation = React.useCallback(() => {}, []); + const ourRef = React.useMemo( + () => ({ + prepareForSelectionMutation, + }), + [prepareForSelectionMutation], + ); + + React.useImperativeHandle(ref, () => ourRef, [ourRef]); + + return ; +}); + +const MemoizedSelectableTextInput: React.AbstractComponent< + SelectableTextInputProps, + SelectableTextInputRef, +> = React.memo( + SelectableTextInput, +); + +export default MemoizedSelectableTextInput; diff --git a/native/components/selectable-text-input.react.js b/native/components/selectable-text-input.react.js new file mode 100644 --- /dev/null +++ b/native/components/selectable-text-input.react.js @@ -0,0 +1,44 @@ +// @flow + +import * as React from 'react'; + +import ClearableTextInput from './clearable-text-input.react'; +import type { + SelectableTextInputProps, + SelectableTextInputRef, +} from './selectable-text-input'; + +const SelectableTextInput = React.forwardRef(function BaseSelectableTextInput( + props, + ref, +): React.Node { + const { clearableTextInputRef, onUpdateSyncedSelectionData, ...rest } = props; + + const clearableTextInputRefCallback = React.useCallback( + (clearableTextInput: ?React.ElementRef) => { + clearableTextInputRef(clearableTextInput); + }, + [clearableTextInputRef], + ); + + const prepareForSelectionMutation = React.useCallback(() => {}, []); + const ourRef = React.useMemo( + () => ({ + prepareForSelectionMutation, + }), + [prepareForSelectionMutation], + ); + + React.useImperativeHandle(ref, () => ourRef, [ourRef]); + + return ; +}); + +const MemoizedSelectableTextInput: React.AbstractComponent< + SelectableTextInputProps, + SelectableTextInputRef, +> = React.memo( + SelectableTextInput, +); + +export default MemoizedSelectableTextInput;