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({
17 value,
18 onChange,
19 dtMax,
20 min = 0.5,
21 max = 1.2,
22 disabled,
23}: CflSliderProps) {
24 const unstable = value > 1;
25 const pct = ((1 - min) / (max - min)) * 100;
26 return (
27 <div className={disabled ? "pl-disabled" : undefined}>
28 <div className="pl-slider">
29 <input
30 type="range"
31 min={min}
32 max={max}
33 step={0.01}
34 value={value}
35 disabled={disabled}
36 onChange={(e) => onChange(Number(e.target.value))}
37 style={{
38 background: `linear-gradient(to right, #cfe0f5 0%, #cfe0f5 ${pct}%, #f5c6c6 ${pct}%, #f5c6c6 100%)`,
39 }}
40 aria-label="time step as a fraction of the stability limit"
41 />
42 <span className="pl-mono" style={{ color: unstable ? "var(--pl-error)" : undefined }}>
43 {value.toFixed(2)} dt<sub>max</sub>
44 </span>
45 </div>
46 <div className="pl-small pl-muted">
47 {dtMax !== undefined ? (
48 <>
49 dt = {(value * dtMax * 1e6).toPrecision(3)} µs (limit {(dtMax * 1e6).toPrecision(3)}{" "}
50 µs).{" "}
51 </>
52 ) : null}
53 {unstable ? (
54 <span style={{ color: "var(--pl-error)" }}>Above the limit: the run will diverge.</span>
55 ) : (
56 "Stable below 1."
57 )}
58 </div>
59 </div>
60 );
61}