/* Copyright 2026 Marimo. All rights reserved. */ import { type JSX, useEffect, useId, useState } from "react"; import { useLocale } from "react-aria"; import { z } from "zod"; import { NumberField } from "@/components/ui/number-field"; import { cn } from "@/utils/cn"; import { prettyScientificNumber } from "@/utils/numbers"; import { Slider } from "../../components/ui/slider"; import type { IPlugin, IPluginProps, Setter } from "../types"; import { Labeled } from "./common/labeled"; type T = number; interface Data { start: T; stop: T; step?: T; label: string | null; steps: T[] | null; debounce: boolean; orientation: "horizontal" | "vertical"; showValue: boolean; fullWidth: boolean; includeInput: boolean; disabled?: boolean; } export class SliderPlugin implements IPlugin { tagName = "marimo-slider"; validator = z.object({ initialValue: z.number(), label: z.string().nullable(), start: z.number(), stop: z.number(), step: z.number().optional(), steps: z.array(z.number()).nullable(), debounce: z.boolean().default(false), orientation: z.enum(["horizontal", "vertical"]).default("horizontal"), showValue: z.boolean().default(false), fullWidth: z.boolean().default(false), includeInput: z.boolean().default(false), disabled: z.boolean().optional(), }); render(props: IPluginProps): JSX.Element { // Create the valueMap function const valueMap = (sliderValue: number): number => { if (props.data.steps && props.data.steps.length > 0) { return props.data.steps[sliderValue]; } return sliderValue; }; return ( ); } } interface SliderProps extends Data { value: T; setValue: Setter; valueMap: (sliderValue: number) => number; } const SliderComponent = ({ label, setValue, value, start, stop, step, steps, debounce, orientation, showValue, fullWidth, valueMap, includeInput, disabled, }: SliderProps): JSX.Element => { const id = useId(); const { locale } = useLocale(); // Hold internal value const [internalValue, setInternalValue] = useState(value); // Update internal value on prop change useEffect(() => { setInternalValue(value); }, [value]); const sliderElement = (
{ setInternalValue(nextValue); if (!debounce) { setValue(nextValue); } }} // Triggered on mouse up onValueCommit={([nextValue]) => { if (debounce) { setValue(nextValue); } }} valueMap={valueMap} // Pass valueMap to Slider disabled={disabled} /> {showValue && (
{prettyScientificNumber(valueMap(internalValue), { locale })}
)} {includeInput && ( { // If nextValue is null/undefined/NaN (input cleared), set to start if (nextValue == null || Number.isNaN(nextValue)) { nextValue = Number(start); } setInternalValue(nextValue); setValue(nextValue); }} minValue={start} maxValue={stop} step={step} className="w-24" aria-label={`${label || "Slider"} value input`} isDisabled={disabled} /> )}
); return sliderElement; };