import PropTypes from 'prop-types'; 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) => , [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 ( setShowVariableSuggestions(false)} > {/* ref-able single child for ClickAwayListener */} { focusEditor(editor); }} > {`${label}${required ? ' *' : ''}`} { setShowVariableSuggestions(true); }} onBlur={() => { controllerOnBlur(); }} /> {isSingleChoice && serialize(editor.children) !== '' && ( )} {/* ghost placer for the variables popover */}
{fieldState.isTouched ? fieldState.error?.message || description : description} ); } ControlledCustomAutocomplete.propTypes = { options: PropTypes.array, loading: PropTypes.bool.isRequired, showOptionValue: PropTypes.bool, dependsOn: PropTypes.arrayOf(PropTypes.string), defaultValue: PropTypes.string, name: PropTypes.string.isRequired, label: PropTypes.string, type: PropTypes.string, required: PropTypes.bool, readOnly: PropTypes.bool, description: PropTypes.string, docUrl: PropTypes.string, clickToCopy: PropTypes.bool, disabled: PropTypes.bool, shouldUnregister: PropTypes.bool, }; export default ControlledCustomAutocomplete;