Skip to content

Commit

Permalink
Prevent loss of focus when editing the value of an object variable in…
Browse files Browse the repository at this point in the history
… the instance properties panel (#5247)
  • Loading branch information
AlexandreSi committed Apr 21, 2023
1 parent 55c7c4e commit 52fcf52
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export type ParameterFieldProps = {|
export type FieldFocusFunction = (
?{|
selectAll?: boolean,
caretPosition?: 'end',
caretPosition?: 'end' | number | null,
|}
) => void;

Expand Down
6 changes: 6 additions & 0 deletions newIDE/app/src/UI/SemiControlledTextField.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export type SemiControlledTextFieldInterface = {|
forceSetSelection: (start: number, end: number) => void,
getInputNode: () => ?HTMLInputElement,
getFieldWidth: () => ?number,
getCaretPosition: () => ?number,
|};

/**
Expand Down Expand Up @@ -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 {
Expand Down
13 changes: 13 additions & 0 deletions newIDE/app/src/UI/TextField.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ export type TextFieldInterface = {|
blur: () => void,
getInputNode: () => ?HTMLInputElement,
getFieldWidth: () => ?number,
getCaretPosition: () => ?number,
|};

/**
Expand Down Expand Up @@ -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);
}
}
};

Expand All @@ -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;
Expand Down
52 changes: 30 additions & 22 deletions newIDE/app/src/VariablesList/VariablesList.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -794,7 +787,7 @@ const VariablesList = ({ onComputeAllVariableNames, ...props }: Props) => {
);
_onChange();
setSelectedNodes([newName]);
setVariablePtrToFocus(variable.ptr);
refocusNameField({ identifier: variable.ptr });
return;
}

Expand All @@ -819,7 +812,7 @@ const VariablesList = ({ onComputeAllVariableNames, ...props }: Props) => {
);
_onChange();
setSelectedNodes([newName]);
setVariablePtrToFocus(variable.ptr);
refocusNameField({ identifier: variable.ptr });
};

const renderVariableAndChildrenRows = (
Expand Down Expand Up @@ -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'
}
Expand Down Expand Up @@ -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(
Expand Down
31 changes: 31 additions & 0 deletions newIDE/app/src/VariablesList/useRefocusField.js
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit 52fcf52

Please sign in to comment.