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()
|
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')
|
||||||
|
|
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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%;
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue