{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "parameter-slider",
  "title": "Parameter Slider",
  "description": "A labelled parameter slider with a numeric input, units, marks, reset, linear or log scaling, and unit scrubbing.",
  "dependencies": [
    "solid-js@^1.9.15",
    "@kobalte/core@https://github.com/jhomra21/kobalte/releases/download/kobalte-solid1-audiocn-e0e3bf095f05c7e61230a251b79181c6d834d408/kobalte-core-0.13.14-audiocn.0.e0e3bf09.tgz",
    "class-variance-authority"
  ],
  "registryDependencies": [
    "@audiocn-solid/core",
    "@audiocn-solid/use-audio-config"
  ],
  "files": [
    {
      "path": "components/ui/parameter-slider.tsx",
      "content": "import * as SliderPrimitive from \"@kobalte/core/slider\";\nimport {\n  createContext,\n  createMemo,\n  createSignal,\n  createUniqueId,\n  For,\n  Show,\n  useContext,\n} from \"solid-js\";\n\nimport { useAudioConfig } from \"@/hooks/use-audio-config\";\nimport { clamp } from \"@/lib/audio/decibels\";\nimport { linearTaper, logTaper } from \"@/lib/audio/taper\";\nimport type { Taper } from \"@/lib/audio/types\";\nimport { roundValue, widestFormattedValue } from \"@/lib/number\";\nimport { provideContext } from \"@/lib/solid/context\";\nimport { createCompatEffect } from \"@/lib/solid/effect\";\nimport type {\n  ButtonDOMProps,\n  DivDOMProps,\n  InputDOMProps,\n  ParagraphDOMProps,\n  SpanDOMProps,\n} from \"@/lib/solid/jsx-types\";\nimport { useKobalteSliderCompat } from \"@/lib/solid/kobalte-slider\";\nimport { omitProps } from \"@/lib/solid/props\";\nimport { mergeStyleVars } from \"@/lib/solid/style\";\nimport type { StyleValue } from \"@/lib/solid/style\";\nimport { cn } from \"@/lib/utils\";\n\nconst POSITION_STEP = 0.0005;\n\nconst WIDTH_SAMPLES = 24;\n\nconst SCRUB_PIXELS_PER_STEP = 4;\n\nexport type ParameterChangeReason = \"drag\" | \"keyboard\" | \"input\" | \"reset\";\n\nexport interface ParameterChangeDetails {\n  reason: ParameterChangeReason;\n  event?: Event;\n}\n\nexport interface ParameterMark {\n  value: number;\n  label?: string;\n}\n\ninterface ParameterSliderContextValue {\n  change: (value: number, details: ParameterChangeDetails) => void;\n  commit: (value: number) => void;\n  commitLatest: () => void;\n  decimals: () => number;\n  descriptionId: string;\n  disabled: () => boolean;\n  format: () => (value: number) => string;\n  handleKeyDown: (event: KeyboardEvent) => void;\n  labelId: string;\n  largeStep: () => number;\n  marks: () => readonly ParameterMark[] | undefined;\n  max: () => number;\n  min: () => number;\n  originPosition: () => number;\n  position: () => number;\n  quantize: (value: number) => number;\n  resetValue: () => number;\n  step: () => number;\n  taper: () => Taper;\n  unit: () => string | undefined;\n  value: () => number;\n}\n\nconst ParameterSliderContext =\n  createContext<ParameterSliderContextValue | null>(null);\n\nconst useParameterSlider = (part: string): ParameterSliderContextValue => {\n  const context = useContext(ParameterSliderContext);\n\n  if (!context) {\n    throw new Error(`${part} must be used inside ParameterSlider.`);\n  }\n\n  return context;\n};\n\nconst decimalsOf = (step: number): number => {\n  const text = String(step);\n  const dot = text.indexOf(\".\");\n\n  return dot === -1 ? 0 : text.length - dot - 1;\n};\n\nconst SliderCompat = () => {\n  const parameter = useParameterSlider(\"SliderCompat\");\n  const context = SliderPrimitive.useSliderContext();\n  useKobalteSliderCompat(context, {\n    onChangeEnd: parameter.commitLatest,\n    suppressStep: true,\n  });\n\n  return null;\n};\n\ntype SliderRootProps = Parameters<typeof SliderPrimitive.Root>[0];\n\ntype ParameterSliderDOMProps = Omit<\n  DivDOMProps,\n  \"children\" | \"class\" | \"className\" | \"onChange\" | \"ref\"\n>;\n\nexport interface ParameterSliderProps extends ParameterSliderDOMProps {\n  class?: string;\n  className?: string;\n  children?: DivDOMProps[\"children\"];\n  decimals?: number;\n  defaultValue?: number;\n  disabled?: boolean;\n  format?: (value: number) => string;\n  largeStep?: number;\n  marks?: ParameterMark[];\n  max?: number;\n  min?: number;\n  onValueChange?: (value: number, details: ParameterChangeDetails) => void;\n  onValueCommitted?: (value: number) => void;\n  origin?: number;\n  resetValue?: number;\n  scale?: \"linear\" | \"log\";\n  step?: number;\n  unit?: string;\n  value?: number;\n}\n\nconst PARAMETER_SLIDER_OWN = [\n  \"children\",\n  \"class\",\n  \"className\",\n  \"decimals\",\n  \"defaultValue\",\n  \"disabled\",\n  \"format\",\n  \"largeStep\",\n  \"marks\",\n  \"max\",\n  \"min\",\n  \"onValueChange\",\n  \"onValueCommitted\",\n  \"origin\",\n  \"resetValue\",\n  \"scale\",\n  \"step\",\n  \"unit\",\n  \"value\",\n] as const;\n\nexport const ParameterSlider = (props: ParameterSliderProps) => {\n  const rest = omitProps(props, PARAMETER_SLIDER_OWN);\n  const config = useAudioConfig();\n  const id = createUniqueId();\n  const labelId = `parameter-slider-${id}-label`;\n  const descriptionId = `parameter-slider-${id}-description`;\n\n  const min = () => props.min ?? 0;\n  const max = () => props.max ?? 100;\n  const step = () => props.step ?? 1;\n  const largeStep = () => props.largeStep ?? 10;\n  const decimals = () => props.decimals ?? decimalsOf(step());\n\n  const resetValue = () => props.resetValue ?? props.defaultValue ?? min();\n\n  const disabled = () => props.disabled ?? config.disabled ?? false;\n  const unit = () => props.unit;\n  const marks = () => props.marks;\n  const scale = () => props.scale ?? \"linear\";\n\n  const [uncontrolled, setUncontrolled] = createSignal(\n    clamp(props.defaultValue ?? resetValue(), min(), max())\n  );\n\n  const value = () => props.value ?? uncontrolled();\n  let latestValue = value();\n\n  createCompatEffect(value, (next) => {\n    latestValue = next;\n  });\n\n  const taper = createMemo(() =>\n    scale() === \"log\" ? logTaper(min(), max()) : linearTaper(min(), max())\n  );\n\n  const format = () => {\n    if (props.format) {\n      return props.format;\n    }\n\n    return (next: number) => {\n      const text = next.toFixed(decimals());\n\n      return unit() ? `${text} ${unit()}` : text;\n    };\n  };\n\n  const quantize = (next: number): number => {\n    const stepped = min() + Math.round((next - min()) / step()) * step();\n\n    return roundValue(clamp(stepped, min(), max()));\n  };\n\n  const change = (next: number, details: ParameterChangeDetails) => {\n    if (next === latestValue) {\n      return;\n    }\n\n    latestValue = next;\n\n    if (props.value === undefined) {\n      setUncontrolled(next);\n    }\n\n    props.onValueChange?.(next, details);\n\n    if (props.value !== undefined) {\n      queueMicrotask(() => {\n        latestValue = value();\n      });\n    }\n  };\n\n  const commit = (next: number) => {\n    props.onValueCommitted?.(next);\n  };\n\n  const commitLatest = () => {\n    props.onValueCommitted?.(latestValue);\n  };\n\n  const handleKeyDown = (event: KeyboardEvent) => {\n    const increment = event.shiftKey ? largeStep() : step();\n    let next: number | undefined;\n\n    switch (event.key) {\n      case \"ArrowUp\":\n      case \"ArrowRight\": {\n        next = latestValue + increment;\n\n        break;\n      }\n\n      case \"ArrowDown\":\n      case \"ArrowLeft\": {\n        next = latestValue - increment;\n\n        break;\n      }\n\n      case \"PageUp\": {\n        next = latestValue + largeStep();\n\n        break;\n      }\n\n      case \"PageDown\": {\n        next = latestValue - largeStep();\n\n        break;\n      }\n\n      case \"Home\": {\n        next = min();\n\n        break;\n      }\n\n      case \"End\": {\n        next = max();\n\n        break;\n      }\n\n      default: {\n        return;\n      }\n    }\n\n    event.preventDefault();\n\n    const quantized = quantize(next);\n\n    change(quantized, {\n      event,\n      reason: \"keyboard\",\n    });\n    commit(quantized);\n  };\n\n  const position = () => taper().toPosition(value());\n\n  const originPosition = () =>\n    taper().toPosition(clamp(props.origin ?? min(), min(), max()));\n\n  const context: ParameterSliderContextValue = {\n    change,\n    commit,\n    commitLatest,\n    decimals,\n    descriptionId,\n    disabled,\n    format,\n    handleKeyDown,\n    labelId,\n    largeStep,\n    marks,\n    max,\n    min,\n    originPosition,\n    position,\n    quantize,\n    resetValue,\n    step,\n    taper,\n    unit,\n    value,\n  };\n\n  return provideContext(ParameterSliderContext, context, () => (\n    <div\n      aria-labelledby={labelId}\n      class={cn(\n        \"group/parameter-slider flex w-full flex-col gap-2 data-disabled:opacity-50\",\n        props.class,\n        props.className\n      )}\n      data-disabled={disabled() ? \"\" : undefined}\n      data-modified={value() === resetValue() ? undefined : \"\"}\n      data-slot=\"parameter-slider\"\n      role=\"group\"\n      {...rest}\n    >\n      {props.children}\n    </div>\n  ));\n};\n\nexport interface ParameterSliderHeaderProps extends Omit<\n  DivDOMProps,\n  \"children\" | \"class\" | \"className\"\n> {\n  class?: string;\n  className?: string;\n  children?: DivDOMProps[\"children\"];\n}\n\nconst HEADER_OWN = [\"children\", \"class\", \"className\"] as const;\n\nexport const ParameterSliderHeader = (props: ParameterSliderHeaderProps) => {\n  const rest = omitProps(props, HEADER_OWN);\n\n  return (\n    <div\n      class={cn(\n        \"flex min-h-6 items-center gap-2\",\n        props.class,\n        props.className\n      )}\n      data-slot=\"parameter-slider-header\"\n      {...rest}\n    >\n      {props.children}\n    </div>\n  );\n};\n\nexport interface ParameterSliderLabelProps extends Omit<\n  SpanDOMProps,\n  \"children\" | \"class\" | \"className\"\n> {\n  class?: string;\n  className?: string;\n  children?: SpanDOMProps[\"children\"];\n}\n\nconst LABEL_OWN = [\"children\", \"class\", \"className\", \"id\"] as const;\n\nexport const ParameterSliderLabel = (props: ParameterSliderLabelProps) => {\n  const context = useParameterSlider(\"ParameterSliderLabel\");\n  const rest = omitProps(props, LABEL_OWN);\n\n  return (\n    <span\n      class={cn(\"mr-auto text-sm font-medium\", props.class, props.className)}\n      data-slot=\"parameter-slider-label\"\n      id={context.labelId}\n      {...rest}\n    >\n      {props.children}\n    </span>\n  );\n};\n\nexport interface ParameterSliderInputProps extends Omit<\n  InputDOMProps,\n  | \"class\"\n  | \"className\"\n  | \"defaultValue\"\n  | \"max\"\n  | \"min\"\n  | \"onChange\"\n  | \"onInput\"\n  | \"step\"\n  | \"type\"\n  | \"value\"\n> {\n  class?: string;\n  className?: string;\n  scrub?: boolean;\n}\n\nconst INPUT_OWN = [\"class\", \"className\", \"scrub\"] as const;\n\nexport const ParameterSliderInput = (props: ParameterSliderInputProps) => {\n  const context = useParameterSlider(\"ParameterSliderInput\");\n  const rest = omitProps(props, INPUT_OWN);\n  const [draft, setDraft] = createSignal<string | null>(null);\n  let scrubStartX = 0;\n  let scrubStartValue = 0;\n\n  const numericText = () => context.value().toFixed(context.decimals());\n\n  const displayValue = () => draft() ?? numericText();\n\n  const applyDraft = (reason: ParameterChangeReason, event?: Event) => {\n    const current = draft();\n\n    if (current === null) {\n      return;\n    }\n\n    const parsed = Number(current);\n\n    if (Number.isFinite(parsed)) {\n      context.change(context.quantize(parsed), {\n        event,\n        reason,\n      });\n    }\n  };\n\n  const finish = (event: Event & { currentTarget: HTMLInputElement }) => {\n    applyDraft(\"input\", event);\n    context.commitLatest();\n    setDraft(null);\n    // Ignored incomplete text never reaches the draft, so restore it here.\n    event.currentTarget.value = numericText();\n  };\n\n  const unit = () => context.unit();\n\n  const suffix = () => (\n    <span\n      aria-hidden=\"true\"\n      class={cn(\n        \"text-muted-foreground px-1.5 text-xs\",\n        (props.scrub ?? true) && unit() && !context.disabled()\n          ? \"cursor-ew-resize select-none\"\n          : undefined\n      )}\n      data-slot=\"parameter-slider-unit\"\n      onPointerDown={(event: PointerEvent) => {\n        if (!(props.scrub ?? true) || !unit() || context.disabled()) {\n          return;\n        }\n\n        event.preventDefault();\n\n        const target = event.currentTarget;\n\n        if (!(target instanceof HTMLSpanElement)) {\n          return;\n        }\n\n        target.setPointerCapture(event.pointerId);\n        scrubStartX = event.clientX;\n        scrubStartValue = context.value();\n        setDraft(null);\n      }}\n      onPointerMove={(event: PointerEvent) => {\n        const target = event.currentTarget;\n\n        if (\n          !(target instanceof HTMLSpanElement) ||\n          !target.hasPointerCapture(event.pointerId)\n        ) {\n          return;\n        }\n\n        const steps = Math.round(\n          (event.clientX - scrubStartX) / SCRUB_PIXELS_PER_STEP\n        );\n\n        const next = context.quantize(scrubStartValue + steps * context.step());\n\n        context.change(next, {\n          event,\n          reason: \"input\",\n        });\n      }}\n      onPointerUp={(event: PointerEvent) => {\n        const target = event.currentTarget;\n\n        if (\n          !(target instanceof HTMLSpanElement) ||\n          !target.hasPointerCapture(event.pointerId)\n        ) {\n          return;\n        }\n\n        target.releasePointerCapture(event.pointerId);\n        context.commitLatest();\n      }}\n    >\n      {unit()}\n    </span>\n  );\n\n  return (\n    <div\n      class=\"bg-input/50 focus-within:ring-ring/30 flex h-7 shrink-0 items-center rounded-lg focus-within:ring-3\"\n      data-slot=\"parameter-slider-input-group\"\n    >\n      <input\n        aria-labelledby={context.labelId}\n        aria-valuemax={context.max()}\n        aria-valuemin={context.min()}\n        aria-valuenow={context.value()}\n        aria-valuetext={context.format()(context.value())}\n        class={cn(\n          \"h-full w-16 bg-transparent px-2 text-end font-mono text-xs tabular-nums outline-hidden\",\n          props.class,\n          props.className\n        )}\n        data-slot=\"parameter-slider-input\"\n        disabled={context.disabled()}\n        inputmode=\"decimal\"\n        max={context.max()}\n        min={context.min()}\n        onBlur={(event) => finish(event)}\n        onFocus={() => {\n          setDraft(numericText());\n        }}\n        onInput={(event) => {\n          // Number inputs report incomplete text such as \"-\" as \"\".\n          if (event.currentTarget.value === \"\") {\n            return;\n          }\n\n          setDraft(event.currentTarget.value);\n          applyDraft(\"input\", event);\n        }}\n        onKeyDown={(event) => {\n          if (event.key === \"Enter\") {\n            finish(event);\n          } else if (event.key === \"Escape\") {\n            setDraft(null);\n            event.currentTarget.blur();\n          }\n        }}\n        step={context.step()}\n        type=\"number\"\n        value={displayValue()}\n        {...rest}\n      />\n      <Show when={unit()}>{suffix()}</Show>\n    </div>\n  );\n};\n\nexport interface ParameterSliderValueProps extends Omit<\n  SpanDOMProps,\n  \"children\" | \"class\" | \"className\" | \"style\"\n> {\n  class?: string;\n  className?: string;\n  style?: StyleValue;\n}\n\nconst VALUE_OWN = [\"class\", \"className\", \"style\"] as const;\n\nexport const ParameterSliderValue = (props: ParameterSliderValueProps) => {\n  const context = useParameterSlider(\"ParameterSliderValue\");\n  const rest = omitProps(props, VALUE_OWN);\n\n  const valueWidth = createMemo(() =>\n    widestFormattedValue(\n      Array.from({ length: WIDTH_SAMPLES + 1 }, (_, index) =>\n        context.quantize(context.taper().toValue(index / WIDTH_SAMPLES))\n      ),\n      context.format()\n    )\n  );\n\n  return (\n    <span\n      class={cn(\n        \"text-muted-foreground inline-block min-w-(--parameter-value-width) text-end font-mono text-xs whitespace-nowrap tabular-nums\",\n        props.class,\n        props.className\n      )}\n      data-slot=\"parameter-slider-value\"\n      style={mergeStyleVars(props.style, {\n        \"--parameter-value-width\": `${valueWidth()}ch`,\n      })}\n      {...rest}\n    >\n      {context.format()(context.value())}\n    </span>\n  );\n};\n\nexport interface ParameterSliderResetProps extends Omit<\n  ButtonDOMProps,\n  \"children\" | \"class\" | \"className\" | \"onClick\" | \"type\"\n> {\n  class?: string;\n  className?: string;\n  children?: ButtonDOMProps[\"children\"];\n  onClick?: (event: MouseEvent) => void;\n}\n\nconst RESET_OWN = [\"children\", \"class\", \"className\", \"onClick\"] as const;\n\nexport const ParameterSliderReset = (props: ParameterSliderResetProps) => {\n  const context = useParameterSlider(\"ParameterSliderReset\");\n  const rest = omitProps(props, RESET_OWN);\n\n  const modified = () => context.value() !== context.resetValue();\n\n  return (\n    <button\n      aria-label=\"Reset\"\n      class={cn(\n        \"text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:ring-ring/30 inline-flex h-6 items-center rounded-md px-1.5 text-xs outline-none focus-visible:ring-3 disabled:pointer-events-none disabled:opacity-0\",\n        props.class,\n        props.className\n      )}\n      data-modified={modified() ? \"\" : undefined}\n      data-slot=\"parameter-slider-reset\"\n      disabled={context.disabled() || !modified()}\n      onClick={(event) => {\n        props.onClick?.(event);\n        context.change(context.resetValue(), {\n          event,\n          reason: \"reset\",\n        });\n        context.commit(context.resetValue());\n      }}\n      type=\"button\"\n      {...rest}\n    >\n      {props.children ?? \"Reset\"}\n    </button>\n  );\n};\n\nexport type ParameterSliderControlProps = Omit<\n  SliderRootProps,\n  | \"children\"\n  | \"class\"\n  | \"className\"\n  | \"defaultValue\"\n  | \"disabled\"\n  | \"getValueLabel\"\n  | \"maxValue\"\n  | \"minValue\"\n  | \"onChange\"\n  | \"onChangeEnd\"\n  | \"orientation\"\n  | \"step\"\n  | \"value\"\n> & {\n  class?: string;\n  className?: string;\n};\n\nconst CONTROL_OWN = [\"class\", \"className\"] as const;\n\nexport const ParameterSliderControl = (props: ParameterSliderControlProps) => {\n  const context = useParameterSlider(\"ParameterSliderControl\");\n  const rest = omitProps(props, CONTROL_OWN);\n  let pointerEvent: PointerEvent | undefined;\n\n  const quantizeFromPosition = (position: number): number =>\n    context.quantize(context.taper().toValue(position));\n\n  const start = () =>\n    Math.min(context.originPosition(), context.position()) * 100;\n\n  const end = () =>\n    Math.max(context.originPosition(), context.position()) * 100;\n\n  const sliderValue = createMemo(() => [context.position()]);\n\n  return (\n    <SliderPrimitive.Root\n      class={cn(\n        \"relative flex w-full touch-none items-center select-none\",\n        props.class,\n        props.className\n      )}\n      data-slot=\"parameter-slider-control\"\n      disabled={context.disabled()}\n      getValueLabel={() => context.format()(context.value())}\n      maxValue={1}\n      minValue={0}\n      onChange={(next) => {\n        const position = next[0];\n\n        if (position === undefined) {\n          return;\n        }\n\n        context.change(quantizeFromPosition(position), {\n          event: pointerEvent,\n          reason: \"drag\",\n        });\n      }}\n      onChangeEnd={context.commitLatest}\n      orientation=\"horizontal\"\n      step={POSITION_STEP}\n      value={sliderValue()}\n      {...rest}\n    >\n      <SliderCompat />\n\n      <SliderPrimitive.Track\n        class=\"relative flex h-4 w-full items-center px-2 before:absolute before:inset-x-0 before:-inset-y-1.5 pointer-coarse:before:-inset-y-3\"\n        onPointerDown={(event: PointerEvent) => {\n          pointerEvent = event;\n        }}\n        onPointerMove={(event: PointerEvent) => {\n          pointerEvent = event;\n        }}\n      >\n        <div\n          class=\"bg-input/90 relative h-1 w-full grow rounded-full\"\n          data-slot=\"parameter-slider-track\"\n        >\n          <div\n            aria-hidden=\"true\"\n            class=\"bg-primary absolute inset-y-0 left-(--parameter-range-start) w-(--parameter-range-size) rounded-full\"\n            data-slot=\"parameter-slider-range\"\n            style={{\n              \"--parameter-range-size\": `${end() - start()}%`,\n              \"--parameter-range-start\": `${start()}%`,\n            }}\n          />\n        </div>\n\n        <SliderPrimitive.Thumb\n          aria-describedby={context.descriptionId}\n          aria-labelledby={context.labelId}\n          aria-valuetext={context.format()(context.value())}\n          class=\"bg-background ring-foreground/15 hover:ring-ring/30 focus-visible:ring-ring/40 data-dragging:ring-ring/30 block size-4 shrink-0 rounded-full shadow-sm ring-1 outline-hidden transition-[box-shadow] hover:ring-4 focus-visible:ring-4 data-dragging:ring-4\"\n          data-slot=\"parameter-slider-thumb\"\n          onDblClick={(event: MouseEvent) => {\n            context.change(context.resetValue(), {\n              event,\n              reason: \"reset\",\n            });\n            context.commit(context.resetValue());\n          }}\n          onKeyDown={context.handleKeyDown}\n          onPointerDown={(event: PointerEvent) => {\n            pointerEvent = event;\n          }}\n          onPointerMove={(event: PointerEvent) => {\n            pointerEvent = event;\n          }}\n        >\n          <SliderPrimitive.Input />\n        </SliderPrimitive.Thumb>\n      </SliderPrimitive.Track>\n    </SliderPrimitive.Root>\n  );\n};\n\nexport interface ParameterSliderMarksProps extends Omit<\n  DivDOMProps,\n  \"children\" | \"class\" | \"className\"\n> {\n  class?: string;\n  className?: string;\n}\n\nconst MARKS_OWN = [\"class\", \"className\"] as const;\n\nexport const ParameterSliderMarks = (props: ParameterSliderMarksProps) => {\n  const context = useParameterSlider(\"ParameterSliderMarks\");\n  const rest = omitProps(props, MARKS_OWN);\n\n  return (\n    <Show when={(context.marks()?.length ?? 0) > 0}>\n      <div\n        aria-hidden=\"true\"\n        class={cn(\n          \"text-muted-foreground relative mx-2 h-4 text-[0.625rem]\",\n          props.class,\n          props.className\n        )}\n        data-slot=\"parameter-slider-marks\"\n        {...rest}\n      >\n        <For each={context.marks() ?? []}>\n          {(mark) => (\n            <span\n              class=\"absolute top-0 left-(--mark-position) -translate-x-1/2 whitespace-nowrap tabular-nums\"\n              style={{\n                \"--mark-position\": `${\n                  context.taper().toPosition(mark.value) * 100\n                }%`,\n              }}\n            >\n              {mark.label ?? context.format()(mark.value)}\n            </span>\n          )}\n        </For>\n      </div>\n    </Show>\n  );\n};\n\nexport interface ParameterSliderDescriptionProps extends Omit<\n  ParagraphDOMProps,\n  \"children\" | \"class\" | \"className\"\n> {\n  class?: string;\n  className?: string;\n  children?: ParagraphDOMProps[\"children\"];\n}\n\nconst DESCRIPTION_OWN = [\"children\", \"class\", \"className\", \"id\"] as const;\n\nexport const ParameterSliderDescription = (\n  props: ParameterSliderDescriptionProps\n) => {\n  const context = useParameterSlider(\"ParameterSliderDescription\");\n\n  const rest = omitProps(props, DESCRIPTION_OWN);\n\n  return (\n    <p\n      class={cn(\"text-muted-foreground text-xs\", props.class, props.className)}\n      data-slot=\"parameter-slider-description\"\n      id={context.descriptionId}\n      {...rest}\n    >\n      {props.children}\n    </p>\n  );\n};\n",
      "type": "registry:ui"
    },
    {
      "path": "lib/solid/kobalte-slider.ts",
      "content": "import type { SliderContextValue } from \"@kobalte/core/slider\";\nimport * as Solid from \"solid-js\";\n\nimport { createCompatEffect } from \"@/lib/solid/effect\";\n\ninterface SliderCallbacks {\n  onChangeEnd?: () => void;\n  onSlideEnd?: (original: SliderContextValue[\"onSlideEnd\"]) => void;\n  onSlideMove?: (\n    original: SliderContextValue[\"onSlideMove\"],\n    delta: { deltaX: number; deltaY: number }\n  ) => void;\n  onSlideStart?: (\n    original: SliderContextValue[\"onSlideStart\"],\n    index: number,\n    value: number\n  ) => void;\n  suppressStep?: boolean;\n}\n\n/**\n * Kobalte 0.13.14 and 2.0.0-alpha.2 call these context methods dynamically.\n * SliderContextValue exposes them, but method replacement is not a documented\n * extension point, so keep the Solid 2 thumb-editability workaround and all\n * required pointer remapping in one adapter. Alpha.2's move handler calls\n * onChange twice: setThumbPercent emits the new value, then onChange(values())\n * replays the previous controlled value before Solid 2's queued update settles.\n * Its end handler likewise reads the old dragging array after writing it and\n * misses onChangeEnd. Adapt those two synchronous-read assumptions here, while\n * leaving vendor pointer capture, focus, keyboard and input handling intact.\n */\nexport const useKobalteSliderCompat = (\n  context: SliderContextValue,\n  callbacks: SliderCallbacks = {}\n): void => {\n  const originals = {\n    onSlideEnd: context.onSlideEnd,\n    onSlideMove: context.onSlideMove,\n    onSlideStart: context.onSlideStart,\n    onStepKeyDown: context.onStepKeyDown,\n  };\n\n  const queuedUpdates = \"onSettled\" in Solid;\n  let activeIndex: number | undefined;\n  let currentPosition: number | null = null;\n\n  const move: SliderContextValue[\"onSlideMove\"] = (delta) => {\n    if (!queuedUpdates) {\n      // 0.13.14 uses inversion alone for horizontal deltas, although thumb\n      // placement and track presses also use locale. Correct RTL before delegation.\n      const rtl = context.isSlidingFromLeft() === context.inverted();\n      originals.onSlideMove?.({\n        deltaX: rtl ? -delta.deltaX : delta.deltaX,\n        deltaY: delta.deltaY,\n      });\n\n      return;\n    }\n\n    if (activeIndex === undefined || context.state.isDisabled()) return;\n    const rect = context.trackRef()!.getBoundingClientRect();\n    const vertical = context.state.orientation() === \"vertical\";\n    const size = vertical ? rect.height : rect.width;\n\n    if (size <= 0) return;\n    currentPosition ??= context.state.getThumbPercent(activeIndex) * size;\n\n    const direction = vertical\n      ? context.isSlidingFromBottom()\n        ? -1\n        : 1\n      : context.isSlidingFromLeft()\n        ? 1\n        : -1;\n\n    currentPosition += (vertical ? delta.deltaY : delta.deltaX) * direction;\n    context.state.setThumbPercent(\n      activeIndex,\n      Math.min(1, Math.max(0, currentPosition / size))\n    );\n  };\n\n  context.onSlideStart = (index, value) => {\n    activeIndex = index;\n    currentPosition = null;\n\n    if (callbacks.onSlideStart)\n      callbacks.onSlideStart(originals.onSlideStart, index, value);\n    else originals.onSlideStart?.(index, value);\n  };\n\n  context.onSlideMove = (delta) => {\n    if (callbacks.onSlideMove) callbacks.onSlideMove(move, delta);\n    else move(delta);\n  };\n\n  context.onSlideEnd = () => {\n    if (callbacks.onSlideEnd) callbacks.onSlideEnd(originals.onSlideEnd);\n    else {\n      originals.onSlideEnd?.();\n\n      if (\n        queuedUpdates &&\n        activeIndex !== undefined &&\n        !context.state.isDisabled()\n      )\n        callbacks.onChangeEnd?.();\n    }\n\n    activeIndex = undefined;\n  };\n\n  if (callbacks.suppressStep) {\n    context.onStepKeyDown = () => undefined;\n  }\n\n  Solid.onCleanup(() => {\n    context.onSlideEnd = originals.onSlideEnd;\n    context.onSlideMove = originals.onSlideMove;\n    context.onSlideStart = originals.onSlideStart;\n    context.onStepKeyDown = originals.onStepKeyDown;\n  });\n\n  createCompatEffect(\n    () => [context.thumbs().length, context.state.isDisabled()] as const,\n    ([thumbCount, isDisabled]) => {\n      for (let index = 0; index < thumbCount; index += 1) {\n        context.state.setThumbEditable(index, !isDisabled);\n      }\n    }\n  );\n};\n",
      "type": "registry:lib"
    },
    {
      "path": "lib/audiocn-license.txt",
      "content": "Audio component source adapted from https://github.com/audiocn/ui.\nMIT License — Copyright (c) 2026 OrcDev\n\nRegular SVG icon geometry adapted from @phosphor-icons/react 2.1.10.\nMIT License — Copyright (c) 2020 Phosphor Icons\n\nThe following permission and warranty terms apply to both notices.\nRetain this file with copies or substantial portions of the source.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n",
      "type": "registry:file",
      "target": "@lib/audiocn-license.txt"
    }
  ],
  "type": "registry:ui"
}
