mirror of
https://github.com/4ian/GDevelop.git
synced 2025-10-15 10:19:04 +00:00
Prevent loss of focus when editing the value of an object variable in the instance properties panel (#5247)
This commit is contained in:
@@ -56,7 +56,7 @@ export type ParameterFieldProps = {|
|
||||
export type FieldFocusFunction = (
|
||||
?{|
|
||||
selectAll?: boolean,
|
||||
caretPosition?: 'end',
|
||||
caretPosition?: 'end' | number | null,
|
||||
|}
|
||||
) => void;
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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(
|
||||
|
31
newIDE/app/src/VariablesList/useRefocusField.js
Normal file
31
newIDE/app/src/VariablesList/useRefocusField.js
Normal 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;
|
Reference in New Issue
Block a user