diff --git a/apps/docs/src/examples/slider.tsx b/apps/docs/src/examples/slider.tsx
index 8f05c58b..9b0a247c 100644
--- a/apps/docs/src/examples/slider.tsx
+++ b/apps/docs/src/examples/slider.tsx
@@ -31,7 +31,7 @@ export function MultipleThumbsExample() {
-
+
@@ -98,7 +98,7 @@ export function MinStepsBetweenExample() {
-
+
@@ -141,7 +141,7 @@ export function CustomValueLabelExample() {
-
+
diff --git a/packages/core/src/skeleton/index.tsx b/packages/core/src/skeleton/index.tsx
index 81e54e2f..09858ff5 100644
--- a/packages/core/src/skeleton/index.tsx
+++ b/packages/core/src/skeleton/index.tsx
@@ -1 +1,5 @@
-export { Skeleton as Root } from "./skeleton";
+import { Skeleton as Root, type SkeletonProps } from "./skeleton";
+
+export { Root };
+
+export type { SkeletonProps };
diff --git a/packages/core/src/slider/create-slider-state.ts b/packages/core/src/slider/create-slider-state.ts
index 47fff0a5..7cd5c623 100644
--- a/packages/core/src/slider/create-slider-state.ts
+++ b/packages/core/src/slider/create-slider-state.ts
@@ -134,13 +134,15 @@ export function createSliderState(props: StateOpts): SliderState {
};
const getThumbMinValue = (index: number) => {
- return index === 0 ? mergedProps.minValue!() : values()[index - 1];
+ return index === 0
+ ? props.minValue!()
+ : values()[index - 1] + props.minStepsBetweenThumbs!() * props.step!();
};
const getThumbMaxValue = (index: number) => {
return index === values().length - 1
- ? mergedProps.maxValue!()
- : values()[index + 1];
+ ? props.maxValue!()
+ : values()[index + 1] - props.minStepsBetweenThumbs!() * props.step!();
};
const isThumbEditable = (index: number) => {
@@ -215,9 +217,8 @@ export function createSliderState(props: StateOpts): SliderState {
);
};
- const incrementThumb = (index: number, stepSize = 1) => {
- const s = Math.max(stepSize, mergedProps.step!());
- const nextValue = values()[index] + s;
+ const snapThumbValue = (index: number, value: number) => {
+ const nextValue = values()[index] + value;
const nextValues = getNextSortedValues(values(), nextValue, index);
if (
hasMinStepsBetweenValues(
@@ -237,26 +238,12 @@ export function createSliderState(props: StateOpts): SliderState {
}
};
+ const incrementThumb = (index: number, stepSize = 1) => {
+ snapThumbValue(index, Math.max(stepSize, props.step!()));
+ };
+
const decrementThumb = (index: number, stepSize = 1) => {
- const s = Math.max(stepSize, mergedProps.step!());
- const nextValue = values()[index] - s;
- const nextValues = getNextSortedValues(values(), nextValue, index);
- if (
- hasMinStepsBetweenValues(
- nextValues,
- mergedProps.minStepsBetweenThumbs!() * mergedProps.step!(),
- )
- ) {
- updateValue(
- index,
- snapValueToStep(
- nextValue,
- mergedProps.minValue!(),
- mergedProps.maxValue!(),
- mergedProps.step!(),
- ),
- );
- }
+ snapThumbValue(index, -Math.max(stepSize, props.step!()));
};
return {
diff --git a/packages/core/src/slider/slider-input.tsx b/packages/core/src/slider/slider-input.tsx
index cd42fb17..82e0334a 100644
--- a/packages/core/src/slider/slider-input.tsx
+++ b/packages/core/src/slider/slider-input.tsx
@@ -80,7 +80,7 @@ export function SliderInput(props: SliderInputProps) {
type="range"
id={fieldProps.id()}
name={formControlContext.name()}
- tabIndex={!context.state.isDisabled() ? 0 : undefined}
+ tabIndex={context.state.isDisabled() ? undefined : -1}
min={context.state.getThumbMinValue(thumb.index())}
max={context.state.getThumbMaxValue(thumb.index())}
step={context.state.step()}
diff --git a/packages/core/src/slider/slider-root.tsx b/packages/core/src/slider/slider-root.tsx
index e0dec06c..b0b00f16 100644
--- a/packages/core/src/slider/slider-root.tsx
+++ b/packages/core/src/slider/slider-root.tsx
@@ -39,9 +39,9 @@ import {
SliderDataSet,
} from "./slider-context";
import {
- getClosestValueIndex,
getNextSortedValues,
hasMinStepsBetweenValues,
+ stopEventDefaultAndPropagation,
} from "./utils";
export interface GetValueLabelParams {
@@ -278,22 +278,26 @@ export function SliderRoot(props: SliderRootProps) {
if (activeThumb !== undefined) {
state.setThumbDragging(activeThumb, false);
+ (thumbs()[activeThumb].ref() as HTMLElement).focus();
}
};
- const onHomeKeyDown = () => {
- !formControlContext.isDisabled() &&
- state.focusedThumb() !== undefined &&
- state.setThumbValue(0, state.getThumbMinValue(0));
+ const onHomeKeyDown = (event: KeyboardEvent) => {
+ const focusedThumb = state.focusedThumb();
+
+ if (!formControlContext.isDisabled() && focusedThumb !== undefined) {
+ stopEventDefaultAndPropagation(event);
+ state.setThumbValue(focusedThumb, state.getThumbMinValue(focusedThumb));
+ }
};
- const onEndKeyDown = () => {
- !formControlContext.isDisabled() &&
- state.focusedThumb() !== undefined &&
- state.setThumbValue(
- state.values().length - 1,
- state.getThumbMaxValue(state.values().length - 1),
- );
+ const onEndKeyDown = (event: KeyboardEvent) => {
+ const focusedThumb = state.focusedThumb();
+
+ if (!formControlContext.isDisabled() && focusedThumb !== undefined) {
+ stopEventDefaultAndPropagation(event);
+ state.setThumbValue(focusedThumb, state.getThumbMaxValue(focusedThumb));
+ }
};
const onStepKeyDown = (event: KeyboardEvent, index: number) => {
@@ -301,8 +305,9 @@ export function SliderRoot(props: SliderRootProps) {
switch (event.key) {
case "Left":
case "ArrowLeft":
- event.preventDefault();
- event.stopPropagation();
+ case "Down":
+ case "ArrowDown":
+ stopEventDefaultAndPropagation(event);
if (!isLTR()) {
state.incrementThumb(
index,
@@ -317,24 +322,9 @@ export function SliderRoot(props: SliderRootProps) {
break;
case "Right":
case "ArrowRight":
- event.preventDefault();
- event.stopPropagation();
- if (!isLTR()) {
- state.decrementThumb(
- index,
- event.shiftKey ? state.pageSize() : state.step(),
- );
- } else {
- state.incrementThumb(
- index,
- event.shiftKey ? state.pageSize() : state.step(),
- );
- }
- break;
case "Up":
case "ArrowUp":
- event.preventDefault();
- event.stopPropagation();
+ stopEventDefaultAndPropagation(event);
if (!isLTR()) {
state.decrementThumb(
index,
@@ -347,32 +337,18 @@ export function SliderRoot(props: SliderRootProps) {
);
}
break;
- case "Down":
- case "ArrowDown":
- event.preventDefault();
- event.stopPropagation();
- if (!isLTR()) {
- state.incrementThumb(
- index,
- event.shiftKey ? state.pageSize() : state.step(),
- );
- } else {
- state.decrementThumb(
- index,
- event.shiftKey ? state.pageSize() : state.step(),
- );
- }
- break;
case "Home":
- onHomeKeyDown();
+ onHomeKeyDown(event);
break;
case "End":
- onEndKeyDown();
+ onEndKeyDown(event);
break;
case "PageUp":
+ stopEventDefaultAndPropagation(event);
state.incrementThumb(index, state.pageSize());
break;
case "PageDown":
+ stopEventDefaultAndPropagation(event);
state.decrementThumb(index, state.pageSize());
break;
}
diff --git a/packages/core/src/slider/utils.ts b/packages/core/src/slider/utils.ts
index b255a321..1c04e297 100644
--- a/packages/core/src/slider/utils.ts
+++ b/packages/core/src/slider/utils.ts
@@ -20,7 +20,11 @@ export function getClosestValueIndex(values: number[], nextValue: number) {
if (values.length === 1) return 0;
const distances = values.map((value) => Math.abs(value - nextValue));
const closestDistance = Math.min(...distances);
- return distances.indexOf(closestDistance);
+ const closestIndex = distances.indexOf(closestDistance);
+
+ return nextValue < values[closestIndex]
+ ? closestIndex
+ : distances.lastIndexOf(closestDistance);
}
/**
@@ -78,3 +82,8 @@ export function linearScale(
return output[0] + ratio * (value - input[0]);
};
}
+
+export function stopEventDefaultAndPropagation(event: Event) {
+ event.preventDefault();
+ event.stopPropagation();
+}