@@ -8,6 +8,7 @@ import { Scalar, Tools } from "babylonjs";
88import Mexp from "math-expression-evaluator" ;
99
1010import { Tooltip , TooltipContent , TooltipProvider , TooltipTrigger } from "../../../../ui/shadcn/ui/tooltip" ;
11+ import { cn } from "../../../../ui/utils" ;
1112
1213import { registerSimpleUndoRedo } from "../../../../tools/undoredo" ;
1314import { getInspectorPropertyValue , setInspectorEffectivePropertyValue } from "../../../../tools/property" ;
@@ -16,7 +17,7 @@ import { IEditorInspectorFieldProps } from "./field";
1617
1718const mexp = new Mexp ( ) ;
1819
19- export interface IEditorInspectorNumberFieldProps extends IEditorInspectorFieldProps {
20+ export interface IEditorInspectorNumberFieldProps extends Partial < IEditorInspectorFieldProps > {
2021 min ?: number ;
2122 max ?: number ;
2223
@@ -27,24 +28,42 @@ export interface IEditorInspectorNumberFieldProps extends IEditorInspectorFieldP
2728
2829 onChange ?: ( value : number ) => void ;
2930 onFinishChange ?: ( value : number , oldValue : number ) => void ;
31+
32+ /** When set, value is driven from React state; object/property and inspector mutation are skipped. */
33+ controlledValue ?: number ;
34+ /** Overrides fractional digits for display/scrub (defaults from step). */
35+ decimals ?: number ;
36+
37+ wrapperClassName ?: string ;
38+ inputClassName ?: string ;
39+ title ?: string ;
3040}
3141
3242export function EditorInspectorNumberField ( props : IEditorInspectorNumberFieldProps ) {
43+ const isControlled = props . controlledValue !== undefined ;
44+
3345 const [ shiftDown , setShiftDown ] = useState ( false ) ;
3446 const [ pointerOver , setPointerOver ] = useState ( false ) ;
3547
3648 const [ warning , setWarning ] = useState ( false ) ;
3749
3850 const step = props . step ?? 0.01 ;
39- const digitCount = props . step ?. toString ( ) . split ( "." ) [ 1 ] ?. length ?? 2 ;
51+ const digitCount = props . decimals ?? ( props . step ?. toString ( ) . split ( "." ) [ 1 ] ?. length ?? 2 ) ;
4052
41- const [ value , setValue ] = useState < string > ( getStartValue ( ) ) ;
42- const [ oldValue , setOldValue ] = useState < string > ( getStartValue ( ) ) ;
53+ const [ value , setValue ] = useState < string > ( ( ) => formatInitial ( ) ) ;
54+ const [ oldValue , setOldValue ] = useState < string > ( ( ) => formatInitial ( ) ) ;
55+
56+ function formatInitial ( ) : string {
57+ const n = getStartValue ( ) ;
58+ return typeof n === "number" && Number . isFinite ( n ) ? n . toFixed ( digitCount ) : String ( n ) ;
59+ }
4360
4461 useEffect ( ( ) => {
45- setValue ( getStartValue ( ) ) ;
46- setOldValue ( getStartValue ( ) ) ;
47- } , [ props . object , props . property , props . step ] ) ;
62+ const n = getStartValue ( ) ;
63+ const s = typeof n === "number" && Number . isFinite ( n ) ? n . toFixed ( digitCount ) : String ( n ) ;
64+ setValue ( s ) ;
65+ setOldValue ( s ) ;
66+ } , isControlled ? [ props . controlledValue , props . step , props . asDegrees , digitCount ] : [ props . object , props . property , props . step , props . asDegrees , digitCount ] ) ;
4867
4968 useEventListener ( "keydown" , ( ev ) => {
5069 if ( ev . key === "Shift" ) {
@@ -59,18 +78,23 @@ export function EditorInspectorNumberField(props: IEditorInspectorNumberFieldPro
5978 } ) ;
6079
6180 function getStartValue ( ) {
81+ if ( isControlled ) {
82+ let v = props . controlledValue as number ;
83+ if ( props . asDegrees ) {
84+ v = Tools . ToDegrees ( v ) ;
85+ }
86+ return v ;
87+ }
88+
89+ if ( ! props . object || ! props . property ) {
90+ return 0 ;
91+ }
92+
6293 let startValue = getInspectorPropertyValue ( props . object , props . property ) ?? 0 ;
6394 if ( props . asDegrees ) {
6495 startValue = Tools . ToDegrees ( startValue ) ;
6596 }
6697
67- // Determine if the value should be fixed at "step" digit counts or kept as-is.
68- // if (props.asDegrees) {
69- // startValue = Tools.ToDegrees(startValue).toFixed(digitCount);
70- // } else {
71- // startValue = startValue.toFixed(digitCount);
72- // }
73-
7498 return startValue ;
7599 }
76100
@@ -108,7 +132,11 @@ export function EditorInspectorNumberField(props: IEditorInspectorNumberFieldPro
108132 const ratio = hasMinMax ? getRatio ( ) : 0 ;
109133
110134 return (
111- < div className = "flex gap-2 items-center px-2" onMouseOver = { ( ) => setPointerOver ( true ) } onMouseLeave = { ( ) => setPointerOver ( false ) } >
135+ < div
136+ className = { cn ( "flex gap-2 items-center px-2" , props . wrapperClassName ) }
137+ onMouseOver = { ( ) => setPointerOver ( true ) }
138+ onMouseLeave = { ( ) => setPointerOver ( false ) }
139+ >
112140 { props . label && (
113141 < div className = "flex items-center gap-2 w-1/3 text-ellipsis overflow-hidden whitespace-nowrap" >
114142 < div
@@ -135,6 +163,7 @@ export function EditorInspectorNumberField(props: IEditorInspectorNumberFieldPro
135163
136164 < input
137165 type = "text"
166+ title = { props . title }
138167 value = { value }
139168 onChange = { ( ev ) => {
140169 setValue ( ev . currentTarget . value ) ;
@@ -160,7 +189,9 @@ export function EditorInspectorNumberField(props: IEditorInspectorNumberFieldPro
160189
161190 setWarning ( false ) ;
162191
163- setInspectorEffectivePropertyValue ( props . object , props . property , float ) ;
192+ if ( ! isControlled && props . object && props . property ) {
193+ setInspectorEffectivePropertyValue ( props . object , props . property , float ) ;
194+ }
164195 props . onChange ?.( float ) ;
165196 }
166197 } }
@@ -171,12 +202,12 @@ export function EditorInspectorNumberField(props: IEditorInspectorNumberFieldPro
171202 ? `linear-gradient(to right, hsl(var(--muted-foreground) / 0.5) ${ ratio } %, hsl(var(--muted-foreground) / 0.1) ${ ratio } %, hsl(var(--muted-foreground) / 0.1) 100%)`
172203 : undefined ,
173204 } }
174- className = { `
175- px-5 py-2 rounded-lg bg-muted-foreground/10 outline-none ring-yellow-500
176- ${ warning ? "ring-2 bg-background" : "ring-0" }
177- ${ props . label ? "w-2/3" : "w-full" }
178- transition-all duration-300 ease-in-out
179- ` }
205+ className = { cn (
206+ " px-5 py-2 rounded-lg bg-muted-foreground/10 outline-none ring-yellow-500 transition-all duration-300 ease-in-out" ,
207+ warning ? "ring-2 bg-background" : "ring-0" ,
208+ props . label ? "w-2/3" : "w-full" ,
209+ props . inputClassName
210+ ) }
180211 onKeyUp = { ( ev ) => ev . key === "Enter" && ev . currentTarget . blur ( ) }
181212 onBlur = { ( ev ) => {
182213 if ( ev . currentTarget . value !== oldValue ) {
@@ -208,7 +239,7 @@ export function EditorInspectorNumberField(props: IEditorInspectorNumberFieldPro
208239 newValueFloat = Tools . ToRadians ( newValueFloat ) ;
209240 }
210241
211- if ( ! props . noUndoRedo ) {
242+ if ( ! props . noUndoRedo && ! isControlled && props . object && props . property ) {
212243 registerSimpleUndoRedo ( {
213244 object : props . object ,
214245 property : props . property ,
@@ -275,7 +306,9 @@ export function EditorInspectorNumberField(props: IEditorInspectorNumberFieldPro
275306 setWarning ( false ) ;
276307 setValue ( v . toFixed ( digitCount ) ) ;
277308
278- setInspectorEffectivePropertyValue ( props . object , props . property , finalValue ) ;
309+ if ( ! isControlled && props . object && props . property ) {
310+ setInspectorEffectivePropertyValue ( props . object , props . property , finalValue ) ;
311+ }
279312 props . onChange ?.( finalValue ) ;
280313 } )
281314 ) ;
@@ -285,7 +318,7 @@ export function EditorInspectorNumberField(props: IEditorInspectorNumberFieldPro
285318 ( mouseUpListener = ( ) => {
286319 document . exitPointerLock ( ) ;
287320
288- if ( v !== oldV && ! props . noUndoRedo ) {
321+ if ( v !== oldV && ! props . noUndoRedo && ! isControlled && props . object && props . property ) {
289322 setValue ( v . toFixed ( digitCount ) ) ;
290323
291324 let finalValue = v ;
@@ -308,6 +341,17 @@ export function EditorInspectorNumberField(props: IEditorInspectorNumberFieldPro
308341
309342 props . onFinishChange ?.( finalValue , oldValue ) ;
310343 }
344+ } else if ( v !== oldV && isControlled ) {
345+ setValue ( v . toFixed ( digitCount ) ) ;
346+ let finalValue = v ;
347+ if ( props . asDegrees ) {
348+ finalValue = Tools . ToRadians ( finalValue ) ;
349+ }
350+ if ( ! isNaN ( v ) && ! isNaN ( oldV ) ) {
351+ const oldVal = props . asDegrees ? Tools . ToRadians ( oldV ) : oldV ;
352+ setOldValue ( v . toFixed ( digitCount ) ) ;
353+ props . onFinishChange ?.( finalValue , oldVal ) ;
354+ }
311355 }
312356
313357 document . body . style . cursor = "auto" ;
0 commit comments