Fixed the glitches with string props
This commit is contained in:
parent
bbf77123b2
commit
5407ed1df8
4 changed files with 171 additions and 13 deletions
|
@ -8,7 +8,8 @@ export default function useKeyboardShortcuts() {
|
|||
const studio = getStudio()
|
||||
useEffect(() => {
|
||||
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 (
|
||||
target &&
|
||||
(target.tagName === 'INPUT' || target.tagName === 'TEXTAREA')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type {PropTypeConfig_String} from '@theatre/core/propTypes'
|
||||
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||
import React, {useCallback} from 'react'
|
||||
import React from 'react'
|
||||
import {useEditingToolsForPrimitiveProp} from './utils/useEditingToolsForPrimitiveProp'
|
||||
import {SingleRowPropEditor} from './utils/SingleRowPropEditor'
|
||||
import BasicStringInput from '@theatre/studio/uiComponents/form/BasicStringInput'
|
||||
|
@ -16,16 +16,14 @@ const StringPropEditor: React.FC<{
|
|||
propConfig,
|
||||
)
|
||||
|
||||
const onChange = useCallback(
|
||||
(el: React.ChangeEvent<HTMLInputElement>) => {
|
||||
stuff.permenantlySetValue(String(el.target.value))
|
||||
},
|
||||
[propConfig, pointerToProp, obj],
|
||||
)
|
||||
|
||||
return (
|
||||
<SingleRowPropEditor {...{stuff, propConfig, pointerToProp}}>
|
||||
<BasicStringInput value={stuff.value} onChange={onChange} />
|
||||
<BasicStringInput
|
||||
value={stuff.value}
|
||||
temporarilySetValue={stuff.temporarilySetValue}
|
||||
discardTemporaryValue={stuff.discardTemporaryValue}
|
||||
permenantlySetValue={stuff.permenantlySetValue}
|
||||
/>
|
||||
</SingleRowPropEditor>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@ import styled from 'styled-components'
|
|||
import DraggableArea from '@theatre/studio/uiComponents/DraggableArea'
|
||||
import mergeRefs from 'react-merge-refs'
|
||||
|
||||
type IMode = IState['mode']
|
||||
|
||||
const Container = styled.div`
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
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;
|
||||
border: 1px solid transparent;
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue