Chore/architecture refactoring #42
@@ -70,6 +70,19 @@ const SLIDER_PERSIST_DEBOUNCE_MS = 100;
|
|||||||
const SLIDER_PADDING_MOBILE_PX = 48;
|
const SLIDER_PADDING_MOBILE_PX = 48;
|
||||||
const SLIDER_PADDING_DESKTOP_PX = 96;
|
const SLIDER_PADDING_DESKTOP_PX = 96;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Position bounds (percent of container width).
|
||||||
|
*/
|
||||||
|
const SLIDER_MIN = 0;
|
||||||
|
const SLIDER_MAX = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fine and coarse keyboard step sizes. Shift / Page keys use the coarse
|
||||||
|
* step; bare arrow keys use the fine step.
|
||||||
|
*/
|
||||||
|
const SLIDER_STEP_FINE = 1;
|
||||||
|
const SLIDER_STEP_COARSE = 10;
|
||||||
|
|
||||||
const fontA = $derived(comparisonStore.fontA);
|
const fontA = $derived(comparisonStore.fontA);
|
||||||
const fontB = $derived(comparisonStore.fontB);
|
const fontB = $derived(comparisonStore.fontB);
|
||||||
const isLoading = $derived(comparisonStore.isLoading || !comparisonStore.isReady);
|
const isLoading = $derived(comparisonStore.isLoading || !comparisonStore.isReady);
|
||||||
@@ -130,6 +143,46 @@ function startDragging(e: PointerEvent) {
|
|||||||
handleMove(e);
|
handleMove(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keyboard control for the comparison slider. Implements the standard
|
||||||
|
* ARIA slider keyboard contract: arrows step the position, Shift+arrow
|
||||||
|
* and PageUp/PageDown jump by the coarse step, Home/End snap to bounds.
|
||||||
|
*/
|
||||||
|
function handleKeydown(e: KeyboardEvent) {
|
||||||
|
const coarse = e.shiftKey;
|
||||||
|
const step = coarse ? SLIDER_STEP_COARSE : SLIDER_STEP_FINE;
|
||||||
|
const current = sliderSpring.target;
|
||||||
|
let next = current;
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case 'ArrowLeft':
|
||||||
|
case 'ArrowDown':
|
||||||
|
next = current - step;
|
||||||
|
break;
|
||||||
|
case 'ArrowRight':
|
||||||
|
case 'ArrowUp':
|
||||||
|
next = current + step;
|
||||||
|
break;
|
||||||
|
case 'PageDown':
|
||||||
|
next = current - SLIDER_STEP_COARSE;
|
||||||
|
break;
|
||||||
|
case 'PageUp':
|
||||||
|
next = current + SLIDER_STEP_COARSE;
|
||||||
|
break;
|
||||||
|
case 'Home':
|
||||||
|
next = SLIDER_MIN;
|
||||||
|
break;
|
||||||
|
case 'End':
|
||||||
|
next = SLIDER_MAX;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
sliderSpring.target = Math.max(SLIDER_MIN, Math.min(SLIDER_MAX, next));
|
||||||
|
}
|
||||||
|
|
||||||
const storeSliderPosition = debounce((value: number) => {
|
const storeSliderPosition = debounce((value: number) => {
|
||||||
comparisonStore.sliderPosition = value;
|
comparisonStore.sliderPosition = value;
|
||||||
}, SLIDER_PERSIST_DEBOUNCE_MS);
|
}, SLIDER_PERSIST_DEBOUNCE_MS);
|
||||||
@@ -271,8 +324,12 @@ const paddingClass = $derived(
|
|||||||
role="slider"
|
role="slider"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
aria-valuenow={Math.round(sliderPos)}
|
aria-valuenow={Math.round(sliderPos)}
|
||||||
|
aria-valuemin={SLIDER_MIN}
|
||||||
|
aria-valuemax={SLIDER_MAX}
|
||||||
|
aria-orientation="horizontal"
|
||||||
aria-label="Font comparison slider"
|
aria-label="Font comparison slider"
|
||||||
onpointerdown={startDragging}
|
onpointerdown={startDragging}
|
||||||
|
onkeydown={handleKeydown}
|
||||||
class="
|
class="
|
||||||
relative w-full max-w-6xl h-full
|
relative w-full max-w-6xl h-full
|
||||||
flex flex-col justify-center
|
flex flex-col justify-center
|
||||||
|
|||||||
Reference in New Issue
Block a user