1- import { Slider , SliderProps , Text } from '@mantine/core' ;
1+ import { Group , NumberInput , Slider , SliderProps , Text , Title } from '@mantine/core' ;
22import { useHotkeys } from '@mantine/hooks' ;
3- import { ReactNode , useState } from 'react' ;
3+ import { KeyboardEvent , ReactNode , useEffect , useState } from 'react' ;
44import { AlgorithmDataRow } from '../../models.ts' ;
55import { useStore } from '../../store.ts' ;
66import { formatNumber } from '../../utils/format.ts' ;
@@ -24,6 +24,11 @@ export function TimestampsCard(): ReactNode {
2424 // const timestampStep = 100;
2525
2626 const [ timestamp , setTimestamp ] = useState ( timestampMin ) ;
27+ const [ inputValue , setInputValue ] = useState < number | string > ( timestampMin ) ;
28+
29+ useEffect ( ( ) => {
30+ setInputValue ( timestamp ) ;
31+ } , [ timestamp ] ) ;
2732
2833 const marks : SliderProps [ 'marks' ] = [ ] ;
2934 for ( let i = timestampMin ; i < timestampMax ; i += ( timestampMax + 100 ) / 4 ) {
@@ -33,13 +38,51 @@ export function TimestampsCard(): ReactNode {
3338 } ) ;
3439 }
3540
41+ function snapToNearest ( value : number ) : number {
42+ const clamped = Math . max ( timestampMin , Math . min ( timestampMax , value ) ) ;
43+ return Math . round ( ( clamped - timestampMin ) / timestampStep ) * timestampStep + timestampMin ;
44+ }
45+
46+ function commit ( ) : void {
47+ const parsed = typeof inputValue === 'number' ? inputValue : Number ( inputValue ) ;
48+ if ( ! isNaN ( parsed ) ) {
49+ setTimestamp ( snapToNearest ( parsed ) ) ;
50+ }
51+ }
52+
53+ function handleKeyDown ( e : KeyboardEvent < HTMLInputElement > ) : void {
54+ if ( e . key === 'Enter' ) commit ( ) ;
55+ }
56+
3657 useHotkeys ( [
3758 [ 'ArrowLeft' , ( ) => setTimestamp ( timestamp === timestampMin ? timestamp : timestamp - timestampStep ) ] ,
3859 [ 'ArrowRight' , ( ) => setTimestamp ( timestamp === timestampMax ? timestamp : timestamp + timestampStep ) ] ,
3960 ] ) ;
4061
4162 return (
42- < VisualizerCard title = "Timestamps" >
63+ < VisualizerCard >
64+ < Group align = "center" gap = "xs" mb = "xs" >
65+ < Title order = { 4 } > Timestamps</ Title >
66+ < NumberInput
67+ value = { inputValue }
68+ onChange = { value => {
69+ setInputValue ( value ) ;
70+ // Stepper buttons produce a valid snapped timestamp — commit immediately.
71+ // Partial typed values (e.g. 273 when heading to 27300) won't match and are left pending.
72+ if ( typeof value === 'number' && snapToNearest ( value ) === value ) {
73+ setTimestamp ( value ) ;
74+ }
75+ } }
76+ onBlur = { commit }
77+ onKeyDown = { handleKeyDown }
78+ min = { timestampMin }
79+ max = { timestampMax }
80+ step = { timestampStep }
81+ style = { { width : 150 } }
82+ styles = { { input : { fontWeight : 700 , fontSize : 'var(--mantine-font-size-sm)' } } }
83+ />
84+ </ Group >
85+
4386 < Slider
4487 min = { timestampMin }
4588 max = { timestampMax }
0 commit comments