import { NativeSpriteImplementation , NativeSpriteUtils } from "@replay/core" ; import { TextInputProps } from "./props" ; type TextInputState = { domId : string ; storedProps : TextInputProps ; selectionEnd : number | null ; } ; /**
 * Text input for the web. Note that it will always be on top of other Sprites,
 * regardless of its ordering. It can also appear outside of canvas area.
 */ export const TextInputWeb = { create : ( { props , parentGlobalId , getState , updateState , utils } ) => { const domId = `${ parentGlobalId }--${ props . id }` ; let inputElement ; if ( ! props . numberOfLines || props . numberOfLines <= 1 ) { inputElement = document . createElement ( "input" ) ; inputElement . type = "text" ; } else { inputElement = document . createElement ( "textarea" ) ; inputElement . style . resize = "none" ; } inputElement . id = domId ; inputElement . style . position = "absolute" ; inputElement . style . boxSizing = "border-box" ; updateInput ( inputElement , props , null , utils ) ; inputElement . addEventListener ( "input" , ( e ) => { const thisInput = e . currentTarget as HTMLInputElement | HTMLTextAreaElement ; const selectionEnd = thisInput . selectionEnd ; updateState ( { selectionEnd } ) ; const state = getState ( ) ; state . storedProps . onChangeText ( thisInput . value ) ; thisInput . value = state . storedProps . text ; thisInput . selectionEnd = selectionEnd ? selectionEnd - 1 : null ; } ) ; document . body . appendChild ( inputElement ) ; return { domId , storedProps : props , selectionEnd : null } ; } , loop : ( { props , state , utils } ) => { const domId = state . domId ; let didUpdateProps = false ; for ( const [ propKey , propValue ] of Object . entries ( props ) ) { for ( const [ stateKey , stateValue ] of Object . entries ( state . storedProps ) ) { if ( propKey === stateKey && propValue !== stateValue && ! propsThatDontUpdate . includes ( propKey ) ) { didUpdateProps = true ; break ; } } if ( didUpdateProps ) { break ; } } if ( utils . didResize || didUpdateProps ) { const inputElement = document . getElementById ( domId ) ; if ( ! inputElement ) { throw Error ( `No input found with id ${ domId }` ) ; } updateInput ( inputElement as HTMLInputElement | HTMLTextAreaElement , props , state . selectionEnd , utils ) ; } return { domId , storedProps : props , selectionEnd : state . selectionEnd } ; } , cleanup : ( { state } ) => { const domId = state . domId ; const inputElement = document . getElementById ( domId ) ; if ( ! inputElement ) { throw Error ( `No input found with id ${ domId }` ) ; } document . body . removeChild ( inputElement ) ; } , } ; const propsThatDontUpdate = [ "id" , "onChangeText" ] ; const padding = 4 ; const border = 1 ; function updateInput ( inputElement , props , selectionEnd , { gameYToPlatformY , gameXToPlatformX , scale } ) { inputElement . value = props . text ; if ( selectionEnd !== null ) { inputElement . selectionEnd = selectionEnd ; } const fontSize = props . fontSize * scale ; const width = props . width * scale ; inputElement . style . font = `${ fontSize }px ${ props . fontName }` ; inputElement . style . padding = `${ padding * scale }px` ; inputElement . style . borderWidth = `${ border * scale }px` ; inputElement . style . textAlign = props . align || "center" ; inputElement . style . color = props . color || "black" ; inputElement . style . width = `${ width }px` ; inputElement . style . lineHeight = `${ fontSize }px` ; const height = fontSize * ( props . numberOfLines || 1 ) + padding * scale * 2 + border * scale * 2 ; inputElement . style . height = `${ height }px` ; const leftX = gameXToPlatformX ( props . x || 0 ) - width / 2 ; inputElement . style . left = `${ leftX }px` ; const topY = gameYToPlatformY ( props . y || 0 ) - height / 2 ; inputElement . style . top = `${ topY }px` ; }