mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
fix(ui): fix inconsistent shift modifier capture
The shift key listener didn't catch pressed when focused in a textarea or input field, causing jank on slider number inputs. Add keydown and keyup listeners to all such fields, which ensures that the `shift` state is always correct. Also add the action tracking it to `actionsDenylist` to not clutter up devtools.
This commit is contained in:
parent
26cea7b13d
commit
a0ccb4385f
@ -9,4 +9,5 @@ export const actionsDenylist = [
|
|||||||
'canvas/addPointToCurrentLine',
|
'canvas/addPointToCurrentLine',
|
||||||
'socket/socketGeneratorProgress',
|
'socket/socketGeneratorProgress',
|
||||||
'socket/appSocketGeneratorProgress',
|
'socket/appSocketGeneratorProgress',
|
||||||
|
'hotkeys/shiftKeyPressed',
|
||||||
];
|
];
|
||||||
|
@ -5,8 +5,10 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
InputProps,
|
InputProps,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { stopPastePropagation } from 'common/util/stopPastePropagation';
|
import { stopPastePropagation } from 'common/util/stopPastePropagation';
|
||||||
import { ChangeEvent, memo } from 'react';
|
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
|
||||||
|
import { ChangeEvent, KeyboardEvent, memo, useCallback } from 'react';
|
||||||
|
|
||||||
interface IAIInputProps extends InputProps {
|
interface IAIInputProps extends InputProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
@ -25,6 +27,25 @@ const IAIInput = (props: IAIInputProps) => {
|
|||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
dispatch(shiftKeyPressed(true));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKeyUp = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (!e.shiftKey) {
|
||||||
|
dispatch(shiftKeyPressed(false));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl
|
<FormControl
|
||||||
isInvalid={isInvalid}
|
isInvalid={isInvalid}
|
||||||
@ -32,7 +53,12 @@ const IAIInput = (props: IAIInputProps) => {
|
|||||||
{...formControlProps}
|
{...formControlProps}
|
||||||
>
|
>
|
||||||
{label !== '' && <FormLabel>{label}</FormLabel>}
|
{label !== '' && <FormLabel>{label}</FormLabel>}
|
||||||
<Input {...rest} onPaste={stopPastePropagation} />
|
<Input
|
||||||
|
{...rest}
|
||||||
|
onPaste={stopPastePropagation}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onKeyUp={handleKeyUp}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { Tooltip, useColorMode, useToken } from '@chakra-ui/react';
|
import { Tooltip, useColorMode, useToken } from '@chakra-ui/react';
|
||||||
import { MultiSelect, MultiSelectProps } from '@mantine/core';
|
import { MultiSelect, MultiSelectProps } from '@mantine/core';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
||||||
import { RefObject, memo } from 'react';
|
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
|
||||||
|
import { KeyboardEvent, RefObject, memo, useCallback } from 'react';
|
||||||
import { mode } from 'theme/util/mode';
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
type IAIMultiSelectProps = MultiSelectProps & {
|
type IAIMultiSelectProps = MultiSelectProps & {
|
||||||
@ -11,6 +13,7 @@ type IAIMultiSelectProps = MultiSelectProps & {
|
|||||||
|
|
||||||
const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
|
const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
|
||||||
const { searchable = true, tooltip, inputRef, ...rest } = props;
|
const { searchable = true, tooltip, inputRef, ...rest } = props;
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const {
|
const {
|
||||||
base50,
|
base50,
|
||||||
base100,
|
base100,
|
||||||
@ -31,10 +34,30 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
|
|||||||
const [boxShadow] = useToken('shadows', ['dark-lg']);
|
const [boxShadow] = useToken('shadows', ['dark-lg']);
|
||||||
const { colorMode } = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
dispatch(shiftKeyPressed(true));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKeyUp = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (!e.shiftKey) {
|
||||||
|
dispatch(shiftKeyPressed(false));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label={tooltip} placement="top" hasArrow isOpen={true}>
|
<Tooltip label={tooltip} placement="top" hasArrow isOpen={true}>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onKeyUp={handleKeyUp}
|
||||||
searchable={searchable}
|
searchable={searchable}
|
||||||
styles={() => ({
|
styles={() => ({
|
||||||
label: {
|
label: {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import { Tooltip, useColorMode, useToken } from '@chakra-ui/react';
|
import { Tooltip, useColorMode, useToken } from '@chakra-ui/react';
|
||||||
import { Select, SelectProps } from '@mantine/core';
|
import { Select, SelectProps } from '@mantine/core';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
import { useChakraThemeTokens } from 'common/hooks/useChakraThemeTokens';
|
||||||
import { memo } from 'react';
|
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
|
||||||
|
import { KeyboardEvent, memo, useCallback } from 'react';
|
||||||
import { mode } from 'theme/util/mode';
|
import { mode } from 'theme/util/mode';
|
||||||
|
|
||||||
export type IAISelectDataType = {
|
export type IAISelectDataType = {
|
||||||
@ -16,6 +18,7 @@ type IAISelectProps = SelectProps & {
|
|||||||
|
|
||||||
const IAIMantineSelect = (props: IAISelectProps) => {
|
const IAIMantineSelect = (props: IAISelectProps) => {
|
||||||
const { searchable = true, tooltip, ...rest } = props;
|
const { searchable = true, tooltip, ...rest } = props;
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const {
|
const {
|
||||||
base50,
|
base50,
|
||||||
base100,
|
base100,
|
||||||
@ -36,11 +39,31 @@ const IAIMantineSelect = (props: IAISelectProps) => {
|
|||||||
|
|
||||||
const { colorMode } = useColorMode();
|
const { colorMode } = useColorMode();
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
dispatch(shiftKeyPressed(true));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKeyUp = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (!e.shiftKey) {
|
||||||
|
dispatch(shiftKeyPressed(false));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
const [boxShadow] = useToken('shadows', ['dark-lg']);
|
const [boxShadow] = useToken('shadows', ['dark-lg']);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip label={tooltip} placement="top" hasArrow>
|
<Tooltip label={tooltip} placement="top" hasArrow>
|
||||||
<Select
|
<Select
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onKeyUp={handleKeyUp}
|
||||||
searchable={searchable}
|
searchable={searchable}
|
||||||
styles={() => ({
|
styles={() => ({
|
||||||
label: {
|
label: {
|
||||||
|
@ -14,10 +14,19 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipProps,
|
TooltipProps,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { stopPastePropagation } from 'common/util/stopPastePropagation';
|
import { stopPastePropagation } from 'common/util/stopPastePropagation';
|
||||||
|
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
|
|
||||||
import { FocusEvent, memo, useEffect, useState } from 'react';
|
import {
|
||||||
|
FocusEvent,
|
||||||
|
KeyboardEvent,
|
||||||
|
memo,
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
} from 'react';
|
||||||
|
|
||||||
const numberStringRegex = /^-?(0\.)?\.?$/;
|
const numberStringRegex = /^-?(0\.)?\.?$/;
|
||||||
|
|
||||||
@ -60,6 +69,8 @@ const IAINumberInput = (props: Props) => {
|
|||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Using a controlled input with a value that accepts decimals needs special
|
* Using a controlled input with a value that accepts decimals needs special
|
||||||
* handling. If the user starts to type in "1.5", by the time they press the
|
* handling. If the user starts to type in "1.5", by the time they press the
|
||||||
@ -109,6 +120,24 @@ const IAINumberInput = (props: Props) => {
|
|||||||
onChange(clamped);
|
onChange(clamped);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
dispatch(shiftKeyPressed(true));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKeyUp = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (!e.shiftKey) {
|
||||||
|
dispatch(shiftKeyPressed(false));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip {...tooltipProps}>
|
<Tooltip {...tooltipProps}>
|
||||||
<FormControl
|
<FormControl
|
||||||
@ -128,7 +157,11 @@ const IAINumberInput = (props: Props) => {
|
|||||||
{...rest}
|
{...rest}
|
||||||
onPaste={stopPastePropagation}
|
onPaste={stopPastePropagation}
|
||||||
>
|
>
|
||||||
<NumberInputField {...numberInputFieldProps} />
|
<NumberInputField
|
||||||
|
{...numberInputFieldProps}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onKeyUp={handleKeyUp}
|
||||||
|
/>
|
||||||
{showStepper && (
|
{showStepper && (
|
||||||
<NumberInputStepper>
|
<NumberInputStepper>
|
||||||
<NumberIncrementStepper {...numberInputStepperProps} />
|
<NumberIncrementStepper {...numberInputStepperProps} />
|
||||||
|
@ -26,9 +26,12 @@ import {
|
|||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { clamp } from 'lodash-es';
|
import { clamp } from 'lodash-es';
|
||||||
|
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
|
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
|
||||||
|
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
|
||||||
import {
|
import {
|
||||||
FocusEvent,
|
FocusEvent,
|
||||||
|
KeyboardEvent,
|
||||||
memo,
|
memo,
|
||||||
MouseEvent,
|
MouseEvent,
|
||||||
useCallback,
|
useCallback,
|
||||||
@ -107,7 +110,7 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
|||||||
sliderIAIIconButtonProps,
|
sliderIAIIconButtonProps,
|
||||||
...rest
|
...rest
|
||||||
} = props;
|
} = props;
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [localInputValue, setLocalInputValue] = useState<
|
const [localInputValue, setLocalInputValue] = useState<
|
||||||
@ -167,6 +170,24 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
dispatch(shiftKeyPressed(true));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKeyUp = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (!e.shiftKey) {
|
||||||
|
dispatch(shiftKeyPressed(false));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl
|
<FormControl
|
||||||
onClick={forceInputBlur}
|
onClick={forceInputBlur}
|
||||||
@ -310,6 +331,8 @@ const IAISlider = (props: IAIFullSliderProps) => {
|
|||||||
{...sliderNumberInputProps}
|
{...sliderNumberInputProps}
|
||||||
>
|
>
|
||||||
<NumberInputField
|
<NumberInputField
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onKeyUp={handleKeyUp}
|
||||||
minWidth={inputWidth}
|
minWidth={inputWidth}
|
||||||
{...sliderNumberInputFieldProps}
|
{...sliderNumberInputFieldProps}
|
||||||
/>
|
/>
|
||||||
|
@ -1,9 +1,38 @@
|
|||||||
import { Textarea, TextareaProps, forwardRef } from '@chakra-ui/react';
|
import { Textarea, TextareaProps, forwardRef } from '@chakra-ui/react';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { stopPastePropagation } from 'common/util/stopPastePropagation';
|
import { stopPastePropagation } from 'common/util/stopPastePropagation';
|
||||||
import { memo } from 'react';
|
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
|
||||||
|
import { KeyboardEvent, memo, useCallback } from 'react';
|
||||||
|
|
||||||
const IAITextarea = forwardRef((props: TextareaProps, ref) => {
|
const IAITextarea = forwardRef((props: TextareaProps, ref) => {
|
||||||
return <Textarea ref={ref} onPaste={stopPastePropagation} {...props} />;
|
const dispatch = useAppDispatch();
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
dispatch(shiftKeyPressed(true));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKeyUp = useCallback(
|
||||||
|
(e: KeyboardEvent<HTMLTextAreaElement>) => {
|
||||||
|
if (!e.shiftKey) {
|
||||||
|
dispatch(shiftKeyPressed(false));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Textarea
|
||||||
|
ref={ref}
|
||||||
|
onPaste={stopPastePropagation}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onKeyUp={handleKeyUp}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export default memo(IAITextarea);
|
export default memo(IAITextarea);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user