diff --git a/.changeset/mighty-timers-cough.md b/.changeset/mighty-timers-cough.md
new file mode 100644
index 00000000..0465ff11
--- /dev/null
+++ b/.changeset/mighty-timers-cough.md
@@ -0,0 +1,5 @@
+---
+"@kobalte/core": patch
+---
+
+added `Slider` component
diff --git a/apps/docs/src/VERSIONS.ts b/apps/docs/src/VERSIONS.ts
index 43c3db55..86f3ef15 100644
--- a/apps/docs/src/VERSIONS.ts
+++ b/apps/docs/src/VERSIONS.ts
@@ -14,4 +14,4 @@ export const CORE_VERSIONS = [
export const LATEST_CORE_CHANGELOG_URL = `/docs/changelog/${CORE_VERSIONS[0].replaceAll(".", "-")}`;
-export const LATEST_CORE_VERSION_NAME = "v0.11.1";
+export const LATEST_CORE_VERSION_NAME = "v0.11.2";
diff --git a/apps/docs/src/examples/slider.module.css b/apps/docs/src/examples/slider.module.css
new file mode 100644
index 00000000..a28a6bbc
--- /dev/null
+++ b/apps/docs/src/examples/slider.module.css
@@ -0,0 +1,71 @@
+.SliderRoot {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ user-select: none;
+ touch-action: none;
+ width: 200px;
+}
+
+.SliderRoot[data-orientation="vertical"] {
+ height: 200px;
+}
+
+.SliderTrack {
+ background-color: hsl(240 6% 90%);
+ position: relative;
+ border-radius: 9999px;
+ height: 8px;
+ width: 100%;
+}
+
+.SliderTrack[data-orientation="vertical"] {
+ width: 8px;
+ height: 100%;
+}
+
+.SliderRange {
+ position: absolute;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ height: 100%;
+}
+
+.SliderRange[data-orientation="vertical"] {
+ width: 100%;
+ height: unset;
+}
+
+.SliderThumb {
+ display: block;
+ width: 16px;
+ height: 16px;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ top: -4px;
+}
+
+.SliderThumb[data-orientation="vertical"] {
+ left: -4px;
+ top: unset;
+}
+
+.SliderThumb:hover {
+ box-shadow: 0 0 0 5px #2a91fe98;
+}
+
+.SliderThumb:focus {
+ outline: none;
+ box-shadow: 0 0 0 5px #2a91fe98;
+}
+
+.SliderLabel {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+}
+
+[data-kb-theme="dark"] .SliderTrack {
+ background-color: hsl(240 5% 26%);
+}
diff --git a/apps/docs/src/examples/slider.tsx b/apps/docs/src/examples/slider.tsx
new file mode 100644
index 00000000..4feb80a0
--- /dev/null
+++ b/apps/docs/src/examples/slider.tsx
@@ -0,0 +1,164 @@
+import { Slider } from "@kobalte/core";
+import { createSignal } from "solid-js";
+import style from "./slider.module.css";
+
+export function BasicExample() {
+ return (
+
+
+ Label
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function MultipleThumbsExample() {
+ return (
+
+
+ Label
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function StepExample() {
+ return (
+
+
+
+ Step size 8
+
+
+
+
+
+
+
+
+
+
+
+ Step size 10
+
+
+
+
+
+
+
+
+
+
+
+ Step size 20
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function MinStepsBetweenExample() {
+ return (
+
+
+ Label
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function VerticalSliderExample() {
+ return (
+
+
+ Label
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function CustomValueLabelExample() {
+ return (
+ `$${params.values[0]} - $${params.values[1]}`}
+ >
+
+ Money
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export function ControlledExample() {
+ const [values, setValues] = createSignal([40]);
+ return (
+
+
+ Label
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/docs/src/routes/docs/changelog/0-11-x.mdx b/apps/docs/src/routes/docs/changelog/0-11-x.mdx
index 61a501d1..3e7ca2c1 100644
--- a/apps/docs/src/routes/docs/changelog/0-11-x.mdx
+++ b/apps/docs/src/routes/docs/changelog/0-11-x.mdx
@@ -1,5 +1,15 @@
# v0.11.x
+## v0.11.2 (October 21, 2023)
+
+**New features**
+
+- Added `Slider` component.
+
+**Bug fixes**
+
+- [#278](https://github.com/kobaltedev/kobalte/pull/278)
+
## v0.11.1 (October 20, 2023)
**New features**
diff --git a/apps/docs/src/routes/docs/core.tsx b/apps/docs/src/routes/docs/core.tsx
index 40eb8ee0..efab4157 100644
--- a/apps/docs/src/routes/docs/core.tsx
+++ b/apps/docs/src/routes/docs/core.tsx
@@ -67,7 +67,6 @@ const CORE_NAV_SECTIONS: NavSection[] = [
{
title: "Combobox",
href: "/docs/core/components/combobox",
- status: "updated",
},
{
title: "Context Menu",
@@ -122,6 +121,11 @@ const CORE_NAV_SECTIONS: NavSection[] = [
href: "/docs/core/components/skeleton",
status: "new",
},
+ {
+ title: "Slider",
+ href: "/docs/core/components/slider",
+ status: "new",
+ },
{
title: "Switch",
href: "/docs/core/components/switch",
diff --git a/apps/docs/src/routes/docs/core/components/slider.mdx b/apps/docs/src/routes/docs/core/components/slider.mdx
new file mode 100644
index 00000000..98f1e899
--- /dev/null
+++ b/apps/docs/src/routes/docs/core/components/slider.mdx
@@ -0,0 +1,813 @@
+import { Preview, TabsSnippets, Kbd } from "../../../../components";
+import {
+ BasicExample,
+ MultipleThumbsExample,
+ StepExample,
+ MinStepsBetweenExample,
+ VerticalSliderExample,
+ CustomValueLabelExample,
+ ControlledExample,
+} from "../../../../examples/slider";
+
+# Slider
+
+An input where the user selects a value from within a given range.
+
+## Import
+
+```ts
+import { Slider } from "@kobalte/core";
+```
+
+## Features
+
+- Follow the [WAI ARIA Slider](https://www.w3.org/WAI/ARIA/apg/patterns/slider-multithumb/) design pattern.
+- Can be controlled or uncontrolled.
+- Support for multiple thumbs.
+- Support a minimum step between thumbs.
+- Support click or touch on track to change value.
+- Support right or left direction.
+- Support for custom value label.
+
+## Anatomy
+
+The slider consists of:
+
+- **Slider.Root:** The root container for the slider.
+- **Slider.Track:** The component that visually represents the slider track.
+- **Slider.Fill:** The component that visually represents the slider value.
+- **Slider.Thumb:** The thumb that is used to visually indicate a value in the slider.
+- **Slider.Input:** The native html input that is visually hidden in the slider thumb.
+- **Slider.Label:** The label that gives the user information on the slider.
+- **Slider.ValueLabel:** The accessible label text representing the current value in a human-readable format.
+
+```tsx
+
+
+
+
+
+
+
+
+
+
+```
+
+## Example
+
+
+
+
+
+
+
+ index.tsx
+ style.css
+
+{/* */}
+
+ ```tsx
+ import { Slider } from "@kobalte/core";
+ import "./style.css";
+
+ function App() {
+ return (
+
+
+ Label
+
+
+
+
+
+
+
+
+
+ );
+ }
+ ```
+
+
+
+ ```css
+ .SliderRoot {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ user-select: none;
+ touch-action: none;
+ width: 200px;
+ }
+
+ .SliderTrack {
+ background-color: hsl(240 6% 90%);
+ position: relative;
+ border-radius: 9999px;
+ height: 8px;
+ width: 100%;
+ }
+
+ .SliderRange {
+ position: absolute;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ height: 100%;
+ }
+
+ .SliderThumb {
+ display: block;
+ width: 16px;
+ height: 16px;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ top: -4px;
+ }
+
+ .SliderThumb:hover {
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderThumb:focus {
+ outline: none;
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderLabel {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ }
+ ```
+
+
+{/* */}
+
+
+## Usage
+
+### Multiple Thumbs
+
+
+
+
+
+
+
+ index.tsx
+ style.css
+
+{/* */}
+
+ ```tsx
+ import { Slider } from "@kobalte/core";
+ import "./style.css";
+
+ function App() {
+ return (
+
+
+ Label
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+ ```
+
+
+
+ ```css
+ .SliderRoot {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ user-select: none;
+ touch-action: none;
+ width: 200px;
+ }
+
+ .SliderTrack {
+ background-color: hsl(240 6% 90%);
+ position: relative;
+ border-radius: 9999px;
+ height: 8px;
+ width: 100%;
+ }
+
+ .SliderRange {
+ position: absolute;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ height: 100%;
+ }
+
+ .SliderThumb {
+ display: block;
+ width: 16px;
+ height: 16px;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ top: -4px;
+ }
+
+ .SliderThumb:hover {
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderThumb:focus {
+ outline: none;
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderLabel {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ }
+ ```
+
+
+{/* */}
+
+
+### Modify step size
+
+
+
+
+
+
+
+ index.tsx
+ style.css
+
+{/* */}
+
+ ```tsx
+ import { Slider } from "@kobalte/core";
+ import "./style.css";
+
+ function App() {
+ return (
+ <>
+
+
+ Step size 8
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Step size 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Step size 20
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+ }
+ ```
+
+
+
+ ```css
+ .SliderRoot {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ user-select: none;
+ touch-action: none;
+ width: 200px;
+ }
+
+ .SliderTrack {
+ background-color: hsl(240 6% 90%);
+ position: relative;
+ border-radius: 9999px;
+ height: 8px;
+ width: 100%;
+ }
+
+ .SliderRange {
+ position: absolute;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ height: 100%;
+ }
+
+ .SliderThumb {
+ display: block;
+ width: 16px;
+ height: 16px;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ top: -4px;
+ }
+
+ .SliderThumb:hover {
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderThumb:focus {
+ outline: none;
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderLabel {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ }
+ ```
+
+
+{/* */}
+
+
+### Steps between thumbs
+
+
+
+
+
+
+
+ index.tsx
+ style.css
+
+{/* */}
+
+ ```tsx
+ import { Slider } from "@kobalte/core";
+ import "./style.css";
+
+ function App() {
+ return (
+
+
+ Label
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+ ```
+
+
+
+ ```css
+ .SliderRoot {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ user-select: none;
+ touch-action: none;
+ width: 200px;
+ }
+
+ .SliderTrack {
+ background-color: hsl(240 6% 90%);
+ position: relative;
+ border-radius: 9999px;
+ height: 8px;
+ width: 100%;
+ }
+
+ .SliderRange {
+ position: absolute;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ height: 100%;
+ }
+
+ .SliderThumb {
+ display: block;
+ width: 16px;
+ height: 16px;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ top: -4px;
+ }
+
+ .SliderThumb:hover {
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderThumb:focus {
+ outline: none;
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderLabel {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ }
+ ```
+
+
+{/* */}
+
+
+### Vertical Slider
+
+
+
+
+
+
+
+ index.tsx
+ style.css
+
+{/* */}
+
+ ```tsx
+ import { Slider } from "@kobalte/core";
+ import "./style.css";
+
+ function App() {
+ return (
+
+
+ Label
+
+
+
+
+
+
+
+
+
+ );
+ }
+ ```
+
+
+
+ ```css
+ .SliderRoot {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ user-select: none;
+ touch-action: none;
+ height: 200px;
+ }
+
+ .SliderTrack {
+ background-color: hsl(240 6% 90%);
+ position: relative;
+ border-radius: 9999px;
+ width: 8px;
+ height: 100%;
+ }
+
+ .SliderRange {
+ position: absolute;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ width: 100%;
+ }
+
+ .SliderThumb {
+ display: block;
+ width: 16px;
+ height: 16px;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ left: -4px;
+ }
+
+ .SliderThumb:hover {
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderThumb:focus {
+ outline: none;
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderLabel {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ }
+ ```
+
+
+{/* */}
+
+
+### Custom Value Label
+
+
+
+
+
+
+
+ index.tsx
+ style.css
+
+{/* */}
+
+ ```tsx
+ import { Slider } from "@kobalte/core";
+ import "./style.css";
+
+ function App() {
+ return (
+ `$${params.values[0]} - $${params.values[1]}`}
+ >
+
+ Money
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+ ```
+
+
+
+ ```css
+ .SliderRoot {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ user-select: none;
+ touch-action: none;
+ width: 200px;
+ }
+
+ .SliderTrack {
+ background-color: hsl(240 6% 90%);
+ position: relative;
+ border-radius: 9999px;
+ height: 8px;
+ width: 100%;
+ }
+
+ .SliderRange {
+ position: absolute;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ height: 100%;
+ }
+
+ .SliderThumb {
+ display: block;
+ width: 16px;
+ height: 16px;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ top: -4px;
+ }
+
+ .SliderThumb:hover {
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderThumb:focus {
+ outline: none;
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderLabel {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ }
+ ```
+
+
+{/* */}
+
+
+### Controlled Value
+
+
+
+
+
+
+
+ index.tsx
+ style.css
+
+{/* */}
+
+ ```tsx
+ import { createSignal } from 'solid-js'
+ import { Slider } from "@kobalte/core";
+ import "./style.css";
+
+ function App() {
+ const [values, setValues] = createSignal([40])
+
+ return (
+
+
+ Label
+
+
+
+
+
+
+
+
+
+ );
+ }
+ ```
+
+
+
+ ```css
+ .SliderRoot {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ user-select: none;
+ touch-action: none;
+ width: 200px;
+ }
+
+ .SliderTrack {
+ background-color: hsl(240 6% 90%);
+ position: relative;
+ border-radius: 9999px;
+ height: 8px;
+ width: 100%;
+ }
+
+ .SliderRange {
+ position: absolute;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ height: 100%;
+ }
+
+ .SliderThumb {
+ display: block;
+ width: 16px;
+ height: 16px;
+ background-color: hsl(200 98% 39%);
+ border-radius: 9999px;
+ top: -4px;
+ }
+
+ .SliderThumb:hover {
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderThumb:focus {
+ outline: none;
+ box-shadow: 0 0 0 5px #2a91fe98;
+ }
+
+ .SliderLabel {
+ width: 100%;
+ display: flex;
+ justify-content: space-between;
+ }
+ ```
+
+
+{/* */}
+
+## API Reference
+
+### Slider.Root
+
+| Prop | Description |
+| :-------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| value | `number[]`
The controlled values of the slider. Must be used in conjunction with `onChange`. |
+| defaultValue | `number[]`
The value of the slider when initially rendered. Use when you do not need to control the state of the slider. |
+| onChange | `(value: number[]) => void`
Event handler called when the value changes. |
+| onChangeEnd | `(value: number[]) => void`
Event handler called when the value changes at the end of an interaction. |
+| inverted | `boolean`
Whether the slider is visually inverted. Defaults to false. |
+| minValue | `number`
The minimum slider value. Defaults to 0 |
+| maxValue | `number`
The maximum slider value. Defaults to 100 |
+| step | `number`
The stepping interval. Defaults to 1 |
+| minStepsBetweenThumbs | `number`
The minimum permitted steps between thumbs. Defaults to 0 |
+| getValueLabel | `(params: GetValueLabelParams) => string`
A function to get the accessible label text representing the current value in a human-readable format. If not provided, the value label will be read as a percentage of the max value. |
+| orientation | `'horizontal' \| 'vertical'`
The orientation of the slider. |
+| name | `string`
The name of the slider, used when submitting an HTML form. |
+| validationState | `'valid' \| 'invalid'`
Whether the slider should display its "valid" or "invalid" visual styling. |
+| required | `boolean`
Whether the user must check a radio group item before the owning form can be submitted. |
+| disabled | `boolean`
Whether the radio group is disabled. |
+| readOnly | `boolean`
Whether the radio group items can be selected but not changed by the user. |
+
+| Data attribute | Description |
+| :---------------------------- | :--------------------------------------------------------------------------------- |
+| data-orientation='horizontal' | Present when the slider has horizontal orientation. |
+| data-orientation='vertical' | Present when the slider has vertical orientation. |
+| data-valid | Present when the slider is valid according to the validation rules. |
+| data-invalid | Present when the slider is invalid according to the validation rules. |
+| data-required | Present when the user must slider an item before the owning form can be submitted. |
+| data-disabled | Present when the slider is disabled. |
+| data-readonly | Present when the slider is read only. |
+
+`Slider.ValueLabel`, `Slider.Fill`, `Slider.Input`, `Slider.Thumb` and `Slider.Track` share the same data-attributes.
+
+## Rendered elements
+
+| Component | Default rendered element |
+| :------------------ | :----------------------- |
+| `Slider.Root` | `div` |
+| `Slider.Track` | `div` |
+| `Slider.Fill` | `div` |
+| `Slider.Thumb` | `span` |
+| `Slider.Input` | `input` |
+| `Slider.ValueLabel` | `div` |
+
+## Accessibility
+
+### Keyboard Interactions
+
+| Key | Description |
+| :-------------------- | :-------------------------------------------------------------------- |
+| PageUp | Increases the value of the focused thumb by a larger `step`. |
+| PageDown | Decreases the value of the focused thumb by a larger `step`. |
+| ArrowDown | Decreases the value of the focused thumb by the `step` amount. |
+| ArrowUp | Increases the value of the focused thumb by the `step` amount. |
+| ArrowRight | Increments/decrements by the `step` value depending on `orientation`. |
+| ArrowLeft | Increments/decrements by the `step` value depending on `orientation`. |
+| Home | Sets the value of the first thumb to the minimum value. |
+| End | Sets the value of the last thumb to the maximum value. |
diff --git a/packages/core/src/slider/slider-context.tsx b/packages/core/src/slider/slider-context.tsx
index 01c11f1f..9876d220 100644
--- a/packages/core/src/slider/slider-context.tsx
+++ b/packages/core/src/slider/slider-context.tsx
@@ -16,7 +16,7 @@ export interface SliderContextValue {
state: SliderState;
thumbs: Accessor;
setThumbs: Setter;
- onSlideStart: ((value: number) => void) | undefined;
+ onSlideStart: ((index: number, value: number) => void) | undefined;
onSlideMove: ((deltas: { deltaX: number; deltaY: number }) => void) | undefined;
onSlideEnd: (() => void) | undefined;
onStepKeyDown: (event: KeyboardEvent, index: number) => void;
diff --git a/packages/core/src/slider/slider-root.tsx b/packages/core/src/slider/slider-root.tsx
index adfa5287..c99c3dc3 100644
--- a/packages/core/src/slider/slider-root.tsx
+++ b/packages/core/src/slider/slider-root.tsx
@@ -205,14 +205,11 @@ export function SliderRoot(props: SliderRootProps) {
const [trackRef, setTrackRef] = createSignal();
let currentPosition: number | null = null;
- const onSlideStart = (value: number) => {
- const closestIndex = getClosestValueIndex(state.values(), value);
- if (closestIndex >= 0) {
- state.setFocusedThumb(closestIndex);
- state.setThumbDragging(closestIndex, true);
- state.setThumbValue(closestIndex, value);
- currentPosition = null;
- }
+ const onSlideStart = (index: number, value: number) => {
+ state.setFocusedThumb(index);
+ state.setThumbDragging(index, true);
+ state.setThumbValue(index, value);
+ currentPosition = null;
};
const onSlideMove = ({ deltaX, deltaY }: { deltaX: number; deltaY: number }) => {
diff --git a/packages/core/src/slider/slider-thumb.tsx b/packages/core/src/slider/slider-thumb.tsx
index d23c8c2d..f62d3d1e 100644
--- a/packages/core/src/slider/slider-thumb.tsx
+++ b/packages/core/src/slider/slider-thumb.tsx
@@ -13,7 +13,15 @@
*/
import { callHandler, mergeDefaultProps, mergeRefs, OverrideComponentProps } from "@kobalte/utils";
-import { Accessor, createContext, JSX, onMount, splitProps, useContext } from "solid-js";
+import {
+ Accessor,
+ createContext,
+ createUniqueId,
+ JSX,
+ onMount,
+ splitProps,
+ useContext,
+} from "solid-js";
import { createFormControlField, FORM_CONTROL_FIELD_PROP_NAMES } from "../form-control";
import { AsChildProp, Polymorphic } from "../polymorphic";
@@ -33,7 +41,7 @@ export function SliderThumb(props: SliderThumbProps) {
props = mergeDefaultProps(
{
- id: context.generateId("thumb"),
+ id: context.generateId(`thumb-${createUniqueId()}`),
},
props,
);
@@ -101,18 +109,20 @@ export function SliderThumb(props: SliderThumbProps) {
const target = e.currentTarget as HTMLElement;
- target.setPointerCapture(e.pointerId);
e.preventDefault();
+ e.stopPropagation();
+ target.setPointerCapture(e.pointerId);
target.focus();
startPosition = context.state.orientation() === "horizontal" ? e.clientX : e.clientY;
- if (value()) {
- context.onSlideStart?.(value()!);
+ if (value() !== undefined) {
+ context.onSlideStart?.(index(), value()!);
}
};
const onPointerMove: JSX.EventHandlerUnion = e => {
+ e.stopPropagation();
callHandler(e, local.onPointerMove);
const target = e.currentTarget as HTMLElement;
@@ -129,6 +139,7 @@ export function SliderThumb(props: SliderThumbProps) {
};
const onPointerUp: JSX.EventHandlerUnion = e => {
+ e.stopPropagation();
callHandler(e, local.onPointerUp);
const target = e.currentTarget as HTMLElement;
diff --git a/packages/core/src/slider/slider-track.tsx b/packages/core/src/slider/slider-track.tsx
index f3b39920..0197da44 100644
--- a/packages/core/src/slider/slider-track.tsx
+++ b/packages/core/src/slider/slider-track.tsx
@@ -3,7 +3,7 @@ import { createSignal, JSX, splitProps } from "solid-js";
import { AsChildProp, Polymorphic } from "../polymorphic";
import { useSliderContext } from "./slider-context";
-import { linearScale } from "./utils";
+import { getClosestValueIndex, linearScale } from "./utils";
export interface SliderTrackProps extends OverrideComponentProps<"div", AsChildProp> {}
@@ -58,7 +58,8 @@ export function SliderTrack(props: SliderTrackProps) {
context.state.orientation() === "horizontal" ? e.clientX : e.clientY,
);
startPosition = context.state.orientation() === "horizontal" ? e.clientX : e.clientY;
- context.onSlideStart?.(value);
+ const closestIndex = getClosestValueIndex(context.state.values(), value);
+ context.onSlideStart?.(closestIndex, value);
};
const onPointerMove: JSX.EventHandlerUnion = e => {