@@ -18,13 +18,12 @@ import {
1818 zoneLabel ,
1919} from "../../../lib/admin/movement" ;
2020
21- export const chipClass =
22- "min-h-11 rounded-sm border px-3 py-1 text-sm transition-colors disabled:opacity-40 sm:min-h-0" ;
21+ export const chipClass = "min-h-11 rounded-sm border px-3 py-1 text-sm transition-colors disabled:opacity-40" ;
2322export const chipOn = "border-[var(--tfmc-accent)] text-[var(--tfmc-cream)]" ;
2423export const chipOff =
2524 "border-[color-mix(in_srgb,var(--tfmc-cream)_20%,transparent)] text-[var(--tfmc-stone)] hover:text-[var(--tfmc-cream)]" ;
2625export const inputClass =
27- "min-h-11 w-full rounded-sm border border-[color-mix(in_srgb,var(--tfmc-cream)_20%,transparent)] bg-transparent px-2 py-1 text-sm text-[var(--tfmc-cream)] [color-scheme:dark] sm:min-h-0 " ;
26+ "min-h-11 w-full rounded-sm border border-[color-mix(in_srgb,var(--tfmc-cream)_20%,transparent)] bg-transparent px-2 py-1 text-sm text-[var(--tfmc-cream)] [color-scheme:dark]" ;
2827export const mutedClass = "text-sm text-[var(--tfmc-mist)]" ;
2928const errorClass = "text-sm text-[#e8a0a0]" ;
3029
@@ -102,10 +101,14 @@ export function RangeForm({
102101 const [ toText , setToText ] = useState ( toLocalInput ( value . to ) ) ;
103102 const [ seen , setSeen ] = useState ( value ) ;
104103 const [ error , setError ] = useState < string | null > ( null ) ;
104+ // Typed but not applied: following the clock must not overwrite it.
105+ const [ dirty , setDirty ] = useState ( false ) ;
105106 if ( seen . from !== value . from || seen . to !== value . to ) {
106107 setSeen ( value ) ;
107- setFromText ( toLocalInput ( value . from ) ) ;
108- setToText ( toLocalInput ( value . to ) ) ;
108+ if ( ! dirty ) {
109+ setFromText ( toLocalInput ( value . from ) ) ;
110+ setToText ( toLocalInput ( value . to ) ) ;
111+ }
109112 }
110113
111114 const apply = ( follow : boolean ) => {
@@ -116,6 +119,7 @@ export function RangeForm({
116119 if ( from . time ! >= to . time ! ) return setError ( "From must be before To." ) ;
117120 if ( to . time ! - from . time ! > longest ) return setError ( `Choose a range of up to ${ formatDuration ( longest ) . toLowerCase ( ) } .` ) ;
118121 setError ( null ) ;
122+ setDirty ( false ) ;
119123 onApply ( { from : from . time ! , to : to . time ! , follow } ) ;
120124 } ;
121125
@@ -137,6 +141,9 @@ export function RangeForm({
137141 if ( ! preset ) return ;
138142 const [ from , to ] = preset . range ( Math . floor ( Date . now ( ) / 1000 ) ) ;
139143 setError ( null ) ;
144+ setDirty ( false ) ;
145+ setFromText ( toLocalInput ( from ) ) ;
146+ setToText ( toLocalInput ( to ) ) ;
140147 onApply ( { from, to, follow : false } ) ;
141148 } }
142149 >
@@ -149,11 +156,11 @@ export function RangeForm({
149156 < div className = "grid grid-cols-1 gap-2 sm:grid-cols-2 lg:grid-cols-1 xl:grid-cols-2" >
150157 < label className = "flex flex-col gap-1 text-sm text-[var(--tfmc-mist)]" >
151158 From
152- < input type = "datetime-local" className = { inputClass } value = { fromText } onChange = { ( e ) => setFromText ( e . target . value ) } />
159+ < input type = "datetime-local" className = { inputClass } value = { fromText } onChange = { ( e ) => { setFromText ( e . target . value ) ; setDirty ( true ) ; } } />
153160 </ label >
154161 < label className = "flex flex-col gap-1 text-sm text-[var(--tfmc-mist)]" >
155162 To
156- < input type = "datetime-local" className = { inputClass } value = { toText } onChange = { ( e ) => setToText ( e . target . value ) } />
163+ < input type = "datetime-local" className = { inputClass } value = { toText } onChange = { ( e ) => { setToText ( e . target . value ) ; setDirty ( true ) ; } } />
157164 </ label >
158165 </ div >
159166 < div className = "flex flex-wrap gap-2" >
@@ -163,19 +170,25 @@ export function RangeForm({
163170 < button
164171 type = "button"
165172 className = { `${ chipClass } ${ chipOff } ` }
166- onClick = { ( ) => setToText ( toLocalInput ( Math . floor ( Date . now ( ) / 1000 ) ) ) }
173+ onClick = { ( ) => {
174+ setToText ( toLocalInput ( Math . floor ( Date . now ( ) / 1000 ) ) ) ;
175+ setDirty ( true ) ;
176+ } }
167177 >
168178 To now
169179 </ button >
170180 </ div >
171- < label className = "flex items-center gap-2 text-sm text-[var(--tfmc-cream)]" >
181+ < label className = "flex min-h-11 items-center gap-2 text-sm text-[var(--tfmc-cream)]" >
172182 < input
173183 type = "checkbox"
174184 className = "h-4 w-4 accent-[var(--tfmc-accent)]"
175185 checked = { value . follow }
176186 onChange = { ( event ) => {
177187 if ( event . target . checked ) {
178188 const now = Math . floor ( Date . now ( ) / 1000 ) ;
189+ setDirty ( false ) ;
190+ setFromText ( toLocalInput ( now - ( value . to - value . from ) ) ) ;
191+ setToText ( toLocalInput ( now ) ) ;
179192 onApply ( { from : now - ( value . to - value . from ) , to : now , follow : true } ) ;
180193 } else {
181194 onApply ( { ...value , follow : false } ) ;
@@ -270,17 +283,20 @@ export function Timeline({
270283 { ticks . map ( ( t ) => (
271284 < div key = { t } className = "absolute bottom-0 h-2 border-l border-[color-mix(in_srgb,var(--tfmc-cream)_35%,transparent)]" style = { { left : pct ( t ) } } />
272285 ) ) }
273- < div className = "pointer-events-none absolute inset-y-[-3px] w-0.5 bg-white shadow" style = { { left : pct ( cursor ) } } />
274286 < input
275287 type = "range"
276288 aria-label = "Inspected moment"
289+ aria-valuetext = { formatMoment ( cursor , true ) }
277290 min = { since }
278291 max = { until }
279- step = { 1 }
292+ step = { 60 }
280293 value = { Math . min ( until , Math . max ( since , cursor ) ) }
281294 onChange = { ( event ) => onCursor ( Number ( event . target . value ) ) }
282- className = "absolute inset-0 h-full w-full cursor-pointer opacity-0"
295+ className = "peer absolute inset-0 z-10 h-full w-full cursor-pointer opacity-0"
283296 />
297+ { /* The input is invisible, so its keyboard focus is drawn here. */ }
298+ < div className = "pointer-events-none absolute inset-0 rounded-sm peer-focus-visible:ring-2 peer-focus-visible:ring-[var(--tfmc-accent)]" />
299+ < div className = "pointer-events-none absolute inset-y-[-3px] w-0.5 bg-white shadow" style = { { left : pct ( cursor ) } } />
284300 </ div >
285301 < div className = "relative h-4 text-[11px] text-[var(--tfmc-mist)]" >
286302 { ticks . map ( ( t ) => (
0 commit comments