refactor(web): remove typescript
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
import * as React from 'react';
|
||||
import { useController, useFormContext } from 'react-hook-form';
|
||||
import { IconButton } from '@mui/material';
|
||||
import FormHelperText from '@mui/material/FormHelperText';
|
||||
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
|
||||
import ClearIcon from '@mui/icons-material/Clear';
|
||||
import { ActionButtonsWrapper } from './style';
|
||||
import ClickAwayListener from '@mui/base/ClickAwayListener';
|
||||
import InputLabel from '@mui/material/InputLabel';
|
||||
import { createEditor } from 'slate';
|
||||
import { Editable, ReactEditor } from 'slate-react';
|
||||
import Slate from 'components/Slate';
|
||||
import Element from 'components/Slate/Element';
|
||||
import {
|
||||
serialize,
|
||||
deserialize,
|
||||
insertVariable,
|
||||
customizeEditor,
|
||||
resetEditor,
|
||||
overrideEditorValue,
|
||||
focusEditor,
|
||||
} from 'components/Slate/utils';
|
||||
import {
|
||||
FakeInput,
|
||||
InputLabelWrapper,
|
||||
ChildrenWrapper,
|
||||
} from 'components/PowerInput/style';
|
||||
import CustomOptions from './CustomOptions';
|
||||
import { processStepWithExecutions } from 'components/PowerInput/data';
|
||||
import { StepExecutionsContext } from 'contexts/StepExecutions';
|
||||
function ControlledCustomAutocomplete(props) {
|
||||
const {
|
||||
defaultValue = '',
|
||||
name,
|
||||
label,
|
||||
required,
|
||||
options = [],
|
||||
dependsOn = [],
|
||||
description,
|
||||
loading,
|
||||
disabled,
|
||||
shouldUnregister,
|
||||
} = props;
|
||||
const { control, watch } = useFormContext();
|
||||
const { field, fieldState } = useController({
|
||||
control,
|
||||
name,
|
||||
defaultValue,
|
||||
rules: { required },
|
||||
shouldUnregister,
|
||||
});
|
||||
const {
|
||||
value,
|
||||
onChange: controllerOnChange,
|
||||
onBlur: controllerOnBlur,
|
||||
} = field;
|
||||
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
|
||||
const [isInitialValueSet, setInitialValue] = React.useState(false);
|
||||
const [isSingleChoice, setSingleChoice] = React.useState(undefined);
|
||||
const priorStepsWithExecutions = React.useContext(StepExecutionsContext);
|
||||
const editorRef = React.useRef(null);
|
||||
const renderElement = React.useCallback(
|
||||
(props) => <Element {...props} disabled={disabled} />,
|
||||
[disabled],
|
||||
);
|
||||
const [editor] = React.useState(() => customizeEditor(createEditor()));
|
||||
const [showVariableSuggestions, setShowVariableSuggestions] =
|
||||
React.useState(false);
|
||||
let dependsOnValues = [];
|
||||
if (dependsOn?.length) {
|
||||
dependsOnValues = watch(dependsOn);
|
||||
}
|
||||
React.useEffect(() => {
|
||||
const ref = ReactEditor.toDOMNode(editor, editor);
|
||||
resizeObserver.observe(ref);
|
||||
return () => resizeObserver.unobserve(ref);
|
||||
}, []);
|
||||
const promoteValue = () => {
|
||||
const serializedValue = serialize(editor.children);
|
||||
controllerOnChange(serializedValue);
|
||||
};
|
||||
const resizeObserver = React.useMemo(function syncCustomOptionsPosition() {
|
||||
return new ResizeObserver(() => {
|
||||
forceUpdate();
|
||||
});
|
||||
}, []);
|
||||
React.useEffect(() => {
|
||||
const hasDependencies = dependsOnValues.length;
|
||||
if (hasDependencies) {
|
||||
// Reset the field when a dependent has been updated
|
||||
resetEditor(editor);
|
||||
}
|
||||
}, dependsOnValues);
|
||||
React.useEffect(
|
||||
function updateInitialValue() {
|
||||
const hasOptions = options.length;
|
||||
const isOptionsLoaded = loading === false;
|
||||
if (!isInitialValueSet && hasOptions && isOptionsLoaded) {
|
||||
setInitialValue(true);
|
||||
const option = options.find((option) => option.value === value);
|
||||
if (option) {
|
||||
overrideEditorValue(editor, { option, focus: false });
|
||||
setSingleChoice(true);
|
||||
} else if (value) {
|
||||
setSingleChoice(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
[isInitialValueSet, options, loading],
|
||||
);
|
||||
React.useEffect(() => {
|
||||
if (!showVariableSuggestions && value !== serialize(editor.children)) {
|
||||
promoteValue();
|
||||
}
|
||||
}, [showVariableSuggestions]);
|
||||
const hideSuggestionsOnShift = (event) => {
|
||||
if (event.code === 'Tab') {
|
||||
setShowVariableSuggestions(false);
|
||||
}
|
||||
};
|
||||
const handleKeyDown = (event) => {
|
||||
hideSuggestionsOnShift(event);
|
||||
if (event.code === 'Tab') {
|
||||
promoteValue();
|
||||
}
|
||||
if (isSingleChoice && event.code !== 'Tab') {
|
||||
event.preventDefault();
|
||||
}
|
||||
};
|
||||
const stepsWithVariables = React.useMemo(() => {
|
||||
return processStepWithExecutions(priorStepsWithExecutions);
|
||||
}, [priorStepsWithExecutions]);
|
||||
const handleVariableSuggestionClick = React.useCallback(
|
||||
(variable) => {
|
||||
insertVariable(editor, variable, stepsWithVariables);
|
||||
},
|
||||
[stepsWithVariables],
|
||||
);
|
||||
const handleOptionClick = React.useCallback(
|
||||
(event, option) => {
|
||||
event.stopPropagation();
|
||||
overrideEditorValue(editor, { option, focus: false });
|
||||
setShowVariableSuggestions(false);
|
||||
setSingleChoice(true);
|
||||
},
|
||||
[stepsWithVariables],
|
||||
);
|
||||
const handleClearButtonClick = (event) => {
|
||||
event.stopPropagation();
|
||||
resetEditor(editor);
|
||||
promoteValue();
|
||||
setSingleChoice(undefined);
|
||||
};
|
||||
const reset = (tabIndex) => {
|
||||
const isOptions = tabIndex === 0;
|
||||
setSingleChoice(isOptions);
|
||||
resetEditor(editor, { focus: true });
|
||||
};
|
||||
return (
|
||||
<Slate
|
||||
editor={editor}
|
||||
value={deserialize(value, options, stepsWithVariables)}
|
||||
>
|
||||
<ClickAwayListener
|
||||
mouseEvent="onMouseDown"
|
||||
onClickAway={() => setShowVariableSuggestions(false)}
|
||||
>
|
||||
{/* ref-able single child for ClickAwayListener */}
|
||||
<ChildrenWrapper style={{ width: '100%' }} data-test="power-input">
|
||||
<FakeInput
|
||||
disabled={disabled}
|
||||
tabIndex={-1}
|
||||
onClick={() => {
|
||||
focusEditor(editor);
|
||||
}}
|
||||
>
|
||||
<InputLabelWrapper>
|
||||
<InputLabel
|
||||
shrink={true}
|
||||
disabled={disabled}
|
||||
variant="outlined"
|
||||
sx={{ bgcolor: 'white', display: 'inline-block', px: 0.75 }}
|
||||
>
|
||||
{label}
|
||||
</InputLabel>
|
||||
</InputLabelWrapper>
|
||||
|
||||
<Editable
|
||||
readOnly={disabled}
|
||||
style={{ width: '100%' }}
|
||||
renderElement={renderElement}
|
||||
onKeyDown={handleKeyDown}
|
||||
onFocus={() => {
|
||||
setShowVariableSuggestions(true);
|
||||
}}
|
||||
onBlur={() => {
|
||||
controllerOnBlur();
|
||||
}}
|
||||
/>
|
||||
|
||||
<ActionButtonsWrapper direction="row" mr={1.5}>
|
||||
{isSingleChoice && serialize(editor.children) !== '' && (
|
||||
<IconButton
|
||||
disabled={disabled}
|
||||
edge="end"
|
||||
size="small"
|
||||
tabIndex={-1}
|
||||
onClick={handleClearButtonClick}
|
||||
>
|
||||
<ClearIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
<IconButton
|
||||
disabled={disabled}
|
||||
edge="end"
|
||||
size="small"
|
||||
tabIndex={-1}
|
||||
>
|
||||
<ArrowDropDownIcon />
|
||||
</IconButton>
|
||||
</ActionButtonsWrapper>
|
||||
</FakeInput>
|
||||
{/* ghost placer for the variables popover */}
|
||||
<div
|
||||
ref={editorRef}
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 16,
|
||||
left: 16,
|
||||
}}
|
||||
/>
|
||||
|
||||
<CustomOptions
|
||||
label={label}
|
||||
open={showVariableSuggestions}
|
||||
initialTabIndex={
|
||||
isSingleChoice === undefined ? undefined : isSingleChoice ? 0 : 1
|
||||
}
|
||||
anchorEl={editorRef.current}
|
||||
data={stepsWithVariables}
|
||||
options={options}
|
||||
onSuggestionClick={handleVariableSuggestionClick}
|
||||
onOptionClick={handleOptionClick}
|
||||
onTabChange={reset}
|
||||
/>
|
||||
|
||||
<FormHelperText
|
||||
variant="outlined"
|
||||
error={Boolean(fieldState.isTouched && fieldState.error)}
|
||||
>
|
||||
{fieldState.isTouched
|
||||
? fieldState.error?.message || description
|
||||
: description}
|
||||
</FormHelperText>
|
||||
</ChildrenWrapper>
|
||||
</ClickAwayListener>
|
||||
</Slate>
|
||||
);
|
||||
}
|
||||
export default ControlledCustomAutocomplete;
|
Reference in New Issue
Block a user