fix(stack/git): fix cursor movement issue in git text fields (#8656)

pull/8691/head
Oscar Zhou 2023-03-20 10:00:35 +13:00 committed by GitHub
parent 3a30c8ed1e
commit 0ca56ddbb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 37 additions and 24 deletions

View File

@ -182,7 +182,7 @@ class StackRedeployGitFormController {
this.formValues.AutoUpdate = parseAutoUpdateResponse(this.stack.AutoUpdate); this.formValues.AutoUpdate = parseAutoUpdateResponse(this.stack.AutoUpdate);
if (this.stack.AutoUpdate.Webhook) { if (this.stack.AutoUpdate && this.stack.AutoUpdate.Webhook) {
this.state.webhookId = this.stack.AutoUpdate.Webhook; this.state.webhookId = this.stack.AutoUpdate.Webhook;
} }

View File

@ -0,0 +1,19 @@
import { useState, useCallback, useEffect } from 'react';
export function useStateWrapper<T>(value: T, onChange: (value: T) => void) {
const [inputValue, setInputValue] = useState(value);
const updateInputValue = useCallback(
(value: T) => {
setInputValue(value);
onChange(value);
},
[onChange, setInputValue]
);
useEffect(() => {
setInputValue(value);
}, [value]);
return [inputValue, updateInputValue] as const;
}

View File

@ -1,9 +1,10 @@
import { FormikErrors } from 'formik'; import { FormikErrors } from 'formik';
import { useStateWrapper } from '@/react/hooks/useStateWrapper';
import { FormError } from '@@/form-components/FormError'; import { FormError } from '@@/form-components/FormError';
import { InputGroup } from '@@/form-components/InputGroup'; import { InputGroup } from '@@/form-components/InputGroup';
import { InputList, ItemProps } from '@@/form-components/InputList'; import { InputList, ItemProps } from '@@/form-components/InputList';
import { useCaretPosition } from '@@/form-components/useCaretPosition';
interface Props { interface Props {
value: Array<string>; value: Array<string>;
@ -32,21 +33,19 @@ function Item({
error, error,
readOnly, readOnly,
}: ItemProps<string>) { }: ItemProps<string>) {
const { ref, updateCaret } = useCaretPosition(); const [inputValue, updateInputValue] = useStateWrapper(item, onChange);
return ( return (
<div className="relative flex flex-col"> <div className="relative flex flex-col">
<InputGroup size="small"> <InputGroup size="small">
<InputGroup.Addon>path</InputGroup.Addon> <InputGroup.Addon>path</InputGroup.Addon>
<InputGroup.Input <InputGroup.Input
mRef={ref}
required required
disabled={disabled} disabled={disabled}
readOnly={readOnly} readOnly={readOnly}
value={item} value={inputValue}
onChange={(e) => { onChange={(e) => {
onChange(e.target.value); updateInputValue(e.target.value);
updateCaret();
}} }}
/> />
</InputGroup> </InputGroup>

View File

@ -1,7 +1,8 @@
import { useStateWrapper } from '@/react/hooks/useStateWrapper';
import { FormControl } from '@@/form-components/FormControl'; import { FormControl } from '@@/form-components/FormControl';
import { TextTip } from '@@/Tip/TextTip'; import { TextTip } from '@@/Tip/TextTip';
import { Input } from '@@/form-components/Input'; import { Input } from '@@/form-components/Input';
import { useCaretPosition } from '@@/form-components/useCaretPosition';
import { GitFormModel } from '../types'; import { GitFormModel } from '../types';
import { isBE } from '../../feature-flags/feature-flags.service'; import { isBE } from '../../feature-flags/feature-flags.service';
@ -25,7 +26,7 @@ export function ComposePathField({
isDockerStandalone, isDockerStandalone,
errors, errors,
}: Props) { }: Props) {
const { ref, updateCaret } = useCaretPosition(); const [inputValue, updateInputValue] = useStateWrapper(value, onChange);
return ( return (
<div className="form-group"> <div className="form-group">
@ -65,11 +66,9 @@ export function ComposePathField({
/> />
) : ( ) : (
<Input <Input
mRef={ref} value={inputValue}
value={value}
onChange={(e) => { onChange={(e) => {
onChange(e.target.value); updateInputValue(e.target.value);
updateCaret();
}} }}
placeholder={isCompose ? 'docker-compose.yml' : 'manifest.yml'} placeholder={isCompose ? 'docker-compose.yml' : 'manifest.yml'}
/> />

View File

@ -12,8 +12,6 @@ import clsx from 'clsx';
import { useSearch } from '@/react/portainer/gitops/queries/useSearch'; import { useSearch } from '@/react/portainer/gitops/queries/useSearch';
import { useDebounce } from '@/react/hooks/useDebounce'; import { useDebounce } from '@/react/hooks/useDebounce';
import { useCaretPosition } from '@@/form-components/useCaretPosition';
import { getAuthentication } from '../utils'; import { getAuthentication } from '../utils';
import { GitFormModel } from '../types'; import { GitFormModel } from '../types';
@ -30,7 +28,7 @@ export function PathSelector({
placeholder: string; placeholder: string;
model: GitFormModel; model: GitFormModel;
}) { }) {
const [searchTerm, setSearchTerm] = useDebounce('', () => {}); const [searchTerm, setSearchTerm] = useDebounce(value, onChange);
const creds = getAuthentication(model); const creds = getAuthentication(model);
const payload = { const payload = {
@ -43,7 +41,6 @@ export function PathSelector({
model.RepositoryURL && model.RepositoryURLValid && searchTerm model.RepositoryURL && model.RepositoryURLValid && searchTerm
); );
const { data: searchResults } = useSearch(payload, enabled); const { data: searchResults } = useSearch(payload, enabled);
const { ref, updateCaret } = useCaretPosition();
return ( return (
<Combobox <Combobox
@ -53,11 +50,10 @@ export function PathSelector({
data-cy="component-gitComposeInput" data-cy="component-gitComposeInput"
> >
<ComboboxInput <ComboboxInput
ref={ref}
className="form-control" className="form-control"
onChange={handleChange} onChange={handleChange}
placeholder={placeholder} placeholder={placeholder}
value={value} value={searchTerm}
/> />
{searchResults && searchResults.length > 0 && ( {searchResults && searchResults.length > 0 && (
<ComboboxPopover> <ComboboxPopover>
@ -81,12 +77,9 @@ export function PathSelector({
function handleChange(e: ChangeEvent<HTMLInputElement>) { function handleChange(e: ChangeEvent<HTMLInputElement>) {
setSearchTerm(e.target.value); setSearchTerm(e.target.value);
onChange(e.target.value);
updateCaret();
} }
function onSelect(value: string) { function onSelect(value: string) {
setSearchTerm('');
onChange(value); onChange(value);
} }
} }

View File

@ -2,6 +2,7 @@ import { PropsWithChildren, ReactNode } from 'react';
import { SchemaOf, string } from 'yup'; import { SchemaOf, string } from 'yup';
import { StackId } from '@/react/docker/stacks/types'; import { StackId } from '@/react/docker/stacks/types';
import { useStateWrapper } from '@/react/hooks/useStateWrapper';
import { FormControl } from '@@/form-components/FormControl'; import { FormControl } from '@@/form-components/FormControl';
import { Input } from '@@/form-components/Input'; import { Input } from '@@/form-components/Input';
@ -29,6 +30,8 @@ export function RefField({
isUrlValid, isUrlValid,
stackId, stackId,
}: Props) { }: Props) {
const [inputValue, updateInputValue] = useStateWrapper(value, onChange);
return isBE ? ( return isBE ? (
<Wrapper <Wrapper
errors={error} errors={error}
@ -62,8 +65,8 @@ export function RefField({
} }
> >
<Input <Input
value={value} value={inputValue}
onChange={(e) => onChange(e.target.value)} onChange={(e) => updateInputValue(e.target.value)}
placeholder="refs/heads/main" placeholder="refs/heads/main"
/> />
</Wrapper> </Wrapper>