packageDocumentation module import "./CustomNumberEditor.scss" ; import classnames from "classnames" ; import * as React from "react" ; import { Logger } from "@itwin/core-bentley" ; import { CustomFormattedNumberParams , IconEditorParams , InputEditorSizeParams , MessageSeverity , PrimitiveValue , PropertyEditorParams , PropertyEditorParamTypes , PropertyRecord , PropertyValue , PropertyValueFormat , SpecialKey , StandardEditorNames , StandardTypeNames , UiAdmin , } from "@itwin/appui-abstract" ; import { Icon , IconInput } from "@itwin/core-react" ; import { Input , InputProps } from "@itwin/itwinui-react" ; import { UiComponents } from "../UiComponents" ; import { PropertyEditorProps , TypeEditor } from "./EditorContainer" ; import { PropertyEditorBase , PropertyEditorManager } from "./PropertyEditorManager" ; internal interface CustomNumberEditorState { inputValue : string ; size ? : number ; maxLength ? : number ; iconSpec ? : string ; } alpha export class CustomNumberEditor extends React . PureComponent < PropertyEditorProps , CustomNumberEditorState > implements TypeEditor { private _isMounted = false ; private _formatParams : CustomFormattedNumberParams | undefined ; private _inputElement = React . createRef < HTMLInputElement > ( ) ; private _lastValidValue : PropertyValue | undefined ; internal public override readonly state : Readonly < CustomNumberEditorState > = { inputValue : "" } ; public async getPropertyValue ( ) { const record = this . props . propertyRecord as PropertyRecord ; let propertyValue ; if ( record . isReadonly ) { return { valueFormat : PropertyValueFormat . Primitive , value : ( record . value as PrimitiveValue ) . value , displayValue : ( record . value as PrimitiveValue ) . displayValue } ; } if ( record && record . value . valueFormat === PropertyValueFormat . Primitive ) { const parseResults = ( this . _formatParams as CustomFormattedNumberParams ) . parseFunction ( this . state . inputValue ) ; if ( ! parseResults . parseError && undefined !== parseResults . value ) { const newDisplayValue = ( this . _formatParams as CustomFormattedNumberParams ) . formatFunction ( parseResults . value as number ) ; propertyValue = { valueFormat : PropertyValueFormat . Primitive , value : parseResults . value , displayValue : newDisplayValue } ; this . _lastValidValue = { ... propertyValue } ; if ( newDisplayValue !== this . state . inputValue ) { this . setState ( { inputValue : newDisplayValue } ) ; } } else { if ( this . htmlElement ) { UiAdmin . messagePresenter . displayInputFieldMessage ( this . htmlElement , MessageSeverity . Error , parseResults . parseError ? parseResults . parseError : UiComponents . translate ( "errors.unable-to-parse-quantity" ) ) ; this . htmlElement . focus ( ) ; } else { UiAdmin . messagePresenter . displayMessage ( MessageSeverity . Error , parseResults . parseError ? parseResults . parseError : UiComponents . translate ( "errors.unable-to-parse-quantity" ) ) ; } const displayValue = ( record . value . displayValue && record . value . displayValue . length > 0 ) ? record . value . displayValue : ( this . _formatParams as CustomFormattedNumberParams ) . formatFunction ( record . value . value as number ) ; propertyValue = this . _lastValidValue ? { ... this . _lastValidValue } : { valueFormat : PropertyValueFormat . Primitive , value : record . value . value , displayValue } ; } } return propertyValue ; } public get htmlElement ( ) : HTMLElement | null { return this . _inputElement . current ; } public get hasFocus ( ) : boolean { return document . activeElement === this . _inputElement . current ; } private shouldSetFocus ( ) { if ( ! this . props . setFocus ) return false ; const record = this . props . propertyRecord as PropertyRecord ; const disabled = ( record && ! record . isDisabled ) ? false : true ; const readonly = ( record && ! record . isReadonly ) ? false : true ; return ( ! disabled && ! readonly ) ; } private _applyUpdatedValue ( userInput ) { const record = this . props . propertyRecord as PropertyRecord ; const readonly = ( record && ! record . isReadonly ) ? false : true ; const disabled = ( record && ! record . isDisabled ) ? false : true ; if ( readonly || disabled ) return ; if ( this . _isMounted ) this . setState ( { inputValue : userInput } ) ; } private _updateInputValue = ( e ) => { this . _applyUpdatedValue ( e . target . value ) ; } ; internal public override componentDidMount ( ) { this . _isMounted = true ; this . setStateFromProps ( ) ; } internal public override componentWillUnmount ( ) { this . _isMounted = false ; } internal public override componentDidUpdate ( prevProps ) { if ( this . props . propertyRecord !== prevProps . propertyRecord ) { this . setStateFromProps ( ) ; } } private _getInitialDisplayValue ( ) { const record = this . props . propertyRecord ; let initialDisplayValue = "" ; let numberValue = 0 ; if ( record ) { if ( record . value . valueFormat === PropertyValueFormat . Primitive ) { const primitiveValue = record . value ; numberValue = ( undefined !== primitiveValue . value ) ? primitiveValue . value as number : 0 ; if ( primitiveValue . displayValue ) initialDisplayValue = primitiveValue . displayValue ; else initialDisplayValue = ( this . _formatParams as CustomFormattedNumberParams ) . formatFunction ( numberValue ) ; } } return initialDisplayValue ; } private async setStateFromProps ( ) { const record = this . props . propertyRecord ; if ( ! record || ! record . property ) { Logger . logError ( UiComponents . loggerCategory ( this ) , "PropertyRecord must be defined to use CustomNumberPropertyEditor" ) ; return ; } if ( record . property && record . property . editor && record . property . editor . params ) { this . _formatParams = record . property . editor . params . find ( ( param ) => param . type === PropertyEditorParamTypes . CustomFormattedNumber ) as CustomFormattedNumberParams ; } if ( ! this . _formatParams ) { Logger . logError ( UiComponents . loggerCategory ( this ) , `CustomFormattedNumberParams must be defined for property ${ record . property . name }` ) ; return ; } const initialDisplayValue = this . _getInitialDisplayValue ( ) ; this . _lastValidValue = { valueFormat : PropertyValueFormat . Primitive , value : ( record . value as PrimitiveValue ) . value , displayValue : initialDisplayValue } ; let size ; let maxLength ; let iconSpec ; if ( record . property && record . property . editor && record . property . editor . params ) { const editorSizeParams = record . property . editor . params . find ( ( param ) => param . type === PropertyEditorParamTypes . InputEditorSize ) as InputEditorSizeParams ; if ( editorSizeParams ) { if ( editorSizeParams . size ) size = editorSizeParams . size ; if ( editorSizeParams . maxLength ) maxLength = editorSizeParams . maxLength ; } const iconParams = record . property . editor . params . find ( ( param ) => param . type === PropertyEditorParamTypes . Icon ) as IconEditorParams ; if ( iconParams ) { iconSpec = iconParams . definition . iconSpec ; } } if ( this . _isMounted ) this . setState ( { inputValue : initialDisplayValue , size , maxLength , iconSpec } ) ; } private _resetToLastValidDisplayValue ( ) { const initialDisplayValue = ( this . _lastValidValue && ( this . _lastValidValue as PrimitiveValue ) . displayValue ) ?? this . _getInitialDisplayValue ( ) ; this . setState ( { inputValue : initialDisplayValue } ) ; } private _onKeyPress = ( e ) => { if ( e . key === SpecialKey . Escape ) { const initialDisplayValue = ( this . _lastValidValue && ( this . _lastValidValue as PrimitiveValue ) . displayValue ) ?? this . _getInitialDisplayValue ( ) ; if ( initialDisplayValue !== this . state . inputValue ) { e . preventDefault ( ) ; e . stopPropagation ( ) ; this . _resetToLastValidDisplayValue ( ) ; } else { if ( this . props . onCancel ) this . props . onCancel ( ) ; } } if ( e . key !== SpecialKey . Enter ) { UiAdmin . messagePresenter . closeInputFieldMessage ( ) ; } } ; private _onFocus = ( e ) => { e . target . select ( ) ; } ; internal public override render ( ) { const minSize = this . state . size ? this . state . size : 8 ; const minWidthStyle = { minWidth : `${ minSize * 0.75 }em` } ; const record = this . props . propertyRecord as PropertyRecord ; if ( ! record || ! this . _formatParams ) return null ; const readOnly = ! record . isReadonly ? false : true ; const disabled = ! record . isDisabled ? false : true ; const className = classnames ( "components-cell-editor" , "components-customnumber-editor" , this . props . className ) ; const inputProps = { className , style : this . props . style ? this . props . style : minWidthStyle , readOnly , disabled , maxLength : this . state . maxLength , value : this . state . inputValue , onChange : this . _updateInputValue , onBlur : this . props . onBlur , onFocus : this . _onFocus , setFocus : this . shouldSetFocus ( ) , onKeyDown : this . _onKeyPress } ; let reactNode ; if ( this . state . iconSpec ) { const icon = < Icon iconSpec = { this . state . iconSpec } />; reactNode = ( < IconInput { ... inputProps } ref = { this . _inputElement } icon = { icon } data - testid = "components-customnumber-editor" / > ) ; } else { reactNode = ( < Input { ... inputProps } ref = { this . _inputElement } data - testid = "components-customnumber-editor" size = "small" / > ) ; } return reactNode ; } } alpha export class CustomNumberPropertyEditor extends PropertyEditorBase { public get reactNode ( ) : React . ReactNode { return < CustomNumberEditor />; } public override get containerHandlesEscape ( ) : boolean { return false ; } } PropertyEditorManager . registerEditor ( StandardTypeNames . Number , CustomNumberPropertyEditor , StandardEditorNames . NumberCustom ) ;