INIT
CI / build (push) Has been skipped
CI / deploy (push) Successful in 1m11s

This commit is contained in:
eewing
2026-02-18 15:17:47 -06:00
parent e74c106f85
commit ec317eb17c
11532 changed files with 1631690 additions and 1 deletions
+33
View File
@@ -0,0 +1,33 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { SliderRangeProps } from "../types.js";
import { SliderRangeState } from "../slider.svelte.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
children,
child,
ref = $bindable(null),
id = createId(uid),
...restProps
}: SliderRangeProps = $props();
const rangeState = SliderRangeState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, rangeState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<span {...mergedProps}>
{@render children?.()}
</span>
{/if}
@@ -0,0 +1,4 @@
import type { SliderRangeProps } from "../types.js";
declare const SliderRange: import("svelte").Component<SliderRangeProps, {}, "ref">;
type SliderRange = ReturnType<typeof SliderRange>;
export default SliderRange;
@@ -0,0 +1,50 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { SliderThumbLabelProps } from "../types.js";
import { SliderRootContext, SliderThumbLabelState } from "../slider.svelte.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
children,
child,
ref = $bindable(null),
id = createId(uid),
index,
position: positionProp,
...restProps
}: SliderThumbLabelProps = $props();
const root = SliderRootContext.get();
const position = $derived.by(() => {
if (positionProp !== undefined) return positionProp;
switch (root.direction) {
case "lr":
case "rl":
return "top";
case "tb":
case "bt":
return "left";
}
});
const tickLabelState = SliderThumbLabelState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
index: boxWith(() => index),
position: boxWith(() => position),
});
const mergedProps = $derived(mergeProps(restProps, tickLabelState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<span {...mergedProps}>{@render children?.()}</span>
{/if}
@@ -0,0 +1,4 @@
import type { SliderThumbLabelProps } from "../types.js";
declare const SliderThumbLabel: import("svelte").Component<SliderThumbLabelProps, {}, "ref">;
type SliderThumbLabel = ReturnType<typeof SliderThumbLabel>;
export default SliderThumbLabel;
+43
View File
@@ -0,0 +1,43 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { SliderThumbProps } from "../types.js";
import { SliderThumbState } from "../slider.svelte.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
children,
child,
ref = $bindable(null),
id = createId(uid),
index,
disabled = false,
...restProps
}: SliderThumbProps = $props();
const thumbState = SliderThumbState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
index: boxWith(() => index),
disabled: boxWith(() => disabled),
});
const mergedProps = $derived(mergeProps(restProps, thumbState.props));
</script>
{#if child}
{@render child({
active: thumbState.root.isThumbActive(thumbState.opts.index.current),
props: mergedProps,
})}
{:else}
<span {...mergedProps}>
{@render children?.({
active: thumbState.root.isThumbActive(thumbState.opts.index.current),
})}
</span>
{/if}
@@ -0,0 +1,4 @@
import type { SliderThumbProps } from "../types.js";
declare const SliderThumb: import("svelte").Component<SliderThumbProps, {}, "ref">;
type SliderThumb = ReturnType<typeof SliderThumb>;
export default SliderThumb;
@@ -0,0 +1,50 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { SliderTickLabelProps } from "../types.js";
import { SliderRootContext, SliderTickLabelState } from "../slider.svelte.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
children,
child,
ref = $bindable(null),
id = createId(uid),
index,
position: positionProp,
...restProps
}: SliderTickLabelProps = $props();
const root = SliderRootContext.get();
const position = $derived.by(() => {
if (positionProp !== undefined) return positionProp;
switch (root.direction) {
case "lr":
case "rl":
return "top";
case "tb":
case "bt":
return "left";
}
});
const tickLabelState = SliderTickLabelState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
index: boxWith(() => index),
position: boxWith(() => position),
});
const mergedProps = $derived(mergeProps(restProps, tickLabelState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<span {...mergedProps}>{@render children?.()}</span>
{/if}
@@ -0,0 +1,4 @@
import type { SliderTickLabelProps } from "../types.js";
declare const SliderTickLabel: import("svelte").Component<SliderTickLabelProps, {}, "ref">;
type SliderTickLabel = ReturnType<typeof SliderTickLabel>;
export default SliderTickLabel;
+34
View File
@@ -0,0 +1,34 @@
<script lang="ts">
import { boxWith, mergeProps } from "svelte-toolbelt";
import type { SliderTickProps } from "../types.js";
import { SliderTickState } from "../slider.svelte.js";
import { createId } from "../../../internal/create-id.js";
const uid = $props.id();
let {
children,
child,
ref = $bindable(null),
id = createId(uid),
index,
...restProps
}: SliderTickProps = $props();
const tickState = SliderTickState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
index: boxWith(() => index),
});
const mergedProps = $derived(mergeProps(restProps, tickState.props));
</script>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<span {...mergedProps}>{@render children?.()}</span>
{/if}
@@ -0,0 +1,4 @@
import type { SliderTickProps } from "../types.js";
declare const SliderTick: import("svelte").Component<SliderTickProps, {}, "ref">;
type SliderTick = ReturnType<typeof SliderTick>;
export default SliderTick;
+99
View File
@@ -0,0 +1,99 @@
<script lang="ts">
import { boxWith, mergeProps, type WritableBox } from "svelte-toolbelt";
import type { SliderRootProps } from "../types.js";
import { SliderRootState } from "../slider.svelte.js";
import { createId } from "../../../internal/create-id.js";
import { noop } from "../../../internal/noop.js";
import { watch } from "runed";
const uid = $props.id();
let {
children,
child,
id = createId(uid),
ref = $bindable(null),
value = $bindable(),
type,
onValueChange = noop,
onValueCommit = noop,
disabled = false,
min: minProp,
max: maxProp,
step = 1,
dir = "ltr",
autoSort = true,
orientation = "horizontal",
thumbPositioning = "contain",
trackPadding,
...restProps
}: SliderRootProps = $props();
const min = $derived.by(() => {
if (minProp !== undefined) return minProp;
if (Array.isArray(step)) return Math.min(...step);
return 0;
});
const max = $derived.by(() => {
if (maxProp !== undefined) return maxProp;
if (Array.isArray(step)) return Math.max(...step);
return 100;
});
function handleDefaultValue() {
if (value !== undefined) return;
if (type === "single") {
return min;
}
return [];
}
// SSR
handleDefaultValue();
watch.pre(
() => value,
() => {
handleDefaultValue();
}
);
const rootState = SliderRootState.create({
id: boxWith(() => id),
ref: boxWith(
() => ref,
(v) => (ref = v)
),
value: boxWith(
() => value,
(v) => {
value = v;
// @ts-expect-error - we know
onValueChange(v);
}
) as WritableBox<number> | WritableBox<number[]>,
// @ts-expect-error - we know
onValueCommit: boxWith(() => onValueCommit),
disabled: boxWith(() => disabled),
min: boxWith(() => min),
max: boxWith(() => max),
step: boxWith(() => step),
dir: boxWith(() => dir),
autoSort: boxWith(() => autoSort),
orientation: boxWith(() => orientation),
thumbPositioning: boxWith(() => thumbPositioning),
type,
trackPadding: boxWith(() => trackPadding),
});
const mergedProps = $derived(mergeProps(restProps, rootState.props));
</script>
{#if child}
{@render child({ props: mergedProps, ...rootState.snippetProps })}
{:else}
<span {...mergedProps}>
{@render children?.(rootState.snippetProps)}
</span>
{/if}
+4
View File
@@ -0,0 +1,4 @@
import type { SliderRootProps } from "../types.js";
declare const Slider: import("svelte").Component<SliderRootProps, {}, "value" | "ref">;
type Slider = ReturnType<typeof Slider>;
export default Slider;
+7
View File
@@ -0,0 +1,7 @@
export { default as Root } from "./components/slider.svelte";
export { default as Range } from "./components/slider-range.svelte";
export { default as Thumb } from "./components/slider-thumb.svelte";
export { default as Tick } from "./components/slider-tick.svelte";
export { default as TickLabel } from "./components/slider-tick-label.svelte";
export { default as ThumbLabel } from "./components/slider-thumb-label.svelte";
export type { SliderRootProps as RootProps, SliderRangeProps as RangeProps, SliderThumbProps as ThumbProps, SliderTickProps as TickProps, SliderTickLabelProps as TickLabelProps, SliderThumbLabelProps as ThumbLabelProps, } from "./types.js";
+6
View File
@@ -0,0 +1,6 @@
export { default as Root } from "./components/slider.svelte";
export { default as Range } from "./components/slider-range.svelte";
export { default as Thumb } from "./components/slider-thumb.svelte";
export { default as Tick } from "./components/slider-tick.svelte";
export { default as TickLabel } from "./components/slider-tick-label.svelte";
export { default as ThumbLabel } from "./components/slider-thumb-label.svelte";
+18
View File
@@ -0,0 +1,18 @@
import type { StyleProperties } from "../../shared/index.js";
export declare function getRangeStyles(direction: "lr" | "rl" | "tb" | "bt", min: number, max: number): StyleProperties;
export declare function getThumbStyles(direction: "lr" | "rl" | "tb" | "bt", thumbPos: number): StyleProperties;
export declare function getTickStyles(direction: "lr" | "rl" | "tb" | "bt", tickPosition: number, offsetPercentage: number): StyleProperties;
export declare function getTickLabelStyles(direction: "lr" | "rl" | "tb" | "bt", tickPosition: number, labelPosition?: "top" | "bottom" | "left" | "right"): StyleProperties;
export declare function getThumbLabelStyles(direction: "lr" | "rl" | "tb" | "bt", thumbPosition: number, labelPosition?: "top" | "bottom" | "left" | "right"): StyleProperties;
/**
* Normalizes step to always be a sorted array of valid values within min/max range
*/
export declare function normalizeSteps(step: number | number[], min: number, max: number): number[];
/**
* Snaps a value to the nearest step in a custom steps array
*/
export declare function snapValueToCustomSteps(value: number, steps: number[]): number;
/**
* Gets the next/previous step value for keyboard navigation
*/
export declare function getAdjacentStepValue(currentValue: number, steps: number[], direction: "next" | "prev"): number;
+219
View File
@@ -0,0 +1,219 @@
export function getRangeStyles(direction, min, max) {
const styles = {
position: "absolute",
};
if (direction === "lr") {
styles.left = `${min}%`;
styles.right = `${max}%`;
}
else if (direction === "rl") {
styles.right = `${min}%`;
styles.left = `${max}%`;
}
else if (direction === "bt") {
styles.bottom = `${min}%`;
styles.top = `${max}%`;
}
else {
styles.top = `${min}%`;
styles.bottom = `${max}%`;
}
return styles;
}
export function getThumbStyles(direction, thumbPos) {
const styles = {
position: "absolute",
};
if (direction === "lr") {
styles.left = `${thumbPos}%`;
styles.translate = "-50% 0";
}
else if (direction === "rl") {
styles.right = `${thumbPos}%`;
styles.translate = "50% 0";
}
else if (direction === "bt") {
styles.bottom = `${thumbPos}%`;
styles.translate = "0 50%";
}
else {
styles.top = `${thumbPos}%`;
styles.translate = "0 -50%";
}
return styles;
}
export function getTickStyles(direction, tickPosition, offsetPercentage) {
const style = {
position: "absolute",
};
if (direction === "lr") {
style.left = `${tickPosition}%`;
style.translate = `${offsetPercentage}% 0`;
}
else if (direction === "rl") {
style.right = `${tickPosition}%`;
style.translate = `${-offsetPercentage}% 0`;
}
else if (direction === "bt") {
style.bottom = `${tickPosition}%`;
style.translate = `0 ${-offsetPercentage}%`;
}
else {
style.top = `${tickPosition}%`;
style.translate = `0 ${offsetPercentage}%`;
}
return style;
}
export function getTickLabelStyles(direction, tickPosition, labelPosition = "top") {
const style = {
position: "absolute",
};
if (direction === "lr" || direction === "rl") {
// Horizontal slider
style.left = direction === "lr" ? `${tickPosition}%` : undefined;
style.right = direction === "rl" ? `${tickPosition}%` : undefined;
style.translate = "-50% 0";
if (labelPosition === "top") {
style.bottom = "100%";
}
else if (labelPosition === "bottom") {
style.top = "100%";
}
}
else {
// Vertical slider - use same positioning as ticks
if (direction === "tb") {
style.top = `${tickPosition}%`;
}
else {
style.bottom = `${tickPosition}%`;
}
style.translate = "0 50%";
if (labelPosition === "left") {
style.right = "100%";
}
else if (labelPosition === "right") {
style.left = "100%";
}
}
return style;
}
export function getThumbLabelStyles(direction, thumbPosition, labelPosition = "top") {
const style = {
position: "absolute",
};
if (direction === "lr" || direction === "rl") {
// Horizontal slider
style.left = direction === "lr" ? `${thumbPosition}%` : undefined;
style.right = direction === "rl" ? `${thumbPosition}%` : undefined;
style.translate = "-50% 0";
if (labelPosition === "top") {
style.bottom = "100%";
}
else if (labelPosition === "bottom") {
style.top = "100%";
}
}
else {
// Vertical slider
if (direction === "tb") {
style.top = `${thumbPosition}%`;
}
else {
style.bottom = `${thumbPosition}%`;
}
style.translate = "0 -50%";
if (labelPosition === "left") {
style.right = "100%";
}
else if (labelPosition === "right") {
style.left = "100%";
}
}
return style;
}
/**
* Gets the number of decimal places in a number
*/
function getDecimalPlaces(num) {
if (Math.floor(num) === num)
return 0;
const str = num.toString();
if (str.indexOf(".") !== -1 && str.indexOf("e-") === -1) {
return str.split(".")[1].length;
}
else if (str.indexOf("e-") !== -1) {
const parts = str.split("e-");
return parseInt(parts[1], 10);
}
return 0;
}
/**
* Rounds a number to the specified number of decimal places
*/
function roundToPrecision(num, precision) {
const factor = Math.pow(10, precision);
return Math.round(num * factor) / factor;
}
/**
* Normalizes step to always be a sorted array of valid values within min/max range
*/
export function normalizeSteps(step, min, max) {
if (typeof step === "number") {
// generate regular steps - match original behavior exactly
const difference = max - min;
let count = Math.ceil(difference / step);
// Get precision from step to avoid floating point errors
const precision = getDecimalPlaces(step);
// Check if difference is divisible by step using integer arithmetic to avoid floating point errors
const factor = Math.pow(10, precision);
const intDifference = Math.round(difference * factor);
const intStep = Math.round(step * factor);
if (intDifference % intStep === 0) {
count++;
}
const steps = [];
for (let i = 0; i < count; i++) {
const value = min + i * step;
// Round to the precision of the step to avoid floating point errors
const roundedValue = roundToPrecision(value, precision);
steps.push(roundedValue);
}
return steps;
}
return [...new Set(step)].filter((value) => value >= min && value <= max).sort((a, b) => a - b);
}
/**
* Snaps a value to the nearest step in a custom steps array
*/
export function snapValueToCustomSteps(value, steps) {
if (steps.length === 0)
return value;
// Find the closest step
let closest = steps[0];
let minDistance = Math.abs(value - closest);
for (const step of steps) {
const distance = Math.abs(value - step);
if (distance < minDistance) {
minDistance = distance;
closest = step;
}
}
return closest;
}
/**
* Gets the next/previous step value for keyboard navigation
*/
export function getAdjacentStepValue(currentValue, steps, direction) {
const currentIndex = steps.indexOf(currentValue);
if (currentIndex === -1) {
// current value is not in steps, snap to nearest
return snapValueToCustomSteps(currentValue, steps);
}
if (direction === "next") {
return currentIndex < steps.length - 1 ? steps[currentIndex + 1] : currentValue;
}
else {
return currentIndex > 0 ? steps[currentIndex - 1] : currentValue;
}
}
+1
View File
@@ -0,0 +1 @@
export * as Slider from "./exports.js";
+1
View File
@@ -0,0 +1 @@
export * as Slider from "./exports.js";
File diff suppressed because it is too large Load Diff
+810
View File
@@ -0,0 +1,810 @@
/**
* This logic is adapted from the @melt-ui/svelte slider, which was mostly written by
* Abdelrahman (https://github.com/abdel-17)
*/
import { untrack } from "svelte";
import { executeCallbacks, onMountEffect, attachRef, DOMContext, } from "svelte-toolbelt";
import { on } from "svelte/events";
import { Context, watch } from "runed";
import { getRangeStyles, getThumbStyles, getTickStyles, normalizeSteps, snapValueToCustomSteps, getAdjacentStepValue, getTickLabelStyles, getThumbLabelStyles, } from "./helpers.js";
import { createBitsAttrs, boolToStr, boolToEmptyStrOrUndef } from "../../internal/attrs.js";
import { kbd } from "../../internal/kbd.js";
import { isElementOrSVGElement } from "../../internal/is.js";
import { isValidIndex } from "../../internal/arrays.js";
import { linearScale } from "../../internal/math.js";
const sliderAttrs = createBitsAttrs({
component: "slider",
parts: ["root", "thumb", "range", "tick", "tick-label", "thumb-label"],
});
export const SliderRootContext = new Context("Slider.Root");
class SliderBaseRootState {
opts;
attachment;
isActive = $state(false);
direction = $derived.by(() => {
if (this.opts.orientation.current === "horizontal") {
return this.opts.dir.current === "rtl" ? "rl" : "lr";
}
else {
return this.opts.dir.current === "rtl" ? "tb" : "bt";
}
});
// Normalized steps array for consistent handling
normalizedSteps = $derived.by(() => {
return normalizeSteps(this.opts.step.current, this.opts.min.current, this.opts.max.current);
});
domContext;
constructor(opts) {
this.opts = opts;
this.attachment = attachRef(opts.ref);
this.domContext = new DOMContext(this.opts.ref);
}
isThumbActive(_index) {
return this.isActive;
}
#touchAction = $derived.by(() => {
if (this.opts.disabled.current)
return undefined;
return this.opts.orientation.current === "horizontal" ? "pan-y" : "pan-x";
});
getAllThumbs = () => {
const node = this.opts.ref.current;
if (!node)
return [];
return Array.from(node.querySelectorAll(sliderAttrs.selector("thumb")));
};
getThumbScale = () => {
// If trackPadding is explicitly set, use it directly instead of calculating from thumb size
const trackPadding = this.opts.trackPadding?.current;
if (trackPadding !== undefined && trackPadding > 0) {
return [trackPadding, 100 - trackPadding];
}
if (this.opts.thumbPositioning.current === "exact") {
// User opted out of containment
return [0, 100];
}
const isVertical = this.opts.orientation.current === "vertical";
// this assumes all thumbs are the same width
const activeThumb = this.getAllThumbs()[0];
const thumbSize = isVertical ? activeThumb?.offsetHeight : activeThumb?.offsetWidth;
// if thumb size is undefined or 0, fallback to a 0-100 scale
if (thumbSize === undefined || Number.isNaN(thumbSize) || thumbSize === 0)
return [0, 100];
const trackSize = isVertical
? this.opts.ref.current?.offsetHeight
: this.opts.ref.current?.offsetWidth;
// if track size is undefined or 0, fallback to a 0-100 scale
if (trackSize === undefined || Number.isNaN(trackSize) || trackSize === 0)
return [0, 100];
// the padding on either side
// half the width of the thumb
const percentPadding = (thumbSize / 2 / trackSize) * 100;
const min = percentPadding;
const max = 100 - percentPadding;
return [min, max];
};
getPositionFromValue = (thumbValue) => {
const thumbScale = this.getThumbScale();
const scale = linearScale([this.opts.min.current, this.opts.max.current], thumbScale);
return scale(thumbValue);
};
props = $derived.by(() => ({
id: this.opts.id.current,
"data-orientation": this.opts.orientation.current,
"data-disabled": boolToEmptyStrOrUndef(this.opts.disabled.current),
style: {
touchAction: this.#touchAction,
},
[sliderAttrs.root]: "",
...this.attachment,
}));
}
class SliderSingleRootState extends SliderBaseRootState {
opts;
isMulti = false;
constructor(opts) {
super(opts);
this.opts = opts;
onMountEffect(() => {
return executeCallbacks(on(this.domContext.getDocument(), "pointerdown", this.handlePointerDown), on(this.domContext.getDocument(), "pointerup", this.handlePointerUp), on(this.domContext.getDocument(), "pointermove", this.handlePointerMove), on(this.domContext.getDocument(), "pointerleave", this.handlePointerUp));
});
watch([
() => this.opts.step.current,
() => this.opts.min.current,
() => this.opts.max.current,
() => this.opts.value.current,
], ([step, min, max, value]) => {
const steps = normalizeSteps(step, min, max);
const isValidValue = (v) => {
return steps.includes(v);
};
const gcv = (v) => {
return snapValueToCustomSteps(v, steps);
};
if (!isValidValue(value)) {
this.opts.value.current = gcv(value);
}
});
}
isTickValueSelected = (tickValue) => {
return this.opts.value.current === tickValue;
};
applyPosition({ clientXY, start, end }) {
const min = this.opts.min.current;
const max = this.opts.max.current;
const percent = (clientXY - start) / (end - start);
const val = percent * (max - min) + min;
if (val < min) {
this.updateValue(min);
}
else if (val > max) {
this.updateValue(max);
}
else {
const steps = this.normalizedSteps;
const newValue = snapValueToCustomSteps(val, steps);
this.updateValue(newValue);
}
}
updateValue = (newValue) => {
this.opts.value.current = snapValueToCustomSteps(newValue, this.normalizedSteps);
};
handlePointerMove = (e) => {
if (!this.isActive || this.opts.disabled.current)
return;
e.preventDefault();
e.stopPropagation();
const sliderNode = this.opts.ref.current;
const activeThumb = this.getAllThumbs()[0];
if (!sliderNode || !activeThumb)
return;
activeThumb.focus();
const { left, right, top, bottom } = sliderNode.getBoundingClientRect();
if (this.direction === "lr") {
this.applyPosition({
clientXY: e.clientX,
start: left,
end: right,
});
}
else if (this.direction === "rl") {
this.applyPosition({
clientXY: e.clientX,
start: right,
end: left,
});
}
else if (this.direction === "bt") {
this.applyPosition({
clientXY: e.clientY,
start: bottom,
end: top,
});
}
else if (this.direction === "tb") {
this.applyPosition({
clientXY: e.clientY,
start: top,
end: bottom,
});
}
};
handlePointerDown = (e) => {
if (e.button !== 0 || this.opts.disabled.current)
return;
const sliderNode = this.opts.ref.current;
const closestThumb = this.getAllThumbs()[0];
if (!closestThumb || !sliderNode)
return;
const target = e.composedPath()[0] ?? e.target;
if (!isElementOrSVGElement(target) || !sliderNode.contains(target))
return;
e.preventDefault();
closestThumb.focus();
this.isActive = true;
this.handlePointerMove(e);
};
handlePointerUp = () => {
if (this.opts.disabled.current)
return;
if (this.isActive) {
this.opts.onValueCommit.current(untrack(() => this.opts.value.current));
}
this.isActive = false;
};
thumbsPropsArr = $derived.by(() => {
const currValue = this.opts.value.current;
return Array.from({ length: 1 }, () => {
const thumbValue = currValue;
const thumbPosition = this.getPositionFromValue(thumbValue);
const style = getThumbStyles(this.direction, thumbPosition);
return {
role: "slider",
"aria-valuemin": this.opts.min.current,
"aria-valuemax": this.opts.max.current,
"aria-valuenow": thumbValue,
"aria-disabled": boolToStr(this.opts.disabled.current),
"aria-orientation": this.opts.orientation.current,
"data-value": thumbValue,
"data-orientation": this.opts.orientation.current,
style,
[sliderAttrs.thumb]: "",
};
});
});
thumbsRenderArr = $derived.by(() => {
return this.thumbsPropsArr.map((_, i) => i);
});
ticksPropsArr = $derived.by(() => {
const steps = this.normalizedSteps;
const currValue = this.opts.value.current;
return steps.map((tickValue, i) => {
// Calculate position relative to the range
const tickPosition = this.getPositionFromValue(tickValue);
const isFirst = i === 0;
const isLast = i === steps.length - 1;
const offsetPercentage = isFirst ? 0 : isLast ? -100 : -50;
const style = getTickStyles(this.direction, tickPosition, offsetPercentage);
const bounded = tickValue <= currValue;
return {
"data-disabled": boolToEmptyStrOrUndef(this.opts.disabled.current),
"data-orientation": this.opts.orientation.current,
"data-bounded": bounded ? "" : undefined,
"data-value": tickValue,
"data-selected": this.isTickValueSelected(tickValue) ? "" : undefined,
style,
[sliderAttrs.tick]: "",
};
});
});
ticksRenderArr = $derived.by(() => {
return this.ticksPropsArr.map((_, i) => i);
});
tickItemsArr = $derived.by(() => {
return this.ticksPropsArr.map((tick, i) => ({
value: tick["data-value"],
index: i,
}));
});
thumbItemsArr = $derived.by(() => {
const currValue = this.opts.value.current;
return [
{
value: currValue,
index: 0,
},
];
});
snippetProps = $derived.by(() => ({
ticks: this.ticksRenderArr,
thumbs: this.thumbsRenderArr,
tickItems: this.tickItemsArr,
thumbItems: this.thumbItemsArr,
}));
}
class SliderMultiRootState extends SliderBaseRootState {
opts;
isMulti = true;
activeThumb = $state(null);
currentThumbIdx = $state(0);
constructor(opts) {
super(opts);
this.opts = opts;
onMountEffect(() => {
return executeCallbacks(on(this.domContext.getDocument(), "pointerdown", this.handlePointerDown), on(this.domContext.getDocument(), "pointerup", this.handlePointerUp), on(this.domContext.getDocument(), "pointermove", this.handlePointerMove), on(this.domContext.getDocument(), "pointerleave", this.handlePointerUp));
});
watch([
() => this.opts.step.current,
() => this.opts.min.current,
() => this.opts.max.current,
() => this.opts.value.current,
], ([step, min, max, value]) => {
const steps = normalizeSteps(step, min, max);
const isValidValue = (v) => {
return steps.includes(v);
};
const gcv = (v) => {
return snapValueToCustomSteps(v, steps);
};
if (value.some((v) => !isValidValue(v))) {
this.opts.value.current = value.map(gcv);
}
});
}
isTickValueSelected = (tickValue) => {
return this.opts.value.current.includes(tickValue);
};
isThumbActive(index) {
return this.isActive && this.activeThumb?.idx === index;
}
applyPosition({ clientXY, activeThumbIdx, start, end, }) {
const min = this.opts.min.current;
const max = this.opts.max.current;
const percent = (clientXY - start) / (end - start);
const val = percent * (max - min) + min;
if (val < min) {
this.updateValue(min, activeThumbIdx);
}
else if (val > max) {
this.updateValue(max, activeThumbIdx);
}
else {
const steps = this.normalizedSteps;
const newValue = snapValueToCustomSteps(val, steps);
this.updateValue(newValue, activeThumbIdx);
}
}
#getClosestThumb = (e) => {
const thumbs = this.getAllThumbs();
if (!thumbs.length)
return;
for (const thumb of thumbs) {
thumb.blur();
}
const distances = thumbs.map((thumb) => {
if (this.opts.orientation.current === "horizontal") {
const { left, right } = thumb.getBoundingClientRect();
return Math.abs(e.clientX - (left + right) / 2);
}
else {
const { top, bottom } = thumb.getBoundingClientRect();
return Math.abs(e.clientY - (top + bottom) / 2);
}
});
const node = thumbs[distances.indexOf(Math.min(...distances))];
const idx = thumbs.indexOf(node);
return {
node,
idx,
};
};
handlePointerMove = (e) => {
if (!this.isActive || this.opts.disabled.current)
return;
e.preventDefault();
e.stopPropagation();
const sliderNode = this.opts.ref.current;
const activeThumb = this.activeThumb;
if (!sliderNode || !activeThumb)
return;
activeThumb.node.focus();
const { left, right, top, bottom } = sliderNode.getBoundingClientRect();
const direction = this.direction;
if (direction === "lr") {
this.applyPosition({
clientXY: e.clientX,
activeThumbIdx: activeThumb.idx,
start: left,
end: right,
});
}
else if (direction === "rl") {
this.applyPosition({
clientXY: e.clientX,
activeThumbIdx: activeThumb.idx,
start: right,
end: left,
});
}
else if (direction === "bt") {
this.applyPosition({
clientXY: e.clientY,
activeThumbIdx: activeThumb.idx,
start: bottom,
end: top,
});
}
else if (direction === "tb") {
this.applyPosition({
clientXY: e.clientY,
activeThumbIdx: activeThumb.idx,
start: top,
end: bottom,
});
}
};
handlePointerDown = (e) => {
if (e.button !== 0 || this.opts.disabled.current)
return;
const sliderNode = this.opts.ref.current;
const closestThumb = this.#getClosestThumb(e);
if (!closestThumb || !sliderNode)
return;
const target = e.composedPath()[0] ?? e.target;
if (!isElementOrSVGElement(target) || !sliderNode.contains(target))
return;
e.preventDefault();
this.activeThumb = closestThumb;
closestThumb.node.focus();
this.isActive = true;
this.handlePointerMove(e);
};
handlePointerUp = () => {
if (this.opts.disabled.current)
return;
if (this.isActive) {
this.opts.onValueCommit.current(untrack(() => this.opts.value.current));
}
this.isActive = false;
};
getAllThumbs = () => {
const node = this.opts.ref.current;
if (!node)
return [];
const thumbs = Array.from(node.querySelectorAll(sliderAttrs.selector("thumb")));
return thumbs;
};
updateValue = (thumbValue, idx) => {
const currValue = this.opts.value.current;
if (!currValue.length) {
this.opts.value.current.push(thumbValue);
return;
}
const valueAtIndex = currValue[idx];
if (valueAtIndex === thumbValue)
return;
const newValue = [...currValue];
if (!isValidIndex(idx, newValue))
return;
const direction = newValue[idx] > thumbValue ? -1 : +1;
const swap = () => {
const diffIndex = idx + direction;
newValue[idx] = newValue[diffIndex];
newValue[diffIndex] = thumbValue;
const thumbs = this.getAllThumbs();
if (!thumbs.length)
return;
thumbs[diffIndex]?.focus();
this.activeThumb = { node: thumbs[diffIndex], idx: diffIndex };
};
if (this.opts.autoSort.current &&
((direction === -1 && thumbValue < newValue[idx - 1]) ||
(direction === 1 && thumbValue > newValue[idx + 1]))) {
swap();
this.opts.value.current = newValue;
return;
}
const steps = this.normalizedSteps;
newValue[idx] = snapValueToCustomSteps(thumbValue, steps);
this.opts.value.current = newValue;
};
thumbsPropsArr = $derived.by(() => {
const currValue = this.opts.value.current;
return Array.from({ length: currValue.length || 1 }, (_, i) => {
const currThumb = untrack(() => this.currentThumbIdx);
if (currThumb < currValue.length) {
untrack(() => {
this.currentThumbIdx = currThumb + 1;
});
}
const thumbValue = currValue[i];
const thumbPosition = this.getPositionFromValue(thumbValue ?? 0);
const style = getThumbStyles(this.direction, thumbPosition);
return {
role: "slider",
"aria-valuemin": this.opts.min.current,
"aria-valuemax": this.opts.max.current,
"aria-valuenow": thumbValue,
"aria-disabled": boolToStr(this.opts.disabled.current),
"aria-orientation": this.opts.orientation.current,
"data-value": thumbValue,
"data-orientation": this.opts.orientation.current,
style,
[sliderAttrs.thumb]: "",
};
});
});
thumbsRenderArr = $derived.by(() => {
return this.thumbsPropsArr.map((_, i) => i);
});
ticksPropsArr = $derived.by(() => {
const steps = this.normalizedSteps;
const currValue = this.opts.value.current;
return steps.map((tickValue, i) => {
// Calculate position relative to the range
const tickPosition = this.getPositionFromValue(tickValue);
const isFirst = i === 0;
const isLast = i === steps.length - 1;
const offsetPercentage = isFirst ? 0 : isLast ? -100 : -50;
const style = getTickStyles(this.direction, tickPosition, offsetPercentage);
const bounded = currValue.length === 1
? tickValue <= currValue[0]
: currValue[0] <= tickValue && tickValue <= currValue[currValue.length - 1];
return {
"data-disabled": boolToEmptyStrOrUndef(this.opts.disabled.current),
"data-orientation": this.opts.orientation.current,
"data-bounded": bounded ? "" : undefined,
"data-value": tickValue,
style,
[sliderAttrs.tick]: "",
};
});
});
ticksRenderArr = $derived.by(() => {
return this.ticksPropsArr.map((_, i) => i);
});
tickItemsArr = $derived.by(() => {
return this.ticksPropsArr.map((tick, i) => ({
value: tick["data-value"],
index: i,
}));
});
thumbItemsArr = $derived.by(() => {
const currValue = this.opts.value.current;
return currValue.map((value, index) => ({
value,
index,
}));
});
snippetProps = $derived.by(() => ({
ticks: this.ticksRenderArr,
thumbs: this.thumbsRenderArr,
tickItems: this.tickItemsArr,
thumbItems: this.thumbItemsArr,
}));
}
export class SliderRootState {
static create(opts) {
const { type, ...rest } = opts;
const rootState = type === "single"
? new SliderSingleRootState(rest)
: new SliderMultiRootState(rest);
return SliderRootContext.set(rootState);
}
}
const VALID_SLIDER_KEYS = [
kbd.ARROW_LEFT,
kbd.ARROW_RIGHT,
kbd.ARROW_UP,
kbd.ARROW_DOWN,
kbd.HOME,
kbd.END,
];
export class SliderRangeState {
static create(opts) {
return new SliderRangeState(opts, SliderRootContext.get());
}
opts;
root;
attachment;
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.attachment = attachRef(opts.ref);
}
rangeStyles = $derived.by(() => {
if (Array.isArray(this.root.opts.value.current)) {
// Multi-slider: range between min and max thumbs
const min = this.root.opts.value.current.length > 1
? this.root.getPositionFromValue(Math.min(...this.root.opts.value.current) ?? 0)
: 0;
const max = 100 -
this.root.getPositionFromValue(Math.max(...this.root.opts.value.current) ?? 0);
return {
position: "absolute",
...getRangeStyles(this.root.direction, min, max),
};
}
else {
// Single slider: range from start to current value
const trackPadding = this.root.opts.trackPadding?.current;
const currentValue = this.root.opts.value.current;
const maxValue = this.root.opts.max.current;
// Always start from 0% (beginning of track container)
const min = 0;
// If trackPadding is set and we're at max value, extend to fill the container
// Otherwise use the thumb position
const max = trackPadding !== undefined && trackPadding > 0 && currentValue === maxValue
? 0 // 100% - 0% = full width
: 100 - this.root.getPositionFromValue(currentValue);
return {
position: "absolute",
...getRangeStyles(this.root.direction, min, max),
};
}
});
props = $derived.by(() => ({
id: this.opts.id.current,
"data-orientation": this.root.opts.orientation.current,
"data-disabled": boolToEmptyStrOrUndef(this.root.opts.disabled.current),
style: this.rangeStyles,
[sliderAttrs.range]: "",
...this.attachment,
}));
}
export class SliderThumbState {
static create(opts) {
return new SliderThumbState(opts, SliderRootContext.get());
}
opts;
root;
attachment;
#isDisabled = $derived.by(() => this.root.opts.disabled.current || this.opts.disabled.current);
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.attachment = attachRef(opts.ref);
this.onkeydown = this.onkeydown.bind(this);
}
#updateValue(newValue) {
if (this.root.isMulti) {
this.root.updateValue(newValue, this.opts.index.current);
}
else {
this.root.updateValue(newValue);
}
}
onkeydown(e) {
if (this.#isDisabled)
return;
const currNode = this.opts.ref.current;
if (!currNode)
return;
const thumbs = this.root.getAllThumbs();
if (!thumbs.length)
return;
const idx = thumbs.indexOf(currNode);
if (this.root.isMulti) {
this.root.currentThumbIdx = idx;
}
if (!VALID_SLIDER_KEYS.includes(e.key))
return;
e.preventDefault();
const min = this.root.opts.min.current;
const max = this.root.opts.max.current;
const value = this.root.opts.value.current;
const thumbValue = Array.isArray(value) ? value[idx] : value;
const orientation = this.root.opts.orientation.current;
const direction = this.root.direction;
const steps = this.root.normalizedSteps;
switch (e.key) {
case kbd.HOME:
this.#updateValue(min);
break;
case kbd.END:
this.#updateValue(max);
break;
case kbd.ARROW_LEFT:
if (orientation !== "horizontal")
break;
if (e.metaKey) {
const newValue = direction === "rl" ? max : min;
this.#updateValue(newValue);
}
else {
const stepDirection = direction === "rl" ? "next" : "prev";
const newValue = getAdjacentStepValue(thumbValue, steps, stepDirection);
this.#updateValue(newValue);
}
break;
case kbd.ARROW_RIGHT:
if (orientation !== "horizontal")
break;
if (e.metaKey) {
const newValue = direction === "rl" ? min : max;
this.#updateValue(newValue);
}
else {
const stepDirection = direction === "rl" ? "prev" : "next";
const newValue = getAdjacentStepValue(thumbValue, steps, stepDirection);
this.#updateValue(newValue);
}
break;
case kbd.ARROW_UP:
if (e.metaKey) {
const newValue = direction === "tb" ? min : max;
this.#updateValue(newValue);
}
else {
const stepDirection = direction === "tb" ? "prev" : "next";
const newValue = getAdjacentStepValue(thumbValue, steps, stepDirection);
this.#updateValue(newValue);
}
break;
case kbd.ARROW_DOWN:
if (e.metaKey) {
const newValue = direction === "tb" ? max : min;
this.#updateValue(newValue);
}
else {
const stepDirection = direction === "tb" ? "next" : "prev";
const newValue = getAdjacentStepValue(thumbValue, steps, stepDirection);
this.#updateValue(newValue);
}
break;
}
// @ts-expect-error - this is fine
this.root.opts.onValueCommit.current(this.root.opts.value.current);
}
props = $derived.by(() => ({
...this.root.thumbsPropsArr[this.opts.index.current],
id: this.opts.id.current,
onkeydown: this.onkeydown,
"data-active": this.root.isThumbActive(this.opts.index.current) ? "" : undefined,
"data-disabled": boolToEmptyStrOrUndef(this.opts.disabled.current || this.root.opts.disabled.current),
tabindex: this.opts.disabled.current || this.root.opts.disabled.current ? -1 : 0,
...this.attachment,
}));
}
export class SliderTickState {
static create(opts) {
return new SliderTickState(opts, SliderRootContext.get());
}
opts;
root;
attachment;
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.attachment = attachRef(opts.ref);
}
props = $derived.by(() => ({
...this.root.ticksPropsArr[this.opts.index.current],
id: this.opts.id.current,
...this.attachment,
}));
}
export class SliderTickLabelState {
static create(opts) {
return new SliderTickLabelState(opts, SliderRootContext.get());
}
opts;
root;
attachment;
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.attachment = attachRef(opts.ref);
}
props = $derived.by(() => {
const tickProps = this.root.ticksPropsArr[this.opts.index.current];
const steps = this.root.normalizedSteps;
const tickValue = steps[this.opts.index.current];
const tickPosition = this.root.getPositionFromValue(tickValue);
const labelPosition = this.opts.position?.current ?? "top";
const style = getTickLabelStyles(this.root.direction, tickPosition, labelPosition);
return {
id: this.opts.id.current,
"data-orientation": this.root.opts.orientation.current,
"data-disabled": boolToEmptyStrOrUndef(this.root.opts.disabled.current),
"data-bounded": tickProps["data-bounded"],
"data-value": tickValue,
"data-selected": this.root.isTickValueSelected(tickValue) ? "" : undefined,
"data-position": labelPosition,
style,
[sliderAttrs["tick-label"]]: "",
...this.attachment,
};
});
}
export class SliderThumbLabelState {
static create(opts) {
return new SliderThumbLabelState(opts, SliderRootContext.get());
}
opts;
root;
attachment;
constructor(opts, root) {
this.opts = opts;
this.root = root;
this.attachment = attachRef(opts.ref);
}
props = $derived.by(() => {
const value = this.root.opts.value.current;
const thumbValue = Array.isArray(value) ? value[this.opts.index.current] : value;
const thumbPosition = this.root.getPositionFromValue(thumbValue);
const labelPosition = this.opts.position?.current ?? "top";
const style = getThumbLabelStyles(this.root.direction, thumbPosition, labelPosition);
return {
id: this.opts.id.current,
"data-orientation": this.root.opts.orientation.current,
"data-disabled": boolToEmptyStrOrUndef(this.root.opts.disabled.current),
"data-value": thumbValue,
"data-active": this.root.isThumbActive(this.opts.index.current) ? "" : undefined,
"data-position": labelPosition,
style,
[sliderAttrs["thumb-label"]]: "",
...this.attachment,
};
});
}
+233
View File
@@ -0,0 +1,233 @@
import type { OnChangeFn, WithChild, Without } from "../../internal/types.js";
import type { BitsPrimitiveSpanAttributes } from "../../shared/attributes.js";
import type { Direction, Orientation, SliderThumbPositioning } from "../../shared/index.js";
export type TickItem = {
/**
* The value this tick represents
*/
value: number;
/**
* The index of this tick in the array of ticks provided by the `children`
* or `child` snippet prop of the `Slider.Root` component.
*/
index: number;
};
export type ThumbItem = {
/**
* The value this thumb represents
*/
value: number;
/**
* The index of this thumb in the array of thumbs provided by the `children`
* or `child` snippet prop of the `Slider.Root` component.
*/
index: number;
};
export type SliderLabelPosition = "top" | "bottom" | "left" | "right";
export type SliderRootSnippetProps = {
/**
* Use `tickItems` instead. Will be removed in Bits UI v3.
*
* The indices of the ticks.
*
* @deprecated Use `tickItems` instead.
*/
ticks: number[];
/**
* Use `thumbItems` instead. Will be removed in Bits UI v3.
*
* The indices of the thumbs.
*
* @deprecated Use `thumbItems` instead
*/
thumbs: number[];
/**
* An array of objects containing the value and index of each tick, useful for
* rendering ticks along with labels for each tick.
*/
tickItems: TickItem[];
/**
* An array of objects containing the value and index of each thumb, useful for
* rendering thumbs along with labels for each thumb.
*/
thumbItems: ThumbItem[];
};
export type BaseSliderRootPropsWithoutHTML = {
/**
* Whether to automatically sort the values in the array when moving thumbs past
* one another.
*
* @default true
*/
autoSort?: boolean;
/**
* The minimum value of the slider.
*
* @default 0 (for number step) or the min value of the step array (for array step)
*/
min?: number;
/**
* The maximum value of the slider.
*
* @default 100 (for number step) or the max value of the step array (for array step)
*/
max?: number;
/**
* The amount to increment the value by when the user presses the arrow keys,
* or an array of specific values that the slider can snap to.
*
* When an array is provided, the slider will only allow values that exist in the array,
* creating discrete tick points. The array values should be within the min/max range
* and will be automatically sorted.
*
* @default 1
*/
step?: number | number[];
/**
* The direction of the slider.
*
* For vertical sliders, setting `dir` to `'rtl'` will caus the slider to start
* from the top and move downwards. For horizontal sliders, setting `dir` to `'rtl'`
* will cause the slider to start from the left and move rightwards.
*
* @default 'ltr'
*/
dir?: Direction;
/**
* The orientation of the slider.
*
* @default "horizontal"
*/
orientation?: Orientation;
/**
* Whether the slider is disabled or not.
*
* @default false
*/
disabled?: boolean;
/**
* The positioning of the slider thumb.
*
* @default "contain"
*/
thumbPositioning?: SliderThumbPositioning;
/**
* Padding percentage for the track. Creates space before the first
* and after the last tick positions.
*
* This can also be used as an SSR-friendly alternative to `thumbPositioning="contain"`,
* which requires client-side measurement.
*/
trackPadding?: number;
};
export type SliderSingleRootPropsWithoutHTML = BaseSliderRootPropsWithoutHTML & {
/**
* The type of slider. If set to `'multiple'`, the slider will
* allow multiple ticks and the `value` will be an array of numbers.
*
* @required
*/
type: "single";
/**
* The value of the slider.
* @bindable
*
* @default min
*/
value?: number;
/**
* A callback function called when the value changes.
*/
onValueChange?: OnChangeFn<number>;
/**
* A callback function called when the user stops dragging the
* thumb and the value is committed.
*/
onValueCommit?: OnChangeFn<number>;
};
export type SliderMultiRootPropsWithoutHTML = BaseSliderRootPropsWithoutHTML & {
/**
* The type of slider. If set to `'multiple'`, the slider will
* allow multiple ticks and the `value` will be an array of numbers.
*
* @required
*/
type: "multiple";
/**
* The value of the slider.
* @bindable
*/
value?: number[];
/**
* A callback function called when the value changes.
*/
onValueChange?: OnChangeFn<number[]>;
/**
* A callback function called when the user stops dragging the
* thumb and the value is committed.
*/
onValueCommit?: OnChangeFn<number[]>;
};
export type SliderRootPropsWithoutHTML = WithChild<SliderSingleRootPropsWithoutHTML, SliderRootSnippetProps> | WithChild<SliderMultiRootPropsWithoutHTML, SliderRootSnippetProps>;
export type SliderSingleRootProps = SliderSingleRootPropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, WithChild<SliderSingleRootPropsWithoutHTML, SliderRootSnippetProps>>;
export type SliderMultipleRootProps = SliderMultiRootPropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, WithChild<SliderMultiRootPropsWithoutHTML, SliderRootSnippetProps>>;
export type SliderRootProps = SliderRootPropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, SliderRootPropsWithoutHTML>;
export type SliderRangePropsWithoutHTML = WithChild;
export type SliderRangeProps = SliderRangePropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, SliderRangePropsWithoutHTML>;
export type SliderThumbSnippetProps = {
active: boolean;
};
export type SliderThumbPropsWithoutHTML = WithChild<{
/**
* Whether the thumb is disabled or not.
*
* @default false
*/
disabled?: boolean;
/**
* The index of the thumb in the array of thumbs provided by the `children` snippet prop of the
* `Slider.Root` component.
*/
index: number;
}, SliderThumbSnippetProps>;
export type SliderThumbProps = SliderThumbPropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, SliderThumbPropsWithoutHTML>;
export type SliderTickPropsWithoutHTML = WithChild<{
/**
* The index of the tick in the array of ticks provided by the `children` snippet prop of the
* `Slider.Root` component.
*/
index: number;
}>;
export type SliderTickProps = SliderTickPropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, SliderTickPropsWithoutHTML>;
export type SliderTickLabelPropsWithoutHTML = WithChild<{
/**
* The index of the tick the label represents in the array of ticks
* provided by the `children` snippet prop of the `Slider.Root` component.
*/
index: number;
/**
* The position of the label relative to the tick.
* For horizontal sliders: "top" | "bottom"
* For vertical sliders: "left" | "right"
*
* @default for horizontal sliders = "top" and for vertical sliders = "left"
*/
position?: SliderLabelPosition;
}>;
export type SliderTickLabelProps = SliderTickLabelPropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, SliderTickLabelPropsWithoutHTML>;
export type SliderThumbLabelPropsWithoutHTML = WithChild<{
/**
* The index of the thumb the label represents in the array of thumbs
* provided by the `children` snippet prop of the `Slider.Root` component.
*/
index: number;
/**
* The position of the label relative to the thumb.
* For horizontal sliders: "top" | "bottom"
* For vertical sliders: "left" | "right"
*
* @default for horizontal sliders = "top" and for vertical sliders = "left"
*/
position?: SliderLabelPosition;
}>;
export type SliderThumbLabelProps = SliderThumbLabelPropsWithoutHTML & Without<BitsPrimitiveSpanAttributes, SliderThumbLabelPropsWithoutHTML>;
+1
View File
@@ -0,0 +1 @@
export {};