Let the user edit the font size settings with the keyboard (#1547)

* let the user edit the stepper input with keyboard

* consider exceptions and fix styling

* fix onBlur with empty strings

* always set the internal state value

* misc fixes

Co-authored-by: David Simpson <45690499+davegarthsimpson@users.noreply.github.com>
This commit is contained in:
Alberto Iannaccone 2022-10-21 17:36:19 +02:00 committed by GitHub
parent 5424dfcf70
commit 32d904ca36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 78 additions and 31 deletions

View File

@ -180,7 +180,8 @@ export class SettingsComponent extends React.Component<
<div className="column"> <div className="column">
<div className="flex-line"> <div className="flex-line">
<SettingsStepInput <SettingsStepInput
value={this.state.editorFontSize} key={`font-size-stepper-${String(this.state.editorFontSize)}`}
initialValue={this.state.editorFontSize}
setSettingsStateValue={this.setFontSize} setSettingsStateValue={this.setFontSize}
step={fontSizeStep} step={fontSizeStep}
maxValue={maxFontSize} maxValue={maxFontSize}
@ -199,13 +200,18 @@ export class SettingsComponent extends React.Component<
</label> </label>
<div> <div>
<SettingsStepInput <SettingsStepInput
value={scalePercentage} key={`scale-stepper-${String(scalePercentage)}`}
initialValue={scalePercentage}
setSettingsStateValue={this.setInterfaceScale} setSettingsStateValue={this.setInterfaceScale}
step={scaleStep} step={scaleStep}
maxValue={maxScale} maxValue={maxScale}
minValue={minScale} minValue={minScale}
unitOfMeasure="%" unitOfMeasure="%"
classNames={{ input: 'theia-input small with-margin' }} classNames={{
input: 'theia-input small with-margin',
buttonsContainer:
'settings-step-input-buttons-container-perc',
}}
/> />
</div> </div>
</div> </div>

View File

@ -2,7 +2,7 @@ import * as React from '@theia/core/shared/react';
import classnames from 'classnames'; import classnames from 'classnames';
interface SettingsStepInputProps { interface SettingsStepInputProps {
value: number; initialValue: number;
setSettingsStateValue: (value: number) => void; setSettingsStateValue: (value: number) => void;
step: number; step: number;
maxValue: number; maxValue: number;
@ -15,7 +15,7 @@ const SettingsStepInput: React.FC<SettingsStepInputProps> = (
props: SettingsStepInputProps props: SettingsStepInputProps
) => { ) => {
const { const {
value, initialValue,
setSettingsStateValue, setSettingsStateValue,
step, step,
maxValue, maxValue,
@ -24,18 +24,35 @@ const SettingsStepInput: React.FC<SettingsStepInputProps> = (
classNames, classNames,
} = props; } = props;
const [valueState, setValueState] = React.useState<{
currentValue: number;
isEmptyString: boolean;
}>({
currentValue: initialValue,
isEmptyString: false,
});
const { currentValue, isEmptyString } = valueState;
const clamp = (value: number, min: number, max: number): number => { const clamp = (value: number, min: number, max: number): number => {
return Math.min(Math.max(value, min), max); return Math.min(Math.max(value, min), max);
}; };
const resetToInitialState = (): void => {
setValueState({
currentValue: initialValue,
isEmptyString: false,
});
};
const onStep = ( const onStep = (
roundingOperation: 'ceil' | 'floor', roundingOperation: 'ceil' | 'floor',
stepOperation: (a: number, b: number) => number stepOperation: (a: number, b: number) => number
): void => { ): void => {
const valueRoundedToScale = Math[roundingOperation](value / step) * step; const valueRoundedToScale =
Math[roundingOperation](currentValue / step) * step;
const calculatedValue = const calculatedValue =
valueRoundedToScale === value valueRoundedToScale === currentValue
? stepOperation(value, step) ? stepOperation(currentValue, step)
: valueRoundedToScale; : valueRoundedToScale;
const newValue = clamp(calculatedValue, minValue, maxValue); const newValue = clamp(calculatedValue, minValue, maxValue);
@ -52,33 +69,53 @@ const SettingsStepInput: React.FC<SettingsStepInputProps> = (
const onUserInput = (event: React.ChangeEvent<HTMLInputElement>): void => { const onUserInput = (event: React.ChangeEvent<HTMLInputElement>): void => {
const { value: eventValue } = event.target; const { value: eventValue } = event.target;
setValueState({
if (eventValue === '') { currentValue: Number(eventValue),
setSettingsStateValue(0); isEmptyString: eventValue === '',
} });
const number = Number(eventValue);
if (!isNaN(number) && number !== value) {
const newValue = clamp(number, minValue, maxValue);
setSettingsStateValue(newValue);
}
}; };
const upDisabled = value >= maxValue; /* Prevent the user from entering invalid values */
const downDisabled = value <= minValue; const onBlur = (event: React.FocusEvent): void => {
if (
(currentValue === initialValue && !isEmptyString) ||
event.currentTarget.contains(event.relatedTarget as Node)
) {
return;
}
const clampedValue = clamp(currentValue, minValue, maxValue);
if (clampedValue === initialValue || isNaN(currentValue) || isEmptyString) {
resetToInitialState();
return;
}
setSettingsStateValue(clampedValue);
};
const valueIsNotWithinRange =
currentValue < minValue || currentValue > maxValue;
const isDisabledException =
valueIsNotWithinRange || isEmptyString || isNaN(currentValue);
const upDisabled = isDisabledException || currentValue >= maxValue;
const downDisabled = isDisabledException || currentValue <= minValue;
return ( return (
<div className="settings-step-input-container"> <div className="settings-step-input-container" onBlur={onBlur}>
<input <input
className={classnames('settings-step-input-element', classNames?.input)} className={classnames('settings-step-input-element', classNames?.input)}
value={value.toString()} value={isEmptyString ? '' : String(currentValue)}
onChange={onUserInput} onChange={onUserInput}
type="number" type="number"
pattern="[0-9]+" pattern="[0-9]+"
/> />
<div className="settings-step-input-buttons-container"> <div
className={classnames(
'settings-step-input-buttons-container',
classNames?.buttonsContainer
)}
>
<button <button
className="settings-step-input-button settings-step-input-up-button" className="settings-step-input-button settings-step-input-up-button"
disabled={upDisabled} disabled={upDisabled}

View File

@ -12,7 +12,7 @@
display: none; display: none;
flex-direction: column; flex-direction: column;
position: absolute; position: absolute;
right: 14px; right: 0px;
top: 50%; top: 50%;
transform: translate(0px, -50%); transform: translate(0px, -50%);
height: calc(100% - 4px); height: calc(100% - 4px);
@ -21,7 +21,11 @@
background: var(--theia-input-background); background: var(--theia-input-background);
} }
.settings-step-input-container:hover > .settings-step-input-buttons-container { .settings-step-input-buttons-container-perc {
right: 14px;
}
.settings-step-input-container:hover>.settings-step-input-buttons-container {
display: flex; display: flex;
} }