feat: add keep alive

This commit is contained in:
2026-07-26 17:56:25 +02:00
parent a157b5f198
commit 31f645ebb2
21 changed files with 1205 additions and 261 deletions
+29 -28
View File
@@ -2,12 +2,8 @@ import { useMemo, useRef, useState, useEffect } from 'react';
import './VerticalSlider.css';
// Design size 260*70 (height*width) — kept as the reference ratio.
// All CSS dimensions are expressed in container-query width units (cqw)
// relative to this DESIGN_WIDTH, so the whole component scales together
// and preserves this aspect ratio at any rendered size.
const DESIGN_WIDTH = 70;
const DESIGN_HEIGHT = 240;
type VerticalSliderProps = {
min: number;
@@ -59,6 +55,24 @@ export default function VerticalSlider({
const thumbRef = useRef<HTMLDivElement>(null);
const keyboardEditing = useRef(false);
const viewportRef = useRef<HTMLDivElement>(null);
const [scale, setScale] = useState(1);
useEffect(() => {
const el = viewportRef.current;
if (!el) return;
const observer = new ResizeObserver((entries) => {
const entry = entries[0];
const { width, height } = entry.contentRect;
if (width === 0 || height === 0) return;
setScale(Math.min(width / DESIGN_WIDTH, height / DESIGN_HEIGHT));
});
observer.observe(el);
return () => observer.disconnect();
}, []);
useEffect(() => {
if (keyboardEditing.current) return;
@@ -76,26 +90,6 @@ export default function VerticalSlider({
[step, switchStep]
);
const maxChars = useMemo(() => {
const minStr = min.toFixed(decimals);
const maxStr = max.toFixed(decimals);
return Math.max(minStr.length, maxStr.length) + unit.length;
}, [min, max, decimals, unit]);
// Computed in "design pixels" (i.e. assuming the component is rendered at
// DESIGN_WIDTH px wide), then converted below to cqw so it scales with
// the actual rendered size instead of staying a fixed pixel value.
const valueFontSize = useMemo(() => {
const availableWidth = DESIGN_WIDTH - 8;
const AVG_CHAR_WIDTH_RATIO = 0.6;
const sizeByWidth = availableWidth / (maxChars * AVG_CHAR_WIDTH_RATIO);
return Math.min(16, Math.max(8, sizeByWidth));
}, [maxChars]);
// Convert the "design px" font size into cqw units relative to the
// component's own width, so it scales proportionally with everything else.
const valueFontSizeCqw = useMemo(() => (valueFontSize / DESIGN_WIDTH) * 100, [valueFontSize]);
function normalizeValue(input: number) {
const clamped = Math.min(max, Math.max(min, input));
const snapped = Math.round(clamped / currentStep) * currentStep;
@@ -202,8 +196,15 @@ export default function VerticalSlider({
const percent = (value - min) / (max - min);
return (
<div className={['gauge-viewport', className].filter(Boolean).join(' ')} style={style}>
<div className="gauge-container">
<div
className={['gauge-viewport', className].filter(Boolean).join(' ')}
style={style}
ref={viewportRef}
>
<div
className="gauge-container"
style={{ transform: `translate(-50%, -50%) scale(${scale})` }}
>
<div className="gauge-track">
<div className="ticks left">
{Array.from({ length: 15 }).map((_, i) => (
@@ -233,7 +234,7 @@ export default function VerticalSlider({
</div>
</div>
<div className="value" style={{ fontSize: `${valueFontSizeCqw}cqw` }}>
<div className="value">
<input
ref={inputRef}
type="text"