Better DurationView
This commit is contained in:
parent
51d7c78cac
commit
e5fd8ce364
@ -6,13 +6,90 @@ import { Duration } from "@common/Duration";
|
|||||||
|
|
||||||
import "@client/styles/DurationView";
|
import "@client/styles/DurationView";
|
||||||
|
|
||||||
export default class DurationView extends React.Component<{
|
export interface DurationViewProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
inline?: boolean;
|
inline?: boolean;
|
||||||
duration: Duration;
|
duration: Duration;
|
||||||
onDurationChange?: (newDuration: Duration) => void;
|
onDurationChange?: (newDuration: Duration) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
}> {
|
}
|
||||||
|
|
||||||
|
function roundOrString(val: number | string): number | string {
|
||||||
|
if (typeof val === "number") {
|
||||||
|
return Math.round(val);
|
||||||
|
} else {
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NumberInputProps {
|
||||||
|
className?: string;
|
||||||
|
label?: string;
|
||||||
|
value: number;
|
||||||
|
max?: number;
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function NumberInput(props: NumberInputProps): React.ReactElement {
|
||||||
|
const [valueState, setValueState] = React.useState<number | string>(props.value);
|
||||||
|
const [elementId, setElementId] = React.useState(() => `NumberInput-${Math.round(Math.random() * 100000000)}`);
|
||||||
|
const [isWheelChange, setIsWheelChange] = React.useState(false);
|
||||||
|
|
||||||
|
const onChange: InputProps["onChange"] = (_e, data) => {
|
||||||
|
setValueState(data.value);
|
||||||
|
const newValue = parseFloat(data.value);
|
||||||
|
if (!isNaN(newValue) && data.value.length > 0 && isWheelChange) {
|
||||||
|
props.onChange(Math.round(newValue));
|
||||||
|
setIsWheelChange(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onBlur: React.FocusEventHandler = () => {
|
||||||
|
const newValue = (typeof valueState === "number") ? valueState : parseFloat(valueState);
|
||||||
|
if (!props.onChange || isNaN(newValue)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (props.value !== newValue) {
|
||||||
|
props.onChange(Math.round(newValue));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onWheel = (e: Event) => {
|
||||||
|
// do nothing
|
||||||
|
setIsWheelChange(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const el = document.getElementById(elementId);
|
||||||
|
if (el) {
|
||||||
|
// Not passive events
|
||||||
|
el.addEventListener("wheel", onWheel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (props.value !== valueState) {
|
||||||
|
setValueState(props.value);
|
||||||
|
}
|
||||||
|
}, [props.value]);
|
||||||
|
|
||||||
|
return <Input
|
||||||
|
id={elementId}
|
||||||
|
type="number"
|
||||||
|
pattern="[0-9\.]*" // for safari
|
||||||
|
inputMode="numeric"
|
||||||
|
value={roundOrString(valueState)}
|
||||||
|
onChange={onChange}
|
||||||
|
// onMouseOut={onBlur}
|
||||||
|
onBlur={onBlur}
|
||||||
|
className={props.className}
|
||||||
|
label={props.label}
|
||||||
|
max={props.max}
|
||||||
|
labelPosition="right"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class DurationView extends React.Component<DurationViewProps> {
|
||||||
render() {
|
render() {
|
||||||
const { duration, label, inline, onDurationChange, className } = this.props;
|
const { duration, label, inline, onDurationChange, className } = this.props;
|
||||||
const inputsClassName = classNames("durationInputs", { inline });
|
const inputsClassName = classNames("durationInputs", { inline });
|
||||||
@ -22,24 +99,18 @@ export default class DurationView extends React.Component<{
|
|||||||
<Form.Field inline={inline} className={className}>
|
<Form.Field inline={inline} className={className}>
|
||||||
{label && <label>{label}</label>}
|
{label && <label>{label}</label>}
|
||||||
<div className={inputsClassName}>
|
<div className={inputsClassName}>
|
||||||
<Input
|
<NumberInput
|
||||||
type="number"
|
|
||||||
className="durationInput minutes"
|
className="durationInput minutes"
|
||||||
value={duration.minutes}
|
value={this.props.duration.minutes}
|
||||||
onChange={this.onMinutesChange}
|
onChange={this.onMinutesChange}
|
||||||
label="M"
|
label="M"
|
||||||
labelPosition="right"
|
|
||||||
onWheel={this.onWheel}
|
|
||||||
/>
|
/>
|
||||||
<Input
|
<NumberInput
|
||||||
type="number"
|
|
||||||
className="durationInput seconds"
|
className="durationInput seconds"
|
||||||
value={duration.seconds}
|
value={this.props.duration.seconds}
|
||||||
onChange={this.onSecondsChange}
|
onChange={this.onSecondsChange}
|
||||||
max="60"
|
max={60}
|
||||||
label="S"
|
label="S"
|
||||||
labelPosition="right"
|
|
||||||
onWheel={this.onWheel}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
@ -55,23 +126,25 @@ export default class DurationView extends React.Component<{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private onMinutesChange: InputProps["onChange"] = (e, { value }) => {
|
componentWillReceiveProps(nextProps: Readonly<DurationViewProps>) {
|
||||||
if (!this.props.onDurationChange || isNaN(Number(value))) {
|
if (nextProps.duration.minutes !== this.props.duration.minutes ||
|
||||||
return;
|
nextProps.duration.seconds !== this.props.duration.seconds) {
|
||||||
|
this.setState({
|
||||||
|
minutes: nextProps.duration.minutes,
|
||||||
|
seconds: nextProps.duration.seconds,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onMinutesChange = (newMinutes: number) => {
|
||||||
|
if (this.props.onDurationChange) {
|
||||||
|
this.props.onDurationChange(this.props.duration.withMinutes(newMinutes));
|
||||||
}
|
}
|
||||||
const newMinutes = Number(value);
|
|
||||||
this.props.onDurationChange(this.props.duration.withMinutes(newMinutes));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private onSecondsChange: InputProps["onChange"] = (e, { value }) => {
|
private onSecondsChange = (newSeconds: number) => {
|
||||||
if (!this.props.onDurationChange || isNaN(Number(value))) {
|
if (this.props.onDurationChange) {
|
||||||
return;
|
this.props.onDurationChange(this.props.duration.withSeconds(newSeconds));
|
||||||
}
|
}
|
||||||
const newSeconds = Number(value);
|
|
||||||
this.props.onDurationChange(this.props.duration.withSeconds(newSeconds));
|
|
||||||
};
|
|
||||||
|
|
||||||
private onWheel = () => {
|
|
||||||
// do nothing
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user