/* Copyright 2026 Marimo. All rights reserved. */ import { dequal as isEqual } from "dequal"; import { type JSX, useEffect, useId, useState } from "react"; import { useLocale } from "react-aria"; import { z } from "zod"; import { cn } from "@/utils/cn"; import { prettyScientificNumber } from "@/utils/numbers"; import { RangeSlider } from "../../components/ui/range-slider"; import type { IPlugin, IPluginProps, Setter } from "../types"; import { Labeled } from "./common/labeled"; type T = number[]; interface Data { start: number; stop: number; step?: number; label: string | null; steps: T | null; debounce: boolean; orientation: "horizontal" | "vertical"; showValue: boolean; fullWidth: boolean; disabled?: boolean; } export class RangeSliderPlugin implements IPlugin { tagName = "marimo-range-slider"; validator = z.object({ initialValue: z.array(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), disabled: z.boolean().optional(), }); render(props: IPluginProps): JSX.Element { const valueMap = (sliderValue: number): number => { if (props.data.steps && props.data.steps.length > 0) { return props.data.steps[sliderValue]; } return sliderValue; }; return ( ); } } interface RangeSliderProps extends Data { value: T; setValue: Setter; valueMap: (sliderValue: number) => number; } const RangeSliderComponent = ({ label, setValue, value, start, stop, step, steps, debounce, orientation, showValue, fullWidth, disabled, valueMap, }: RangeSliderProps): 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: number[]) => { if (debounce) { setValue(nextValue); } }} // Sometimes onValueCommit doesn't trigger // see https://github.com/radix-ui/primitives/issues/1760 // So we also set the value on pointer/mouse up onPointerUp={() => { if (debounce && !isEqual(internalValue, value)) { setValue(internalValue); } }} onMouseUp={() => { if (debounce && !isEqual(internalValue, value)) { setValue(internalValue); } }} valueMap={valueMap} /> {showValue && (
{`${prettyScientificNumber(valueMap(internalValue[0]), { locale, })}, ${prettyScientificNumber(valueMap(internalValue[1]), { locale })}`}
)}
); return sliderElement; };