Fixed the glitches with string props

This commit is contained in:
Aria Minaei 2021-09-14 11:55:13 +02:00
parent bbf77123b2
commit 5407ed1df8
4 changed files with 171 additions and 13 deletions

View file

@ -8,7 +8,8 @@ export default function useKeyboardShortcuts() {
const studio = getStudio() const studio = getStudio()
useEffect(() => { useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => { const handleKeyDown = (e: KeyboardEvent) => {
const target: null | HTMLElement = e.target as unknown as $IntentionalAny const target: null | HTMLElement =
e.composedPath()[0] as unknown as $IntentionalAny
if ( if (
target && target &&
(target.tagName === 'INPUT' || target.tagName === 'TEXTAREA') (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')

View file

@ -1,6 +1,6 @@
import type {PropTypeConfig_String} from '@theatre/core/propTypes' import type {PropTypeConfig_String} from '@theatre/core/propTypes'
import type SheetObject from '@theatre/core/sheetObjects/SheetObject' import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
import React, {useCallback} from 'react' import React from 'react'
import {useEditingToolsForPrimitiveProp} from './utils/useEditingToolsForPrimitiveProp' import {useEditingToolsForPrimitiveProp} from './utils/useEditingToolsForPrimitiveProp'
import {SingleRowPropEditor} from './utils/SingleRowPropEditor' import {SingleRowPropEditor} from './utils/SingleRowPropEditor'
import BasicStringInput from '@theatre/studio/uiComponents/form/BasicStringInput' import BasicStringInput from '@theatre/studio/uiComponents/form/BasicStringInput'
@ -16,16 +16,14 @@ const StringPropEditor: React.FC<{
propConfig, propConfig,
) )
const onChange = useCallback(
(el: React.ChangeEvent<HTMLInputElement>) => {
stuff.permenantlySetValue(String(el.target.value))
},
[propConfig, pointerToProp, obj],
)
return ( return (
<SingleRowPropEditor {...{stuff, propConfig, pointerToProp}}> <SingleRowPropEditor {...{stuff, propConfig, pointerToProp}}>
<BasicStringInput value={stuff.value} onChange={onChange} /> <BasicStringInput
value={stuff.value}
temporarilySetValue={stuff.temporarilySetValue}
discardTemporaryValue={stuff.discardTemporaryValue}
permenantlySetValue={stuff.permenantlySetValue}
/>
</SingleRowPropEditor> </SingleRowPropEditor>
) )
} }

View file

@ -5,8 +5,6 @@ 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'
type IMode = IState['mode']
const Container = styled.div` const Container = styled.div`
height: 100%; height: 100%;
width: 100%; width: 100%;

View file

@ -1,6 +1,9 @@
import styled from 'styled-components' import styled from 'styled-components'
import type {MutableRefObject} from 'react'
import React, {useMemo, useRef, useState} from 'react'
import mergeRefs from 'react-merge-refs'
const BasicStringInput = styled.input.attrs({type: 'text'})` const Input = styled.input.attrs({type: 'text'})`
background: transparent; background: transparent;
border: 1px solid transparent; border: 1px solid transparent;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
@ -28,4 +31,162 @@ const BasicStringInput = styled.input.attrs({type: 'text'})`
} }
` `
type IState_NoFocus = {
mode: 'noFocus'
}
type IState_EditingViaKeyboard = {
mode: 'editingViaKeyboard'
currentEditedValueInString: string
valueBeforeEditing: string
}
type IState = IState_NoFocus | IState_EditingViaKeyboard
const alwaysValid = (v: string) => true
const BasicStringInput: React.FC<{
value: string
temporarilySetValue: (v: string) => void
discardTemporaryValue: () => void
permenantlySetValue: (v: string) => void
className?: string
isValid?: (v: string) => boolean
inputRef?: MutableRefObject<HTMLInputElement | null>
/**
* Called when the user hits Enter. One of the *SetValue() callbacks will be called
* before this, so use this for UI purposes such as closing a popover.
*/
onBlur?: () => void
}> = (propsA) => {
const [stateA, setState] = useState<IState>({mode: 'noFocus'})
const isValid = propsA.isValid ?? alwaysValid
const refs = useRef({state: stateA, props: propsA})
refs.current = {state: stateA, props: propsA}
const inputRef = useRef<HTMLInputElement | null>(null)
const callbacks = useMemo(() => {
const inputChange = (e: React.ChangeEvent) => {
const target = e.target as HTMLInputElement
const {value} = target
const curState = refs.current.state as IState_EditingViaKeyboard
setState({...curState, currentEditedValueInString: value})
if (!isValid(value)) return
refs.current.props.temporarilySetValue(value)
}
const onBlur = () => {
if (refs.current.state.mode === 'editingViaKeyboard') {
commitKeyboardInput()
setState({mode: 'noFocus'})
}
if (propsA.onBlur) propsA.onBlur()
}
const commitKeyboardInput = () => {
const curState = refs.current.state as IState_EditingViaKeyboard
const value = curState.currentEditedValueInString
if (!isValid(value)) {
refs.current.props.discardTemporaryValue()
} else {
if (curState.valueBeforeEditing === value) {
refs.current.props.discardTemporaryValue()
} else {
refs.current.props.permenantlySetValue(value)
}
}
}
const onInputKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Escape') {
refs.current.props.discardTemporaryValue()
inputRef.current!.blur()
} else if (e.key === 'Enter' || e.key === 'Tab') {
commitKeyboardInput()
inputRef.current!.blur()
}
}
const onClick = (e: React.MouseEvent) => {
if (refs.current.state.mode === 'noFocus') {
const c = inputRef.current!
c.focus()
e.preventDefault()
e.stopPropagation()
} else {
e.stopPropagation()
}
}
const onFocus = () => {
if (refs.current.state.mode === 'noFocus') {
transitionToEditingViaKeyboardMode()
} else if (refs.current.state.mode === 'editingViaKeyboard') {
}
}
const transitionToEditingViaKeyboardMode = () => {
const curValue = refs.current.props.value
setState({
mode: 'editingViaKeyboard',
currentEditedValueInString: String(curValue),
valueBeforeEditing: curValue,
})
setTimeout(() => {
inputRef.current!.focus()
})
}
return {
inputChange,
onBlur,
onInputKeyDown,
onClick,
onFocus,
}
}, [refs, setState, inputRef])
let value =
stateA.mode !== 'editingViaKeyboard'
? format(propsA.value)
: stateA.currentEditedValueInString
const _refs = [inputRef]
if (propsA.inputRef) _refs.push(propsA.inputRef)
const theInput = (
<Input
key="input"
type="text"
onChange={callbacks.inputChange}
value={value}
onBlur={callbacks.onBlur}
onKeyDown={callbacks.onInputKeyDown}
onClick={callbacks.onClick}
onFocus={callbacks.onFocus}
ref={mergeRefs(_refs)}
onMouseDown={(e: React.MouseEvent) => {
e.stopPropagation()
}}
onDoubleClick={(e: React.MouseEvent) => {
e.preventDefault()
e.stopPropagation()
}}
/>
)
return theInput
}
function format(v: string): string {
return v
}
export default BasicStringInput export default BasicStringInput