Prevent loss of focus when editing the value of an object variable in the instance properties panel (#5247)

This commit is contained in:
AlexandreS
2023-04-21 13:39:56 +02:00
committed by GitHub
parent 55c7c4e8bf
commit 52fcf52ecc
5 changed files with 81 additions and 23 deletions

View File

@@ -56,7 +56,7 @@ export type ParameterFieldProps = {|
export type FieldFocusFunction = (
?{|
selectAll?: boolean,
caretPosition?: 'end',
caretPosition?: 'end' | number | null,
|}
) => void;

View File

@@ -56,6 +56,7 @@ export type SemiControlledTextFieldInterface = {|
forceSetSelection: (start: number, end: number) => void,
getInputNode: () => ?HTMLInputElement,
getFieldWidth: () => ?number,
getCaretPosition: () => ?number,
|};
/**
@@ -96,12 +97,17 @@ const SemiControlledTextField = React.forwardRef<
if (textFieldRef.current) return textFieldRef.current.getFieldWidth();
};
const getCaretPosition = () => {
if (textFieldRef.current) return textFieldRef.current.getCaretPosition();
};
React.useImperativeHandle(ref, () => ({
focus,
getInputNode,
forceSetSelection,
forceSetValue,
getFieldWidth,
getCaretPosition,
}));
const {

View File

@@ -176,6 +176,7 @@ export type TextFieldInterface = {|
blur: () => void,
getInputNode: () => ?HTMLInputElement,
getFieldWidth: () => ?number,
getCaretPosition: () => ?number,
|};
/**
@@ -203,6 +204,10 @@ const TextField = React.forwardRef<Props, TextFieldInterface>((props, ref) => {
props.value.toString().length
);
}
if (options && Number.isInteger(options.caretPosition) && props.value) {
const position = Number(options.caretPosition);
input.setSelectionRange(position, position);
}
}
};
@@ -227,11 +232,19 @@ const TextField = React.forwardRef<Props, TextFieldInterface>((props, ref) => {
return null;
};
const getCaretPosition = () => {
if (inputRef.current) {
return inputRef.current.selectionStart;
}
return null;
};
React.useImperativeHandle(ref, () => ({
focus,
blur,
getInputNode,
getFieldWidth,
getCaretPosition,
}));
const onChange = props.onChange || undefined;

View File

@@ -14,7 +14,9 @@ import ChevronRight from '../UI/CustomSvgIcons/ChevronArrowRight';
import ChevronBottom from '../UI/CustomSvgIcons/ChevronArrowBottom';
import { Column, Line, Spacer } from '../UI/Grid';
import SemiControlledTextField from '../UI/SemiControlledTextField';
import SemiControlledTextField, {
type SemiControlledTextFieldInterface,
} from '../UI/SemiControlledTextField';
import IconButton from '../UI/IconButton';
import { DragHandleIcon } from '../UI/DragHandle';
import { makeDragSourceAndDropTarget } from '../UI/DragAndDrop/DragSourceAndDropTarget';
@@ -73,6 +75,7 @@ import VariablesListToolbar from './VariablesListToolbar';
import { normalizeString } from '../Utils/Search';
import { I18n } from '@lingui/react';
import SwitchHorizontal from '../UI/CustomSvgIcons/SwitchHorizontal';
import useRefocusField from './useRefocusField';
const gd: libGDevelop = global.gd;
const DragSourceAndDropTarget = makeDragSourceAndDropTarget('variable-editor');
@@ -177,12 +180,16 @@ const VariablesList = ({ onComputeAllVariableNames, ...props }: Props) => {
const [nameErrors, setNameErrors] = React.useState<{ [number]: React.Node }>(
{}
);
const topLevelVariableNameInputRefs = React.useRef<{
const topLevelVariableNameInputRefs = React.useRef<{|
[number]: SemiControlledAutoCompleteInterface,
}>({});
const [variablePtrToFocus, setVariablePtrToFocus] = React.useState<?number>(
null
);
|}>({});
const topLevelVariableValueInputRefs = React.useRef<{|
[number]: SemiControlledTextFieldInterface,
|}>({});
// $FlowFixMe - Hard to fix issue regarding strict checking with interface.
const refocusNameField = useRefocusField(topLevelVariableNameInputRefs);
// $FlowFixMe - Hard to fix issue regarding strict checking with interface.
const refocusValueField = useRefocusField(topLevelVariableValueInputRefs);
const gdevelopTheme = React.useContext(GDevelopThemeContext);
const draggedNodeId = React.useRef<?string>(null);
const forceUpdate = useForceUpdate();
@@ -217,20 +224,6 @@ const VariablesList = ({ onComputeAllVariableNames, ...props }: Props) => {
[searchText, triggerSearch]
);
React.useEffect(
() => {
if (variablePtrToFocus) {
const inputRef =
topLevelVariableNameInputRefs.current[variablePtrToFocus];
if (inputRef) {
inputRef.focus();
setVariablePtrToFocus(null);
}
}
},
[variablePtrToFocus]
);
const shouldHideExpandIcons =
!hasVariablesContainerSubChildren(props.variablesContainer) &&
(props.inheritedVariablesContainer
@@ -794,7 +787,7 @@ const VariablesList = ({ onComputeAllVariableNames, ...props }: Props) => {
);
_onChange();
setSelectedNodes([newName]);
setVariablePtrToFocus(variable.ptr);
refocusNameField({ identifier: variable.ptr });
return;
}
@@ -819,7 +812,7 @@ const VariablesList = ({ onComputeAllVariableNames, ...props }: Props) => {
);
_onChange();
setSelectedNodes([newName]);
setVariablePtrToFocus(variable.ptr);
refocusNameField({ identifier: variable.ptr });
};
const renderVariableAndChildrenRows = (
@@ -1082,6 +1075,13 @@ const VariablesList = ({ onComputeAllVariableNames, ...props }: Props) => {
) : (
<SemiControlledTextField
margin="none"
ref={element => {
if (depth === 0 && element) {
topLevelVariableValueInputRefs.current[
variable.ptr
] = element;
}
}}
type={
type === gd.Variable.Number ? 'number' : 'text'
}
@@ -1356,6 +1356,14 @@ const VariablesList = ({ onComputeAllVariableNames, ...props }: Props) => {
} else {
setSelectedNodes([...newSelectedNodes, name]);
}
const currentlyFocusedValueField =
topLevelVariableValueInputRefs.current[changedInheritedVariable.ptr];
refocusValueField({
identifier: variable.ptr,
caretPosition: currentlyFocusedValueField
? currentlyFocusedValueField.getCaretPosition()
: null,
});
newVariable.delete();
} else {
const { variable: changedVariable } = getVariableContextFromNodeId(

View File

@@ -0,0 +1,31 @@
// @flow
import React from 'react';
const useRefocusField = (fieldRefs: {|
current: {|
[identifier: number]: {|
+focus: (?{| caretPosition: ?('end' | number) |}) => void,
|},
|},
|}) => {
const [fieldToFocus, setFieldToFocus] = React.useState<?{
identifier: number,
caretPosition?: ?number,
}>(null);
React.useEffect(
() => {
if (fieldToFocus) {
const fieldRef = fieldRefs.current[fieldToFocus.identifier];
if (fieldRef) {
fieldRef.focus({ caretPosition: fieldToFocus.caretPosition });
setFieldToFocus(null);
}
}
},
[fieldToFocus, fieldRefs]
);
return setFieldToFocus;
};
export default useRefocusField;