feat(PowerInput): show variable suggestions when focused

This commit is contained in:
Ali BARIN
2022-09-26 21:42:03 +02:00
parent c9056516a1
commit 93b31c10ae
2 changed files with 50 additions and 96 deletions

View File

@@ -15,25 +15,19 @@ import type { IStep } from '@automatisch/types';
const ListItemText = styled(MuiListItemText)``; const ListItemText = styled(MuiListItemText)``;
type SuggestionsProps = { type SuggestionsProps = {
query?: string | null; data: any[];
index: number;
data: any;
onSuggestionClick: (variable: any) => void; onSuggestionClick: (variable: any) => void;
}; };
const SHORT_LIST_LENGTH = 4; const SHORT_LIST_LENGTH = 4;
const LIST_HEIGHT = 256; const LIST_HEIGHT = 256;
const getPartialFilteredArray = (array: any[], query = '', length = array.length) => { const getPartialArray = (array: any[], length = array.length) => {
return array return array.slice(0, length);
.filter((suboption: any) => suboption.name.includes(query))
.slice(0, length);
} }
const Suggestions = (props: SuggestionsProps) => { const Suggestions = (props: SuggestionsProps) => {
const { const {
query = '',
index: focusIndex,
data, data,
onSuggestionClick = () => null, onSuggestionClick = () => null,
} = props; } = props;
@@ -79,13 +73,13 @@ const Suggestions = (props: SuggestionsProps) => {
<Collapse in={current === index} timeout="auto" unmountOnExit> <Collapse in={current === index} timeout="auto" unmountOnExit>
<List component="div" disablePadding sx={{ maxHeight: LIST_HEIGHT, overflowY: 'auto' }}> <List component="div" disablePadding sx={{ maxHeight: LIST_HEIGHT, overflowY: 'auto' }}>
{getPartialFilteredArray(option.output as any || [], query as string, listLength) {getPartialArray(option.output as any || [], listLength)
.map((suboption: any, index: number) => ( .map((suboption: any, index: number) => (
<ListItemButton <ListItemButton
sx={{ pl: 4 }} sx={{ pl: 4 }}
divider divider
onClick={() => onSuggestionClick(suboption)} onClick={() => onSuggestionClick(suboption)}
selected={focusIndex === index}> >
<ListItemText <ListItemText
primary={suboption.name} primary={suboption.name}
primaryTypographyProps={{ primaryTypographyProps={{

View File

@@ -56,9 +56,9 @@ const PowerInput = (props: PowerInputProps) => {
const editorRef = React.useRef<HTMLDivElement | null>(null); const editorRef = React.useRef<HTMLDivElement | null>(null);
const [target, setTarget] = React.useState<Range | null>(null); const [target, setTarget] = React.useState<Range | null>(null);
const [index, setIndex] = React.useState(0); const [index, setIndex] = React.useState(0);
const [search, setSearch] = React.useState<string | null>(null);
const renderElement = React.useCallback(props => <Element {...props} />, []); const renderElement = React.useCallback(props => <Element {...props} />, []);
const [editor] = React.useState(() => customizeEditor(createEditor())); const [editor] = React.useState(() => customizeEditor(createEditor()));
const [showVariableSuggestions, setShowVariableSuggestions] = React.useState(false);
const stepsWithVariables = React.useMemo(() => { const stepsWithVariables = React.useMemo(() => {
return processStepWithExecutions(priorStepsWithExecutions); return processStepWithExecutions(priorStepsWithExecutions);
@@ -70,46 +70,9 @@ const PowerInput = (props: PowerInputProps) => {
const handleVariableSuggestionClick = React.useCallback( const handleVariableSuggestionClick = React.useCallback(
(variable: Pick<VariableElement, "name" | "value">) => { (variable: Pick<VariableElement, "name" | "value">) => {
if (target) {
Transforms.select(editor, target);
insertVariable(editor, variable, stepsWithVariables); insertVariable(editor, variable, stepsWithVariables);
setTarget(null);
}
}, },
[index, target, stepsWithVariables] [index, stepsWithVariables]
);
const onKeyDown = React.useCallback(
event => {
if (target) {
switch (event.key) {
case 'ArrowDown': {
event.preventDefault();
setIndex((currentIndex) => currentIndex + 1);
break
}
case 'ArrowUp': {
event.preventDefault();
setIndex((currentIndex) => currentIndex - 1 < 0 ? 0 : currentIndex - 1);
break
}
case 'Tab':
case 'Enter': {
event.preventDefault();
Transforms.select(editor, target);
insertVariable(editor, stepsWithVariables[0].output[index], stepsWithVariables);
setTarget(null);
break
}
case 'Escape': {
event.preventDefault();
setTarget(null);
break
}
}
}
},
[index, search, target, stepsWithVariables]
); );
return ( return (
@@ -125,42 +88,12 @@ const PowerInput = (props: PowerInputProps) => {
value={deserialize(value, stepsWithVariables)} value={deserialize(value, stepsWithVariables)}
onChange={value => { onChange={value => {
controllerOnChange(serialize(value)); controllerOnChange(serialize(value));
const { selection } = editor
if (selection && Range.isCollapsed(selection)) {
const [start] = Range.edges(selection);
const lineBefore = Editor.before(editor, start, { unit: 'line' });
const before = lineBefore && Editor.before(editor, lineBefore);
const beforeRange = (before || lineBefore) && Editor.range(editor, before || lineBefore, start);
const beforeText = beforeRange && Editor.string(editor, beforeRange);
const variableMatch = beforeText && beforeText.match(/@([\w.]*?)$/);
if (variableMatch) {
const beginningOfVariable = Editor.before(
editor,
start,
{
unit: 'offset',
distance: (variableMatch[1].length || 0) + 1
}
);
if (beginningOfVariable) {
const newTarget = Editor.range(editor, beginningOfVariable, start);
if (newTarget) {
setTarget(newTarget);
}
}
setIndex(0);
setSearch(variableMatch[1]);
return;
}
}
setSearch(null);
}} }}
> >
<ClickAwayListener onClickAway={() => setSearch(null)}> <ClickAwayListener
mouseEvent="onMouseDown"
onClickAway={() => { setShowVariableSuggestions(false); }}
>
{/* ref-able single child for ClickAwayListener */} {/* ref-able single child for ClickAwayListener */}
<div style={{ width: '100%' }}> <div style={{ width: '100%' }}>
<FakeInput disabled={disabled}> <FakeInput disabled={disabled}>
@@ -179,7 +112,9 @@ const PowerInput = (props: PowerInputProps) => {
readOnly={disabled} readOnly={disabled}
style={{ width: '100%' }} style={{ width: '100%' }}
renderElement={renderElement} renderElement={renderElement}
onKeyDown={onKeyDown} onFocus={() => {
setShowVariableSuggestions(true);
}}
onBlur={() => { controllerOnBlur(); handleBlur(value); }} onBlur={() => { controllerOnBlur(); handleBlur(value); }}
/> />
</FakeInput> </FakeInput>
@@ -192,18 +127,12 @@ const PowerInput = (props: PowerInputProps) => {
{description} {description}
</FormHelperText> </FormHelperText>
<Popper <SuggestionsPopper
open={target !== null && search !== null} open={showVariableSuggestions}
anchorEl={editorRef.current} anchorEl={editorRef.current}
style={{ width: editorRef.current?.clientWidth, zIndex: 1, }} data={stepsWithVariables}
> onSuggestionClick={handleVariableSuggestionClick}
<Suggestions />
query={search}
index={index}
data={stepsWithVariables}
onSuggestionClick={handleVariableSuggestionClick}
/>
</Popper>
</div> </div>
</ClickAwayListener> </ClickAwayListener>
</Slate> </Slate>
@@ -212,6 +141,37 @@ const PowerInput = (props: PowerInputProps) => {
) )
} }
const SuggestionsPopper = (props: any) => {
const {
open,
anchorEl,
data,
onSuggestionClick,
} = props;
return (
<Popper
open={open}
anchorEl={anchorEl}
style={{ width: anchorEl?.clientWidth, zIndex: 1, }}
modifiers={[
{
name: 'flip',
enabled: false,
options: {
altBoundary: false,
}
}
]}
>
<Suggestions
data={data}
onSuggestionClick={onSuggestionClick}
/>
</Popper>
);
};
const Element = (props: any) => { const Element = (props: any) => {
const { attributes, children, element } = props; const { attributes, children, element } = props;
switch (element.type) { switch (element.type) {