1export interface CflSliderProps {
2 value: number;
3 onChange: (v: number) => void;
4 /** Largest stable step, when known, to show the resulting dt */
5 dtMax?: number;
6 min?: number;
7 max?: number;
8 disabled?: boolean;
9}
11/**
12 * The time step as a fraction of the stability limit. Past 1 the explicit
13 * scheme amplifies its shortest waves every step, and the run blows up within
14 * a few dozen steps; the slider colours that region so it is not a surprise.
15 */
16export function CflSlider({ value, onChange, dtMax, min = 0.5, max = 1.2, disabled }: CflSliderProps) {
17 const unstable = value > 1;
18 const pct = ((1 - min) / (max - min)) * 100;
19 return (
20 <div className={disabled ? "pl-disabled" : undefined}>
21 <div className="pl-slider">
22 <input
23 type="range"
24 min={min}
25 max={max}
26 step={0.01}
27 value={value}
28 disabled={disabled}
29 onChange={(e) => onChange(Number(e.target.value))}
30 style={{
31 background: `linear-gradient(to right, #cfe0f5 0%, #cfe0f5 ${pct}%, #f5c6c6 ${pct}%, #f5c6c6 100%)`,
32 }}
33 aria-label="time step as a fraction of the stability limit"
34 />
35 <span className="pl-mono" style={{ color: unstable ? "var(--pl-error)" : undefined }}>
36 {value.toFixed(2)} dt<sub>max</sub>
37 </span>
38 </div>
39 <div className="pl-small pl-muted">
40 {dtMax !== undefined ? <>dt = {(value * dtMax * 1e6).toPrecision(3)} µs (limit {(dtMax * 1e6).toPrecision(3)} µs). </> : null}
41 {unstable ? <span style={{ color: "var(--pl-error)" }}>Above the limit: the run will diverge.</span> : "Stable below 1."}
42 </div>
43 </div>
44 );
45}