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:
psychedelicious 2023-07-08 18:52:37 +10:00
parent 26cea7b13d
commit a0ccb4385f
7 changed files with 167 additions and 9 deletions

View File

@ -9,4 +9,5 @@ export const actionsDenylist = [
'canvas/addPointToCurrentLine',
'socket/socketGeneratorProgress',
'socket/appSocketGeneratorProgress',
'hotkeys/shiftKeyPressed',
];

View File

@ -5,8 +5,10 @@ import {
Input,
InputProps,
} from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks';
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 {
label?: string;
@ -25,6 +27,25 @@ const IAIInput = (props: IAIInputProps) => {
...rest
} = 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 (
<FormControl
isInvalid={isInvalid}
@ -32,7 +53,12 @@ const IAIInput = (props: IAIInputProps) => {
{...formControlProps}
>
{label !== '' && <FormLabel>{label}</FormLabel>}
<Input {...rest} onPaste={stopPastePropagation} />
<Input
{...rest}
onPaste={stopPastePropagation}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
/>
</FormControl>
);
};

View File

@ -1,7 +1,9 @@
import { Tooltip, useColorMode, useToken } from '@chakra-ui/react';
import { MultiSelect, MultiSelectProps } from '@mantine/core';
import { useAppDispatch } from 'app/store/storeHooks';
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';
type IAIMultiSelectProps = MultiSelectProps & {
@ -11,6 +13,7 @@ type IAIMultiSelectProps = MultiSelectProps & {
const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
const { searchable = true, tooltip, inputRef, ...rest } = props;
const dispatch = useAppDispatch();
const {
base50,
base100,
@ -31,10 +34,30 @@ const IAIMantineMultiSelect = (props: IAIMultiSelectProps) => {
const [boxShadow] = useToken('shadows', ['dark-lg']);
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 (
<Tooltip label={tooltip} placement="top" hasArrow isOpen={true}>
<MultiSelect
ref={inputRef}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
searchable={searchable}
styles={() => ({
label: {

View File

@ -1,7 +1,9 @@
import { Tooltip, useColorMode, useToken } from '@chakra-ui/react';
import { Select, SelectProps } from '@mantine/core';
import { useAppDispatch } from 'app/store/storeHooks';
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';
export type IAISelectDataType = {
@ -16,6 +18,7 @@ type IAISelectProps = SelectProps & {
const IAIMantineSelect = (props: IAISelectProps) => {
const { searchable = true, tooltip, ...rest } = props;
const dispatch = useAppDispatch();
const {
base50,
base100,
@ -36,11 +39,31 @@ const IAIMantineSelect = (props: IAISelectProps) => {
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']);
return (
<Tooltip label={tooltip} placement="top" hasArrow>
<Select
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
searchable={searchable}
styles={() => ({
label: {

View File

@ -14,10 +14,19 @@ import {
Tooltip,
TooltipProps,
} from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks';
import { stopPastePropagation } from 'common/util/stopPastePropagation';
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
import { clamp } from 'lodash-es';
import { FocusEvent, memo, useEffect, useState } from 'react';
import {
FocusEvent,
KeyboardEvent,
memo,
useCallback,
useEffect,
useState,
} from 'react';
const numberStringRegex = /^-?(0\.)?\.?$/;
@ -60,6 +69,8 @@ const IAINumberInput = (props: Props) => {
...rest
} = props;
const dispatch = useAppDispatch();
/**
* 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
@ -109,6 +120,24 @@ const IAINumberInput = (props: Props) => {
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 (
<Tooltip {...tooltipProps}>
<FormControl
@ -128,7 +157,11 @@ const IAINumberInput = (props: Props) => {
{...rest}
onPaste={stopPastePropagation}
>
<NumberInputField {...numberInputFieldProps} />
<NumberInputField
{...numberInputFieldProps}
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
/>
{showStepper && (
<NumberInputStepper>
<NumberIncrementStepper {...numberInputStepperProps} />

View File

@ -26,9 +26,12 @@ import {
} from '@chakra-ui/react';
import { clamp } from 'lodash-es';
import { useAppDispatch } from 'app/store/storeHooks';
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
import { shiftKeyPressed } from 'features/ui/store/hotkeysSlice';
import {
FocusEvent,
KeyboardEvent,
memo,
MouseEvent,
useCallback,
@ -107,7 +110,7 @@ const IAISlider = (props: IAIFullSliderProps) => {
sliderIAIIconButtonProps,
...rest
} = props;
const dispatch = useAppDispatch();
const { t } = useTranslation();
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 (
<FormControl
onClick={forceInputBlur}
@ -310,6 +331,8 @@ const IAISlider = (props: IAIFullSliderProps) => {
{...sliderNumberInputProps}
>
<NumberInputField
onKeyDown={handleKeyDown}
onKeyUp={handleKeyUp}
minWidth={inputWidth}
{...sliderNumberInputFieldProps}
/>

View File

@ -1,9 +1,38 @@
import { Textarea, TextareaProps, forwardRef } from '@chakra-ui/react';
import { useAppDispatch } from 'app/store/storeHooks';
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) => {
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);