import * as React from 'react'; import { useController, useFormContext } from 'react-hook-form'; import { IconButton } from '@mui/material'; import FormHelperText from '@mui/material/FormHelperText'; import { AutocompleteProps } from '@mui/material/Autocomplete'; import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown'; import ClearIcon from '@mui/icons-material/Clear'; import type { IFieldDropdownOption } from '@automatisch/types'; 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 { VariableElement } from 'components/Slate/types'; import CustomOptions from './CustomOptions'; import { processStepWithExecutions } from 'components/PowerInput/data'; import { StepExecutionsContext } from 'contexts/StepExecutions'; interface ControlledCustomAutocompleteProps extends AutocompleteProps { showOptionValue?: boolean; dependsOn?: string[]; defaultValue?: string; name: string; label?: string; type?: string; required?: boolean; readOnly?: boolean; description?: string; docUrl?: string; clickToCopy?: boolean; disabled?: boolean; shouldUnregister?: boolean; } function ControlledCustomAutocomplete( props: ControlledCustomAutocompleteProps ): React.ReactElement { 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: unknown[] = []; 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: IFieldDropdownOption | undefined = 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: React.KeyboardEvent ) => { if (event.code === 'Tab') { setShowVariableSuggestions(false); } }; const handleKeyDown = (event: React.KeyboardEvent) => { 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: Pick) => { insertVariable(editor, variable, stepsWithVariables); }, [stepsWithVariables] ); const handleOptionClick = React.useCallback( (event: React.MouseEvent, option: IFieldDropdownOption) => { event.stopPropagation(); overrideEditorValue(editor, { option, focus: false }); setShowVariableSuggestions(false); setSingleChoice(true); }, [stepsWithVariables] ); const handleClearButtonClick = (event: React.MouseEvent) => { event.stopPropagation(); resetEditor(editor); promoteValue(); setSingleChoice(undefined); }; const reset = (tabIndex: 0 | 1) => { const isOptions = tabIndex === 0; setSingleChoice(isOptions); resetEditor(editor, { focus: true }); }; return ( setShowVariableSuggestions(false)} > {/* ref-able single child for ClickAwayListener */} { focusEditor(editor); }} > {label} { setShowVariableSuggestions(true); }} onBlur={() => { controllerOnBlur(); }} /> {isSingleChoice && serialize(editor.children) && ( )} {/* ghost placer for the variables popover */}
{fieldState.isTouched ? fieldState.error?.message || description : description} ); } export default ControlledCustomAutocomplete;