BasicNumberInput now undos on Escape

This commit is contained in:
Aria Minaei 2021-11-14 13:15:52 +01:00
parent 4d7a4d7f25
commit e479d32f3b

View file

@ -1,9 +1,10 @@
import {clamp, isInteger, round} from 'lodash-es' import {clamp, isInteger, round} from 'lodash-es'
import type {MutableRefObject} from 'react' import type {MutableRefObject} from 'react'
import React, {useMemo, useRef, useState} from 'react' import React, {useMemo, useRef} from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import DraggableArea from '@theatre/studio/uiComponents/DraggableArea' import DraggableArea from '@theatre/studio/uiComponents/DraggableArea'
import mergeRefs from 'react-merge-refs' import mergeRefs from 'react-merge-refs'
import useRefAndState from '@theatre/studio/utils/useRefAndState'
const Container = styled.div` const Container = styled.div`
height: 100%; height: 100%;
@ -115,11 +116,11 @@ const BasicNumberInput: React.FC<{
onBlur?: () => void onBlur?: () => void
nudge: BasicNumberInputNudgeFn nudge: BasicNumberInputNudgeFn
}> = (propsA) => { }> = (propsA) => {
const [stateA, setState] = useState<IState>({mode: 'noFocus'}) const [stateRef] = useRefAndState<IState>({mode: 'noFocus'})
const isValid = propsA.isValid ?? alwaysValid const isValid = propsA.isValid ?? alwaysValid
const refs = useRef({state: stateA, props: propsA}) const propsRef = useRef(propsA)
refs.current = {state: stateA, props: propsA} propsRef.current = propsA
const inputRef = useRef<HTMLInputElement | null>(null) const inputRef = useRef<HTMLInputElement | null>(null)
@ -129,42 +130,43 @@ const BasicNumberInput: React.FC<{
const inputChange = (e: React.ChangeEvent) => { const inputChange = (e: React.ChangeEvent) => {
const target = e.target as HTMLInputElement const target = e.target as HTMLInputElement
const {value} = target const {value} = target
const curState = refs.current.state as IState_EditingViaKeyboard const curState = stateRef.current as IState_EditingViaKeyboard
setState({...curState, currentEditedValueInString: value}) stateRef.current = {...curState, currentEditedValueInString: value}
const valInFloat = parseFloat(value) const valInFloat = parseFloat(value)
if (!isFinite(valInFloat) || !isValid(valInFloat)) return if (!isFinite(valInFloat) || !isValid(valInFloat)) return
refs.current.props.temporarilySetValue(valInFloat) propsRef.current.temporarilySetValue(valInFloat)
} }
const onBlur = () => { const onBlur = () => {
if (refs.current.state.mode === 'editingViaKeyboard') { if (stateRef.current.mode === 'editingViaKeyboard') {
commitKeyboardInput() commitKeyboardInput()
setState({mode: 'noFocus'}) stateRef.current = {mode: 'noFocus'}
} }
if (propsA.onBlur) propsA.onBlur() if (propsA.onBlur) propsA.onBlur()
} }
const commitKeyboardInput = () => { const commitKeyboardInput = () => {
const curState = refs.current.state as IState_EditingViaKeyboard const curState = stateRef.current as IState_EditingViaKeyboard
const value = parseFloat(curState.currentEditedValueInString) const value = parseFloat(curState.currentEditedValueInString)
if (!isFinite(value) || !isValid(value)) { if (!isFinite(value) || !isValid(value)) {
refs.current.props.discardTemporaryValue() propsRef.current.discardTemporaryValue()
} else { } else {
if (curState.valueBeforeEditing === value) { if (curState.valueBeforeEditing === value) {
refs.current.props.discardTemporaryValue() propsRef.current.discardTemporaryValue()
} else { } else {
refs.current.props.permenantlySetValue(value) propsRef.current.permenantlySetValue(value)
} }
} }
} }
const onInputKeyDown = (e: React.KeyboardEvent) => { const onInputKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') { if (e.key === 'Escape') {
refs.current.props.discardTemporaryValue() propsRef.current.discardTemporaryValue()
stateRef.current = {mode: 'noFocus'}
inputRef.current!.blur() inputRef.current!.blur()
} else if (e.key === 'Enter' || e.key === 'Tab') { } else if (e.key === 'Enter' || e.key === 'Tab') {
commitKeyboardInput() commitKeyboardInput()
@ -173,7 +175,7 @@ const BasicNumberInput: React.FC<{
} }
const onClick = (e: React.MouseEvent) => { const onClick = (e: React.MouseEvent) => {
if (refs.current.state.mode === 'noFocus') { if (stateRef.current.mode === 'noFocus') {
const c = inputRef.current! const c = inputRef.current!
c.focus() c.focus()
e.preventDefault() e.preventDefault()
@ -184,19 +186,19 @@ const BasicNumberInput: React.FC<{
} }
const onFocus = () => { const onFocus = () => {
if (refs.current.state.mode === 'noFocus') { if (stateRef.current.mode === 'noFocus') {
transitionToEditingViaKeyboardMode() transitionToEditingViaKeyboardMode()
} else if (refs.current.state.mode === 'editingViaKeyboard') { } else if (stateRef.current.mode === 'editingViaKeyboard') {
} }
} }
const transitionToEditingViaKeyboardMode = () => { const transitionToEditingViaKeyboardMode = () => {
const curValue = refs.current.props.value const curValue = propsRef.current.value
setState({ stateRef.current = {
mode: 'editingViaKeyboard', mode: 'editingViaKeyboard',
currentEditedValueInString: String(curValue), currentEditedValueInString: String(curValue),
valueBeforeEditing: curValue, valueBeforeEditing: curValue,
}) }
setTimeout(() => { setTimeout(() => {
inputRef.current!.focus() inputRef.current!.focus()
@ -207,39 +209,39 @@ const BasicNumberInput: React.FC<{
let inputWidth: number let inputWidth: number
const transitionToDraggingMode = () => { const transitionToDraggingMode = () => {
const curValue = refs.current.props.value const curValue = propsRef.current.value
inputWidth = inputRef.current?.getBoundingClientRect().width! inputWidth = inputRef.current?.getBoundingClientRect().width!
setState({ stateRef.current = {
mode: 'dragging', mode: 'dragging',
valueBeforeDragging: curValue, valueBeforeDragging: curValue,
currentDraggingValue: curValue, currentDraggingValue: curValue,
}) }
bodyCursorBeforeDrag.current = document.body.style.cursor bodyCursorBeforeDrag.current = document.body.style.cursor
} }
const onDragEnd = (happened: boolean) => { const onDragEnd = (happened: boolean) => {
if (!happened) { if (!happened) {
refs.current.props.discardTemporaryValue() propsRef.current.discardTemporaryValue()
setState({mode: 'noFocus'}) stateRef.current = {mode: 'noFocus'}
inputRef.current!.focus() inputRef.current!.focus()
inputRef.current!.setSelectionRange(0, 100) inputRef.current!.setSelectionRange(0, 100)
} else { } else {
const curState = refs.current.state as IState_Dragging const curState = stateRef.current as IState_Dragging
const value = curState.currentDraggingValue const value = curState.currentDraggingValue
if (curState.valueBeforeDragging === value) { if (curState.valueBeforeDragging === value) {
refs.current.props.discardTemporaryValue() propsRef.current.discardTemporaryValue()
} else { } else {
refs.current.props.permenantlySetValue(value) propsRef.current.permenantlySetValue(value)
} }
setState({mode: 'noFocus'}) stateRef.current = {mode: 'noFocus'}
} }
} }
const onDrag = (deltaX: number, _dy: number) => { const onDrag = (deltaX: number, _dy: number) => {
const curState = refs.current.state as IState_Dragging const curState = stateRef.current as IState_Dragging
let newValue = let newValue =
curState.valueBeforeDragging + curState.valueBeforeDragging +
@ -253,12 +255,12 @@ const BasicNumberInput: React.FC<{
newValue = clamp(newValue, propsA.range[0], propsA.range[1]) newValue = clamp(newValue, propsA.range[0], propsA.range[1])
} }
setState({ stateRef.current = {
...curState, ...curState,
currentDraggingValue: newValue, currentDraggingValue: newValue,
}) }
refs.current.props.temporarilySetValue(newValue) propsRef.current.temporarilySetValue(newValue)
} }
return { return {
@ -271,12 +273,12 @@ const BasicNumberInput: React.FC<{
onDragEnd, onDragEnd,
onDrag, onDrag,
} }
}, [refs, setState, inputRef]) }, [])
let value = let value =
stateA.mode !== 'editingViaKeyboard' stateRef.current.mode !== 'editingViaKeyboard'
? format(propsA.value) ? format(propsA.value)
: stateA.currentEditedValueInString : stateRef.current.currentEditedValueInString
if (typeof value === 'number' && isNaN(value)) { if (typeof value === 'number' && isNaN(value)) {
value = 'NaN' value = 'NaN'
@ -319,13 +321,13 @@ const BasicNumberInput: React.FC<{
) : null ) : null
return ( return (
<Container className={propsA.className + ' ' + refs.current.state.mode}> <Container className={propsA.className + ' ' + stateRef.current.mode}>
<DraggableArea <DraggableArea
key="draggableArea" key="draggableArea"
onDragStart={callbacks.transitionToDraggingMode} onDragStart={callbacks.transitionToDraggingMode}
onDragEnd={callbacks.onDragEnd} onDragEnd={callbacks.onDragEnd}
onDrag={callbacks.onDrag} onDrag={callbacks.onDrag}
enabled={refs.current.state.mode !== 'editingViaKeyboard'} enabled={stateRef.current.mode !== 'editingViaKeyboard'}
lockCursorTo="ew-resize" lockCursorTo="ew-resize"
> >
{theInput} {theInput}