@cutoff/audio-ui-react 1.0.0-preview.20260123.1125 → 1.0.0-preview.20260126.2009

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/components/primitives/controls/OptionView.tsx","../src/components/defaults/controls/ButtonView.tsx","../src/utils/textUtils.ts","../src/components/primitives/AdaptiveBox.tsx","../src/hooks/useAudioParameter.ts","../src/hooks/useBooleanInteraction.ts","../src/hooks/useBooleanParameterResolution.ts","../src/components/primitives/controls/BooleanControl.tsx","../src/hooks/useAdaptiveSize.ts","../src/hooks/useThemableProps.ts","../src/components/defaults/controls/Button.tsx","../src/hooks/useArcAngle.ts","../src/components/primitives/svg/RingArc.tsx","../src/components/primitives/svg/ValueRing.tsx","../src/components/primitives/svg/RadialImage.tsx","../src/components/primitives/svg/RotaryImage.tsx","../src/components/defaults/controls/KnobView.tsx","../src/hooks/useContinuousInteraction.ts","../src/hooks/useContinuousParameterResolution.ts","../src/components/primitives/controls/ContinuousControl.tsx","../src/components/defaults/controls/Knob.tsx","../src/hooks/useDiscreteInteraction.ts","../src/hooks/useDiscreteParameterResolution.ts","../src/components/primitives/controls/DiscreteControl.tsx","../src/components/defaults/controls/CycleButton.tsx","../src/components/primitives/svg/LinearStrip.tsx","../src/components/primitives/svg/ValueStrip.tsx","../src/components/primitives/svg/LinearCursor.tsx","../src/components/defaults/controls/SliderView.tsx","../src/components/defaults/controls/Slider.tsx","../src/components/primitives/svg/FilmstripImage.tsx","../src/components/generic/controls/FilmstripView.tsx","../src/components/generic/controls/FilmStripContinuousControl.tsx","../src/components/generic/controls/FilmStripDiscreteControl.tsx","../src/components/generic/controls/FilmStripBooleanControl.tsx","../src/components/generic/controls/ImageKnobView.tsx","../src/components/generic/controls/ImageKnob.tsx","../src/components/generic/controls/ImageRotarySwitch.tsx","../src/components/primitives/svg/Image.tsx","../src/components/generic/controls/ImageSwitchView.tsx","../src/components/generic/controls/ImageSwitch.tsx","../src/components/primitives/svg/RadialHtmlOverlay.tsx","../src/components/primitives/svg/RevealingPath.tsx","../src/components/primitives/svg/TickRing.tsx","../src/components/primitives/svg/LabelRing.tsx","../src/hooks/useNoteInteraction.ts","../src/components/defaults/devices/Keys.tsx","../src/utils/theme.ts"],"sourcesContent":["/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\n\nexport type OptionViewProps = {\n /** Value associated with this option */\n value?: string | number;\n /** Child content (Visual representation) */\n children?: React.ReactNode;\n /** Optional text label for the parameter model (accessibility/aria-label) */\n label?: string;\n};\n\nexport default function OptionView(_props: OptionViewProps) {\n return null; // This component is never rendered directly\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport { translateButtonRoundness } from \"@cutoff/audio-ui-core\";\nimport { DEFAULT_ROUNDNESS } from \"@cutoff/audio-ui-core\";\nimport { ControlComponent } from \"@/types\";\n\n/**\n * Props for the ButtonView component\n */\nexport type ButtonViewProps = {\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Threshold for determining \"on\" state (default 0.5) */\n threshold?: number;\n /** Corner roundness (normalized 0.0-1.0, maps to 0-50, or CSS variable string) */\n roundness?: number | string;\n /** Color prop (kept for API compatibility, but colors are read from CSS variables) */\n color: string;\n /** Additional CSS class name */\n className?: string;\n};\n\n/**\n * Pure SVG presentation component for a button.\n * Renders a rectangle with conditional styling based on normalized value vs threshold.\n *\n * Colors are read from CSS variables (`--audioui-primary-color`, `--audioui-primary-50`, `--audioui-primary-20`)\n * which are set by the parent Button component based on the `color` prop.\n *\n * @param normalizedValue - Value between 0 and 1\n * @param threshold - Threshold value (default 0.5), determines \"on\" state\n * @param roundness - Normalized roundness 0.0-1.0 (default 0.3, maps to 0-50)\n * @param color - Color prop (kept for API compatibility, but not used - CSS variables are used instead)\n * @param className - Optional CSS class\n */\nfunction ButtonView({\n normalizedValue,\n threshold = 0.5,\n roundness,\n color: _color, // Prefixed with _ to indicate intentionally unused (kept for API compatibility)\n className,\n}: ButtonViewProps): JSX.Element {\n // Determine if button is \"on\" based on threshold\n const isOn = useMemo(() => normalizedValue > threshold, [normalizedValue, threshold]);\n\n // Translate normalized roundness to legacy range (0-50) or use CSS variable\n // When roundness is a CSS variable string (from theme), pass it directly to SVG rx/ry attributes.\n // When roundness is a number, translate it to the legacy pixel range.\n const cornerRadius = useMemo(() => {\n if (typeof roundness === \"string\") {\n // CSS variable - pass directly to SVG (browser will resolve it)\n return roundness;\n }\n // Numeric value - translate to legacy pixel range (0-50)\n return translateButtonRoundness(roundness ?? DEFAULT_ROUNDNESS);\n }, [roundness]);\n\n // Use CSS variables for colors - CSS handles variant generation via color-mix\n const buttonStyles = useMemo(\n () => ({\n stroke: isOn ? \"var(--audioui-primary-50)\" : \"var(--audioui-primary-20)\",\n fill: isOn ? \"var(--audioui-primary-color)\" : \"var(--audioui-primary-50)\",\n strokeWidth: \"var(--audioui-button-stroke-width, 5px)\",\n }),\n [isOn]\n );\n\n return (\n <rect\n className={className}\n style={{\n stroke: buttonStyles.stroke,\n fill: buttonStyles.fill,\n strokeWidth: buttonStyles.strokeWidth,\n rx: cornerRadius,\n ry: cornerRadius,\n }}\n x={10}\n y={10}\n width={80}\n height={40}\n // Use 0 as fallback for older browsers that don't support CSS rx/ry\n // If cornerRadius is a number, we can use it directly\n rx={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n ry={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n />\n );\n}\n\n/**\n * ViewBox dimensions for the ButtonView component.\n * The parent component should use these values when setting up the SVG container.\n */\nconst VIEW_BOX = {\n width: 100,\n height: 60,\n} as const;\n\nconst ButtonViewMemo = React.memo(ButtonView);\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ButtonViewMemo as any).viewBox = VIEW_BOX;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ButtonViewMemo as any).labelHeightUnits = 20;\n\nexport default ButtonViewMemo as unknown as ControlComponent<\n Omit<ButtonViewProps, \"normalizedValue\" | \"className\" | \"style\">\n>;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Abbreviates text to a maximum number of characters.\n * Returns the first N characters of the text, or the original text if it's shorter.\n *\n * @param text - The text to abbreviate\n * @param maxLength - Maximum length (default: 3)\n * @returns Abbreviated text\n *\n * @example\n * abbreviateText(\"Volume\", 3) // \"Vol\"\n * abbreviateText(\"Hi\", 3) // \"Hi\"\n * abbreviateText(\"\", 3) // \"\"\n */\nexport function abbreviateText(text: string, maxLength: number = 3): string {\n if (!text || text.length <= maxLength) {\n return text;\n }\n return text.slice(0, maxLength);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, {\n createContext,\n CSSProperties,\n PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { abbreviateText } from \"@/utils/textUtils\";\n\n// Shared string unions following the demo controls and spec\nexport type FlexAlign = \"start\" | \"center\" | \"end\";\nexport type DisplayMode = \"scaleToFit\" | \"fill\";\nexport type LabelMode = \"none\" | \"hidden\" | \"visible\";\nexport type LabelPosition = \"above\" | \"below\";\n\n// Context to coordinate between root and subcomponents\ninterface BoxContextValue {\n // Minimal config exposed to children to avoid unnecessary re-renders\n hasLabel: boolean;\n labelPosition: LabelPosition;\n\n // Root-level configuration\n displayMode: DisplayMode;\n labelMode: LabelMode;\n labelHeightUnits: number;\n labelOverflow: \"ellipsis\" | \"abbreviate\" | \"auto\";\n debug: boolean;\n\n // ViewBox dimensions (for SVG this maps to viewBox; for Canvas/GL this will map to canvas/gl dimensions)\n viewBoxWidth: number;\n viewBoxHeight: number;\n\n // Computed grid row for main content (Svg, HtmlOverlay, Canvas, GL all use this)\n mainContentGridRow: string;\n\n // Registration APIs\n registerSvg: (info: { hAlign?: FlexAlign; vAlign?: FlexAlign }) => void;\n registerLabel: (info: { position?: LabelPosition; align?: FlexAlign }) => void;\n}\n\nconst BoxContext = createContext<BoxContextValue | null>(null);\n\nfunction useBoxContext() {\n const ctx = useContext(BoxContext);\n if (!ctx) throw new Error(\"AdaptiveBox subcomponents must be used within <AdaptiveBox>\");\n return ctx;\n}\n\nexport interface AdaptiveBoxProps extends PropsWithChildren {\n className?: string;\n style?: CSSProperties;\n displayMode?: DisplayMode;\n labelMode?: LabelMode;\n labelHeightUnits?: number; // in the same units as viewBox height; default 15\n labelOverflow?: \"ellipsis\" | \"abbreviate\" | \"auto\";\n /**\n * ViewBox width in the same coordinate system as the content.\n * For SVG content, this maps to the SVG viewBox width.\n * For Canvas/GL content (future), this will map to canvas/gl dimensions.\n */\n viewBoxWidth: number;\n /**\n * ViewBox height in the same coordinate system as the content.\n * For SVG content, this maps to the SVG viewBox height.\n * For Canvas/GL content (future), this will map to canvas/gl dimensions.\n */\n viewBoxHeight: number;\n minWidth?: number;\n minHeight?: number;\n debug?: boolean; // Enables visual debugging aids (scaler border, svg background). Defaults to false\n}\n\n/**\n * AdaptiveBox provides a CSS/SVG-based layout system for controls with labels.\n *\n * Handles aspect ratio preservation, label positioning, and responsive sizing using\n * container queries. The component automatically adapts its layout based on labelMode\n * and viewBox dimensions to prevent layout shift during initial render.\n *\n * @param props - Component props including layout configuration and viewBox dimensions\n * @returns Rendered AdaptiveBox with subcomponents (Svg, Label, HtmlOverlay) available via context\n *\n * @example\n * ```tsx\n * <AdaptiveBox\n * viewBoxWidth={100}\n * viewBoxHeight={100}\n * labelMode=\"visible\"\n * >\n * <AdaptiveBox.Svg>\n * <circle cx={50} cy={50} r={40} />\n * </AdaptiveBox.Svg>\n * <AdaptiveBox.Label>Volume</AdaptiveBox.Label>\n * </AdaptiveBox>\n * ```\n */\nexport function AdaptiveBox({\n className,\n style,\n displayMode = \"scaleToFit\",\n labelMode = \"visible\",\n labelHeightUnits,\n labelOverflow = \"auto\",\n viewBoxWidth,\n viewBoxHeight,\n minWidth,\n minHeight,\n debug = false,\n children,\n}: AdaptiveBoxProps) {\n const [svgInfo, setSvgInfo] = useState<{ hAlign?: FlexAlign; vAlign?: FlexAlign } | null>(null);\n const [labelInfo, setLabelInfo] = useState<{ position: LabelPosition; align: FlexAlign } | null>(null);\n\n // Stable callbacks that only update state when values actually change\n const registerSvg = useCallback((info: { hAlign?: FlexAlign; vAlign?: FlexAlign }) => {\n setSvgInfo((prev) => {\n const next = {\n hAlign: info.hAlign,\n vAlign: info.vAlign,\n } as const;\n if (!prev || prev.hAlign !== next.hAlign || prev.vAlign !== next.vAlign) {\n return { ...next };\n }\n return prev;\n });\n }, []);\n\n const registerLabel = useCallback((info: { position?: LabelPosition; align?: FlexAlign }) => {\n setLabelInfo((prev) => {\n const next = {\n position: info.position ?? \"below\",\n align: info.align ?? \"center\",\n } as const;\n if (!prev || prev.position !== next.position || prev.align !== next.align) {\n return { ...next };\n }\n return prev;\n });\n }, []);\n\n const labelHeightUnitsEffective = labelHeightUnits ?? 15;\n\n // Derived layout numbers (kept local for readability and to avoid context churn)\n const styleH = (style?.justifySelf as FlexAlign) ?? \"center\";\n const styleV = (style?.alignSelf as FlexAlign) ?? \"center\";\n const hAlign = svgInfo?.hAlign ?? styleH;\n const vAlign = svgInfo?.vAlign ?? styleV;\n const effectiveLabelPosition: LabelPosition = labelInfo?.position ?? \"below\";\n const isFill = displayMode === \"fill\";\n\n // Compute grid row for main content (used by Svg, HtmlOverlay, Canvas, GL)\n // Only labelMode matters for reserving space - not whether labelInfo has been registered yet\n // This prevents layout shift during initial render when Label component registers via useLayoutEffect\n const showLabelSpace = labelMode !== \"none\";\n const mainContentGridRow = showLabelSpace && effectiveLabelPosition === \"above\" ? \"2 / 3\" : \"1 / 2\";\n\n const ctxValue = useMemo<BoxContextValue>(\n () => ({\n hasLabel: !!labelInfo,\n labelPosition: labelInfo?.position ?? \"below\",\n displayMode,\n labelMode,\n labelHeightUnits: labelHeightUnitsEffective,\n labelOverflow,\n debug,\n viewBoxWidth,\n viewBoxHeight,\n mainContentGridRow,\n registerSvg,\n registerLabel,\n }),\n [\n labelInfo,\n displayMode,\n labelMode,\n labelHeightUnitsEffective,\n labelOverflow,\n debug,\n viewBoxWidth,\n viewBoxHeight,\n mainContentGridRow,\n registerSvg,\n registerLabel,\n ]\n );\n const L = labelHeightUnitsEffective;\n const combinedHeightUnits = showLabelSpace ? viewBoxHeight + L : viewBoxHeight;\n\n // Grid template rows for SVG + (optional) label\n let gridTemplateRows = \"1fr\";\n if (showLabelSpace) {\n const svgPercent = (viewBoxHeight / combinedHeightUnits) * 100;\n const labelPercent = (L / combinedHeightUnits) * 100;\n if (effectiveLabelPosition === \"above\") {\n gridTemplateRows = `${labelPercent}% ${svgPercent}%`;\n } else {\n gridTemplateRows = `${svgPercent}% ${labelPercent}%`;\n }\n }\n\n return (\n <BoxContext.Provider value={ctxValue}>\n <div\n data-name=\"Control+Label Wrapper\"\n className={className}\n style={{\n width: \"100%\",\n height: \"100%\",\n containerType: \"size\",\n display: \"grid\",\n justifyItems: \"center\",\n alignItems: \"center\",\n overflow: \"hidden\",\n minWidth,\n minHeight,\n ...(style ?? {}),\n }}\n >\n <div\n data-name=\"Aspect Scaler\"\n style={{\n aspectRatio: `${viewBoxWidth} / ${combinedHeightUnits}`,\n width: isFill ? \"100%\" : `min(100%, calc(100cqh * ${viewBoxWidth} / ${combinedHeightUnits}))`,\n height: isFill ? \"100%\" : \"auto\",\n display: \"grid\",\n gridTemplateRows,\n justifyItems: \"center\",\n alignItems: \"center\",\n justifySelf: hAlign,\n alignSelf: vAlign,\n minWidth: 0,\n minHeight: 0,\n containerType: \"inline-size\",\n position: \"relative\",\n border: debug ? \"2px solid hsl(41, 96%, 40%)\" : undefined,\n }}\n >\n {/* SVG and Label are rendered by subcomponents via context; this wrapper only provides layout */}\n {children}\n </div>\n </div>\n </BoxContext.Provider>\n );\n}\n\nexport interface AdaptiveBoxSvgProps extends PropsWithChildren {\n vAlign?: FlexAlign;\n hAlign?: FlexAlign;\n className?: string;\n style?: CSSProperties;\n // Event handlers (use native WheelEvent as requested)\n onWheel?: (e: React.WheelEvent<SVGSVGElement>) => void;\n onClick?: React.MouseEventHandler<SVGSVGElement>;\n onDoubleClick?: React.MouseEventHandler<SVGSVGElement>;\n onMouseDown?: React.MouseEventHandler<SVGSVGElement>;\n onMouseUp?: React.MouseEventHandler<SVGSVGElement>;\n onMouseEnter?: React.MouseEventHandler<SVGSVGElement>;\n onMouseLeave?: React.MouseEventHandler<SVGSVGElement>;\n onTouchStart?: React.TouchEventHandler<SVGSVGElement>;\n onTouchMove?: React.TouchEventHandler<SVGSVGElement>;\n onTouchEnd?: React.TouchEventHandler<SVGSVGElement>;\n onKeyDown?: React.KeyboardEventHandler<SVGSVGElement>;\n onKeyUp?: React.KeyboardEventHandler<SVGSVGElement>;\n onPointerDown?: React.PointerEventHandler<SVGSVGElement>;\n onPointerMove?: React.PointerEventHandler<SVGSVGElement>;\n onPointerUp?: React.PointerEventHandler<SVGSVGElement>;\n onPointerCancel?: React.PointerEventHandler<SVGSVGElement>;\n tabIndex?: number;\n role?: string;\n \"aria-valuenow\"?: number;\n \"aria-valuemin\"?: number;\n \"aria-valuemax\"?: number;\n \"aria-label\"?: string;\n \"aria-valuetext\"?: string;\n \"aria-orientation\"?: \"horizontal\" | \"vertical\";\n \"aria-pressed\"?: boolean | \"mixed\";\n}\n\nfunction Svg({ vAlign, hAlign, className, style, children, onWheel, ...rest }: AdaptiveBoxSvgProps) {\n const ctx = useBoxContext();\n\n // Register alignment preferences (viewBox dimensions come from AdaptiveBox props)\n useLayoutEffect(() => {\n ctx.registerSvg({ hAlign, vAlign });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hAlign, vAlign]);\n\n const svgRef = useRef<SVGSVGElement>(null);\n\n // Use native non-passive listener to reliably prevent scrolling (especially Safari/Mobile)\n // React's onWheel prop is passive by default in some contexts\n useEffect(() => {\n if (!svgRef.current || !onWheel) return;\n\n const element = svgRef.current;\n const wheelHandler = (e: globalThis.WheelEvent) => {\n onWheel(e as unknown as React.WheelEvent<SVGSVGElement>);\n };\n\n element.addEventListener(\"wheel\", wheelHandler, { passive: false });\n return () => element.removeEventListener(\"wheel\", wheelHandler);\n }, [onWheel]);\n\n const preserveAspect = ctx.displayMode === \"fill\" ? \"none\" : \"xMidYMid meet\";\n\n return (\n <svg\n ref={svgRef}\n data-name=\"Main Component\"\n viewBox={`0 0 ${ctx.viewBoxWidth} ${ctx.viewBoxHeight}`}\n preserveAspectRatio={preserveAspect as React.SVGProps<SVGSVGElement>[\"preserveAspectRatio\"]}\n className={className}\n style={{\n display: \"block\",\n width: \"100%\",\n height: \"100%\",\n // Position in main content grid row\n gridRow: ctx.mainContentGridRow,\n gridColumn: 1,\n backgroundColor: ctx.debug ? \"hsl(0, 100%, 50% / 0.06)\" : undefined,\n ...(style ?? {}),\n }}\n {...rest}\n >\n {children}\n </svg>\n );\n}\n\nexport interface AdaptiveBoxLabelProps extends PropsWithChildren {\n className?: string;\n style?: CSSProperties;\n position?: LabelPosition; // above | below\n align?: FlexAlign; // start | center | end\n}\n\nfunction Label({ className, style, position = \"below\", align = \"center\", children }: AdaptiveBoxLabelProps) {\n const ctx = useBoxContext();\n\n useLayoutEffect(() => {\n ctx.registerLabel({ position, align });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [position, align]);\n\n // Determine if we should abbreviate based on labelOverflow prop\n // Must be called before early return to satisfy React hooks rules\n const shouldAbbreviate = useMemo(() => {\n if (ctx.labelOverflow === \"abbreviate\") {\n return true;\n }\n if (ctx.labelOverflow === \"ellipsis\") {\n return false;\n }\n // \"auto\" mode: abbreviate only when viewBox is portrait (width < height)\n return ctx.viewBoxWidth < ctx.viewBoxHeight;\n }, [ctx.labelOverflow, ctx.viewBoxWidth, ctx.viewBoxHeight]);\n\n const labelContent = useMemo(() => {\n if (shouldAbbreviate && typeof children === \"string\" && children.length > 5) {\n return abbreviateText(children, 3);\n }\n return children;\n }, [children, shouldAbbreviate]);\n\n if (ctx.labelMode === \"none\") return null;\n\n const visibility = ctx.labelMode === \"hidden\" ? \"hidden\" : \"visible\";\n const gridRow = ctx.labelPosition === \"above\" ? \"1 / 2\" : \"2 / 3\";\n\n return (\n <div\n data-name=\"Label\"\n className={className}\n style={{\n width: \"100%\",\n gridRow,\n visibility,\n containerType: \"size\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: align,\n ...(style ?? {}),\n }}\n >\n <span\n style={{\n fontSize: \"75cqh\",\n lineHeight: 1,\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n textOverflow: shouldAbbreviate ? \"clip\" : \"ellipsis\",\n }}\n >\n {labelContent}\n </span>\n </div>\n );\n}\n\nexport interface AdaptiveBoxHtmlOverlayProps extends PropsWithChildren {\n className?: string;\n style?: CSSProperties;\n /**\n * Pointer events behavior.\n * - \"none\" (default): Clicks pass through to elements below (e.g., SVG)\n * - \"auto\": Overlay is interactive\n */\n pointerEvents?: \"none\" | \"auto\";\n}\n\n/**\n * HTML overlay positioned over the main content area (same grid cell as Svg/Canvas/GL).\n * Used for rendering text, icons, or other HTML content on top of SVG graphics.\n *\n * This approach avoids Safari's foreignObject rendering bugs with container queries\n * by rendering HTML content outside the SVG as a sibling element.\n *\n * The overlay provides a container query context (`containerType: \"size\"`) enabling\n * responsive sizing with `cqmin`, `cqmax`, `cqw`, `cqh` units.\n *\n * Uses CSS Grid stacking: multiple elements in the same grid cell overlap\n * in DOM order (later elements appear on top).\n */\nfunction HtmlOverlay({ className, style, pointerEvents = \"none\", children }: AdaptiveBoxHtmlOverlayProps) {\n const ctx = useBoxContext();\n\n return (\n <div\n data-name=\"HTML Overlay\"\n className={className}\n style={{\n // Same grid cell as Svg - CSS Grid stacks elements in DOM order\n gridRow: ctx.mainContentGridRow,\n gridColumn: 1,\n // Override parent's centering to fill the entire grid cell\n placeSelf: \"stretch\",\n // Container query context for cqmin/cqmax units\n containerType: \"size\",\n // Center content within the overlay\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n // Pointer events (default: pass through to SVG below)\n pointerEvents,\n ...(style ?? {}),\n }}\n >\n {children}\n </div>\n );\n}\n\n// Compose compound component\nAdaptiveBox.Svg = Svg;\nAdaptiveBox.Label = Label;\nAdaptiveBox.HtmlOverlay = HtmlOverlay;\n\n// Type exports (using type aliases instead of namespace)\nexport type AdaptiveBoxSvg = typeof Svg;\nexport type AdaptiveBoxLabel = typeof Label;\nexport type AdaptiveBoxHtmlOverlay = typeof HtmlOverlay;\n\nexport default AdaptiveBox;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { useMemo, useCallback, useRef } from \"react\";\nimport { AudioParameter, AudioParameterConverter } from \"@cutoff/audio-ui-core\";\nimport { AudioControlEvent } from \"../components/types\";\n\nexport interface UseAudioParameterResult {\n /** The normalized value (0..1) for UI rendering */\n normalizedValue: number;\n /** Formatted string representation of the current value */\n formattedValue: string;\n /** The effective label to display (computed from userLabel, valueAsLabel, or parameter name) */\n effectiveLabel: string;\n /** The full parameter model instance */\n converter: AudioParameterConverter;\n /**\n * Set the value using a normalized (0..1) input.\n * Automatically denormalizes and calls onChange.\n */\n setNormalizedValue: (normalized: number) => void;\n /**\n * Adjust the value relatively (e.g. from mouse wheel or drag).\n * @param delta The amount to change (in normalized units, typically small like 0.01)\n * @param sensitivity Optional multiplier (default 1.0)\n */\n adjustValue: (delta: number, sensitivity?: number) => void;\n /**\n * Get the default normalized value (0..1).\n * Returns the normalized defaultValue from the parameter if defined,\n * otherwise returns 0.0 for unipolar or 0.5 for bipolar parameters.\n */\n getDefaultNormalizedValue: () => number;\n /**\n * Reset the value to the default value.\n * Uses the parameter's defaultValue if defined, otherwise uses 0.0 for unipolar or 0.5 for bipolar.\n */\n resetToDefault: () => void;\n}\n\n/**\n * Hook to manage audio parameter logic, normalization, and formatting.\n *\n * This is the primary hook for connecting audio parameter models to React components.\n * It handles all value conversions (real ↔ normalized ↔ MIDI) and provides formatted\n * display values. The hook ensures that all conversions go through the MIDI pivot,\n * maintaining consistency with hardware standards.\n *\n * The hook provides:\n * - `normalizedValue`: 0..1 value for UI rendering\n * - `formattedValue`: Formatted string for display\n * - `converter`: The AudioParameterConverter instance for advanced operations\n * - `setNormalizedValue`: Set value from normalized input (e.g., from UI slider)\n * - `adjustValue`: Adjust value relatively (e.g., from mouse wheel or drag)\n *\n * @param value The current real-world value (Source of Truth)\n * @param onChange Callback when value changes. Receives an AudioControlEvent with all value representations.\n * @param parameterDef The parameter definition (AudioParameter)\n * @param userValueFormatter Optional custom renderer for the value display. If provided and returns a value, it takes precedence over the default formatter.\n * @param userLabel Optional custom label. If provided and valueAsLabel is false, takes precedence over parameter name.\n * @param valueAsLabel When true, displays the formatted value as the label instead of the provided label or parameter name.\n * @returns Object containing normalizedValue, formattedValue, effectiveLabel, converter, setNormalizedValue, and adjustValue\n *\n * @example\n * ```tsx\n * // Basic usage\n * const { normalizedValue, formattedValue, adjustValue } = useAudioParameter(\n * volume,\n * (e) => setVolume(e.value),\n * volumeParam\n * );\n *\n * // With custom label and value formatter\n * const { normalizedValue, formattedValue, effectiveLabel, adjustValue } = useAudioParameter(\n * volume,\n * (e) => setVolume(e.value),\n * volumeParam,\n * (val) => `${val.toFixed(1)} dB`, // Custom formatter\n * \"Master Volume\", // Custom label\n * false // Don't use value as label\n * );\n *\n * // Use normalizedValue for rendering\n * <KnobView normalizedValue={normalizedValue} />\n *\n * // Use adjustValue for relative changes\n * <div onWheel={(e) => adjustValue(e.deltaY, 0.001)} />\n *\n * // Use effectiveLabel for display\n * <label>{effectiveLabel}</label>\n * ```\n */\nexport function useAudioParameter<T extends number | boolean | string>(\n value: T,\n onChange: undefined | ((event: AudioControlEvent<T>) => void),\n parameterDef: AudioParameter,\n userValueFormatter?: (value: T, parameterDef: AudioParameter) => string | undefined,\n userLabel?: string,\n valueAsLabel?: boolean\n): UseAudioParameterResult {\n const converter = useMemo(() => {\n return new AudioParameterConverter(parameterDef);\n }, [parameterDef]);\n\n const normalizedValue = useMemo(() => {\n return converter.normalize(value);\n }, [value, converter]);\n\n // Track latest value in ref to avoid stale closures during rapid events (e.g., wheel)\n const valueRef = useRef(value);\n valueRef.current = value;\n\n const commitValue = useCallback(\n (newValue: T) => {\n if (!onChange) return;\n\n const normalized = converter.normalize(newValue);\n const midi = converter.toMidi(newValue);\n\n onChange({\n value: newValue,\n normalizedValue: normalized,\n midiValue: midi,\n parameter: parameterDef,\n });\n },\n [converter, onChange, parameterDef]\n );\n\n const setNormalizedValue = useCallback(\n (newNormal: number) => {\n if (!onChange) return;\n const clamped = Math.max(0, Math.min(1, newNormal));\n const realValue = converter.denormalize(clamped) as T;\n\n if (realValue !== valueRef.current) {\n valueRef.current = realValue;\n commitValue(realValue);\n }\n },\n [converter, onChange, commitValue]\n );\n\n const adjustValue = useCallback(\n (delta: number, sensitivity = 0.001) => {\n if (!onChange) return;\n\n // Use ref for calculation base to prevent stale closure jitter during rapid events\n const currentReal = valueRef.current as number;\n const currentNormal = converter.normalize(currentReal);\n\n const newNormal = Math.max(0, Math.min(1, currentNormal + delta * sensitivity));\n setNormalizedValue(newNormal);\n },\n [converter, onChange, setNormalizedValue]\n );\n\n // Custom valueFormatter takes precedence if provided and returns a value; otherwise fall back to default formatter\n const formattedValue = useMemo(() => {\n if (userValueFormatter) {\n const customValue = userValueFormatter(value, parameterDef);\n if (customValue !== undefined) {\n return customValue;\n }\n }\n return converter.format(value);\n }, [value, converter, userValueFormatter, parameterDef]);\n\n // Compute effective label: valueAsLabel takes precedence, then userLabel, then parameter name\n const effectiveLabel = useMemo(() => {\n if (valueAsLabel) {\n return formattedValue;\n }\n return userLabel ?? parameterDef.name;\n }, [valueAsLabel, formattedValue, userLabel, parameterDef.name]);\n\n // Get default normalized value\n const getDefaultNormalizedValue = useCallback(() => {\n if (parameterDef.type === \"continuous\") {\n if (parameterDef.defaultValue !== undefined) {\n return converter.normalize(parameterDef.defaultValue);\n }\n // If no defaultValue, use 0.0 for unipolar, 0.5 for bipolar\n const isBipolar = parameterDef.bipolar === true;\n return isBipolar ? 0.5 : 0.0;\n }\n // For non-continuous parameters, return 0.0\n return 0.0;\n }, [converter, parameterDef]);\n\n // Reset to default value\n const resetToDefault = useCallback(() => {\n if (!onChange) return;\n const defaultNormalized = getDefaultNormalizedValue();\n setNormalizedValue(defaultNormalized);\n }, [onChange, getDefaultNormalizedValue, setNormalizedValue]);\n\n return {\n normalizedValue,\n formattedValue,\n effectiveLabel,\n converter,\n setNormalizedValue,\n adjustValue,\n getDefaultNormalizedValue,\n resetToDefault,\n };\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React, { useCallback, useRef, useEffect } from \"react\";\nimport { BooleanInteractionController, BooleanInteractionMode } from \"@cutoff/audio-ui-core\";\n\nexport interface UseBooleanInteractionProps {\n /** Current value of the control */\n value: boolean;\n /** Interaction mode: toggle (latch) or momentary */\n mode: BooleanInteractionMode;\n /** Callback to update the value */\n onValueChange: (value: boolean) => void;\n /** Whether the control is disabled */\n disabled?: boolean;\n /** Optional user-provided mouse down handler (composed with hook handler) */\n onMouseDown?: React.MouseEventHandler;\n /** Optional user-provided mouse up handler (composed with hook handler) */\n onMouseUp?: React.MouseEventHandler;\n /** Optional user-provided touch start handler (composed with hook handler) */\n onTouchStart?: React.TouchEventHandler;\n /** Optional user-provided touch end handler (composed with hook handler) */\n onTouchEnd?: React.TouchEventHandler;\n /** Optional user-provided keyboard key down handler (composed with hook handler) */\n onKeyDown?: React.KeyboardEventHandler;\n /** Optional user-provided keyboard key up handler (composed with hook handler) */\n onKeyUp?: React.KeyboardEventHandler;\n}\n\nexport interface UseBooleanInteractionResult {\n /** Handler for mouse down events */\n handleMouseDown: (e: React.MouseEvent) => void;\n /** Handler for mouse up events */\n handleMouseUp: (e: React.MouseEvent) => void;\n /** Handler for mouse enter events (for drag-in behavior) */\n handleMouseEnter: (e: React.MouseEvent) => void;\n /** Handler for mouse leave events (for drag-out behavior) */\n handleMouseLeave: (e: React.MouseEvent) => void;\n /** Handler for touch start events */\n handleTouchStart: (e: React.TouchEvent) => void;\n /** Handler for touch end events */\n handleTouchEnd: (e: React.TouchEvent) => void;\n /** Handler for touch move events (for touch drag-in/drag-out behavior) */\n handleTouchMove: (e: React.TouchEvent) => void;\n /** Ref callback to attach to the button element for touch tracking */\n setButtonElement: (element: HTMLElement | SVGSVGElement | null) => void;\n /** Handler for keyboard key down events (Enter/Space to activate) */\n handleKeyDown: (e: React.KeyboardEvent) => void;\n /** Handler for keyboard key up events (Enter/Space to release momentary buttons) */\n handleKeyUp: (e: React.KeyboardEvent) => void;\n}\n\n/**\n * Hook to manage interactions for boolean controls (buttons, toggles).\n *\n * Provides standardized logic for:\n * - Toggle mode: Click or Space/Enter to flip the value\n * - Momentary mode: Press to activate, release to deactivate (with global pointer tracking)\n * - Drag-in/drag-out behavior: Buttons respond to pointer entering/leaving while pressed, even when press starts outside the button\n * - Keyboard support: Enter/Space for activation/release\n *\n * The hook wraps the framework-agnostic `BooleanInteractionController` and provides React\n * event handlers that can be attached directly to DOM elements. It maintains stable callback\n * references across renders using `useCallback` and updates the controller configuration via\n * `useEffect` when props change.\n *\n * **Drag-In/Drag-Out Behavior:**\n * - **Momentary Mode**: Press inside → turns on; drag out while pressed → turns off; drag back in while pressed → turns on again. Works even when press starts outside the button.\n * - **Toggle Mode**: Press inside → toggles state; drag out while pressed → no change; drag back in while pressed → toggles again. Works even when press starts outside the button.\n *\n * The hook automatically attaches global pointer listeners (mousedown, mouseup, touchstart, touchmove, touchend)\n * to track pointer state globally, enabling drag-in behavior from anywhere on the page. For mouse interactions,\n * it handles mouseenter/mouseleave events to detect when the pointer crosses the button boundary while pressed.\n * For touch interactions, it manually tracks touch position using touchmove events and elementFromPoint to detect\n * when the touch point crosses button boundaries (since touch events don't fire mouseenter/mouseleave).\n *\n * @param {UseBooleanInteractionProps} props - Configuration for the boolean interaction hook\n * @param {boolean} props.value - Current value of the control\n * @param {BooleanInteractionMode} props.mode - Interaction mode: \"toggle\" or \"momentary\"\n * @param {(value: boolean) => void} props.onValueChange - Callback to update the value\n * @param {boolean} [props.disabled=false] - Whether the control is disabled\n * @param {React.MouseEventHandler} [props.onMouseDown] - Optional user-provided mouse down handler\n * @param {React.MouseEventHandler} [props.onMouseUp] - Optional user-provided mouse up handler\n * @param {React.TouchEventHandler} [props.onTouchStart] - Optional user-provided touch start handler\n * @param {React.TouchEventHandler} [props.onTouchEnd] - Optional user-provided touch end handler\n * @param {React.KeyboardEventHandler} [props.onKeyDown] - Optional user-provided keyboard key down handler\n * @param {React.KeyboardEventHandler} [props.onKeyUp] - Optional user-provided keyboard key up handler\n * @returns {UseBooleanInteractionResult} Object containing event handlers including handleMouseEnter and handleMouseLeave for drag-in/drag-out behavior\n *\n * @example\n * ```tsx\n * const { handleMouseDown, handleMouseUp, handleMouseEnter, handleMouseLeave, handleTouchStart, handleTouchEnd, handleKeyDown, handleKeyUp } = useBooleanInteraction({\n * value,\n * mode: \"momentary\",\n * onValueChange: (val) => setValue(val)\n * });\n *\n * <div\n * onMouseDown={handleMouseDown}\n * onMouseUp={handleMouseUp}\n * onMouseEnter={handleMouseEnter}\n * onMouseLeave={handleMouseLeave}\n * onTouchStart={handleTouchStart}\n * onTouchEnd={handleTouchEnd}\n * onKeyDown={handleKeyDown}\n * onKeyUp={handleKeyUp}\n * />\n * ```\n */\nexport function useBooleanInteraction({\n value,\n mode,\n onValueChange,\n disabled = false,\n onMouseDown: userOnMouseDown,\n onMouseUp: userOnMouseUp,\n onTouchStart: userOnTouchStart,\n onTouchEnd: userOnTouchEnd,\n onKeyDown: userOnKeyDown,\n onKeyUp: userOnKeyUp,\n}: UseBooleanInteractionProps): UseBooleanInteractionResult {\n const controllerRef = useRef<BooleanInteractionController | null>(null);\n const buttonElementRef = useRef<HTMLElement | SVGSVGElement | null>(null);\n const touchIsInsideRef = useRef<boolean>(false);\n const isGlobalPointerDownRef = useRef<boolean>(false);\n\n if (!controllerRef.current) {\n controllerRef.current = new BooleanInteractionController({\n value,\n mode,\n onValueChange,\n disabled,\n });\n }\n\n useEffect(() => {\n controllerRef.current?.updateConfig({\n value,\n mode,\n onValueChange,\n disabled,\n });\n }, [value, mode, onValueChange, disabled]);\n\n // Global pointer down handler - tracks when ANY pointer is pressed anywhere\n // This enables drag-in behavior: pressing outside and dragging into the button\n const handleGlobalMouseDown = useCallback((e: MouseEvent) => {\n // Only track primary button (left click)\n if (e.button === 0) {\n isGlobalPointerDownRef.current = true;\n controllerRef.current?.handleGlobalPointerDown(false);\n }\n }, []);\n\n const handleGlobalTouchStart = useCallback((_e: TouchEvent) => {\n isGlobalPointerDownRef.current = true;\n controllerRef.current?.handleGlobalPointerDown(false);\n }, []);\n\n // Global pointer up handler - resets global pointer state and handles release\n const handleGlobalMouseUp = useCallback(() => {\n isGlobalPointerDownRef.current = false;\n controllerRef.current?.handleGlobalPointerUp();\n }, []);\n\n // Global touchend handler - resets global pointer state and handles release\n const handleGlobalTouchEnd = useCallback(() => {\n isGlobalPointerDownRef.current = false;\n touchIsInsideRef.current = false;\n controllerRef.current?.handleGlobalPointerUp();\n }, []);\n\n // Global touchmove handler - tracks touch position to detect enter/leave for touch devices\n // Touch events don't fire mouseenter/mouseleave, so we need to manually track when touch\n // crosses button boundaries\n const handleGlobalTouchMove = useCallback((e: TouchEvent) => {\n if (!controllerRef.current || !buttonElementRef.current) return;\n\n // Only track if global pointer is down\n if (!isGlobalPointerDownRef.current) return;\n\n const controller = controllerRef.current;\n const buttonElement = buttonElementRef.current;\n\n // Get the first touch point\n if (e.touches.length === 0) return;\n const touch = e.touches[0];\n\n // Find element at touch point\n const elementAtPoint = document.elementFromPoint(touch.clientX, touch.clientY);\n\n // Check if touch is over this button (or any child of it)\n const isInside = elementAtPoint === buttonElement || buttonElement.contains(elementAtPoint);\n\n // Only react if state changed\n if (isInside !== touchIsInsideRef.current) {\n touchIsInsideRef.current = isInside;\n\n if (isInside) {\n // Touch entered the button\n controller.handleMouseEnter();\n } else {\n // Touch left the button\n controller.handleMouseLeave();\n }\n }\n }, []);\n\n // Attach global pointer listeners for drag-in/drag-out behavior\n // This enables buttons to respond even when press starts outside the button\n useEffect(() => {\n if (!disabled) {\n window.addEventListener(\"mousedown\", handleGlobalMouseDown);\n window.addEventListener(\"mouseup\", handleGlobalMouseUp);\n window.addEventListener(\"touchstart\", handleGlobalTouchStart, { passive: true });\n window.addEventListener(\"touchmove\", handleGlobalTouchMove, { passive: false });\n window.addEventListener(\"touchend\", handleGlobalTouchEnd);\n return () => {\n window.removeEventListener(\"mousedown\", handleGlobalMouseDown);\n window.removeEventListener(\"mouseup\", handleGlobalMouseUp);\n window.removeEventListener(\"touchstart\", handleGlobalTouchStart);\n window.removeEventListener(\"touchmove\", handleGlobalTouchMove);\n window.removeEventListener(\"touchend\", handleGlobalTouchEnd);\n };\n }\n return undefined;\n }, [\n disabled,\n handleGlobalMouseDown,\n handleGlobalMouseUp,\n handleGlobalTouchStart,\n handleGlobalTouchMove,\n handleGlobalTouchEnd,\n ]);\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n // Call user handler first\n userOnMouseDown?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n // Store button element reference for potential touch tracking\n buttonElementRef.current = e.currentTarget as HTMLElement;\n isGlobalPointerDownRef.current = true;\n controllerRef.current?.handleMouseDown(e.defaultPrevented);\n }\n },\n [userOnMouseDown]\n );\n\n const handleMouseUp = useCallback(\n (e: React.MouseEvent) => {\n // Call user handler first\n userOnMouseUp?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n isGlobalPointerDownRef.current = false;\n controllerRef.current?.handleMouseUp(e.defaultPrevented);\n }\n },\n [userOnMouseUp]\n );\n\n const handleMouseEnter = useCallback((_e: React.MouseEvent) => {\n controllerRef.current?.handleMouseEnter();\n }, []);\n\n const handleMouseLeave = useCallback((_e: React.MouseEvent) => {\n controllerRef.current?.handleMouseLeave();\n }, []);\n\n // Set button element reference (called when component mounts or element changes)\n const setButtonElement = useCallback((element: HTMLElement | SVGSVGElement | null) => {\n buttonElementRef.current = element;\n }, []);\n\n const handleTouchStart = useCallback(\n (e: React.TouchEvent) => {\n // Call user handler first\n userOnTouchStart?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n // Prevent default to avoid mouse event emulation and double-firing\n e.preventDefault();\n // Store button element reference for touch tracking\n buttonElementRef.current = e.currentTarget as HTMLElement;\n touchIsInsideRef.current = true; // Touch started on this button\n controllerRef.current?.handleMouseDown(false);\n }\n },\n [userOnTouchStart]\n );\n\n const handleTouchEnd = useCallback(\n (e: React.TouchEvent) => {\n // Call user handler first\n userOnTouchEnd?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n // Prevent default to avoid mouse event emulation and double-firing\n e.preventDefault();\n controllerRef.current?.handleMouseUp(false);\n touchIsInsideRef.current = false;\n }\n },\n [userOnTouchEnd]\n );\n\n // Touch move handler - prevents default scrolling and allows touch tracking\n const handleTouchMove = useCallback((e: React.TouchEvent) => {\n // Prevent default scrolling during touch drag\n e.preventDefault();\n }, []);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n // Call user handler first\n userOnKeyDown?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n const handled = controllerRef.current?.handleKeyDown(e.key);\n if (handled) {\n e.preventDefault();\n }\n }\n },\n [userOnKeyDown]\n );\n\n const handleKeyUp = useCallback(\n (e: React.KeyboardEvent) => {\n // Call user handler first\n userOnKeyUp?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n const handled = controllerRef.current?.handleKeyUp(e.key);\n if (handled) {\n e.preventDefault();\n }\n }\n },\n [userOnKeyUp]\n );\n\n return {\n handleMouseDown,\n handleMouseUp,\n handleMouseEnter,\n handleMouseLeave,\n handleTouchStart,\n handleTouchEnd,\n handleTouchMove,\n setButtonElement,\n handleKeyDown,\n handleKeyUp,\n };\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { useMemo } from \"react\";\nimport { BooleanParameter, AudioParameterFactory, MidiResolution } from \"@cutoff/audio-ui-core\";\n\nexport interface UseBooleanParameterResolutionProps {\n /** The parameter definition (Strict mode) */\n parameter?: BooleanParameter;\n /** Identifier for the parameter (used in Ad-Hoc mode) */\n paramId?: string;\n /** Label for the parameter (Ad-Hoc mode) */\n label?: string;\n /** Whether the button should latch (toggle between states) or momentary (only active while pressed) (Ad-Hoc mode) */\n latch?: boolean;\n /** MIDI resolution in bits (Ad-Hoc mode)\n * @default 7\n */\n midiResolution?: MidiResolution;\n}\n\nexport interface UseBooleanParameterResolutionResult {\n /** The resolved BooleanParameter (derived from props or created from ad-hoc props) */\n derivedParameter: BooleanParameter;\n}\n\n/**\n * Hook to resolve a BooleanParameter from props.\n *\n * Supports two modes:\n * 1. Strict Mode (Parameter only): Model provided via parameter prop.\n * 2. Ad-Hoc Mode (Props only): Model created from individual props (label, latch, etc.).\n *\n * When `parameter` is provided, it takes precedence over ad-hoc props.\n *\n * @param props - Configuration object for parameter resolution\n * @param props.parameter - The parameter definition (Strict mode). When provided, takes precedence over ad-hoc props.\n * @param props.paramId - Identifier for the parameter (used in Ad-Hoc mode)\n * @param props.label - Label for the parameter (Ad-Hoc mode)\n * @param props.latch - Whether the button should latch (toggle) or be momentary (Ad-Hoc mode)\n * @param props.midiResolution - MIDI resolution in bits (Ad-Hoc mode, default: 7)\n * @returns Object containing the resolved BooleanParameter\n *\n * @example\n * ```tsx\n * // Strict mode: use provided parameter\n * const { derivedParameter } = useBooleanParameterResolution({\n * parameter: powerParam\n * });\n *\n * // Ad-Hoc mode: create from props\n * const { derivedParameter } = useBooleanParameterResolution({\n * paramId: \"power\",\n * label: \"Power\",\n * latch: true\n * });\n * ```\n */\nexport function useBooleanParameterResolution({\n parameter,\n paramId,\n label,\n latch,\n midiResolution = 7,\n}: UseBooleanParameterResolutionProps): UseBooleanParameterResolutionResult {\n return useMemo(() => {\n let derivedParameter: BooleanParameter;\n\n if (parameter) {\n // Strict mode: use provided parameter\n derivedParameter = parameter;\n } else {\n // Ad-hoc mode: create parameter from props\n derivedParameter = AudioParameterFactory.createSwitch(label || \"\", latch ? \"toggle\" : \"momentary\");\n // Override id if paramId is provided (including empty string)\n if (paramId !== undefined) {\n derivedParameter = {\n ...derivedParameter,\n id: paramId,\n };\n }\n // Override midiResolution if provided\n if (midiResolution !== undefined) {\n derivedParameter = {\n ...derivedParameter,\n midiResolution,\n };\n }\n }\n\n return {\n derivedParameter,\n };\n }, [parameter, paramId, label, latch, midiResolution]);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo, useCallback } from \"react\";\nimport classNames from \"classnames\";\nimport { CLASSNAMES } from \"@cutoff/audio-ui-core\";\nimport { AdaptiveBoxProps, AdaptiveBoxLogicalSizeProps, BooleanControlProps, ControlComponent } from \"@/types\";\nimport AdaptiveBox from \"../AdaptiveBox\";\nimport { useAudioParameter } from \"@/hooks/useAudioParameter\";\nimport { useBooleanInteraction } from \"@/hooks/useBooleanInteraction\";\nimport { useBooleanParameterResolution } from \"@/hooks/useBooleanParameterResolution\";\n\nexport type BooleanControlComponentProps<P extends object = Record<string, unknown>> =\n // Base Control Props (includes all BooleanControlProps)\n BooleanControlProps &\n // Layout props that configure AdaptiveBox behavior\n AdaptiveBoxProps &\n // Logical size props that override view component defaults\n AdaptiveBoxLogicalSizeProps & {\n /**\n * The Visualization Component.\n * Must adhere to ControlComponent contract.\n */\n view: ControlComponent<P>;\n\n /**\n * Props specific to the Visualization Component.\n */\n viewProps: P;\n\n /**\n * Content overlay (HTML) rendered over the SVG (e.g. text value, icons).\n * Rendered via AdaptiveBox.HtmlOverlay to avoid foreignObject issues.\n */\n htmlOverlay?: React.ReactNode;\n };\n\n/**\n * A Generic Boolean Control that connects a Data Model (BooleanParameter)\n * to a Visualization View (ControlComponent).\n *\n * This component handles parameter resolution, value management, interaction handling,\n * and layout management for boolean controls (Button, Toggle, etc.).\n *\n * Supports two modes of operation:\n * 1. Strict Mode (Parameter only): Model provided via parameter prop.\n * 2. Ad-Hoc Mode (Props only): Model created from individual props (label, latch, etc.).\n *\n * **Interaction Modes:**\n * - **Editable Control**: When `onChange` is provided, the control is editable and responds to\n * all interaction methods (mouse, touch, keyboard) to change the value. The full interaction\n * system handles complex behaviors like drag-in/drag-out for momentary buttons.\n * - **Clickable View**: When only `onClick` is provided (no `onChange`), the control is a\n * clickable view that triggers the onClick handler but does not change its value. Touch events\n * are handled to ensure onClick works on touch devices.\n * - **Both**: When both `onChange` and `onClick` are provided, the control is editable and\n * also triggers onClick for mouse clicks (onChange handles touch events for value changes).\n *\n * @param props - Component props including parameter configuration, view component, and layout options\n * @returns Rendered boolean control component\n *\n * @example\n * ```tsx\n * // Editable control\n * <BooleanControl\n * value={isOn}\n * onChange={(e) => setIsOn(e.value)}\n * view={ButtonView}\n * viewProps={{ color: \"blue\", roundness: 0.3 }}\n * />\n *\n * // Clickable view (non-editable)\n * <BooleanControl\n * value={false}\n * onClick={() => handleClick()}\n * view={ButtonView}\n * viewProps={{ color: \"gray\" }}\n * />\n * ```\n */\nexport function BooleanControl<P extends object = Record<string, unknown>>(props: BooleanControlComponentProps<P>) {\n const {\n view: View,\n viewProps,\n htmlOverlay,\n value,\n onChange,\n label,\n paramId,\n parameter,\n latch,\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n viewBoxWidthUnits,\n viewBoxHeightUnits,\n labelHeightUnits,\n className,\n style,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n midiResolution,\n } = props;\n\n const { derivedParameter } = useBooleanParameterResolution({\n parameter,\n paramId,\n label,\n latch,\n midiResolution,\n });\n\n const { normalizedValue, converter } = useAudioParameter(value, onChange, derivedParameter);\n\n const fireChange = useCallback(\n (newValue: boolean) => {\n if (!onChange) return;\n const normalized = converter.normalize(newValue);\n const midi = converter.toMidi(newValue);\n onChange({\n value: newValue,\n normalizedValue: normalized,\n midiValue: midi,\n parameter: derivedParameter,\n });\n },\n [onChange, converter, derivedParameter]\n );\n\n const {\n handleMouseDown,\n handleMouseUp,\n handleMouseEnter,\n handleMouseLeave,\n handleTouchStart,\n handleTouchEnd,\n handleTouchMove,\n setButtonElement,\n handleKeyDown,\n handleKeyUp,\n } = useBooleanInteraction({\n value,\n mode: derivedParameter.mode ?? (latch ? \"toggle\" : \"momentary\"),\n onValueChange: fireChange,\n disabled: !onChange,\n onMouseDown,\n onMouseUp,\n onKeyDown: undefined, // BooleanControl doesn't have onKeyDown prop, only uses hook handler\n onKeyUp: undefined, // BooleanControl doesn't have onKeyUp prop, only uses hook handler\n });\n\n // Handle onClick for touch events when onChange is not provided\n // This ensures onClick works on touch devices even when the control is not editable\n // When onChange is provided, the interaction system handles touch events and we don't need this\n const handleTouchEndForClick = useCallback(\n (e: React.TouchEvent<SVGSVGElement>) => {\n if (!onChange && onClick) {\n // Prevent default to avoid mouse event emulation\n e.preventDefault();\n // Create a synthetic mouse event for onClick\n const touch = e.changedTouches[0];\n if (touch) {\n const syntheticEvent = {\n ...e,\n clientX: touch.clientX,\n clientY: touch.clientY,\n currentTarget: e.currentTarget,\n type: \"click\",\n } as unknown as React.MouseEvent<SVGSVGElement>;\n onClick(syntheticEvent);\n }\n }\n },\n [onChange, onClick]\n );\n\n const handleTouchStartForClick = useCallback(\n (e: React.TouchEvent<SVGSVGElement>) => {\n if (!onChange && onClick) {\n // Prevent default to avoid mouse event emulation\n e.preventDefault();\n }\n },\n [onChange, onClick]\n );\n\n const effectiveLabel = label ?? derivedParameter.name;\n\n const componentClassNames = useMemo(() => {\n return classNames(className, CLASSNAMES.root, CLASSNAMES.container);\n }, [className]);\n\n const svgClassNames = useMemo(() => {\n return onChange || onClick ? CLASSNAMES.highlight : \"\";\n }, [onChange, onClick]);\n\n // Add clickable cursor when interactive (onChange or onClick)\n // Uses CSS variable for customizable cursor type\n // Add touchAction: \"none\" to prevent default touch behaviors (scrolling/zooming)\n const svgStyle = useMemo(\n () => ({\n ...(onClick || onChange ? { cursor: \"var(--audioui-cursor-clickable)\" as const } : {}),\n touchAction: \"none\" as const,\n }),\n [onClick, onChange]\n );\n\n // Wrap handlers to set element reference\n const handleMouseDownWithRef = useCallback(\n (e: React.MouseEvent<SVGSVGElement>) => {\n setButtonElement(e.currentTarget);\n handleMouseDown(e);\n },\n [setButtonElement, handleMouseDown]\n );\n\n const handleTouchStartWithRef = useCallback(\n (e: React.TouchEvent<SVGSVGElement>) => {\n setButtonElement(e.currentTarget);\n handleTouchStart(e);\n },\n [setButtonElement, handleTouchStart]\n );\n\n return (\n <AdaptiveBox\n displayMode={displayMode ?? \"scaleToFit\"}\n labelMode={labelMode}\n labelOverflow={labelOverflow}\n className={componentClassNames}\n style={style}\n labelHeightUnits={labelHeightUnits ?? View.labelHeightUnits ?? 20}\n viewBoxWidth={viewBoxWidthUnits ?? View.viewBox.width}\n viewBoxHeight={viewBoxHeightUnits ?? View.viewBox.height}\n >\n <AdaptiveBox.Svg\n className={svgClassNames}\n style={svgStyle}\n onClick={onClick}\n onMouseDown={onChange ? handleMouseDownWithRef : undefined}\n onMouseUp={onChange ? handleMouseUp : undefined}\n onMouseEnter={(e) => {\n if (onChange) {\n handleMouseEnter(e);\n }\n onMouseEnter?.(e);\n }}\n onMouseLeave={(e) => {\n if (onChange) {\n handleMouseLeave(e);\n }\n onMouseLeave?.(e);\n }}\n onTouchStart={onChange ? handleTouchStartWithRef : onClick ? handleTouchStartForClick : undefined}\n onTouchEnd={onChange ? handleTouchEnd : onClick ? handleTouchEndForClick : undefined}\n onTouchMove={onChange ? handleTouchMove : undefined}\n onKeyDown={onChange ? handleKeyDown : undefined}\n onKeyUp={onChange ? handleKeyUp : undefined}\n tabIndex={onChange || onClick ? 0 : undefined}\n role=\"button\"\n aria-pressed={value}\n aria-label={effectiveLabel}\n >\n <View normalizedValue={normalizedValue} {...viewProps} />\n </AdaptiveBox.Svg>\n {htmlOverlay && <AdaptiveBox.HtmlOverlay>{htmlOverlay}</AdaptiveBox.HtmlOverlay>}\n {effectiveLabel && (\n <AdaptiveBox.Label position={labelPosition} align={labelAlign ?? \"center\"}>\n {effectiveLabel}\n </AdaptiveBox.Label>\n )}\n </AdaptiveBox>\n );\n}\n\nexport default React.memo(BooleanControl) as typeof BooleanControl;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { useMemo } from \"react\";\nimport { getSizeClassForComponent, getSizeStyleForComponent, SizeType } from \"@cutoff/audio-ui-core\";\n\n/**\n * Hook that encapsulates adaptive sizing logic for components implementing AdaptiveSizeProps.\n *\n * Determines sizing behavior based on `adaptiveSize` and `size` props:\n * - When `adaptiveSize` is true, the component stretches to fill its container (no size constraints)\n * - When `adaptiveSize` is false, size classes and styles are applied based on the `size` prop\n *\n * @param adaptiveSize - Whether the component should stretch to fill its container\n * @param size - The size value (ignored when adaptiveSize is true)\n * @param componentType - The type of component ('knob', 'button', 'keys', or 'slider')\n * @param orientation - Optional orientation for slider components ('vertical' or 'horizontal')\n * @returns Object containing `sizeClassName` and `sizeStyle`\n *\n * @example\n * ```tsx\n * const { sizeClassName, sizeStyle } = useAdaptiveSize(\n * adaptiveSize,\n * size,\n * \"knob\"\n * );\n * ```\n */\nexport function useAdaptiveSize(\n adaptiveSize: boolean = false,\n size: SizeType = \"normal\",\n componentType: \"knob\" | \"button\" | \"keys\" | \"slider\",\n orientation?: \"vertical\" | \"horizontal\"\n) {\n return useMemo(() => {\n // When adaptiveSize is true, component stretches to fill container (no size constraints)\n // When false, apply size classes and styles based on the size prop\n const sizeClassName = adaptiveSize\n ? undefined\n : orientation !== undefined\n ? getSizeClassForComponent(componentType, size, orientation)\n : getSizeClassForComponent(componentType, size);\n\n const sizeStyle = adaptiveSize\n ? undefined\n : orientation !== undefined\n ? getSizeStyleForComponent(componentType, size, orientation)\n : getSizeStyleForComponent(componentType, size);\n\n return {\n sizeClassName,\n sizeStyle,\n };\n }, [adaptiveSize, size, componentType, orientation]);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React, { useMemo } from \"react\";\nimport { clampNormalized, generateTransparencyVariant } from \"@cutoff/audio-ui-core\";\n\n/**\n * Options for useThemableProps hook\n */\nexport interface UseThemablePropsOptions {\n /** Component primary color - any valid CSS color value */\n color?: string;\n /** Roundness for component corners/caps (normalized 0.0-1.0) */\n roundness?: number;\n /** User-provided style object (takes precedence over generated CSS variables) */\n style?: React.CSSProperties;\n}\n\n/**\n * Return value from useThemableProps hook\n */\nexport interface UseThemablePropsResult {\n /** Merged style object with CSS variables and user styles */\n style: React.CSSProperties;\n /** Clamped roundness value (undefined if not provided) */\n clampedRoundness?: number;\n}\n\n/**\n * Hook that encapsulates CSS variable computation for themable components.\n *\n * This hook handles:\n * - Clamping normalized roundness values to valid ranges\n * - Generating CSS variables from themable props (color, roundness)\n * - Automatically inferring linecap from roundness (0.0 = square, >0.0 = round)\n * - Computing color variants (primary-50, primary-20) when color is provided\n * - Merging with user-provided style (user style takes precedence)\n *\n * **Roundness and Linecap Relationship:**\n * The roundness attribute serves as a single source of truth for the overall \"feeling\" of the UI.\n * For arc-based components (like knobs), the roundness value automatically determines the stroke-linecap:\n * - `roundness = 0.0` → `stroke-linecap: square` (sharp, technical look)\n * - `roundness > 0.0` → `stroke-linecap: round` (smooth, rounded look)\n *\n * This hook sets both `--audioui-roundness-base` and `--audioui-linecap-base` CSS variables\n * when a roundness prop is provided, ensuring consistent visual styling across all components.\n *\n * The hook returns a style object that can be spread directly onto component elements,\n * along with clamped values that can be used for viewProps.\n * CSS variables are set on the element, allowing child components to read them via `var(--audioui-*)`.\n *\n * @param options - Configuration object with color, roundness, and style\n * @returns Object containing style and clamped roundness value\n *\n * @example\n * ```tsx\n * function MyComponent({ color, roundness, style }: ThemableProps & BaseProps) {\n * const { style: themableStyle } = useThemableProps({\n * color,\n * roundness,\n * style,\n * });\n *\n * return (\n * <div style={themableStyle}>\n * {/* Child components can read CSS variables via var(--audioui-primary-color) *\\/}\n * <ViewComponent roundness={roundness ?? \"var(--audioui-roundness-button)\"} />\n * </div>\n * );\n * }\n * ```\n */\nexport function useThemableProps({ color, roundness, style }: UseThemablePropsOptions): UseThemablePropsResult {\n return useMemo(() => {\n const vars: Record<string, string> = {};\n let clampedRoundness: number | undefined;\n\n // Handle roundness - clamp and store for return\n // Automatically infer linecap from roundness: this ensures arc-based components\n // (like knobs) have consistent visual styling. The linecap is a derived property\n // that should not be set independently - it's always inferred from roundness.\n if (roundness !== undefined) {\n clampedRoundness = clampNormalized(roundness);\n vars[\"--audioui-roundness-base\"] = clampedRoundness.toString();\n // Automatically infer linecap: 0.0 = square (sharp), >0.0 = round (smooth)\n vars[\"--audioui-linecap-base\"] = clampedRoundness === 0 ? \"square\" : \"round\";\n }\n\n // Handle color and generate variants\n if (color !== undefined) {\n vars[\"--audioui-primary-color\"] = color;\n // Compute variants for this component instance\n vars[\"--audioui-primary-50\"] = generateTransparencyVariant(color, 50);\n vars[\"--audioui-primary-20\"] = generateTransparencyVariant(color, 20);\n }\n\n // Merge CSS variables with user style (user style takes precedence)\n return {\n style: { ...vars, ...style } as React.CSSProperties,\n clampedRoundness,\n };\n }, [color, roundness, style]);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, BooleanControlProps, ThemableProps } from \"@/types\";\nimport ButtonView from \"./ButtonView\";\nimport BooleanControl from \"@/primitives/controls/BooleanControl\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { useThemableProps } from \"@/hooks/useThemableProps\";\n\n/**\n * Props for the Button component (built-in control with theming support)\n */\nexport type ButtonProps = BooleanControlProps & AdaptiveSizeProps & AdaptiveBoxProps & ThemableProps;\n\n/**\n * A button component for audio applications.\n *\n * Supports both toggle (latch) and momentary modes, with proper handling of\n * global mouse events for momentary buttons to ensure reliable release behavior\n * even when the mouse is dragged outside the button before release.\n *\n * Supports two modes of operation:\n * 1. Strict Mode (Parameter only): Model provided via parameter prop.\n * 2. Ad-Hoc Mode (Props only): Model created from individual props (label, latch, etc.).\n *\n * @param props - Component props\n * @returns Rendered Button component\n *\n * @example\n * ```tsx\n * // Ad-Hoc Mode - Toggle button\n * <Button\n * label=\"Power\"\n * latch={true}\n * value={isOn}\n * onChange={(e) => setIsOn(e.value)}\n * />\n *\n * // Ad-Hoc Mode - Momentary button\n * <Button\n * label=\"Record\"\n * latch={false}\n * value={isRecording}\n * onChange={(e) => setIsRecording(e.value)}\n * />\n *\n * // Strict Mode with parameter\n * <Button\n * parameter={powerParam}\n * value={isOn}\n * onChange={(e) => setIsOn(e.value)}\n * />\n * ```\n */\nfunction Button({\n latch = false,\n value = false,\n onChange,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n labelHeightUnits,\n parameter,\n paramId,\n midiResolution,\n color,\n roundness,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: ButtonProps) {\n const { style: themableStyle } = useThemableProps({\n color,\n roundness,\n style,\n });\n const { sizeClassName, sizeStyle: adaptiveSizeStyle } = useAdaptiveSize(adaptiveSize, size, \"button\");\n\n return (\n <BooleanControl\n value={value}\n onChange={onChange}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n labelOverflow={labelOverflow}\n labelHeightUnits={labelHeightUnits}\n className={classNames(sizeClassName, className)}\n style={{ ...adaptiveSizeStyle, ...themableStyle }}\n parameter={parameter}\n paramId={paramId}\n latch={latch}\n midiResolution={midiResolution}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n view={ButtonView}\n viewProps={{\n threshold: 0.5,\n roundness: roundness ?? \"var(--audioui-roundness-button)\",\n color: color ?? \"var(--audioui-primary-color)\",\n }}\n />\n );\n}\n\nexport default React.memo(Button);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { useMemo } from \"react\";\nimport { calculateArcAngles, ArcAngleResult } from \"@cutoff/audio-ui-core\";\n\nexport type UseArcAngleResult = ArcAngleResult;\n\n/**\n * Hook to calculate arc angles for rotary controls (ValueRing, RotaryImage components, or any other circular control).\n *\n * Calculates the angular range based on openness and converts a normalized value (0-1)\n * to an angle within that range.\n *\n * The angle system:\n * - 0 degrees is at 3 o'clock, increasing clockwise\n * - Standard knob (90° openness) goes from ~225° (7:30) to ~495° (4:30)\n * - 360° corresponds to UP (12 o'clock)\n *\n * @param normalizedValue Normalized value between 0 and 1\n * @param openness Openness of the arc in degrees (0-360, default 90)\n * @param rotation Rotation angle offset in degrees (default 0)\n * @param bipolar Whether to start the value arc from the center (12 o'clock) instead of the start angle (default false)\n * @param positions Optional number of discrete positions. When defined, the value will snap to the nearest position. Defaults to undefined (continuous mode).\n * @returns Calculated angles and normalized values\n */\nexport function useArcAngle(\n normalizedValue: number,\n openness: number = 90,\n rotation: number = 0,\n bipolar: boolean = false,\n positions?: number\n): UseArcAngleResult {\n return useMemo(() => {\n return calculateArcAngles(normalizedValue, openness, rotation, bipolar, positions);\n }, [normalizedValue, openness, rotation, bipolar, positions]);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, useMemo } from \"react\";\nimport { calculateArcPath } from \"@cutoff/audio-ui-core\";\n\nexport type RingArcProps = {\n startAngle: number;\n endAngle: number;\n style: CSSProperties | undefined;\n cx: number;\n cy: number;\n radius: number;\n thickness: number;\n strokeLinecap: \"round\" | \"square\" | \"butt\" | string;\n};\n\n/**\n * Helper component to render either a circle or path based on whether it's a full circle.\n *\n * @internal\n */\nfunction RingArc({ startAngle, endAngle, style, cx, cy, radius, thickness, strokeLinecap }: RingArcProps) {\n // Determine if it's a full circle based on angular difference (>= 360 degrees)\n const isFullCircle = Math.abs(endAngle - startAngle) >= 360;\n\n const path = useMemo(() => {\n if (isFullCircle) return undefined;\n\n // Use default \"counter-clockwise\" (End -> Start) for standard static shapes.\n return calculateArcPath(cx, cy, startAngle, endAngle, radius, \"counter-clockwise\");\n }, [isFullCircle, cx, cy, startAngle, endAngle, radius]);\n\n if (isFullCircle) {\n return (\n <circle\n cx={cx}\n cy={cy}\n r={radius}\n fill=\"none\"\n strokeWidth={thickness}\n style={{\n ...style,\n // @ts-expect-error - strokeLinecap accepts CSS variables (strings) but React types are strict\n strokeLinecap: strokeLinecap,\n }}\n />\n );\n }\n\n if (!path) return null;\n\n return (\n <path\n d={path}\n fill=\"none\"\n strokeWidth={thickness}\n style={{\n ...style,\n // @ts-expect-error - strokeLinecap accepts CSS variables (strings) but React types are strict\n strokeLinecap: strokeLinecap,\n }}\n />\n );\n}\n\nexport default React.memo(RingArc);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, useMemo } from \"react\";\nimport { useArcAngle } from \"@/hooks/useArcAngle\";\nimport RingArc from \"./RingArc\";\n\nexport type ValueRingProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Radius of the ring */\n radius: number;\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Whether to start the arc from center (bipolar mode) */\n bipolar?: boolean;\n /** Thickness of the knob's stroke (1-20 pixels) */\n thickness?: number;\n /** Roundness for stroke linecap (false = square, true = round, or CSS variable string) */\n roundness?: boolean | string;\n /** Openness of the ring in degrees (value between 0-360º; 0º: closed; 90º: 3/4 open; 180º: half-circle;) */\n openness?: number;\n /** Optional rotation angle offset in degrees (default 0) */\n rotation?: number;\n /** Optional number of discrete positions. When defined, the value will snap to the nearest position. */\n positions?: number;\n /** Optional style overrides for the foreground (value) arc */\n fgArcStyle?: CSSProperties;\n /** Optional style overrides for the background arc */\n bgArcStyle?: CSSProperties;\n};\n\n/**\n * A reusable SVG fragment that renders a ring (arc) used in circular controls like knobs.\n * It handles the calculation of SVG paths for both the background track and the foreground value indicator.\n *\n * This component is designed to be used inside an <svg> element.\n */\nfunction ValueRing({\n cx,\n cy,\n radius,\n normalizedValue,\n bipolar = false,\n thickness = 6,\n roundness,\n openness = 90,\n rotation = 0,\n positions,\n fgArcStyle,\n bgArcStyle,\n}: ValueRingProps) {\n // Calculate arc angles using shared hook (rotation computation factored into hook)\n const { startAngle, endAngle, valueToAngle, valueStartAngle } = useArcAngle(\n normalizedValue,\n openness,\n rotation,\n bipolar,\n positions\n );\n\n const strokeLinecap = useMemo(() => {\n if (typeof roundness === \"string\") {\n return roundness;\n }\n return roundness ? \"round\" : \"square\";\n }, [roundness]);\n\n // Calculate actual radius to make stroke expand inward from the outer edge\n // SVG strokes are centered on the path by default, so a stroke of thickness N\n // extends N/2 pixels on each side of the path. To make the stroke grow inward\n // (keeping the outer edge at the specified radius), we subtract half the thickness.\n // This ensures the visual appearance matches the design intent: outer edge stays fixed,\n // inner edge moves inward as thickness increases.\n const actualRadius = useMemo(() => {\n return Math.max(0, radius - thickness / 2);\n }, [radius, thickness]);\n\n // Check if we can use the optimized RevealingPath\n // We only use RevealingPath for unipolar, non-full-circle arcs\n // (Full circles require complex path construction to work with stroke-dasharray)\n // PERFORMANCE NOTE: We explicitly AVOID the RevealingPath optimization (stroke-dasharray)\n // here because in high-concurrency scenarios (hundreds of knobs), the React overhead\n // of the extra component layer and DOM attribute updates proved slower than\n // simply recalculating the geometric path in JS.\n\n return (\n <>\n {/* Background Arc - Always the same regardless of optimization */}\n <RingArc\n startAngle={startAngle}\n endAngle={endAngle}\n style={bgArcStyle}\n cx={cx}\n cy={cy}\n radius={actualRadius}\n thickness={thickness}\n strokeLinecap={strokeLinecap}\n />\n\n {/* Foreground Arc */}\n <RingArc\n startAngle={valueStartAngle}\n endAngle={valueToAngle}\n style={fgArcStyle}\n cx={cx}\n cy={cy}\n radius={actualRadius}\n thickness={thickness}\n strokeLinecap={strokeLinecap}\n />\n </>\n );\n}\n\nexport default React.memo(ValueRing);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties } from \"react\";\n\nexport type RadialImageProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Radius the content should fit within */\n radius: number;\n /** Optional image URL to display */\n imageHref?: string;\n /** Optional SVG content to display (e.g., icon components) */\n children?: React.ReactNode;\n /** Optional SVG transform attribute */\n transform?: string;\n /** Additional CSS class name */\n className?: string;\n /**\n * Inline styles.\n * Supports `color` property for icon theming - icons using currentColor will inherit this value.\n */\n style?: CSSProperties;\n};\n\n/**\n * A primitive component that displays static content at radial coordinates.\n * The content is sized to fit within the specified radius and centered at (cx, cy).\n *\n * This component can display an image (via imageHref) or arbitrary SVG content (via children).\n * It is designed to work alongside ValueRing and RotaryImage components in composing knobs.\n *\n * Useful for displaying icons or static images within knob compositions.\n */\nfunction RadialImage({ cx, cy, radius, imageHref, children, transform, className, style }: RadialImageProps) {\n return (\n <g className={className} style={style} transform={transform}>\n {imageHref && (\n <image\n href={imageHref}\n x={cx - radius}\n y={cy - radius}\n width={radius * 2}\n height={radius * 2}\n preserveAspectRatio=\"xMidYMid meet\"\n />\n )}\n {children && (\n <svg\n x={cx - radius}\n y={cy - radius}\n width={radius * 2}\n height={radius * 2}\n viewBox={`0 0 ${radius * 2} ${radius * 2}`}\n style={{ overflow: \"visible\", ...(style?.color ? { color: style.color } : {}) }}\n >\n {children}\n </svg>\n )}\n </g>\n );\n}\n\nexport default React.memo(RadialImage);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties } from \"react\";\nimport { useArcAngle } from \"@/hooks/useArcAngle\";\nimport RadialImage from \"./RadialImage\";\n\nexport type RotaryImageProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Radius of the rotary control (used for bounds/image sizing) */\n radius: number;\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Whether to start the arc from center (bipolar mode) */\n bipolar?: boolean;\n /** Openness of the arc in degrees (default 90) */\n openness?: number;\n /** Optional image URL to display */\n imageHref?: string;\n /** Optional SVG content to rotate */\n children?: React.ReactNode;\n /** Optional rotation angle offset in degrees (default 0) */\n rotation?: number;\n /** Optional number of discrete positions. When defined, the value will snap to the nearest position. */\n positions?: number;\n /** Optional X coordinate for the center of rotation (defaults to cx) */\n pivotX?: number;\n /** Optional Y coordinate for the center of rotation (defaults to cy) */\n pivotY?: number;\n /** Additional CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n};\n\n/**\n * A primitive component that rotates its content based on a normalized value.\n * Designed to work with the same angle logic as ValueRing.tsx.\n *\n * This component wraps RadialImage and applies rotation based on the normalized value.\n * It can display an image (via imageHref) or arbitrary SVG content (via children).\n */\nfunction RotaryImage({\n cx,\n cy,\n radius,\n normalizedValue,\n bipolar = false,\n openness = 90,\n imageHref,\n children,\n rotation = 0,\n positions,\n pivotX,\n pivotY,\n className,\n style,\n}: RotaryImageProps) {\n // Calculate arc angles using shared hook (rotation computation factored into hook)\n const { valueToAngle } = useArcAngle(normalizedValue, openness, rotation, bipolar, positions);\n\n // Use explicit pivot point if provided, otherwise default to center (cx, cy)\n const rotateX = pivotX ?? cx;\n const rotateY = pivotY ?? cy;\n\n return (\n <RadialImage\n cx={cx}\n cy={cy}\n radius={radius}\n imageHref={imageHref}\n transform={`rotate(${valueToAngle}, ${rotateX}, ${rotateY})`}\n className={className}\n style={{ ...style, willChange: \"transform\" }}\n >\n {children}\n </RadialImage>\n );\n}\n\nexport default React.memo(RotaryImage);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport { ControlComponent, KnobVariant } from \"@/types\";\nimport { translateKnobRoundness, translateKnobThickness } from \"@cutoff/audio-ui-core\";\nimport { DEFAULT_ROUNDNESS } from \"@cutoff/audio-ui-core\";\nimport ValueRing from \"@/primitives/svg/ValueRing\";\nimport RotaryImage from \"@/primitives/svg/RotaryImage\";\nimport RadialImage from \"@/primitives/svg/RadialImage\";\n\n/**\n * Props for the KnobView component\n */\nexport type KnobViewProps = {\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Whether to start the arc from center (bipolar mode) */\n bipolar?: boolean;\n /** Visual variant of the knob */\n variant?: KnobVariant;\n /** Thickness of the knob's stroke (normalized 0.0-1.0, maps to 1-20) */\n thickness?: number;\n /** Roundness for stroke linecap (normalized 0.0-1.0, 0.0 = square, >0.0 = round, or CSS variable string) */\n roundness?: number | string;\n /** Openness of the ring in degrees (value between 0-360º; 0º: closed; 90º: 3/4 open; 180º: half-circle;) */\n openness?: number;\n /** Optional rotation angle offset in degrees */\n rotation?: number;\n /** Resolved color string */\n color?: string;\n /** Additional CSS class name */\n className?: string;\n /**\n * Whether to use RotaryImage (true) or RadialImage (false) for iconCap overlay.\n * When true, the icon rotates with the knob value; when false, the icon remains static.\n * Only applies when variant is \"iconCap\" and svgOverlay is provided.\n * @default false\n */\n svgOverlayRotary?: boolean;\n /**\n * SVG content to display as overlay in iconCap variant.\n * Typically an icon component (e.g., wave icons) that will be rendered at the center of the knob.\n * The icon inherits color via currentColor, so it will adapt to light/dark mode automatically.\n * Only used when variant is \"iconCap\".\n */\n svgOverlay?: React.ReactNode;\n};\n\n/**\n * Pure SVG presentation component for a knob.\n * Renders background and foreground arcs as the visual indicator.\n *\n * Center content (text, icons, images) is rendered via the `overlay` prop\n * on AdaptiveBox.Svg, which places HTML content OUTSIDE the SVG to avoid\n * Safari's foreignObject rendering bugs with container queries.\n *\n * @param normalizedValue - Value between 0 and 1\n * @param bipolar - Whether to start arc from center (default false)\n * @param variant - Visual variant of the knob (default \"abstract\")\n * @param thickness - Normalized thickness 0.0-1.0 (default: 0.4 for abstract/simplest, 0.2 for others; maps to 1-20)\n * @param roundness - Normalized roundness 0.0-1.0 (default 0.3, 0.0 = square, >0.0 = round)\n * @param openness - Openness of the ring in degrees (default 90)\n * @param rotation - Optional rotation angle offset in degrees (default 0)\n * @param color - Resolved color string\n * @param className - Optional CSS class\n * @param svgOverlayRotary - Whether to use RotaryImage (true) or RadialImage (false) for iconCap overlay (default false)\n * @param svgOverlay - SVG content to display as overlay in iconCap variant (typically an icon component)\n */\nfunction KnobView({\n normalizedValue,\n bipolar = false,\n variant = \"abstract\",\n thickness,\n roundness,\n openness = 90,\n rotation = 0,\n color: _color, // Prefixed with _ to indicate intentionally unused (kept for API compatibility)\n className,\n svgOverlayRotary = false,\n svgOverlay,\n}: KnobViewProps) {\n // Determine default thickness based on variant\n // abstract and simplest: 0.4 (8 units), others: 0.2 (4 units)\n const defaultThickness = variant === \"abstract\" || variant === \"simplest\" ? 0.4 : 0.2;\n const effectiveThickness = thickness ?? defaultThickness;\n\n // Translate normalized thickness to pixel range (1-20)\n const pixelThickness = useMemo(() => {\n return translateKnobThickness(effectiveThickness);\n }, [effectiveThickness]);\n\n // Translate normalized roundness to stroke-linecap value\n // When roundness is a CSS variable (from theme), use the corresponding linecap variable\n // which is automatically managed by the theme system based on the roundness value.\n // When roundness is a number, infer linecap: 0.0 = square, >0.0 = round\n const strokeLinecap = useMemo(() => {\n if (typeof roundness === \"string\") {\n // If it's a CSS variable for roundness, use the corresponding linecap variable\n // The linecap variable is automatically set by useThemableProps or setThemeRoundness\n // based on whether roundness is 0.0 (square) or >0.0 (round)\n if (roundness === \"var(--audioui-roundness-knob)\") {\n return \"var(--audioui-linecap-knob)\";\n }\n return \"round\"; // Fallback for other CSS variable strings\n }\n // For numeric roundness, infer linecap: 0 = square, >0 = round\n return translateKnobRoundness(roundness ?? DEFAULT_ROUNDNESS) !== 0 ? \"round\" : \"square\";\n }, [roundness]);\n\n // Reusable ValueRing element for value indication\n // Note: Not memoized since normalizedValue changes frequently during interactions\n // Use CSS variables for colors - CSS handles variant generation via color-mix\n const valueRing = (\n <ValueRing\n cx={50}\n cy={50}\n radius={50}\n normalizedValue={normalizedValue}\n bipolar={bipolar}\n thickness={pixelThickness}\n roundness={strokeLinecap}\n openness={openness}\n rotation={rotation}\n fgArcStyle={{ stroke: \"var(--audioui-primary-color)\" }}\n bgArcStyle={{ stroke: \"var(--audioui-primary-50)\" }}\n />\n );\n\n // Render variant-specific content\n const finalLinecap = typeof strokeLinecap === \"string\" ? strokeLinecap : (strokeLinecap as \"round\" | \"square\");\n switch (variant) {\n case \"plainCap\":\n return (\n <g className={className}>\n {valueRing}\n\n <RotaryImage\n cx={50}\n cy={50}\n radius={50 - pixelThickness - 6}\n normalizedValue={normalizedValue}\n openness={openness}\n rotation={rotation}\n >\n <circle cx=\"50%\" cy=\"50%\" r=\"50%\" fill=\"var(--audioui-knob-cap-fill, #4a4d50)\" />\n <line\n x1=\"50%\"\n y1=\"15%\"\n x2=\"50%\"\n y2=\"5%\"\n stroke=\"var(--audioui-nearwhite)\"\n strokeWidth={(50 - pixelThickness - 6) * 0.1}\n // @ts-expect-error - strokeLinecap accepts CSS variables (strings) but React types are strict\n strokeLinecap={finalLinecap}\n />\n </RotaryImage>\n </g>\n );\n\n case \"iconCap\": {\n // iconCap inherits from plainCap and adds an overlay\n const iconRadius = (50 - pixelThickness - 6) * 0.35;\n // Get the adaptive color for the icon (icons use currentColor)\n const overlayContent = svgOverlay ? (\n svgOverlayRotary ? (\n <RotaryImage\n cx={50}\n cy={50}\n radius={iconRadius}\n normalizedValue={normalizedValue}\n openness={openness}\n rotation={rotation}\n style={{ color: \"var(--audioui-nearwhite)\" }}\n >\n {svgOverlay}\n </RotaryImage>\n ) : (\n <RadialImage cx={50} cy={50} radius={iconRadius} style={{ color: \"var(--audioui-nearwhite)\" }}>\n {svgOverlay}\n </RadialImage>\n )\n ) : null;\n\n return (\n <g className={className}>\n {valueRing}\n {/* PlainCap content */}\n <RotaryImage\n cx={50}\n cy={50}\n radius={50 - pixelThickness - 6}\n normalizedValue={normalizedValue}\n openness={openness}\n rotation={rotation}\n >\n <circle cx=\"50%\" cy=\"50%\" r=\"50%\" fill=\"var(--audioui-knob-cap-fill, #4a4d50)\" />\n <line\n x1=\"50%\"\n y1=\"15%\"\n x2=\"50%\"\n y2=\"5%\"\n stroke=\"var(--audioui-nearwhite)\"\n strokeWidth={(50 - pixelThickness - 6) * 0.1}\n // @ts-expect-error - strokeLinecap accepts CSS variables (strings) but React types are strict\n strokeLinecap={finalLinecap}\n />\n </RotaryImage>\n {/* Icon overlay */}\n {overlayContent}\n </g>\n );\n }\n\n case \"abstract\":\n case \"simplest\":\n default:\n // Default variant and other variants use ValueRing only\n return <g className={className}>{valueRing}</g>;\n }\n}\n\n// Create memoized component\nconst MemoKnobView = React.memo(KnobView);\n\n// Explicitly attach static properties to the memoized component\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(MemoKnobView as any).viewBox = { width: 100, height: 100 };\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(MemoKnobView as any).labelHeightUnits = 20;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(MemoKnobView as any).interaction = { mode: \"both\", direction: \"circular\" } as const;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(MemoKnobView as any).title = \"Knob\";\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(MemoKnobView as any).description = \"A rotary knob control with circular arc indicator\";\n\nexport default MemoKnobView as unknown as ControlComponent<\n Omit<KnobViewProps, \"normalizedValue\" | \"className\" | \"style\">\n>;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React, { useEffect, useMemo, useRef } from \"react\";\nimport { ContinuousInteractionController, ContinuousInteractionConfig } from \"@cutoff/audio-ui-core\";\nimport { DEFAULT_CONTINUOUS_SENSITIVITY, DEFAULT_KEYBOARD_STEP, TARGET_PIXELS_PER_STEP } from \"@cutoff/audio-ui-core\";\n\nexport type UseContinuousInteractionProps = Omit<ContinuousInteractionConfig, \"adjustValue\"> & {\n adjustValue: (delta: number, sensitivity?: number) => void;\n editable?: boolean;\n /** Minimum value (real domain). Used to calculate normalized step if step is not provided. */\n min?: number;\n /** Maximum value (real domain). Used to calculate normalized step if step is not provided. */\n max?: number;\n /** Step size (real domain). Used to calculate normalized step if step is not provided. */\n paramStep?: number;\n /** Function to reset the value to its default (called on double-click) */\n resetToDefault?: () => void;\n /** Optional user-provided mouse down handler (composed with hook handler) */\n onMouseDown?: React.MouseEventHandler;\n /** Optional user-provided touch start handler (composed with hook handler) */\n onTouchStart?: React.TouchEventHandler;\n /** Optional user-provided wheel handler (composed with hook handler) */\n onWheel?: React.WheelEventHandler;\n /** Optional user-provided keyboard key down handler (composed with hook handler) */\n onKeyDown?: React.KeyboardEventHandler;\n /** Optional user-provided double click handler (composed with hook handler) */\n onDoubleClick?: React.MouseEventHandler;\n};\n\nexport interface ContinuousInteractionHandlers {\n onMouseDown: React.MouseEventHandler;\n onTouchStart: React.TouchEventHandler;\n onWheel: React.WheelEventHandler;\n onKeyDown: React.KeyboardEventHandler;\n onDoubleClick: React.MouseEventHandler;\n tabIndex: number;\n role: string;\n \"aria-disabled\"?: boolean;\n style?: React.CSSProperties;\n}\n\n/**\n * Hook to standardize user interaction for continuous controls (Knob, Slider).\n *\n * This hook provides a unified interface for handling all user input methods:\n * - Drag interactions (mouse and touch)\n * - Wheel scrolling\n * - Keyboard navigation (arrow keys, Home/End)\n * - Double-click to reset to default value\n *\n * The hook wraps the framework-agnostic `ContinuousInteractionController` and provides React\n * event handlers that can be attached directly to SVG elements. It handles focus\n * management, accessibility attributes, and cursor styling automatically.\n *\n * Cursor types are customizable via CSS variables in themes.css (e.g., `--audioui-cursor-clickable`,\n * `--audioui-cursor-bidirectional`). The cursor selection logic (which cursor to show when) is\n * fixed based on interaction state, but the actual cursor values are customizable.\n *\n * @param adjustValue Function to adjust the value based on a delta. Receives (delta, sensitivity).\n * @param keyboardStep Step size for keyboard interaction (normalized 0..1, default: 0.05)\n * @param interactionMode Interaction mode: \"drag\", \"wheel\", or \"both\" (default: \"both\")\n * @param direction Direction of drag interaction: \"vertical\", \"horizontal\", \"circular\", or \"both\" (default: \"both\")\n * @param sensitivity Sensitivity of the control (default: 0.005). Higher = more sensitive.\n * @param wheelSensitivity Optional separate sensitivity for wheel events. Defaults to DEFAULT_WHEEL_SENSITIVITY (0.005).\n * @param step Normalized step size (0..1). Used for adaptive wheel interaction and sensitivity scaling.\n * @param min Minimum value (real domain). Used to calculate normalized step if step is not provided.\n * @param max Maximum value (real domain). Used to calculate normalized step if step is not provided.\n * @param paramStep Step size (real domain). Used to calculate normalized step if step is not provided.\n * @param disabled Whether the control is disabled (default: false)\n * @param editable Whether the control is editable (default: true). When false, uses `--audioui-cursor-noneditable`.\n * @param resetToDefault Function to reset the value to its default (called on double-click). Only active when editable and not disabled.\n * @param onDragStart Callback when drag interaction starts\n * @param onDragEnd Callback when drag interaction ends\n * @param onMouseDown Optional user-provided mouse down handler (composed with hook handler)\n * @param onTouchStart Optional user-provided touch start handler (composed with hook handler)\n * @param onWheel Optional user-provided wheel handler (composed with hook handler)\n * @param onKeyDown Optional user-provided keyboard key down handler (composed with hook handler)\n * @param onDoubleClick Optional user-provided double click handler (composed with hook handler)\n * @returns Object containing React event handlers (onMouseDown, onTouchStart, onWheel, onKeyDown, onDoubleClick) and accessibility props\n *\n * @example\n * ```tsx\n * const interactiveProps = useContinuousInteraction({\n * adjustValue: (delta, sensitivity) => {\n * setValue(v => clamp(v + delta * sensitivity, 0, 100));\n * },\n * resetToDefault: () => setValue(defaultValue),\n * interactionMode: \"both\",\n * direction: \"vertical\",\n * sensitivity: 0.01\n * });\n *\n * <svg {...interactiveProps}>\n * <circle cx={50} cy={50} r={30} />\n * </svg>\n * ```\n */\nexport function useContinuousInteraction({\n adjustValue,\n keyboardStep = DEFAULT_KEYBOARD_STEP,\n interactionMode = \"both\",\n direction = \"both\",\n sensitivity,\n wheelSensitivity,\n step,\n min,\n max,\n paramStep,\n disabled = false,\n editable = true,\n onDragStart,\n onDragEnd,\n resetToDefault,\n onMouseDown: userOnMouseDown,\n onTouchStart: userOnTouchStart,\n onWheel: userOnWheel,\n onKeyDown: userOnKeyDown,\n onDoubleClick: userOnDoubleClick,\n}: UseContinuousInteractionProps): ContinuousInteractionHandlers {\n // Adaptive Sensitivity Logic\n // If a step is provided, ensure that dragging one step corresponds to at most TARGET_PIXELS_PER_STEP.\n // This prevents \"dead zones\" where you have to drag huge distances to change a low-resolution parameter.\n const effectiveStep = useMemo(() => {\n if (step !== undefined) return step;\n if (paramStep !== undefined && min !== undefined && max !== undefined && max !== min) {\n return paramStep / Math.abs(max - min);\n }\n return undefined;\n }, [step, min, max, paramStep]);\n\n const effectiveSensitivity = useMemo(() => {\n const base = sensitivity ?? DEFAULT_CONTINUOUS_SENSITIVITY;\n if (effectiveStep) {\n // e.g. Step = 0.1 (range 0-10). Target = 5px.\n // Required sensitivity = 0.1 / 5 = 0.02.\n // Base = 0.005.\n // Result = 0.02.\n const minSensitivityForStep = effectiveStep / TARGET_PIXELS_PER_STEP;\n return Math.max(base, minSensitivityForStep);\n }\n return base;\n }, [sensitivity, effectiveStep]);\n\n const controllerRef = useRef<ContinuousInteractionController | null>(null);\n\n if (!controllerRef.current) {\n controllerRef.current = new ContinuousInteractionController({\n adjustValue,\n keyboardStep,\n interactionMode,\n direction,\n sensitivity: effectiveSensitivity,\n wheelSensitivity,\n step: effectiveStep,\n disabled,\n onDragStart,\n onDragEnd,\n });\n }\n\n useEffect(() => {\n controllerRef.current?.updateConfig({\n adjustValue,\n keyboardStep,\n interactionMode,\n direction,\n sensitivity: effectiveSensitivity,\n wheelSensitivity,\n step: effectiveStep,\n disabled,\n onDragStart,\n onDragEnd,\n });\n }, [\n adjustValue,\n keyboardStep,\n interactionMode,\n direction,\n effectiveSensitivity,\n wheelSensitivity,\n effectiveStep,\n disabled,\n onDragStart,\n onDragEnd,\n ]);\n\n useEffect(() => {\n return () => {\n controllerRef.current?.dispose();\n };\n }, []);\n\n // Handlers with user handler composition\n const handlers = useMemo(() => {\n const ctrl = controllerRef.current!;\n\n return {\n onMouseDown: (e: React.MouseEvent) => {\n // Call user handler first\n userOnMouseDown?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n ctrl.handleMouseDown(e.clientX, e.clientY, e.currentTarget);\n }\n },\n onTouchStart: (e: React.TouchEvent) => {\n // Call user handler first\n userOnTouchStart?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n const touch = e.touches[0];\n ctrl.handleTouchStart(touch.clientX, touch.clientY, e.currentTarget);\n }\n },\n onWheel: (e: React.WheelEvent) => {\n // Call user handler first\n userOnWheel?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n ctrl.handleWheel(e as unknown as WheelEvent);\n }\n },\n onKeyDown: (e: React.KeyboardEvent) => {\n // Call user handler first\n userOnKeyDown?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n ctrl.handleKeyDown(e as unknown as KeyboardEvent);\n }\n },\n onDoubleClick: (e: React.MouseEvent) => {\n // Call user handler first\n userOnDoubleClick?.(e);\n // Only reset if not prevented and resetToDefault is provided\n if (!e.defaultPrevented && resetToDefault && editable && !disabled) {\n e.preventDefault();\n resetToDefault();\n }\n },\n };\n }, [\n userOnMouseDown,\n userOnTouchStart,\n userOnWheel,\n userOnKeyDown,\n userOnDoubleClick,\n resetToDefault,\n editable,\n disabled,\n ]);\n\n // Cursor selection based on interaction state - uses CSS variables for customization\n // The logic (when to show which cursor) is fixed, but cursor types are customizable via CSS\n const cursor = disabled\n ? \"var(--audioui-cursor-disabled)\"\n : !editable\n ? \"var(--audioui-cursor-noneditable)\"\n : interactionMode === \"wheel\"\n ? \"var(--audioui-cursor-vertical)\"\n : direction === \"horizontal\"\n ? \"var(--audioui-cursor-horizontal)\"\n : direction === \"vertical\"\n ? \"var(--audioui-cursor-vertical)\"\n : direction === \"both\"\n ? \"var(--audioui-cursor-bidirectional)\"\n : direction === \"circular\"\n ? \"var(--audioui-cursor-circular)\"\n : \"var(--audioui-cursor-clickable)\";\n\n return {\n ...handlers,\n tabIndex: disabled ? -1 : 0,\n role: \"slider\",\n \"aria-disabled\": disabled,\n style: {\n cursor,\n touchAction: \"none\",\n },\n };\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { useMemo } from \"react\";\nimport { ContinuousParameter, AudioParameterFactory, ScaleType, MidiResolution } from \"@cutoff/audio-ui-core\";\n\nexport interface UseContinuousParameterResolutionProps {\n /** The parameter definition (Strict mode) */\n parameter?: ContinuousParameter;\n /** Identifier for the parameter (used in Ad-Hoc mode) */\n paramId?: string;\n /** Label for the parameter (Ad-Hoc mode) */\n label?: string;\n /** Minimum value (Ad-Hoc mode) */\n min?: number;\n /** Maximum value (Ad-Hoc mode) */\n max?: number;\n /** Step size for value adjustments (Ad-Hoc mode) */\n step?: number;\n /** Whether the parameter operates in bipolar mode (Ad-Hoc mode) */\n bipolar?: boolean;\n /** Unit suffix for the value (Ad-Hoc mode, e.g. \"dB\", \"Hz\") */\n unit?: string;\n /** Scale function or shortcut for the parameter (Ad-Hoc mode) */\n scale?: ScaleType;\n /** MIDI resolution in bits (Ad-Hoc mode)\n * @default 32\n */\n midiResolution?: MidiResolution;\n /** Default value for the parameter (Ad-Hoc mode) */\n defaultValue?: number;\n}\n\nexport interface UseContinuousParameterResolutionResult {\n /** The resolved ContinuousParameter (derived from props or created from ad-hoc props) */\n derivedParameter: ContinuousParameter;\n}\n\n/**\n * Hook to resolve a ContinuousParameter from props.\n *\n * Supports two modes:\n * 1. Strict Mode (Parameter only): Model provided via parameter prop.\n * 2. Ad-Hoc Mode (Props only): Model created from individual props (min, max, step, etc.).\n *\n * When `parameter` is provided, it takes precedence over ad-hoc props.\n *\n * @param props - Configuration object for parameter resolution\n * @param props.parameter - The parameter definition (Strict mode). When provided, takes precedence over ad-hoc props.\n * @param props.paramId - Identifier for the parameter (used in Ad-Hoc mode)\n * @param props.label - Label for the parameter (Ad-Hoc mode)\n * @param props.min - Minimum value (Ad-Hoc mode)\n * @param props.max - Maximum value (Ad-Hoc mode)\n * @param props.step - Step size for value adjustments (Ad-Hoc mode)\n * @param props.bipolar - Whether the parameter operates in bipolar mode (Ad-Hoc mode)\n * @param props.unit - Unit suffix for the value (Ad-Hoc mode, e.g. \"dB\", \"Hz\")\n * @param props.scale - Scale function or shortcut for the parameter (Ad-Hoc mode)\n * @param props.midiResolution - MIDI resolution in bits (Ad-Hoc mode, default: 32)\n * @param props.defaultValue - Default value for the parameter (Ad-Hoc mode)\n * @returns Object containing the resolved ContinuousParameter\n *\n * @example\n * ```tsx\n * // Strict mode: use provided parameter\n * const { derivedParameter } = useContinuousParameterResolution({\n * parameter: volumeParam\n * });\n *\n * // Ad-Hoc mode: create from props\n * const { derivedParameter } = useContinuousParameterResolution({\n * paramId: \"volume\",\n * label: \"Volume\",\n * min: 0,\n * max: 100,\n * step: 1,\n * unit: \"%\"\n * });\n * ```\n */\nexport function useContinuousParameterResolution({\n parameter,\n paramId,\n label,\n min,\n max,\n step,\n bipolar,\n unit,\n scale,\n midiResolution = 32,\n defaultValue,\n}: UseContinuousParameterResolutionProps): UseContinuousParameterResolutionResult {\n return useMemo(() => {\n let derivedParameter: ContinuousParameter;\n\n if (parameter) {\n // Strict mode: use provided parameter\n derivedParameter = parameter;\n } else {\n // Ad-hoc mode: create parameter from props\n derivedParameter = AudioParameterFactory.createControl({\n id: paramId ?? \"adhoc-continuous\",\n label,\n min,\n max,\n step,\n bipolar,\n unit,\n scale,\n midiResolution,\n defaultValue,\n });\n }\n\n return {\n derivedParameter,\n };\n }, [parameter, paramId, label, min, max, step, bipolar, unit, scale, midiResolution, defaultValue]);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport classNames from \"classnames\";\nimport { CLASSNAMES } from \"@cutoff/audio-ui-core\";\nimport {\n AdaptiveBoxProps,\n AdaptiveBoxLogicalSizeProps,\n ContinuousControlProps,\n ControlComponent,\n ValueLabelMode,\n} from \"@/types\";\nimport AdaptiveBox from \"../AdaptiveBox\";\nimport { useAudioParameter } from \"@/hooks/useAudioParameter\";\nimport { useContinuousInteraction } from \"@/hooks/useContinuousInteraction\";\nimport { useContinuousParameterResolution } from \"@/hooks/useContinuousParameterResolution\";\n\nexport type ContinuousControlComponentProps<P extends object = Record<string, unknown>> =\n // Base Control Props (includes all ContinuousControlProps)\n ContinuousControlProps &\n // Layout props that configure AdaptiveBox behavior\n AdaptiveBoxProps &\n // Logical size props that override view component defaults\n AdaptiveBoxLogicalSizeProps & {\n /**\n * The Visualization Component.\n * Must adhere to ControlComponent contract.\n */\n view: ControlComponent<P>;\n\n /**\n * Props specific to the Visualization Component.\n */\n viewProps: P;\n\n /**\n * Content overlay (HTML) rendered over the SVG (e.g. text value).\n * Rendered via AdaptiveBox.HtmlOverlay to avoid foreignObject issues.\n */\n htmlOverlay?: React.ReactNode;\n\n /**\n * Controls how the label and value are displayed.\n * - \"labelOnly\": Always shows the label (default)\n * - \"valueOnly\": Always shows the value\n * - \"interactive\": Shows label normally, but temporarily swaps to value during interaction\n * @default \"labelOnly\"\n */\n valueAsLabel?: ValueLabelMode;\n };\n\n/**\n * A Generic Continuous Control that connects a Data Model (AudioParameter)\n * to a Visualization View (ControlComponent).\n *\n * This component provides a generic wrapper for continuous controls (Knob, Slider, etc.),\n * decoupling behavior (AudioParameter, interaction logic) from visualization (SVG rendering).\n * It handles parameter resolution, normalization, interaction, and layout automatically.\n *\n * Supports double-click to reset to default value when editable (onChange provided).\n * The default value is determined by the parameter's `defaultValue` property, or\n * calculated as 0.0 for unipolar or 0.5 for bipolar parameters when not specified.\n *\n * @param props Component props including parameter configuration, view component, and layout props\n * @returns Rendered continuous control with AdaptiveBox layout\n */\nexport function ContinuousControl<P extends object = Record<string, unknown>>(\n props: ContinuousControlComponentProps<P>\n) {\n const {\n view: View,\n viewProps,\n htmlOverlay,\n min,\n max,\n step,\n value,\n label,\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n viewBoxWidthUnits,\n viewBoxHeightUnits,\n labelHeightUnits,\n className,\n style,\n onChange,\n paramId,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n parameter,\n interactionMode,\n interactionDirection,\n interactionSensitivity,\n unit,\n scale,\n valueFormatter,\n valueAsLabel = \"labelOnly\",\n midiResolution,\n defaultValue,\n } = props;\n\n const bipolar = props.bipolar ?? false;\n const { derivedParameter } = useContinuousParameterResolution({\n parameter,\n paramId,\n label,\n min,\n max,\n step,\n bipolar,\n unit,\n scale,\n midiResolution,\n defaultValue,\n });\n\n // Interaction state for valueAsLabel=\"interactive\"\n const [isDragging, setIsDragging] = React.useState(false);\n const [isRecentlyActive, setIsRecentlyActive] = React.useState(false);\n const activityTimerRef = React.useRef<number | undefined>(undefined);\n\n const handleDragStart = React.useCallback(() => {\n setIsDragging(true);\n setIsRecentlyActive(true);\n if (activityTimerRef.current) window.clearTimeout(activityTimerRef.current);\n }, []);\n\n const handleDragEnd = React.useCallback(() => {\n setIsDragging(false);\n // Start decay timer\n if (activityTimerRef.current) window.clearTimeout(activityTimerRef.current);\n activityTimerRef.current = window.setTimeout(() => {\n setIsRecentlyActive(false);\n }, 1000);\n }, []);\n\n const handleActivity = React.useCallback(() => {\n setIsRecentlyActive(true);\n if (activityTimerRef.current) window.clearTimeout(activityTimerRef.current);\n // If not dragging, start decay immediately (debounce effect)\n if (!isDragging) {\n activityTimerRef.current = window.setTimeout(() => {\n setIsRecentlyActive(false);\n }, 1000);\n }\n }, [isDragging]);\n\n const showValueAsLabel =\n valueAsLabel === \"valueOnly\" || (valueAsLabel === \"interactive\" && (isDragging || isRecentlyActive));\n\n const { normalizedValue, adjustValue, effectiveLabel, resetToDefault } = useAudioParameter(\n value,\n onChange,\n derivedParameter,\n valueFormatter,\n label,\n showValueAsLabel\n );\n\n // Wrap adjustValue to trigger activity on changes (wheel, keyboard)\n const wrappedAdjustValue = React.useCallback(\n (delta: number, sensitivity?: number) => {\n if (valueAsLabel === \"interactive\" && onChange) {\n handleActivity();\n }\n adjustValue(delta, sensitivity);\n },\n [adjustValue, valueAsLabel, handleActivity, onChange]\n );\n\n const effectiveInteractionMode = interactionMode ?? View.interaction.mode ?? \"both\";\n const effectiveDirection = interactionDirection ?? View.interaction.direction ?? \"both\";\n\n // Only editable when onChange is provided (onClick is not relevant for interaction controller)\n const interactiveProps = useContinuousInteraction({\n adjustValue: wrappedAdjustValue,\n interactionMode: effectiveInteractionMode,\n direction: effectiveDirection,\n sensitivity: interactionSensitivity,\n min: derivedParameter.min,\n max: derivedParameter.max,\n paramStep: derivedParameter.step,\n editable: !!onChange,\n resetToDefault: onChange ? resetToDefault : undefined,\n onDragStart: valueAsLabel === \"interactive\" && onChange ? handleDragStart : undefined,\n onDragEnd: valueAsLabel === \"interactive\" && onChange ? handleDragEnd : undefined,\n onMouseDown,\n onTouchStart: undefined, // ContinuousControl doesn't have onTouchStart prop\n onWheel: undefined, // ContinuousControl doesn't have onWheel prop\n onKeyDown: undefined, // ContinuousControl doesn't have onKeyDown prop\n });\n\n const componentClassNames = useMemo(() => {\n return classNames(className, CLASSNAMES.root, CLASSNAMES.container);\n }, [className]);\n\n const svgClassNames = useMemo(() => {\n return onChange || onClick ? CLASSNAMES.highlight : \"\";\n }, [onChange, onClick]);\n\n // Add clickable cursor when clickable but not draggable (onClick but no onChange)\n // Uses CSS variable for customizable cursor type\n const svgStyle = {\n ...(interactiveProps.style ?? {}),\n // Override cursor for click-only controls\n ...(onClick && !onChange ? { cursor: \"var(--audioui-cursor-clickable)\" as const } : {}),\n };\n\n return (\n <AdaptiveBox\n displayMode={displayMode ?? \"scaleToFit\"}\n labelMode={labelMode}\n labelOverflow={labelOverflow}\n className={componentClassNames}\n style={style}\n labelHeightUnits={labelHeightUnits ?? View.labelHeightUnits ?? 20}\n viewBoxWidth={viewBoxWidthUnits ?? View.viewBox.width}\n viewBoxHeight={viewBoxHeightUnits ?? View.viewBox.height}\n >\n <AdaptiveBox.Svg\n className={svgClassNames}\n style={svgStyle}\n onWheel={interactiveProps.onWheel}\n onClick={onClick}\n onMouseDown={interactiveProps.onMouseDown}\n onTouchStart={interactiveProps.onTouchStart}\n onKeyDown={interactiveProps.onKeyDown}\n onDoubleClick={interactiveProps.onDoubleClick}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n tabIndex={interactiveProps.tabIndex}\n role={interactiveProps.role}\n aria-valuenow={value}\n aria-label={effectiveLabel}\n >\n <View normalizedValue={normalizedValue} {...viewProps} />\n </AdaptiveBox.Svg>\n {htmlOverlay && <AdaptiveBox.HtmlOverlay>{htmlOverlay}</AdaptiveBox.HtmlOverlay>}\n {effectiveLabel && (\n <AdaptiveBox.Label position={labelPosition} align={labelAlign}>\n {effectiveLabel}\n </AdaptiveBox.Label>\n )}\n </AdaptiveBox>\n );\n}\n\nexport default React.memo(ContinuousControl) as typeof ContinuousControl;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport KnobView from \"./KnobView\";\nimport ContinuousControl from \"@/primitives/controls/ContinuousControl\";\nimport {\n AdaptiveBoxProps,\n AdaptiveSizeProps,\n ContinuousControlProps,\n ThemableProps,\n KnobVariant,\n ValueLabelMode,\n} from \"@/types\";\nimport { clampNormalized } from \"@cutoff/audio-ui-core\";\nimport { useAudioParameter } from \"@/hooks/useAudioParameter\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { useContinuousParameterResolution } from \"@/hooks/useContinuousParameterResolution\";\nimport { useThemableProps } from \"@/hooks/useThemableProps\";\n\n/**\n * Default openness for knob ring in degrees (matches ValueRing default)\n */\nexport const DEFAULT_OPENNESS = 90;\n\n/**\n * Default rotation angle offset in degrees (matches ValueRing default)\n */\nexport const DEFAULT_ROTATION = 0;\n\n/**\n * Props for the Knob component (built-in control with theming support)\n */\nexport type KnobProps = ContinuousControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n ThemableProps & {\n /** Visual variant of the knob\n * @default \"abstract\"\n */\n variant?: KnobVariant;\n /** Thickness of the knob's stroke (normalized 0.0-1.0, maps to 1-20)\n * @default Variant-dependent: 0.4 for abstract/simplest, 0.2 for others\n */\n thickness?: number;\n /** Openness of the ring in degrees (value between 0-360º; 0º: closed; 90º: 3/4 open; 180º: half-circle;)\n * @default 90\n */\n openness?: number;\n /** Optional rotation angle offset in degrees\n * @default 0\n */\n rotation?: number;\n /**\n * Whether to use RotaryImage (true) or RadialImage (false) for iconCap overlay.\n * When true, the icon rotates with the knob value; when false, the icon remains static.\n * Only applies when variant is \"iconCap\" and children is provided.\n * @default false\n */\n rotaryOverlay?: boolean;\n /**\n * SVG content to display as overlay in iconCap variant.\n * Typically an icon component (e.g., wave icons) that will be rendered at the center of the knob.\n * The icon inherits color via currentColor, so it will adapt to light/dark mode automatically.\n * Only used when variant is \"iconCap\".\n *\n * @example\n * ```tsx\n * <Knob variant=\"iconCap\" value={50} min={0} max={100}>\n * <SineWaveIcon />\n * </Knob>\n * ```\n */\n children?: React.ReactNode;\n /**\n * Controls how the label and value are displayed.\n * - \"labelOnly\": Always shows the label (default)\n * - \"valueOnly\": Always shows the value\n * - \"interactive\": Shows label normally, but temporarily swaps to value during interaction\n * @default \"labelOnly\"\n */\n valueAsLabel?: ValueLabelMode;\n };\n\n/**\n * Knob component provides a circular control for value adjustment.\n * Supports continuous value ranges with optional bipolar mode, custom formatting,\n * and multiple visual variants.\n *\n * @example Basic usage\n * ```tsx\n * <Knob value={50} min={0} max={100} label=\"Volume\" />\n * ```\n *\n * @example With iconCap variant and icon\n * ```tsx\n * <Knob variant=\"iconCap\" value={64} min={0} max={127} rotaryOverlay={true}>\n * <SineWaveIcon />\n * </Knob>\n * ```\n */\nfunction Knob({\n min,\n max,\n step,\n bipolar = false,\n value,\n onChange,\n valueFormatter,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n labelHeightUnits,\n color,\n roundness,\n variant = \"abstract\",\n thickness,\n openness = DEFAULT_OPENNESS,\n rotation = DEFAULT_ROTATION,\n parameter,\n unit,\n scale,\n paramId,\n interactionMode,\n interactionDirection,\n interactionSensitivity,\n rotaryOverlay: svgOverlayRotary = false,\n children: svgOverlay,\n valueAsLabel = \"labelOnly\",\n midiResolution,\n defaultValue,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: KnobProps) {\n const { style: themableStyle } = useThemableProps({\n color,\n roundness,\n style,\n });\n\n // Clamp thickness if provided\n const clampedThickness = thickness !== undefined ? clampNormalized(thickness) : undefined;\n\n const { derivedParameter: parameterDef } = useContinuousParameterResolution({\n parameter,\n paramId: paramId ?? \"adhoc-knob\",\n label,\n min,\n max,\n step,\n bipolar,\n unit,\n scale,\n midiResolution,\n defaultValue,\n });\n\n const { formattedValue } = useAudioParameter(value, onChange, parameterDef, valueFormatter);\n\n const { sizeClassName, sizeStyle: adaptiveSizeStyle } = useAdaptiveSize(adaptiveSize, size, \"knob\");\n\n // Uses container query units (cqmin) so text scales with component size\n const displayValueOverlay =\n variant === \"abstract\" ? (\n <div\n style={{\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontSize: \"22cqmin\",\n fontWeight: \"500\",\n color: \"var(--audioui-text-color)\",\n cursor: \"inherit\",\n }}\n >\n {formattedValue}\n </div>\n ) : undefined;\n\n return (\n <ContinuousControl\n min={min}\n max={max}\n step={step}\n bipolar={bipolar}\n value={value}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n labelOverflow={labelOverflow}\n labelHeightUnits={labelHeightUnits}\n className={classNames(sizeClassName, className)}\n style={{ ...adaptiveSizeStyle, ...themableStyle }}\n onChange={onChange}\n paramId={paramId}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n parameter={parameter}\n unit={unit}\n scale={scale}\n midiResolution={midiResolution}\n defaultValue={defaultValue}\n interactionMode={interactionMode}\n interactionDirection={interactionDirection}\n interactionSensitivity={interactionSensitivity}\n valueFormatter={valueFormatter}\n valueAsLabel={valueAsLabel}\n view={KnobView}\n viewProps={{\n color: color ?? \"var(--audioui-primary-color)\",\n variant: variant,\n thickness: clampedThickness,\n roundness: roundness ?? \"var(--audioui-roundness-knob)\",\n openness: openness,\n rotation: rotation,\n svgOverlayRotary: svgOverlayRotary,\n svgOverlay: svgOverlay,\n bipolar: bipolar,\n }}\n htmlOverlay={displayValueOverlay}\n />\n );\n}\n\nexport default React.memo(Knob);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React, { useCallback, useRef, useEffect } from \"react\";\nimport { DiscreteInteractionController } from \"@cutoff/audio-ui-core\";\n\nexport interface UseDiscreteInteractionProps {\n /** Current value of the control */\n value: string | number;\n /** List of available options */\n options: Array<{ value: string | number }>;\n /** Callback to update the value */\n onValueChange: (value: string | number) => void;\n /** Whether the control is disabled */\n disabled?: boolean;\n /** Optional user-provided click handler (composed with hook handler) */\n onClick?: React.MouseEventHandler;\n /** Optional user-provided mouse down handler (composed with hook handler) */\n onMouseDown?: React.MouseEventHandler;\n /** Optional user-provided keyboard key down handler (composed with hook handler) */\n onKeyDown?: React.KeyboardEventHandler;\n}\n\nexport interface UseDiscreteInteractionResult {\n /** Handler for click events (cycles through options) */\n handleClick: (e: React.MouseEvent) => void;\n /** Handler for keyboard events (Arrows to step, Space/Enter to cycle) */\n handleKeyDown: (e: React.KeyboardEvent) => void;\n /** Handler for mouse down events (passes through to user handler if provided) */\n handleMouseDown?: React.MouseEventHandler;\n /** Manually cycle to the next value (wrapping around) */\n cycleNext: () => void;\n /** Manually step to the next value (clamped) */\n stepNext: () => void;\n /** Manually step to the previous value (clamped) */\n stepPrev: () => void;\n}\n\n/**\n * Hook to manage interactions for discrete controls (switches, toggles, selectors).\n *\n * Provides standardized logic for:\n * - Cycling through options (wrapping) via Click or Space/Enter\n * - Stepping through options (clamped) via Arrow keys\n * - Finding the nearest valid option index when value doesn't match\n *\n * The hook wraps the framework-agnostic `DiscreteInteractionController` and provides React\n * event handlers that can be attached directly to DOM elements. It maintains stable callback\n * references across renders using `useCallback` and updates the controller configuration via\n * `useEffect` when props change.\n *\n * @param {UseDiscreteInteractionProps} props - Configuration for the discrete interaction hook\n * @param {string | number} props.value - Current value of the control\n * @param {Array<{ value: string | number }>} props.options - List of available options\n * @param {(value: string | number) => void} props.onValueChange - Callback to update the value\n * @param {boolean} [props.disabled=false] - Whether the control is disabled\n * @param {React.MouseEventHandler} [props.onClick] - Optional user-provided click handler\n * @param {React.MouseEventHandler} [props.onMouseDown] - Optional user-provided mouse down handler\n * @param {React.KeyboardEventHandler} [props.onKeyDown] - Optional user-provided keyboard key down handler\n * @returns {UseDiscreteInteractionResult} Object containing event handlers and manual control methods\n *\n * @example\n * ```tsx\n * const { handleClick, handleKeyDown } = useDiscreteInteraction({\n * value,\n * options,\n * onValueChange: (val) => setNormalizedValue(converter.normalize(val))\n * });\n *\n * <div onClick={handleClick} onKeyDown={handleKeyDown} />\n * ```\n */\nexport function useDiscreteInteraction({\n value,\n options,\n onValueChange,\n disabled = false,\n onClick: userOnClick,\n onMouseDown: userOnMouseDown,\n onKeyDown: userOnKeyDown,\n}: UseDiscreteInteractionProps): UseDiscreteInteractionResult {\n const controllerRef = useRef<DiscreteInteractionController | null>(null);\n\n if (!controllerRef.current) {\n controllerRef.current = new DiscreteInteractionController({\n value,\n options,\n onValueChange,\n disabled,\n });\n }\n\n useEffect(() => {\n controllerRef.current?.updateConfig({\n value,\n options,\n onValueChange,\n disabled,\n });\n }, [value, options, onValueChange, disabled]);\n\n const cycleNext = useCallback(() => controllerRef.current?.cycleNext(), []);\n const stepNext = useCallback(() => controllerRef.current?.stepNext(), []);\n const stepPrev = useCallback(() => controllerRef.current?.stepPrev(), []);\n\n const handleClick = useCallback(\n (e: React.MouseEvent) => {\n // Call user handler first\n userOnClick?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n controllerRef.current?.handleClick(e.defaultPrevented);\n }\n },\n [userOnClick]\n );\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n // Call user handler first\n userOnKeyDown?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n const handled = controllerRef.current?.handleKeyDown(e.key);\n if (handled) {\n e.preventDefault();\n }\n }\n },\n [userOnKeyDown]\n );\n\n // Pass through mouse down handler if provided (no discrete interaction logic needed for mousedown)\n const handleMouseDown = userOnMouseDown;\n\n return {\n handleClick,\n handleKeyDown,\n handleMouseDown,\n cycleNext,\n stepNext,\n stepPrev,\n };\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React, { useMemo } from \"react\";\nimport { DiscreteParameter, DiscreteOption, MidiResolution } from \"@cutoff/audio-ui-core\";\nimport { OptionViewProps } from \"@/primitives/controls/OptionView\";\n\nexport interface UseDiscreteParameterResolutionProps {\n /** Child elements (OptionView components) for visual content mapping (Hybrid mode)\n *\n * **Visual Content Only**: Children provide ReactNodes for rendering (icons, text, custom components).\n * They do NOT define the parameter model - use `options` prop or `parameter` prop for that.\n *\n * When both `options` and `children` are provided, children are matched to options by value\n * to create the visual content map.\n */\n children?: React.ReactNode;\n /** Option definitions for the parameter model (Ad-Hoc mode)\n *\n * **Parameter Model Only**: This prop defines the parameter structure (value, label, midiValue).\n * It does NOT provide visual content - use `children` (OptionView components) for that.\n *\n * When both `options` and `children` are provided:\n * - `options` defines the parameter model\n * - `children` provide visual content (matched by value)\n */\n options?: DiscreteOption[];\n /** Identifier for the parameter (used in Ad-Hoc mode) */\n paramId?: string;\n /** The parameter definition (Strict or Hybrid mode) */\n parameter?: DiscreteParameter;\n /** Default value (Ad-Hoc mode) or override for parameter default */\n defaultValue?: string | number;\n /** Label for the parameter (Ad-Hoc mode) */\n label?: string;\n /** MIDI resolution in bits (Ad-Hoc mode)\n * @default 7\n */\n midiResolution?: MidiResolution;\n /** MIDI mapping strategy (Ad-Hoc mode)\n * @default \"spread\"\n */\n midiMapping?: \"spread\" | \"sequential\" | \"custom\";\n}\n\nexport interface UseDiscreteParameterResolutionResult {\n /** The resolved DiscreteParameter (derived from props or children) */\n derivedParameter: DiscreteParameter;\n /** Map of values to visual content (ReactNodes) from children */\n visualContentMap: Map<string | number, React.ReactNode>;\n /** The effective default value (resolved from parameter, prop, or first option) */\n effectiveDefaultValue: string | number;\n}\n\n/**\n * Hook to resolve a DiscreteParameter and visual content from props and/or children.\n *\n * **Important: Options vs Children**\n *\n * - **`options` prop**: Defines the parameter model (value, label, midiValue). Used for parameter structure.\n * - **`children` (OptionView components)**: Provides visual content (ReactNodes) for rendering. Used for display.\n *\n * These serve different purposes and can be used together:\n * - Use `options` when you have data-driven option definitions\n * - Use `children` when you want to provide custom visual content (icons, styled text, etc.)\n * - Use both: `options` for the model, `children` for visuals (matched by value)\n *\n * Supports four modes of operation:\n * 1. **Ad-Hoc Mode (Options prop)**: Model from `options` prop, visual from `children` (if provided) or default rendering\n * 2. **Ad-Hoc Mode (Children only)**: Model inferred from OptionView children, visual from children\n * 3. **Strict Mode (Parameter only)**: Model from `parameter` prop, visual via `renderOption` callback\n * 4. **Hybrid Mode (Parameter + Children)**: Model from `parameter` prop, visual from children (matched by value)\n *\n * @param props - Configuration object for discrete parameter resolution\n * @param props.options - Option definitions for parameter model (Ad-Hoc mode). Takes precedence over children for model.\n * @param props.children - Child elements (OptionView components) for visual content mapping (Hybrid/Ad-Hoc mode)\n * @param props.paramId - Identifier for the parameter (used in Ad-Hoc mode)\n * @param props.parameter - The parameter definition (Strict or Hybrid mode)\n * @param props.defaultValue - Default value (Ad-Hoc mode) or override for parameter default\n * @param props.label - Label for the parameter (Ad-Hoc mode)\n * @param props.midiResolution - MIDI resolution in bits (Ad-Hoc mode, default: 7)\n * @param props.midiMapping - MIDI mapping strategy (Ad-Hoc mode, default: \"spread\")\n * @returns Object containing the resolved DiscreteParameter, visual content map, and effective default value\n *\n * @example\n * ```tsx\n * // Ad-Hoc Mode with options prop (data-driven)\n * const { derivedParameter } = useDiscreteParameterResolution({\n * options: [\n * { value: \"sine\", label: \"Sine Wave\" },\n * { value: \"square\", label: \"Square Wave\" }\n * ],\n * paramId: \"waveform\"\n * });\n *\n * // Ad-Hoc Mode with children (visual content)\n * const { derivedParameter, visualContentMap } = useDiscreteParameterResolution({\n * children: [\n * <OptionView value=\"sine\"><SineIcon /></OptionView>,\n * <OptionView value=\"square\"><SquareIcon /></OptionView>\n * ],\n * paramId: \"waveform\"\n * });\n *\n * // Hybrid: options for model, children for visuals\n * const { derivedParameter, visualContentMap } = useDiscreteParameterResolution({\n * options: [\n * { value: \"sine\", label: \"Sine Wave\", midiValue: 0 },\n * { value: \"square\", label: \"Square Wave\", midiValue: 1 }\n * ],\n * children: [\n * <OptionView value=\"sine\"><SineIcon /></OptionView>,\n * <OptionView value=\"square\"><SquareIcon /></OptionView>\n * ],\n * paramId: \"waveform\"\n * });\n *\n * // Strict Mode\n * const result = useDiscreteParameterResolution({\n * parameter: myDiscreteParameter,\n * defaultValue: \"custom\"\n * });\n * ```\n */\nexport function useDiscreteParameterResolution({\n children,\n options,\n paramId,\n parameter,\n defaultValue,\n label,\n midiResolution = 7,\n midiMapping = \"spread\",\n}: UseDiscreteParameterResolutionProps): UseDiscreteParameterResolutionResult {\n return useMemo(() => {\n // Build visual content map from children (always extract visual content if children provided)\n const optionEls = React.Children.toArray(children).filter(\n React.isValidElement\n ) as React.ReactElement<OptionViewProps>[];\n\n const visualContentMap = new Map<string | number, React.ReactNode>();\n\n optionEls.forEach((child, index) => {\n const val = child.props.value !== undefined ? child.props.value : index;\n if (child.props.children) {\n visualContentMap.set(val, child.props.children);\n }\n });\n\n // Determine parameter model\n let param: DiscreteParameter;\n let effectiveDefaultValue: string | number;\n\n if (parameter) {\n // Strict mode: use provided parameter\n param = parameter;\n effectiveDefaultValue =\n defaultValue !== undefined\n ? defaultValue\n : (parameter.defaultValue ?? parameter.options[0]?.value ?? \"\");\n } else if (options && options.length > 0) {\n // Ad-hoc mode with options prop: use options for parameter model\n effectiveDefaultValue = defaultValue !== undefined ? defaultValue : options[0].value;\n\n param = {\n id: paramId ?? \"adhoc-discrete\",\n type: \"discrete\",\n name: label || \"\",\n options,\n defaultValue: effectiveDefaultValue,\n midiResolution,\n midiMapping,\n };\n } else {\n // Ad-hoc mode with children only: infer parameter model from children\n const getLabel = (child: React.ReactElement<OptionViewProps>, val: string | number): string => {\n if (child.props.label) return child.props.label;\n if (typeof child.props.children === \"string\") return child.props.children;\n if (typeof child.props.children === \"number\") return String(child.props.children);\n // Fallback for icons/elements: use the value key as string\n return String(val);\n };\n\n const inferredOptions = optionEls.map((child, index) => {\n const val = child.props.value !== undefined ? child.props.value : index;\n return {\n value: val,\n label: getLabel(child, val),\n };\n });\n\n if (inferredOptions.length === 0) {\n inferredOptions.push({ value: 0, label: \"None\" });\n }\n\n effectiveDefaultValue = defaultValue !== undefined ? defaultValue : inferredOptions[0].value;\n\n param = {\n id: paramId ?? \"adhoc-discrete\",\n type: \"discrete\",\n name: label || \"\",\n options: inferredOptions,\n defaultValue: effectiveDefaultValue,\n midiResolution,\n midiMapping,\n };\n }\n\n return {\n derivedParameter: param,\n visualContentMap: visualContentMap,\n effectiveDefaultValue,\n };\n }, [parameter, options, children, label, paramId, defaultValue, midiResolution, midiMapping]);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo, useCallback } from \"react\";\nimport classNames from \"classnames\";\nimport { CLASSNAMES } from \"@cutoff/audio-ui-core\";\nimport { AdaptiveBoxProps, AdaptiveBoxLogicalSizeProps, DiscreteControlProps, ControlComponent } from \"@/types\";\nimport AdaptiveBox from \"../AdaptiveBox\";\nimport { useAudioParameter } from \"@/hooks/useAudioParameter\";\nimport { useDiscreteInteraction } from \"@/hooks/useDiscreteInteraction\";\nimport { useDiscreteParameterResolution } from \"@/hooks/useDiscreteParameterResolution\";\n\nexport type DiscreteControlComponentProps<P extends object = Record<string, unknown>> =\n // Base Control Props (includes all DiscreteControlProps)\n DiscreteControlProps &\n // Layout props that configure AdaptiveBox behavior\n AdaptiveBoxProps &\n // Logical size props that override view component defaults\n AdaptiveBoxLogicalSizeProps & {\n /**\n * The Visualization Component.\n * Must adhere to ControlComponent contract.\n */\n view: ControlComponent<P>;\n\n /**\n * Props specific to the Visualization Component.\n */\n viewProps: P;\n\n /**\n * Content overlay (HTML) rendered over the SVG (e.g. text value, icons).\n * Rendered via AdaptiveBox.HtmlOverlay to avoid foreignObject issues.\n */\n htmlOverlay?: React.ReactNode;\n };\n\n/**\n * A Generic Discrete Control that connects a Data Model (DiscreteParameter)\n * to a Visualization View (ControlComponent).\n *\n * This component handles parameter resolution, value management, interaction handling,\n * and layout management for discrete controls (CycleButton, Selector, etc.).\n *\n * **Important: Options vs Children**\n *\n * - **`options` prop**: Defines the parameter model (value, label, midiValue). Used for parameter structure.\n * - **`children` (OptionView components)**: Provides visual content (ReactNodes) for rendering. Used for display.\n *\n * Supports four modes of operation:\n * 1. **Ad-Hoc Mode (Options prop)**: Model from `options` prop, visual from `children` (if provided) or default rendering\n * 2. **Ad-Hoc Mode (Children only)**: Model inferred from OptionView children, visual from children\n * 3. **Strict Mode (Parameter only)**: Model from `parameter` prop, visual via `renderOption` callback\n * 4. **Hybrid Mode (Parameter + Children)**: Model from `parameter` prop, visual from children (matched by value)\n *\n * @param props - Component props including parameter configuration, view component, and layout options\n * @returns Rendered discrete control component\n *\n * @example\n * ```tsx\n * // Ad-Hoc Mode with options prop\n * <DiscreteControl\n * value=\"sine\"\n * onChange={(e) => setValue(e.value)}\n * options={[\n * { value: \"sine\", label: \"Sine Wave\" },\n * { value: \"square\", label: \"Square Wave\" }\n * ]}\n * view={KnobView}\n * viewProps={{ color: \"blue\", thickness: 0.4 }}\n * />\n *\n * // Ad-Hoc Mode with children\n * <DiscreteControl\n * value=\"sine\"\n * onChange={(e) => setValue(e.value)}\n * view={KnobView}\n * viewProps={{ color: \"blue\", thickness: 0.4 }}\n * >\n * <OptionView value=\"sine\">Sine</OptionView>\n * <OptionView value=\"square\">Square</OptionView>\n * </DiscreteControl>\n * ```\n */\nexport function DiscreteControl<P extends object = Record<string, unknown>>(props: DiscreteControlComponentProps<P>) {\n const {\n view: View,\n viewProps,\n htmlOverlay,\n value,\n defaultValue,\n onChange,\n children,\n options,\n label,\n paramId,\n parameter,\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n viewBoxWidthUnits,\n viewBoxHeightUnits,\n labelHeightUnits,\n className,\n style,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n midiResolution,\n midiMapping,\n } = props;\n\n const { derivedParameter, effectiveDefaultValue } = useDiscreteParameterResolution({\n children,\n options,\n paramId,\n parameter,\n defaultValue,\n label,\n midiResolution,\n midiMapping,\n });\n\n const effectiveValue = value !== undefined ? value : effectiveDefaultValue;\n\n const { normalizedValue, setNormalizedValue, formattedValue, converter } = useAudioParameter(\n effectiveValue,\n onChange,\n derivedParameter\n );\n\n const handleValueChange = useCallback(\n (val: number | string) => {\n setNormalizedValue(converter.normalize(val));\n },\n [setNormalizedValue, converter]\n );\n\n const { handleClick, handleKeyDown, handleMouseDown } = useDiscreteInteraction({\n value: effectiveValue,\n options: derivedParameter.options,\n onValueChange: handleValueChange,\n disabled: !onChange,\n // Type cast needed because onClick prop expects React.MouseEvent<SVGSVGElement>\n // but hook accepts React.MouseEvent\n onClick: onClick\n ? (e: React.MouseEvent) => onClick(e as unknown as React.MouseEvent<SVGSVGElement>)\n : undefined,\n onMouseDown,\n onKeyDown: undefined, // DiscreteControl doesn't have onKeyDown prop, only uses hook handler\n });\n\n const effectiveLabel = label ?? derivedParameter.name;\n\n const componentClassNames = useMemo(() => {\n return classNames(className, CLASSNAMES.root, CLASSNAMES.container);\n }, [className]);\n\n const svgClassNames = useMemo(() => {\n return onChange || onClick ? CLASSNAMES.highlight : \"\";\n }, [onChange, onClick]);\n\n // Add clickable cursor when interactive (onChange or onClick)\n // Uses CSS variable for customizable cursor type\n const svgStyle = useMemo(\n () => ({\n ...(onClick || onChange ? { cursor: \"var(--audioui-cursor-clickable)\" as const } : {}),\n }),\n [onClick, onChange]\n );\n\n return (\n <AdaptiveBox\n displayMode={displayMode ?? \"scaleToFit\"}\n labelMode={labelMode}\n labelOverflow={labelOverflow}\n className={componentClassNames}\n style={style}\n labelHeightUnits={labelHeightUnits ?? View.labelHeightUnits ?? 20}\n viewBoxWidth={viewBoxWidthUnits ?? View.viewBox.width}\n viewBoxHeight={viewBoxHeightUnits ?? View.viewBox.height}\n >\n <AdaptiveBox.Svg\n className={svgClassNames}\n style={svgStyle}\n onClick={handleClick}\n onMouseDown={handleMouseDown}\n onKeyDown={handleKeyDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n tabIndex={0}\n role=\"spinbutton\"\n aria-valuenow={typeof effectiveValue === \"number\" ? effectiveValue : undefined}\n aria-valuetext={formattedValue}\n aria-label={effectiveLabel}\n >\n <View normalizedValue={normalizedValue} {...viewProps} />\n </AdaptiveBox.Svg>\n {htmlOverlay && <AdaptiveBox.HtmlOverlay>{htmlOverlay}</AdaptiveBox.HtmlOverlay>}\n {effectiveLabel && (\n <AdaptiveBox.Label position={labelPosition} align={labelAlign ?? \"center\"}>\n {effectiveLabel}\n </AdaptiveBox.Label>\n )}\n </AdaptiveBox>\n );\n}\n\nexport default React.memo(DiscreteControl) as typeof DiscreteControl;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport classNames from \"classnames\";\nimport DiscreteControl from \"@/primitives/controls/DiscreteControl\";\nimport KnobView from \"./KnobView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, DiscreteControlProps, ThemableProps } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { useDiscreteParameterResolution } from \"@/hooks/useDiscreteParameterResolution\";\nimport { DEFAULT_ROUNDNESS, CLASSNAMES, clampNormalized } from \"@cutoff/audio-ui-core\";\nimport { useThemableProps } from \"@/hooks/useThemableProps\";\n\n/**\n * Default openness for knob ring in degrees (matches ValueRing default)\n */\nconst DEFAULT_OPENNESS = 90;\n\n/**\n * Default rotation angle offset in degrees (matches ValueRing default)\n */\nconst DEFAULT_ROTATION = 0;\n\nconst CONTENT_WRAPPER_STYLE: React.CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontSize: \"22cqmin\",\n fontWeight: \"500\",\n color: \"var(--audioui-text-color)\",\n cursor: \"inherit\",\n};\n\nconst ICON_WRAPPER_STYLE: React.CSSProperties = {\n width: \"50cqmin\",\n height: \"50cqmin\",\n};\n\n/**\n * Props for the CycleButton component\n */\nexport type CycleButtonProps = DiscreteControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n ThemableProps & {\n /** Custom renderer for options (used when parameter is provided but no children map exists) */\n renderOption?: (option: { value: string | number; label: string }) => React.ReactNode;\n /** Thickness of the stroke (normalized 0.0-1.0, maps to 1-20). Used by rotary variant. */\n thickness?: number;\n /** Openness of the ring in degrees (value between 0-360º; 0º: closed; 90º: 3/4 open; 180º: half-circle;)\n * @default 90\n */\n openness?: number;\n /** Optional rotation angle offset in degrees\n * @default 0\n */\n rotation?: number;\n };\n\n/**\n * A discrete interaction control that cycles through a set of discrete options.\n *\n * This control supports discrete interaction only (click to cycle, keyboard to step).\n * It does not support continuous interaction (drag/wheel).\n *\n * **Important: Options vs Children**\n *\n * - **`options` prop**: Defines the parameter model (value, label, midiValue). Used for parameter structure.\n * - **`children` (OptionView components)**: Provides visual content (ReactNodes) for rendering. Used for display.\n *\n * Supports multiple visual variants (rotary knob-style, LED indicators, etc.) and\n * four modes of operation:\n * 1. **Ad-Hoc Mode (Options prop)**: Model from `options` prop, visual from `children` (if provided) or default rendering\n * 2. **Ad-Hoc Mode (Children only)**: Model inferred from OptionView children, visual from children\n * 3. **Strict Mode (Parameter only)**: Model from `parameter` prop, visual via `renderOption` callback\n * 4. **Hybrid Mode (Parameter + Children)**: Model from `parameter` prop, visual from children (matched by value)\n *\n * @param props - Component props\n * @returns Rendered CycleButton component\n *\n * @example\n * ```tsx\n * // Ad-Hoc Mode with options prop (data-driven)\n * <CycleButton\n * options={[\n * { value: \"sine\", label: \"Sine Wave\" },\n * { value: \"square\", label: \"Square Wave\" }\n * ]}\n * />\n *\n * // Ad-Hoc Mode with children (visual content)\n * <CycleButton defaultValue=\"sine\" label=\"Waveform\">\n * <OptionView value=\"sine\"><SineIcon /></OptionView>\n * <OptionView value=\"square\"><SquareIcon /></OptionView>\n * </CycleButton>\n *\n * // Hybrid: options for model, children for visuals\n * <CycleButton\n * options={[\n * { value: \"sine\", label: \"Sine Wave\", midiValue: 0 },\n * { value: \"square\", label: \"Square Wave\", midiValue: 1 }\n * ]}\n * >\n * <OptionView value=\"sine\"><SineIcon /></OptionView>\n * <OptionView value=\"square\"><SquareIcon /></OptionView>\n * </CycleButton>\n *\n * // Strict Mode with custom renderer\n * <CycleButton\n * parameter={waveformParam}\n * renderOption={(opt) => <Icon name={opt.value} />}\n * />\n * ```\n */\nfunction CycleButton({\n value,\n defaultValue,\n onChange,\n renderOption,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n labelHeightUnits,\n color,\n roundness,\n thickness = 0.4,\n openness = DEFAULT_OPENNESS,\n rotation = DEFAULT_ROTATION,\n parameter,\n paramId,\n options,\n midiResolution,\n midiMapping,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n children,\n}: CycleButtonProps) {\n const { style: themableStyle, clampedRoundness } = useThemableProps({\n color,\n roundness,\n style,\n });\n\n // Clamp thickness if provided\n const clampedThickness = thickness !== undefined ? clampNormalized(thickness) : undefined;\n\n // Get visualContentMap and derivedParameter for content rendering.\n // Note: DiscreteControl also calls useDiscreteParameterResolution internally for parameter resolution,\n // but we need visualContentMap and derivedParameter here to render the HTML overlay content\n // (icons, text, or custom renderOption output) based on the current value.\n const { visualContentMap, derivedParameter, effectiveDefaultValue } = useDiscreteParameterResolution({\n children,\n options,\n paramId,\n parameter,\n defaultValue,\n label,\n midiResolution,\n midiMapping,\n });\n\n const effectiveValue = value !== undefined ? value : effectiveDefaultValue;\n\n // Get formattedValue for fallback display\n const formattedValue = useMemo(() => {\n const opt = derivedParameter.options.find((opt) => opt.value === effectiveValue);\n return opt?.label ?? String(effectiveValue);\n }, [derivedParameter.options, effectiveValue]);\n\n const content = useMemo(() => {\n const wrapContent = (node: React.ReactNode): React.ReactNode => {\n if (typeof node === \"string\" || typeof node === \"number\") {\n return node;\n }\n\n return (\n <div className={CLASSNAMES.iconWrapper} style={ICON_WRAPPER_STYLE}>\n {node}\n </div>\n );\n };\n\n if (visualContentMap && visualContentMap.has(effectiveValue)) {\n return wrapContent(visualContentMap.get(effectiveValue));\n }\n\n if (renderOption) {\n const opt = derivedParameter.options.find((opt) => opt.value === effectiveValue);\n if (opt) return wrapContent(renderOption(opt));\n }\n\n return formattedValue;\n }, [visualContentMap, effectiveValue, renderOption, derivedParameter.options, formattedValue]);\n\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"knob\");\n\n return (\n <DiscreteControl\n value={value}\n defaultValue={defaultValue}\n onChange={onChange}\n label={label}\n paramId={paramId}\n parameter={parameter}\n options={options}\n midiResolution={midiResolution}\n midiMapping={midiMapping}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n labelOverflow={labelOverflow}\n labelHeightUnits={labelHeightUnits}\n className={classNames(sizeClassName, className)}\n style={{ ...sizeStyle, ...themableStyle }}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n view={KnobView}\n viewProps={{\n bipolar: false,\n thickness: clampedThickness,\n roundness: clampedRoundness ?? DEFAULT_ROUNDNESS,\n openness: openness,\n rotation: rotation,\n color: color ?? \"var(--audioui-primary-color)\",\n }}\n htmlOverlay={<div style={CONTENT_WRAPPER_STYLE}>{content}</div>}\n >\n {children}\n </DiscreteControl>\n );\n}\n\nexport default React.memo(CycleButton);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, useMemo } from \"react\";\nimport { translateSliderRoundness } from \"@cutoff/audio-ui-core\";\nimport { DEFAULT_ROUNDNESS } from \"@cutoff/audio-ui-core\";\n\nexport type LinearStripProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Length of the strip */\n length: number;\n /** Rotation angle in degrees (0 = vertical, -90 or 270 = horizontal) */\n rotation?: number;\n /** Width of the strip (thickness) */\n thickness: number;\n /** Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string) */\n roundness?: number | string;\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n};\n\n/**\n * A reusable SVG primitive that renders a linear strip (rectangle) for linear controls.\n * The strip is positioned at a center point (cx, cy) and can be rotated to any angle.\n *\n * This component is designed to be used inside an <svg> element.\n *\n * @param {number} cx - X coordinate of the center point\n * @param {number} cy - Y coordinate of the center point\n * @param {number} length - Length of the strip (height of the rectangle)\n * @param {number} [rotation=0] - Rotation angle in degrees (0 = vertical, -90 or 270 = horizontal)\n * @param {number} thickness - Width of the strip (thickness of the rectangle)\n * @param {number | string} [roundness] - Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string)\n * @param {string} [className] - CSS class name\n * @param {CSSProperties} [style] - Inline styles\n * @returns {JSX.Element} SVG rect element representing the linear strip\n *\n * @example\n * ```tsx\n * // Vertical strip (default)\n * <LinearStrip cx={50} cy={150} length={260} thickness={6} />\n *\n * // Horizontal strip (rotated -90 degrees)\n * <LinearStrip cx={150} cy={50} length={260} thickness={6} rotation={-90} />\n * ```\n */\nfunction LinearStrip({\n cx,\n cy,\n length,\n rotation = 0,\n thickness,\n roundness = DEFAULT_ROUNDNESS,\n className,\n style,\n}: LinearStripProps) {\n // Calculate corner radius from roundness prop or CSS variable\n const cornerRadius = useMemo(() => {\n if (typeof roundness === \"string\") {\n // CSS variable - pass directly to SVG (browser will resolve it)\n return roundness;\n }\n // Numeric value - translate to legacy pixel range (0-20)\n const legacyRoundness = translateSliderRoundness(roundness ?? DEFAULT_ROUNDNESS);\n\n // If roundness is 0, use square corners\n if (legacyRoundness === 0) {\n return 0;\n }\n\n // Use the translated roundness value\n return legacyRoundness;\n }, [roundness]);\n\n // Calculate rectangle position and dimensions\n // The rectangle is centered at (cx, cy) with width=thickness and height=length\n // Then rotated around the center point\n const rectX = useMemo(() => {\n return cx - thickness / 2;\n }, [cx, thickness]);\n\n const rectY = useMemo(() => {\n return cy - length / 2;\n }, [cy, length]);\n\n // Build transform string for rotation\n const transform = useMemo(() => {\n if (rotation === 0) {\n return undefined;\n }\n // Negate rotation to match Radial components (positive = counter-clockwise)\n return `rotate(${-rotation} ${cx} ${cy})`;\n }, [rotation, cx, cy]);\n\n return (\n <rect\n style={{\n ...style,\n rx: cornerRadius,\n ry: cornerRadius,\n }}\n x={rectX}\n y={rectY}\n width={thickness}\n height={length}\n // Use 0 as fallback for older browsers that don't support CSS rx/ry\n // If cornerRadius is a number, we can use it directly\n rx={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n ry={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n transform={transform}\n className={className}\n />\n );\n}\n\nexport default React.memo(LinearStrip);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, useMemo } from \"react\";\nimport { translateSliderRoundness, DEFAULT_ROUNDNESS, calculateLinearPosition } from \"@cutoff/audio-ui-core\";\n\nexport type ValueStripProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Length of the strip */\n length: number;\n /** Rotation angle in degrees (0 = vertical, -90 or 270 = horizontal) */\n rotation?: number;\n /** Width of the strip (thickness) */\n thickness: number;\n /** Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string) */\n roundness?: number | string;\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Whether to start fill from center (bipolar mode) */\n bipolar?: boolean;\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n};\n\n/**\n * A reusable SVG primitive that renders the active (foreground) portion of a linear strip.\n * Designed to work in tandem with LinearStrip (background).\n *\n * The strip is positioned at a center point (cx, cy) and can be rotated to any angle.\n * - In unipolar mode (default), it fills from the \"bottom\" (relative to rotation) to the current value.\n * - In bipolar mode, it fills from the center to the current value.\n *\n * This component is designed to be used inside an <svg> element.\n *\n * @param {number} cx - X coordinate of the center point\n * @param {number} cy - Y coordinate of the center point\n * @param {number} length - Length of the strip\n * @param {number} [rotation=0] - Rotation angle in degrees (0 = vertical, -90 or 270 = horizontal)\n * @param {number} thickness - Width of the strip (thickness)\n * @param {number | string} [roundness] - Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string)\n * @param {number} normalizedValue - Normalized value between 0 and 1\n * @param {boolean} [bipolar=false] - Whether to start fill from center (bipolar mode)\n * @param {string} [className] - CSS class name\n * @param {CSSProperties} [style] - Inline styles\n * @returns {JSX.Element | null} SVG rect element representing the filled portion, or null if height is too small\n *\n * @example\n * ```tsx\n * // Vertical value strip (unipolar)\n * <ValueStrip cx={50} cy={150} length={260} thickness={6} normalizedValue={0.65} />\n *\n * // Horizontal value strip (bipolar)\n * <ValueStrip cx={150} cy={50} length={260} thickness={6} rotation={-90} normalizedValue={0.75} bipolar />\n * ```\n */\nfunction ValueStrip({\n cx,\n cy,\n length,\n rotation = 0,\n thickness,\n roundness = DEFAULT_ROUNDNESS,\n normalizedValue,\n bipolar = false,\n className,\n style,\n}: ValueStripProps) {\n // Calculate corner radius\n const cornerRadius = useMemo(() => {\n if (typeof roundness === \"string\") {\n return roundness;\n }\n const legacyRoundness = translateSliderRoundness(roundness ?? DEFAULT_ROUNDNESS);\n if (legacyRoundness === 0) return 0;\n return legacyRoundness;\n }, [roundness]);\n\n // Calculate dimensions of the filled rectangle in unrotated coordinate space\n // The rectangle will be rotated around (cx, cy) using the transform attribute\n // The rectangle is computed based on the cursor center position\n const rectProps = useMemo(() => {\n // Get cursor Y position (cursor is always centered horizontally at cx)\n // Note: cursor position is independent of bipolar mode - bipolar only affects rectangle drawing\n const cursorY = calculateLinearPosition(cy, length, normalizedValue);\n\n // Base X position (unrotated) - centered horizontally around cx\n const x = cx - thickness / 2;\n const width = thickness;\n\n let y, height;\n\n if (bipolar) {\n // Bipolar mode: rectangle extends from center (cy) to cursor center (cursorY)\n // Height is the distance from center to cursor\n height = Math.abs(cy - cursorY);\n // Y position is the minimum of center and cursor (top edge of rectangle)\n y = Math.min(cy, cursorY);\n } else {\n // Unipolar mode: rectangle extends from bottom (cy + length/2) to cursor center (cursorY)\n const bottomY = cy + length / 2;\n // Height is the distance from bottom to cursor\n height = bottomY - cursorY;\n // Y position is at cursor (top edge of rectangle)\n y = cursorY;\n }\n\n return { x, y, width, height };\n }, [cx, cy, length, thickness, normalizedValue, bipolar]);\n\n // Build rotation transform\n const transform = useMemo(() => {\n if (rotation === 0) return undefined;\n // Negate rotation to match Radial components (positive = counter-clockwise)\n return `rotate(${-rotation} ${cx} ${cy})`;\n }, [rotation, cx, cy]);\n\n // Optimization: Don't render if height is 0 (or very small)\n if (rectProps.height <= 0.001) {\n return null;\n }\n\n return (\n <rect\n style={{\n ...style,\n rx: cornerRadius,\n ry: cornerRadius,\n }}\n x={rectProps.x}\n y={rectProps.y}\n width={rectProps.width}\n height={rectProps.height}\n // Use 0 as fallback for older browsers that don't support CSS rx/ry\n rx={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n ry={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n transform={transform}\n className={className}\n />\n );\n}\n\nexport default React.memo(ValueStrip);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, useMemo } from \"react\";\nimport { translateSliderRoundness, DEFAULT_ROUNDNESS, calculateLinearPosition } from \"@cutoff/audio-ui-core\";\n\nexport type LinearCursorProps = {\n /** X coordinate of the center point of the strip */\n cx: number;\n /** Y coordinate of the center point of the strip */\n cy: number;\n /** Length of the strip */\n length: number;\n /** Rotation angle in degrees (0 = vertical, -90 or 270 = horizontal) */\n rotation?: number;\n /** Normalized value between 0 and 1, driving the cursor position */\n normalizedValue: number;\n /** Width of the cursor (x axis) */\n width: number;\n /** Aspect ratio of the cursor (numeric value, e.g., 1 = 1:1, 1.5 = 1.5:1). Height = width / aspectRatio. Ignored when imageHref is provided. */\n aspectRatio: number;\n /** Optional image URL to display as cursor. When provided, roundness and aspectRatio are ignored (image preserves its natural aspect ratio). */\n imageHref?: string;\n /** Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string). 1.0 = ellipse/circle, 0.0-1.0 (excl.) = rounded rectangle. Ignored when imageHref is provided. */\n roundness?: number | string;\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n};\n\n/**\n * A reusable SVG primitive that renders a cursor that slides along a linear strip.\n * The cursor position is driven by a normalized value (0.0 to 1.0).\n *\n * The cursor slides along a virtual bar centered at (cx, cy) with a defined length.\n * - normalizedValue 0.0: cursor at the bottom of the virtual bar\n * - normalizedValue 1.0: cursor at the top of the virtual bar\n *\n * The rotation prop represents the rotation of the virtual bar (not the cursor itself).\n * The cursor rotates around the strip center (cx, cy) along with the bar.\n *\n * The cursor can be rendered as:\n * - An image (via imageHref)\n * - An SVG shape (rectangle or ellipse based on roundness)\n *\n * This component is designed to be used inside an <svg> element.\n *\n * @param {number} cx - X coordinate of the center point of the strip\n * @param {number} cy - Y coordinate of the center point of the strip\n * @param {number} length - Length of the strip\n * @param {number} [rotation=0] - Rotation angle in degrees of the virtual bar (0 = vertical, -90 or 270 = horizontal)\n * @param {number} normalizedValue - Normalized value between 0 and 1, driving the cursor position\n * @param {number} width - Width of the cursor (x axis)\n * @param {number} aspectRatio - Aspect ratio of the cursor (numeric value, e.g., 1 = 1:1, 1.5 = 1.5:1). Height = width / aspectRatio. Ignored when imageHref is provided.\n * @param {string} [imageHref] - Optional image URL to display as cursor. When provided, roundness and aspectRatio are ignored (image preserves its natural aspect ratio).\n * @param {number | string} [roundness] - Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string). 1.0 = ellipse/circle. Ignored when imageHref is provided.\n * @param {string} [className] - CSS class name\n * @param {CSSProperties} [style] - Inline styles\n * @returns {JSX.Element} SVG element representing the cursor\n *\n * @example\n * ```tsx\n * // Vertical cursor (rectangle)\n * <LinearCursor cx={50} cy={150} length={260} normalizedValue={0.65} width={6} aspectRatio={1} />\n *\n * // Horizontal cursor (ellipse)\n * <LinearCursor cx={150} cy={50} length={260} rotation={-90} normalizedValue={0.75} width={8} aspectRatio={1} roundness={1} />\n *\n * // Image-based cursor (aspectRatio and roundness ignored, image preserves natural aspect ratio)\n * <LinearCursor cx={50} cy={150} length={260} normalizedValue={0.5} width={20} aspectRatio={1} imageHref=\"/cursor.png\" />\n * ```\n */\nfunction LinearCursor({\n cx,\n cy,\n length,\n rotation = 0,\n normalizedValue,\n width,\n aspectRatio,\n imageHref,\n roundness = DEFAULT_ROUNDNESS,\n className,\n style,\n}: LinearCursorProps) {\n // Calculate cursor position along the strip (no memo - normalizedValue changes frequently)\n const cursorY = calculateLinearPosition(cy, length, normalizedValue);\n\n // Calculate height from width and aspect ratio (only used for non-image cursors)\n const height = useMemo(() => {\n return width / aspectRatio;\n }, [width, aspectRatio]);\n\n // Calculate corner radius from roundness prop or CSS variable\n const cornerRadius = useMemo(() => {\n if (typeof roundness === \"string\") {\n // CSS variable - pass directly to SVG (browser will resolve it)\n return roundness;\n }\n // Numeric value - translate to legacy pixel range (0-20)\n const legacyRoundness = translateSliderRoundness(roundness ?? DEFAULT_ROUNDNESS);\n\n // If roundness is 0, use square corners\n if (legacyRoundness === 0) {\n return 0;\n }\n\n // Use the translated roundness value\n return legacyRoundness;\n }, [roundness]);\n\n // Determine if we should render an ellipse (roundness = 1.0) or rectangle\n const isEllipse = useMemo(() => {\n if (typeof roundness === \"string\") {\n return false; // CSS variables can't be compared, default to rectangle\n }\n // Check if normalized roundness is exactly 1.0 (ellipse/circle)\n return (roundness ?? DEFAULT_ROUNDNESS) >= 1.0;\n }, [roundness]);\n\n // Calculate position for the cursor (centered at cursorY, horizontally centered at cx)\n const cursorX = useMemo(() => {\n return cx - width / 2;\n }, [cx, width]);\n\n // Calculate Y position (no memo - depends on cursorY which changes frequently)\n const cursorYPos = cursorY - height / 2;\n\n // Build transform string for rotation\n // The rotation represents the rotation of the virtual bar (strip), so the cursor rotates\n // around the center of the strip (cx, cy), not around the cursor's current position\n const transform = useMemo(() => {\n if (rotation === 0) {\n return undefined;\n }\n // Negate rotation to match Radial components (positive = counter-clockwise)\n // Rotate around the strip center (cx, cy)\n return `rotate(${-rotation} ${cx} ${cy})`;\n }, [rotation, cx, cy]);\n\n // Render image if provided (aspectRatio and roundness are ignored - image preserves natural aspect ratio)\n if (imageHref) {\n // Use a square bounding box (width x width) and let preserveAspectRatio maintain the image's natural aspect ratio\n // The image will be centered and scaled to fit within the square while preserving its aspect ratio\n return (\n <image\n href={imageHref}\n x={cx - width / 2}\n y={cursorY - width / 2}\n width={width}\n height={width}\n transform={transform}\n className={className}\n style={style}\n preserveAspectRatio=\"xMidYMid meet\"\n />\n );\n }\n\n // Render ellipse if roundness = 1.0\n if (isEllipse) {\n return (\n <ellipse\n cx={cx}\n cy={cursorY}\n rx={width / 2}\n ry={height / 2}\n transform={transform}\n className={className}\n style={style}\n />\n );\n }\n\n // Render rounded rectangle\n return (\n <rect\n x={cursorX}\n y={cursorYPos}\n width={width}\n height={height}\n // Use 0 as fallback for older browsers that don't support CSS rx/ry\n rx={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n ry={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n transform={transform}\n className={className}\n style={{\n ...style,\n rx: cornerRadius,\n ry: cornerRadius,\n }}\n />\n );\n}\n\nexport default React.memo(LinearCursor);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport { translateSliderThickness, CSS_VARS } from \"@cutoff/audio-ui-core\";\nimport { DEFAULT_ROUNDNESS } from \"@cutoff/audio-ui-core\";\nimport { ControlComponent, SliderVariant, SliderCursorSize } from \"@/types\";\nimport LinearStrip from \"@/primitives/svg/LinearStrip\";\nimport ValueStrip from \"@/primitives/svg/ValueStrip\";\nimport LinearCursor from \"@/primitives/svg/LinearCursor\";\n\n// Constants for cursor size calculations (future use for Tick and Label sizes)\nconst TICK_TRACK_SHIFT = 30;\nconst LABEL_TRACK_SHIFT = 30;\n\n/**\n * Props for the SliderView component\n */\nexport type SliderViewProps = {\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Whether to start fill from center (bipolar mode) */\n bipolar?: boolean;\n /** Visual variant of the slider */\n variant?: SliderVariant;\n /** Orientation of the slider */\n orientation?: \"horizontal\" | \"vertical\";\n /** Thickness of the slider (normalized 0.0-1.0, maps to 1-50) */\n thickness?: number;\n /** Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string) */\n roundness?: number | string;\n /** Color prop (kept for API compatibility, but colors are read from CSS variables) */\n color?: string;\n /** Cursor size option - determines which component's width is used for the cursor */\n cursorSize?: SliderCursorSize;\n /** Aspect ratio of the cursor */\n cursorAspectRatio?: number;\n /** Overrides the roundness factor of the cursor. Defaults to `roundness` */\n cursorRoundness?: number | string;\n /** Optional image URL to display as cursor */\n cursorImageHref?: string;\n /** Optional CSS class name for the cursor */\n cursorClassName?: string;\n /** Optional inline styles for the cursor */\n cursorStyle?: React.CSSProperties;\n /** Additional CSS class name */\n className?: string;\n /** Content to render (unused in default slider but required by generic props) */\n children?: React.ReactNode;\n};\n\n/**\n * Pure SVG presentation component for a slider.\n * Renders background and filled rectangles based on normalized value.\n *\n * Colors are read from CSS variables (`--audioui-primary-color`, `--audioui-primary-50`)\n * which are set by the parent Slider component based on the `color` prop.\n *\n * **Performance Optimizations:**\n * - Memoized calculations: thickness translation, layout coordinates, strip padding, cursor dimensions, style objects\n * - Constants moved outside component to avoid unnecessary dependency array entries\n * - Simple boolean checks and nullish coalescing are not memoized (minimal overhead)\n * - Style objects memoized to prevent unnecessary re-renders of child components\n *\n * @param {number} normalizedValue - Value between 0 and 1\n * @param {boolean} [bipolar=false] - Whether to fill from center (bipolar mode)\n * @param {SliderVariant} [variant=\"abstract\"] - Visual variant of the slider\n * @param {\"horizontal\" | \"vertical\"} [orientation=\"vertical\"] - Orientation of the slider\n * @param {number} [thickness=0.4] - Normalized thickness 0.0-1.0 (maps to 1-50)\n * @param {number | string} [roundness] - Normalized roundness 0.0-1.0 (maps to 0-20) or CSS variable string\n * @param {string} [color] - Color prop (kept for API compatibility, but not used - CSS variables are used instead)\n * @param {SliderCursorSize} [cursorSize] - Cursor size option (None, Strip, Track, Tick, Label)\n * @param {number} [cursorAspectRatio] - Aspect ratio of the cursor\n * @param {number | string} [cursorRoundness] - Overrides cursor roundness (defaults to roundness prop)\n * @param {string} [cursorImageHref] - Optional image URL for cursor\n * @param {string} [cursorClassName] - Optional CSS class name for cursor\n * @param {React.CSSProperties} [cursorStyle] - Optional inline styles for cursor\n * @param {string} [className] - Optional CSS class name\n * @returns {JSX.Element} SVG group element containing background and foreground strips\n */\nfunction SliderView({\n normalizedValue,\n bipolar = false,\n variant = \"abstract\",\n orientation = \"vertical\",\n thickness = 0.4,\n roundness = DEFAULT_ROUNDNESS,\n cursorSize,\n cursorAspectRatio,\n cursorRoundness,\n cursorImageHref,\n cursorClassName,\n cursorStyle,\n className,\n}: SliderViewProps): JSX.Element {\n // Translate normalized thickness to pixel range (1-50)\n const effectiveThickness = useMemo(() => {\n return translateSliderThickness(thickness);\n }, [thickness]);\n\n // Determine layout based on orientation\n const { cx, cy, rotation } = useMemo(() => {\n if (orientation === \"vertical\") {\n return {\n cx: 50,\n cy: 150,\n rotation: 0,\n };\n } else {\n return {\n cx: 150,\n cy: 50,\n rotation: -90,\n };\n }\n }, [orientation]);\n\n // Calculate strip padding for trackfull variant (thickness-dependent)\n const stripPadding = useMemo(() => {\n return variant === \"trackfull\" ? 25 * thickness + 5 : 0;\n }, [variant, thickness]);\n\n // Calculate cursor width based on cursorSize prop\n // If cursorSize is not provided, maintain backward compatibility (old behavior)\n const cursorWidth = useMemo(() => {\n // If cursorSize is explicitly \"None\", don't render cursor\n if (cursorSize === \"None\") {\n return undefined;\n }\n\n // If cursorSize is provided, use it to calculate width\n if (cursorSize) {\n switch (cursorSize) {\n case \"Strip\":\n // Width of the ValueStrip (if variant supports it)\n return variant !== \"stripless\" ? effectiveThickness - stripPadding : effectiveThickness;\n case \"Track\":\n // Width of the LinearStrip (track)\n return effectiveThickness;\n case \"Tick\":\n // Width of the TickStrip (future use - for now, use track width)\n return effectiveThickness + TICK_TRACK_SHIFT;\n case \"Label\":\n // Entire width of the Slider (future use - for now, use track width)\n return effectiveThickness + TICK_TRACK_SHIFT + LABEL_TRACK_SHIFT;\n default:\n return undefined;\n }\n }\n\n // Backward compatibility: if cursorSize is not provided, use old behavior\n // (render cursor when variant !== \"abstract\" with old width calculation)\n return variant !== \"abstract\" ? effectiveThickness - stripPadding : undefined;\n }, [cursorSize, effectiveThickness, stripPadding, variant]);\n\n // Determine if cursor should be rendered\n const shouldRenderCursor = cursorWidth !== undefined;\n\n // Determine cursor roundness (defaults to roundness prop if not specified)\n const effectiveCursorRoundness = cursorRoundness ?? roundness;\n\n // Calculate cursor height to adjust the length (prevent cursor from going beyond strip bounds)\n const cursorHeight = useMemo(() => {\n if (!shouldRenderCursor || cursorWidth === undefined) {\n return 0;\n }\n // For images, LinearCursor uses a square bounding box (width x width)\n // TODO: Calculate actual image aspect ratio: (cursorWidth / imageWidth) * imageHeight\n // This requires loading the image to get its dimensions, which would add async complexity\n // Current implementation uses cursorWidth as height (square) as a reasonable approximation\n if (cursorImageHref) {\n return cursorWidth;\n }\n // For non-image cursors, height = width / aspectRatio\n return cursorWidth / (cursorAspectRatio ?? 1);\n }, [shouldRenderCursor, cursorWidth, cursorAspectRatio, cursorImageHref]);\n\n // Calculate effective length for cursor (subtract cursor height to keep cursor within bounds)\n const cursorLength = useMemo(() => {\n return 260 - cursorHeight;\n }, [cursorHeight]);\n\n // Calculate ValueStrip length (reduced by padding for trackfull variant)\n const valueStripLength = useMemo(() => {\n return 260 - stripPadding;\n }, [stripPadding]);\n\n // Memoize styles to prevent unnecessary re-renders of child components\n const bgStripStyle = useMemo(\n () => ({\n fill: variant === \"abstract\" ? `var(${CSS_VARS.primary20})` : `var(${CSS_VARS.sliderTrackColor})`,\n }),\n [variant]\n );\n\n const valueStripStyle = useMemo(\n () => ({\n fill: `var(${CSS_VARS.sliderStripColor})`,\n }),\n []\n );\n\n const cursorStyleMemo = useMemo(\n () => ({\n fill: `var(${CSS_VARS.sliderCursorColor})`,\n stroke: `var(${CSS_VARS.sliderCursorBorderColor})`,\n strokeWidth: `var(${CSS_VARS.sliderCursorBorderWidth})`,\n ...cursorStyle,\n }),\n [cursorStyle]\n );\n\n return (\n <g className={className}>\n {/* Background Strip */}\n <LinearStrip\n cx={cx}\n cy={cy}\n length={260}\n thickness={effectiveThickness}\n rotation={rotation}\n roundness={roundness}\n style={bgStripStyle}\n />\n\n {/* Foreground Value Strip */}\n {variant !== \"stripless\" ? (\n <ValueStrip\n cx={cx}\n cy={cy}\n length={valueStripLength}\n thickness={effectiveThickness - stripPadding}\n rotation={rotation}\n roundness={roundness}\n normalizedValue={normalizedValue}\n bipolar={bipolar}\n style={valueStripStyle}\n />\n ) : undefined}\n\n {/* Cursor */}\n {shouldRenderCursor && cursorWidth !== undefined ? (\n <LinearCursor\n cx={cx}\n cy={cy}\n length={cursorLength}\n rotation={rotation}\n normalizedValue={normalizedValue}\n width={cursorWidth}\n aspectRatio={cursorAspectRatio ?? 1}\n roundness={effectiveCursorRoundness}\n imageHref={cursorImageHref}\n className={cursorClassName}\n style={cursorStyleMemo}\n />\n ) : undefined}\n </g>\n );\n}\n\nconst SLIDER_VIEWBOX = {\n vertical: {\n width: 100,\n height: 300,\n },\n horizontal: {\n width: 300,\n height: 100,\n },\n} as const;\n\n/**\n * ViewBox dimensions for the SliderView component.\n * The parent component should use these values when setting up the SVG container.\n * Dimensions vary based on orientation.\n */\nSliderView.viewBox = SLIDER_VIEWBOX;\n\n/**\n * Props for specialized slider views (without normalizedValue, children, className, style, orientation)\n */\ntype SpecializedSliderProps = Omit<\n SliderViewProps,\n \"normalizedValue\" | \"children\" | \"className\" | \"style\" | \"orientation\"\n>;\n\n/**\n * Specialized Vertical Slider for Generic Control System\n */\nfunction VerticalSliderViewComponent(\n props: SpecializedSliderProps & {\n normalizedValue: number;\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n }\n) {\n return <SliderView {...props} orientation=\"vertical\" />;\n}\n\nconst VerticalSliderViewMemo = React.memo(VerticalSliderViewComponent);\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(VerticalSliderViewMemo as any).viewBox = SLIDER_VIEWBOX.vertical;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(VerticalSliderViewMemo as any).labelHeightUnits = 40;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(VerticalSliderViewMemo as any).interaction = {\n mode: \"both\" as const,\n direction: \"vertical\" as const,\n};\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(VerticalSliderViewMemo as any).title = \"Vertical Slider\";\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(VerticalSliderViewMemo as any).description = \"A vertical fader control with linear fill indicator\";\n\n// Cast the memoized components to the ControlComponent type which includes the static properties\nconst VerticalSliderView = VerticalSliderViewMemo as unknown as ControlComponent<SpecializedSliderProps>;\n\n/**\n * Specialized Horizontal Slider for Generic Control System\n */\nfunction HorizontalSliderViewComponent(\n props: SpecializedSliderProps & {\n normalizedValue: number;\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n }\n) {\n return <SliderView {...props} orientation=\"horizontal\" />;\n}\n\nconst HorizontalSliderViewMemo = React.memo(HorizontalSliderViewComponent);\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(HorizontalSliderViewMemo as any).viewBox = SLIDER_VIEWBOX.horizontal;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(HorizontalSliderViewMemo as any).labelHeightUnits = 40;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(HorizontalSliderViewMemo as any).interaction = {\n mode: \"both\" as const,\n direction: \"horizontal\" as const,\n};\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(HorizontalSliderViewMemo as any).title = \"Horizontal Slider\";\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(HorizontalSliderViewMemo as any).description = \"A horizontal fader control with linear fill indicator\";\n\nconst HorizontalSliderView = HorizontalSliderViewMemo as unknown as ControlComponent<SpecializedSliderProps>;\n\nexport { VerticalSliderView, HorizontalSliderView };\nexport default React.memo(SliderView);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport { VerticalSliderView, HorizontalSliderView } from \"./SliderView\";\nimport ContinuousControl from \"@/primitives/controls/ContinuousControl\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport {\n AdaptiveBoxProps,\n AdaptiveSizeProps,\n ContinuousControlProps,\n ThemableProps,\n SliderVariant,\n SliderCursorSize,\n ValueLabelMode,\n} from \"@/types\";\nimport { clampNormalized } from \"@cutoff/audio-ui-core\";\nimport { useThemableProps } from \"@/hooks/useThemableProps\";\n\n/**\n * Props for the Slider component (built-in control with theming support)\n */\nexport type SliderProps = ContinuousControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n ThemableProps & {\n /** Visual variant of the slider\n * @default \"abstract\"\n */\n variant?: SliderVariant;\n /** Orientation of the slider\n * @default 'vertical' */\n orientation?: \"horizontal\" | \"vertical\";\n /** Thickness of the slider (normalized 0.0-1.0, maps to 1-50)\n * @default 0.4 */\n thickness?: number;\n /**\n * Controls how the label and value are displayed.\n * - \"labelOnly\": Always shows the label (default)\n * - \"valueOnly\": Always shows the value\n * - \"interactive\": Shows label normally, but temporarily swaps to value during interaction\n * @default \"labelOnly\"\n */\n valueAsLabel?: ValueLabelMode;\n /** Cursor size option - determines which component's width is used for the cursor */\n cursorSize?: SliderCursorSize;\n /** Aspect ratio of the cursor */\n cursorAspectRatio?: number;\n /** Overrides the roundness factor of the cursor. Defaults to `roundness` */\n cursorRoundness?: number | string;\n /** Optional image URL to display as cursor */\n cursorImageHref?: string;\n /** Optional CSS class name for the cursor */\n cursorClassName?: string;\n /** Optional inline styles for the cursor */\n cursorStyle?: React.CSSProperties;\n };\n\n/**\n * A slider component for audio applications.\n * Supports both horizontal and vertical orientations with continuous value adjustment.\n *\n * Features:\n * - Multiple visual variants (abstract, trackless, trackfull, stripless)\n * - Configurable orientation (horizontal or vertical)\n * - Bipolar mode support (centered at zero, grows in both directions)\n * - Customizable thickness and roundness\n * - Full theming support via CSS variables\n * - Adaptive sizing or fixed size variants\n * - Supports drag, wheel, and keyboard interactions\n * - Custom value formatting\n *\n * @example\n * ```tsx\n * // Basic vertical slider\n * <Slider value={50} min={0} max={100} onChange={(e) => setValue(e.value)} />\n *\n * // Horizontal bipolar slider (pan control)\n * <Slider\n * orientation=\"horizontal\"\n * bipolar\n * value={0}\n * min={-100}\n * max={100}\n * label=\"Pan\"\n * />\n *\n * // With parameter model\n * <Slider\n * parameter={volumeParam}\n * value={volume}\n * onChange={handleVolumeChange}\n * />\n * ```\n */\nfunction Slider({\n orientation = \"vertical\",\n min,\n max,\n step,\n bipolar = false,\n value,\n onChange,\n valueFormatter,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n labelHeightUnits,\n color,\n roundness,\n variant = \"abstract\",\n thickness = 0.5,\n parameter,\n unit,\n scale,\n paramId,\n interactionMode,\n interactionDirection,\n interactionSensitivity,\n valueAsLabel = \"labelOnly\",\n midiResolution,\n defaultValue,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n cursorSize,\n cursorAspectRatio,\n cursorRoundness,\n cursorImageHref,\n cursorClassName,\n cursorStyle,\n}: SliderProps) {\n const { style: themableStyle } = useThemableProps({\n color,\n roundness,\n style,\n });\n\n // Clamp thickness if provided\n const clampedThickness = thickness !== undefined ? clampNormalized(thickness) : undefined;\n\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"slider\", orientation);\n\n const mergedClassName = classNames(sizeClassName, className);\n\n // Select view component based on orientation (different SVG implementations for optimal rendering)\n const ViewComponent = orientation === \"vertical\" ? VerticalSliderView : HorizontalSliderView;\n\n return (\n <ContinuousControl\n min={min}\n max={max}\n step={step}\n bipolar={bipolar}\n value={value}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n labelOverflow={labelOverflow}\n labelHeightUnits={labelHeightUnits}\n className={mergedClassName}\n style={{ ...sizeStyle, ...themableStyle }}\n onChange={onChange}\n paramId={paramId}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n parameter={parameter}\n unit={unit}\n scale={scale}\n midiResolution={midiResolution}\n defaultValue={defaultValue}\n interactionMode={interactionMode}\n interactionDirection={interactionDirection}\n interactionSensitivity={interactionSensitivity}\n valueFormatter={valueFormatter}\n valueAsLabel={valueAsLabel}\n view={ViewComponent}\n viewProps={{\n color: color ?? \"var(--audioui-primary-color)\",\n variant: variant,\n thickness: clampedThickness,\n roundness: roundness ?? \"var(--audioui-roundness-slider)\",\n bipolar: bipolar,\n cursorSize: cursorSize,\n cursorAspectRatio: cursorAspectRatio,\n cursorRoundness: cursorRoundness,\n cursorImageHref: cursorImageHref,\n cursorClassName: cursorClassName,\n cursorStyle: cursorStyle,\n }}\n />\n );\n}\n\nexport default React.memo(Slider);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties } from \"react\";\n\nexport type FilmstripImageProps = {\n /** X coordinate of the top-left corner (default: 0) */\n x?: number;\n /** Y coordinate of the top-left corner (default: 0) */\n y?: number;\n /** Width of a SINGLE frame */\n frameWidth: number;\n /** Height of a SINGLE frame */\n frameHeight: number;\n /** Total number of frames in the strip */\n frameCount: number;\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** URL to the sprite sheet/filmstrip image */\n imageHref: string;\n /** Orientation of the strip (default: \"vertical\") */\n orientation?: \"vertical\" | \"horizontal\";\n /** Optional frame rotation in degrees (default: 0) */\n frameRotation?: number;\n /**\n * If true, inverts the normalized value (0.0 -> 1.0 and 1.0 -> 0.0).\n * Useful for filmstrips where frame 0 represents \"on\" and frame 1 represents \"off\".\n * @default false\n */\n invertValue?: boolean;\n /** Additional CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n};\n\n/**\n * A primitive component that displays a single frame from a sprite sheet (filmstrip)\n * based on a normalized value.\n *\n * This is the most performant way to animate complex knobs and UI elements in audio applications.\n * It uses a nested SVG with a shifted viewBox to \"scrub\" through the filmstrip, which is hardware accelerated.\n *\n * The filmstrip image should contain all frames stacked vertically (default) or horizontally.\n *\n * @param {FilmstripImageProps} props - Component props\n * @param {number} [props.x=0] - X coordinate of the top-left corner\n * @param {number} [props.y=0] - Y coordinate of the top-left corner\n * @param {number} props.frameWidth - Width of a single frame in the filmstrip\n * @param {number} props.frameHeight - Height of a single frame in the filmstrip\n * @param {number} props.frameCount - Total number of frames in the strip\n * @param {number} props.normalizedValue - Normalized value between 0 and 1 (determines which frame to display)\n * @param {string} props.imageHref - URL to the sprite sheet/filmstrip image\n * @param {\"vertical\" | \"horizontal\"} [props.orientation=\"vertical\"] - Orientation of the strip\n * @param {number} [props.frameRotation=0] - Optional frame rotation in degrees\n * @param {boolean} [props.invertValue=false] - If true, inverts the normalized value (0.0 -> 1.0 and 1.0 -> 0.0)\n * @param {string} [props.className] - Additional CSS class name\n * @param {CSSProperties} [props.style] - Inline styles\n * @returns {JSX.Element} SVG group element containing the filmstrip frame\n */\nfunction FilmstripImage({\n x = 0,\n y = 0,\n frameWidth,\n frameHeight,\n frameCount,\n normalizedValue,\n imageHref,\n orientation = \"vertical\",\n frameRotation = 0,\n invertValue = false,\n className,\n style,\n}: FilmstripImageProps) {\n // Invert value if requested\n const effectiveValue = invertValue ? 1 - normalizedValue : normalizedValue;\n // Clamp value between 0 and 1\n const clampedValue = Math.max(0, Math.min(1, effectiveValue));\n\n // Calculate frame index (0 to frameCount - 1)\n const frameIndex = Math.round(clampedValue * (frameCount - 1));\n\n // Calculate total dimensions of the strip\n const totalWidth = orientation === \"horizontal\" ? frameWidth * frameCount : frameWidth;\n const totalHeight = orientation === \"vertical\" ? frameHeight * frameCount : frameHeight;\n\n // Calculate viewBox coordinates for the current frame\n const viewBoxX = orientation === \"horizontal\" ? frameIndex * frameWidth : 0;\n const viewBoxY = orientation === \"vertical\" ? frameIndex * frameHeight : 0;\n\n // Calculate center point for rotation\n const centerX = x + frameWidth / 2;\n const centerY = y + frameHeight / 2;\n\n return (\n <g className={className} style={style} transform={`rotate(${frameRotation}, ${centerX}, ${centerY})`}>\n <svg\n x={x}\n y={y}\n width={frameWidth}\n height={frameHeight}\n viewBox={`0 0 ${frameWidth} ${frameHeight}`}\n preserveAspectRatio=\"none\"\n style={{ overflow: \"hidden\" }}\n >\n <image\n href={imageHref}\n width={totalWidth}\n height={totalHeight}\n preserveAspectRatio=\"none\"\n style={{\n transform: `translate(${-viewBoxX}px, ${-viewBoxY}px)`,\n willChange: \"transform\",\n }}\n />\n </svg>\n </g>\n );\n}\n\nexport default React.memo(FilmstripImage);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport { ControlComponent } from \"@/types\";\nimport FilmstripImage from \"@/primitives/svg/FilmstripImage\";\n\n/**\n * Props specific to filmstrip visualization\n */\nexport type FilmstripViewProps = {\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Width of a SINGLE frame */\n frameWidth: number;\n /** Height of a SINGLE frame */\n frameHeight: number;\n /** Total number of frames in the strip */\n frameCount: number;\n /** URL to the sprite sheet/filmstrip image */\n imageHref: string;\n /** Orientation of the strip (default: \"vertical\") */\n orientation?: \"vertical\" | \"horizontal\";\n /** Optional frame rotation in degrees (default: 0) */\n frameRotation?: number;\n /**\n * If true, inverts the normalized value (0.0 -> 1.0 and 1.0 -> 0.0).\n * Useful for filmstrips where frame 0 represents \"on\" and frame 1 represents \"off\".\n * @default false\n */\n invertValue?: boolean;\n /** Additional CSS class name */\n className?: string;\n /** Content to render (unused in filmstrip but required by generic props) */\n children?: React.ReactNode;\n};\n\n/**\n * Pure SVG presentation component for a filmstrip control.\n * Renders a single frame from a sprite sheet based on normalized value.\n *\n * @param {FilmstripViewProps} props - Component props\n * @param {number} props.normalizedValue - Value between 0 and 1\n * @param {number} props.frameWidth - Width of a single frame in the filmstrip\n * @param {number} props.frameHeight - Height of a single frame in the filmstrip\n * @param {number} props.frameCount - Total number of frames in the strip\n * @param {string} props.imageHref - URL to the sprite sheet/filmstrip image\n * @param {\"vertical\" | \"horizontal\"} [props.orientation=\"vertical\"] - Orientation of the strip\n * @param {number} [props.frameRotation=0] - Optional frame rotation in degrees\n * @param {boolean} [props.invertValue=false] - If true, inverts the normalized value (0.0 -> 1.0 and 1.0 -> 0.0)\n * @param {string} [props.className] - Optional CSS class\n * @returns {JSX.Element} SVG group element containing the filmstrip frame (FilmstripImage component)\n */\nfunction FilmstripView({\n normalizedValue,\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation = \"vertical\",\n frameRotation = 0,\n invertValue = false,\n className,\n}: FilmstripViewProps): JSX.Element {\n return (\n <FilmstripImage\n x={0}\n y={0}\n frameWidth={frameWidth}\n frameHeight={frameHeight}\n frameCount={frameCount}\n normalizedValue={normalizedValue}\n imageHref={imageHref}\n orientation={orientation}\n frameRotation={frameRotation}\n invertValue={invertValue}\n className={className}\n />\n );\n}\n\nconst FilmstripViewMemo = React.memo(FilmstripView);\n\n// Attach static properties required by ControlComponent contract\n// Note: viewBox dimensions are dynamic based on frameWidth/frameHeight\n// We use a default that can be overridden by the parent component\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(FilmstripViewMemo as any).viewBox = { width: 100, height: 100 };\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(FilmstripViewMemo as any).labelHeightUnits = 20;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(FilmstripViewMemo as any).interaction = {\n mode: \"both\" as const,\n direction: \"vertical\" as const,\n};\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(FilmstripViewMemo as any).title = \"Filmstrip\";\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(FilmstripViewMemo as any).description = \"A filmstrip control that displays frames from a sprite sheet\";\n\n// Props for the ControlComponent (excluding normalizedValue, className, style, children)\ntype FilmstripViewComponentProps = Omit<FilmstripViewProps, \"normalizedValue\" | \"className\" | \"style\" | \"children\">;\n\nexport default FilmstripViewMemo as unknown as ControlComponent<FilmstripViewComponentProps>;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport ContinuousControl from \"@/primitives/controls/ContinuousControl\";\nimport FilmstripView from \"./FilmstripView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, ContinuousControlProps, ValueLabelMode } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\n\n/**\n * Props specific to filmstrip controls\n */\nexport type FilmstripProps = {\n /** Width of a SINGLE frame in the filmstrip */\n frameWidth: number;\n /** Height of a SINGLE frame in the filmstrip */\n frameHeight: number;\n /** Total number of frames in the strip */\n frameCount: number;\n /** URL to the sprite sheet/filmstrip image */\n imageHref: string;\n /** Orientation of the strip (default: \"vertical\") */\n orientation?: \"vertical\" | \"horizontal\";\n /** Optional frame rotation in degrees (default: 0) */\n frameRotation?: number;\n};\n\n/**\n * Props for the FilmStripContinuousControl component\n */\nexport type FilmStripContinuousControlProps = ContinuousControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n FilmstripProps & {\n /**\n * Controls how the label and value are displayed.\n * - \"labelOnly\": Always shows the label (default)\n * - \"valueOnly\": Always shows the value\n * - \"interactive\": Shows label normally, but temporarily swaps to value during interaction\n * @default \"labelOnly\"\n */\n valueAsLabel?: ValueLabelMode;\n /**\n * If true, inverts the normalized value (0.0 -> 1.0 and 1.0 -> 0.0).\n * Useful for filmstrips where frame 0 represents the maximum value and frame N represents the minimum value.\n * @default false\n */\n invertValue?: boolean;\n };\n\n/**\n * A continuous control that displays frames from a filmstrip sprite sheet.\n *\n * This component supports the widely-used current industry standard for control representation:\n * bitmap sprite sheets (filmstrips). While bitmap-based visualization is more constrained\n * than SVG (no dynamic theming, fixed visual appearance), this component provides full\n * access to all library features:\n *\n * - Complete layout system: AdaptiveBox with all sizing modes, label positioning, alignment\n * - Full parameter model: AudioParameter with min/max ranges, scaling functions, units, formatting\n * - Complete interaction system: Drag, wheel, and keyboard with configurable sensitivity and modes\n * - Full accessibility: ARIA attributes, focus management, keyboard navigation\n *\n * The frame displayed is determined by the normalized value (0-1 maps to frame 0 to frameCount-1).\n *\n * Supports two modes of operation:\n * 1. Parameter model mode: Provide `parameter` (ContinuousParameter) - all range/label info comes from the model\n * 2. Ad-hoc mode: Provide `min`, `max`, `step`, `label` directly as props\n *\n * Note: This component does NOT support themable props (color, roundness, thickness) as\n * visuals are entirely determined by the image content.\n *\n * @param props - Component props\n * @returns Rendered FilmStripContinuousControl component\n *\n * @example\n * ```tsx\n * <FilmStripContinuousControl\n * value={50}\n * min={0}\n * max={100}\n * label=\"Volume\"\n * frameWidth={100}\n * frameHeight={100}\n * frameCount={64}\n * imageHref=\"/knob-frames.png\"\n * />\n * ```\n */\nfunction FilmStripContinuousControl({\n min,\n max,\n step,\n bipolar = false,\n value,\n onChange,\n valueFormatter,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation = \"vertical\",\n frameRotation = 0,\n parameter,\n unit,\n scale,\n paramId,\n interactionMode,\n interactionDirection,\n interactionSensitivity,\n valueAsLabel = \"labelOnly\",\n midiResolution,\n defaultValue,\n invertValue = false,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: FilmStripContinuousControlProps) {\n const { sizeClassName, sizeStyle: adaptiveSizeStyle } = useAdaptiveSize(adaptiveSize, size, \"knob\");\n\n return (\n <ContinuousControl\n min={min}\n max={max}\n step={step}\n bipolar={bipolar}\n value={value}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n className={classNames(sizeClassName, className)}\n style={{ ...adaptiveSizeStyle, ...style }}\n onChange={onChange}\n paramId={paramId}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n parameter={parameter}\n unit={unit}\n scale={scale}\n midiResolution={midiResolution}\n defaultValue={defaultValue}\n interactionMode={interactionMode}\n interactionDirection={interactionDirection}\n interactionSensitivity={interactionSensitivity}\n valueFormatter={valueFormatter}\n valueAsLabel={valueAsLabel}\n viewBoxWidthUnits={frameWidth}\n viewBoxHeightUnits={frameHeight}\n view={FilmstripView}\n viewProps={{\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation,\n frameRotation,\n invertValue,\n }}\n />\n );\n}\n\nexport default React.memo(FilmStripContinuousControl);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport DiscreteControl from \"@/primitives/controls/DiscreteControl\";\nimport FilmstripView from \"./FilmstripView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, DiscreteControlProps } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { FilmstripProps } from \"./FilmStripContinuousControl\";\n\n/**\n * Props for the FilmStripDiscreteControl component\n */\nexport type FilmStripDiscreteControlProps = DiscreteControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n FilmstripProps;\n\n/**\n * A discrete control that displays frames from a filmstrip sprite sheet.\n *\n * This component supports the widely-used current industry standard for control representation:\n * bitmap sprite sheets (filmstrips). While bitmap-based visualization is more constrained\n * than SVG (no dynamic theming, fixed visual appearance), this component provides full\n * access to all library features:\n *\n * - Complete layout system: AdaptiveBox with all sizing modes, label positioning, alignment\n * - Full parameter model: DiscreteParameter with options, labels, value mapping\n * - Complete interaction system: Click and keyboard interactions for cycling/stepping\n * - Full accessibility: ARIA attributes, focus management, keyboard navigation\n *\n * The frame displayed is determined by mapping the current value to a frame index.\n *\n * Supports three modes of operation:\n * 1. Ad-Hoc Mode (Children only): Model inferred from OptionView children.\n * 2. Strict Mode (Parameter only): Model provided via parameter prop. View via renderOption.\n * 3. Hybrid Mode (Parameter + Children): Model from parameter, View from children (matched by value).\n *\n * Note: This component does NOT support themable props (color, roundness, thickness) as\n * visuals are entirely determined by the image content.\n *\n * @param props - Component props\n * @returns Rendered FilmStripDiscreteControl component\n *\n * @example\n * ```tsx\n * <FilmStripDiscreteControl\n * value=\"sine\"\n * onChange={(e) => setValue(e.value)}\n * frameWidth={100}\n * frameHeight={100}\n * frameCount={4}\n * imageHref=\"/waveform-frames.png\"\n * >\n * <Option value=\"sine\">Sine</Option>\n * <Option value=\"square\">Square</Option>\n * <Option value=\"triangle\">Triangle</Option>\n * <Option value=\"sawtooth\">Sawtooth</Option>\n * </FilmStripDiscreteControl>\n * ```\n */\nfunction FilmStripDiscreteControl({\n value,\n defaultValue,\n onChange,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation = \"vertical\",\n frameRotation = 0,\n parameter,\n paramId,\n options,\n midiResolution,\n midiMapping,\n children,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: FilmStripDiscreteControlProps) {\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"knob\");\n\n return (\n <DiscreteControl\n value={value}\n defaultValue={defaultValue}\n onChange={onChange}\n label={label}\n paramId={paramId}\n parameter={parameter}\n options={options}\n midiResolution={midiResolution}\n midiMapping={midiMapping}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n className={classNames(sizeClassName, className)}\n style={{ ...sizeStyle, ...style }}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n viewBoxWidthUnits={frameWidth}\n viewBoxHeightUnits={frameHeight}\n view={FilmstripView}\n viewProps={{\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation,\n frameRotation,\n }}\n >\n {children}\n </DiscreteControl>\n );\n}\n\nexport default React.memo(FilmStripDiscreteControl);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport BooleanControl from \"@/primitives/controls/BooleanControl\";\nimport FilmstripView from \"./FilmstripView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, BooleanControlProps } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { FilmstripProps } from \"./FilmStripContinuousControl\";\n\n/**\n * Props for the FilmStripBooleanControl component\n */\nexport type FilmStripBooleanControlProps = BooleanControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n FilmstripProps & {\n /**\n * If true, inverts the normalized value passed to the view (0.0 -> 1.0 and 1.0 -> 0.0).\n * Useful for filmstrips where frame 0 represents \"on\" and frame 1 represents \"off\".\n * @default false\n */\n invertValue?: boolean;\n };\n\n/**\n * A boolean control that displays frames from a filmstrip sprite sheet.\n *\n * This component supports the widely-used current industry standard for control representation:\n * bitmap sprite sheets (filmstrips). While bitmap-based visualization is more constrained\n * than SVG (no dynamic theming, fixed visual appearance), this component provides full\n * access to all library features:\n *\n * - Complete layout system: AdaptiveBox with all sizing modes, label positioning, alignment\n * - Full parameter model: BooleanParameter with latch/momentary modes\n * - Complete interaction system: Click, drag-in/drag-out, and keyboard interactions\n * - Full accessibility: ARIA attributes, focus management, keyboard navigation\n *\n * Typically uses 2 frames: frame 0 for false/off, frame 1 for true/on.\n *\n * Supports two modes of operation:\n * 1. Strict Mode (Parameter only): Model provided via parameter prop.\n * 2. Ad-Hoc Mode (Props only): Model created from individual props (label, latch, etc.).\n *\n * Note: This component does NOT support themable props (color, roundness, thickness) as\n * visuals are entirely determined by the image content.\n *\n * @param props - Component props\n * @returns Rendered FilmStripBooleanControl component\n *\n * @example\n * ```tsx\n * <FilmStripBooleanControl\n * value={isOn}\n * onChange={(e) => setIsOn(e.value)}\n * label=\"Power\"\n * latch={true}\n * frameWidth={100}\n * frameHeight={100}\n * frameCount={2}\n * imageHref=\"/button-frames.png\"\n * />\n * ```\n */\nfunction FilmStripBooleanControl({\n latch = false,\n value = false,\n onChange,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation = \"vertical\",\n frameRotation = 0,\n invertValue = false,\n parameter,\n paramId,\n midiResolution,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: FilmStripBooleanControlProps) {\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"button\");\n\n return (\n <BooleanControl\n value={value}\n onChange={onChange}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n className={classNames(sizeClassName, className)}\n style={{ ...sizeStyle, ...style }}\n parameter={parameter}\n paramId={paramId}\n latch={latch}\n midiResolution={midiResolution}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n viewBoxWidthUnits={frameWidth}\n viewBoxHeightUnits={frameHeight}\n view={FilmstripView}\n viewProps={{\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation,\n frameRotation,\n invertValue,\n }}\n />\n );\n}\n\nexport default React.memo(FilmStripBooleanControl);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport { ControlComponent } from \"@/types\";\nimport RotaryImage from \"@/primitives/svg/RotaryImage\";\n\n/**\n * Props specific to rotary image visualization\n */\nexport type ImageKnobViewProps = {\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Width of the viewBox (determines viewBox width) */\n frameWidth: number;\n /** Height of the viewBox (determines viewBox height) */\n frameHeight: number;\n /** URL to the image to rotate */\n imageHref: string;\n /** Optional rotation angle offset in degrees (default: 0) */\n rotation?: number;\n /** Openness of the arc in degrees (value between 0-360º; 0º: closed; 90º: 3/4 open; 180º: half-circle;)\n * @default 90\n */\n openness?: number;\n /** Whether to start the arc from center (bipolar mode)\n * @default false\n */\n bipolar?: boolean;\n /** Optional number of discrete positions. When defined, the value will snap to the nearest position.\n * Used by discrete controls to snap to option positions.\n */\n positions?: number;\n /** Additional CSS class name */\n className?: string;\n /** Content to render (unused in rotary image but required by generic props) */\n children?: React.ReactNode;\n};\n\n/**\n * Pure SVG presentation component for a rotary image control.\n * Renders a rotating image based on normalized value.\n *\n * @param {ImageKnobViewProps} props - Component props\n * @param {number} props.normalizedValue - Value between 0 and 1\n * @param {number} props.frameWidth - Width of the viewBox\n * @param {number} props.frameHeight - Height of the viewBox\n * @param {string} props.imageHref - URL to the image to rotate\n * @param {number} [props.rotation=0] - Optional rotation angle offset in degrees\n * @param {number} [props.openness=90] - Openness of the arc in degrees\n * @param {boolean} [props.bipolar=false] - Whether to start the arc from center (bipolar mode)\n * @param {number} [props.positions] - Optional number of discrete positions for snapping\n * @param {string} [props.className] - Optional CSS class\n * @returns {JSX.Element} SVG group element containing the rotary image (RotaryImage component)\n */\nfunction ImageKnobView({\n normalizedValue,\n frameWidth,\n frameHeight,\n imageHref,\n rotation = 0,\n openness = 90,\n bipolar = false,\n positions,\n className,\n}: ImageKnobViewProps): JSX.Element {\n // Center point of the viewBox\n const cx = frameWidth / 2;\n const cy = frameHeight / 2;\n // Calculate radius from frame dimensions\n const radius = Math.min(frameWidth, frameHeight) / 2;\n\n return (\n <RotaryImage\n cx={cx}\n cy={cy}\n radius={radius}\n normalizedValue={normalizedValue}\n imageHref={imageHref}\n rotation={rotation}\n openness={openness}\n bipolar={bipolar}\n positions={positions}\n className={className}\n />\n );\n}\n\nconst ImageKnobViewMemo = React.memo(ImageKnobView);\n\n// Attach static properties required by ControlComponent contract\n// Note: viewBox dimensions are dynamic based on frameWidth/frameHeight\n// We use a default that can be overridden by the parent component\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageKnobViewMemo as any).viewBox = { width: 100, height: 100 };\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageKnobViewMemo as any).labelHeightUnits = 20;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageKnobViewMemo as any).interaction = {\n mode: \"both\" as const,\n direction: \"circular\" as const,\n};\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageKnobViewMemo as any).title = \"Rotary Image\";\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageKnobViewMemo as any).description = \"A rotary control that rotates an image based on value\";\n\n// Props for the ControlComponent (excluding normalizedValue, className, style, children)\ntype ImageKnobViewComponentProps = Omit<ImageKnobViewProps, \"normalizedValue\" | \"className\" | \"style\" | \"children\">;\n\nexport default ImageKnobViewMemo as unknown as ControlComponent<ImageKnobViewComponentProps>;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport ContinuousControl from \"@/primitives/controls/ContinuousControl\";\nimport ImageKnobView from \"./ImageKnobView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, ContinuousControlProps, ValueLabelMode } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\n\n/**\n * Props specific to rotary image controls\n */\nexport type RotaryImageProps = {\n /** Width of the viewBox (determines viewBox width) */\n frameWidth: number;\n /** Height of the viewBox (determines viewBox height) */\n frameHeight: number;\n /** URL to the image to rotate */\n imageHref: string;\n /** Optional rotation angle offset in degrees (default: 0) */\n rotation?: number;\n /** Openness of the arc in degrees (value between 0-360º; 0º: closed; 90º: 3/4 open; 180º: half-circle;)\n * @default 90\n */\n openness?: number;\n};\n\n/**\n * Props for the ImageKnob component\n */\nexport type ImageKnobProps = ContinuousControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n RotaryImageProps & {\n /**\n * Controls how the label and value are displayed.\n * - \"labelOnly\": Always shows the label (default)\n * - \"valueOnly\": Always shows the value\n * - \"interactive\": Shows label normally, but temporarily swaps to value during interaction\n * @default \"labelOnly\"\n */\n valueAsLabel?: ValueLabelMode;\n };\n\n/**\n * A continuous control that rotates an image based on a normalized value.\n *\n * This component provides full access to all library features:\n *\n * - Complete layout system: AdaptiveBox with all sizing modes, label positioning, alignment\n * - Full parameter model: AudioParameter with min/max ranges, scaling functions, units, formatting\n * - Complete interaction system: Drag, wheel, and keyboard with configurable sensitivity and modes\n * - Full accessibility: ARIA attributes, focus management, keyboard navigation\n *\n * The image rotation is determined by the normalized value (0-1 maps to rotation based on openness and rotation offset).\n *\n * Supports two modes of operation:\n * 1. Parameter model mode: Provide `parameter` (ContinuousParameter) - all range/label info comes from the model\n * 2. Ad-hoc mode: Provide `min`, `max`, `step`, `label` directly as props\n *\n * Note: This component does NOT support themable props (color, roundness, thickness) as\n * visuals are entirely determined by the image content.\n *\n * @param props - Component props\n * @returns Rendered ImageKnob component\n *\n * @example\n * ```tsx\n * <ImageKnob\n * value={50}\n * min={0}\n * max={100}\n * label=\"Volume\"\n * frameWidth={100}\n * frameHeight={100}\n * imageHref=\"/knob-image.png\"\n * openness={90}\n * rotation={0}\n * />\n * ```\n */\nfunction ImageKnob({\n min,\n max,\n step,\n bipolar = false,\n value,\n onChange,\n valueFormatter,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n frameWidth,\n frameHeight,\n imageHref,\n rotation = 0,\n openness = 90,\n parameter,\n unit,\n scale,\n paramId,\n interactionMode,\n interactionDirection,\n interactionSensitivity,\n valueAsLabel = \"labelOnly\",\n midiResolution,\n defaultValue,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: ImageKnobProps) {\n const { sizeClassName, sizeStyle: adaptiveSizeStyle } = useAdaptiveSize(adaptiveSize, size, \"knob\");\n\n return (\n <ContinuousControl\n min={min}\n max={max}\n step={step}\n bipolar={bipolar}\n value={value}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n className={classNames(sizeClassName, className)}\n style={{ ...adaptiveSizeStyle, ...style }}\n onChange={onChange}\n paramId={paramId}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n parameter={parameter}\n unit={unit}\n scale={scale}\n midiResolution={midiResolution}\n defaultValue={defaultValue}\n interactionMode={interactionMode}\n interactionDirection={interactionDirection}\n interactionSensitivity={interactionSensitivity}\n valueFormatter={valueFormatter}\n valueAsLabel={valueAsLabel}\n viewBoxWidthUnits={frameWidth}\n viewBoxHeightUnits={frameHeight}\n view={ImageKnobView}\n viewProps={{\n frameWidth,\n frameHeight,\n imageHref,\n rotation,\n openness,\n bipolar,\n }}\n />\n );\n}\n\nexport default React.memo(ImageKnob);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport classNames from \"classnames\";\nimport DiscreteControl from \"@/primitives/controls/DiscreteControl\";\nimport ImageKnobView from \"./ImageKnobView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, DiscreteControlProps } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { useDiscreteParameterResolution } from \"@/hooks/useDiscreteParameterResolution\";\nimport { RotaryImageProps } from \"./ImageKnob\";\n\n/**\n * Props for the ImageRotarySwitch component\n */\nexport type ImageRotarySwitchProps = DiscreteControlProps & AdaptiveSizeProps & AdaptiveBoxProps & RotaryImageProps;\n\n/**\n * A discrete control that rotates an image based on discrete option values.\n *\n * This component provides full access to all library features:\n *\n * - Complete layout system: AdaptiveBox with all sizing modes, label positioning, alignment\n * - Full parameter model: DiscreteParameter with options, labels, value mapping\n * - Complete interaction system: Click and keyboard interactions for cycling/stepping\n * - Full accessibility: ARIA attributes, focus management, keyboard navigation\n *\n * The image rotation is determined by mapping the current value to a normalized position,\n * with snapping to discrete positions based on the number of options.\n *\n * Supports three modes of operation:\n * 1. Ad-Hoc Mode (Children only): Model inferred from OptionView children.\n * 2. Strict Mode (Parameter only): Model provided via parameter prop. View via renderOption.\n * 3. Hybrid Mode (Parameter + Children): Model from parameter, View from children (matched by value).\n *\n * Note: This component does NOT support themable props (color, roundness, thickness) as\n * visuals are entirely determined by the image content.\n *\n * @param props - Component props\n * @returns Rendered ImageRotarySwitch component\n *\n * @example\n * ```tsx\n * <ImageRotarySwitch\n * value=\"sine\"\n * onChange={(e) => setValue(e.value)}\n * frameWidth={100}\n * frameHeight={100}\n * imageHref=\"/waveform-knob.png\"\n * openness={90}\n * rotation={0}\n * >\n * <Option value=\"sine\">Sine</Option>\n * <Option value=\"square\">Square</Option>\n * <Option value=\"triangle\">Triangle</Option>\n * <Option value=\"sawtooth\">Sawtooth</Option>\n * </ImageRotarySwitch>\n * ```\n */\nfunction ImageRotarySwitch({\n value,\n defaultValue,\n onChange,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n frameWidth,\n frameHeight,\n imageHref,\n rotation = 0,\n openness = 90,\n parameter,\n paramId,\n options,\n midiResolution,\n midiMapping,\n children,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: ImageRotarySwitchProps) {\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"knob\");\n\n // Get derivedParameter to determine the number of options for positions\n const { derivedParameter } = useDiscreteParameterResolution({\n children,\n options,\n paramId,\n parameter,\n defaultValue,\n label,\n midiResolution,\n midiMapping,\n });\n\n // Calculate positions from the number of options\n const positions = useMemo(() => derivedParameter.options.length, [derivedParameter.options.length]);\n\n return (\n <DiscreteControl\n value={value}\n defaultValue={defaultValue}\n onChange={onChange}\n label={label}\n paramId={paramId}\n parameter={parameter}\n options={options}\n midiResolution={midiResolution}\n midiMapping={midiMapping}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n className={classNames(sizeClassName, className)}\n style={{ ...sizeStyle, ...style }}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n viewBoxWidthUnits={frameWidth}\n viewBoxHeightUnits={frameHeight}\n view={ImageKnobView}\n viewProps={{\n frameWidth,\n frameHeight,\n imageHref,\n rotation,\n openness,\n positions,\n }}\n >\n {children}\n </DiscreteControl>\n );\n}\n\nexport default React.memo(ImageRotarySwitch);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties } from \"react\";\n\nexport type ImageProps = {\n /** X coordinate of the top-left corner (default: 0) */\n x?: number;\n /** Y coordinate of the top-left corner (default: 0) */\n y?: number;\n /** Width of the image */\n width: number;\n /** Height of the image */\n height: number;\n /** Optional image URL to display */\n imageHref?: string;\n /** Optional SVG content to display (e.g., icon components) */\n children?: React.ReactNode;\n /** Optional SVG transform attribute */\n transform?: string;\n /** Additional CSS class name */\n className?: string;\n /**\n * Inline styles.\n * Supports `color` property for icon theming - icons using currentColor will inherit this value.\n */\n style?: CSSProperties;\n};\n\n/**\n * A primitive component that displays static content at rectangular coordinates.\n * The content is positioned at (x, y) with the specified width and height.\n *\n * This component can display an image (via imageHref) or arbitrary SVG content (via children).\n * It is designed for straightforward rectangular image placement without radial/centered positioning.\n *\n * Useful for displaying images or icons at specific positions and sizes.\n *\n * @param {ImageProps} props - Component props\n * @param {number} [props.x=0] - X coordinate of the top-left corner\n * @param {number} [props.y=0] - Y coordinate of the top-left corner\n * @param {number} props.width - Width of the image\n * @param {number} props.height - Height of the image\n * @param {string} [props.imageHref] - Optional image URL to display\n * @param {React.ReactNode} [props.children] - Optional SVG content to display\n * @param {string} [props.transform] - Optional SVG transform attribute\n * @param {string} [props.className] - Additional CSS class name\n * @param {CSSProperties} [props.style] - Inline styles\n * @returns {JSX.Element} SVG group element containing the image\n */\nfunction Image({ x = 0, y = 0, width, height, imageHref, children, transform, className, style }: ImageProps) {\n return (\n <g className={className} style={style} transform={transform}>\n {imageHref && (\n <image href={imageHref} x={x} y={y} width={width} height={height} preserveAspectRatio=\"xMidYMid meet\" />\n )}\n {children && (\n <svg\n x={x}\n y={y}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n style={{ overflow: \"visible\", ...(style?.color ? { color: style.color } : {}) }}\n >\n {children}\n </svg>\n )}\n </g>\n );\n}\n\nexport default React.memo(Image);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport { ControlComponent } from \"@/types\";\nimport Image from \"@/primitives/svg/Image\";\n\n/**\n * Props specific to image switch visualization\n */\nexport type ImageSwitchViewProps = {\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Width of the viewBox (determines viewBox width) */\n frameWidth: number;\n /** Height of the viewBox (determines viewBox height) */\n frameHeight: number;\n /** URL to the image for false/off state */\n imageHrefFalse: string;\n /** URL to the image for true/on state */\n imageHrefTrue: string;\n /** Additional CSS class name */\n className?: string;\n /** Content to render (unused in image view but required by generic props) */\n children?: React.ReactNode;\n};\n\n/**\n * Pure SVG presentation component for an image switch control.\n * Renders one of two images based on normalized value (false = 0, true = 1).\n *\n * @param {ImageSwitchViewProps} props - Component props\n * @param {number} props.normalizedValue - Value between 0 and 1 (0 = false, 1 = true)\n * @param {number} props.frameWidth - Width of the viewBox\n * @param {number} props.frameHeight - Height of the viewBox\n * @param {string} props.imageHrefFalse - URL to the image for false/off state\n * @param {string} props.imageHrefTrue - URL to the image for true/on state\n * @param {string} [props.className] - Optional CSS class\n * @returns {JSX.Element} SVG image element\n */\nfunction ImageSwitchView({\n normalizedValue,\n frameWidth,\n frameHeight,\n imageHrefFalse,\n imageHrefTrue,\n className,\n}: ImageSwitchViewProps): JSX.Element {\n // Determine which image to show based on normalized value (threshold at 0.5)\n const imageHref = normalizedValue > 0.5 ? imageHrefTrue : imageHrefFalse;\n\n return <Image x={0} y={0} width={frameWidth} height={frameHeight} imageHref={imageHref} className={className} />;\n}\n\nconst ImageSwitchViewMemo = React.memo(ImageSwitchView);\n\n// Attach static properties required by ControlComponent contract\n// Note: viewBox dimensions are dynamic based on frameWidth/frameHeight\n// We use a default that can be overridden by the parent component\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageSwitchViewMemo as any).viewBox = { width: 100, height: 100 };\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageSwitchViewMemo as any).labelHeightUnits = 20;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageSwitchViewMemo as any).interaction = {\n mode: \"both\" as const,\n direction: \"vertical\" as const,\n};\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageSwitchViewMemo as any).title = \"Image Switch\";\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageSwitchViewMemo as any).description = \"A boolean control that displays one of two images based on value\";\n\n// Props for the ControlComponent (excluding normalizedValue, className, style, children)\ntype ImageSwitchViewComponentProps = Omit<ImageSwitchViewProps, \"normalizedValue\" | \"className\" | \"style\" | \"children\">;\n\nexport default ImageSwitchViewMemo as unknown as ControlComponent<ImageSwitchViewComponentProps>;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport BooleanControl from \"@/primitives/controls/BooleanControl\";\nimport ImageSwitchView from \"./ImageSwitchView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, BooleanControlProps } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\n\n/**\n * Props specific to image switch controls\n */\nexport type ImageSwitchProps = {\n /** Width of the viewBox (determines viewBox width) */\n frameWidth: number;\n /** Height of the viewBox (determines viewBox height) */\n frameHeight: number;\n /** URL to the image for false/off state */\n imageHrefFalse: string;\n /** URL to the image for true/on state */\n imageHrefTrue: string;\n};\n\n/**\n * Props for the ImageSwitch component\n */\nexport type ImageSwitchComponentProps = BooleanControlProps & AdaptiveSizeProps & AdaptiveBoxProps & ImageSwitchProps;\n\n/**\n * A boolean control that displays one of two images based on the boolean value.\n *\n * This component provides full access to all library features:\n *\n * - Complete layout system: AdaptiveBox with all sizing modes, label positioning, alignment\n * - Full parameter model: BooleanParameter with latch/momentary modes\n * - Complete interaction system: Click, drag-in/drag-out, and keyboard interactions\n * - Full accessibility: ARIA attributes, focus management, keyboard navigation\n *\n * The image displayed is determined by the boolean value:\n * - `false` (normalized 0.0) displays `imageHrefFalse`\n * - `true` (normalized 1.0) displays `imageHrefTrue`\n *\n * Supports two modes of operation:\n * 1. Strict Mode (Parameter only): Model provided via parameter prop.\n * 2. Ad-Hoc Mode (Props only): Model created from individual props (label, latch, etc.).\n *\n * Note: This component does NOT support themable props (color, roundness, thickness) as\n * visuals are entirely determined by the image content.\n *\n * @param props - Component props\n * @returns Rendered ImageSwitch component\n *\n * @example\n * ```tsx\n * <ImageSwitch\n * value={isFavorite}\n * onChange={(e) => setIsFavorite(e.value)}\n * label=\"Favorite\"\n * latch={true}\n * frameWidth={100}\n * frameHeight={100}\n * imageHrefFalse=\"/star-off.png\"\n * imageHrefTrue=\"/star-on.png\"\n * />\n * ```\n */\nfunction ImageSwitch({\n latch = false,\n value = false,\n onChange,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n frameWidth,\n frameHeight,\n imageHrefFalse,\n imageHrefTrue,\n parameter,\n paramId,\n midiResolution,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: ImageSwitchComponentProps) {\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"button\");\n\n return (\n <BooleanControl\n value={value}\n onChange={onChange}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n className={classNames(sizeClassName, className)}\n style={{ ...sizeStyle, ...style }}\n parameter={parameter}\n paramId={paramId}\n latch={latch}\n midiResolution={midiResolution}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n viewBoxWidthUnits={frameWidth}\n viewBoxHeightUnits={frameHeight}\n view={ImageSwitchView}\n viewProps={{\n frameWidth,\n frameHeight,\n imageHrefFalse,\n imageHrefTrue,\n }}\n />\n );\n}\n\nexport default React.memo(ImageSwitch);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React from \"react\";\n\nexport type RadialHtmlOverlayProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Radius from center to the edge of the content box. The content will be a square with side = radius * 2 */\n radius: number;\n /** Content to render inside the overlay */\n children?: React.ReactNode;\n /** Additional CSS class name */\n className?: string;\n /** Inline styles for the foreignObject */\n style?: React.CSSProperties;\n /** Pointer events behavior (default: \"none\" to let clicks pass through to SVG below, set to \"auto\" if interactive) */\n pointerEvents?: \"none\" | \"auto\";\n};\n\n/**\n * A primitive that renders HTML content inside an SVG at a radial position.\n * It creates a square foreignObject centered at (cx, cy) with size based on radius.\n *\n * This is the preferred way to render text inside knobs, as it leverages the browser's\n * native HTML text rendering engine (Flexbox, wrapping, fonts) which is superior to SVG text.\n */\nfunction RadialHtmlOverlay({\n cx,\n cy,\n radius,\n children,\n className,\n style,\n pointerEvents = \"none\",\n}: RadialHtmlOverlayProps) {\n const size = radius * 2;\n const x = cx - radius;\n const y = cy - radius;\n\n return (\n <foreignObject\n x={x}\n y={y}\n width={size}\n height={size}\n className={className}\n style={{\n pointerEvents,\n ...style,\n }}\n >\n <div\n // @ts-expect-error - xmlns is valid for XHTML but not strictly in React's HTML definitions\n xmlns=\"http://www.w3.org/1999/xhtml\"\n style={{\n // Use 100% to fill the foreignObject (which is sized in SVG user units)\n // Do NOT use explicit pixels here, as that breaks SVG scaling (viewBox).\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n textAlign: \"center\",\n }}\n >\n {children}\n </div>\n </foreignObject>\n );\n}\n\nexport default React.memo(RadialHtmlOverlay);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { SVGProps } from \"react\";\n\nexport interface RevealingPathProps extends Omit<\n SVGProps<SVGPathElement>,\n \"strokeDasharray\" | \"strokeDashoffset\" | \"pathLength\"\n> {\n /**\n * Normalized value between 0 and 1 indicating how much of the path to reveal.\n * 0 = path is completely hidden\n * 1 = path is completely visible\n */\n normalizedValue: number;\n /**\n * The internal resolution used for the path length calculation.\n * Higher values might offer smoother animations but \"100\" is usually sufficient for percentage-based fills.\n * Defaults to 100.\n */\n resolution?: number;\n}\n\n/**\n * A primitive SVG component that reveals a path from start to end using CSS stroke-dashoffset.\n *\n * This component uses the SVG `pathLength` attribute to normalize the path's length\n * calculation, allowing for performant \"filling\" animations without calculating\n * the actual geometric length of the path in JavaScript.\n *\n * Usage:\n * ```tsx\n * <RevealingPath\n * d=\"M...\"\n * normalizedValue={0.5} // 50% revealed from the start\n * stroke=\"currentColor\"\n * fill=\"none\"\n * />\n * ```\n */\nfunction RevealingPath({ normalizedValue, resolution = 100, className, style, ...props }: RevealingPathProps) {\n // Clamp value to ensure it stays within 0-1 range\n const clampedValue = Math.max(0, Math.min(1, normalizedValue));\n\n // Calculate the dash offset based on the resolution.\n // - When value is 1 (100%), offset is 0 (dash fully covers path).\n // - When value is 0 (0%), offset is resolution (dash is shifted fully 'off' the path).\n const numericResolution = Number(resolution);\n const calculatedOffset = numericResolution * (1 - clampedValue);\n\n return (\n <path\n {...props}\n className={className}\n // pathLength normalizes the browser's internal distance calculation for this element\n // to this value (default 100), regardless of the path's actual pixel length.\n pathLength={numericResolution}\n // Create a single dash that is the length of the entire path.\n // This ensures the \"filled\" part is solid and the \"empty\" part is transparent.\n strokeDasharray={numericResolution}\n // Shift the dash pattern to reveal only the desired portion.\n strokeDashoffset={calculatedOffset}\n style={style}\n />\n );\n}\n\nexport default React.memo(RevealingPath);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, ReactNode, useMemo } from \"react\";\nimport { polarToCartesian } from \"@cutoff/audio-ui-core\";\n\nexport type TickData = {\n x: number;\n y: number;\n angle: number;\n index: number;\n};\n\nexport type TickRingProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Outer radius of the ring (ticks extend inward) */\n radius: number;\n /** Length of the ticks in pixels (or diameter of dots) */\n thickness?: number;\n /** Openness of the ring in degrees (default 90) */\n openness?: number;\n /** Rotation offset in degrees (default 0) */\n rotation?: number;\n /** Total number of ticks to distribute evenly */\n count?: number;\n /** Angle in degrees between ticks (alternative to count) */\n step?: number;\n /**\n * Shape of the ticks:\n * - \"line\": Radial lines (optimized single path)\n * - \"dot\": Circles (optimized single path)\n * - \"pill\": Rounded lines (using stroke-linecap round)\n */\n variant?: \"line\" | \"dot\" | \"pill\";\n /**\n * Optional callback to render custom content at each tick position.\n * If provided, the component bails out of single-path optimization and renders generic ReactNodes.\n */\n renderTick?: (data: TickData) => ReactNode;\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n};\n\n/**\n * A reusable SVG primitive that renders a ring of tick marks.\n * Useful for creating scales on knobs and dials.\n *\n * Supports optimized single-path rendering for lines and dots,\n * or custom rendering via renderTick callback.\n */\nfunction TickRing({\n cx,\n cy,\n radius,\n thickness = 4,\n openness = 90,\n rotation = 0,\n count,\n step,\n variant = \"line\",\n renderTick,\n className,\n style,\n}: TickRingProps) {\n // 1. Calculate positions (shared logic)\n const positions = useMemo(() => {\n const clampedOpenness = Math.max(0, Math.min(360, openness));\n const baseStart = 180 + clampedOpenness / 2;\n const baseEnd = 540 - clampedOpenness / 2;\n const startAngle = baseStart - rotation;\n const totalAngle = baseEnd - baseStart;\n\n let numTicks = 0;\n let angleStep = 0;\n\n // Handle full circle case (openness = 0 or 360)\n // If openness is 0, we have a full circle (360 degrees range).\n // For full circles, we distribute ticks evenly around 360 degrees,\n // ensuring the last tick doesn't overlap the first.\n const isFullCircle = clampedOpenness <= 0.01;\n\n if (count !== undefined && count > 1) {\n numTicks = count;\n if (isFullCircle) {\n // For full circle, distribute evenly around 360\n angleStep = 360 / count;\n } else {\n angleStep = totalAngle / (count - 1);\n }\n } else if (step !== undefined && step > 0) {\n numTicks = Math.floor(totalAngle / step) + 1;\n angleStep = step;\n } else if (count === 1) {\n numTicks = 1;\n angleStep = 0;\n }\n\n if (numTicks <= 0) return [];\n\n const results: TickData[] = [];\n // Round coordinates to avoid hydration mismatches\n const r = (n: number) => Math.round(n * 10000) / 10000;\n\n for (let i = 0; i < numTicks; i++) {\n const angle = startAngle + i * angleStep;\n\n // Calculate position at outer radius\n const pos = polarToCartesian(cx, cy, radius, angle);\n\n results.push({\n x: r(pos.x),\n y: r(pos.y),\n angle: r(angle), // Keep precise angle for rotation transforms\n index: i,\n });\n }\n return results;\n }, [cx, cy, radius, openness, rotation, count, step]);\n\n // 2. Optimized Path Mode (calculate before early return to satisfy hooks rules)\n const pathData = useMemo(() => {\n if (renderTick || positions.length === 0) return \"\";\n\n const commands: string[] = [];\n const r = (n: number) => Math.round(n * 10000) / 10000;\n\n for (const pos of positions) {\n if (variant === \"dot\") {\n // Circle centered at radius - thickness/2\n // We need to recalculate center because pos is at outer radius\n const center = polarToCartesian(cx, cy, radius - thickness / 2, pos.angle);\n const rx = thickness / 2;\n const ry = thickness / 2;\n\n // Draw circle using two arcs\n // M cx-r cy A r r 0 1 0 cx+r cy A r r 0 1 0 cx-r cy\n commands.push(\n `M ${r(center.x - rx)} ${r(center.y)} ` +\n `A ${r(rx)} ${r(ry)} 0 1 0 ${r(center.x + rx)} ${r(center.y)} ` +\n `A ${r(rx)} ${r(ry)} 0 1 0 ${r(center.x - rx)} ${r(center.y)}`\n );\n } else {\n // \"line\" or \"pill\"\n // Line from inner to outer\n const outer = polarToCartesian(cx, cy, radius, pos.angle);\n const inner = polarToCartesian(cx, cy, radius - thickness, pos.angle);\n commands.push(`M ${r(inner.x)} ${r(inner.y)} L ${r(outer.x)} ${r(outer.y)}`);\n }\n }\n return commands.join(\" \");\n }, [positions, cx, cy, radius, thickness, variant, renderTick]);\n\n // 3. Custom Render Mode (Bail out of optimization)\n if (renderTick) {\n return (\n <g className={className} style={style}>\n {positions.map((pos) => (\n <React.Fragment key={pos.index}>{renderTick(pos)}</React.Fragment>\n ))}\n </g>\n );\n }\n\n if (!pathData) return null;\n\n // Default styles based on variant\n const defaultStyles: CSSProperties =\n variant === \"dot\"\n ? { fill: \"currentColor\", stroke: \"none\" }\n : { fill: \"none\", stroke: \"currentColor\", strokeLinecap: variant === \"pill\" ? \"round\" : \"butt\" };\n\n return (\n <path\n d={pathData}\n className={className}\n style={{ ...defaultStyles, ...style }}\n // For lines/pills, use strokeWidth=1 default if not set in style\n // For dots, strokeWidth doesn't matter unless user overrides fill/stroke\n strokeWidth={variant === \"dot\" ? undefined : style?.strokeWidth || 1}\n />\n );\n}\n\nexport default React.memo(TickRing);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, ReactNode } from \"react\";\nimport TickRing from \"./TickRing\";\n\nexport type LabelRingProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Outer radius of the ring (content is centered at this radius) */\n radius: number;\n /** Array of content to render at each tick position */\n labels: (string | number | ReactNode)[];\n /** Orientation of the labels:\n * - \"upright\": Text stays upright (default)\n * - \"radial\": Text rotates to match the angle\n */\n orientation?: \"upright\" | \"radial\";\n /** Openness of the ring in degrees (default 90) */\n openness?: number;\n /** Rotation offset in degrees (default 0) */\n rotation?: number;\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n /** CSS class name for the text elements (only applies to string/number labels) */\n labelClassName?: string;\n /** Inline styles for the text elements (only applies to string/number labels) */\n labelStyle?: CSSProperties;\n /**\n * Default size for icon labels. Used to center the icons.\n * If not provided, the component attempts to read `size`, `width`, or `height` from the icon props.\n */\n iconSize?: number;\n};\n\n/**\n * A wrapper around TickRing designed for rendering text labels or icons at radial positions.\n * Automatically handles positioning and rotation based on the specified orientation.\n */\nfunction LabelRing({\n cx,\n cy,\n radius,\n labels,\n orientation = \"upright\",\n openness = 90,\n rotation = 0,\n className,\n style,\n labelClassName,\n labelStyle,\n iconSize,\n}: LabelRingProps) {\n return (\n <TickRing\n cx={cx}\n cy={cy}\n radius={radius}\n openness={openness}\n rotation={rotation}\n count={labels.length}\n className={className}\n style={style}\n renderTick={({ x, y, angle, index }) => {\n const content = labels[index];\n\n // If content is null/undefined, skip\n if (content === null || content === undefined) return null;\n\n const isComponent = React.isValidElement(content);\n\n let transform = undefined;\n\n if (orientation === \"radial\") {\n // Standard radial rotation: text bottom points to center\n\n // Base rotation: angle + 90 makes text tangent to the circle\n let textRotation = angle + 90;\n\n // Readability optimization:\n // If text is in the right hemisphere (roughly 90° to 270°),\n // it appears upside-down. We flip it by 180° to keep it readable.\n // Normalize textRotation to check orientation zone\n const normalizedRotation = ((textRotation % 360) + 360) % 360;\n\n if (normalizedRotation > 90 && normalizedRotation < 270) {\n textRotation += 180;\n }\n\n transform = `rotate(${textRotation}, ${x}, ${y})`;\n }\n\n if (isComponent) {\n // For React nodes (icons, etc), wrap in group and translate\n // Extract rotation value from transform if present to apply to group\n // Note: We strip the center point from rotation for the group transform\n // because the group is already translated to {x, y}\n const rotationValue = transform ? transform.match(/rotate\\(([-\\d.]+)/)?.[1] : 0;\n\n // Attempt to calculate offset to center the icon\n // We assume icons are square and positioned top-left by default (standard SVG behavior)\n let offset = 0;\n if (iconSize !== undefined) {\n offset = -iconSize / 2;\n } else {\n // Heuristic: Try to find size from props\n // This works for Lucide, Radix, and many other icon libraries\n const props = (content as React.ReactElement).props;\n const size = props?.size ?? props?.width ?? props?.height;\n const numericSize = Number(size);\n if (!isNaN(numericSize) && numericSize > 0) {\n offset = -numericSize / 2;\n }\n }\n\n return (\n <g transform={`translate(${x}, ${y}) rotate(${rotationValue}) translate(${offset}, ${offset})`}>\n {content}\n </g>\n );\n }\n\n // For text/numbers\n return (\n <text\n x={x}\n y={y}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n className={labelClassName}\n style={labelStyle}\n transform={transform}\n fill=\"currentColor\"\n >\n {content}\n </text>\n );\n }}\n />\n );\n}\n\nexport default React.memo(LabelRing);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React, { useCallback, useRef, useEffect } from \"react\";\nimport { NoteInteractionController } from \"@cutoff/audio-ui-core\";\n\n/**\n * Props for the useNoteInteraction hook.\n */\nexport interface UseNoteInteractionProps {\n /** Callback triggered when a note is pressed */\n onNoteOn: (note: number) => void;\n /** Callback triggered when a note is released */\n onNoteOff: (note: number) => void;\n /** Whether the interaction is disabled */\n disabled?: boolean;\n /** Optional user-provided pointer down handler */\n onPointerDown?: React.PointerEventHandler;\n /** Optional user-provided pointer move handler */\n onPointerMove?: React.PointerEventHandler;\n /** Optional user-provided pointer up handler */\n onPointerUp?: React.PointerEventHandler;\n /** Optional user-provided pointer cancel handler */\n onPointerCancel?: React.PointerEventHandler;\n}\n\n/**\n * Result object for the useNoteInteraction hook.\n */\nexport interface UseNoteInteractionResult {\n /** Pointer down handler to be attached to the target element */\n onPointerDown: React.PointerEventHandler;\n /** Pointer move handler to be attached to the target element */\n onPointerMove: React.PointerEventHandler;\n /** Pointer up handler to be attached to the target element */\n onPointerUp: React.PointerEventHandler;\n /** Pointer cancel handler to be attached to the target element */\n onPointerCancel: React.PointerEventHandler;\n /** Standardized styles for the interactive element (e.g., touchAction: \"none\") */\n style: React.CSSProperties;\n}\n\n/**\n * Hook to manage note interactions for keyboard-like components.\n *\n * Provides standardized logic for:\n * - Multi-touch support via PointerEvents\n * - Glissando (sliding across keys) detection\n * - Standardized note on/off event triggering\n *\n * It uses the framework-agnostic `NoteInteractionController` to handle the\n * core interaction logic and manages pointer capture for reliable glissando.\n *\n * The hook returns pointer event handlers and a style object containing\n * `touchAction: \"none\"` to prevent default touch behaviors. Cursor styling\n * is handled by the consuming component based on its interactivity state.\n *\n * @param {UseNoteInteractionProps} props - Configuration for the note interaction hook\n * @param {(note: number) => void} props.onNoteOn - Callback triggered when a note is pressed\n * @param {(note: number) => void} props.onNoteOff - Callback triggered when a note is released\n * @param {boolean} [props.disabled=false] - Whether the interaction is disabled\n * @param {React.PointerEventHandler} [props.onPointerDown] - Optional user-provided pointer down handler\n * @param {React.PointerEventHandler} [props.onPointerMove] - Optional user-provided pointer move handler\n * @param {React.PointerEventHandler} [props.onPointerUp] - Optional user-provided pointer up handler\n * @param {React.PointerEventHandler} [props.onPointerCancel] - Optional user-provided pointer cancel handler\n * @returns {UseNoteInteractionResult} Object containing pointer event handlers and styles\n *\n * @example\n * ```tsx\n * const { onPointerDown, onPointerMove, onPointerUp, style } = useNoteInteraction({\n * onNoteOn: (note) => synth.noteOn(note),\n * onNoteOff: (note) => synth.noteOff(note)\n * });\n *\n * return (\n * <svg\n * onPointerDown={onPointerDown}\n * onPointerMove={onPointerMove}\n * onPointerUp={onPointerUp}\n * style={style}\n * >\n * {keys.map(key => (\n * <rect key={key.note} data-note={key.note} {...key.rectProps} />\n * ))}\n * </svg>\n * );\n * ```\n */\nexport function useNoteInteraction({\n onNoteOn,\n onNoteOff,\n disabled = false,\n onPointerDown: userOnPointerDown,\n onPointerMove: userOnPointerMove,\n onPointerUp: userOnPointerUp,\n onPointerCancel: userOnPointerCancel,\n}: UseNoteInteractionProps): UseNoteInteractionResult {\n const controllerRef = useRef<NoteInteractionController | null>(null);\n\n if (!controllerRef.current) {\n controllerRef.current = new NoteInteractionController({\n onNoteOn: (note) => onNoteOn(note),\n onNoteOff: (note) => onNoteOff(note),\n disabled,\n });\n }\n\n useEffect(() => {\n controllerRef.current?.updateConfig({\n onNoteOn: (note) => onNoteOn(note),\n onNoteOff: (note) => onNoteOff(note),\n disabled,\n });\n }, [onNoteOn, onNoteOff, disabled]);\n\n useEffect(() => {\n return () => {\n controllerRef.current?.cancelAll();\n };\n }, []);\n\n const getNoteFromEvent = useCallback((e: React.PointerEvent) => {\n const element = document.elementFromPoint(e.clientX, e.clientY);\n if (!element) return null;\n\n const noteAttr = element.getAttribute(\"data-note\");\n if (noteAttr) {\n return parseInt(noteAttr, 10);\n }\n return null;\n }, []);\n\n const handlePointerDown = useCallback(\n (e: React.PointerEvent) => {\n userOnPointerDown?.(e);\n if (e.defaultPrevented || disabled) return;\n\n // Capture the pointer to continue receiving events even if it leaves the element\n (e.currentTarget as Element).setPointerCapture(e.pointerId);\n\n const note = getNoteFromEvent(e);\n controllerRef.current?.handlePointerDown(e.pointerId, note);\n },\n [userOnPointerDown, disabled, getNoteFromEvent]\n );\n\n const handlePointerMove = useCallback(\n (e: React.PointerEvent) => {\n userOnPointerMove?.(e);\n if (e.defaultPrevented || disabled) return;\n\n const note = getNoteFromEvent(e);\n controllerRef.current?.handlePointerMove(e.pointerId, note);\n },\n [userOnPointerMove, disabled, getNoteFromEvent]\n );\n\n const handlePointerUp = useCallback(\n (e: React.PointerEvent) => {\n userOnPointerUp?.(e);\n if (e.defaultPrevented || disabled) return;\n\n controllerRef.current?.handlePointerUp(e.pointerId);\n },\n [userOnPointerUp, disabled]\n );\n\n const handlePointerCancel = useCallback(\n (e: React.PointerEvent) => {\n userOnPointerCancel?.(e);\n if (e.defaultPrevented || disabled) return;\n\n controllerRef.current?.handlePointerUp(e.pointerId);\n },\n [userOnPointerCancel, disabled]\n );\n\n return {\n onPointerDown: handlePointerDown,\n onPointerMove: handlePointerMove,\n onPointerUp: handlePointerUp,\n onPointerCancel: handlePointerCancel,\n style: {\n touchAction: \"none\",\n },\n };\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo, useCallback } from \"react\";\nimport classNames from \"classnames\";\nimport AdaptiveBox from \"@/primitives/AdaptiveBox\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, AudioControlEvent, BaseProps, ThemableProps } from \"@/types\";\nimport { generateColorVariants } from \"@cutoff/audio-ui-core\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { useThemableProps } from \"@/hooks/useThemableProps\";\nimport { useNoteInteraction } from \"@/hooks/useNoteInteraction\";\nimport {\n createNoteNumSet,\n DIATONIC_TO_CHROMATIC,\n noteNumToNote,\n noteToNoteNum,\n WHITE_KEY_NAMES,\n WHITE_KEY_POSITIONS,\n WHITE_KEY_TO_CHROMATIC,\n} from \"@cutoff/audio-ui-core\";\nimport \"@cutoff/audio-ui-core/styles.css\";\nimport { CLASSNAMES } from \"@cutoff/audio-ui-core\";\nimport { CSS_VARS } from \"@cutoff/audio-ui-core\";\nimport { translateKeysRoundness } from \"@cutoff/audio-ui-core\";\n\n/**\n * Type definition for note names (C to B)\n */\ntype NoteName = (typeof WHITE_KEY_NAMES)[number];\nconst notesCount = WHITE_KEY_NAMES.length;\n\n/**\n * Props for the Keys component\n */\nexport type KeysProps = BaseProps &\n AdaptiveSizeProps &\n // Keys uses AdaptiveBox layout props but deliberately does not support labels.\n // labelMode/labelPosition/labelAlign are omitted on purpose.\n Omit<AdaptiveBoxProps, \"labelMode\" | \"labelPosition\" | \"labelAlign\"> &\n ThemableProps & {\n /** Number of keys on the keyboard\n * @default 61 */\n nbKeys?: number;\n /** Starting note name (A-G)\n * @default 'C' for 61 keys, 'A' for 88 keys */\n startKey?: NoteName;\n /** Octave transpose index (default: 0)\n * Positive values shift notes up by that many octaves, negative values shift down\n * @default 0 */\n octaveShift?: number;\n /** Array of notes that should be highlighted\n * Notes can be specified as:\n * - Strings in the format: NoteName + Octave (+ optional '#' for sharp), e.g., 'C4', 'F#5'\n * - Numbers representing MIDI note IDs (e.g., 60 for C4, 61 for C#4, etc.)\n * @example ['C4', 'E4', 'G4'] or [60, 64, 67] or ['C4', 64, 'G4'] */\n notesOn?: (string | number)[];\n /** Key styling mode\n * - 'theme': Uses theme colors (current behavior, uses color prop and themable hook)\n * - 'classic': Classic piano style with ivory white keys and ebony black keys\n * - 'classic-inverted': Inverted classic style with ebony white keys and ivory black keys\n * @default 'theme' */\n keyStyle?: \"theme\" | \"classic\" | \"classic-inverted\";\n /** Callback triggered when a key is pressed or released.\n * Only active if this prop is provided. */\n onChange?: (event: AudioControlEvent<{ note: number; active: boolean }>) => void;\n };\n\n/**\n * Calculate positive modulo (different from JavaScript's % operator for negative numbers)\n */\nconst positiveModulo = (number: number, modulus: number): number => {\n return ((number % modulus) + modulus) % modulus;\n};\n\n/**\n * Piano Keys component that renders a piano keyboard visualization.\n * Supports variable number of keys, different starting positions, and note highlighting.\n *\n * Features:\n * - Configurable number of keys (default 61, supports any number from 1 to 128)\n * - Customizable starting position (note and octave)\n * - Highlights active notes (supports both note names and MIDI note numbers)\n * - Maintains proper piano key layout and proportions\n * - Responsive sizing through AdaptiveSvgComponent integration\n * - Multiple size variants (xsmall, small, normal, large, xlarge)\n *\n * @property {boolean} adaptiveSize - Whether the keys component fills its container (ignores size constraints when true)\n * @property {number} nbKeys - Number of keys on the keyboard (default 61)\n * @property {NoteName} startKey - Starting note name (A-G) (default 'C' for 61 keys, 'A' for 88 keys)\n * @property {number} octaveShift - Octave transpose index (default 0). Positive values shift notes up by that many octaves, negative values shift down.\n * @property {(string | number)[]} notesOn - Array of notes that should be highlighted. Can contain note names (e.g., 'C4') or MIDI note numbers (e.g., 60 for C4).\n * @property {string} className - Additional CSS classes\n * @property {React.CSSProperties} style - Additional inline styles\n * @property {SizeType} size - Size of the component (xsmall, small, normal, large, xlarge)\n * @property {\"theme\" | \"classic\" | \"classic-inverted\"} keyStyle - Key styling mode (default \"theme\")\n *\n * @example\n * ```tsx\n * // Basic usage\n * <Keys />\n *\n * // Full piano configuration with note names\n * <Keys\n * nbKeys={88}\n * startKey=\"A\"\n * octaveShift={0}\n * notesOn={['C4', 'E4', 'G4']}\n * />\n *\n * // Using MIDI note numbers\n * <Keys\n * notesOn={[60, 64, 67]} // C4, E4, G4\n * />\n *\n * // Mixing note names and MIDI note numbers\n * <Keys\n * notesOn={['C4', 64, 'G4']} // C4, E4, G4\n * />\n *\n * // Custom styling with size\n * <Keys\n * className=\"my-keyboard\"\n * style={{ marginTop: '20px' }}\n * size=\"large\"\n * />\n *\n * // Classic piano style\n * <Keys keyStyle=\"classic\" />\n *\n * // Inverted classic style\n * <Keys keyStyle=\"classic-inverted\" />\n * ```\n */\nfunction Keys({\n nbKeys = 61,\n startKey = nbKeys === 88 ? \"A\" : \"C\",\n octaveShift = 0,\n notesOn = [],\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n keyStyle = \"theme\",\n color,\n roundness,\n className = \"\",\n style = {},\n onChange,\n onClick,\n}: KeysProps) {\n // Ensure nbKeys is within valid range (1-128)\n const validNbKeys = Math.max(1, Math.min(128, nbKeys));\n\n // Interaction setup\n const {\n onPointerDown,\n onPointerMove,\n onPointerUp,\n onPointerCancel,\n style: interactionStyle,\n } = useNoteInteraction({\n onNoteOn: (note) => {\n onChange?.({\n value: { note, active: true },\n normalizedValue: note / 127,\n midiValue: note,\n });\n },\n onNoteOff: (note) => {\n onChange?.({\n value: { note, active: false },\n normalizedValue: note / 127,\n midiValue: note,\n });\n },\n });\n // Memoize initial computations\n const keysDimensions = useMemo(() => {\n const nbOctaves = Math.floor(validNbKeys / 12);\n const keyRemainder = validNbKeys % 12;\n\n // Calculate white keys: Each octave has 7 white keys (C, D, E, F, G, A, B)\n // For partial octaves, use the ratio 7/12 (white keys per semitone) to estimate\n // This ensures accurate white key count for any number of keys\n const whiteKeysInRemainder = Math.ceil((keyRemainder * 7) / 12);\n const nbWhite = nbOctaves * 7 + whiteKeysInRemainder;\n\n const startKeyIndex = WHITE_KEY_NAMES.indexOf(startKey);\n\n const middleKeyIndex = Math.floor((nbWhite - 1) / 2);\n const octavesFromMiddle = Math.floor(middleKeyIndex / 7);\n\n // Octave adjustment for keys starting with A or B:\n // In MIDI convention, A and B belong to the octave of the next C.\n // For example, A4 and B4 are in the same octave as C5, not C4.\n // This adjustment ensures correct octave calculation when starting with A or B.\n const octaveAdjustment = startKeyIndex >= 5 ? 1 : 0;\n\n // Calculate starting octave with special handling for extreme keyboard sizes\n let startOctave;\n if (validNbKeys <= 12) {\n // Very small keyboards (1-12 keys): Center around C4 (middle C) for usability\n startOctave = 4 - octaveAdjustment;\n } else if (validNbKeys >= 120) {\n // Very large keyboards (120-128 keys): Prevent going below C0 (MIDI note 0)\n // This ensures we don't generate invalid MIDI note numbers\n startOctave = Math.max(0, 4 - octavesFromMiddle - octaveAdjustment);\n } else {\n // Standard keyboards: Calculate octave based on middle key position\n // This centers the keyboard around middle C (C4) for typical sizes\n startOctave = 4 - octavesFromMiddle - octaveAdjustment;\n }\n\n // Key dimensions (in SVG units)\n const whiteWidth = 25;\n const whiteHeight = 150;\n const blackWidth = 13;\n const blackXShift = 18;\n const blackHeight = (whiteHeight / 3) * 2;\n const outerStrokeWidth = 2;\n const innerStrokeWidth = 2;\n\n const halfInnerStrokeWidth = innerStrokeWidth / 2;\n\n // Calculate total width\n const width = nbWhite * whiteWidth;\n\n // Calculate black key positions\n const blackKeyShift = parseInt(startKey, 36) - parseInt(\"C\", 36);\n const blackPass = [positiveModulo(2 + blackKeyShift, 7), positiveModulo(6 + blackKeyShift, 7)];\n\n return {\n nbWhite,\n startKeyIndex,\n startOctave,\n whiteWidth,\n whiteHeight,\n blackWidth,\n blackXShift,\n blackHeight,\n outerStrokeWidth,\n innerStrokeWidth,\n width,\n blackPass,\n halfInnerStrokeWidth,\n };\n }, [validNbKeys, startKey]);\n\n // Build CSS variables from props (if provided)\n const { style: themableStyle } = useThemableProps({\n color,\n roundness,\n style,\n });\n\n // Translate normalized roundness to legacy range (0-12) or use CSS variable\n // When roundness is a CSS variable string (from theme), pass it directly to SVG rx attributes.\n // When roundness is undefined, fallback to theme CSS variable.\n // When roundness is a number, translate it to the legacy pixel range.\n const cornerRadius = useMemo(() => {\n if (typeof roundness === \"string\") {\n // CSS variable - pass directly to SVG (browser will resolve it)\n return roundness;\n }\n if (roundness === undefined) {\n // No prop provided - use theme default via CSS variable\n return \"var(--audioui-roundness-keys)\";\n }\n // Numeric value - translate to legacy pixel range (0-12)\n return translateKeysRoundness(roundness);\n }, [roundness]);\n\n // Generate color variants using the centralized utility\n // Used for theme mode rendering and for active keys in classic modes\n // Read color from CSS variable if not provided as prop\n const resolvedColor = color ?? \"var(--audioui-primary-color)\";\n const colorVariants = useMemo(() => {\n return generateColorVariants(resolvedColor, \"luminosity\");\n }, [resolvedColor]);\n\n // Determine key colors based on keyStyle\n // Active keys always use theme color (colorVariants.primary), regardless of keyStyle\n const keyColors = useMemo(() => {\n if (keyStyle === \"theme\") {\n return null; // Use colorVariants instead\n } else if (keyStyle === \"classic\") {\n return {\n whiteFill: `var(${CSS_VARS.keysIvory})`,\n whiteStroke: `var(${CSS_VARS.keysIvoryStroke})`,\n blackFill: `var(${CSS_VARS.keysEbony})`,\n blackStroke: `var(${CSS_VARS.keysEbonyStroke})`,\n };\n } else {\n // classic-inverted\n return {\n whiteFill: `var(${CSS_VARS.keysEbony})`,\n whiteStroke: `var(${CSS_VARS.keysEbonyStroke})`,\n blackFill: `var(${CSS_VARS.keysIvory})`,\n blackStroke: `var(${CSS_VARS.keysIvoryStroke})`,\n };\n }\n }, [keyStyle]);\n\n // Memoize the active notes set for efficient lookups\n const activeNoteNumSet = useMemo(() => {\n return createNoteNumSet(notesOn || []);\n }, [notesOn]);\n\n // Calculate which white key positions should NOT have black keys above them\n // In a standard piano layout, there are no black keys between:\n // - E and F (semitone gap, no black key)\n // - B and C (semitone gap, no black key)\n // These correspond to white key indices 2 and 6 when starting with C\n // The modulo calculation adjusts for different starting keys\n const correctBlackPass = useMemo(() => {\n const startKeyIndex = WHITE_KEY_NAMES.indexOf(startKey);\n return [\n positiveModulo(2 - startKeyIndex, 7), // E-F gap (no black key after E)\n positiveModulo(6 - startKeyIndex, 7), // B-C gap (no black key after B)\n ];\n }, [startKey]);\n\n // Create a memoized function to check if a note is active\n const isNoteActive = useCallback(\n (note: string) => {\n // If it's a string note, convert to MIDI note number and check if it's in the set\n const noteNum = noteToNoteNum(note);\n return noteNum !== -1 && activeNoteNumSet.has(noteNum);\n },\n [activeNoteNumSet]\n );\n\n // Memoize white keys rendering\n const renderWhiteKeys = useMemo(() => {\n const { nbWhite, startKeyIndex, startOctave, whiteWidth, whiteHeight, innerStrokeWidth, halfInnerStrokeWidth } =\n keysDimensions;\n\n // Get the chromatic index of the start key\n const startKeyChromatic = DIATONIC_TO_CHROMATIC[startKey];\n\n // Calculate the base MIDI note number for the starting key\n const baseNoteNum = (startOctave + 1) * 12 + startKeyChromatic;\n\n return Array.from({ length: nbWhite }, (_, index) => {\n // Calculate the diatonic index (white keys only)\n const currentNoteIndex = (startKeyIndex + index) % notesCount;\n\n // Calculate how many octaves we've moved from the start\n const octaveOffset = Math.floor((startKeyIndex + index) / notesCount);\n\n // Calculate the MIDI note number for this key\n // We need to find how many semitones we've moved from the start key\n const chromaticOffset = WHITE_KEY_TO_CHROMATIC[currentNoteIndex] - WHITE_KEY_TO_CHROMATIC[startKeyIndex];\n const adjustedOffset = chromaticOffset + octaveOffset * 12;\n const noteNum = baseNoteNum + adjustedOffset - octaveShift * 12;\n\n // Convert the MIDI note number to a note name and octave\n const currentWhiteNote = noteNumToNote(noteNum);\n\n // Color resolution: Active keys always use theme color for visual feedback\n // Inactive keys use theme colors (theme mode) or classic colors (classic modes)\n let strokeColor: string;\n let fillColor: string;\n\n if (keyStyle === \"theme\") {\n strokeColor = colorVariants.primary50;\n fillColor = isNoteActive(currentWhiteNote) ? colorVariants.primary : \"transparent\";\n } else if (keyColors) {\n strokeColor = keyColors.whiteStroke;\n fillColor = isNoteActive(currentWhiteNote) ? colorVariants.primary : keyColors.whiteFill;\n } else {\n // Fallback (should not happen)\n strokeColor = \"#000\";\n fillColor = \"transparent\";\n }\n\n return (\n <rect\n key={`white-${index}-${currentWhiteNote}`}\n data-note={noteNum}\n style={{\n stroke: strokeColor,\n fill: fillColor,\n rx: cornerRadius,\n ry: cornerRadius,\n }}\n strokeWidth={innerStrokeWidth}\n x={index * whiteWidth + halfInnerStrokeWidth}\n y={halfInnerStrokeWidth}\n width={whiteWidth}\n height={whiteHeight}\n // Use 0 as fallback for older browsers that don't support CSS rx/ry\n // If cornerRadius is a number, we can use it directly\n rx={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n />\n );\n });\n }, [keysDimensions, octaveShift, isNoteActive, cornerRadius, colorVariants, keyColors, keyStyle, startKey]);\n\n // Memoize black keys rendering\n const renderBlackKeys = useMemo(() => {\n const {\n nbWhite,\n startKeyIndex,\n startOctave,\n whiteWidth,\n blackWidth,\n blackXShift,\n blackHeight,\n innerStrokeWidth,\n halfInnerStrokeWidth,\n } = keysDimensions;\n\n // Get the chromatic index of the start key\n const startKeyChromatic = DIATONIC_TO_CHROMATIC[startKey];\n\n // Calculate the base MIDI note number for the starting key\n const baseNoteNum = (startOctave + 1) * 12 + startKeyChromatic;\n\n // Render black keys: There are nbWhite - 1 potential black key positions\n // (one less than white keys because black keys sit between white keys)\n return Array.from({ length: nbWhite - 1 }, (_, index) => {\n const octaveIndex = index % 7;\n // Skip positions that shouldn't have black keys (E-F and B-C gaps)\n if (correctBlackPass.includes(octaveIndex)) return null;\n\n // Calculate the diatonic index (white keys only)\n const currentNoteIndex = (startKeyIndex + index) % notesCount;\n\n // Calculate how many octaves we've moved from the start\n const octaveOffset = Math.floor((startKeyIndex + index) / notesCount);\n\n // Calculate the MIDI note number for this key\n // We need to find how many semitones we've moved from the start key\n const chromaticOffset = WHITE_KEY_TO_CHROMATIC[currentNoteIndex] - WHITE_KEY_TO_CHROMATIC[startKeyIndex];\n const adjustedOffset = chromaticOffset + octaveOffset * 12;\n\n // Black keys are positioned one semitone higher than the white key to their left\n // Add 1 to the chromatic offset to get the black key's MIDI note number\n const noteNum = baseNoteNum + adjustedOffset + 1 - octaveShift * 12;\n\n // Skip black keys that would map to white key positions (E-F and B-C gaps)\n // This prevents duplicate note assignments and ensures correct piano layout\n // The modulo 12 gives us the chromatic position within the octave\n if (WHITE_KEY_POSITIONS.has(noteNum % 12)) return null;\n\n // Convert the MIDI note number to a note name and octave\n const currentBlackNote = noteNumToNote(noteNum);\n\n // Determine colors based on keyStyle\n // Active keys always use theme color (colorVariants.primary)\n let strokeColor: string;\n let fillColor: string;\n\n if (keyStyle === \"theme\") {\n strokeColor = colorVariants.primary50;\n fillColor = isNoteActive(currentBlackNote) ? colorVariants.primary : colorVariants.primary50;\n } else if (keyColors) {\n strokeColor = keyColors.blackStroke;\n fillColor = isNoteActive(currentBlackNote) ? colorVariants.primary : keyColors.blackFill;\n } else {\n // Fallback (should not happen)\n strokeColor = \"#000\";\n fillColor = \"#000\";\n }\n\n return (\n <rect\n key={`black-${index}-${currentBlackNote}`}\n data-note={noteNum}\n style={{\n zIndex: 1,\n stroke: strokeColor,\n fill: fillColor,\n rx: cornerRadius,\n ry: cornerRadius,\n }}\n strokeWidth={innerStrokeWidth}\n x={index * whiteWidth + blackXShift + halfInnerStrokeWidth}\n y={halfInnerStrokeWidth}\n width={blackWidth}\n height={blackHeight}\n // Use 0 as fallback for older browsers that don't support CSS rx/ry\n // If cornerRadius is a number, we can use it directly\n rx={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n />\n );\n }).filter(Boolean);\n }, [\n keysDimensions,\n octaveShift,\n isNoteActive,\n correctBlackPass,\n cornerRadius,\n colorVariants,\n keyColors,\n keyStyle,\n startKey,\n ]);\n\n // Get adaptive sizing values\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"keys\");\n\n // Memoize the classNames calculation: size class first, then base classes, then user className (user takes precedence)\n const componentClassNames = useMemo(() => {\n return classNames(sizeClassName, CLASSNAMES.root, className);\n }, [sizeClassName, className]);\n\n // Add highlight class when interactive (onChange or onClick)\n const svgClassNames = useMemo(() => {\n return onChange || onClick ? CLASSNAMES.highlight : \"\";\n }, [onChange, onClick]);\n\n // Add clickable cursor when interactive (onChange or onClick)\n // Uses CSS variable for customizable cursor type\n // View-only components (no onChange, no onClick) get default cursor\n const svgStyle = useMemo(\n () => ({\n ...(interactionStyle ?? {}),\n ...(onClick || onChange ? { cursor: \"var(--audioui-cursor-clickable)\" as const } : {}),\n }),\n [interactionStyle, onClick, onChange]\n );\n\n return (\n <AdaptiveBox\n displayMode={displayMode ?? \"scaleToFit\"}\n // Keys does not expose labels; hide label row explicitly.\n labelMode=\"none\"\n className={componentClassNames}\n style={{ ...sizeStyle, ...themableStyle }}\n viewBoxWidth={keysDimensions.width + keysDimensions.innerStrokeWidth}\n viewBoxHeight={keysDimensions.whiteHeight + keysDimensions.innerStrokeWidth}\n minWidth={40}\n minHeight={40}\n >\n <AdaptiveBox.Svg\n className={svgClassNames}\n style={svgStyle}\n onClick={onClick}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerCancel}\n >\n {renderWhiteKeys}\n {renderBlackKeys}\n </AdaptiveBox.Svg>\n </AdaptiveBox>\n );\n}\n\n// Wrap the component in React.memo to prevent unnecessary re-renders\nexport default React.memo(Keys);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Set the global theme color.\n * Updates the CSS variable `--audioui-primary-color` on the document root.\n * Color variants (`--audioui-primary-50`, `--audioui-primary-20`) are automatically computed\n * by CSS using `color-mix()`.\n *\n * @param color - Any valid CSS color value (e.g., \"blue\", \"#FF5500\", \"hsl(200, 100%, 50%)\", \"var(--my-color)\")\n *\n * @example\n * ```ts\n * setThemeColor(\"blue\");\n * setThemeColor(\"var(--audioui-theme-purple)\");\n * setThemeColor(\"hsl(200, 100%, 50%)\");\n * ```\n */\nexport function setThemeColor(color: string): void {\n if (typeof document !== \"undefined\") {\n document.documentElement.style.setProperty(\"--audioui-primary-color\", color);\n }\n}\n\n/**\n * Set the global theme roundness.\n * Updates the CSS variable `--audioui-roundness-base` on the document root.\n * Component-specific roundness values are automatically calculated from this base value.\n *\n * This function also updates the `--audioui-linecap-base` helper variable:\n * - value = 0.0 results in \"square\"\n * - value > 0.0 results in \"round\"\n *\n * @param value - Normalized roundness value between 0.0 and 1.0\n *\n * @example\n * ```ts\n * setThemeRoundness(0.3); // 30% roundness, linecap: round\n * setThemeRoundness(0.0); // Square corners, linecap: square\n * setThemeRoundness(1.0); // Maximum roundness, linecap: round\n * ```\n */\nexport function setThemeRoundness(value: number): void {\n if (typeof document !== \"undefined\") {\n document.documentElement.style.setProperty(\"--audioui-roundness-base\", value.toString());\n // Automatically infer linecap from roundness: 0 = square, >0 = round\n const linecap = value === 0 ? \"square\" : \"round\";\n document.documentElement.style.setProperty(\"--audioui-linecap-base\", linecap);\n }\n}\n\n/**\n * Theme configuration object for setting multiple theme values at once.\n */\nexport interface ThemeConfig {\n /** Theme color - any valid CSS color value */\n color?: string;\n /** Roundness (normalized 0.0-1.0) */\n roundness?: number;\n}\n\n/**\n * Set multiple theme values at once.\n * Convenience function that calls individual setter functions for each provided value.\n *\n * @param theme - Theme configuration object with optional color and roundness values\n *\n * @example\n * ```ts\n * setTheme({ color: \"blue\", roundness: 0.3 });\n * setTheme({ color: \"purple\" }); // Only sets color, leaves roundness unchanged\n * ```\n */\nexport function setTheme(theme: ThemeConfig): void {\n if (typeof document === \"undefined\") return;\n\n if (theme.color !== undefined) {\n setThemeColor(theme.color);\n }\n if (theme.roundness !== undefined) {\n setThemeRoundness(theme.roundness);\n }\n}\n\n/**\n * Get current theme color from CSS variable.\n * Reads the value of `--audioui-primary-color` from the document root.\n *\n * @returns The current theme color value as a string, or `null` if not set or in SSR context\n *\n * @example\n * ```ts\n * const currentColor = getThemeColor();\n * if (currentColor) {\n * console.log(`Current theme color: ${currentColor}`);\n * }\n * ```\n */\nexport function getThemeColor(): string | null {\n if (typeof document === \"undefined\" || typeof window === \"undefined\") return null;\n return window.getComputedStyle(document.documentElement).getPropertyValue(\"--audioui-primary-color\").trim() || null;\n}\n\n/**\n * Get current theme roundness from CSS variable.\n * Reads the value of `--audioui-roundness-base` from the document root.\n *\n * @returns The current theme roundness value as a number (0.0-1.0), or `null` if not set or in SSR context\n *\n * @example\n * ```ts\n * const roundness = getThemeRoundness();\n * if (roundness !== null) {\n * console.log(`Current roundness: ${roundness * 100}%`);\n * }\n * ```\n */\nexport function getThemeRoundness(): number | null {\n if (typeof document === \"undefined\" || typeof window === \"undefined\") return null;\n const value = window.getComputedStyle(document.documentElement).getPropertyValue(\"--audioui-roundness-base\").trim();\n return value ? parseFloat(value) : null;\n}\n"],"names":["OptionView","_props","ButtonView","normalizedValue","threshold","roundness","_color","className","isOn","useMemo","cornerRadius","translateButtonRoundness","DEFAULT_ROUNDNESS","buttonStyles","jsx","VIEW_BOX","ButtonViewMemo","React","abbreviateText","text","maxLength","BoxContext","createContext","useBoxContext","ctx","useContext","AdaptiveBox","style","displayMode","labelMode","labelHeightUnits","labelOverflow","viewBoxWidth","viewBoxHeight","minWidth","minHeight","debug","children","svgInfo","setSvgInfo","useState","labelInfo","setLabelInfo","registerSvg","useCallback","info","prev","next","registerLabel","labelHeightUnitsEffective","styleH","styleV","hAlign","vAlign","effectiveLabelPosition","isFill","showLabelSpace","mainContentGridRow","ctxValue","L","combinedHeightUnits","gridTemplateRows","svgPercent","labelPercent","Svg","onWheel","rest","useLayoutEffect","svgRef","useRef","useEffect","element","wheelHandler","e","preserveAspect","Label","position","align","shouldAbbreviate","labelContent","visibility","gridRow","HtmlOverlay","pointerEvents","useAudioParameter","value","onChange","parameterDef","userValueFormatter","userLabel","valueAsLabel","converter","AudioParameterConverter","valueRef","commitValue","newValue","normalized","midi","setNormalizedValue","newNormal","clamped","realValue","adjustValue","delta","sensitivity","currentReal","currentNormal","formattedValue","customValue","effectiveLabel","getDefaultNormalizedValue","resetToDefault","defaultNormalized","useBooleanInteraction","mode","onValueChange","disabled","userOnMouseDown","userOnMouseUp","userOnTouchStart","userOnTouchEnd","userOnKeyDown","userOnKeyUp","controllerRef","buttonElementRef","touchIsInsideRef","isGlobalPointerDownRef","BooleanInteractionController","handleGlobalMouseDown","handleGlobalTouchStart","_e","handleGlobalMouseUp","handleGlobalTouchEnd","handleGlobalTouchMove","controller","buttonElement","touch","elementAtPoint","isInside","handleMouseDown","handleMouseUp","handleMouseEnter","handleMouseLeave","setButtonElement","handleTouchStart","handleTouchEnd","handleTouchMove","handleKeyDown","handleKeyUp","useBooleanParameterResolution","parameter","paramId","label","latch","midiResolution","derivedParameter","AudioParameterFactory","BooleanControl","props","View","viewProps","htmlOverlay","labelPosition","labelAlign","viewBoxWidthUnits","viewBoxHeightUnits","onClick","onMouseDown","onMouseUp","onMouseEnter","onMouseLeave","fireChange","handleTouchEndForClick","syntheticEvent","handleTouchStartForClick","componentClassNames","classNames","CLASSNAMES","svgClassNames","svgStyle","handleMouseDownWithRef","handleTouchStartWithRef","jsxs","BooleanControl$1","useAdaptiveSize","adaptiveSize","size","componentType","orientation","sizeClassName","getSizeClassForComponent","sizeStyle","getSizeStyleForComponent","useThemableProps","color","vars","clampedRoundness","clampNormalized","generateTransparencyVariant","Button","themableStyle","adaptiveSizeStyle","Button_default","useArcAngle","openness","rotation","bipolar","positions","calculateArcAngles","RingArc","startAngle","endAngle","cx","cy","radius","thickness","strokeLinecap","isFullCircle","path","calculateArcPath","RingArc$1","ValueRing","fgArcStyle","bgArcStyle","valueToAngle","valueStartAngle","actualRadius","Fragment","ValueRing$1","RadialImage","imageHref","transform","RadialImage$1","RotaryImage","pivotX","pivotY","RotaryImage$1","KnobView","variant","svgOverlayRotary","svgOverlay","effectiveThickness","pixelThickness","translateKnobThickness","translateKnobRoundness","valueRing","finalLinecap","iconRadius","overlayContent","MemoKnobView","useContinuousInteraction","keyboardStep","DEFAULT_KEYBOARD_STEP","interactionMode","direction","wheelSensitivity","step","min","max","paramStep","editable","onDragStart","onDragEnd","userOnWheel","userOnDoubleClick","effectiveStep","effectiveSensitivity","base","DEFAULT_CONTINUOUS_SENSITIVITY","minSensitivityForStep","TARGET_PIXELS_PER_STEP","ContinuousInteractionController","ctrl","useContinuousParameterResolution","unit","scale","defaultValue","ContinuousControl","interactionDirection","interactionSensitivity","valueFormatter","isDragging","setIsDragging","isRecentlyActive","setIsRecentlyActive","activityTimerRef","handleDragStart","handleDragEnd","handleActivity","showValueAsLabel","wrappedAdjustValue","effectiveInteractionMode","effectiveDirection","interactiveProps","ContinuousControl$1","DEFAULT_OPENNESS","DEFAULT_ROTATION","Knob","clampedThickness","displayValueOverlay","Knob_default","useDiscreteInteraction","options","userOnClick","DiscreteInteractionController","cycleNext","stepNext","stepPrev","handleClick","useDiscreteParameterResolution","midiMapping","optionEls","visualContentMap","child","index","val","param","effectiveDefaultValue","getLabel","inferredOptions","DiscreteControl","effectiveValue","handleValueChange","DiscreteControl$1","CONTENT_WRAPPER_STYLE","ICON_WRAPPER_STYLE","CycleButton","renderOption","opt","content","wrapContent","node","CycleButton_default","LinearStrip","length","legacyRoundness","translateSliderRoundness","rectX","rectY","LinearStrip$1","ValueStrip","rectProps","cursorY","calculateLinearPosition","x","width","y","height","ValueStrip$1","LinearCursor","aspectRatio","isEllipse","cursorX","cursorYPos","LinearCursor$1","TICK_TRACK_SHIFT","LABEL_TRACK_SHIFT","SliderView","cursorSize","cursorAspectRatio","cursorRoundness","cursorImageHref","cursorClassName","cursorStyle","translateSliderThickness","stripPadding","cursorWidth","shouldRenderCursor","effectiveCursorRoundness","cursorHeight","cursorLength","valueStripLength","bgStripStyle","CSS_VARS","valueStripStyle","cursorStyleMemo","SLIDER_VIEWBOX","VerticalSliderViewComponent","VerticalSliderViewMemo","VerticalSliderView","HorizontalSliderViewComponent","HorizontalSliderViewMemo","HorizontalSliderView","SliderView_default","Slider","mergedClassName","ViewComponent","Slider_default","FilmstripImage","frameWidth","frameHeight","frameCount","frameRotation","invertValue","clampedValue","frameIndex","totalWidth","totalHeight","viewBoxX","viewBoxY","centerX","centerY","FilmstripImage$1","FilmstripView","FilmstripViewMemo","FilmStripContinuousControl","FilmStripContinuousControl_default","FilmStripDiscreteControl","FilmStripDiscreteControl_default","FilmStripBooleanControl","FilmStripBooleanControl_default","ImageKnobView","ImageKnobViewMemo","ImageKnob","ImageKnob_default","ImageRotarySwitch","ImageRotarySwitch_default","Image","Image$1","ImageSwitchView","imageHrefFalse","imageHrefTrue","ImageSwitchViewMemo","ImageSwitch","ImageSwitch_default","RadialHtmlOverlay","RadialHtmlOverlay_default","RevealingPath","resolution","numericResolution","calculatedOffset","RevealingPath_default","TickRing","count","renderTick","clampedOpenness","baseStart","baseEnd","totalAngle","numTicks","angleStep","results","r","n","i","angle","pos","polarToCartesian","pathData","commands","center","rx","ry","outer","inner","TickRing$1","LabelRing","labels","labelClassName","labelStyle","iconSize","isComponent","textRotation","normalizedRotation","rotationValue","offset","numericSize","LabelRing_default","useNoteInteraction","onNoteOn","onNoteOff","userOnPointerDown","userOnPointerMove","userOnPointerUp","userOnPointerCancel","NoteInteractionController","note","getNoteFromEvent","noteAttr","handlePointerDown","handlePointerMove","handlePointerUp","handlePointerCancel","notesCount","WHITE_KEY_NAMES","positiveModulo","number","modulus","Keys","nbKeys","startKey","octaveShift","notesOn","keyStyle","validNbKeys","onPointerDown","onPointerMove","onPointerUp","onPointerCancel","interactionStyle","keysDimensions","nbOctaves","keyRemainder","whiteKeysInRemainder","nbWhite","startKeyIndex","middleKeyIndex","octavesFromMiddle","octaveAdjustment","startOctave","whiteWidth","whiteHeight","blackWidth","blackXShift","blackHeight","outerStrokeWidth","innerStrokeWidth","halfInnerStrokeWidth","blackKeyShift","blackPass","translateKeysRoundness","resolvedColor","colorVariants","generateColorVariants","keyColors","activeNoteNumSet","createNoteNumSet","correctBlackPass","isNoteActive","noteNum","noteToNoteNum","renderWhiteKeys","startKeyChromatic","DIATONIC_TO_CHROMATIC","baseNoteNum","_","currentNoteIndex","octaveOffset","adjustedOffset","WHITE_KEY_TO_CHROMATIC","currentWhiteNote","noteNumToNote","strokeColor","fillColor","renderBlackKeys","octaveIndex","WHITE_KEY_POSITIONS","currentBlackNote","Keys_default","setThemeColor","setThemeRoundness","linecap","setTheme","theme","getThemeColor","getThemeRoundness"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/components/primitives/controls/OptionView.tsx","../src/components/defaults/controls/ButtonView.tsx","../src/utils/textUtils.ts","../src/components/primitives/AdaptiveBox.tsx","../src/hooks/useAudioParameter.ts","../src/hooks/useBooleanInteraction.ts","../src/hooks/useBooleanParameterResolution.ts","../src/components/primitives/controls/BooleanControl.tsx","../src/hooks/useAdaptiveSize.ts","../src/hooks/useThemableProps.ts","../src/components/defaults/controls/Button.tsx","../src/hooks/useArcAngle.ts","../src/components/primitives/svg/RingArc.tsx","../src/components/primitives/svg/ValueRing.tsx","../src/components/primitives/svg/RadialImage.tsx","../src/components/primitives/svg/RotaryImage.tsx","../src/components/defaults/controls/KnobView.tsx","../src/hooks/useContinuousInteraction.ts","../src/hooks/useContinuousParameterResolution.ts","../src/components/primitives/controls/ContinuousControl.tsx","../src/components/defaults/controls/Knob.tsx","../src/hooks/useDiscreteInteraction.ts","../src/hooks/useDiscreteParameterResolution.ts","../src/components/primitives/controls/DiscreteControl.tsx","../src/components/defaults/controls/CycleButton.tsx","../src/components/primitives/svg/LinearStrip.tsx","../src/components/primitives/svg/ValueStrip.tsx","../src/components/primitives/svg/LinearCursor.tsx","../src/components/defaults/controls/SliderView.tsx","../src/components/defaults/controls/Slider.tsx","../src/components/primitives/svg/FilmstripImage.tsx","../src/components/generic/controls/FilmstripView.tsx","../src/components/generic/controls/FilmStripContinuousControl.tsx","../src/components/generic/controls/FilmStripDiscreteControl.tsx","../src/components/generic/controls/FilmStripBooleanControl.tsx","../src/components/generic/controls/ImageKnobView.tsx","../src/components/generic/controls/ImageKnob.tsx","../src/components/generic/controls/ImageRotarySwitch.tsx","../src/components/primitives/svg/Image.tsx","../src/components/generic/controls/ImageSwitchView.tsx","../src/components/generic/controls/ImageSwitch.tsx","../src/components/primitives/svg/RadialHtmlOverlay.tsx","../src/components/primitives/svg/RevealingPath.tsx","../src/components/primitives/svg/TickRing.tsx","../src/components/primitives/svg/LabelRing.tsx","../src/hooks/useNoteInteraction.ts","../src/components/defaults/devices/Keys.tsx","../src/utils/theme.ts"],"sourcesContent":["/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\n\nexport type OptionViewProps = {\n /** Value associated with this option */\n value?: string | number;\n /** Child content (Visual representation) */\n children?: React.ReactNode;\n /** Optional text label for the parameter model (accessibility/aria-label) */\n label?: string;\n};\n\nexport default function OptionView(_props: OptionViewProps) {\n return null; // This component is never rendered directly\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport { translateButtonRoundness } from \"@cutoff/audio-ui-core\";\nimport { DEFAULT_ROUNDNESS } from \"@cutoff/audio-ui-core\";\nimport { ControlComponent } from \"@/types\";\n\n/**\n * Props for the ButtonView component\n */\nexport type ButtonViewProps = {\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Threshold for determining \"on\" state (default 0.5) */\n threshold?: number;\n /** Corner roundness (normalized 0.0-1.0, maps to 0-50, or CSS variable string) */\n roundness?: number | string;\n /** Color prop (kept for API compatibility, but colors are read from CSS variables) */\n color: string;\n /** Additional CSS class name */\n className?: string;\n};\n\n/**\n * Pure SVG presentation component for a button.\n * Renders a rectangle with conditional styling based on normalized value vs threshold.\n *\n * Colors are read from CSS variables (`--audioui-primary-color`, `--audioui-primary-50`, `--audioui-primary-20`)\n * which are set by the parent Button component based on the `color` prop.\n *\n * @param normalizedValue - Value between 0 and 1\n * @param threshold - Threshold value (default 0.5), determines \"on\" state\n * @param roundness - Normalized roundness 0.0-1.0 (default 0.3, maps to 0-50)\n * @param color - Color prop (kept for API compatibility, but not used - CSS variables are used instead)\n * @param className - Optional CSS class\n */\nfunction ButtonView({\n normalizedValue,\n threshold = 0.5,\n roundness,\n color: _color, // Prefixed with _ to indicate intentionally unused (kept for API compatibility)\n className,\n}: ButtonViewProps): React.JSX.Element {\n // Determine if button is \"on\" based on threshold\n const isOn = useMemo(() => normalizedValue > threshold, [normalizedValue, threshold]);\n\n // Translate normalized roundness to legacy range (0-50) or use CSS variable\n // When roundness is a CSS variable string (from theme), pass it directly to SVG rx/ry attributes.\n // When roundness is a number, translate it to the legacy pixel range.\n const cornerRadius = useMemo(() => {\n if (typeof roundness === \"string\") {\n // CSS variable - pass directly to SVG (browser will resolve it)\n return roundness;\n }\n // Numeric value - translate to legacy pixel range (0-50)\n return translateButtonRoundness(roundness ?? DEFAULT_ROUNDNESS);\n }, [roundness]);\n\n // Use CSS variables for colors - CSS handles variant generation via color-mix\n const buttonStyles = useMemo(\n () => ({\n stroke: isOn ? \"var(--audioui-primary-50)\" : \"var(--audioui-primary-20)\",\n fill: isOn ? \"var(--audioui-primary-color)\" : \"var(--audioui-primary-50)\",\n strokeWidth: \"var(--audioui-button-stroke-width, 5px)\",\n }),\n [isOn]\n );\n\n return (\n <rect\n className={className}\n style={{\n stroke: buttonStyles.stroke,\n fill: buttonStyles.fill,\n strokeWidth: buttonStyles.strokeWidth,\n rx: cornerRadius,\n ry: cornerRadius,\n }}\n x={10}\n y={10}\n width={80}\n height={40}\n // Use 0 as fallback for older browsers that don't support CSS rx/ry\n // If cornerRadius is a number, we can use it directly\n rx={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n ry={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n />\n );\n}\n\n/**\n * ViewBox dimensions for the ButtonView component.\n * The parent component should use these values when setting up the SVG container.\n */\nconst VIEW_BOX = {\n width: 100,\n height: 60,\n} as const;\n\nconst ButtonViewMemo = React.memo(ButtonView);\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ButtonViewMemo as any).viewBox = VIEW_BOX;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ButtonViewMemo as any).labelHeightUnits = 20;\n\nexport default ButtonViewMemo as unknown as ControlComponent<\n Omit<ButtonViewProps, \"normalizedValue\" | \"className\" | \"style\">\n>;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Abbreviates text to a maximum number of characters.\n * Returns the first N characters of the text, or the original text if it's shorter.\n *\n * @param text - The text to abbreviate\n * @param maxLength - Maximum length (default: 3)\n * @returns Abbreviated text\n *\n * @example\n * abbreviateText(\"Volume\", 3) // \"Vol\"\n * abbreviateText(\"Hi\", 3) // \"Hi\"\n * abbreviateText(\"\", 3) // \"\"\n */\nexport function abbreviateText(text: string, maxLength: number = 3): string {\n if (!text || text.length <= maxLength) {\n return text;\n }\n return text.slice(0, maxLength);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, {\n createContext,\n CSSProperties,\n PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\nimport { abbreviateText } from \"@/utils/textUtils\";\n\n// Shared string unions following the demo controls and spec\nexport type FlexAlign = \"start\" | \"center\" | \"end\";\nexport type DisplayMode = \"scaleToFit\" | \"fill\";\nexport type LabelMode = \"none\" | \"hidden\" | \"visible\";\nexport type LabelPosition = \"above\" | \"below\";\n\n// Context to coordinate between root and subcomponents\ninterface BoxContextValue {\n // Minimal config exposed to children to avoid unnecessary re-renders\n hasLabel: boolean;\n labelPosition: LabelPosition;\n\n // Root-level configuration\n displayMode: DisplayMode;\n labelMode: LabelMode;\n labelHeightUnits: number;\n labelOverflow: \"ellipsis\" | \"abbreviate\" | \"auto\";\n debug: boolean;\n\n // ViewBox dimensions (for SVG this maps to viewBox; for Canvas/GL this will map to canvas/gl dimensions)\n viewBoxWidth: number;\n viewBoxHeight: number;\n\n // Computed grid row for main content (Svg, HtmlOverlay, Canvas, GL all use this)\n mainContentGridRow: string;\n\n // Registration APIs\n registerSvg: (info: { hAlign?: FlexAlign; vAlign?: FlexAlign }) => void;\n registerLabel: (info: { position?: LabelPosition; align?: FlexAlign }) => void;\n}\n\nconst BoxContext = createContext<BoxContextValue | null>(null);\n\nfunction useBoxContext() {\n const ctx = useContext(BoxContext);\n if (!ctx) throw new Error(\"AdaptiveBox subcomponents must be used within <AdaptiveBox>\");\n return ctx;\n}\n\nexport interface AdaptiveBoxProps extends PropsWithChildren {\n className?: string;\n style?: CSSProperties;\n displayMode?: DisplayMode;\n labelMode?: LabelMode;\n labelHeightUnits?: number; // in the same units as viewBox height; default 15\n labelOverflow?: \"ellipsis\" | \"abbreviate\" | \"auto\";\n /**\n * ViewBox width in the same coordinate system as the content.\n * For SVG content, this maps to the SVG viewBox width.\n * For Canvas/GL content (future), this will map to canvas/gl dimensions.\n */\n viewBoxWidth: number;\n /**\n * ViewBox height in the same coordinate system as the content.\n * For SVG content, this maps to the SVG viewBox height.\n * For Canvas/GL content (future), this will map to canvas/gl dimensions.\n */\n viewBoxHeight: number;\n minWidth?: number;\n minHeight?: number;\n debug?: boolean; // Enables visual debugging aids (scaler border, svg background). Defaults to false\n}\n\n/**\n * AdaptiveBox provides a CSS/SVG-based layout system for controls with labels.\n *\n * Handles aspect ratio preservation, label positioning, and responsive sizing using\n * container queries. The component automatically adapts its layout based on labelMode\n * and viewBox dimensions to prevent layout shift during initial render.\n *\n * @param props - Component props including layout configuration and viewBox dimensions\n * @returns Rendered AdaptiveBox with subcomponents (Svg, Label, HtmlOverlay) available via context\n *\n * @example\n * ```tsx\n * <AdaptiveBox\n * viewBoxWidth={100}\n * viewBoxHeight={100}\n * labelMode=\"visible\"\n * >\n * <AdaptiveBox.Svg>\n * <circle cx={50} cy={50} r={40} />\n * </AdaptiveBox.Svg>\n * <AdaptiveBox.Label>Volume</AdaptiveBox.Label>\n * </AdaptiveBox>\n * ```\n */\nexport function AdaptiveBox({\n className,\n style,\n displayMode = \"scaleToFit\",\n labelMode = \"visible\",\n labelHeightUnits,\n labelOverflow = \"auto\",\n viewBoxWidth,\n viewBoxHeight,\n minWidth,\n minHeight,\n debug = false,\n children,\n}: AdaptiveBoxProps) {\n const [svgInfo, setSvgInfo] = useState<{ hAlign?: FlexAlign; vAlign?: FlexAlign } | null>(null);\n const [labelInfo, setLabelInfo] = useState<{ position: LabelPosition; align: FlexAlign } | null>(null);\n\n // Stable callbacks that only update state when values actually change\n const registerSvg = useCallback((info: { hAlign?: FlexAlign; vAlign?: FlexAlign }) => {\n setSvgInfo((prev) => {\n const next = {\n hAlign: info.hAlign,\n vAlign: info.vAlign,\n } as const;\n if (!prev || prev.hAlign !== next.hAlign || prev.vAlign !== next.vAlign) {\n return { ...next };\n }\n return prev;\n });\n }, []);\n\n const registerLabel = useCallback((info: { position?: LabelPosition; align?: FlexAlign }) => {\n setLabelInfo((prev) => {\n const next = {\n position: info.position ?? \"below\",\n align: info.align ?? \"center\",\n } as const;\n if (!prev || prev.position !== next.position || prev.align !== next.align) {\n return { ...next };\n }\n return prev;\n });\n }, []);\n\n const labelHeightUnitsEffective = labelHeightUnits ?? 15;\n\n // Derived layout numbers (kept local for readability and to avoid context churn)\n const styleH = (style?.justifySelf as FlexAlign) ?? \"center\";\n const styleV = (style?.alignSelf as FlexAlign) ?? \"center\";\n const hAlign = svgInfo?.hAlign ?? styleH;\n const vAlign = svgInfo?.vAlign ?? styleV;\n const effectiveLabelPosition: LabelPosition = labelInfo?.position ?? \"below\";\n const isFill = displayMode === \"fill\";\n\n // Compute grid row for main content (used by Svg, HtmlOverlay, Canvas, GL)\n // Only labelMode matters for reserving space - not whether labelInfo has been registered yet\n // This prevents layout shift during initial render when Label component registers via useLayoutEffect\n const showLabelSpace = labelMode !== \"none\";\n const mainContentGridRow = showLabelSpace && effectiveLabelPosition === \"above\" ? \"2 / 3\" : \"1 / 2\";\n\n const ctxValue = useMemo<BoxContextValue>(\n () => ({\n hasLabel: !!labelInfo,\n labelPosition: labelInfo?.position ?? \"below\",\n displayMode,\n labelMode,\n labelHeightUnits: labelHeightUnitsEffective,\n labelOverflow,\n debug,\n viewBoxWidth,\n viewBoxHeight,\n mainContentGridRow,\n registerSvg,\n registerLabel,\n }),\n [\n labelInfo,\n displayMode,\n labelMode,\n labelHeightUnitsEffective,\n labelOverflow,\n debug,\n viewBoxWidth,\n viewBoxHeight,\n mainContentGridRow,\n registerSvg,\n registerLabel,\n ]\n );\n const L = labelHeightUnitsEffective;\n const combinedHeightUnits = showLabelSpace ? viewBoxHeight + L : viewBoxHeight;\n\n // Grid template rows for SVG + (optional) label\n let gridTemplateRows = \"1fr\";\n if (showLabelSpace) {\n const svgPercent = (viewBoxHeight / combinedHeightUnits) * 100;\n const labelPercent = (L / combinedHeightUnits) * 100;\n if (effectiveLabelPosition === \"above\") {\n gridTemplateRows = `${labelPercent}% ${svgPercent}%`;\n } else {\n gridTemplateRows = `${svgPercent}% ${labelPercent}%`;\n }\n }\n\n return (\n <BoxContext.Provider value={ctxValue}>\n <div\n data-name=\"Control+Label Wrapper\"\n className={className}\n style={{\n width: \"100%\",\n height: \"100%\",\n containerType: \"size\",\n display: \"grid\",\n justifyItems: \"center\",\n alignItems: \"center\",\n overflow: \"hidden\",\n minWidth,\n minHeight,\n ...(style ?? {}),\n }}\n >\n <div\n data-name=\"Aspect Scaler\"\n style={{\n aspectRatio: `${viewBoxWidth} / ${combinedHeightUnits}`,\n width: isFill ? \"100%\" : `min(100%, calc(100cqh * ${viewBoxWidth} / ${combinedHeightUnits}))`,\n height: isFill ? \"100%\" : \"auto\",\n display: \"grid\",\n gridTemplateRows,\n justifyItems: \"center\",\n alignItems: \"center\",\n justifySelf: hAlign,\n alignSelf: vAlign,\n minWidth: 0,\n minHeight: 0,\n containerType: \"inline-size\",\n position: \"relative\",\n border: debug ? \"2px solid hsl(41, 96%, 40%)\" : undefined,\n }}\n >\n {/* SVG and Label are rendered by subcomponents via context; this wrapper only provides layout */}\n {children}\n </div>\n </div>\n </BoxContext.Provider>\n );\n}\n\nexport interface AdaptiveBoxSvgProps extends PropsWithChildren {\n vAlign?: FlexAlign;\n hAlign?: FlexAlign;\n className?: string;\n style?: CSSProperties;\n // Event handlers (use native WheelEvent as requested)\n onWheel?: (e: React.WheelEvent<SVGSVGElement>) => void;\n onClick?: React.MouseEventHandler<SVGSVGElement>;\n onDoubleClick?: React.MouseEventHandler<SVGSVGElement>;\n onMouseDown?: React.MouseEventHandler<SVGSVGElement>;\n onMouseUp?: React.MouseEventHandler<SVGSVGElement>;\n onMouseEnter?: React.MouseEventHandler<SVGSVGElement>;\n onMouseLeave?: React.MouseEventHandler<SVGSVGElement>;\n onTouchStart?: React.TouchEventHandler<SVGSVGElement>;\n onTouchMove?: React.TouchEventHandler<SVGSVGElement>;\n onTouchEnd?: React.TouchEventHandler<SVGSVGElement>;\n onKeyDown?: React.KeyboardEventHandler<SVGSVGElement>;\n onKeyUp?: React.KeyboardEventHandler<SVGSVGElement>;\n onPointerDown?: React.PointerEventHandler<SVGSVGElement>;\n onPointerMove?: React.PointerEventHandler<SVGSVGElement>;\n onPointerUp?: React.PointerEventHandler<SVGSVGElement>;\n onPointerCancel?: React.PointerEventHandler<SVGSVGElement>;\n tabIndex?: number;\n role?: string;\n \"aria-valuenow\"?: number;\n \"aria-valuemin\"?: number;\n \"aria-valuemax\"?: number;\n \"aria-label\"?: string;\n \"aria-valuetext\"?: string;\n \"aria-orientation\"?: \"horizontal\" | \"vertical\";\n \"aria-pressed\"?: boolean | \"mixed\";\n}\n\nfunction Svg({ vAlign, hAlign, className, style, children, onWheel, ...rest }: AdaptiveBoxSvgProps) {\n const ctx = useBoxContext();\n\n // Register alignment preferences (viewBox dimensions come from AdaptiveBox props)\n useLayoutEffect(() => {\n ctx.registerSvg({ hAlign, vAlign });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hAlign, vAlign]);\n\n const svgRef = useRef<SVGSVGElement>(null);\n\n // Use native non-passive listener to reliably prevent scrolling (especially Safari/Mobile)\n // React's onWheel prop is passive by default in some contexts\n useEffect(() => {\n if (!svgRef.current || !onWheel) return;\n\n const element = svgRef.current;\n const wheelHandler = (e: globalThis.WheelEvent) => {\n onWheel(e as unknown as React.WheelEvent<SVGSVGElement>);\n };\n\n element.addEventListener(\"wheel\", wheelHandler, { passive: false });\n return () => element.removeEventListener(\"wheel\", wheelHandler);\n }, [onWheel]);\n\n const preserveAspect = ctx.displayMode === \"fill\" ? \"none\" : \"xMidYMid meet\";\n\n return (\n <svg\n ref={svgRef}\n data-name=\"Main Component\"\n viewBox={`0 0 ${ctx.viewBoxWidth} ${ctx.viewBoxHeight}`}\n preserveAspectRatio={preserveAspect as React.SVGProps<SVGSVGElement>[\"preserveAspectRatio\"]}\n className={className}\n style={{\n display: \"block\",\n width: \"100%\",\n height: \"100%\",\n // Position in main content grid row\n gridRow: ctx.mainContentGridRow,\n gridColumn: 1,\n backgroundColor: ctx.debug ? \"hsl(0, 100%, 50% / 0.06)\" : undefined,\n ...(style ?? {}),\n }}\n {...rest}\n >\n {children}\n </svg>\n );\n}\n\nexport interface AdaptiveBoxLabelProps extends PropsWithChildren {\n className?: string;\n style?: CSSProperties;\n position?: LabelPosition; // above | below\n align?: FlexAlign; // start | center | end\n}\n\nfunction Label({ className, style, position = \"below\", align = \"center\", children }: AdaptiveBoxLabelProps) {\n const ctx = useBoxContext();\n\n useLayoutEffect(() => {\n ctx.registerLabel({ position, align });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [position, align]);\n\n // Determine if we should abbreviate based on labelOverflow prop\n // Must be called before early return to satisfy React hooks rules\n const shouldAbbreviate = useMemo(() => {\n if (ctx.labelOverflow === \"abbreviate\") {\n return true;\n }\n if (ctx.labelOverflow === \"ellipsis\") {\n return false;\n }\n // \"auto\" mode: abbreviate only when viewBox is portrait (width < height)\n return ctx.viewBoxWidth < ctx.viewBoxHeight;\n }, [ctx.labelOverflow, ctx.viewBoxWidth, ctx.viewBoxHeight]);\n\n const labelContent = useMemo(() => {\n if (shouldAbbreviate && typeof children === \"string\" && children.length > 5) {\n return abbreviateText(children, 3);\n }\n return children;\n }, [children, shouldAbbreviate]);\n\n if (ctx.labelMode === \"none\") return null;\n\n const visibility = ctx.labelMode === \"hidden\" ? \"hidden\" : \"visible\";\n const gridRow = ctx.labelPosition === \"above\" ? \"1 / 2\" : \"2 / 3\";\n\n return (\n <div\n data-name=\"Label\"\n className={className}\n style={{\n width: \"100%\",\n gridRow,\n visibility,\n containerType: \"size\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: align,\n ...(style ?? {}),\n }}\n >\n <span\n style={{\n fontSize: \"75cqh\",\n lineHeight: 1,\n whiteSpace: \"nowrap\",\n overflow: \"hidden\",\n textOverflow: shouldAbbreviate ? \"clip\" : \"ellipsis\",\n }}\n >\n {labelContent}\n </span>\n </div>\n );\n}\n\nexport interface AdaptiveBoxHtmlOverlayProps extends PropsWithChildren {\n className?: string;\n style?: CSSProperties;\n /**\n * Pointer events behavior.\n * - \"none\" (default): Clicks pass through to elements below (e.g., SVG)\n * - \"auto\": Overlay is interactive\n */\n pointerEvents?: \"none\" | \"auto\";\n}\n\n/**\n * HTML overlay positioned over the main content area (same grid cell as Svg/Canvas/GL).\n * Used for rendering text, icons, or other HTML content on top of SVG graphics.\n *\n * This approach avoids Safari's foreignObject rendering bugs with container queries\n * by rendering HTML content outside the SVG as a sibling element.\n *\n * The overlay provides a container query context (`containerType: \"size\"`) enabling\n * responsive sizing with `cqmin`, `cqmax`, `cqw`, `cqh` units.\n *\n * Uses CSS Grid stacking: multiple elements in the same grid cell overlap\n * in DOM order (later elements appear on top).\n */\nfunction HtmlOverlay({ className, style, pointerEvents = \"none\", children }: AdaptiveBoxHtmlOverlayProps) {\n const ctx = useBoxContext();\n\n return (\n <div\n data-name=\"HTML Overlay\"\n className={className}\n style={{\n // Same grid cell as Svg - CSS Grid stacks elements in DOM order\n gridRow: ctx.mainContentGridRow,\n gridColumn: 1,\n // Override parent's centering to fill the entire grid cell\n placeSelf: \"stretch\",\n // Container query context for cqmin/cqmax units\n containerType: \"size\",\n // Center content within the overlay\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n // Pointer events (default: pass through to SVG below)\n pointerEvents,\n ...(style ?? {}),\n }}\n >\n {children}\n </div>\n );\n}\n\n// Compose compound component\nAdaptiveBox.Svg = Svg;\nAdaptiveBox.Label = Label;\nAdaptiveBox.HtmlOverlay = HtmlOverlay;\n\n// Type exports (using type aliases instead of namespace)\nexport type AdaptiveBoxSvg = typeof Svg;\nexport type AdaptiveBoxLabel = typeof Label;\nexport type AdaptiveBoxHtmlOverlay = typeof HtmlOverlay;\n\nexport default AdaptiveBox;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { useMemo, useCallback, useRef } from \"react\";\nimport { AudioParameter, AudioParameterConverter } from \"@cutoff/audio-ui-core\";\nimport { AudioControlEvent } from \"../components/types\";\n\nexport interface UseAudioParameterResult {\n /** The normalized value (0..1) for UI rendering */\n normalizedValue: number;\n /** Formatted string representation of the current value */\n formattedValue: string;\n /** The effective label to display (computed from userLabel, valueAsLabel, or parameter name) */\n effectiveLabel: string;\n /** The full parameter model instance */\n converter: AudioParameterConverter;\n /**\n * Set the value using a normalized (0..1) input.\n * Automatically denormalizes and calls onChange.\n */\n setNormalizedValue: (normalized: number) => void;\n /**\n * Adjust the value relatively (e.g. from mouse wheel or drag).\n * @param delta The amount to change (in normalized units, typically small like 0.01)\n * @param sensitivity Optional multiplier (default 1.0)\n */\n adjustValue: (delta: number, sensitivity?: number) => void;\n /**\n * Get the default normalized value (0..1).\n * Returns the normalized defaultValue from the parameter if defined,\n * otherwise returns 0.0 for unipolar or 0.5 for bipolar parameters.\n */\n getDefaultNormalizedValue: () => number;\n /**\n * Reset the value to the default value.\n * Uses the parameter's defaultValue if defined, otherwise uses 0.0 for unipolar or 0.5 for bipolar.\n */\n resetToDefault: () => void;\n}\n\n/**\n * Hook to manage audio parameter logic, normalization, and formatting.\n *\n * This is the primary hook for connecting audio parameter models to React components.\n * It handles all value conversions (real ↔ normalized ↔ MIDI) and provides formatted\n * display values. The hook ensures that all conversions go through the MIDI pivot,\n * maintaining consistency with hardware standards.\n *\n * The hook provides:\n * - `normalizedValue`: 0..1 value for UI rendering\n * - `formattedValue`: Formatted string for display\n * - `converter`: The AudioParameterConverter instance for advanced operations\n * - `setNormalizedValue`: Set value from normalized input (e.g., from UI slider)\n * - `adjustValue`: Adjust value relatively (e.g., from mouse wheel or drag)\n *\n * @param value The current real-world value (Source of Truth)\n * @param onChange Callback when value changes. Receives an AudioControlEvent with all value representations.\n * @param parameterDef The parameter definition (AudioParameter)\n * @param userValueFormatter Optional custom renderer for the value display. If provided and returns a value, it takes precedence over the default formatter.\n * @param userLabel Optional custom label. If provided and valueAsLabel is false, takes precedence over parameter name.\n * @param valueAsLabel When true, displays the formatted value as the label instead of the provided label or parameter name.\n * @returns Object containing normalizedValue, formattedValue, effectiveLabel, converter, setNormalizedValue, and adjustValue\n *\n * @example\n * ```tsx\n * // Basic usage\n * const { normalizedValue, formattedValue, adjustValue } = useAudioParameter(\n * volume,\n * (e) => setVolume(e.value),\n * volumeParam\n * );\n *\n * // With custom label and value formatter\n * const { normalizedValue, formattedValue, effectiveLabel, adjustValue } = useAudioParameter(\n * volume,\n * (e) => setVolume(e.value),\n * volumeParam,\n * (val) => `${val.toFixed(1)} dB`, // Custom formatter\n * \"Master Volume\", // Custom label\n * false // Don't use value as label\n * );\n *\n * // Use normalizedValue for rendering\n * <KnobView normalizedValue={normalizedValue} />\n *\n * // Use adjustValue for relative changes\n * <div onWheel={(e) => adjustValue(e.deltaY, 0.001)} />\n *\n * // Use effectiveLabel for display\n * <label>{effectiveLabel}</label>\n * ```\n */\nexport function useAudioParameter<T extends number | boolean | string>(\n value: T,\n onChange: undefined | ((event: AudioControlEvent<T>) => void),\n parameterDef: AudioParameter,\n userValueFormatter?: (value: T, parameterDef: AudioParameter) => string | undefined,\n userLabel?: string,\n valueAsLabel?: boolean\n): UseAudioParameterResult {\n const converter = useMemo(() => {\n return new AudioParameterConverter(parameterDef);\n }, [parameterDef]);\n\n const normalizedValue = useMemo(() => {\n return converter.normalize(value);\n }, [value, converter]);\n\n // Track latest value in ref to avoid stale closures during rapid events (e.g., wheel)\n const valueRef = useRef(value);\n valueRef.current = value;\n\n const commitValue = useCallback(\n (newValue: T) => {\n if (!onChange) return;\n\n const normalized = converter.normalize(newValue);\n const midi = converter.toMidi(newValue);\n\n onChange({\n value: newValue,\n normalizedValue: normalized,\n midiValue: midi,\n parameter: parameterDef,\n });\n },\n [converter, onChange, parameterDef]\n );\n\n const setNormalizedValue = useCallback(\n (newNormal: number) => {\n if (!onChange) return;\n const clamped = Math.max(0, Math.min(1, newNormal));\n const realValue = converter.denormalize(clamped) as T;\n\n if (realValue !== valueRef.current) {\n valueRef.current = realValue;\n commitValue(realValue);\n }\n },\n [converter, onChange, commitValue]\n );\n\n const adjustValue = useCallback(\n (delta: number, sensitivity = 0.001) => {\n if (!onChange) return;\n\n // Use ref for calculation base to prevent stale closure jitter during rapid events\n const currentReal = valueRef.current as number;\n const currentNormal = converter.normalize(currentReal);\n\n const newNormal = Math.max(0, Math.min(1, currentNormal + delta * sensitivity));\n setNormalizedValue(newNormal);\n },\n [converter, onChange, setNormalizedValue]\n );\n\n // Custom valueFormatter takes precedence if provided and returns a value; otherwise fall back to default formatter\n const formattedValue = useMemo(() => {\n if (userValueFormatter) {\n const customValue = userValueFormatter(value, parameterDef);\n if (customValue !== undefined) {\n return customValue;\n }\n }\n return converter.format(value);\n }, [value, converter, userValueFormatter, parameterDef]);\n\n // Compute effective label: valueAsLabel takes precedence, then userLabel, then parameter name\n const effectiveLabel = useMemo(() => {\n if (valueAsLabel) {\n return formattedValue;\n }\n return userLabel ?? parameterDef.name;\n }, [valueAsLabel, formattedValue, userLabel, parameterDef.name]);\n\n // Get default normalized value\n const getDefaultNormalizedValue = useCallback(() => {\n if (parameterDef.type === \"continuous\") {\n if (parameterDef.defaultValue !== undefined) {\n return converter.normalize(parameterDef.defaultValue);\n }\n // If no defaultValue, use 0.0 for unipolar, 0.5 for bipolar\n const isBipolar = parameterDef.bipolar === true;\n return isBipolar ? 0.5 : 0.0;\n }\n // For non-continuous parameters, return 0.0\n return 0.0;\n }, [converter, parameterDef]);\n\n // Reset to default value\n const resetToDefault = useCallback(() => {\n if (!onChange) return;\n const defaultNormalized = getDefaultNormalizedValue();\n setNormalizedValue(defaultNormalized);\n }, [onChange, getDefaultNormalizedValue, setNormalizedValue]);\n\n return {\n normalizedValue,\n formattedValue,\n effectiveLabel,\n converter,\n setNormalizedValue,\n adjustValue,\n getDefaultNormalizedValue,\n resetToDefault,\n };\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React, { useCallback, useRef, useEffect } from \"react\";\nimport { BooleanInteractionController, BooleanInteractionMode } from \"@cutoff/audio-ui-core\";\n\nexport interface UseBooleanInteractionProps {\n /** Current value of the control */\n value: boolean;\n /** Interaction mode: toggle (latch) or momentary */\n mode: BooleanInteractionMode;\n /** Callback to update the value */\n onValueChange: (value: boolean) => void;\n /** Whether the control is disabled */\n disabled?: boolean;\n /** Optional user-provided mouse down handler (composed with hook handler) */\n onMouseDown?: React.MouseEventHandler;\n /** Optional user-provided mouse up handler (composed with hook handler) */\n onMouseUp?: React.MouseEventHandler;\n /** Optional user-provided touch start handler (composed with hook handler) */\n onTouchStart?: React.TouchEventHandler;\n /** Optional user-provided touch end handler (composed with hook handler) */\n onTouchEnd?: React.TouchEventHandler;\n /** Optional user-provided keyboard key down handler (composed with hook handler) */\n onKeyDown?: React.KeyboardEventHandler;\n /** Optional user-provided keyboard key up handler (composed with hook handler) */\n onKeyUp?: React.KeyboardEventHandler;\n}\n\nexport interface UseBooleanInteractionResult {\n /** Handler for mouse down events */\n handleMouseDown: (e: React.MouseEvent) => void;\n /** Handler for mouse up events */\n handleMouseUp: (e: React.MouseEvent) => void;\n /** Handler for mouse enter events (for drag-in behavior) */\n handleMouseEnter: (e: React.MouseEvent) => void;\n /** Handler for mouse leave events (for drag-out behavior) */\n handleMouseLeave: (e: React.MouseEvent) => void;\n /** Handler for touch start events */\n handleTouchStart: (e: React.TouchEvent) => void;\n /** Handler for touch end events */\n handleTouchEnd: (e: React.TouchEvent) => void;\n /** Handler for touch move events (for touch drag-in/drag-out behavior) */\n handleTouchMove: (e: React.TouchEvent) => void;\n /** Ref callback to attach to the button element for touch tracking */\n setButtonElement: (element: HTMLElement | SVGSVGElement | null) => void;\n /** Handler for keyboard key down events (Enter/Space to activate) */\n handleKeyDown: (e: React.KeyboardEvent) => void;\n /** Handler for keyboard key up events (Enter/Space to release momentary buttons) */\n handleKeyUp: (e: React.KeyboardEvent) => void;\n}\n\n/**\n * Hook to manage interactions for boolean controls (buttons, toggles).\n *\n * Provides standardized logic for:\n * - Toggle mode: Click or Space/Enter to flip the value\n * - Momentary mode: Press to activate, release to deactivate (with global pointer tracking)\n * - Drag-in/drag-out behavior: Buttons respond to pointer entering/leaving while pressed, even when press starts outside the button\n * - Keyboard support: Enter/Space for activation/release\n *\n * The hook wraps the framework-agnostic `BooleanInteractionController` and provides React\n * event handlers that can be attached directly to DOM elements. It maintains stable callback\n * references across renders using `useCallback` and updates the controller configuration via\n * `useEffect` when props change.\n *\n * **Drag-In/Drag-Out Behavior:**\n * - **Momentary Mode**: Press inside → turns on; drag out while pressed → turns off; drag back in while pressed → turns on again. Works even when press starts outside the button.\n * - **Toggle Mode**: Press inside → toggles state; drag out while pressed → no change; drag back in while pressed → toggles again. Works even when press starts outside the button.\n *\n * The hook automatically attaches global pointer listeners (mousedown, mouseup, touchstart, touchmove, touchend)\n * to track pointer state globally, enabling drag-in behavior from anywhere on the page. For mouse interactions,\n * it handles mouseenter/mouseleave events to detect when the pointer crosses the button boundary while pressed.\n * For touch interactions, it manually tracks touch position using touchmove events and elementFromPoint to detect\n * when the touch point crosses button boundaries (since touch events don't fire mouseenter/mouseleave).\n *\n * @param {UseBooleanInteractionProps} props - Configuration for the boolean interaction hook\n * @param {boolean} props.value - Current value of the control\n * @param {BooleanInteractionMode} props.mode - Interaction mode: \"toggle\" or \"momentary\"\n * @param {(value: boolean) => void} props.onValueChange - Callback to update the value\n * @param {boolean} [props.disabled=false] - Whether the control is disabled\n * @param {React.MouseEventHandler} [props.onMouseDown] - Optional user-provided mouse down handler\n * @param {React.MouseEventHandler} [props.onMouseUp] - Optional user-provided mouse up handler\n * @param {React.TouchEventHandler} [props.onTouchStart] - Optional user-provided touch start handler\n * @param {React.TouchEventHandler} [props.onTouchEnd] - Optional user-provided touch end handler\n * @param {React.KeyboardEventHandler} [props.onKeyDown] - Optional user-provided keyboard key down handler\n * @param {React.KeyboardEventHandler} [props.onKeyUp] - Optional user-provided keyboard key up handler\n * @returns {UseBooleanInteractionResult} Object containing event handlers including handleMouseEnter and handleMouseLeave for drag-in/drag-out behavior\n *\n * @example\n * ```tsx\n * const { handleMouseDown, handleMouseUp, handleMouseEnter, handleMouseLeave, handleTouchStart, handleTouchEnd, handleKeyDown, handleKeyUp } = useBooleanInteraction({\n * value,\n * mode: \"momentary\",\n * onValueChange: (val) => setValue(val)\n * });\n *\n * <div\n * onMouseDown={handleMouseDown}\n * onMouseUp={handleMouseUp}\n * onMouseEnter={handleMouseEnter}\n * onMouseLeave={handleMouseLeave}\n * onTouchStart={handleTouchStart}\n * onTouchEnd={handleTouchEnd}\n * onKeyDown={handleKeyDown}\n * onKeyUp={handleKeyUp}\n * />\n * ```\n */\nexport function useBooleanInteraction({\n value,\n mode,\n onValueChange,\n disabled = false,\n onMouseDown: userOnMouseDown,\n onMouseUp: userOnMouseUp,\n onTouchStart: userOnTouchStart,\n onTouchEnd: userOnTouchEnd,\n onKeyDown: userOnKeyDown,\n onKeyUp: userOnKeyUp,\n}: UseBooleanInteractionProps): UseBooleanInteractionResult {\n const controllerRef = useRef<BooleanInteractionController | null>(null);\n const buttonElementRef = useRef<HTMLElement | SVGSVGElement | null>(null);\n const touchIsInsideRef = useRef<boolean>(false);\n const isGlobalPointerDownRef = useRef<boolean>(false);\n\n if (!controllerRef.current) {\n controllerRef.current = new BooleanInteractionController({\n value,\n mode,\n onValueChange,\n disabled,\n });\n }\n\n useEffect(() => {\n controllerRef.current?.updateConfig({\n value,\n mode,\n onValueChange,\n disabled,\n });\n }, [value, mode, onValueChange, disabled]);\n\n // Global pointer down handler - tracks when ANY pointer is pressed anywhere\n // This enables drag-in behavior: pressing outside and dragging into the button\n const handleGlobalMouseDown = useCallback((e: MouseEvent) => {\n // Only track primary button (left click)\n if (e.button === 0) {\n isGlobalPointerDownRef.current = true;\n controllerRef.current?.handleGlobalPointerDown(false);\n }\n }, []);\n\n const handleGlobalTouchStart = useCallback((_e: TouchEvent) => {\n isGlobalPointerDownRef.current = true;\n controllerRef.current?.handleGlobalPointerDown(false);\n }, []);\n\n // Global pointer up handler - resets global pointer state and handles release\n const handleGlobalMouseUp = useCallback(() => {\n isGlobalPointerDownRef.current = false;\n controllerRef.current?.handleGlobalPointerUp();\n }, []);\n\n // Global touchend handler - resets global pointer state and handles release\n const handleGlobalTouchEnd = useCallback(() => {\n isGlobalPointerDownRef.current = false;\n touchIsInsideRef.current = false;\n controllerRef.current?.handleGlobalPointerUp();\n }, []);\n\n // Global touchmove handler - tracks touch position to detect enter/leave for touch devices\n // Touch events don't fire mouseenter/mouseleave, so we need to manually track when touch\n // crosses button boundaries\n const handleGlobalTouchMove = useCallback((e: TouchEvent) => {\n if (!controllerRef.current || !buttonElementRef.current) return;\n\n // Only track if global pointer is down\n if (!isGlobalPointerDownRef.current) return;\n\n const controller = controllerRef.current;\n const buttonElement = buttonElementRef.current;\n\n // Get the first touch point\n if (e.touches.length === 0) return;\n const touch = e.touches[0];\n\n // Find element at touch point\n const elementAtPoint = document.elementFromPoint(touch.clientX, touch.clientY);\n\n // Check if touch is over this button (or any child of it)\n const isInside = elementAtPoint === buttonElement || buttonElement.contains(elementAtPoint);\n\n // Only react if state changed\n if (isInside !== touchIsInsideRef.current) {\n touchIsInsideRef.current = isInside;\n\n if (isInside) {\n // Touch entered the button\n controller.handleMouseEnter();\n } else {\n // Touch left the button\n controller.handleMouseLeave();\n }\n }\n }, []);\n\n // Attach global pointer listeners for drag-in/drag-out behavior\n // This enables buttons to respond even when press starts outside the button\n useEffect(() => {\n if (!disabled) {\n window.addEventListener(\"mousedown\", handleGlobalMouseDown);\n window.addEventListener(\"mouseup\", handleGlobalMouseUp);\n window.addEventListener(\"touchstart\", handleGlobalTouchStart, { passive: true });\n window.addEventListener(\"touchmove\", handleGlobalTouchMove, { passive: false });\n window.addEventListener(\"touchend\", handleGlobalTouchEnd);\n return () => {\n window.removeEventListener(\"mousedown\", handleGlobalMouseDown);\n window.removeEventListener(\"mouseup\", handleGlobalMouseUp);\n window.removeEventListener(\"touchstart\", handleGlobalTouchStart);\n window.removeEventListener(\"touchmove\", handleGlobalTouchMove);\n window.removeEventListener(\"touchend\", handleGlobalTouchEnd);\n };\n }\n return undefined;\n }, [\n disabled,\n handleGlobalMouseDown,\n handleGlobalMouseUp,\n handleGlobalTouchStart,\n handleGlobalTouchMove,\n handleGlobalTouchEnd,\n ]);\n\n const handleMouseDown = useCallback(\n (e: React.MouseEvent) => {\n // Call user handler first\n userOnMouseDown?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n // Store button element reference for potential touch tracking\n buttonElementRef.current = e.currentTarget as HTMLElement;\n isGlobalPointerDownRef.current = true;\n controllerRef.current?.handleMouseDown(e.defaultPrevented);\n }\n },\n [userOnMouseDown]\n );\n\n const handleMouseUp = useCallback(\n (e: React.MouseEvent) => {\n // Call user handler first\n userOnMouseUp?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n isGlobalPointerDownRef.current = false;\n controllerRef.current?.handleMouseUp(e.defaultPrevented);\n }\n },\n [userOnMouseUp]\n );\n\n const handleMouseEnter = useCallback((_e: React.MouseEvent) => {\n controllerRef.current?.handleMouseEnter();\n }, []);\n\n const handleMouseLeave = useCallback((_e: React.MouseEvent) => {\n controllerRef.current?.handleMouseLeave();\n }, []);\n\n // Set button element reference (called when component mounts or element changes)\n const setButtonElement = useCallback((element: HTMLElement | SVGSVGElement | null) => {\n buttonElementRef.current = element;\n }, []);\n\n const handleTouchStart = useCallback(\n (e: React.TouchEvent) => {\n // Call user handler first\n userOnTouchStart?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n // Prevent default to avoid mouse event emulation and double-firing\n e.preventDefault();\n // Store button element reference for touch tracking\n buttonElementRef.current = e.currentTarget as HTMLElement;\n touchIsInsideRef.current = true; // Touch started on this button\n controllerRef.current?.handleMouseDown(false);\n }\n },\n [userOnTouchStart]\n );\n\n const handleTouchEnd = useCallback(\n (e: React.TouchEvent) => {\n // Call user handler first\n userOnTouchEnd?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n // Prevent default to avoid mouse event emulation and double-firing\n e.preventDefault();\n controllerRef.current?.handleMouseUp(false);\n touchIsInsideRef.current = false;\n }\n },\n [userOnTouchEnd]\n );\n\n // Touch move handler - prevents default scrolling and allows touch tracking\n const handleTouchMove = useCallback((e: React.TouchEvent) => {\n // Prevent default scrolling during touch drag\n e.preventDefault();\n }, []);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n // Call user handler first\n userOnKeyDown?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n const handled = controllerRef.current?.handleKeyDown(e.key);\n if (handled) {\n e.preventDefault();\n }\n }\n },\n [userOnKeyDown]\n );\n\n const handleKeyUp = useCallback(\n (e: React.KeyboardEvent) => {\n // Call user handler first\n userOnKeyUp?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n const handled = controllerRef.current?.handleKeyUp(e.key);\n if (handled) {\n e.preventDefault();\n }\n }\n },\n [userOnKeyUp]\n );\n\n return {\n handleMouseDown,\n handleMouseUp,\n handleMouseEnter,\n handleMouseLeave,\n handleTouchStart,\n handleTouchEnd,\n handleTouchMove,\n setButtonElement,\n handleKeyDown,\n handleKeyUp,\n };\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { useMemo } from \"react\";\nimport { BooleanParameter, AudioParameterFactory, MidiResolution } from \"@cutoff/audio-ui-core\";\n\nexport interface UseBooleanParameterResolutionProps {\n /** The parameter definition (Strict mode) */\n parameter?: BooleanParameter;\n /** Identifier for the parameter (used in Ad-Hoc mode) */\n paramId?: string;\n /** Label for the parameter (Ad-Hoc mode) */\n label?: string;\n /** Whether the button should latch (toggle between states) or momentary (only active while pressed) (Ad-Hoc mode) */\n latch?: boolean;\n /** MIDI resolution in bits (Ad-Hoc mode)\n * @default 7\n */\n midiResolution?: MidiResolution;\n}\n\nexport interface UseBooleanParameterResolutionResult {\n /** The resolved BooleanParameter (derived from props or created from ad-hoc props) */\n derivedParameter: BooleanParameter;\n}\n\n/**\n * Hook to resolve a BooleanParameter from props.\n *\n * Supports two modes:\n * 1. Strict Mode (Parameter only): Model provided via parameter prop.\n * 2. Ad-Hoc Mode (Props only): Model created from individual props (label, latch, etc.).\n *\n * When `parameter` is provided, it takes precedence over ad-hoc props.\n *\n * @param props - Configuration object for parameter resolution\n * @param props.parameter - The parameter definition (Strict mode). When provided, takes precedence over ad-hoc props.\n * @param props.paramId - Identifier for the parameter (used in Ad-Hoc mode)\n * @param props.label - Label for the parameter (Ad-Hoc mode)\n * @param props.latch - Whether the button should latch (toggle) or be momentary (Ad-Hoc mode)\n * @param props.midiResolution - MIDI resolution in bits (Ad-Hoc mode, default: 7)\n * @returns Object containing the resolved BooleanParameter\n *\n * @example\n * ```tsx\n * // Strict mode: use provided parameter\n * const { derivedParameter } = useBooleanParameterResolution({\n * parameter: powerParam\n * });\n *\n * // Ad-Hoc mode: create from props\n * const { derivedParameter } = useBooleanParameterResolution({\n * paramId: \"power\",\n * label: \"Power\",\n * latch: true\n * });\n * ```\n */\nexport function useBooleanParameterResolution({\n parameter,\n paramId,\n label,\n latch,\n midiResolution = 7,\n}: UseBooleanParameterResolutionProps): UseBooleanParameterResolutionResult {\n return useMemo(() => {\n let derivedParameter: BooleanParameter;\n\n if (parameter) {\n // Strict mode: use provided parameter\n derivedParameter = parameter;\n } else {\n // Ad-hoc mode: create parameter from props\n derivedParameter = AudioParameterFactory.createSwitch(label || \"\", latch ? \"toggle\" : \"momentary\");\n // Override id if paramId is provided (including empty string)\n if (paramId !== undefined) {\n derivedParameter = {\n ...derivedParameter,\n id: paramId,\n };\n }\n // Override midiResolution if provided\n if (midiResolution !== undefined) {\n derivedParameter = {\n ...derivedParameter,\n midiResolution,\n };\n }\n }\n\n return {\n derivedParameter,\n };\n }, [parameter, paramId, label, latch, midiResolution]);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo, useCallback } from \"react\";\nimport classNames from \"classnames\";\nimport { CLASSNAMES } from \"@cutoff/audio-ui-core\";\nimport { AdaptiveBoxProps, AdaptiveBoxLogicalSizeProps, BooleanControlProps, ControlComponent } from \"@/types\";\nimport AdaptiveBox from \"../AdaptiveBox\";\nimport { useAudioParameter } from \"@/hooks/useAudioParameter\";\nimport { useBooleanInteraction } from \"@/hooks/useBooleanInteraction\";\nimport { useBooleanParameterResolution } from \"@/hooks/useBooleanParameterResolution\";\n\nexport type BooleanControlComponentProps<P extends object = Record<string, unknown>> =\n // Base Control Props (includes all BooleanControlProps)\n BooleanControlProps &\n // Layout props that configure AdaptiveBox behavior\n AdaptiveBoxProps &\n // Logical size props that override view component defaults\n AdaptiveBoxLogicalSizeProps & {\n /**\n * The Visualization Component.\n * Must adhere to ControlComponent contract.\n */\n view: ControlComponent<P>;\n\n /**\n * Props specific to the Visualization Component.\n */\n viewProps: P;\n\n /**\n * Content overlay (HTML) rendered over the SVG (e.g. text value, icons).\n * Rendered via AdaptiveBox.HtmlOverlay to avoid foreignObject issues.\n */\n htmlOverlay?: React.ReactNode;\n };\n\n/**\n * A Generic Boolean Control that connects a Data Model (BooleanParameter)\n * to a Visualization View (ControlComponent).\n *\n * This component handles parameter resolution, value management, interaction handling,\n * and layout management for boolean controls (Button, Toggle, etc.).\n *\n * Supports two modes of operation:\n * 1. Strict Mode (Parameter only): Model provided via parameter prop.\n * 2. Ad-Hoc Mode (Props only): Model created from individual props (label, latch, etc.).\n *\n * **Interaction Modes:**\n * - **Editable Control**: When `onChange` is provided, the control is editable and responds to\n * all interaction methods (mouse, touch, keyboard) to change the value. The full interaction\n * system handles complex behaviors like drag-in/drag-out for momentary buttons.\n * - **Clickable View**: When only `onClick` is provided (no `onChange`), the control is a\n * clickable view that triggers the onClick handler but does not change its value. Touch events\n * are handled to ensure onClick works on touch devices.\n * - **Both**: When both `onChange` and `onClick` are provided, the control is editable and\n * also triggers onClick for mouse clicks (onChange handles touch events for value changes).\n *\n * @param props - Component props including parameter configuration, view component, and layout options\n * @returns Rendered boolean control component\n *\n * @example\n * ```tsx\n * // Editable control\n * <BooleanControl\n * value={isOn}\n * onChange={(e) => setIsOn(e.value)}\n * view={ButtonView}\n * viewProps={{ color: \"blue\", roundness: 0.3 }}\n * />\n *\n * // Clickable view (non-editable)\n * <BooleanControl\n * value={false}\n * onClick={() => handleClick()}\n * view={ButtonView}\n * viewProps={{ color: \"gray\" }}\n * />\n * ```\n */\nexport function BooleanControl<P extends object = Record<string, unknown>>(props: BooleanControlComponentProps<P>) {\n const {\n view: View,\n viewProps,\n htmlOverlay,\n value,\n onChange,\n label,\n paramId,\n parameter,\n latch,\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n viewBoxWidthUnits,\n viewBoxHeightUnits,\n labelHeightUnits,\n className,\n style,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n midiResolution,\n } = props;\n\n const { derivedParameter } = useBooleanParameterResolution({\n parameter,\n paramId,\n label,\n latch,\n midiResolution,\n });\n\n const { normalizedValue, converter } = useAudioParameter(value, onChange, derivedParameter);\n\n const fireChange = useCallback(\n (newValue: boolean) => {\n if (!onChange) return;\n const normalized = converter.normalize(newValue);\n const midi = converter.toMidi(newValue);\n onChange({\n value: newValue,\n normalizedValue: normalized,\n midiValue: midi,\n parameter: derivedParameter,\n });\n },\n [onChange, converter, derivedParameter]\n );\n\n const {\n handleMouseDown,\n handleMouseUp,\n handleMouseEnter,\n handleMouseLeave,\n handleTouchStart,\n handleTouchEnd,\n handleTouchMove,\n setButtonElement,\n handleKeyDown,\n handleKeyUp,\n } = useBooleanInteraction({\n value,\n mode: derivedParameter.mode ?? (latch ? \"toggle\" : \"momentary\"),\n onValueChange: fireChange,\n disabled: !onChange,\n onMouseDown,\n onMouseUp,\n onKeyDown: undefined, // BooleanControl doesn't have onKeyDown prop, only uses hook handler\n onKeyUp: undefined, // BooleanControl doesn't have onKeyUp prop, only uses hook handler\n });\n\n // Handle onClick for touch events when onChange is not provided\n // This ensures onClick works on touch devices even when the control is not editable\n // When onChange is provided, the interaction system handles touch events and we don't need this\n const handleTouchEndForClick = useCallback(\n (e: React.TouchEvent<SVGSVGElement>) => {\n if (!onChange && onClick) {\n // Prevent default to avoid mouse event emulation\n e.preventDefault();\n // Create a synthetic mouse event for onClick\n const touch = e.changedTouches[0];\n if (touch) {\n const syntheticEvent = {\n ...e,\n clientX: touch.clientX,\n clientY: touch.clientY,\n currentTarget: e.currentTarget,\n type: \"click\",\n } as unknown as React.MouseEvent<SVGSVGElement>;\n onClick(syntheticEvent);\n }\n }\n },\n [onChange, onClick]\n );\n\n const handleTouchStartForClick = useCallback(\n (e: React.TouchEvent<SVGSVGElement>) => {\n if (!onChange && onClick) {\n // Prevent default to avoid mouse event emulation\n e.preventDefault();\n }\n },\n [onChange, onClick]\n );\n\n const effectiveLabel = label ?? derivedParameter.name;\n\n const componentClassNames = useMemo(() => {\n return classNames(className, CLASSNAMES.root, CLASSNAMES.container);\n }, [className]);\n\n const svgClassNames = useMemo(() => {\n return onChange || onClick ? CLASSNAMES.highlight : \"\";\n }, [onChange, onClick]);\n\n // Add clickable cursor when interactive (onChange or onClick)\n // Uses CSS variable for customizable cursor type\n // Add touchAction: \"none\" to prevent default touch behaviors (scrolling/zooming)\n const svgStyle = useMemo(\n () => ({\n ...(onClick || onChange ? { cursor: \"var(--audioui-cursor-clickable)\" as const } : {}),\n touchAction: \"none\" as const,\n }),\n [onClick, onChange]\n );\n\n // Wrap handlers to set element reference\n const handleMouseDownWithRef = useCallback(\n (e: React.MouseEvent<SVGSVGElement>) => {\n setButtonElement(e.currentTarget);\n handleMouseDown(e);\n },\n [setButtonElement, handleMouseDown]\n );\n\n const handleTouchStartWithRef = useCallback(\n (e: React.TouchEvent<SVGSVGElement>) => {\n setButtonElement(e.currentTarget);\n handleTouchStart(e);\n },\n [setButtonElement, handleTouchStart]\n );\n\n return (\n <AdaptiveBox\n displayMode={displayMode ?? \"scaleToFit\"}\n labelMode={labelMode}\n labelOverflow={labelOverflow}\n className={componentClassNames}\n style={style}\n labelHeightUnits={labelHeightUnits ?? View.labelHeightUnits ?? 20}\n viewBoxWidth={viewBoxWidthUnits ?? View.viewBox.width}\n viewBoxHeight={viewBoxHeightUnits ?? View.viewBox.height}\n >\n <AdaptiveBox.Svg\n className={svgClassNames}\n style={svgStyle}\n onClick={onClick}\n onMouseDown={onChange ? handleMouseDownWithRef : undefined}\n onMouseUp={onChange ? handleMouseUp : undefined}\n onMouseEnter={(e) => {\n if (onChange) {\n handleMouseEnter(e);\n }\n onMouseEnter?.(e);\n }}\n onMouseLeave={(e) => {\n if (onChange) {\n handleMouseLeave(e);\n }\n onMouseLeave?.(e);\n }}\n onTouchStart={onChange ? handleTouchStartWithRef : onClick ? handleTouchStartForClick : undefined}\n onTouchEnd={onChange ? handleTouchEnd : onClick ? handleTouchEndForClick : undefined}\n onTouchMove={onChange ? handleTouchMove : undefined}\n onKeyDown={onChange ? handleKeyDown : undefined}\n onKeyUp={onChange ? handleKeyUp : undefined}\n tabIndex={onChange || onClick ? 0 : undefined}\n role=\"button\"\n aria-pressed={value}\n aria-label={effectiveLabel}\n >\n <View normalizedValue={normalizedValue} {...viewProps} />\n </AdaptiveBox.Svg>\n {htmlOverlay && <AdaptiveBox.HtmlOverlay>{htmlOverlay}</AdaptiveBox.HtmlOverlay>}\n {effectiveLabel && (\n <AdaptiveBox.Label position={labelPosition} align={labelAlign ?? \"center\"}>\n {effectiveLabel}\n </AdaptiveBox.Label>\n )}\n </AdaptiveBox>\n );\n}\n\nexport default React.memo(BooleanControl) as typeof BooleanControl;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { useMemo } from \"react\";\nimport { getSizeClassForComponent, getSizeStyleForComponent, SizeType } from \"@cutoff/audio-ui-core\";\n\n/**\n * Hook that encapsulates adaptive sizing logic for components implementing AdaptiveSizeProps.\n *\n * Determines sizing behavior based on `adaptiveSize` and `size` props:\n * - When `adaptiveSize` is true, the component stretches to fill its container (no size constraints)\n * - When `adaptiveSize` is false, size classes and styles are applied based on the `size` prop\n *\n * @param adaptiveSize - Whether the component should stretch to fill its container\n * @param size - The size value (ignored when adaptiveSize is true)\n * @param componentType - The type of component ('knob', 'button', 'keys', or 'slider')\n * @param orientation - Optional orientation for slider components ('vertical' or 'horizontal')\n * @returns Object containing `sizeClassName` and `sizeStyle`\n *\n * @example\n * ```tsx\n * const { sizeClassName, sizeStyle } = useAdaptiveSize(\n * adaptiveSize,\n * size,\n * \"knob\"\n * );\n * ```\n */\nexport function useAdaptiveSize(\n adaptiveSize: boolean = false,\n size: SizeType = \"normal\",\n componentType: \"knob\" | \"button\" | \"keys\" | \"slider\",\n orientation?: \"vertical\" | \"horizontal\"\n) {\n return useMemo(() => {\n // When adaptiveSize is true, component stretches to fill container (no size constraints)\n // When false, apply size classes and styles based on the size prop\n const sizeClassName = adaptiveSize\n ? undefined\n : orientation !== undefined\n ? getSizeClassForComponent(componentType, size, orientation)\n : getSizeClassForComponent(componentType, size);\n\n const sizeStyle = adaptiveSize\n ? undefined\n : orientation !== undefined\n ? getSizeStyleForComponent(componentType, size, orientation)\n : getSizeStyleForComponent(componentType, size);\n\n return {\n sizeClassName,\n sizeStyle,\n };\n }, [adaptiveSize, size, componentType, orientation]);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React, { useMemo } from \"react\";\nimport { clampNormalized, generateTransparencyVariant } from \"@cutoff/audio-ui-core\";\n\n/**\n * Options for useThemableProps hook\n */\nexport interface UseThemablePropsOptions {\n /** Component primary color - any valid CSS color value */\n color?: string;\n /** Roundness for component corners/caps (normalized 0.0-1.0) */\n roundness?: number;\n /** User-provided style object (takes precedence over generated CSS variables) */\n style?: React.CSSProperties;\n}\n\n/**\n * Return value from useThemableProps hook\n */\nexport interface UseThemablePropsResult {\n /** Merged style object with CSS variables and user styles */\n style: React.CSSProperties;\n /** Clamped roundness value (undefined if not provided) */\n clampedRoundness?: number;\n}\n\n/**\n * Hook that encapsulates CSS variable computation for themable components.\n *\n * This hook handles:\n * - Clamping normalized roundness values to valid ranges\n * - Generating CSS variables from themable props (color, roundness)\n * - Automatically inferring linecap from roundness (0.0 = square, >0.0 = round)\n * - Computing color variants (primary-50, primary-20) when color is provided\n * - Merging with user-provided style (user style takes precedence)\n *\n * **Roundness and Linecap Relationship:**\n * The roundness attribute serves as a single source of truth for the overall \"feeling\" of the UI.\n * For arc-based components (like knobs), the roundness value automatically determines the stroke-linecap:\n * - `roundness = 0.0` → `stroke-linecap: square` (sharp, technical look)\n * - `roundness > 0.0` → `stroke-linecap: round` (smooth, rounded look)\n *\n * This hook sets both `--audioui-roundness-base` and `--audioui-linecap-base` CSS variables\n * when a roundness prop is provided, ensuring consistent visual styling across all components.\n *\n * The hook returns a style object that can be spread directly onto component elements,\n * along with clamped values that can be used for viewProps.\n * CSS variables are set on the element, allowing child components to read them via `var(--audioui-*)`.\n *\n * @param options - Configuration object with color, roundness, and style\n * @returns Object containing style and clamped roundness value\n *\n * @example\n * ```tsx\n * function MyComponent({ color, roundness, style }: ThemableProps & BaseProps) {\n * const { style: themableStyle } = useThemableProps({\n * color,\n * roundness,\n * style,\n * });\n *\n * return (\n * <div style={themableStyle}>\n * {/* Child components can read CSS variables via var(--audioui-primary-color) *\\/}\n * <ViewComponent roundness={roundness ?? \"var(--audioui-roundness-button)\"} />\n * </div>\n * );\n * }\n * ```\n */\nexport function useThemableProps({ color, roundness, style }: UseThemablePropsOptions): UseThemablePropsResult {\n return useMemo(() => {\n const vars: Record<string, string> = {};\n let clampedRoundness: number | undefined;\n\n // Handle roundness - clamp and store for return\n // Automatically infer linecap from roundness: this ensures arc-based components\n // (like knobs) have consistent visual styling. The linecap is a derived property\n // that should not be set independently - it's always inferred from roundness.\n if (roundness !== undefined) {\n clampedRoundness = clampNormalized(roundness);\n vars[\"--audioui-roundness-base\"] = clampedRoundness.toString();\n // Automatically infer linecap: 0.0 = square (sharp), >0.0 = round (smooth)\n vars[\"--audioui-linecap-base\"] = clampedRoundness === 0 ? \"square\" : \"round\";\n }\n\n // Handle color and generate variants\n if (color !== undefined) {\n vars[\"--audioui-primary-color\"] = color;\n // Compute variants for this component instance\n vars[\"--audioui-primary-50\"] = generateTransparencyVariant(color, 50);\n vars[\"--audioui-primary-20\"] = generateTransparencyVariant(color, 20);\n }\n\n // Merge CSS variables with user style (user style takes precedence)\n return {\n style: { ...vars, ...style } as React.CSSProperties,\n clampedRoundness,\n };\n }, [color, roundness, style]);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, BooleanControlProps, ThemableProps } from \"@/types\";\nimport ButtonView from \"./ButtonView\";\nimport BooleanControl from \"@/primitives/controls/BooleanControl\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { useThemableProps } from \"@/hooks/useThemableProps\";\n\n/**\n * Props for the Button component (built-in control with theming support)\n */\nexport type ButtonProps = BooleanControlProps & AdaptiveSizeProps & AdaptiveBoxProps & ThemableProps;\n\n/**\n * A button component for audio applications.\n *\n * Supports both toggle (latch) and momentary modes, with proper handling of\n * global mouse events for momentary buttons to ensure reliable release behavior\n * even when the mouse is dragged outside the button before release.\n *\n * Supports two modes of operation:\n * 1. Strict Mode (Parameter only): Model provided via parameter prop.\n * 2. Ad-Hoc Mode (Props only): Model created from individual props (label, latch, etc.).\n *\n * @param props - Component props\n * @returns Rendered Button component\n *\n * @example\n * ```tsx\n * // Ad-Hoc Mode - Toggle button\n * <Button\n * label=\"Power\"\n * latch={true}\n * value={isOn}\n * onChange={(e) => setIsOn(e.value)}\n * />\n *\n * // Ad-Hoc Mode - Momentary button\n * <Button\n * label=\"Record\"\n * latch={false}\n * value={isRecording}\n * onChange={(e) => setIsRecording(e.value)}\n * />\n *\n * // Strict Mode with parameter\n * <Button\n * parameter={powerParam}\n * value={isOn}\n * onChange={(e) => setIsOn(e.value)}\n * />\n * ```\n */\nfunction Button({\n latch = false,\n value = false,\n onChange,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n labelHeightUnits,\n parameter,\n paramId,\n midiResolution,\n color,\n roundness,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: ButtonProps) {\n const { style: themableStyle } = useThemableProps({\n color,\n roundness,\n style,\n });\n const { sizeClassName, sizeStyle: adaptiveSizeStyle } = useAdaptiveSize(adaptiveSize, size, \"button\");\n\n return (\n <BooleanControl\n value={value}\n onChange={onChange}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n labelOverflow={labelOverflow}\n labelHeightUnits={labelHeightUnits}\n className={classNames(sizeClassName, className)}\n style={{ ...adaptiveSizeStyle, ...themableStyle }}\n parameter={parameter}\n paramId={paramId}\n latch={latch}\n midiResolution={midiResolution}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n view={ButtonView}\n viewProps={{\n threshold: 0.5,\n roundness: roundness ?? \"var(--audioui-roundness-button)\",\n color: color ?? \"var(--audioui-primary-color)\",\n }}\n />\n );\n}\n\nexport default React.memo(Button);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { useMemo } from \"react\";\nimport { calculateArcAngles, ArcAngleResult } from \"@cutoff/audio-ui-core\";\n\nexport type UseArcAngleResult = ArcAngleResult;\n\n/**\n * Hook to calculate arc angles for rotary controls (ValueRing, RotaryImage components, or any other circular control).\n *\n * Calculates the angular range based on openness and converts a normalized value (0-1)\n * to an angle within that range.\n *\n * The angle system:\n * - 0 degrees is at 3 o'clock, increasing clockwise\n * - Standard knob (90° openness) goes from ~225° (7:30) to ~495° (4:30)\n * - 360° corresponds to UP (12 o'clock)\n *\n * @param normalizedValue Normalized value between 0 and 1\n * @param openness Openness of the arc in degrees (0-360, default 90)\n * @param rotation Rotation angle offset in degrees (default 0)\n * @param bipolar Whether to start the value arc from the center (12 o'clock) instead of the start angle (default false)\n * @param positions Optional number of discrete positions. When defined, the value will snap to the nearest position. Defaults to undefined (continuous mode).\n * @returns Calculated angles and normalized values\n */\nexport function useArcAngle(\n normalizedValue: number,\n openness: number = 90,\n rotation: number = 0,\n bipolar: boolean = false,\n positions?: number\n): UseArcAngleResult {\n return useMemo(() => {\n return calculateArcAngles(normalizedValue, openness, rotation, bipolar, positions);\n }, [normalizedValue, openness, rotation, bipolar, positions]);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, useMemo } from \"react\";\nimport { calculateArcPath } from \"@cutoff/audio-ui-core\";\n\nexport type RingArcProps = {\n startAngle: number;\n endAngle: number;\n style: CSSProperties | undefined;\n cx: number;\n cy: number;\n radius: number;\n thickness: number;\n strokeLinecap: \"round\" | \"square\" | \"butt\" | string;\n};\n\n/**\n * Helper component to render either a circle or path based on whether it's a full circle.\n *\n * @internal\n */\nfunction RingArc({ startAngle, endAngle, style, cx, cy, radius, thickness, strokeLinecap }: RingArcProps) {\n // Determine if it's a full circle based on angular difference (>= 360 degrees)\n const isFullCircle = Math.abs(endAngle - startAngle) >= 360;\n\n const path = useMemo(() => {\n if (isFullCircle) return undefined;\n\n // Use default \"counter-clockwise\" (End -> Start) for standard static shapes.\n return calculateArcPath(cx, cy, startAngle, endAngle, radius, \"counter-clockwise\");\n }, [isFullCircle, cx, cy, startAngle, endAngle, radius]);\n\n if (isFullCircle) {\n return (\n <circle\n cx={cx}\n cy={cy}\n r={radius}\n fill=\"none\"\n strokeWidth={thickness}\n style={{\n ...style,\n // @ts-expect-error - strokeLinecap accepts CSS variables (strings) but React types are strict\n strokeLinecap: strokeLinecap,\n }}\n />\n );\n }\n\n if (!path) return null;\n\n return (\n <path\n d={path}\n fill=\"none\"\n strokeWidth={thickness}\n style={{\n ...style,\n // @ts-expect-error - strokeLinecap accepts CSS variables (strings) but React types are strict\n strokeLinecap: strokeLinecap,\n }}\n />\n );\n}\n\nexport default React.memo(RingArc);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, useMemo } from \"react\";\nimport { useArcAngle } from \"@/hooks/useArcAngle\";\nimport RingArc from \"./RingArc\";\n\nexport type ValueRingProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Radius of the ring */\n radius: number;\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Whether to start the arc from center (bipolar mode) */\n bipolar?: boolean;\n /** Thickness of the knob's stroke (1-20 pixels) */\n thickness?: number;\n /** Roundness for stroke linecap (false = square, true = round, or CSS variable string) */\n roundness?: boolean | string;\n /** Openness of the ring in degrees (value between 0-360º; 0º: closed; 90º: 3/4 open; 180º: half-circle;) */\n openness?: number;\n /** Optional rotation angle offset in degrees (default 0) */\n rotation?: number;\n /** Optional number of discrete positions. When defined, the value will snap to the nearest position. */\n positions?: number;\n /** Optional style overrides for the foreground (value) arc */\n fgArcStyle?: CSSProperties;\n /** Optional style overrides for the background arc */\n bgArcStyle?: CSSProperties;\n};\n\n/**\n * A reusable SVG fragment that renders a ring (arc) used in circular controls like knobs.\n * It handles the calculation of SVG paths for both the background track and the foreground value indicator.\n *\n * This component is designed to be used inside an <svg> element.\n */\nfunction ValueRing({\n cx,\n cy,\n radius,\n normalizedValue,\n bipolar = false,\n thickness = 6,\n roundness,\n openness = 90,\n rotation = 0,\n positions,\n fgArcStyle,\n bgArcStyle,\n}: ValueRingProps) {\n // Calculate arc angles using shared hook (rotation computation factored into hook)\n const { startAngle, endAngle, valueToAngle, valueStartAngle } = useArcAngle(\n normalizedValue,\n openness,\n rotation,\n bipolar,\n positions\n );\n\n const strokeLinecap = useMemo(() => {\n if (typeof roundness === \"string\") {\n return roundness;\n }\n return roundness ? \"round\" : \"square\";\n }, [roundness]);\n\n // Calculate actual radius to make stroke expand inward from the outer edge\n // SVG strokes are centered on the path by default, so a stroke of thickness N\n // extends N/2 pixels on each side of the path. To make the stroke grow inward\n // (keeping the outer edge at the specified radius), we subtract half the thickness.\n // This ensures the visual appearance matches the design intent: outer edge stays fixed,\n // inner edge moves inward as thickness increases.\n const actualRadius = useMemo(() => {\n return Math.max(0, radius - thickness / 2);\n }, [radius, thickness]);\n\n // Check if we can use the optimized RevealingPath\n // We only use RevealingPath for unipolar, non-full-circle arcs\n // (Full circles require complex path construction to work with stroke-dasharray)\n // PERFORMANCE NOTE: We explicitly AVOID the RevealingPath optimization (stroke-dasharray)\n // here because in high-concurrency scenarios (hundreds of knobs), the React overhead\n // of the extra component layer and DOM attribute updates proved slower than\n // simply recalculating the geometric path in JS.\n\n return (\n <>\n {/* Background Arc - Always the same regardless of optimization */}\n <RingArc\n startAngle={startAngle}\n endAngle={endAngle}\n style={bgArcStyle}\n cx={cx}\n cy={cy}\n radius={actualRadius}\n thickness={thickness}\n strokeLinecap={strokeLinecap}\n />\n\n {/* Foreground Arc */}\n <RingArc\n startAngle={valueStartAngle}\n endAngle={valueToAngle}\n style={fgArcStyle}\n cx={cx}\n cy={cy}\n radius={actualRadius}\n thickness={thickness}\n strokeLinecap={strokeLinecap}\n />\n </>\n );\n}\n\nexport default React.memo(ValueRing);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties } from \"react\";\n\nexport type RadialImageProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Radius the content should fit within */\n radius: number;\n /** Optional image URL to display */\n imageHref?: string;\n /** Optional SVG content to display (e.g., icon components) */\n children?: React.ReactNode;\n /** Optional SVG transform attribute */\n transform?: string;\n /** Additional CSS class name */\n className?: string;\n /**\n * Inline styles.\n * Supports `color` property for icon theming - icons using currentColor will inherit this value.\n */\n style?: CSSProperties;\n};\n\n/**\n * A primitive component that displays static content at radial coordinates.\n * The content is sized to fit within the specified radius and centered at (cx, cy).\n *\n * This component can display an image (via imageHref) or arbitrary SVG content (via children).\n * It is designed to work alongside ValueRing and RotaryImage components in composing knobs.\n *\n * Useful for displaying icons or static images within knob compositions.\n */\nfunction RadialImage({ cx, cy, radius, imageHref, children, transform, className, style }: RadialImageProps) {\n return (\n <g className={className} style={style} transform={transform}>\n {imageHref && (\n <image\n href={imageHref}\n x={cx - radius}\n y={cy - radius}\n width={radius * 2}\n height={radius * 2}\n preserveAspectRatio=\"xMidYMid meet\"\n />\n )}\n {children && (\n <svg\n x={cx - radius}\n y={cy - radius}\n width={radius * 2}\n height={radius * 2}\n viewBox={`0 0 ${radius * 2} ${radius * 2}`}\n style={{ overflow: \"visible\", ...(style?.color ? { color: style.color } : {}) }}\n >\n {children}\n </svg>\n )}\n </g>\n );\n}\n\nexport default React.memo(RadialImage);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties } from \"react\";\nimport { useArcAngle } from \"@/hooks/useArcAngle\";\nimport RadialImage from \"./RadialImage\";\n\nexport type RotaryImageProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Radius of the rotary control (used for bounds/image sizing) */\n radius: number;\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Whether to start the arc from center (bipolar mode) */\n bipolar?: boolean;\n /** Openness of the arc in degrees (default 90) */\n openness?: number;\n /** Optional image URL to display */\n imageHref?: string;\n /** Optional SVG content to rotate */\n children?: React.ReactNode;\n /** Optional rotation angle offset in degrees (default 0) */\n rotation?: number;\n /** Optional number of discrete positions. When defined, the value will snap to the nearest position. */\n positions?: number;\n /** Optional X coordinate for the center of rotation (defaults to cx) */\n pivotX?: number;\n /** Optional Y coordinate for the center of rotation (defaults to cy) */\n pivotY?: number;\n /** Additional CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n};\n\n/**\n * A primitive component that rotates its content based on a normalized value.\n * Designed to work with the same angle logic as ValueRing.tsx.\n *\n * This component wraps RadialImage and applies rotation based on the normalized value.\n * It can display an image (via imageHref) or arbitrary SVG content (via children).\n */\nfunction RotaryImage({\n cx,\n cy,\n radius,\n normalizedValue,\n bipolar = false,\n openness = 90,\n imageHref,\n children,\n rotation = 0,\n positions,\n pivotX,\n pivotY,\n className,\n style,\n}: RotaryImageProps) {\n // Calculate arc angles using shared hook (rotation computation factored into hook)\n const { valueToAngle } = useArcAngle(normalizedValue, openness, rotation, bipolar, positions);\n\n // Use explicit pivot point if provided, otherwise default to center (cx, cy)\n const rotateX = pivotX ?? cx;\n const rotateY = pivotY ?? cy;\n\n return (\n <RadialImage\n cx={cx}\n cy={cy}\n radius={radius}\n imageHref={imageHref}\n transform={`rotate(${valueToAngle}, ${rotateX}, ${rotateY})`}\n className={className}\n style={{ ...style, willChange: \"transform\" }}\n >\n {children}\n </RadialImage>\n );\n}\n\nexport default React.memo(RotaryImage);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport { ControlComponent, KnobVariant } from \"@/types\";\nimport { translateKnobRoundness, translateKnobThickness } from \"@cutoff/audio-ui-core\";\nimport { DEFAULT_ROUNDNESS } from \"@cutoff/audio-ui-core\";\nimport ValueRing from \"@/primitives/svg/ValueRing\";\nimport RotaryImage from \"@/primitives/svg/RotaryImage\";\nimport RadialImage from \"@/primitives/svg/RadialImage\";\n\n/**\n * Props for the KnobView component\n */\nexport type KnobViewProps = {\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Whether to start the arc from center (bipolar mode) */\n bipolar?: boolean;\n /** Visual variant of the knob */\n variant?: KnobVariant;\n /** Thickness of the knob's stroke (normalized 0.0-1.0, maps to 1-20) */\n thickness?: number;\n /** Roundness for stroke linecap (normalized 0.0-1.0, 0.0 = square, >0.0 = round, or CSS variable string) */\n roundness?: number | string;\n /** Openness of the ring in degrees (value between 0-360º; 0º: closed; 90º: 3/4 open; 180º: half-circle;) */\n openness?: number;\n /** Optional rotation angle offset in degrees */\n rotation?: number;\n /** Resolved color string */\n color?: string;\n /** Additional CSS class name */\n className?: string;\n /**\n * Whether to use RotaryImage (true) or RadialImage (false) for iconCap overlay.\n * When true, the icon rotates with the knob value; when false, the icon remains static.\n * Only applies when variant is \"iconCap\" and svgOverlay is provided.\n * @default false\n */\n svgOverlayRotary?: boolean;\n /**\n * SVG content to display as overlay in iconCap variant.\n * Typically an icon component (e.g., wave icons) that will be rendered at the center of the knob.\n * The icon inherits color via currentColor, so it will adapt to light/dark mode automatically.\n * Only used when variant is \"iconCap\".\n */\n svgOverlay?: React.ReactNode;\n};\n\n/**\n * Pure SVG presentation component for a knob.\n * Renders background and foreground arcs as the visual indicator.\n *\n * Center content (text, icons, images) is rendered via the `overlay` prop\n * on AdaptiveBox.Svg, which places HTML content OUTSIDE the SVG to avoid\n * Safari's foreignObject rendering bugs with container queries.\n *\n * @param normalizedValue - Value between 0 and 1\n * @param bipolar - Whether to start arc from center (default false)\n * @param variant - Visual variant of the knob (default \"abstract\")\n * @param thickness - Normalized thickness 0.0-1.0 (default: 0.4 for abstract/simplest, 0.2 for others; maps to 1-20)\n * @param roundness - Normalized roundness 0.0-1.0 (default 0.3, 0.0 = square, >0.0 = round)\n * @param openness - Openness of the ring in degrees (default 90)\n * @param rotation - Optional rotation angle offset in degrees (default 0)\n * @param color - Resolved color string\n * @param className - Optional CSS class\n * @param svgOverlayRotary - Whether to use RotaryImage (true) or RadialImage (false) for iconCap overlay (default false)\n * @param svgOverlay - SVG content to display as overlay in iconCap variant (typically an icon component)\n */\nfunction KnobView({\n normalizedValue,\n bipolar = false,\n variant = \"abstract\",\n thickness,\n roundness,\n openness = 90,\n rotation = 0,\n color: _color, // Prefixed with _ to indicate intentionally unused (kept for API compatibility)\n className,\n svgOverlayRotary = false,\n svgOverlay,\n}: KnobViewProps) {\n // Determine default thickness based on variant\n // abstract and simplest: 0.4 (8 units), others: 0.2 (4 units)\n const defaultThickness = variant === \"abstract\" || variant === \"simplest\" ? 0.4 : 0.2;\n const effectiveThickness = thickness ?? defaultThickness;\n\n // Translate normalized thickness to pixel range (1-20)\n const pixelThickness = useMemo(() => {\n return translateKnobThickness(effectiveThickness);\n }, [effectiveThickness]);\n\n // Translate normalized roundness to stroke-linecap value\n // When roundness is a CSS variable (from theme), use the corresponding linecap variable\n // which is automatically managed by the theme system based on the roundness value.\n // When roundness is a number, infer linecap: 0.0 = square, >0.0 = round\n const strokeLinecap = useMemo(() => {\n if (typeof roundness === \"string\") {\n // If it's a CSS variable for roundness, use the corresponding linecap variable\n // The linecap variable is automatically set by useThemableProps or setThemeRoundness\n // based on whether roundness is 0.0 (square) or >0.0 (round)\n if (roundness === \"var(--audioui-roundness-knob)\") {\n return \"var(--audioui-linecap-knob)\";\n }\n return \"round\"; // Fallback for other CSS variable strings\n }\n // For numeric roundness, infer linecap: 0 = square, >0 = round\n return translateKnobRoundness(roundness ?? DEFAULT_ROUNDNESS) !== 0 ? \"round\" : \"square\";\n }, [roundness]);\n\n // Reusable ValueRing element for value indication\n // Note: Not memoized since normalizedValue changes frequently during interactions\n // Use CSS variables for colors - CSS handles variant generation via color-mix\n const valueRing = (\n <ValueRing\n cx={50}\n cy={50}\n radius={50}\n normalizedValue={normalizedValue}\n bipolar={bipolar}\n thickness={pixelThickness}\n roundness={strokeLinecap}\n openness={openness}\n rotation={rotation}\n fgArcStyle={{ stroke: \"var(--audioui-primary-color)\" }}\n bgArcStyle={{ stroke: \"var(--audioui-primary-50)\" }}\n />\n );\n\n // Render variant-specific content\n const finalLinecap = typeof strokeLinecap === \"string\" ? strokeLinecap : (strokeLinecap as \"round\" | \"square\");\n switch (variant) {\n case \"plainCap\":\n return (\n <g className={className}>\n {valueRing}\n\n <RotaryImage\n cx={50}\n cy={50}\n radius={50 - pixelThickness - 6}\n normalizedValue={normalizedValue}\n openness={openness}\n rotation={rotation}\n >\n <circle cx=\"50%\" cy=\"50%\" r=\"50%\" fill=\"var(--audioui-knob-cap-fill, #4a4d50)\" />\n <line\n x1=\"50%\"\n y1=\"15%\"\n x2=\"50%\"\n y2=\"5%\"\n stroke=\"var(--audioui-nearwhite)\"\n strokeWidth={(50 - pixelThickness - 6) * 0.1}\n // @ts-expect-error - strokeLinecap accepts CSS variables (strings) but React types are strict\n strokeLinecap={finalLinecap}\n />\n </RotaryImage>\n </g>\n );\n\n case \"iconCap\": {\n // iconCap inherits from plainCap and adds an overlay\n const iconRadius = (50 - pixelThickness - 6) * 0.35;\n // Get the adaptive color for the icon (icons use currentColor)\n const overlayContent = svgOverlay ? (\n svgOverlayRotary ? (\n <RotaryImage\n cx={50}\n cy={50}\n radius={iconRadius}\n normalizedValue={normalizedValue}\n openness={openness}\n rotation={rotation}\n style={{ color: \"var(--audioui-nearwhite)\" }}\n >\n {svgOverlay}\n </RotaryImage>\n ) : (\n <RadialImage cx={50} cy={50} radius={iconRadius} style={{ color: \"var(--audioui-nearwhite)\" }}>\n {svgOverlay}\n </RadialImage>\n )\n ) : null;\n\n return (\n <g className={className}>\n {valueRing}\n {/* PlainCap content */}\n <RotaryImage\n cx={50}\n cy={50}\n radius={50 - pixelThickness - 6}\n normalizedValue={normalizedValue}\n openness={openness}\n rotation={rotation}\n >\n <circle cx=\"50%\" cy=\"50%\" r=\"50%\" fill=\"var(--audioui-knob-cap-fill, #4a4d50)\" />\n <line\n x1=\"50%\"\n y1=\"15%\"\n x2=\"50%\"\n y2=\"5%\"\n stroke=\"var(--audioui-nearwhite)\"\n strokeWidth={(50 - pixelThickness - 6) * 0.1}\n // @ts-expect-error - strokeLinecap accepts CSS variables (strings) but React types are strict\n strokeLinecap={finalLinecap}\n />\n </RotaryImage>\n {/* Icon overlay */}\n {overlayContent}\n </g>\n );\n }\n\n case \"abstract\":\n case \"simplest\":\n default:\n // Default variant and other variants use ValueRing only\n return <g className={className}>{valueRing}</g>;\n }\n}\n\n// Create memoized component\nconst MemoKnobView = React.memo(KnobView);\n\n// Explicitly attach static properties to the memoized component\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(MemoKnobView as any).viewBox = { width: 100, height: 100 };\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(MemoKnobView as any).labelHeightUnits = 20;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(MemoKnobView as any).interaction = { mode: \"both\", direction: \"circular\" } as const;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(MemoKnobView as any).title = \"Knob\";\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(MemoKnobView as any).description = \"A rotary knob control with circular arc indicator\";\n\nexport default MemoKnobView as unknown as ControlComponent<\n Omit<KnobViewProps, \"normalizedValue\" | \"className\" | \"style\">\n>;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React, { useEffect, useMemo, useRef } from \"react\";\nimport { ContinuousInteractionController, ContinuousInteractionConfig } from \"@cutoff/audio-ui-core\";\nimport { DEFAULT_CONTINUOUS_SENSITIVITY, DEFAULT_KEYBOARD_STEP, TARGET_PIXELS_PER_STEP } from \"@cutoff/audio-ui-core\";\n\nexport type UseContinuousInteractionProps = Omit<ContinuousInteractionConfig, \"adjustValue\"> & {\n adjustValue: (delta: number, sensitivity?: number) => void;\n editable?: boolean;\n /** Minimum value (real domain). Used to calculate normalized step if step is not provided. */\n min?: number;\n /** Maximum value (real domain). Used to calculate normalized step if step is not provided. */\n max?: number;\n /** Step size (real domain). Used to calculate normalized step if step is not provided. */\n paramStep?: number;\n /** Function to reset the value to its default (called on double-click) */\n resetToDefault?: () => void;\n /** Optional user-provided mouse down handler (composed with hook handler) */\n onMouseDown?: React.MouseEventHandler;\n /** Optional user-provided touch start handler (composed with hook handler) */\n onTouchStart?: React.TouchEventHandler;\n /** Optional user-provided wheel handler (composed with hook handler) */\n onWheel?: React.WheelEventHandler;\n /** Optional user-provided keyboard key down handler (composed with hook handler) */\n onKeyDown?: React.KeyboardEventHandler;\n /** Optional user-provided double click handler (composed with hook handler) */\n onDoubleClick?: React.MouseEventHandler;\n};\n\nexport interface ContinuousInteractionHandlers {\n onMouseDown: React.MouseEventHandler;\n onTouchStart: React.TouchEventHandler;\n onWheel: React.WheelEventHandler;\n onKeyDown: React.KeyboardEventHandler;\n onDoubleClick: React.MouseEventHandler;\n tabIndex: number;\n role: string;\n \"aria-disabled\"?: boolean;\n style?: React.CSSProperties;\n}\n\n/**\n * Hook to standardize user interaction for continuous controls (Knob, Slider).\n *\n * This hook provides a unified interface for handling all user input methods:\n * - Drag interactions (mouse and touch)\n * - Wheel scrolling\n * - Keyboard navigation (arrow keys, Home/End)\n * - Double-click to reset to default value\n *\n * The hook wraps the framework-agnostic `ContinuousInteractionController` and provides React\n * event handlers that can be attached directly to SVG elements. It handles focus\n * management, accessibility attributes, and cursor styling automatically.\n *\n * Cursor types are customizable via CSS variables in themes.css (e.g., `--audioui-cursor-clickable`,\n * `--audioui-cursor-bidirectional`). The cursor selection logic (which cursor to show when) is\n * fixed based on interaction state, but the actual cursor values are customizable.\n *\n * @param adjustValue Function to adjust the value based on a delta. Receives (delta, sensitivity).\n * @param keyboardStep Step size for keyboard interaction (normalized 0..1, default: 0.05)\n * @param interactionMode Interaction mode: \"drag\", \"wheel\", or \"both\" (default: \"both\")\n * @param direction Direction of drag interaction: \"vertical\", \"horizontal\", \"circular\", or \"both\" (default: \"both\")\n * @param sensitivity Sensitivity of the control (default: 0.005). Higher = more sensitive.\n * @param wheelSensitivity Optional separate sensitivity for wheel events. Defaults to DEFAULT_WHEEL_SENSITIVITY (0.005).\n * @param step Normalized step size (0..1). Used for adaptive wheel interaction and sensitivity scaling.\n * @param min Minimum value (real domain). Used to calculate normalized step if step is not provided.\n * @param max Maximum value (real domain). Used to calculate normalized step if step is not provided.\n * @param paramStep Step size (real domain). Used to calculate normalized step if step is not provided.\n * @param disabled Whether the control is disabled (default: false)\n * @param editable Whether the control is editable (default: true). When false, uses `--audioui-cursor-noneditable`.\n * @param resetToDefault Function to reset the value to its default (called on double-click). Only active when editable and not disabled.\n * @param onDragStart Callback when drag interaction starts\n * @param onDragEnd Callback when drag interaction ends\n * @param onMouseDown Optional user-provided mouse down handler (composed with hook handler)\n * @param onTouchStart Optional user-provided touch start handler (composed with hook handler)\n * @param onWheel Optional user-provided wheel handler (composed with hook handler)\n * @param onKeyDown Optional user-provided keyboard key down handler (composed with hook handler)\n * @param onDoubleClick Optional user-provided double click handler (composed with hook handler)\n * @returns Object containing React event handlers (onMouseDown, onTouchStart, onWheel, onKeyDown, onDoubleClick) and accessibility props\n *\n * @example\n * ```tsx\n * const interactiveProps = useContinuousInteraction({\n * adjustValue: (delta, sensitivity) => {\n * setValue(v => clamp(v + delta * sensitivity, 0, 100));\n * },\n * resetToDefault: () => setValue(defaultValue),\n * interactionMode: \"both\",\n * direction: \"vertical\",\n * sensitivity: 0.01\n * });\n *\n * <svg {...interactiveProps}>\n * <circle cx={50} cy={50} r={30} />\n * </svg>\n * ```\n */\nexport function useContinuousInteraction({\n adjustValue,\n keyboardStep = DEFAULT_KEYBOARD_STEP,\n interactionMode = \"both\",\n direction = \"both\",\n sensitivity,\n wheelSensitivity,\n step,\n min,\n max,\n paramStep,\n disabled = false,\n editable = true,\n onDragStart,\n onDragEnd,\n resetToDefault,\n onMouseDown: userOnMouseDown,\n onTouchStart: userOnTouchStart,\n onWheel: userOnWheel,\n onKeyDown: userOnKeyDown,\n onDoubleClick: userOnDoubleClick,\n}: UseContinuousInteractionProps): ContinuousInteractionHandlers {\n // Adaptive Sensitivity Logic\n // If a step is provided, ensure that dragging one step corresponds to at most TARGET_PIXELS_PER_STEP.\n // This prevents \"dead zones\" where you have to drag huge distances to change a low-resolution parameter.\n const effectiveStep = useMemo(() => {\n if (step !== undefined) return step;\n if (paramStep !== undefined && min !== undefined && max !== undefined && max !== min) {\n return paramStep / Math.abs(max - min);\n }\n return undefined;\n }, [step, min, max, paramStep]);\n\n const effectiveSensitivity = useMemo(() => {\n const base = sensitivity ?? DEFAULT_CONTINUOUS_SENSITIVITY;\n if (effectiveStep) {\n // e.g. Step = 0.1 (range 0-10). Target = 5px.\n // Required sensitivity = 0.1 / 5 = 0.02.\n // Base = 0.005.\n // Result = 0.02.\n const minSensitivityForStep = effectiveStep / TARGET_PIXELS_PER_STEP;\n return Math.max(base, minSensitivityForStep);\n }\n return base;\n }, [sensitivity, effectiveStep]);\n\n const controllerRef = useRef<ContinuousInteractionController | null>(null);\n\n if (!controllerRef.current) {\n controllerRef.current = new ContinuousInteractionController({\n adjustValue,\n keyboardStep,\n interactionMode,\n direction,\n sensitivity: effectiveSensitivity,\n wheelSensitivity,\n step: effectiveStep,\n disabled,\n onDragStart,\n onDragEnd,\n });\n }\n\n useEffect(() => {\n controllerRef.current?.updateConfig({\n adjustValue,\n keyboardStep,\n interactionMode,\n direction,\n sensitivity: effectiveSensitivity,\n wheelSensitivity,\n step: effectiveStep,\n disabled,\n onDragStart,\n onDragEnd,\n });\n }, [\n adjustValue,\n keyboardStep,\n interactionMode,\n direction,\n effectiveSensitivity,\n wheelSensitivity,\n effectiveStep,\n disabled,\n onDragStart,\n onDragEnd,\n ]);\n\n useEffect(() => {\n return () => {\n controllerRef.current?.dispose();\n };\n }, []);\n\n // Handlers with user handler composition\n const handlers = useMemo(() => {\n const ctrl = controllerRef.current!;\n\n return {\n onMouseDown: (e: React.MouseEvent) => {\n // Call user handler first\n userOnMouseDown?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n ctrl.handleMouseDown(e.clientX, e.clientY, e.currentTarget);\n }\n },\n onTouchStart: (e: React.TouchEvent) => {\n // Call user handler first\n userOnTouchStart?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n const touch = e.touches[0];\n ctrl.handleTouchStart(touch.clientX, touch.clientY, e.currentTarget);\n }\n },\n onWheel: (e: React.WheelEvent) => {\n // Call user handler first\n userOnWheel?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n ctrl.handleWheel(e as unknown as WheelEvent);\n }\n },\n onKeyDown: (e: React.KeyboardEvent) => {\n // Call user handler first\n userOnKeyDown?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n ctrl.handleKeyDown(e as unknown as KeyboardEvent);\n }\n },\n onDoubleClick: (e: React.MouseEvent) => {\n // Call user handler first\n userOnDoubleClick?.(e);\n // Only reset if not prevented and resetToDefault is provided\n if (!e.defaultPrevented && resetToDefault && editable && !disabled) {\n e.preventDefault();\n resetToDefault();\n }\n },\n };\n }, [\n userOnMouseDown,\n userOnTouchStart,\n userOnWheel,\n userOnKeyDown,\n userOnDoubleClick,\n resetToDefault,\n editable,\n disabled,\n ]);\n\n // Cursor selection based on interaction state - uses CSS variables for customization\n // The logic (when to show which cursor) is fixed, but cursor types are customizable via CSS\n const cursor = disabled\n ? \"var(--audioui-cursor-disabled)\"\n : !editable\n ? \"var(--audioui-cursor-noneditable)\"\n : interactionMode === \"wheel\"\n ? \"var(--audioui-cursor-vertical)\"\n : direction === \"horizontal\"\n ? \"var(--audioui-cursor-horizontal)\"\n : direction === \"vertical\"\n ? \"var(--audioui-cursor-vertical)\"\n : direction === \"both\"\n ? \"var(--audioui-cursor-bidirectional)\"\n : direction === \"circular\"\n ? \"var(--audioui-cursor-circular)\"\n : \"var(--audioui-cursor-clickable)\";\n\n return {\n ...handlers,\n tabIndex: disabled ? -1 : 0,\n role: \"slider\",\n \"aria-disabled\": disabled,\n style: {\n cursor,\n touchAction: \"none\",\n },\n };\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport { useMemo } from \"react\";\nimport { ContinuousParameter, AudioParameterFactory, ScaleType, MidiResolution } from \"@cutoff/audio-ui-core\";\n\nexport interface UseContinuousParameterResolutionProps {\n /** The parameter definition (Strict mode) */\n parameter?: ContinuousParameter;\n /** Identifier for the parameter (used in Ad-Hoc mode) */\n paramId?: string;\n /** Label for the parameter (Ad-Hoc mode) */\n label?: string;\n /** Minimum value (Ad-Hoc mode) */\n min?: number;\n /** Maximum value (Ad-Hoc mode) */\n max?: number;\n /** Step size for value adjustments (Ad-Hoc mode) */\n step?: number;\n /** Whether the parameter operates in bipolar mode (Ad-Hoc mode) */\n bipolar?: boolean;\n /** Unit suffix for the value (Ad-Hoc mode, e.g. \"dB\", \"Hz\") */\n unit?: string;\n /** Scale function or shortcut for the parameter (Ad-Hoc mode) */\n scale?: ScaleType;\n /** MIDI resolution in bits (Ad-Hoc mode)\n * @default 32\n */\n midiResolution?: MidiResolution;\n /** Default value for the parameter (Ad-Hoc mode) */\n defaultValue?: number;\n}\n\nexport interface UseContinuousParameterResolutionResult {\n /** The resolved ContinuousParameter (derived from props or created from ad-hoc props) */\n derivedParameter: ContinuousParameter;\n}\n\n/**\n * Hook to resolve a ContinuousParameter from props.\n *\n * Supports two modes:\n * 1. Strict Mode (Parameter only): Model provided via parameter prop.\n * 2. Ad-Hoc Mode (Props only): Model created from individual props (min, max, step, etc.).\n *\n * When `parameter` is provided, it takes precedence over ad-hoc props.\n *\n * @param props - Configuration object for parameter resolution\n * @param props.parameter - The parameter definition (Strict mode). When provided, takes precedence over ad-hoc props.\n * @param props.paramId - Identifier for the parameter (used in Ad-Hoc mode)\n * @param props.label - Label for the parameter (Ad-Hoc mode)\n * @param props.min - Minimum value (Ad-Hoc mode)\n * @param props.max - Maximum value (Ad-Hoc mode)\n * @param props.step - Step size for value adjustments (Ad-Hoc mode)\n * @param props.bipolar - Whether the parameter operates in bipolar mode (Ad-Hoc mode)\n * @param props.unit - Unit suffix for the value (Ad-Hoc mode, e.g. \"dB\", \"Hz\")\n * @param props.scale - Scale function or shortcut for the parameter (Ad-Hoc mode)\n * @param props.midiResolution - MIDI resolution in bits (Ad-Hoc mode, default: 32)\n * @param props.defaultValue - Default value for the parameter (Ad-Hoc mode)\n * @returns Object containing the resolved ContinuousParameter\n *\n * @example\n * ```tsx\n * // Strict mode: use provided parameter\n * const { derivedParameter } = useContinuousParameterResolution({\n * parameter: volumeParam\n * });\n *\n * // Ad-Hoc mode: create from props\n * const { derivedParameter } = useContinuousParameterResolution({\n * paramId: \"volume\",\n * label: \"Volume\",\n * min: 0,\n * max: 100,\n * step: 1,\n * unit: \"%\"\n * });\n * ```\n */\nexport function useContinuousParameterResolution({\n parameter,\n paramId,\n label,\n min,\n max,\n step,\n bipolar,\n unit,\n scale,\n midiResolution = 32,\n defaultValue,\n}: UseContinuousParameterResolutionProps): UseContinuousParameterResolutionResult {\n return useMemo(() => {\n let derivedParameter: ContinuousParameter;\n\n if (parameter) {\n // Strict mode: use provided parameter\n derivedParameter = parameter;\n } else {\n // Ad-hoc mode: create parameter from props\n derivedParameter = AudioParameterFactory.createControl({\n id: paramId ?? \"adhoc-continuous\",\n label,\n min,\n max,\n step,\n bipolar,\n unit,\n scale,\n midiResolution,\n defaultValue,\n });\n }\n\n return {\n derivedParameter,\n };\n }, [parameter, paramId, label, min, max, step, bipolar, unit, scale, midiResolution, defaultValue]);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport classNames from \"classnames\";\nimport { CLASSNAMES } from \"@cutoff/audio-ui-core\";\nimport {\n AdaptiveBoxProps,\n AdaptiveBoxLogicalSizeProps,\n ContinuousControlProps,\n ControlComponent,\n ValueLabelMode,\n} from \"@/types\";\nimport AdaptiveBox from \"../AdaptiveBox\";\nimport { useAudioParameter } from \"@/hooks/useAudioParameter\";\nimport { useContinuousInteraction } from \"@/hooks/useContinuousInteraction\";\nimport { useContinuousParameterResolution } from \"@/hooks/useContinuousParameterResolution\";\n\nexport type ContinuousControlComponentProps<P extends object = Record<string, unknown>> =\n // Base Control Props (includes all ContinuousControlProps)\n ContinuousControlProps &\n // Layout props that configure AdaptiveBox behavior\n AdaptiveBoxProps &\n // Logical size props that override view component defaults\n AdaptiveBoxLogicalSizeProps & {\n /**\n * The Visualization Component.\n * Must adhere to ControlComponent contract.\n */\n view: ControlComponent<P>;\n\n /**\n * Props specific to the Visualization Component.\n */\n viewProps: P;\n\n /**\n * Content overlay (HTML) rendered over the SVG (e.g. text value).\n * Rendered via AdaptiveBox.HtmlOverlay to avoid foreignObject issues.\n */\n htmlOverlay?: React.ReactNode;\n\n /**\n * Controls how the label and value are displayed.\n * - \"labelOnly\": Always shows the label (default)\n * - \"valueOnly\": Always shows the value\n * - \"interactive\": Shows label normally, but temporarily swaps to value during interaction\n * @default \"labelOnly\"\n */\n valueAsLabel?: ValueLabelMode;\n };\n\n/**\n * A Generic Continuous Control that connects a Data Model (AudioParameter)\n * to a Visualization View (ControlComponent).\n *\n * This component provides a generic wrapper for continuous controls (Knob, Slider, etc.),\n * decoupling behavior (AudioParameter, interaction logic) from visualization (SVG rendering).\n * It handles parameter resolution, normalization, interaction, and layout automatically.\n *\n * Supports double-click to reset to default value when editable (onChange provided).\n * The default value is determined by the parameter's `defaultValue` property, or\n * calculated as 0.0 for unipolar or 0.5 for bipolar parameters when not specified.\n *\n * @param props Component props including parameter configuration, view component, and layout props\n * @returns Rendered continuous control with AdaptiveBox layout\n */\nexport function ContinuousControl<P extends object = Record<string, unknown>>(\n props: ContinuousControlComponentProps<P>\n) {\n const {\n view: View,\n viewProps,\n htmlOverlay,\n min,\n max,\n step,\n value,\n label,\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n viewBoxWidthUnits,\n viewBoxHeightUnits,\n labelHeightUnits,\n className,\n style,\n onChange,\n paramId,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n parameter,\n interactionMode,\n interactionDirection,\n interactionSensitivity,\n unit,\n scale,\n valueFormatter,\n valueAsLabel = \"labelOnly\",\n midiResolution,\n defaultValue,\n } = props;\n\n const bipolar = props.bipolar ?? false;\n const { derivedParameter } = useContinuousParameterResolution({\n parameter,\n paramId,\n label,\n min,\n max,\n step,\n bipolar,\n unit,\n scale,\n midiResolution,\n defaultValue,\n });\n\n // Interaction state for valueAsLabel=\"interactive\"\n const [isDragging, setIsDragging] = React.useState(false);\n const [isRecentlyActive, setIsRecentlyActive] = React.useState(false);\n const activityTimerRef = React.useRef<number | undefined>(undefined);\n\n const handleDragStart = React.useCallback(() => {\n setIsDragging(true);\n setIsRecentlyActive(true);\n if (activityTimerRef.current) window.clearTimeout(activityTimerRef.current);\n }, []);\n\n const handleDragEnd = React.useCallback(() => {\n setIsDragging(false);\n // Start decay timer\n if (activityTimerRef.current) window.clearTimeout(activityTimerRef.current);\n activityTimerRef.current = window.setTimeout(() => {\n setIsRecentlyActive(false);\n }, 1000);\n }, []);\n\n const handleActivity = React.useCallback(() => {\n setIsRecentlyActive(true);\n if (activityTimerRef.current) window.clearTimeout(activityTimerRef.current);\n // If not dragging, start decay immediately (debounce effect)\n if (!isDragging) {\n activityTimerRef.current = window.setTimeout(() => {\n setIsRecentlyActive(false);\n }, 1000);\n }\n }, [isDragging]);\n\n const showValueAsLabel =\n valueAsLabel === \"valueOnly\" || (valueAsLabel === \"interactive\" && (isDragging || isRecentlyActive));\n\n const { normalizedValue, adjustValue, effectiveLabel, resetToDefault } = useAudioParameter(\n value,\n onChange,\n derivedParameter,\n valueFormatter,\n label,\n showValueAsLabel\n );\n\n // Wrap adjustValue to trigger activity on changes (wheel, keyboard)\n const wrappedAdjustValue = React.useCallback(\n (delta: number, sensitivity?: number) => {\n if (valueAsLabel === \"interactive\" && onChange) {\n handleActivity();\n }\n adjustValue(delta, sensitivity);\n },\n [adjustValue, valueAsLabel, handleActivity, onChange]\n );\n\n const effectiveInteractionMode = interactionMode ?? View.interaction.mode ?? \"both\";\n const effectiveDirection = interactionDirection ?? View.interaction.direction ?? \"both\";\n\n // Only editable when onChange is provided (onClick is not relevant for interaction controller)\n const interactiveProps = useContinuousInteraction({\n adjustValue: wrappedAdjustValue,\n interactionMode: effectiveInteractionMode,\n direction: effectiveDirection,\n sensitivity: interactionSensitivity,\n min: derivedParameter.min,\n max: derivedParameter.max,\n paramStep: derivedParameter.step,\n editable: !!onChange,\n resetToDefault: onChange ? resetToDefault : undefined,\n onDragStart: valueAsLabel === \"interactive\" && onChange ? handleDragStart : undefined,\n onDragEnd: valueAsLabel === \"interactive\" && onChange ? handleDragEnd : undefined,\n onMouseDown,\n onTouchStart: undefined, // ContinuousControl doesn't have onTouchStart prop\n onWheel: undefined, // ContinuousControl doesn't have onWheel prop\n onKeyDown: undefined, // ContinuousControl doesn't have onKeyDown prop\n });\n\n const componentClassNames = useMemo(() => {\n return classNames(className, CLASSNAMES.root, CLASSNAMES.container);\n }, [className]);\n\n const svgClassNames = useMemo(() => {\n return onChange || onClick ? CLASSNAMES.highlight : \"\";\n }, [onChange, onClick]);\n\n // Add clickable cursor when clickable but not draggable (onClick but no onChange)\n // Uses CSS variable for customizable cursor type\n const svgStyle = {\n ...(interactiveProps.style ?? {}),\n // Override cursor for click-only controls\n ...(onClick && !onChange ? { cursor: \"var(--audioui-cursor-clickable)\" as const } : {}),\n };\n\n return (\n <AdaptiveBox\n displayMode={displayMode ?? \"scaleToFit\"}\n labelMode={labelMode}\n labelOverflow={labelOverflow}\n className={componentClassNames}\n style={style}\n labelHeightUnits={labelHeightUnits ?? View.labelHeightUnits ?? 20}\n viewBoxWidth={viewBoxWidthUnits ?? View.viewBox.width}\n viewBoxHeight={viewBoxHeightUnits ?? View.viewBox.height}\n >\n <AdaptiveBox.Svg\n className={svgClassNames}\n style={svgStyle}\n onWheel={interactiveProps.onWheel}\n onClick={onClick}\n onMouseDown={interactiveProps.onMouseDown}\n onTouchStart={interactiveProps.onTouchStart}\n onKeyDown={interactiveProps.onKeyDown}\n onDoubleClick={interactiveProps.onDoubleClick}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n tabIndex={interactiveProps.tabIndex}\n role={interactiveProps.role}\n aria-valuenow={value}\n aria-label={effectiveLabel}\n >\n <View normalizedValue={normalizedValue} {...viewProps} />\n </AdaptiveBox.Svg>\n {htmlOverlay && <AdaptiveBox.HtmlOverlay>{htmlOverlay}</AdaptiveBox.HtmlOverlay>}\n {effectiveLabel && (\n <AdaptiveBox.Label position={labelPosition} align={labelAlign}>\n {effectiveLabel}\n </AdaptiveBox.Label>\n )}\n </AdaptiveBox>\n );\n}\n\nexport default React.memo(ContinuousControl) as typeof ContinuousControl;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport KnobView from \"./KnobView\";\nimport ContinuousControl from \"@/primitives/controls/ContinuousControl\";\nimport {\n AdaptiveBoxProps,\n AdaptiveSizeProps,\n ContinuousControlProps,\n ThemableProps,\n KnobVariant,\n ValueLabelMode,\n} from \"@/types\";\nimport { clampNormalized } from \"@cutoff/audio-ui-core\";\nimport { useAudioParameter } from \"@/hooks/useAudioParameter\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { useContinuousParameterResolution } from \"@/hooks/useContinuousParameterResolution\";\nimport { useThemableProps } from \"@/hooks/useThemableProps\";\n\n/**\n * Default openness for knob ring in degrees (matches ValueRing default)\n */\nexport const DEFAULT_OPENNESS = 90;\n\n/**\n * Default rotation angle offset in degrees (matches ValueRing default)\n */\nexport const DEFAULT_ROTATION = 0;\n\n/**\n * Props for the Knob component (built-in control with theming support)\n */\nexport type KnobProps = ContinuousControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n ThemableProps & {\n /** Visual variant of the knob\n * @default \"abstract\"\n */\n variant?: KnobVariant;\n /** Thickness of the knob's stroke (normalized 0.0-1.0, maps to 1-20)\n * @default Variant-dependent: 0.4 for abstract/simplest, 0.2 for others\n */\n thickness?: number;\n /** Openness of the ring in degrees (value between 0-360º; 0º: closed; 90º: 3/4 open; 180º: half-circle;)\n * @default 90\n */\n openness?: number;\n /** Optional rotation angle offset in degrees\n * @default 0\n */\n rotation?: number;\n /**\n * Whether to use RotaryImage (true) or RadialImage (false) for iconCap overlay.\n * When true, the icon rotates with the knob value; when false, the icon remains static.\n * Only applies when variant is \"iconCap\" and children is provided.\n * @default false\n */\n rotaryOverlay?: boolean;\n /**\n * SVG content to display as overlay in iconCap variant.\n * Typically an icon component (e.g., wave icons) that will be rendered at the center of the knob.\n * The icon inherits color via currentColor, so it will adapt to light/dark mode automatically.\n * Only used when variant is \"iconCap\".\n *\n * @example\n * ```tsx\n * <Knob variant=\"iconCap\" value={50} min={0} max={100}>\n * <SineWaveIcon />\n * </Knob>\n * ```\n */\n children?: React.ReactNode;\n /**\n * Controls how the label and value are displayed.\n * - \"labelOnly\": Always shows the label (default)\n * - \"valueOnly\": Always shows the value\n * - \"interactive\": Shows label normally, but temporarily swaps to value during interaction\n * @default \"labelOnly\"\n */\n valueAsLabel?: ValueLabelMode;\n };\n\n/**\n * Knob component provides a circular control for value adjustment.\n * Supports continuous value ranges with optional bipolar mode, custom formatting,\n * and multiple visual variants.\n *\n * @example Basic usage\n * ```tsx\n * <Knob value={50} min={0} max={100} label=\"Volume\" />\n * ```\n *\n * @example With iconCap variant and icon\n * ```tsx\n * <Knob variant=\"iconCap\" value={64} min={0} max={127} rotaryOverlay={true}>\n * <SineWaveIcon />\n * </Knob>\n * ```\n */\nfunction Knob({\n min,\n max,\n step,\n bipolar = false,\n value,\n onChange,\n valueFormatter,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n labelHeightUnits,\n color,\n roundness,\n variant = \"abstract\",\n thickness,\n openness = DEFAULT_OPENNESS,\n rotation = DEFAULT_ROTATION,\n parameter,\n unit,\n scale,\n paramId,\n interactionMode,\n interactionDirection,\n interactionSensitivity,\n rotaryOverlay: svgOverlayRotary = false,\n children: svgOverlay,\n valueAsLabel = \"labelOnly\",\n midiResolution,\n defaultValue,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: KnobProps) {\n const { style: themableStyle } = useThemableProps({\n color,\n roundness,\n style,\n });\n\n // Clamp thickness if provided\n const clampedThickness = thickness !== undefined ? clampNormalized(thickness) : undefined;\n\n const { derivedParameter: parameterDef } = useContinuousParameterResolution({\n parameter,\n paramId: paramId ?? \"adhoc-knob\",\n label,\n min,\n max,\n step,\n bipolar,\n unit,\n scale,\n midiResolution,\n defaultValue,\n });\n\n const { formattedValue } = useAudioParameter(value, onChange, parameterDef, valueFormatter);\n\n const { sizeClassName, sizeStyle: adaptiveSizeStyle } = useAdaptiveSize(adaptiveSize, size, \"knob\");\n\n // Uses container query units (cqmin) so text scales with component size\n const displayValueOverlay =\n variant === \"abstract\" ? (\n <div\n style={{\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontSize: \"22cqmin\",\n fontWeight: \"500\",\n color: \"var(--audioui-text-color)\",\n cursor: \"inherit\",\n }}\n >\n {formattedValue}\n </div>\n ) : undefined;\n\n return (\n <ContinuousControl\n min={min}\n max={max}\n step={step}\n bipolar={bipolar}\n value={value}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n labelOverflow={labelOverflow}\n labelHeightUnits={labelHeightUnits}\n className={classNames(sizeClassName, className)}\n style={{ ...adaptiveSizeStyle, ...themableStyle }}\n onChange={onChange}\n paramId={paramId}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n parameter={parameter}\n unit={unit}\n scale={scale}\n midiResolution={midiResolution}\n defaultValue={defaultValue}\n interactionMode={interactionMode}\n interactionDirection={interactionDirection}\n interactionSensitivity={interactionSensitivity}\n valueFormatter={valueFormatter}\n valueAsLabel={valueAsLabel}\n view={KnobView}\n viewProps={{\n color: color ?? \"var(--audioui-primary-color)\",\n variant: variant,\n thickness: clampedThickness,\n roundness: roundness ?? \"var(--audioui-roundness-knob)\",\n openness: openness,\n rotation: rotation,\n svgOverlayRotary: svgOverlayRotary,\n svgOverlay: svgOverlay,\n bipolar: bipolar,\n }}\n htmlOverlay={displayValueOverlay}\n />\n );\n}\n\nexport default React.memo(Knob);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React, { useCallback, useRef, useEffect } from \"react\";\nimport { DiscreteInteractionController } from \"@cutoff/audio-ui-core\";\n\nexport interface UseDiscreteInteractionProps {\n /** Current value of the control */\n value: string | number;\n /** List of available options */\n options: Array<{ value: string | number }>;\n /** Callback to update the value */\n onValueChange: (value: string | number) => void;\n /** Whether the control is disabled */\n disabled?: boolean;\n /** Optional user-provided click handler (composed with hook handler) */\n onClick?: React.MouseEventHandler;\n /** Optional user-provided mouse down handler (composed with hook handler) */\n onMouseDown?: React.MouseEventHandler;\n /** Optional user-provided keyboard key down handler (composed with hook handler) */\n onKeyDown?: React.KeyboardEventHandler;\n}\n\nexport interface UseDiscreteInteractionResult {\n /** Handler for click events (cycles through options) */\n handleClick: (e: React.MouseEvent) => void;\n /** Handler for keyboard events (Arrows to step, Space/Enter to cycle) */\n handleKeyDown: (e: React.KeyboardEvent) => void;\n /** Handler for mouse down events (passes through to user handler if provided) */\n handleMouseDown?: React.MouseEventHandler;\n /** Manually cycle to the next value (wrapping around) */\n cycleNext: () => void;\n /** Manually step to the next value (clamped) */\n stepNext: () => void;\n /** Manually step to the previous value (clamped) */\n stepPrev: () => void;\n}\n\n/**\n * Hook to manage interactions for discrete controls (switches, toggles, selectors).\n *\n * Provides standardized logic for:\n * - Cycling through options (wrapping) via Click or Space/Enter\n * - Stepping through options (clamped) via Arrow keys\n * - Finding the nearest valid option index when value doesn't match\n *\n * The hook wraps the framework-agnostic `DiscreteInteractionController` and provides React\n * event handlers that can be attached directly to DOM elements. It maintains stable callback\n * references across renders using `useCallback` and updates the controller configuration via\n * `useEffect` when props change.\n *\n * @param {UseDiscreteInteractionProps} props - Configuration for the discrete interaction hook\n * @param {string | number} props.value - Current value of the control\n * @param {Array<{ value: string | number }>} props.options - List of available options\n * @param {(value: string | number) => void} props.onValueChange - Callback to update the value\n * @param {boolean} [props.disabled=false] - Whether the control is disabled\n * @param {React.MouseEventHandler} [props.onClick] - Optional user-provided click handler\n * @param {React.MouseEventHandler} [props.onMouseDown] - Optional user-provided mouse down handler\n * @param {React.KeyboardEventHandler} [props.onKeyDown] - Optional user-provided keyboard key down handler\n * @returns {UseDiscreteInteractionResult} Object containing event handlers and manual control methods\n *\n * @example\n * ```tsx\n * const { handleClick, handleKeyDown } = useDiscreteInteraction({\n * value,\n * options,\n * onValueChange: (val) => setNormalizedValue(converter.normalize(val))\n * });\n *\n * <div onClick={handleClick} onKeyDown={handleKeyDown} />\n * ```\n */\nexport function useDiscreteInteraction({\n value,\n options,\n onValueChange,\n disabled = false,\n onClick: userOnClick,\n onMouseDown: userOnMouseDown,\n onKeyDown: userOnKeyDown,\n}: UseDiscreteInteractionProps): UseDiscreteInteractionResult {\n const controllerRef = useRef<DiscreteInteractionController | null>(null);\n\n if (!controllerRef.current) {\n controllerRef.current = new DiscreteInteractionController({\n value,\n options,\n onValueChange,\n disabled,\n });\n }\n\n useEffect(() => {\n controllerRef.current?.updateConfig({\n value,\n options,\n onValueChange,\n disabled,\n });\n }, [value, options, onValueChange, disabled]);\n\n const cycleNext = useCallback(() => controllerRef.current?.cycleNext(), []);\n const stepNext = useCallback(() => controllerRef.current?.stepNext(), []);\n const stepPrev = useCallback(() => controllerRef.current?.stepPrev(), []);\n\n const handleClick = useCallback(\n (e: React.MouseEvent) => {\n // Call user handler first\n userOnClick?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n controllerRef.current?.handleClick(e.defaultPrevented);\n }\n },\n [userOnClick]\n );\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent) => {\n // Call user handler first\n userOnKeyDown?.(e);\n // Only call hook handler if not prevented\n if (!e.defaultPrevented) {\n const handled = controllerRef.current?.handleKeyDown(e.key);\n if (handled) {\n e.preventDefault();\n }\n }\n },\n [userOnKeyDown]\n );\n\n // Pass through mouse down handler if provided (no discrete interaction logic needed for mousedown)\n const handleMouseDown = userOnMouseDown;\n\n return {\n handleClick,\n handleKeyDown,\n handleMouseDown,\n cycleNext,\n stepNext,\n stepPrev,\n };\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React, { useMemo } from \"react\";\nimport { DiscreteParameter, DiscreteOption, MidiResolution } from \"@cutoff/audio-ui-core\";\nimport { OptionViewProps } from \"@/primitives/controls/OptionView\";\n\nexport interface UseDiscreteParameterResolutionProps {\n /** Child elements (OptionView components) for visual content mapping (Hybrid mode)\n *\n * **Visual Content Only**: Children provide ReactNodes for rendering (icons, text, custom components).\n * They do NOT define the parameter model - use `options` prop or `parameter` prop for that.\n *\n * When both `options` and `children` are provided, children are matched to options by value\n * to create the visual content map.\n */\n children?: React.ReactNode;\n /** Option definitions for the parameter model (Ad-Hoc mode)\n *\n * **Parameter Model Only**: This prop defines the parameter structure (value, label, midiValue).\n * It does NOT provide visual content - use `children` (OptionView components) for that.\n *\n * When both `options` and `children` are provided:\n * - `options` defines the parameter model\n * - `children` provide visual content (matched by value)\n */\n options?: DiscreteOption[];\n /** Identifier for the parameter (used in Ad-Hoc mode) */\n paramId?: string;\n /** The parameter definition (Strict or Hybrid mode) */\n parameter?: DiscreteParameter;\n /** Default value (Ad-Hoc mode) or override for parameter default */\n defaultValue?: string | number;\n /** Label for the parameter (Ad-Hoc mode) */\n label?: string;\n /** MIDI resolution in bits (Ad-Hoc mode)\n * @default 7\n */\n midiResolution?: MidiResolution;\n /** MIDI mapping strategy (Ad-Hoc mode)\n * @default \"spread\"\n */\n midiMapping?: \"spread\" | \"sequential\" | \"custom\";\n}\n\nexport interface UseDiscreteParameterResolutionResult {\n /** The resolved DiscreteParameter (derived from props or children) */\n derivedParameter: DiscreteParameter;\n /** Map of values to visual content (ReactNodes) from children */\n visualContentMap: Map<string | number, React.ReactNode>;\n /** The effective default value (resolved from parameter, prop, or first option) */\n effectiveDefaultValue: string | number;\n}\n\n/**\n * Hook to resolve a DiscreteParameter and visual content from props and/or children.\n *\n * **Important: Options vs Children**\n *\n * - **`options` prop**: Defines the parameter model (value, label, midiValue). Used for parameter structure.\n * - **`children` (OptionView components)**: Provides visual content (ReactNodes) for rendering. Used for display.\n *\n * These serve different purposes and can be used together:\n * - Use `options` when you have data-driven option definitions\n * - Use `children` when you want to provide custom visual content (icons, styled text, etc.)\n * - Use both: `options` for the model, `children` for visuals (matched by value)\n *\n * Supports four modes of operation:\n * 1. **Ad-Hoc Mode (Options prop)**: Model from `options` prop, visual from `children` (if provided) or default rendering\n * 2. **Ad-Hoc Mode (Children only)**: Model inferred from OptionView children, visual from children\n * 3. **Strict Mode (Parameter only)**: Model from `parameter` prop, visual via `renderOption` callback\n * 4. **Hybrid Mode (Parameter + Children)**: Model from `parameter` prop, visual from children (matched by value)\n *\n * @param props - Configuration object for discrete parameter resolution\n * @param props.options - Option definitions for parameter model (Ad-Hoc mode). Takes precedence over children for model.\n * @param props.children - Child elements (OptionView components) for visual content mapping (Hybrid/Ad-Hoc mode)\n * @param props.paramId - Identifier for the parameter (used in Ad-Hoc mode)\n * @param props.parameter - The parameter definition (Strict or Hybrid mode)\n * @param props.defaultValue - Default value (Ad-Hoc mode) or override for parameter default\n * @param props.label - Label for the parameter (Ad-Hoc mode)\n * @param props.midiResolution - MIDI resolution in bits (Ad-Hoc mode, default: 7)\n * @param props.midiMapping - MIDI mapping strategy (Ad-Hoc mode, default: \"spread\")\n * @returns Object containing the resolved DiscreteParameter, visual content map, and effective default value\n *\n * @example\n * ```tsx\n * // Ad-Hoc Mode with options prop (data-driven)\n * const { derivedParameter } = useDiscreteParameterResolution({\n * options: [\n * { value: \"sine\", label: \"Sine Wave\" },\n * { value: \"square\", label: \"Square Wave\" }\n * ],\n * paramId: \"waveform\"\n * });\n *\n * // Ad-Hoc Mode with children (visual content)\n * const { derivedParameter, visualContentMap } = useDiscreteParameterResolution({\n * children: [\n * <OptionView value=\"sine\"><SineIcon /></OptionView>,\n * <OptionView value=\"square\"><SquareIcon /></OptionView>\n * ],\n * paramId: \"waveform\"\n * });\n *\n * // Hybrid: options for model, children for visuals\n * const { derivedParameter, visualContentMap } = useDiscreteParameterResolution({\n * options: [\n * { value: \"sine\", label: \"Sine Wave\", midiValue: 0 },\n * { value: \"square\", label: \"Square Wave\", midiValue: 1 }\n * ],\n * children: [\n * <OptionView value=\"sine\"><SineIcon /></OptionView>,\n * <OptionView value=\"square\"><SquareIcon /></OptionView>\n * ],\n * paramId: \"waveform\"\n * });\n *\n * // Strict Mode\n * const result = useDiscreteParameterResolution({\n * parameter: myDiscreteParameter,\n * defaultValue: \"custom\"\n * });\n * ```\n */\nexport function useDiscreteParameterResolution({\n children,\n options,\n paramId,\n parameter,\n defaultValue,\n label,\n midiResolution = 7,\n midiMapping = \"spread\",\n}: UseDiscreteParameterResolutionProps): UseDiscreteParameterResolutionResult {\n return useMemo(() => {\n // Build visual content map from children (always extract visual content if children provided)\n const optionEls = React.Children.toArray(children).filter(\n React.isValidElement\n ) as React.ReactElement<OptionViewProps>[];\n\n const visualContentMap = new Map<string | number, React.ReactNode>();\n\n optionEls.forEach((child, index) => {\n const val = child.props.value !== undefined ? child.props.value : index;\n if (child.props.children) {\n visualContentMap.set(val, child.props.children);\n }\n });\n\n // Determine parameter model\n let param: DiscreteParameter;\n let effectiveDefaultValue: string | number;\n\n if (parameter) {\n // Strict mode: use provided parameter\n param = parameter;\n effectiveDefaultValue =\n defaultValue !== undefined\n ? defaultValue\n : (parameter.defaultValue ?? parameter.options[0]?.value ?? \"\");\n } else if (options && options.length > 0) {\n // Ad-hoc mode with options prop: use options for parameter model\n effectiveDefaultValue = defaultValue !== undefined ? defaultValue : options[0].value;\n\n param = {\n id: paramId ?? \"adhoc-discrete\",\n type: \"discrete\",\n name: label || \"\",\n options,\n defaultValue: effectiveDefaultValue,\n midiResolution,\n midiMapping,\n };\n } else {\n // Ad-hoc mode with children only: infer parameter model from children\n const getLabel = (child: React.ReactElement<OptionViewProps>, val: string | number): string => {\n if (child.props.label) return child.props.label;\n if (typeof child.props.children === \"string\") return child.props.children;\n if (typeof child.props.children === \"number\") return String(child.props.children);\n // Fallback for icons/elements: use the value key as string\n return String(val);\n };\n\n const inferredOptions = optionEls.map((child, index) => {\n const val = child.props.value !== undefined ? child.props.value : index;\n return {\n value: val,\n label: getLabel(child, val),\n };\n });\n\n if (inferredOptions.length === 0) {\n inferredOptions.push({ value: 0, label: \"None\" });\n }\n\n effectiveDefaultValue = defaultValue !== undefined ? defaultValue : inferredOptions[0].value;\n\n param = {\n id: paramId ?? \"adhoc-discrete\",\n type: \"discrete\",\n name: label || \"\",\n options: inferredOptions,\n defaultValue: effectiveDefaultValue,\n midiResolution,\n midiMapping,\n };\n }\n\n return {\n derivedParameter: param,\n visualContentMap: visualContentMap,\n effectiveDefaultValue,\n };\n }, [parameter, options, children, label, paramId, defaultValue, midiResolution, midiMapping]);\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo, useCallback } from \"react\";\nimport classNames from \"classnames\";\nimport { CLASSNAMES } from \"@cutoff/audio-ui-core\";\nimport { AdaptiveBoxProps, AdaptiveBoxLogicalSizeProps, DiscreteControlProps, ControlComponent } from \"@/types\";\nimport AdaptiveBox from \"../AdaptiveBox\";\nimport { useAudioParameter } from \"@/hooks/useAudioParameter\";\nimport { useDiscreteInteraction } from \"@/hooks/useDiscreteInteraction\";\nimport { useDiscreteParameterResolution } from \"@/hooks/useDiscreteParameterResolution\";\n\nexport type DiscreteControlComponentProps<P extends object = Record<string, unknown>> =\n // Base Control Props (includes all DiscreteControlProps)\n DiscreteControlProps &\n // Layout props that configure AdaptiveBox behavior\n AdaptiveBoxProps &\n // Logical size props that override view component defaults\n AdaptiveBoxLogicalSizeProps & {\n /**\n * The Visualization Component.\n * Must adhere to ControlComponent contract.\n */\n view: ControlComponent<P>;\n\n /**\n * Props specific to the Visualization Component.\n */\n viewProps: P;\n\n /**\n * Content overlay (HTML) rendered over the SVG (e.g. text value, icons).\n * Rendered via AdaptiveBox.HtmlOverlay to avoid foreignObject issues.\n */\n htmlOverlay?: React.ReactNode;\n };\n\n/**\n * A Generic Discrete Control that connects a Data Model (DiscreteParameter)\n * to a Visualization View (ControlComponent).\n *\n * This component handles parameter resolution, value management, interaction handling,\n * and layout management for discrete controls (CycleButton, Selector, etc.).\n *\n * **Important: Options vs Children**\n *\n * - **`options` prop**: Defines the parameter model (value, label, midiValue). Used for parameter structure.\n * - **`children` (OptionView components)**: Provides visual content (ReactNodes) for rendering. Used for display.\n *\n * Supports four modes of operation:\n * 1. **Ad-Hoc Mode (Options prop)**: Model from `options` prop, visual from `children` (if provided) or default rendering\n * 2. **Ad-Hoc Mode (Children only)**: Model inferred from OptionView children, visual from children\n * 3. **Strict Mode (Parameter only)**: Model from `parameter` prop, visual via `renderOption` callback\n * 4. **Hybrid Mode (Parameter + Children)**: Model from `parameter` prop, visual from children (matched by value)\n *\n * @param props - Component props including parameter configuration, view component, and layout options\n * @returns Rendered discrete control component\n *\n * @example\n * ```tsx\n * // Ad-Hoc Mode with options prop\n * <DiscreteControl\n * value=\"sine\"\n * onChange={(e) => setValue(e.value)}\n * options={[\n * { value: \"sine\", label: \"Sine Wave\" },\n * { value: \"square\", label: \"Square Wave\" }\n * ]}\n * view={KnobView}\n * viewProps={{ color: \"blue\", thickness: 0.4 }}\n * />\n *\n * // Ad-Hoc Mode with children\n * <DiscreteControl\n * value=\"sine\"\n * onChange={(e) => setValue(e.value)}\n * view={KnobView}\n * viewProps={{ color: \"blue\", thickness: 0.4 }}\n * >\n * <OptionView value=\"sine\">Sine</OptionView>\n * <OptionView value=\"square\">Square</OptionView>\n * </DiscreteControl>\n * ```\n */\nexport function DiscreteControl<P extends object = Record<string, unknown>>(props: DiscreteControlComponentProps<P>) {\n const {\n view: View,\n viewProps,\n htmlOverlay,\n value,\n defaultValue,\n onChange,\n children,\n options,\n label,\n paramId,\n parameter,\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n viewBoxWidthUnits,\n viewBoxHeightUnits,\n labelHeightUnits,\n className,\n style,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n midiResolution,\n midiMapping,\n } = props;\n\n const { derivedParameter, effectiveDefaultValue } = useDiscreteParameterResolution({\n children,\n options,\n paramId,\n parameter,\n defaultValue,\n label,\n midiResolution,\n midiMapping,\n });\n\n const effectiveValue = value !== undefined ? value : effectiveDefaultValue;\n\n const { normalizedValue, setNormalizedValue, formattedValue, converter } = useAudioParameter(\n effectiveValue,\n onChange,\n derivedParameter\n );\n\n const handleValueChange = useCallback(\n (val: number | string) => {\n setNormalizedValue(converter.normalize(val));\n },\n [setNormalizedValue, converter]\n );\n\n const { handleClick, handleKeyDown, handleMouseDown } = useDiscreteInteraction({\n value: effectiveValue,\n options: derivedParameter.options,\n onValueChange: handleValueChange,\n disabled: !onChange,\n // Type cast needed because onClick prop expects React.MouseEvent<SVGSVGElement>\n // but hook accepts React.MouseEvent\n onClick: onClick\n ? (e: React.MouseEvent) => onClick(e as unknown as React.MouseEvent<SVGSVGElement>)\n : undefined,\n onMouseDown,\n onKeyDown: undefined, // DiscreteControl doesn't have onKeyDown prop, only uses hook handler\n });\n\n const effectiveLabel = label ?? derivedParameter.name;\n\n const componentClassNames = useMemo(() => {\n return classNames(className, CLASSNAMES.root, CLASSNAMES.container);\n }, [className]);\n\n const svgClassNames = useMemo(() => {\n return onChange || onClick ? CLASSNAMES.highlight : \"\";\n }, [onChange, onClick]);\n\n // Add clickable cursor when interactive (onChange or onClick)\n // Uses CSS variable for customizable cursor type\n const svgStyle = useMemo(\n () => ({\n ...(onClick || onChange ? { cursor: \"var(--audioui-cursor-clickable)\" as const } : {}),\n }),\n [onClick, onChange]\n );\n\n return (\n <AdaptiveBox\n displayMode={displayMode ?? \"scaleToFit\"}\n labelMode={labelMode}\n labelOverflow={labelOverflow}\n className={componentClassNames}\n style={style}\n labelHeightUnits={labelHeightUnits ?? View.labelHeightUnits ?? 20}\n viewBoxWidth={viewBoxWidthUnits ?? View.viewBox.width}\n viewBoxHeight={viewBoxHeightUnits ?? View.viewBox.height}\n >\n <AdaptiveBox.Svg\n className={svgClassNames}\n style={svgStyle}\n onClick={handleClick}\n onMouseDown={handleMouseDown}\n onKeyDown={handleKeyDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n tabIndex={0}\n role=\"spinbutton\"\n aria-valuenow={typeof effectiveValue === \"number\" ? effectiveValue : undefined}\n aria-valuetext={formattedValue}\n aria-label={effectiveLabel}\n >\n <View normalizedValue={normalizedValue} {...viewProps} />\n </AdaptiveBox.Svg>\n {htmlOverlay && <AdaptiveBox.HtmlOverlay>{htmlOverlay}</AdaptiveBox.HtmlOverlay>}\n {effectiveLabel && (\n <AdaptiveBox.Label position={labelPosition} align={labelAlign ?? \"center\"}>\n {effectiveLabel}\n </AdaptiveBox.Label>\n )}\n </AdaptiveBox>\n );\n}\n\nexport default React.memo(DiscreteControl) as typeof DiscreteControl;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport classNames from \"classnames\";\nimport DiscreteControl from \"@/primitives/controls/DiscreteControl\";\nimport KnobView from \"./KnobView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, DiscreteControlProps, ThemableProps } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { useDiscreteParameterResolution } from \"@/hooks/useDiscreteParameterResolution\";\nimport { DEFAULT_ROUNDNESS, CLASSNAMES, clampNormalized } from \"@cutoff/audio-ui-core\";\nimport { useThemableProps } from \"@/hooks/useThemableProps\";\n\n/**\n * Default openness for knob ring in degrees (matches ValueRing default)\n */\nconst DEFAULT_OPENNESS = 90;\n\n/**\n * Default rotation angle offset in degrees (matches ValueRing default)\n */\nconst DEFAULT_ROTATION = 0;\n\nconst CONTENT_WRAPPER_STYLE: React.CSSProperties = {\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n fontSize: \"22cqmin\",\n fontWeight: \"500\",\n color: \"var(--audioui-text-color)\",\n cursor: \"inherit\",\n};\n\nconst ICON_WRAPPER_STYLE: React.CSSProperties = {\n width: \"50cqmin\",\n height: \"50cqmin\",\n};\n\n/**\n * Props for the CycleButton component\n */\nexport type CycleButtonProps = DiscreteControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n ThemableProps & {\n /** Custom renderer for options (used when parameter is provided but no children map exists) */\n renderOption?: (option: { value: string | number; label: string }) => React.ReactNode;\n /** Thickness of the stroke (normalized 0.0-1.0, maps to 1-20). Used by rotary variant. */\n thickness?: number;\n /** Openness of the ring in degrees (value between 0-360º; 0º: closed; 90º: 3/4 open; 180º: half-circle;)\n * @default 90\n */\n openness?: number;\n /** Optional rotation angle offset in degrees\n * @default 0\n */\n rotation?: number;\n };\n\n/**\n * A discrete interaction control that cycles through a set of discrete options.\n *\n * This control supports discrete interaction only (click to cycle, keyboard to step).\n * It does not support continuous interaction (drag/wheel).\n *\n * **Important: Options vs Children**\n *\n * - **`options` prop**: Defines the parameter model (value, label, midiValue). Used for parameter structure.\n * - **`children` (OptionView components)**: Provides visual content (ReactNodes) for rendering. Used for display.\n *\n * Supports multiple visual variants (rotary knob-style, LED indicators, etc.) and\n * four modes of operation:\n * 1. **Ad-Hoc Mode (Options prop)**: Model from `options` prop, visual from `children` (if provided) or default rendering\n * 2. **Ad-Hoc Mode (Children only)**: Model inferred from OptionView children, visual from children\n * 3. **Strict Mode (Parameter only)**: Model from `parameter` prop, visual via `renderOption` callback\n * 4. **Hybrid Mode (Parameter + Children)**: Model from `parameter` prop, visual from children (matched by value)\n *\n * @param props - Component props\n * @returns Rendered CycleButton component\n *\n * @example\n * ```tsx\n * // Ad-Hoc Mode with options prop (data-driven)\n * <CycleButton\n * options={[\n * { value: \"sine\", label: \"Sine Wave\" },\n * { value: \"square\", label: \"Square Wave\" }\n * ]}\n * />\n *\n * // Ad-Hoc Mode with children (visual content)\n * <CycleButton defaultValue=\"sine\" label=\"Waveform\">\n * <OptionView value=\"sine\"><SineIcon /></OptionView>\n * <OptionView value=\"square\"><SquareIcon /></OptionView>\n * </CycleButton>\n *\n * // Hybrid: options for model, children for visuals\n * <CycleButton\n * options={[\n * { value: \"sine\", label: \"Sine Wave\", midiValue: 0 },\n * { value: \"square\", label: \"Square Wave\", midiValue: 1 }\n * ]}\n * >\n * <OptionView value=\"sine\"><SineIcon /></OptionView>\n * <OptionView value=\"square\"><SquareIcon /></OptionView>\n * </CycleButton>\n *\n * // Strict Mode with custom renderer\n * <CycleButton\n * parameter={waveformParam}\n * renderOption={(opt) => <Icon name={opt.value} />}\n * />\n * ```\n */\nfunction CycleButton({\n value,\n defaultValue,\n onChange,\n renderOption,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n labelHeightUnits,\n color,\n roundness,\n thickness = 0.4,\n openness = DEFAULT_OPENNESS,\n rotation = DEFAULT_ROTATION,\n parameter,\n paramId,\n options,\n midiResolution,\n midiMapping,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n children,\n}: CycleButtonProps) {\n const { style: themableStyle, clampedRoundness } = useThemableProps({\n color,\n roundness,\n style,\n });\n\n // Clamp thickness if provided\n const clampedThickness = thickness !== undefined ? clampNormalized(thickness) : undefined;\n\n // Get visualContentMap and derivedParameter for content rendering.\n // Note: DiscreteControl also calls useDiscreteParameterResolution internally for parameter resolution,\n // but we need visualContentMap and derivedParameter here to render the HTML overlay content\n // (icons, text, or custom renderOption output) based on the current value.\n const { visualContentMap, derivedParameter, effectiveDefaultValue } = useDiscreteParameterResolution({\n children,\n options,\n paramId,\n parameter,\n defaultValue,\n label,\n midiResolution,\n midiMapping,\n });\n\n const effectiveValue = value !== undefined ? value : effectiveDefaultValue;\n\n // Get formattedValue for fallback display\n const formattedValue = useMemo(() => {\n const opt = derivedParameter.options.find((opt) => opt.value === effectiveValue);\n return opt?.label ?? String(effectiveValue);\n }, [derivedParameter.options, effectiveValue]);\n\n const content = useMemo(() => {\n const wrapContent = (node: React.ReactNode): React.ReactNode => {\n if (typeof node === \"string\" || typeof node === \"number\") {\n return node;\n }\n\n return (\n <div className={CLASSNAMES.iconWrapper} style={ICON_WRAPPER_STYLE}>\n {node}\n </div>\n );\n };\n\n if (visualContentMap && visualContentMap.has(effectiveValue)) {\n return wrapContent(visualContentMap.get(effectiveValue));\n }\n\n if (renderOption) {\n const opt = derivedParameter.options.find((opt) => opt.value === effectiveValue);\n if (opt) return wrapContent(renderOption(opt));\n }\n\n return formattedValue;\n }, [visualContentMap, effectiveValue, renderOption, derivedParameter.options, formattedValue]);\n\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"knob\");\n\n return (\n <DiscreteControl\n value={value}\n defaultValue={defaultValue}\n onChange={onChange}\n label={label}\n paramId={paramId}\n parameter={parameter}\n options={options}\n midiResolution={midiResolution}\n midiMapping={midiMapping}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n labelOverflow={labelOverflow}\n labelHeightUnits={labelHeightUnits}\n className={classNames(sizeClassName, className)}\n style={{ ...sizeStyle, ...themableStyle }}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n view={KnobView}\n viewProps={{\n bipolar: false,\n thickness: clampedThickness,\n roundness: clampedRoundness ?? DEFAULT_ROUNDNESS,\n openness: openness,\n rotation: rotation,\n color: color ?? \"var(--audioui-primary-color)\",\n }}\n htmlOverlay={<div style={CONTENT_WRAPPER_STYLE}>{content}</div>}\n >\n {children}\n </DiscreteControl>\n );\n}\n\nexport default React.memo(CycleButton);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, useMemo } from \"react\";\nimport { translateSliderRoundness } from \"@cutoff/audio-ui-core\";\nimport { DEFAULT_ROUNDNESS } from \"@cutoff/audio-ui-core\";\n\nexport type LinearStripProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Length of the strip */\n length: number;\n /** Rotation angle in degrees (0 = vertical, -90 or 270 = horizontal) */\n rotation?: number;\n /** Width of the strip (thickness) */\n thickness: number;\n /** Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string) */\n roundness?: number | string;\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n};\n\n/**\n * A reusable SVG primitive that renders a linear strip (rectangle) for linear controls.\n * The strip is positioned at a center point (cx, cy) and can be rotated to any angle.\n *\n * This component is designed to be used inside an <svg> element.\n *\n * @param {number} cx - X coordinate of the center point\n * @param {number} cy - Y coordinate of the center point\n * @param {number} length - Length of the strip (height of the rectangle)\n * @param {number} [rotation=0] - Rotation angle in degrees (0 = vertical, -90 or 270 = horizontal)\n * @param {number} thickness - Width of the strip (thickness of the rectangle)\n * @param {number | string} [roundness] - Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string)\n * @param {string} [className] - CSS class name\n * @param {CSSProperties} [style] - Inline styles\n * @returns {JSX.Element} SVG rect element representing the linear strip\n *\n * @example\n * ```tsx\n * // Vertical strip (default)\n * <LinearStrip cx={50} cy={150} length={260} thickness={6} />\n *\n * // Horizontal strip (rotated -90 degrees)\n * <LinearStrip cx={150} cy={50} length={260} thickness={6} rotation={-90} />\n * ```\n */\nfunction LinearStrip({\n cx,\n cy,\n length,\n rotation = 0,\n thickness,\n roundness = DEFAULT_ROUNDNESS,\n className,\n style,\n}: LinearStripProps) {\n // Calculate corner radius from roundness prop or CSS variable\n const cornerRadius = useMemo(() => {\n if (typeof roundness === \"string\") {\n // CSS variable - pass directly to SVG (browser will resolve it)\n return roundness;\n }\n // Numeric value - translate to legacy pixel range (0-20)\n const legacyRoundness = translateSliderRoundness(roundness ?? DEFAULT_ROUNDNESS);\n\n // If roundness is 0, use square corners\n if (legacyRoundness === 0) {\n return 0;\n }\n\n // Use the translated roundness value\n return legacyRoundness;\n }, [roundness]);\n\n // Calculate rectangle position and dimensions\n // The rectangle is centered at (cx, cy) with width=thickness and height=length\n // Then rotated around the center point\n const rectX = useMemo(() => {\n return cx - thickness / 2;\n }, [cx, thickness]);\n\n const rectY = useMemo(() => {\n return cy - length / 2;\n }, [cy, length]);\n\n // Build transform string for rotation\n const transform = useMemo(() => {\n if (rotation === 0) {\n return undefined;\n }\n // Negate rotation to match Radial components (positive = counter-clockwise)\n return `rotate(${-rotation} ${cx} ${cy})`;\n }, [rotation, cx, cy]);\n\n return (\n <rect\n style={{\n ...style,\n rx: cornerRadius,\n ry: cornerRadius,\n }}\n x={rectX}\n y={rectY}\n width={thickness}\n height={length}\n // Use 0 as fallback for older browsers that don't support CSS rx/ry\n // If cornerRadius is a number, we can use it directly\n rx={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n ry={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n transform={transform}\n className={className}\n />\n );\n}\n\nexport default React.memo(LinearStrip);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, useMemo } from \"react\";\nimport { translateSliderRoundness, DEFAULT_ROUNDNESS, calculateLinearPosition } from \"@cutoff/audio-ui-core\";\n\nexport type ValueStripProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Length of the strip */\n length: number;\n /** Rotation angle in degrees (0 = vertical, -90 or 270 = horizontal) */\n rotation?: number;\n /** Width of the strip (thickness) */\n thickness: number;\n /** Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string) */\n roundness?: number | string;\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Whether to start fill from center (bipolar mode) */\n bipolar?: boolean;\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n};\n\n/**\n * A reusable SVG primitive that renders the active (foreground) portion of a linear strip.\n * Designed to work in tandem with LinearStrip (background).\n *\n * The strip is positioned at a center point (cx, cy) and can be rotated to any angle.\n * - In unipolar mode (default), it fills from the \"bottom\" (relative to rotation) to the current value.\n * - In bipolar mode, it fills from the center to the current value.\n *\n * This component is designed to be used inside an <svg> element.\n *\n * @param {number} cx - X coordinate of the center point\n * @param {number} cy - Y coordinate of the center point\n * @param {number} length - Length of the strip\n * @param {number} [rotation=0] - Rotation angle in degrees (0 = vertical, -90 or 270 = horizontal)\n * @param {number} thickness - Width of the strip (thickness)\n * @param {number | string} [roundness] - Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string)\n * @param {number} normalizedValue - Normalized value between 0 and 1\n * @param {boolean} [bipolar=false] - Whether to start fill from center (bipolar mode)\n * @param {string} [className] - CSS class name\n * @param {CSSProperties} [style] - Inline styles\n * @returns {JSX.Element | null} SVG rect element representing the filled portion, or null if height is too small\n *\n * @example\n * ```tsx\n * // Vertical value strip (unipolar)\n * <ValueStrip cx={50} cy={150} length={260} thickness={6} normalizedValue={0.65} />\n *\n * // Horizontal value strip (bipolar)\n * <ValueStrip cx={150} cy={50} length={260} thickness={6} rotation={-90} normalizedValue={0.75} bipolar />\n * ```\n */\nfunction ValueStrip({\n cx,\n cy,\n length,\n rotation = 0,\n thickness,\n roundness = DEFAULT_ROUNDNESS,\n normalizedValue,\n bipolar = false,\n className,\n style,\n}: ValueStripProps) {\n // Calculate corner radius\n const cornerRadius = useMemo(() => {\n if (typeof roundness === \"string\") {\n return roundness;\n }\n const legacyRoundness = translateSliderRoundness(roundness ?? DEFAULT_ROUNDNESS);\n if (legacyRoundness === 0) return 0;\n return legacyRoundness;\n }, [roundness]);\n\n // Calculate dimensions of the filled rectangle in unrotated coordinate space\n // The rectangle will be rotated around (cx, cy) using the transform attribute\n // The rectangle is computed based on the cursor center position\n const rectProps = useMemo(() => {\n // Get cursor Y position (cursor is always centered horizontally at cx)\n // Note: cursor position is independent of bipolar mode - bipolar only affects rectangle drawing\n const cursorY = calculateLinearPosition(cy, length, normalizedValue);\n\n // Base X position (unrotated) - centered horizontally around cx\n const x = cx - thickness / 2;\n const width = thickness;\n\n let y, height;\n\n if (bipolar) {\n // Bipolar mode: rectangle extends from center (cy) to cursor center (cursorY)\n // Height is the distance from center to cursor\n height = Math.abs(cy - cursorY);\n // Y position is the minimum of center and cursor (top edge of rectangle)\n y = Math.min(cy, cursorY);\n } else {\n // Unipolar mode: rectangle extends from bottom (cy + length/2) to cursor center (cursorY)\n const bottomY = cy + length / 2;\n // Height is the distance from bottom to cursor\n height = bottomY - cursorY;\n // Y position is at cursor (top edge of rectangle)\n y = cursorY;\n }\n\n return { x, y, width, height };\n }, [cx, cy, length, thickness, normalizedValue, bipolar]);\n\n // Build rotation transform\n const transform = useMemo(() => {\n if (rotation === 0) return undefined;\n // Negate rotation to match Radial components (positive = counter-clockwise)\n return `rotate(${-rotation} ${cx} ${cy})`;\n }, [rotation, cx, cy]);\n\n // Optimization: Don't render if height is 0 (or very small)\n if (rectProps.height <= 0.001) {\n return null;\n }\n\n return (\n <rect\n style={{\n ...style,\n rx: cornerRadius,\n ry: cornerRadius,\n }}\n x={rectProps.x}\n y={rectProps.y}\n width={rectProps.width}\n height={rectProps.height}\n // Use 0 as fallback for older browsers that don't support CSS rx/ry\n rx={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n ry={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n transform={transform}\n className={className}\n />\n );\n}\n\nexport default React.memo(ValueStrip);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, useMemo } from \"react\";\nimport { translateSliderRoundness, DEFAULT_ROUNDNESS, calculateLinearPosition } from \"@cutoff/audio-ui-core\";\n\nexport type LinearCursorProps = {\n /** X coordinate of the center point of the strip */\n cx: number;\n /** Y coordinate of the center point of the strip */\n cy: number;\n /** Length of the strip */\n length: number;\n /** Rotation angle in degrees (0 = vertical, -90 or 270 = horizontal) */\n rotation?: number;\n /** Normalized value between 0 and 1, driving the cursor position */\n normalizedValue: number;\n /** Width of the cursor (x axis) */\n width: number;\n /** Aspect ratio of the cursor (numeric value, e.g., 1 = 1:1, 1.5 = 1.5:1). Height = width / aspectRatio. Ignored when imageHref is provided. */\n aspectRatio: number;\n /** Optional image URL to display as cursor. When provided, roundness and aspectRatio are ignored (image preserves its natural aspect ratio). */\n imageHref?: string;\n /** Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string). 1.0 = ellipse/circle, 0.0-1.0 (excl.) = rounded rectangle. Ignored when imageHref is provided. */\n roundness?: number | string;\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n};\n\n/**\n * A reusable SVG primitive that renders a cursor that slides along a linear strip.\n * The cursor position is driven by a normalized value (0.0 to 1.0).\n *\n * The cursor slides along a virtual bar centered at (cx, cy) with a defined length.\n * - normalizedValue 0.0: cursor at the bottom of the virtual bar\n * - normalizedValue 1.0: cursor at the top of the virtual bar\n *\n * The rotation prop represents the rotation of the virtual bar (not the cursor itself).\n * The cursor rotates around the strip center (cx, cy) along with the bar.\n *\n * The cursor can be rendered as:\n * - An image (via imageHref)\n * - An SVG shape (rectangle or ellipse based on roundness)\n *\n * This component is designed to be used inside an <svg> element.\n *\n * @param {number} cx - X coordinate of the center point of the strip\n * @param {number} cy - Y coordinate of the center point of the strip\n * @param {number} length - Length of the strip\n * @param {number} [rotation=0] - Rotation angle in degrees of the virtual bar (0 = vertical, -90 or 270 = horizontal)\n * @param {number} normalizedValue - Normalized value between 0 and 1, driving the cursor position\n * @param {number} width - Width of the cursor (x axis)\n * @param {number} aspectRatio - Aspect ratio of the cursor (numeric value, e.g., 1 = 1:1, 1.5 = 1.5:1). Height = width / aspectRatio. Ignored when imageHref is provided.\n * @param {string} [imageHref] - Optional image URL to display as cursor. When provided, roundness and aspectRatio are ignored (image preserves its natural aspect ratio).\n * @param {number | string} [roundness] - Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string). 1.0 = ellipse/circle. Ignored when imageHref is provided.\n * @param {string} [className] - CSS class name\n * @param {CSSProperties} [style] - Inline styles\n * @returns {JSX.Element} SVG element representing the cursor\n *\n * @example\n * ```tsx\n * // Vertical cursor (rectangle)\n * <LinearCursor cx={50} cy={150} length={260} normalizedValue={0.65} width={6} aspectRatio={1} />\n *\n * // Horizontal cursor (ellipse)\n * <LinearCursor cx={150} cy={50} length={260} rotation={-90} normalizedValue={0.75} width={8} aspectRatio={1} roundness={1} />\n *\n * // Image-based cursor (aspectRatio and roundness ignored, image preserves natural aspect ratio)\n * <LinearCursor cx={50} cy={150} length={260} normalizedValue={0.5} width={20} aspectRatio={1} imageHref=\"/cursor.png\" />\n * ```\n */\nfunction LinearCursor({\n cx,\n cy,\n length,\n rotation = 0,\n normalizedValue,\n width,\n aspectRatio,\n imageHref,\n roundness = DEFAULT_ROUNDNESS,\n className,\n style,\n}: LinearCursorProps) {\n // Calculate cursor position along the strip (no memo - normalizedValue changes frequently)\n const cursorY = calculateLinearPosition(cy, length, normalizedValue);\n\n // Calculate height from width and aspect ratio (only used for non-image cursors)\n const height = useMemo(() => {\n return width / aspectRatio;\n }, [width, aspectRatio]);\n\n // Calculate corner radius from roundness prop or CSS variable\n const cornerRadius = useMemo(() => {\n if (typeof roundness === \"string\") {\n // CSS variable - pass directly to SVG (browser will resolve it)\n return roundness;\n }\n // Numeric value - translate to legacy pixel range (0-20)\n const legacyRoundness = translateSliderRoundness(roundness ?? DEFAULT_ROUNDNESS);\n\n // If roundness is 0, use square corners\n if (legacyRoundness === 0) {\n return 0;\n }\n\n // Use the translated roundness value\n return legacyRoundness;\n }, [roundness]);\n\n // Determine if we should render an ellipse (roundness = 1.0) or rectangle\n const isEllipse = useMemo(() => {\n if (typeof roundness === \"string\") {\n return false; // CSS variables can't be compared, default to rectangle\n }\n // Check if normalized roundness is exactly 1.0 (ellipse/circle)\n return (roundness ?? DEFAULT_ROUNDNESS) >= 1.0;\n }, [roundness]);\n\n // Calculate position for the cursor (centered at cursorY, horizontally centered at cx)\n const cursorX = useMemo(() => {\n return cx - width / 2;\n }, [cx, width]);\n\n // Calculate Y position (no memo - depends on cursorY which changes frequently)\n const cursorYPos = cursorY - height / 2;\n\n // Build transform string for rotation\n // The rotation represents the rotation of the virtual bar (strip), so the cursor rotates\n // around the center of the strip (cx, cy), not around the cursor's current position\n const transform = useMemo(() => {\n if (rotation === 0) {\n return undefined;\n }\n // Negate rotation to match Radial components (positive = counter-clockwise)\n // Rotate around the strip center (cx, cy)\n return `rotate(${-rotation} ${cx} ${cy})`;\n }, [rotation, cx, cy]);\n\n // Render image if provided (aspectRatio and roundness are ignored - image preserves natural aspect ratio)\n if (imageHref) {\n // Use a square bounding box (width x width) and let preserveAspectRatio maintain the image's natural aspect ratio\n // The image will be centered and scaled to fit within the square while preserving its aspect ratio\n return (\n <image\n href={imageHref}\n x={cx - width / 2}\n y={cursorY - width / 2}\n width={width}\n height={width}\n transform={transform}\n className={className}\n style={style}\n preserveAspectRatio=\"xMidYMid meet\"\n />\n );\n }\n\n // Render ellipse if roundness = 1.0\n if (isEllipse) {\n return (\n <ellipse\n cx={cx}\n cy={cursorY}\n rx={width / 2}\n ry={height / 2}\n transform={transform}\n className={className}\n style={style}\n />\n );\n }\n\n // Render rounded rectangle\n return (\n <rect\n x={cursorX}\n y={cursorYPos}\n width={width}\n height={height}\n // Use 0 as fallback for older browsers that don't support CSS rx/ry\n rx={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n ry={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n transform={transform}\n className={className}\n style={{\n ...style,\n rx: cornerRadius,\n ry: cornerRadius,\n }}\n />\n );\n}\n\nexport default React.memo(LinearCursor);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport { translateSliderThickness, CSS_VARS } from \"@cutoff/audio-ui-core\";\nimport { DEFAULT_ROUNDNESS } from \"@cutoff/audio-ui-core\";\nimport { ControlComponent, SliderVariant, SliderCursorSize } from \"@/types\";\nimport LinearStrip from \"@/primitives/svg/LinearStrip\";\nimport ValueStrip from \"@/primitives/svg/ValueStrip\";\nimport LinearCursor from \"@/primitives/svg/LinearCursor\";\n\n// Constants for cursor size calculations (future use for Tick and Label sizes)\nconst TICK_TRACK_SHIFT = 30;\nconst LABEL_TRACK_SHIFT = 30;\n\n/**\n * Props for the SliderView component\n */\nexport type SliderViewProps = {\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Whether to start fill from center (bipolar mode) */\n bipolar?: boolean;\n /** Visual variant of the slider */\n variant?: SliderVariant;\n /** Orientation of the slider */\n orientation?: \"horizontal\" | \"vertical\";\n /** Thickness of the slider (normalized 0.0-1.0, maps to 1-50) */\n thickness?: number;\n /** Corner roundness (normalized 0.0-1.0, maps to 0-20, or CSS variable string) */\n roundness?: number | string;\n /** Color prop (kept for API compatibility, but colors are read from CSS variables) */\n color?: string;\n /** Cursor size option - determines which component's width is used for the cursor */\n cursorSize?: SliderCursorSize;\n /** Aspect ratio of the cursor */\n cursorAspectRatio?: number;\n /** Overrides the roundness factor of the cursor. Defaults to `roundness` */\n cursorRoundness?: number | string;\n /** Optional image URL to display as cursor */\n cursorImageHref?: string;\n /** Optional CSS class name for the cursor */\n cursorClassName?: string;\n /** Optional inline styles for the cursor */\n cursorStyle?: React.CSSProperties;\n /** Additional CSS class name */\n className?: string;\n /** Content to render (unused in default slider but required by generic props) */\n children?: React.ReactNode;\n};\n\n/**\n * Pure SVG presentation component for a slider.\n * Renders background and filled rectangles based on normalized value.\n *\n * Colors are read from CSS variables (`--audioui-primary-color`, `--audioui-primary-50`)\n * which are set by the parent Slider component based on the `color` prop.\n *\n * **Performance Optimizations:**\n * - Memoized calculations: thickness translation, layout coordinates, strip padding, cursor dimensions, style objects\n * - Constants moved outside component to avoid unnecessary dependency array entries\n * - Simple boolean checks and nullish coalescing are not memoized (minimal overhead)\n * - Style objects memoized to prevent unnecessary re-renders of child components\n *\n * @param {number} normalizedValue - Value between 0 and 1\n * @param {boolean} [bipolar=false] - Whether to fill from center (bipolar mode)\n * @param {SliderVariant} [variant=\"abstract\"] - Visual variant of the slider\n * @param {\"horizontal\" | \"vertical\"} [orientation=\"vertical\"] - Orientation of the slider\n * @param {number} [thickness=0.4] - Normalized thickness 0.0-1.0 (maps to 1-50)\n * @param {number | string} [roundness] - Normalized roundness 0.0-1.0 (maps to 0-20) or CSS variable string\n * @param {string} [color] - Color prop (kept for API compatibility, but not used - CSS variables are used instead)\n * @param {SliderCursorSize} [cursorSize] - Cursor size option (None, Strip, Track, Tick, Label)\n * @param {number} [cursorAspectRatio] - Aspect ratio of the cursor\n * @param {number | string} [cursorRoundness] - Overrides cursor roundness (defaults to roundness prop)\n * @param {string} [cursorImageHref] - Optional image URL for cursor\n * @param {string} [cursorClassName] - Optional CSS class name for cursor\n * @param {React.CSSProperties} [cursorStyle] - Optional inline styles for cursor\n * @param {string} [className] - Optional CSS class name\n * @returns {JSX.Element} SVG group element containing background and foreground strips\n */\nfunction SliderView({\n normalizedValue,\n bipolar = false,\n variant = \"abstract\",\n orientation = \"vertical\",\n thickness = 0.4,\n roundness = DEFAULT_ROUNDNESS,\n cursorSize,\n cursorAspectRatio,\n cursorRoundness,\n cursorImageHref,\n cursorClassName,\n cursorStyle,\n className,\n}: SliderViewProps): React.JSX.Element {\n // Translate normalized thickness to pixel range (1-50)\n const effectiveThickness = useMemo(() => {\n return translateSliderThickness(thickness);\n }, [thickness]);\n\n // Determine layout based on orientation\n const { cx, cy, rotation } = useMemo(() => {\n if (orientation === \"vertical\") {\n return {\n cx: 50,\n cy: 150,\n rotation: 0,\n };\n } else {\n return {\n cx: 150,\n cy: 50,\n rotation: -90,\n };\n }\n }, [orientation]);\n\n // Calculate strip padding for trackfull variant (thickness-dependent)\n const stripPadding = useMemo(() => {\n return variant === \"trackfull\" ? 25 * thickness + 5 : 0;\n }, [variant, thickness]);\n\n // Calculate cursor width based on cursorSize prop\n // If cursorSize is not provided, maintain backward compatibility (old behavior)\n const cursorWidth = useMemo(() => {\n // If cursorSize is explicitly \"None\", don't render cursor\n if (cursorSize === \"None\") {\n return undefined;\n }\n\n // If cursorSize is provided, use it to calculate width\n if (cursorSize) {\n switch (cursorSize) {\n case \"Strip\":\n // Width of the ValueStrip (if variant supports it)\n return variant !== \"stripless\" ? effectiveThickness - stripPadding : effectiveThickness;\n case \"Track\":\n // Width of the LinearStrip (track)\n return effectiveThickness;\n case \"Tick\":\n // Width of the TickStrip (future use - for now, use track width)\n return effectiveThickness + TICK_TRACK_SHIFT;\n case \"Label\":\n // Entire width of the Slider (future use - for now, use track width)\n return effectiveThickness + TICK_TRACK_SHIFT + LABEL_TRACK_SHIFT;\n default:\n return undefined;\n }\n }\n\n // Backward compatibility: if cursorSize is not provided, use old behavior\n // (render cursor when variant !== \"abstract\" with old width calculation)\n return variant !== \"abstract\" ? effectiveThickness - stripPadding : undefined;\n }, [cursorSize, effectiveThickness, stripPadding, variant]);\n\n // Determine if cursor should be rendered\n const shouldRenderCursor = cursorWidth !== undefined;\n\n // Determine cursor roundness (defaults to roundness prop if not specified)\n const effectiveCursorRoundness = cursorRoundness ?? roundness;\n\n // Calculate cursor height to adjust the length (prevent cursor from going beyond strip bounds)\n const cursorHeight = useMemo(() => {\n if (!shouldRenderCursor || cursorWidth === undefined) {\n return 0;\n }\n // For images, LinearCursor uses a square bounding box (width x width)\n // TODO: Calculate actual image aspect ratio: (cursorWidth / imageWidth) * imageHeight\n // This requires loading the image to get its dimensions, which would add async complexity\n // Current implementation uses cursorWidth as height (square) as a reasonable approximation\n if (cursorImageHref) {\n return cursorWidth;\n }\n // For non-image cursors, height = width / aspectRatio\n return cursorWidth / (cursorAspectRatio ?? 1);\n }, [shouldRenderCursor, cursorWidth, cursorAspectRatio, cursorImageHref]);\n\n // Calculate effective length for cursor (subtract cursor height to keep cursor within bounds)\n const cursorLength = useMemo(() => {\n return 260 - cursorHeight;\n }, [cursorHeight]);\n\n // Calculate ValueStrip length (reduced by padding for trackfull variant)\n const valueStripLength = useMemo(() => {\n return 260 - stripPadding;\n }, [stripPadding]);\n\n // Memoize styles to prevent unnecessary re-renders of child components\n const bgStripStyle = useMemo(\n () => ({\n fill: variant === \"abstract\" ? `var(${CSS_VARS.primary20})` : `var(${CSS_VARS.sliderTrackColor})`,\n }),\n [variant]\n );\n\n const valueStripStyle = useMemo(\n () => ({\n fill: `var(${CSS_VARS.sliderStripColor})`,\n }),\n []\n );\n\n const cursorStyleMemo = useMemo(\n () => ({\n fill: `var(${CSS_VARS.sliderCursorColor})`,\n stroke: `var(${CSS_VARS.sliderCursorBorderColor})`,\n strokeWidth: `var(${CSS_VARS.sliderCursorBorderWidth})`,\n ...cursorStyle,\n }),\n [cursorStyle]\n );\n\n return (\n <g className={className}>\n {/* Background Strip */}\n <LinearStrip\n cx={cx}\n cy={cy}\n length={260}\n thickness={effectiveThickness}\n rotation={rotation}\n roundness={roundness}\n style={bgStripStyle}\n />\n\n {/* Foreground Value Strip */}\n {variant !== \"stripless\" ? (\n <ValueStrip\n cx={cx}\n cy={cy}\n length={valueStripLength}\n thickness={effectiveThickness - stripPadding}\n rotation={rotation}\n roundness={roundness}\n normalizedValue={normalizedValue}\n bipolar={bipolar}\n style={valueStripStyle}\n />\n ) : undefined}\n\n {/* Cursor */}\n {shouldRenderCursor && cursorWidth !== undefined ? (\n <LinearCursor\n cx={cx}\n cy={cy}\n length={cursorLength}\n rotation={rotation}\n normalizedValue={normalizedValue}\n width={cursorWidth}\n aspectRatio={cursorAspectRatio ?? 1}\n roundness={effectiveCursorRoundness}\n imageHref={cursorImageHref}\n className={cursorClassName}\n style={cursorStyleMemo}\n />\n ) : undefined}\n </g>\n );\n}\n\nconst SLIDER_VIEWBOX = {\n vertical: {\n width: 100,\n height: 300,\n },\n horizontal: {\n width: 300,\n height: 100,\n },\n} as const;\n\n/**\n * ViewBox dimensions for the SliderView component.\n * The parent component should use these values when setting up the SVG container.\n * Dimensions vary based on orientation.\n */\nSliderView.viewBox = SLIDER_VIEWBOX;\n\n/**\n * Props for specialized slider views (without normalizedValue, children, className, style, orientation)\n */\ntype SpecializedSliderProps = Omit<\n SliderViewProps,\n \"normalizedValue\" | \"children\" | \"className\" | \"style\" | \"orientation\"\n>;\n\n/**\n * Specialized Vertical Slider for Generic Control System\n */\nfunction VerticalSliderViewComponent(\n props: SpecializedSliderProps & {\n normalizedValue: number;\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n }\n) {\n return <SliderView {...props} orientation=\"vertical\" />;\n}\n\nconst VerticalSliderViewMemo = React.memo(VerticalSliderViewComponent);\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(VerticalSliderViewMemo as any).viewBox = SLIDER_VIEWBOX.vertical;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(VerticalSliderViewMemo as any).labelHeightUnits = 40;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(VerticalSliderViewMemo as any).interaction = {\n mode: \"both\" as const,\n direction: \"vertical\" as const,\n};\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(VerticalSliderViewMemo as any).title = \"Vertical Slider\";\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(VerticalSliderViewMemo as any).description = \"A vertical fader control with linear fill indicator\";\n\n// Cast the memoized components to the ControlComponent type which includes the static properties\nconst VerticalSliderView = VerticalSliderViewMemo as unknown as ControlComponent<SpecializedSliderProps>;\n\n/**\n * Specialized Horizontal Slider for Generic Control System\n */\nfunction HorizontalSliderViewComponent(\n props: SpecializedSliderProps & {\n normalizedValue: number;\n children?: React.ReactNode;\n className?: string;\n style?: React.CSSProperties;\n }\n) {\n return <SliderView {...props} orientation=\"horizontal\" />;\n}\n\nconst HorizontalSliderViewMemo = React.memo(HorizontalSliderViewComponent);\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(HorizontalSliderViewMemo as any).viewBox = SLIDER_VIEWBOX.horizontal;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(HorizontalSliderViewMemo as any).labelHeightUnits = 40;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(HorizontalSliderViewMemo as any).interaction = {\n mode: \"both\" as const,\n direction: \"horizontal\" as const,\n};\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(HorizontalSliderViewMemo as any).title = \"Horizontal Slider\";\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(HorizontalSliderViewMemo as any).description = \"A horizontal fader control with linear fill indicator\";\n\nconst HorizontalSliderView = HorizontalSliderViewMemo as unknown as ControlComponent<SpecializedSliderProps>;\n\nexport { VerticalSliderView, HorizontalSliderView };\nexport default React.memo(SliderView);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport { VerticalSliderView, HorizontalSliderView } from \"./SliderView\";\nimport ContinuousControl from \"@/primitives/controls/ContinuousControl\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport {\n AdaptiveBoxProps,\n AdaptiveSizeProps,\n ContinuousControlProps,\n ThemableProps,\n SliderVariant,\n SliderCursorSize,\n ValueLabelMode,\n} from \"@/types\";\nimport { clampNormalized } from \"@cutoff/audio-ui-core\";\nimport { useThemableProps } from \"@/hooks/useThemableProps\";\n\n/**\n * Props for the Slider component (built-in control with theming support)\n */\nexport type SliderProps = ContinuousControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n ThemableProps & {\n /** Visual variant of the slider\n * @default \"abstract\"\n */\n variant?: SliderVariant;\n /** Orientation of the slider\n * @default 'vertical' */\n orientation?: \"horizontal\" | \"vertical\";\n /** Thickness of the slider (normalized 0.0-1.0, maps to 1-50)\n * @default 0.4 */\n thickness?: number;\n /**\n * Controls how the label and value are displayed.\n * - \"labelOnly\": Always shows the label (default)\n * - \"valueOnly\": Always shows the value\n * - \"interactive\": Shows label normally, but temporarily swaps to value during interaction\n * @default \"labelOnly\"\n */\n valueAsLabel?: ValueLabelMode;\n /** Cursor size option - determines which component's width is used for the cursor */\n cursorSize?: SliderCursorSize;\n /** Aspect ratio of the cursor */\n cursorAspectRatio?: number;\n /** Overrides the roundness factor of the cursor. Defaults to `roundness` */\n cursorRoundness?: number | string;\n /** Optional image URL to display as cursor */\n cursorImageHref?: string;\n /** Optional CSS class name for the cursor */\n cursorClassName?: string;\n /** Optional inline styles for the cursor */\n cursorStyle?: React.CSSProperties;\n };\n\n/**\n * A slider component for audio applications.\n * Supports both horizontal and vertical orientations with continuous value adjustment.\n *\n * Features:\n * - Multiple visual variants (abstract, trackless, trackfull, stripless)\n * - Configurable orientation (horizontal or vertical)\n * - Bipolar mode support (centered at zero, grows in both directions)\n * - Customizable thickness and roundness\n * - Full theming support via CSS variables\n * - Adaptive sizing or fixed size variants\n * - Supports drag, wheel, and keyboard interactions\n * - Custom value formatting\n *\n * @example\n * ```tsx\n * // Basic vertical slider\n * <Slider value={50} min={0} max={100} onChange={(e) => setValue(e.value)} />\n *\n * // Horizontal bipolar slider (pan control)\n * <Slider\n * orientation=\"horizontal\"\n * bipolar\n * value={0}\n * min={-100}\n * max={100}\n * label=\"Pan\"\n * />\n *\n * // With parameter model\n * <Slider\n * parameter={volumeParam}\n * value={volume}\n * onChange={handleVolumeChange}\n * />\n * ```\n */\nfunction Slider({\n orientation = \"vertical\",\n min,\n max,\n step,\n bipolar = false,\n value,\n onChange,\n valueFormatter,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n labelOverflow,\n labelHeightUnits,\n color,\n roundness,\n variant = \"abstract\",\n thickness = 0.5,\n parameter,\n unit,\n scale,\n paramId,\n interactionMode,\n interactionDirection,\n interactionSensitivity,\n valueAsLabel = \"labelOnly\",\n midiResolution,\n defaultValue,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n cursorSize,\n cursorAspectRatio,\n cursorRoundness,\n cursorImageHref,\n cursorClassName,\n cursorStyle,\n}: SliderProps) {\n const { style: themableStyle } = useThemableProps({\n color,\n roundness,\n style,\n });\n\n // Clamp thickness if provided\n const clampedThickness = thickness !== undefined ? clampNormalized(thickness) : undefined;\n\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"slider\", orientation);\n\n const mergedClassName = classNames(sizeClassName, className);\n\n // Select view component based on orientation (different SVG implementations for optimal rendering)\n const ViewComponent = orientation === \"vertical\" ? VerticalSliderView : HorizontalSliderView;\n\n return (\n <ContinuousControl\n min={min}\n max={max}\n step={step}\n bipolar={bipolar}\n value={value}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n labelOverflow={labelOverflow}\n labelHeightUnits={labelHeightUnits}\n className={mergedClassName}\n style={{ ...sizeStyle, ...themableStyle }}\n onChange={onChange}\n paramId={paramId}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n parameter={parameter}\n unit={unit}\n scale={scale}\n midiResolution={midiResolution}\n defaultValue={defaultValue}\n interactionMode={interactionMode}\n interactionDirection={interactionDirection}\n interactionSensitivity={interactionSensitivity}\n valueFormatter={valueFormatter}\n valueAsLabel={valueAsLabel}\n view={ViewComponent}\n viewProps={{\n color: color ?? \"var(--audioui-primary-color)\",\n variant: variant,\n thickness: clampedThickness,\n roundness: roundness ?? \"var(--audioui-roundness-slider)\",\n bipolar: bipolar,\n cursorSize: cursorSize,\n cursorAspectRatio: cursorAspectRatio,\n cursorRoundness: cursorRoundness,\n cursorImageHref: cursorImageHref,\n cursorClassName: cursorClassName,\n cursorStyle: cursorStyle,\n }}\n />\n );\n}\n\nexport default React.memo(Slider);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties } from \"react\";\n\nexport type FilmstripImageProps = {\n /** X coordinate of the top-left corner (default: 0) */\n x?: number;\n /** Y coordinate of the top-left corner (default: 0) */\n y?: number;\n /** Width of a SINGLE frame */\n frameWidth: number;\n /** Height of a SINGLE frame */\n frameHeight: number;\n /** Total number of frames in the strip */\n frameCount: number;\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** URL to the sprite sheet/filmstrip image */\n imageHref: string;\n /** Orientation of the strip (default: \"vertical\") */\n orientation?: \"vertical\" | \"horizontal\";\n /** Optional frame rotation in degrees (default: 0) */\n frameRotation?: number;\n /**\n * If true, inverts the normalized value (0.0 -> 1.0 and 1.0 -> 0.0).\n * Useful for filmstrips where frame 0 represents \"on\" and frame 1 represents \"off\".\n * @default false\n */\n invertValue?: boolean;\n /** Additional CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n};\n\n/**\n * A primitive component that displays a single frame from a sprite sheet (filmstrip)\n * based on a normalized value.\n *\n * This is the most performant way to animate complex knobs and UI elements in audio applications.\n * It uses a nested SVG with a shifted viewBox to \"scrub\" through the filmstrip, which is hardware accelerated.\n *\n * The filmstrip image should contain all frames stacked vertically (default) or horizontally.\n *\n * @param {FilmstripImageProps} props - Component props\n * @param {number} [props.x=0] - X coordinate of the top-left corner\n * @param {number} [props.y=0] - Y coordinate of the top-left corner\n * @param {number} props.frameWidth - Width of a single frame in the filmstrip\n * @param {number} props.frameHeight - Height of a single frame in the filmstrip\n * @param {number} props.frameCount - Total number of frames in the strip\n * @param {number} props.normalizedValue - Normalized value between 0 and 1 (determines which frame to display)\n * @param {string} props.imageHref - URL to the sprite sheet/filmstrip image\n * @param {\"vertical\" | \"horizontal\"} [props.orientation=\"vertical\"] - Orientation of the strip\n * @param {number} [props.frameRotation=0] - Optional frame rotation in degrees\n * @param {boolean} [props.invertValue=false] - If true, inverts the normalized value (0.0 -> 1.0 and 1.0 -> 0.0)\n * @param {string} [props.className] - Additional CSS class name\n * @param {CSSProperties} [props.style] - Inline styles\n * @returns {JSX.Element} SVG group element containing the filmstrip frame\n */\nfunction FilmstripImage({\n x = 0,\n y = 0,\n frameWidth,\n frameHeight,\n frameCount,\n normalizedValue,\n imageHref,\n orientation = \"vertical\",\n frameRotation = 0,\n invertValue = false,\n className,\n style,\n}: FilmstripImageProps) {\n // Invert value if requested\n const effectiveValue = invertValue ? 1 - normalizedValue : normalizedValue;\n // Clamp value between 0 and 1\n const clampedValue = Math.max(0, Math.min(1, effectiveValue));\n\n // Calculate frame index (0 to frameCount - 1)\n const frameIndex = Math.round(clampedValue * (frameCount - 1));\n\n // Calculate total dimensions of the strip\n const totalWidth = orientation === \"horizontal\" ? frameWidth * frameCount : frameWidth;\n const totalHeight = orientation === \"vertical\" ? frameHeight * frameCount : frameHeight;\n\n // Calculate viewBox coordinates for the current frame\n const viewBoxX = orientation === \"horizontal\" ? frameIndex * frameWidth : 0;\n const viewBoxY = orientation === \"vertical\" ? frameIndex * frameHeight : 0;\n\n // Calculate center point for rotation\n const centerX = x + frameWidth / 2;\n const centerY = y + frameHeight / 2;\n\n return (\n <g className={className} style={style} transform={`rotate(${frameRotation}, ${centerX}, ${centerY})`}>\n <svg\n x={x}\n y={y}\n width={frameWidth}\n height={frameHeight}\n viewBox={`0 0 ${frameWidth} ${frameHeight}`}\n preserveAspectRatio=\"none\"\n style={{ overflow: \"hidden\" }}\n >\n <image\n href={imageHref}\n width={totalWidth}\n height={totalHeight}\n preserveAspectRatio=\"none\"\n style={{\n transform: `translate(${-viewBoxX}px, ${-viewBoxY}px)`,\n willChange: \"transform\",\n }}\n />\n </svg>\n </g>\n );\n}\n\nexport default React.memo(FilmstripImage);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport { ControlComponent } from \"@/types\";\nimport FilmstripImage from \"@/primitives/svg/FilmstripImage\";\n\n/**\n * Props specific to filmstrip visualization\n */\nexport type FilmstripViewProps = {\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Width of a SINGLE frame */\n frameWidth: number;\n /** Height of a SINGLE frame */\n frameHeight: number;\n /** Total number of frames in the strip */\n frameCount: number;\n /** URL to the sprite sheet/filmstrip image */\n imageHref: string;\n /** Orientation of the strip (default: \"vertical\") */\n orientation?: \"vertical\" | \"horizontal\";\n /** Optional frame rotation in degrees (default: 0) */\n frameRotation?: number;\n /**\n * If true, inverts the normalized value (0.0 -> 1.0 and 1.0 -> 0.0).\n * Useful for filmstrips where frame 0 represents \"on\" and frame 1 represents \"off\".\n * @default false\n */\n invertValue?: boolean;\n /** Additional CSS class name */\n className?: string;\n /** Content to render (unused in filmstrip but required by generic props) */\n children?: React.ReactNode;\n};\n\n/**\n * Pure SVG presentation component for a filmstrip control.\n * Renders a single frame from a sprite sheet based on normalized value.\n *\n * @param {FilmstripViewProps} props - Component props\n * @param {number} props.normalizedValue - Value between 0 and 1\n * @param {number} props.frameWidth - Width of a single frame in the filmstrip\n * @param {number} props.frameHeight - Height of a single frame in the filmstrip\n * @param {number} props.frameCount - Total number of frames in the strip\n * @param {string} props.imageHref - URL to the sprite sheet/filmstrip image\n * @param {\"vertical\" | \"horizontal\"} [props.orientation=\"vertical\"] - Orientation of the strip\n * @param {number} [props.frameRotation=0] - Optional frame rotation in degrees\n * @param {boolean} [props.invertValue=false] - If true, inverts the normalized value (0.0 -> 1.0 and 1.0 -> 0.0)\n * @param {string} [props.className] - Optional CSS class\n * @returns {JSX.Element} SVG group element containing the filmstrip frame (FilmstripImage component)\n */\nfunction FilmstripView({\n normalizedValue,\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation = \"vertical\",\n frameRotation = 0,\n invertValue = false,\n className,\n}: FilmstripViewProps): React.JSX.Element {\n return (\n <FilmstripImage\n x={0}\n y={0}\n frameWidth={frameWidth}\n frameHeight={frameHeight}\n frameCount={frameCount}\n normalizedValue={normalizedValue}\n imageHref={imageHref}\n orientation={orientation}\n frameRotation={frameRotation}\n invertValue={invertValue}\n className={className}\n />\n );\n}\n\nconst FilmstripViewMemo = React.memo(FilmstripView);\n\n// Attach static properties required by ControlComponent contract\n// Note: viewBox dimensions are dynamic based on frameWidth/frameHeight\n// We use a default that can be overridden by the parent component\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(FilmstripViewMemo as any).viewBox = { width: 100, height: 100 };\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(FilmstripViewMemo as any).labelHeightUnits = 20;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(FilmstripViewMemo as any).interaction = {\n mode: \"both\" as const,\n direction: \"vertical\" as const,\n};\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(FilmstripViewMemo as any).title = \"Filmstrip\";\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(FilmstripViewMemo as any).description = \"A filmstrip control that displays frames from a sprite sheet\";\n\n// Props for the ControlComponent (excluding normalizedValue, className, style, children)\ntype FilmstripViewComponentProps = Omit<FilmstripViewProps, \"normalizedValue\" | \"className\" | \"style\" | \"children\">;\n\nexport default FilmstripViewMemo as unknown as ControlComponent<FilmstripViewComponentProps>;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport ContinuousControl from \"@/primitives/controls/ContinuousControl\";\nimport FilmstripView from \"./FilmstripView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, ContinuousControlProps, ValueLabelMode } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\n\n/**\n * Props specific to filmstrip controls\n */\nexport type FilmstripProps = {\n /** Width of a SINGLE frame in the filmstrip */\n frameWidth: number;\n /** Height of a SINGLE frame in the filmstrip */\n frameHeight: number;\n /** Total number of frames in the strip */\n frameCount: number;\n /** URL to the sprite sheet/filmstrip image */\n imageHref: string;\n /** Orientation of the strip (default: \"vertical\") */\n orientation?: \"vertical\" | \"horizontal\";\n /** Optional frame rotation in degrees (default: 0) */\n frameRotation?: number;\n};\n\n/**\n * Props for the FilmStripContinuousControl component\n */\nexport type FilmStripContinuousControlProps = ContinuousControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n FilmstripProps & {\n /**\n * Controls how the label and value are displayed.\n * - \"labelOnly\": Always shows the label (default)\n * - \"valueOnly\": Always shows the value\n * - \"interactive\": Shows label normally, but temporarily swaps to value during interaction\n * @default \"labelOnly\"\n */\n valueAsLabel?: ValueLabelMode;\n /**\n * If true, inverts the normalized value (0.0 -> 1.0 and 1.0 -> 0.0).\n * Useful for filmstrips where frame 0 represents the maximum value and frame N represents the minimum value.\n * @default false\n */\n invertValue?: boolean;\n };\n\n/**\n * A continuous control that displays frames from a filmstrip sprite sheet.\n *\n * This component supports the widely-used current industry standard for control representation:\n * bitmap sprite sheets (filmstrips). While bitmap-based visualization is more constrained\n * than SVG (no dynamic theming, fixed visual appearance), this component provides full\n * access to all library features:\n *\n * - Complete layout system: AdaptiveBox with all sizing modes, label positioning, alignment\n * - Full parameter model: AudioParameter with min/max ranges, scaling functions, units, formatting\n * - Complete interaction system: Drag, wheel, and keyboard with configurable sensitivity and modes\n * - Full accessibility: ARIA attributes, focus management, keyboard navigation\n *\n * The frame displayed is determined by the normalized value (0-1 maps to frame 0 to frameCount-1).\n *\n * Supports two modes of operation:\n * 1. Parameter model mode: Provide `parameter` (ContinuousParameter) - all range/label info comes from the model\n * 2. Ad-hoc mode: Provide `min`, `max`, `step`, `label` directly as props\n *\n * Note: This component does NOT support themable props (color, roundness, thickness) as\n * visuals are entirely determined by the image content.\n *\n * @param props - Component props\n * @returns Rendered FilmStripContinuousControl component\n *\n * @example\n * ```tsx\n * <FilmStripContinuousControl\n * value={50}\n * min={0}\n * max={100}\n * label=\"Volume\"\n * frameWidth={100}\n * frameHeight={100}\n * frameCount={64}\n * imageHref=\"/knob-frames.png\"\n * />\n * ```\n */\nfunction FilmStripContinuousControl({\n min,\n max,\n step,\n bipolar = false,\n value,\n onChange,\n valueFormatter,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation = \"vertical\",\n frameRotation = 0,\n parameter,\n unit,\n scale,\n paramId,\n interactionMode,\n interactionDirection,\n interactionSensitivity,\n valueAsLabel = \"labelOnly\",\n midiResolution,\n defaultValue,\n invertValue = false,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: FilmStripContinuousControlProps) {\n const { sizeClassName, sizeStyle: adaptiveSizeStyle } = useAdaptiveSize(adaptiveSize, size, \"knob\");\n\n return (\n <ContinuousControl\n min={min}\n max={max}\n step={step}\n bipolar={bipolar}\n value={value}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n className={classNames(sizeClassName, className)}\n style={{ ...adaptiveSizeStyle, ...style }}\n onChange={onChange}\n paramId={paramId}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n parameter={parameter}\n unit={unit}\n scale={scale}\n midiResolution={midiResolution}\n defaultValue={defaultValue}\n interactionMode={interactionMode}\n interactionDirection={interactionDirection}\n interactionSensitivity={interactionSensitivity}\n valueFormatter={valueFormatter}\n valueAsLabel={valueAsLabel}\n viewBoxWidthUnits={frameWidth}\n viewBoxHeightUnits={frameHeight}\n view={FilmstripView}\n viewProps={{\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation,\n frameRotation,\n invertValue,\n }}\n />\n );\n}\n\nexport default React.memo(FilmStripContinuousControl);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport DiscreteControl from \"@/primitives/controls/DiscreteControl\";\nimport FilmstripView from \"./FilmstripView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, DiscreteControlProps } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { FilmstripProps } from \"./FilmStripContinuousControl\";\n\n/**\n * Props for the FilmStripDiscreteControl component\n */\nexport type FilmStripDiscreteControlProps = DiscreteControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n FilmstripProps;\n\n/**\n * A discrete control that displays frames from a filmstrip sprite sheet.\n *\n * This component supports the widely-used current industry standard for control representation:\n * bitmap sprite sheets (filmstrips). While bitmap-based visualization is more constrained\n * than SVG (no dynamic theming, fixed visual appearance), this component provides full\n * access to all library features:\n *\n * - Complete layout system: AdaptiveBox with all sizing modes, label positioning, alignment\n * - Full parameter model: DiscreteParameter with options, labels, value mapping\n * - Complete interaction system: Click and keyboard interactions for cycling/stepping\n * - Full accessibility: ARIA attributes, focus management, keyboard navigation\n *\n * The frame displayed is determined by mapping the current value to a frame index.\n *\n * Supports three modes of operation:\n * 1. Ad-Hoc Mode (Children only): Model inferred from OptionView children.\n * 2. Strict Mode (Parameter only): Model provided via parameter prop. View via renderOption.\n * 3. Hybrid Mode (Parameter + Children): Model from parameter, View from children (matched by value).\n *\n * Note: This component does NOT support themable props (color, roundness, thickness) as\n * visuals are entirely determined by the image content.\n *\n * @param props - Component props\n * @returns Rendered FilmStripDiscreteControl component\n *\n * @example\n * ```tsx\n * <FilmStripDiscreteControl\n * value=\"sine\"\n * onChange={(e) => setValue(e.value)}\n * frameWidth={100}\n * frameHeight={100}\n * frameCount={4}\n * imageHref=\"/waveform-frames.png\"\n * >\n * <Option value=\"sine\">Sine</Option>\n * <Option value=\"square\">Square</Option>\n * <Option value=\"triangle\">Triangle</Option>\n * <Option value=\"sawtooth\">Sawtooth</Option>\n * </FilmStripDiscreteControl>\n * ```\n */\nfunction FilmStripDiscreteControl({\n value,\n defaultValue,\n onChange,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation = \"vertical\",\n frameRotation = 0,\n parameter,\n paramId,\n options,\n midiResolution,\n midiMapping,\n children,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: FilmStripDiscreteControlProps) {\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"knob\");\n\n return (\n <DiscreteControl\n value={value}\n defaultValue={defaultValue}\n onChange={onChange}\n label={label}\n paramId={paramId}\n parameter={parameter}\n options={options}\n midiResolution={midiResolution}\n midiMapping={midiMapping}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n className={classNames(sizeClassName, className)}\n style={{ ...sizeStyle, ...style }}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n viewBoxWidthUnits={frameWidth}\n viewBoxHeightUnits={frameHeight}\n view={FilmstripView}\n viewProps={{\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation,\n frameRotation,\n }}\n >\n {children}\n </DiscreteControl>\n );\n}\n\nexport default React.memo(FilmStripDiscreteControl);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport BooleanControl from \"@/primitives/controls/BooleanControl\";\nimport FilmstripView from \"./FilmstripView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, BooleanControlProps } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { FilmstripProps } from \"./FilmStripContinuousControl\";\n\n/**\n * Props for the FilmStripBooleanControl component\n */\nexport type FilmStripBooleanControlProps = BooleanControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n FilmstripProps & {\n /**\n * If true, inverts the normalized value passed to the view (0.0 -> 1.0 and 1.0 -> 0.0).\n * Useful for filmstrips where frame 0 represents \"on\" and frame 1 represents \"off\".\n * @default false\n */\n invertValue?: boolean;\n };\n\n/**\n * A boolean control that displays frames from a filmstrip sprite sheet.\n *\n * This component supports the widely-used current industry standard for control representation:\n * bitmap sprite sheets (filmstrips). While bitmap-based visualization is more constrained\n * than SVG (no dynamic theming, fixed visual appearance), this component provides full\n * access to all library features:\n *\n * - Complete layout system: AdaptiveBox with all sizing modes, label positioning, alignment\n * - Full parameter model: BooleanParameter with latch/momentary modes\n * - Complete interaction system: Click, drag-in/drag-out, and keyboard interactions\n * - Full accessibility: ARIA attributes, focus management, keyboard navigation\n *\n * Typically uses 2 frames: frame 0 for false/off, frame 1 for true/on.\n *\n * Supports two modes of operation:\n * 1. Strict Mode (Parameter only): Model provided via parameter prop.\n * 2. Ad-Hoc Mode (Props only): Model created from individual props (label, latch, etc.).\n *\n * Note: This component does NOT support themable props (color, roundness, thickness) as\n * visuals are entirely determined by the image content.\n *\n * @param props - Component props\n * @returns Rendered FilmStripBooleanControl component\n *\n * @example\n * ```tsx\n * <FilmStripBooleanControl\n * value={isOn}\n * onChange={(e) => setIsOn(e.value)}\n * label=\"Power\"\n * latch={true}\n * frameWidth={100}\n * frameHeight={100}\n * frameCount={2}\n * imageHref=\"/button-frames.png\"\n * />\n * ```\n */\nfunction FilmStripBooleanControl({\n latch = false,\n value = false,\n onChange,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation = \"vertical\",\n frameRotation = 0,\n invertValue = false,\n parameter,\n paramId,\n midiResolution,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: FilmStripBooleanControlProps) {\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"button\");\n\n return (\n <BooleanControl\n value={value}\n onChange={onChange}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n className={classNames(sizeClassName, className)}\n style={{ ...sizeStyle, ...style }}\n parameter={parameter}\n paramId={paramId}\n latch={latch}\n midiResolution={midiResolution}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n viewBoxWidthUnits={frameWidth}\n viewBoxHeightUnits={frameHeight}\n view={FilmstripView}\n viewProps={{\n frameWidth,\n frameHeight,\n frameCount,\n imageHref,\n orientation,\n frameRotation,\n invertValue,\n }}\n />\n );\n}\n\nexport default React.memo(FilmStripBooleanControl);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport { ControlComponent } from \"@/types\";\nimport RotaryImage from \"@/primitives/svg/RotaryImage\";\n\n/**\n * Props specific to rotary image visualization\n */\nexport type ImageKnobViewProps = {\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Width of the viewBox (determines viewBox width) */\n frameWidth: number;\n /** Height of the viewBox (determines viewBox height) */\n frameHeight: number;\n /** URL to the image to rotate */\n imageHref: string;\n /** Optional rotation angle offset in degrees (default: 0) */\n rotation?: number;\n /** Openness of the arc in degrees (value between 0-360º; 0º: closed; 90º: 3/4 open; 180º: half-circle;)\n * @default 90\n */\n openness?: number;\n /** Whether to start the arc from center (bipolar mode)\n * @default false\n */\n bipolar?: boolean;\n /** Optional number of discrete positions. When defined, the value will snap to the nearest position.\n * Used by discrete controls to snap to option positions.\n */\n positions?: number;\n /** Additional CSS class name */\n className?: string;\n /** Content to render (unused in rotary image but required by generic props) */\n children?: React.ReactNode;\n};\n\n/**\n * Pure SVG presentation component for a rotary image control.\n * Renders a rotating image based on normalized value.\n *\n * @param {ImageKnobViewProps} props - Component props\n * @param {number} props.normalizedValue - Value between 0 and 1\n * @param {number} props.frameWidth - Width of the viewBox\n * @param {number} props.frameHeight - Height of the viewBox\n * @param {string} props.imageHref - URL to the image to rotate\n * @param {number} [props.rotation=0] - Optional rotation angle offset in degrees\n * @param {number} [props.openness=90] - Openness of the arc in degrees\n * @param {boolean} [props.bipolar=false] - Whether to start the arc from center (bipolar mode)\n * @param {number} [props.positions] - Optional number of discrete positions for snapping\n * @param {string} [props.className] - Optional CSS class\n * @returns {JSX.Element} SVG group element containing the rotary image (RotaryImage component)\n */\nfunction ImageKnobView({\n normalizedValue,\n frameWidth,\n frameHeight,\n imageHref,\n rotation = 0,\n openness = 90,\n bipolar = false,\n positions,\n className,\n}: ImageKnobViewProps): React.JSX.Element {\n // Center point of the viewBox\n const cx = frameWidth / 2;\n const cy = frameHeight / 2;\n // Calculate radius from frame dimensions\n const radius = Math.min(frameWidth, frameHeight) / 2;\n\n return (\n <RotaryImage\n cx={cx}\n cy={cy}\n radius={radius}\n normalizedValue={normalizedValue}\n imageHref={imageHref}\n rotation={rotation}\n openness={openness}\n bipolar={bipolar}\n positions={positions}\n className={className}\n />\n );\n}\n\nconst ImageKnobViewMemo = React.memo(ImageKnobView);\n\n// Attach static properties required by ControlComponent contract\n// Note: viewBox dimensions are dynamic based on frameWidth/frameHeight\n// We use a default that can be overridden by the parent component\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageKnobViewMemo as any).viewBox = { width: 100, height: 100 };\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageKnobViewMemo as any).labelHeightUnits = 20;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageKnobViewMemo as any).interaction = {\n mode: \"both\" as const,\n direction: \"circular\" as const,\n};\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageKnobViewMemo as any).title = \"Rotary Image\";\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageKnobViewMemo as any).description = \"A rotary control that rotates an image based on value\";\n\n// Props for the ControlComponent (excluding normalizedValue, className, style, children)\ntype ImageKnobViewComponentProps = Omit<ImageKnobViewProps, \"normalizedValue\" | \"className\" | \"style\" | \"children\">;\n\nexport default ImageKnobViewMemo as unknown as ControlComponent<ImageKnobViewComponentProps>;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport ContinuousControl from \"@/primitives/controls/ContinuousControl\";\nimport ImageKnobView from \"./ImageKnobView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, ContinuousControlProps, ValueLabelMode } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\n\n/**\n * Props specific to rotary image controls\n */\nexport type RotaryImageProps = {\n /** Width of the viewBox (determines viewBox width) */\n frameWidth: number;\n /** Height of the viewBox (determines viewBox height) */\n frameHeight: number;\n /** URL to the image to rotate */\n imageHref: string;\n /** Optional rotation angle offset in degrees (default: 0) */\n rotation?: number;\n /** Openness of the arc in degrees (value between 0-360º; 0º: closed; 90º: 3/4 open; 180º: half-circle;)\n * @default 90\n */\n openness?: number;\n};\n\n/**\n * Props for the ImageKnob component\n */\nexport type ImageKnobProps = ContinuousControlProps &\n AdaptiveSizeProps &\n AdaptiveBoxProps &\n RotaryImageProps & {\n /**\n * Controls how the label and value are displayed.\n * - \"labelOnly\": Always shows the label (default)\n * - \"valueOnly\": Always shows the value\n * - \"interactive\": Shows label normally, but temporarily swaps to value during interaction\n * @default \"labelOnly\"\n */\n valueAsLabel?: ValueLabelMode;\n };\n\n/**\n * A continuous control that rotates an image based on a normalized value.\n *\n * This component provides full access to all library features:\n *\n * - Complete layout system: AdaptiveBox with all sizing modes, label positioning, alignment\n * - Full parameter model: AudioParameter with min/max ranges, scaling functions, units, formatting\n * - Complete interaction system: Drag, wheel, and keyboard with configurable sensitivity and modes\n * - Full accessibility: ARIA attributes, focus management, keyboard navigation\n *\n * The image rotation is determined by the normalized value (0-1 maps to rotation based on openness and rotation offset).\n *\n * Supports two modes of operation:\n * 1. Parameter model mode: Provide `parameter` (ContinuousParameter) - all range/label info comes from the model\n * 2. Ad-hoc mode: Provide `min`, `max`, `step`, `label` directly as props\n *\n * Note: This component does NOT support themable props (color, roundness, thickness) as\n * visuals are entirely determined by the image content.\n *\n * @param props - Component props\n * @returns Rendered ImageKnob component\n *\n * @example\n * ```tsx\n * <ImageKnob\n * value={50}\n * min={0}\n * max={100}\n * label=\"Volume\"\n * frameWidth={100}\n * frameHeight={100}\n * imageHref=\"/knob-image.png\"\n * openness={90}\n * rotation={0}\n * />\n * ```\n */\nfunction ImageKnob({\n min,\n max,\n step,\n bipolar = false,\n value,\n onChange,\n valueFormatter,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n frameWidth,\n frameHeight,\n imageHref,\n rotation = 0,\n openness = 90,\n parameter,\n unit,\n scale,\n paramId,\n interactionMode,\n interactionDirection,\n interactionSensitivity,\n valueAsLabel = \"labelOnly\",\n midiResolution,\n defaultValue,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: ImageKnobProps) {\n const { sizeClassName, sizeStyle: adaptiveSizeStyle } = useAdaptiveSize(adaptiveSize, size, \"knob\");\n\n return (\n <ContinuousControl\n min={min}\n max={max}\n step={step}\n bipolar={bipolar}\n value={value}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n className={classNames(sizeClassName, className)}\n style={{ ...adaptiveSizeStyle, ...style }}\n onChange={onChange}\n paramId={paramId}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n parameter={parameter}\n unit={unit}\n scale={scale}\n midiResolution={midiResolution}\n defaultValue={defaultValue}\n interactionMode={interactionMode}\n interactionDirection={interactionDirection}\n interactionSensitivity={interactionSensitivity}\n valueFormatter={valueFormatter}\n valueAsLabel={valueAsLabel}\n viewBoxWidthUnits={frameWidth}\n viewBoxHeightUnits={frameHeight}\n view={ImageKnobView}\n viewProps={{\n frameWidth,\n frameHeight,\n imageHref,\n rotation,\n openness,\n bipolar,\n }}\n />\n );\n}\n\nexport default React.memo(ImageKnob);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo } from \"react\";\nimport classNames from \"classnames\";\nimport DiscreteControl from \"@/primitives/controls/DiscreteControl\";\nimport ImageKnobView from \"./ImageKnobView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, DiscreteControlProps } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { useDiscreteParameterResolution } from \"@/hooks/useDiscreteParameterResolution\";\nimport { RotaryImageProps } from \"./ImageKnob\";\n\n/**\n * Props for the ImageRotarySwitch component\n */\nexport type ImageRotarySwitchProps = DiscreteControlProps & AdaptiveSizeProps & AdaptiveBoxProps & RotaryImageProps;\n\n/**\n * A discrete control that rotates an image based on discrete option values.\n *\n * This component provides full access to all library features:\n *\n * - Complete layout system: AdaptiveBox with all sizing modes, label positioning, alignment\n * - Full parameter model: DiscreteParameter with options, labels, value mapping\n * - Complete interaction system: Click and keyboard interactions for cycling/stepping\n * - Full accessibility: ARIA attributes, focus management, keyboard navigation\n *\n * The image rotation is determined by mapping the current value to a normalized position,\n * with snapping to discrete positions based on the number of options.\n *\n * Supports three modes of operation:\n * 1. Ad-Hoc Mode (Children only): Model inferred from OptionView children.\n * 2. Strict Mode (Parameter only): Model provided via parameter prop. View via renderOption.\n * 3. Hybrid Mode (Parameter + Children): Model from parameter, View from children (matched by value).\n *\n * Note: This component does NOT support themable props (color, roundness, thickness) as\n * visuals are entirely determined by the image content.\n *\n * @param props - Component props\n * @returns Rendered ImageRotarySwitch component\n *\n * @example\n * ```tsx\n * <ImageRotarySwitch\n * value=\"sine\"\n * onChange={(e) => setValue(e.value)}\n * frameWidth={100}\n * frameHeight={100}\n * imageHref=\"/waveform-knob.png\"\n * openness={90}\n * rotation={0}\n * >\n * <Option value=\"sine\">Sine</Option>\n * <Option value=\"square\">Square</Option>\n * <Option value=\"triangle\">Triangle</Option>\n * <Option value=\"sawtooth\">Sawtooth</Option>\n * </ImageRotarySwitch>\n * ```\n */\nfunction ImageRotarySwitch({\n value,\n defaultValue,\n onChange,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n frameWidth,\n frameHeight,\n imageHref,\n rotation = 0,\n openness = 90,\n parameter,\n paramId,\n options,\n midiResolution,\n midiMapping,\n children,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: ImageRotarySwitchProps) {\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"knob\");\n\n // Get derivedParameter to determine the number of options for positions\n const { derivedParameter } = useDiscreteParameterResolution({\n children,\n options,\n paramId,\n parameter,\n defaultValue,\n label,\n midiResolution,\n midiMapping,\n });\n\n // Calculate positions from the number of options\n const positions = useMemo(() => derivedParameter.options.length, [derivedParameter.options.length]);\n\n return (\n <DiscreteControl\n value={value}\n defaultValue={defaultValue}\n onChange={onChange}\n label={label}\n paramId={paramId}\n parameter={parameter}\n options={options}\n midiResolution={midiResolution}\n midiMapping={midiMapping}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n className={classNames(sizeClassName, className)}\n style={{ ...sizeStyle, ...style }}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n viewBoxWidthUnits={frameWidth}\n viewBoxHeightUnits={frameHeight}\n view={ImageKnobView}\n viewProps={{\n frameWidth,\n frameHeight,\n imageHref,\n rotation,\n openness,\n positions,\n }}\n >\n {children}\n </DiscreteControl>\n );\n}\n\nexport default React.memo(ImageRotarySwitch);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties } from \"react\";\n\nexport type ImageProps = {\n /** X coordinate of the top-left corner (default: 0) */\n x?: number;\n /** Y coordinate of the top-left corner (default: 0) */\n y?: number;\n /** Width of the image */\n width: number;\n /** Height of the image */\n height: number;\n /** Optional image URL to display */\n imageHref?: string;\n /** Optional SVG content to display (e.g., icon components) */\n children?: React.ReactNode;\n /** Optional SVG transform attribute */\n transform?: string;\n /** Additional CSS class name */\n className?: string;\n /**\n * Inline styles.\n * Supports `color` property for icon theming - icons using currentColor will inherit this value.\n */\n style?: CSSProperties;\n};\n\n/**\n * A primitive component that displays static content at rectangular coordinates.\n * The content is positioned at (x, y) with the specified width and height.\n *\n * This component can display an image (via imageHref) or arbitrary SVG content (via children).\n * It is designed for straightforward rectangular image placement without radial/centered positioning.\n *\n * Useful for displaying images or icons at specific positions and sizes.\n *\n * @param {ImageProps} props - Component props\n * @param {number} [props.x=0] - X coordinate of the top-left corner\n * @param {number} [props.y=0] - Y coordinate of the top-left corner\n * @param {number} props.width - Width of the image\n * @param {number} props.height - Height of the image\n * @param {string} [props.imageHref] - Optional image URL to display\n * @param {React.ReactNode} [props.children] - Optional SVG content to display\n * @param {string} [props.transform] - Optional SVG transform attribute\n * @param {string} [props.className] - Additional CSS class name\n * @param {CSSProperties} [props.style] - Inline styles\n * @returns {JSX.Element} SVG group element containing the image\n */\nfunction Image({ x = 0, y = 0, width, height, imageHref, children, transform, className, style }: ImageProps) {\n return (\n <g className={className} style={style} transform={transform}>\n {imageHref && (\n <image href={imageHref} x={x} y={y} width={width} height={height} preserveAspectRatio=\"xMidYMid meet\" />\n )}\n {children && (\n <svg\n x={x}\n y={y}\n width={width}\n height={height}\n viewBox={`0 0 ${width} ${height}`}\n style={{ overflow: \"visible\", ...(style?.color ? { color: style.color } : {}) }}\n >\n {children}\n </svg>\n )}\n </g>\n );\n}\n\nexport default React.memo(Image);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport { ControlComponent } from \"@/types\";\nimport Image from \"@/primitives/svg/Image\";\n\n/**\n * Props specific to image switch visualization\n */\nexport type ImageSwitchViewProps = {\n /** Normalized value between 0 and 1 */\n normalizedValue: number;\n /** Width of the viewBox (determines viewBox width) */\n frameWidth: number;\n /** Height of the viewBox (determines viewBox height) */\n frameHeight: number;\n /** URL to the image for false/off state */\n imageHrefFalse: string;\n /** URL to the image for true/on state */\n imageHrefTrue: string;\n /** Additional CSS class name */\n className?: string;\n /** Content to render (unused in image view but required by generic props) */\n children?: React.ReactNode;\n};\n\n/**\n * Pure SVG presentation component for an image switch control.\n * Renders one of two images based on normalized value (false = 0, true = 1).\n *\n * @param {ImageSwitchViewProps} props - Component props\n * @param {number} props.normalizedValue - Value between 0 and 1 (0 = false, 1 = true)\n * @param {number} props.frameWidth - Width of the viewBox\n * @param {number} props.frameHeight - Height of the viewBox\n * @param {string} props.imageHrefFalse - URL to the image for false/off state\n * @param {string} props.imageHrefTrue - URL to the image for true/on state\n * @param {string} [props.className] - Optional CSS class\n * @returns {JSX.Element} SVG image element\n */\nfunction ImageSwitchView({\n normalizedValue,\n frameWidth,\n frameHeight,\n imageHrefFalse,\n imageHrefTrue,\n className,\n}: ImageSwitchViewProps): React.JSX.Element {\n // Determine which image to show based on normalized value (threshold at 0.5)\n const imageHref = normalizedValue > 0.5 ? imageHrefTrue : imageHrefFalse;\n\n return <Image x={0} y={0} width={frameWidth} height={frameHeight} imageHref={imageHref} className={className} />;\n}\n\nconst ImageSwitchViewMemo = React.memo(ImageSwitchView);\n\n// Attach static properties required by ControlComponent contract\n// Note: viewBox dimensions are dynamic based on frameWidth/frameHeight\n// We use a default that can be overridden by the parent component\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageSwitchViewMemo as any).viewBox = { width: 100, height: 100 };\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageSwitchViewMemo as any).labelHeightUnits = 20;\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageSwitchViewMemo as any).interaction = {\n mode: \"both\" as const,\n direction: \"vertical\" as const,\n};\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageSwitchViewMemo as any).title = \"Image Switch\";\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\n(ImageSwitchViewMemo as any).description = \"A boolean control that displays one of two images based on value\";\n\n// Props for the ControlComponent (excluding normalizedValue, className, style, children)\ntype ImageSwitchViewComponentProps = Omit<ImageSwitchViewProps, \"normalizedValue\" | \"className\" | \"style\" | \"children\">;\n\nexport default ImageSwitchViewMemo as unknown as ControlComponent<ImageSwitchViewComponentProps>;\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport BooleanControl from \"@/primitives/controls/BooleanControl\";\nimport ImageSwitchView from \"./ImageSwitchView\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, BooleanControlProps } from \"@/types\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\n\n/**\n * Props specific to image switch controls\n */\nexport type ImageSwitchProps = {\n /** Width of the viewBox (determines viewBox width) */\n frameWidth: number;\n /** Height of the viewBox (determines viewBox height) */\n frameHeight: number;\n /** URL to the image for false/off state */\n imageHrefFalse: string;\n /** URL to the image for true/on state */\n imageHrefTrue: string;\n};\n\n/**\n * Props for the ImageSwitch component\n */\nexport type ImageSwitchComponentProps = BooleanControlProps & AdaptiveSizeProps & AdaptiveBoxProps & ImageSwitchProps;\n\n/**\n * A boolean control that displays one of two images based on the boolean value.\n *\n * This component provides full access to all library features:\n *\n * - Complete layout system: AdaptiveBox with all sizing modes, label positioning, alignment\n * - Full parameter model: BooleanParameter with latch/momentary modes\n * - Complete interaction system: Click, drag-in/drag-out, and keyboard interactions\n * - Full accessibility: ARIA attributes, focus management, keyboard navigation\n *\n * The image displayed is determined by the boolean value:\n * - `false` (normalized 0.0) displays `imageHrefFalse`\n * - `true` (normalized 1.0) displays `imageHrefTrue`\n *\n * Supports two modes of operation:\n * 1. Strict Mode (Parameter only): Model provided via parameter prop.\n * 2. Ad-Hoc Mode (Props only): Model created from individual props (label, latch, etc.).\n *\n * Note: This component does NOT support themable props (color, roundness, thickness) as\n * visuals are entirely determined by the image content.\n *\n * @param props - Component props\n * @returns Rendered ImageSwitch component\n *\n * @example\n * ```tsx\n * <ImageSwitch\n * value={isFavorite}\n * onChange={(e) => setIsFavorite(e.value)}\n * label=\"Favorite\"\n * latch={true}\n * frameWidth={100}\n * frameHeight={100}\n * imageHrefFalse=\"/star-off.png\"\n * imageHrefTrue=\"/star-on.png\"\n * />\n * ```\n */\nfunction ImageSwitch({\n latch = false,\n value = false,\n onChange,\n label,\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n labelMode,\n labelPosition,\n labelAlign,\n frameWidth,\n frameHeight,\n imageHrefFalse,\n imageHrefTrue,\n parameter,\n paramId,\n midiResolution,\n onClick,\n onMouseDown,\n onMouseUp,\n onMouseEnter,\n onMouseLeave,\n className,\n style,\n}: ImageSwitchComponentProps) {\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"button\");\n\n return (\n <BooleanControl\n value={value}\n onChange={onChange}\n label={label}\n displayMode={displayMode}\n labelMode={labelMode}\n labelPosition={labelPosition}\n labelAlign={labelAlign}\n className={classNames(sizeClassName, className)}\n style={{ ...sizeStyle, ...style }}\n parameter={parameter}\n paramId={paramId}\n latch={latch}\n midiResolution={midiResolution}\n onClick={onClick}\n onMouseDown={onMouseDown}\n onMouseUp={onMouseUp}\n onMouseEnter={onMouseEnter}\n onMouseLeave={onMouseLeave}\n viewBoxWidthUnits={frameWidth}\n viewBoxHeightUnits={frameHeight}\n view={ImageSwitchView}\n viewProps={{\n frameWidth,\n frameHeight,\n imageHrefFalse,\n imageHrefTrue,\n }}\n />\n );\n}\n\nexport default React.memo(ImageSwitch);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React from \"react\";\n\nexport type RadialHtmlOverlayProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Radius from center to the edge of the content box. The content will be a square with side = radius * 2 */\n radius: number;\n /** Content to render inside the overlay */\n children?: React.ReactNode;\n /** Additional CSS class name */\n className?: string;\n /** Inline styles for the foreignObject */\n style?: React.CSSProperties;\n /** Pointer events behavior (default: \"none\" to let clicks pass through to SVG below, set to \"auto\" if interactive) */\n pointerEvents?: \"none\" | \"auto\";\n};\n\n/**\n * A primitive that renders HTML content inside an SVG at a radial position.\n * It creates a square foreignObject centered at (cx, cy) with size based on radius.\n *\n * This is the preferred way to render text inside knobs, as it leverages the browser's\n * native HTML text rendering engine (Flexbox, wrapping, fonts) which is superior to SVG text.\n */\nfunction RadialHtmlOverlay({\n cx,\n cy,\n radius,\n children,\n className,\n style,\n pointerEvents = \"none\",\n}: RadialHtmlOverlayProps) {\n const size = radius * 2;\n const x = cx - radius;\n const y = cy - radius;\n\n return (\n <foreignObject\n x={x}\n y={y}\n width={size}\n height={size}\n className={className}\n style={{\n pointerEvents,\n ...style,\n }}\n >\n <div\n // @ts-expect-error - xmlns is valid for XHTML but not strictly in React's HTML definitions\n xmlns=\"http://www.w3.org/1999/xhtml\"\n style={{\n // Use 100% to fill the foreignObject (which is sized in SVG user units)\n // Do NOT use explicit pixels here, as that breaks SVG scaling (viewBox).\n width: \"100%\",\n height: \"100%\",\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n textAlign: \"center\",\n }}\n >\n {children}\n </div>\n </foreignObject>\n );\n}\n\nexport default React.memo(RadialHtmlOverlay);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { SVGProps } from \"react\";\n\nexport interface RevealingPathProps extends Omit<\n SVGProps<SVGPathElement>,\n \"strokeDasharray\" | \"strokeDashoffset\" | \"pathLength\"\n> {\n /**\n * Normalized value between 0 and 1 indicating how much of the path to reveal.\n * 0 = path is completely hidden\n * 1 = path is completely visible\n */\n normalizedValue: number;\n /**\n * The internal resolution used for the path length calculation.\n * Higher values might offer smoother animations but \"100\" is usually sufficient for percentage-based fills.\n * Defaults to 100.\n */\n resolution?: number;\n}\n\n/**\n * A primitive SVG component that reveals a path from start to end using CSS stroke-dashoffset.\n *\n * This component uses the SVG `pathLength` attribute to normalize the path's length\n * calculation, allowing for performant \"filling\" animations without calculating\n * the actual geometric length of the path in JavaScript.\n *\n * Usage:\n * ```tsx\n * <RevealingPath\n * d=\"M...\"\n * normalizedValue={0.5} // 50% revealed from the start\n * stroke=\"currentColor\"\n * fill=\"none\"\n * />\n * ```\n */\nfunction RevealingPath({ normalizedValue, resolution = 100, className, style, ...props }: RevealingPathProps) {\n // Clamp value to ensure it stays within 0-1 range\n const clampedValue = Math.max(0, Math.min(1, normalizedValue));\n\n // Calculate the dash offset based on the resolution.\n // - When value is 1 (100%), offset is 0 (dash fully covers path).\n // - When value is 0 (0%), offset is resolution (dash is shifted fully 'off' the path).\n const numericResolution = Number(resolution);\n const calculatedOffset = numericResolution * (1 - clampedValue);\n\n return (\n <path\n {...props}\n className={className}\n // pathLength normalizes the browser's internal distance calculation for this element\n // to this value (default 100), regardless of the path's actual pixel length.\n pathLength={numericResolution}\n // Create a single dash that is the length of the entire path.\n // This ensures the \"filled\" part is solid and the \"empty\" part is transparent.\n strokeDasharray={numericResolution}\n // Shift the dash pattern to reveal only the desired portion.\n strokeDashoffset={calculatedOffset}\n style={style}\n />\n );\n}\n\nexport default React.memo(RevealingPath);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, ReactNode, useMemo } from \"react\";\nimport { polarToCartesian } from \"@cutoff/audio-ui-core\";\n\nexport type TickData = {\n x: number;\n y: number;\n angle: number;\n index: number;\n};\n\nexport type TickRingProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Outer radius of the ring (ticks extend inward) */\n radius: number;\n /** Length of the ticks in pixels (or diameter of dots) */\n thickness?: number;\n /** Openness of the ring in degrees (default 90) */\n openness?: number;\n /** Rotation offset in degrees (default 0) */\n rotation?: number;\n /** Total number of ticks to distribute evenly */\n count?: number;\n /** Angle in degrees between ticks (alternative to count) */\n step?: number;\n /**\n * Shape of the ticks:\n * - \"line\": Radial lines (optimized single path)\n * - \"dot\": Circles (optimized single path)\n * - \"pill\": Rounded lines (using stroke-linecap round)\n */\n variant?: \"line\" | \"dot\" | \"pill\";\n /**\n * Optional callback to render custom content at each tick position.\n * If provided, the component bails out of single-path optimization and renders generic ReactNodes.\n */\n renderTick?: (data: TickData) => ReactNode;\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n};\n\n/**\n * A reusable SVG primitive that renders a ring of tick marks.\n * Useful for creating scales on knobs and dials.\n *\n * Supports optimized single-path rendering for lines and dots,\n * or custom rendering via renderTick callback.\n */\nfunction TickRing({\n cx,\n cy,\n radius,\n thickness = 4,\n openness = 90,\n rotation = 0,\n count,\n step,\n variant = \"line\",\n renderTick,\n className,\n style,\n}: TickRingProps) {\n // 1. Calculate positions (shared logic)\n const positions = useMemo(() => {\n const clampedOpenness = Math.max(0, Math.min(360, openness));\n const baseStart = 180 + clampedOpenness / 2;\n const baseEnd = 540 - clampedOpenness / 2;\n const startAngle = baseStart - rotation;\n const totalAngle = baseEnd - baseStart;\n\n let numTicks = 0;\n let angleStep = 0;\n\n // Handle full circle case (openness = 0 or 360)\n // If openness is 0, we have a full circle (360 degrees range).\n // For full circles, we distribute ticks evenly around 360 degrees,\n // ensuring the last tick doesn't overlap the first.\n const isFullCircle = clampedOpenness <= 0.01;\n\n if (count !== undefined && count > 1) {\n numTicks = count;\n if (isFullCircle) {\n // For full circle, distribute evenly around 360\n angleStep = 360 / count;\n } else {\n angleStep = totalAngle / (count - 1);\n }\n } else if (step !== undefined && step > 0) {\n numTicks = Math.floor(totalAngle / step) + 1;\n angleStep = step;\n } else if (count === 1) {\n numTicks = 1;\n angleStep = 0;\n }\n\n if (numTicks <= 0) return [];\n\n const results: TickData[] = [];\n // Round coordinates to avoid hydration mismatches\n const r = (n: number) => Math.round(n * 10000) / 10000;\n\n for (let i = 0; i < numTicks; i++) {\n const angle = startAngle + i * angleStep;\n\n // Calculate position at outer radius\n const pos = polarToCartesian(cx, cy, radius, angle);\n\n results.push({\n x: r(pos.x),\n y: r(pos.y),\n angle: r(angle), // Keep precise angle for rotation transforms\n index: i,\n });\n }\n return results;\n }, [cx, cy, radius, openness, rotation, count, step]);\n\n // 2. Optimized Path Mode (calculate before early return to satisfy hooks rules)\n const pathData = useMemo(() => {\n if (renderTick || positions.length === 0) return \"\";\n\n const commands: string[] = [];\n const r = (n: number) => Math.round(n * 10000) / 10000;\n\n for (const pos of positions) {\n if (variant === \"dot\") {\n // Circle centered at radius - thickness/2\n // We need to recalculate center because pos is at outer radius\n const center = polarToCartesian(cx, cy, radius - thickness / 2, pos.angle);\n const rx = thickness / 2;\n const ry = thickness / 2;\n\n // Draw circle using two arcs\n // M cx-r cy A r r 0 1 0 cx+r cy A r r 0 1 0 cx-r cy\n commands.push(\n `M ${r(center.x - rx)} ${r(center.y)} ` +\n `A ${r(rx)} ${r(ry)} 0 1 0 ${r(center.x + rx)} ${r(center.y)} ` +\n `A ${r(rx)} ${r(ry)} 0 1 0 ${r(center.x - rx)} ${r(center.y)}`\n );\n } else {\n // \"line\" or \"pill\"\n // Line from inner to outer\n const outer = polarToCartesian(cx, cy, radius, pos.angle);\n const inner = polarToCartesian(cx, cy, radius - thickness, pos.angle);\n commands.push(`M ${r(inner.x)} ${r(inner.y)} L ${r(outer.x)} ${r(outer.y)}`);\n }\n }\n return commands.join(\" \");\n }, [positions, cx, cy, radius, thickness, variant, renderTick]);\n\n // 3. Custom Render Mode (Bail out of optimization)\n if (renderTick) {\n return (\n <g className={className} style={style}>\n {positions.map((pos) => (\n <React.Fragment key={pos.index}>{renderTick(pos)}</React.Fragment>\n ))}\n </g>\n );\n }\n\n if (!pathData) return null;\n\n // Default styles based on variant\n const defaultStyles: CSSProperties =\n variant === \"dot\"\n ? { fill: \"currentColor\", stroke: \"none\" }\n : { fill: \"none\", stroke: \"currentColor\", strokeLinecap: variant === \"pill\" ? \"round\" : \"butt\" };\n\n return (\n <path\n d={pathData}\n className={className}\n style={{ ...defaultStyles, ...style }}\n // For lines/pills, use strokeWidth=1 default if not set in style\n // For dots, strokeWidth doesn't matter unless user overrides fill/stroke\n strokeWidth={variant === \"dot\" ? undefined : style?.strokeWidth || 1}\n />\n );\n}\n\nexport default React.memo(TickRing);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { CSSProperties, ReactNode } from \"react\";\nimport TickRing from \"./TickRing\";\n\nexport type LabelRingProps = {\n /** X coordinate of the center point */\n cx: number;\n /** Y coordinate of the center point */\n cy: number;\n /** Outer radius of the ring (content is centered at this radius) */\n radius: number;\n /** Array of content to render at each tick position */\n labels: (string | number | ReactNode)[];\n /** Orientation of the labels:\n * - \"upright\": Text stays upright (default)\n * - \"radial\": Text rotates to match the angle\n */\n orientation?: \"upright\" | \"radial\";\n /** Openness of the ring in degrees (default 90) */\n openness?: number;\n /** Rotation offset in degrees (default 0) */\n rotation?: number;\n /** CSS class name */\n className?: string;\n /** Inline styles */\n style?: CSSProperties;\n /** CSS class name for the text elements (only applies to string/number labels) */\n labelClassName?: string;\n /** Inline styles for the text elements (only applies to string/number labels) */\n labelStyle?: CSSProperties;\n /**\n * Default size for icon labels. Used to center the icons.\n * If not provided, the component attempts to read `size`, `width`, or `height` from the icon props.\n */\n iconSize?: number;\n};\n\n/**\n * A wrapper around TickRing designed for rendering text labels or icons at radial positions.\n * Automatically handles positioning and rotation based on the specified orientation.\n */\nfunction LabelRing({\n cx,\n cy,\n radius,\n labels,\n orientation = \"upright\",\n openness = 90,\n rotation = 0,\n className,\n style,\n labelClassName,\n labelStyle,\n iconSize,\n}: LabelRingProps) {\n return (\n <TickRing\n cx={cx}\n cy={cy}\n radius={radius}\n openness={openness}\n rotation={rotation}\n count={labels.length}\n className={className}\n style={style}\n renderTick={({ x, y, angle, index }) => {\n const content = labels[index];\n\n // If content is null/undefined, skip\n if (content === null || content === undefined) return null;\n\n const isComponent = React.isValidElement(content);\n\n let transform = undefined;\n\n if (orientation === \"radial\") {\n // Standard radial rotation: text bottom points to center\n\n // Base rotation: angle + 90 makes text tangent to the circle\n let textRotation = angle + 90;\n\n // Readability optimization:\n // If text is in the right hemisphere (roughly 90° to 270°),\n // it appears upside-down. We flip it by 180° to keep it readable.\n // Normalize textRotation to check orientation zone\n const normalizedRotation = ((textRotation % 360) + 360) % 360;\n\n if (normalizedRotation > 90 && normalizedRotation < 270) {\n textRotation += 180;\n }\n\n transform = `rotate(${textRotation}, ${x}, ${y})`;\n }\n\n if (isComponent) {\n // For React nodes (icons, etc), wrap in group and translate\n // Extract rotation value from transform if present to apply to group\n // Note: We strip the center point from rotation for the group transform\n // because the group is already translated to {x, y}\n const rotationValue = transform ? transform.match(/rotate\\(([-\\d.]+)/)?.[1] : 0;\n\n // Attempt to calculate offset to center the icon\n // We assume icons are square and positioned top-left by default (standard SVG behavior)\n let offset = 0;\n if (iconSize !== undefined) {\n offset = -iconSize / 2;\n } else {\n // Heuristic: Try to find size from props\n // This works for Lucide, Radix, and many other icon libraries\n const props = (content as React.ReactElement).props as { size?: string | number; width?: string | number; height?: string | number };\n const size = props?.size ?? props?.width ?? props?.height;\n const numericSize = Number(size);\n if (!isNaN(numericSize) && numericSize > 0) {\n offset = -numericSize / 2;\n }\n }\n\n return (\n <g transform={`translate(${x}, ${y}) rotate(${rotationValue}) translate(${offset}, ${offset})`}>\n {content}\n </g>\n );\n }\n\n // For text/numbers\n return (\n <text\n x={x}\n y={y}\n textAnchor=\"middle\"\n dominantBaseline=\"middle\"\n className={labelClassName}\n style={labelStyle}\n transform={transform}\n fill=\"currentColor\"\n >\n {content}\n </text>\n );\n }}\n />\n );\n}\n\nexport default React.memo(LabelRing);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\nimport React, { useCallback, useRef, useEffect } from \"react\";\nimport { NoteInteractionController } from \"@cutoff/audio-ui-core\";\n\n/**\n * Props for the useNoteInteraction hook.\n */\nexport interface UseNoteInteractionProps {\n /** Callback triggered when a note is pressed */\n onNoteOn: (note: number) => void;\n /** Callback triggered when a note is released */\n onNoteOff: (note: number) => void;\n /** Whether the interaction is disabled */\n disabled?: boolean;\n /** Optional user-provided pointer down handler */\n onPointerDown?: React.PointerEventHandler;\n /** Optional user-provided pointer move handler */\n onPointerMove?: React.PointerEventHandler;\n /** Optional user-provided pointer up handler */\n onPointerUp?: React.PointerEventHandler;\n /** Optional user-provided pointer cancel handler */\n onPointerCancel?: React.PointerEventHandler;\n}\n\n/**\n * Result object for the useNoteInteraction hook.\n */\nexport interface UseNoteInteractionResult {\n /** Pointer down handler to be attached to the target element */\n onPointerDown: React.PointerEventHandler;\n /** Pointer move handler to be attached to the target element */\n onPointerMove: React.PointerEventHandler;\n /** Pointer up handler to be attached to the target element */\n onPointerUp: React.PointerEventHandler;\n /** Pointer cancel handler to be attached to the target element */\n onPointerCancel: React.PointerEventHandler;\n /** Standardized styles for the interactive element (e.g., touchAction: \"none\") */\n style: React.CSSProperties;\n}\n\n/**\n * Hook to manage note interactions for keyboard-like components.\n *\n * Provides standardized logic for:\n * - Multi-touch support via PointerEvents\n * - Glissando (sliding across keys) detection\n * - Standardized note on/off event triggering\n *\n * It uses the framework-agnostic `NoteInteractionController` to handle the\n * core interaction logic and manages pointer capture for reliable glissando.\n *\n * The hook returns pointer event handlers and a style object containing\n * `touchAction: \"none\"` to prevent default touch behaviors. Cursor styling\n * is handled by the consuming component based on its interactivity state.\n *\n * @param {UseNoteInteractionProps} props - Configuration for the note interaction hook\n * @param {(note: number) => void} props.onNoteOn - Callback triggered when a note is pressed\n * @param {(note: number) => void} props.onNoteOff - Callback triggered when a note is released\n * @param {boolean} [props.disabled=false] - Whether the interaction is disabled\n * @param {React.PointerEventHandler} [props.onPointerDown] - Optional user-provided pointer down handler\n * @param {React.PointerEventHandler} [props.onPointerMove] - Optional user-provided pointer move handler\n * @param {React.PointerEventHandler} [props.onPointerUp] - Optional user-provided pointer up handler\n * @param {React.PointerEventHandler} [props.onPointerCancel] - Optional user-provided pointer cancel handler\n * @returns {UseNoteInteractionResult} Object containing pointer event handlers and styles\n *\n * @example\n * ```tsx\n * const { onPointerDown, onPointerMove, onPointerUp, style } = useNoteInteraction({\n * onNoteOn: (note) => synth.noteOn(note),\n * onNoteOff: (note) => synth.noteOff(note)\n * });\n *\n * return (\n * <svg\n * onPointerDown={onPointerDown}\n * onPointerMove={onPointerMove}\n * onPointerUp={onPointerUp}\n * style={style}\n * >\n * {keys.map(key => (\n * <rect key={key.note} data-note={key.note} {...key.rectProps} />\n * ))}\n * </svg>\n * );\n * ```\n */\nexport function useNoteInteraction({\n onNoteOn,\n onNoteOff,\n disabled = false,\n onPointerDown: userOnPointerDown,\n onPointerMove: userOnPointerMove,\n onPointerUp: userOnPointerUp,\n onPointerCancel: userOnPointerCancel,\n}: UseNoteInteractionProps): UseNoteInteractionResult {\n const controllerRef = useRef<NoteInteractionController | null>(null);\n\n if (!controllerRef.current) {\n controllerRef.current = new NoteInteractionController({\n onNoteOn: (note) => onNoteOn(note),\n onNoteOff: (note) => onNoteOff(note),\n disabled,\n });\n }\n\n useEffect(() => {\n controllerRef.current?.updateConfig({\n onNoteOn: (note) => onNoteOn(note),\n onNoteOff: (note) => onNoteOff(note),\n disabled,\n });\n }, [onNoteOn, onNoteOff, disabled]);\n\n useEffect(() => {\n return () => {\n controllerRef.current?.cancelAll();\n };\n }, []);\n\n const getNoteFromEvent = useCallback((e: React.PointerEvent) => {\n const element = document.elementFromPoint(e.clientX, e.clientY);\n if (!element) return null;\n\n const noteAttr = element.getAttribute(\"data-note\");\n if (noteAttr) {\n return parseInt(noteAttr, 10);\n }\n return null;\n }, []);\n\n const handlePointerDown = useCallback(\n (e: React.PointerEvent) => {\n userOnPointerDown?.(e);\n if (e.defaultPrevented || disabled) return;\n\n // Capture the pointer to continue receiving events even if it leaves the element\n (e.currentTarget as Element).setPointerCapture(e.pointerId);\n\n const note = getNoteFromEvent(e);\n controllerRef.current?.handlePointerDown(e.pointerId, note);\n },\n [userOnPointerDown, disabled, getNoteFromEvent]\n );\n\n const handlePointerMove = useCallback(\n (e: React.PointerEvent) => {\n userOnPointerMove?.(e);\n if (e.defaultPrevented || disabled) return;\n\n const note = getNoteFromEvent(e);\n controllerRef.current?.handlePointerMove(e.pointerId, note);\n },\n [userOnPointerMove, disabled, getNoteFromEvent]\n );\n\n const handlePointerUp = useCallback(\n (e: React.PointerEvent) => {\n userOnPointerUp?.(e);\n if (e.defaultPrevented || disabled) return;\n\n controllerRef.current?.handlePointerUp(e.pointerId);\n },\n [userOnPointerUp, disabled]\n );\n\n const handlePointerCancel = useCallback(\n (e: React.PointerEvent) => {\n userOnPointerCancel?.(e);\n if (e.defaultPrevented || disabled) return;\n\n controllerRef.current?.handlePointerUp(e.pointerId);\n },\n [userOnPointerCancel, disabled]\n );\n\n return {\n onPointerDown: handlePointerDown,\n onPointerMove: handlePointerMove,\n onPointerUp: handlePointerUp,\n onPointerCancel: handlePointerCancel,\n style: {\n touchAction: \"none\",\n },\n };\n}\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n\"use client\";\n\nimport React, { useMemo, useCallback } from \"react\";\nimport classNames from \"classnames\";\nimport AdaptiveBox from \"@/primitives/AdaptiveBox\";\nimport { AdaptiveBoxProps, AdaptiveSizeProps, AudioControlEvent, BaseProps, ThemableProps } from \"@/types\";\nimport { generateColorVariants } from \"@cutoff/audio-ui-core\";\nimport { useAdaptiveSize } from \"@/hooks/useAdaptiveSize\";\nimport { useThemableProps } from \"@/hooks/useThemableProps\";\nimport { useNoteInteraction } from \"@/hooks/useNoteInteraction\";\nimport {\n createNoteNumSet,\n DIATONIC_TO_CHROMATIC,\n noteNumToNote,\n noteToNoteNum,\n WHITE_KEY_NAMES,\n WHITE_KEY_POSITIONS,\n WHITE_KEY_TO_CHROMATIC,\n} from \"@cutoff/audio-ui-core\";\nimport \"@cutoff/audio-ui-core/styles.css\";\nimport { CLASSNAMES } from \"@cutoff/audio-ui-core\";\nimport { CSS_VARS } from \"@cutoff/audio-ui-core\";\nimport { translateKeysRoundness } from \"@cutoff/audio-ui-core\";\n\n/**\n * Type definition for note names (C to B)\n */\ntype NoteName = (typeof WHITE_KEY_NAMES)[number];\nconst notesCount = WHITE_KEY_NAMES.length;\n\n/**\n * Props for the Keys component\n */\nexport type KeysProps = BaseProps &\n AdaptiveSizeProps &\n // Keys uses AdaptiveBox layout props but deliberately does not support labels.\n // labelMode/labelPosition/labelAlign are omitted on purpose.\n Omit<AdaptiveBoxProps, \"labelMode\" | \"labelPosition\" | \"labelAlign\"> &\n ThemableProps & {\n /** Number of keys on the keyboard\n * @default 61 */\n nbKeys?: number;\n /** Starting note name (A-G)\n * @default 'C' for 61 keys, 'A' for 88 keys */\n startKey?: NoteName;\n /** Octave transpose index (default: 0)\n * Positive values shift notes up by that many octaves, negative values shift down\n * @default 0 */\n octaveShift?: number;\n /** Array of notes that should be highlighted\n * Notes can be specified as:\n * - Strings in the format: NoteName + Octave (+ optional '#' for sharp), e.g., 'C4', 'F#5'\n * - Numbers representing MIDI note IDs (e.g., 60 for C4, 61 for C#4, etc.)\n * @example ['C4', 'E4', 'G4'] or [60, 64, 67] or ['C4', 64, 'G4'] */\n notesOn?: (string | number)[];\n /** Key styling mode\n * - 'theme': Uses theme colors (current behavior, uses color prop and themable hook)\n * - 'classic': Classic piano style with ivory white keys and ebony black keys\n * - 'classic-inverted': Inverted classic style with ebony white keys and ivory black keys\n * @default 'theme' */\n keyStyle?: \"theme\" | \"classic\" | \"classic-inverted\";\n /** Callback triggered when a key is pressed or released.\n * Only active if this prop is provided. */\n onChange?: (event: AudioControlEvent<{ note: number; active: boolean }>) => void;\n };\n\n/**\n * Calculate positive modulo (different from JavaScript's % operator for negative numbers)\n */\nconst positiveModulo = (number: number, modulus: number): number => {\n return ((number % modulus) + modulus) % modulus;\n};\n\n/**\n * Piano Keys component that renders a piano keyboard visualization.\n * Supports variable number of keys, different starting positions, and note highlighting.\n *\n * Features:\n * - Configurable number of keys (default 61, supports any number from 1 to 128)\n * - Customizable starting position (note and octave)\n * - Highlights active notes (supports both note names and MIDI note numbers)\n * - Maintains proper piano key layout and proportions\n * - Responsive sizing through AdaptiveSvgComponent integration\n * - Multiple size variants (xsmall, small, normal, large, xlarge)\n *\n * @property {boolean} adaptiveSize - Whether the keys component fills its container (ignores size constraints when true)\n * @property {number} nbKeys - Number of keys on the keyboard (default 61)\n * @property {NoteName} startKey - Starting note name (A-G) (default 'C' for 61 keys, 'A' for 88 keys)\n * @property {number} octaveShift - Octave transpose index (default 0). Positive values shift notes up by that many octaves, negative values shift down.\n * @property {(string | number)[]} notesOn - Array of notes that should be highlighted. Can contain note names (e.g., 'C4') or MIDI note numbers (e.g., 60 for C4).\n * @property {string} className - Additional CSS classes\n * @property {React.CSSProperties} style - Additional inline styles\n * @property {SizeType} size - Size of the component (xsmall, small, normal, large, xlarge)\n * @property {\"theme\" | \"classic\" | \"classic-inverted\"} keyStyle - Key styling mode (default \"theme\")\n *\n * @example\n * ```tsx\n * // Basic usage\n * <Keys />\n *\n * // Full piano configuration with note names\n * <Keys\n * nbKeys={88}\n * startKey=\"A\"\n * octaveShift={0}\n * notesOn={['C4', 'E4', 'G4']}\n * />\n *\n * // Using MIDI note numbers\n * <Keys\n * notesOn={[60, 64, 67]} // C4, E4, G4\n * />\n *\n * // Mixing note names and MIDI note numbers\n * <Keys\n * notesOn={['C4', 64, 'G4']} // C4, E4, G4\n * />\n *\n * // Custom styling with size\n * <Keys\n * className=\"my-keyboard\"\n * style={{ marginTop: '20px' }}\n * size=\"large\"\n * />\n *\n * // Classic piano style\n * <Keys keyStyle=\"classic\" />\n *\n * // Inverted classic style\n * <Keys keyStyle=\"classic-inverted\" />\n * ```\n */\nfunction Keys({\n nbKeys = 61,\n startKey = nbKeys === 88 ? \"A\" : \"C\",\n octaveShift = 0,\n notesOn = [],\n adaptiveSize = false,\n size = \"normal\",\n displayMode,\n keyStyle = \"theme\",\n color,\n roundness,\n className = \"\",\n style = {},\n onChange,\n onClick,\n}: KeysProps) {\n // Ensure nbKeys is within valid range (1-128)\n const validNbKeys = Math.max(1, Math.min(128, nbKeys));\n\n // Interaction setup\n const {\n onPointerDown,\n onPointerMove,\n onPointerUp,\n onPointerCancel,\n style: interactionStyle,\n } = useNoteInteraction({\n onNoteOn: (note) => {\n onChange?.({\n value: { note, active: true },\n normalizedValue: note / 127,\n midiValue: note,\n });\n },\n onNoteOff: (note) => {\n onChange?.({\n value: { note, active: false },\n normalizedValue: note / 127,\n midiValue: note,\n });\n },\n });\n // Memoize initial computations\n const keysDimensions = useMemo(() => {\n const nbOctaves = Math.floor(validNbKeys / 12);\n const keyRemainder = validNbKeys % 12;\n\n // Calculate white keys: Each octave has 7 white keys (C, D, E, F, G, A, B)\n // For partial octaves, use the ratio 7/12 (white keys per semitone) to estimate\n // This ensures accurate white key count for any number of keys\n const whiteKeysInRemainder = Math.ceil((keyRemainder * 7) / 12);\n const nbWhite = nbOctaves * 7 + whiteKeysInRemainder;\n\n const startKeyIndex = WHITE_KEY_NAMES.indexOf(startKey);\n\n const middleKeyIndex = Math.floor((nbWhite - 1) / 2);\n const octavesFromMiddle = Math.floor(middleKeyIndex / 7);\n\n // Octave adjustment for keys starting with A or B:\n // In MIDI convention, A and B belong to the octave of the next C.\n // For example, A4 and B4 are in the same octave as C5, not C4.\n // This adjustment ensures correct octave calculation when starting with A or B.\n const octaveAdjustment = startKeyIndex >= 5 ? 1 : 0;\n\n // Calculate starting octave with special handling for extreme keyboard sizes\n let startOctave;\n if (validNbKeys <= 12) {\n // Very small keyboards (1-12 keys): Center around C4 (middle C) for usability\n startOctave = 4 - octaveAdjustment;\n } else if (validNbKeys >= 120) {\n // Very large keyboards (120-128 keys): Prevent going below C0 (MIDI note 0)\n // This ensures we don't generate invalid MIDI note numbers\n startOctave = Math.max(0, 4 - octavesFromMiddle - octaveAdjustment);\n } else {\n // Standard keyboards: Calculate octave based on middle key position\n // This centers the keyboard around middle C (C4) for typical sizes\n startOctave = 4 - octavesFromMiddle - octaveAdjustment;\n }\n\n // Key dimensions (in SVG units)\n const whiteWidth = 25;\n const whiteHeight = 150;\n const blackWidth = 13;\n const blackXShift = 18;\n const blackHeight = (whiteHeight / 3) * 2;\n const outerStrokeWidth = 2;\n const innerStrokeWidth = 2;\n\n const halfInnerStrokeWidth = innerStrokeWidth / 2;\n\n // Calculate total width\n const width = nbWhite * whiteWidth;\n\n // Calculate black key positions\n const blackKeyShift = parseInt(startKey, 36) - parseInt(\"C\", 36);\n const blackPass = [positiveModulo(2 + blackKeyShift, 7), positiveModulo(6 + blackKeyShift, 7)];\n\n return {\n nbWhite,\n startKeyIndex,\n startOctave,\n whiteWidth,\n whiteHeight,\n blackWidth,\n blackXShift,\n blackHeight,\n outerStrokeWidth,\n innerStrokeWidth,\n width,\n blackPass,\n halfInnerStrokeWidth,\n };\n }, [validNbKeys, startKey]);\n\n // Build CSS variables from props (if provided)\n const { style: themableStyle } = useThemableProps({\n color,\n roundness,\n style,\n });\n\n // Translate normalized roundness to legacy range (0-12) or use CSS variable\n // When roundness is a CSS variable string (from theme), pass it directly to SVG rx attributes.\n // When roundness is undefined, fallback to theme CSS variable.\n // When roundness is a number, translate it to the legacy pixel range.\n const cornerRadius = useMemo(() => {\n if (typeof roundness === \"string\") {\n // CSS variable - pass directly to SVG (browser will resolve it)\n return roundness;\n }\n if (roundness === undefined) {\n // No prop provided - use theme default via CSS variable\n return \"var(--audioui-roundness-keys)\";\n }\n // Numeric value - translate to legacy pixel range (0-12)\n return translateKeysRoundness(roundness);\n }, [roundness]);\n\n // Generate color variants using the centralized utility\n // Used for theme mode rendering and for active keys in classic modes\n // Read color from CSS variable if not provided as prop\n const resolvedColor = color ?? \"var(--audioui-primary-color)\";\n const colorVariants = useMemo(() => {\n return generateColorVariants(resolvedColor, \"luminosity\");\n }, [resolvedColor]);\n\n // Determine key colors based on keyStyle\n // Active keys always use theme color (colorVariants.primary), regardless of keyStyle\n const keyColors = useMemo(() => {\n if (keyStyle === \"theme\") {\n return null; // Use colorVariants instead\n } else if (keyStyle === \"classic\") {\n return {\n whiteFill: `var(${CSS_VARS.keysIvory})`,\n whiteStroke: `var(${CSS_VARS.keysIvoryStroke})`,\n blackFill: `var(${CSS_VARS.keysEbony})`,\n blackStroke: `var(${CSS_VARS.keysEbonyStroke})`,\n };\n } else {\n // classic-inverted\n return {\n whiteFill: `var(${CSS_VARS.keysEbony})`,\n whiteStroke: `var(${CSS_VARS.keysEbonyStroke})`,\n blackFill: `var(${CSS_VARS.keysIvory})`,\n blackStroke: `var(${CSS_VARS.keysIvoryStroke})`,\n };\n }\n }, [keyStyle]);\n\n // Memoize the active notes set for efficient lookups\n const activeNoteNumSet = useMemo(() => {\n return createNoteNumSet(notesOn || []);\n }, [notesOn]);\n\n // Calculate which white key positions should NOT have black keys above them\n // In a standard piano layout, there are no black keys between:\n // - E and F (semitone gap, no black key)\n // - B and C (semitone gap, no black key)\n // These correspond to white key indices 2 and 6 when starting with C\n // The modulo calculation adjusts for different starting keys\n const correctBlackPass = useMemo(() => {\n const startKeyIndex = WHITE_KEY_NAMES.indexOf(startKey);\n return [\n positiveModulo(2 - startKeyIndex, 7), // E-F gap (no black key after E)\n positiveModulo(6 - startKeyIndex, 7), // B-C gap (no black key after B)\n ];\n }, [startKey]);\n\n // Create a memoized function to check if a note is active\n const isNoteActive = useCallback(\n (note: string) => {\n // If it's a string note, convert to MIDI note number and check if it's in the set\n const noteNum = noteToNoteNum(note);\n return noteNum !== -1 && activeNoteNumSet.has(noteNum);\n },\n [activeNoteNumSet]\n );\n\n // Memoize white keys rendering\n const renderWhiteKeys = useMemo(() => {\n const { nbWhite, startKeyIndex, startOctave, whiteWidth, whiteHeight, innerStrokeWidth, halfInnerStrokeWidth } =\n keysDimensions;\n\n // Get the chromatic index of the start key\n const startKeyChromatic = DIATONIC_TO_CHROMATIC[startKey];\n\n // Calculate the base MIDI note number for the starting key\n const baseNoteNum = (startOctave + 1) * 12 + startKeyChromatic;\n\n return Array.from({ length: nbWhite }, (_, index) => {\n // Calculate the diatonic index (white keys only)\n const currentNoteIndex = (startKeyIndex + index) % notesCount;\n\n // Calculate how many octaves we've moved from the start\n const octaveOffset = Math.floor((startKeyIndex + index) / notesCount);\n\n // Calculate the MIDI note number for this key\n // We need to find how many semitones we've moved from the start key\n const chromaticOffset = WHITE_KEY_TO_CHROMATIC[currentNoteIndex] - WHITE_KEY_TO_CHROMATIC[startKeyIndex];\n const adjustedOffset = chromaticOffset + octaveOffset * 12;\n const noteNum = baseNoteNum + adjustedOffset - octaveShift * 12;\n\n // Convert the MIDI note number to a note name and octave\n const currentWhiteNote = noteNumToNote(noteNum);\n\n // Color resolution: Active keys always use theme color for visual feedback\n // Inactive keys use theme colors (theme mode) or classic colors (classic modes)\n let strokeColor: string;\n let fillColor: string;\n\n if (keyStyle === \"theme\") {\n strokeColor = colorVariants.primary50;\n fillColor = isNoteActive(currentWhiteNote) ? colorVariants.primary : \"transparent\";\n } else if (keyColors) {\n strokeColor = keyColors.whiteStroke;\n fillColor = isNoteActive(currentWhiteNote) ? colorVariants.primary : keyColors.whiteFill;\n } else {\n // Fallback (should not happen)\n strokeColor = \"#000\";\n fillColor = \"transparent\";\n }\n\n return (\n <rect\n key={`white-${index}-${currentWhiteNote}`}\n data-note={noteNum}\n style={{\n stroke: strokeColor,\n fill: fillColor,\n rx: cornerRadius,\n ry: cornerRadius,\n }}\n strokeWidth={innerStrokeWidth}\n x={index * whiteWidth + halfInnerStrokeWidth}\n y={halfInnerStrokeWidth}\n width={whiteWidth}\n height={whiteHeight}\n // Use 0 as fallback for older browsers that don't support CSS rx/ry\n // If cornerRadius is a number, we can use it directly\n rx={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n />\n );\n });\n }, [keysDimensions, octaveShift, isNoteActive, cornerRadius, colorVariants, keyColors, keyStyle, startKey]);\n\n // Memoize black keys rendering\n const renderBlackKeys = useMemo(() => {\n const {\n nbWhite,\n startKeyIndex,\n startOctave,\n whiteWidth,\n blackWidth,\n blackXShift,\n blackHeight,\n innerStrokeWidth,\n halfInnerStrokeWidth,\n } = keysDimensions;\n\n // Get the chromatic index of the start key\n const startKeyChromatic = DIATONIC_TO_CHROMATIC[startKey];\n\n // Calculate the base MIDI note number for the starting key\n const baseNoteNum = (startOctave + 1) * 12 + startKeyChromatic;\n\n // Render black keys: There are nbWhite - 1 potential black key positions\n // (one less than white keys because black keys sit between white keys)\n return Array.from({ length: nbWhite - 1 }, (_, index) => {\n const octaveIndex = index % 7;\n // Skip positions that shouldn't have black keys (E-F and B-C gaps)\n if (correctBlackPass.includes(octaveIndex)) return null;\n\n // Calculate the diatonic index (white keys only)\n const currentNoteIndex = (startKeyIndex + index) % notesCount;\n\n // Calculate how many octaves we've moved from the start\n const octaveOffset = Math.floor((startKeyIndex + index) / notesCount);\n\n // Calculate the MIDI note number for this key\n // We need to find how many semitones we've moved from the start key\n const chromaticOffset = WHITE_KEY_TO_CHROMATIC[currentNoteIndex] - WHITE_KEY_TO_CHROMATIC[startKeyIndex];\n const adjustedOffset = chromaticOffset + octaveOffset * 12;\n\n // Black keys are positioned one semitone higher than the white key to their left\n // Add 1 to the chromatic offset to get the black key's MIDI note number\n const noteNum = baseNoteNum + adjustedOffset + 1 - octaveShift * 12;\n\n // Skip black keys that would map to white key positions (E-F and B-C gaps)\n // This prevents duplicate note assignments and ensures correct piano layout\n // The modulo 12 gives us the chromatic position within the octave\n if (WHITE_KEY_POSITIONS.has(noteNum % 12)) return null;\n\n // Convert the MIDI note number to a note name and octave\n const currentBlackNote = noteNumToNote(noteNum);\n\n // Determine colors based on keyStyle\n // Active keys always use theme color (colorVariants.primary)\n let strokeColor: string;\n let fillColor: string;\n\n if (keyStyle === \"theme\") {\n strokeColor = colorVariants.primary50;\n fillColor = isNoteActive(currentBlackNote) ? colorVariants.primary : colorVariants.primary50;\n } else if (keyColors) {\n strokeColor = keyColors.blackStroke;\n fillColor = isNoteActive(currentBlackNote) ? colorVariants.primary : keyColors.blackFill;\n } else {\n // Fallback (should not happen)\n strokeColor = \"#000\";\n fillColor = \"#000\";\n }\n\n return (\n <rect\n key={`black-${index}-${currentBlackNote}`}\n data-note={noteNum}\n style={{\n zIndex: 1,\n stroke: strokeColor,\n fill: fillColor,\n rx: cornerRadius,\n ry: cornerRadius,\n }}\n strokeWidth={innerStrokeWidth}\n x={index * whiteWidth + blackXShift + halfInnerStrokeWidth}\n y={halfInnerStrokeWidth}\n width={blackWidth}\n height={blackHeight}\n // Use 0 as fallback for older browsers that don't support CSS rx/ry\n // If cornerRadius is a number, we can use it directly\n rx={typeof cornerRadius === \"number\" ? cornerRadius : 0}\n />\n );\n }).filter(Boolean);\n }, [\n keysDimensions,\n octaveShift,\n isNoteActive,\n correctBlackPass,\n cornerRadius,\n colorVariants,\n keyColors,\n keyStyle,\n startKey,\n ]);\n\n // Get adaptive sizing values\n const { sizeClassName, sizeStyle } = useAdaptiveSize(adaptiveSize, size, \"keys\");\n\n // Memoize the classNames calculation: size class first, then base classes, then user className (user takes precedence)\n const componentClassNames = useMemo(() => {\n return classNames(sizeClassName, CLASSNAMES.root, className);\n }, [sizeClassName, className]);\n\n // Add highlight class when interactive (onChange or onClick)\n const svgClassNames = useMemo(() => {\n return onChange || onClick ? CLASSNAMES.highlight : \"\";\n }, [onChange, onClick]);\n\n // Add clickable cursor when interactive (onChange or onClick)\n // Uses CSS variable for customizable cursor type\n // View-only components (no onChange, no onClick) get default cursor\n const svgStyle = useMemo(\n () => ({\n ...(interactionStyle ?? {}),\n ...(onClick || onChange ? { cursor: \"var(--audioui-cursor-clickable)\" as const } : {}),\n }),\n [interactionStyle, onClick, onChange]\n );\n\n return (\n <AdaptiveBox\n displayMode={displayMode ?? \"scaleToFit\"}\n // Keys does not expose labels; hide label row explicitly.\n labelMode=\"none\"\n className={componentClassNames}\n style={{ ...sizeStyle, ...themableStyle }}\n viewBoxWidth={keysDimensions.width + keysDimensions.innerStrokeWidth}\n viewBoxHeight={keysDimensions.whiteHeight + keysDimensions.innerStrokeWidth}\n minWidth={40}\n minHeight={40}\n >\n <AdaptiveBox.Svg\n className={svgClassNames}\n style={svgStyle}\n onClick={onClick}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n onPointerCancel={onPointerCancel}\n >\n {renderWhiteKeys}\n {renderBlackKeys}\n </AdaptiveBox.Svg>\n </AdaptiveBox>\n );\n}\n\n// Wrap the component in React.memo to prevent unnecessary re-renders\nexport default React.memo(Keys);\n","/*\n * Copyright (c) 2026 Tylium.\n * SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-TELF-1.0\n * See LICENSE.md for details.\n */\n\n/**\n * Set the global theme color.\n * Updates the CSS variable `--audioui-primary-color` on the document root.\n * Color variants (`--audioui-primary-50`, `--audioui-primary-20`) are automatically computed\n * by CSS using `color-mix()`.\n *\n * @param color - Any valid CSS color value (e.g., \"blue\", \"#FF5500\", \"hsl(200, 100%, 50%)\", \"var(--my-color)\")\n *\n * @example\n * ```ts\n * setThemeColor(\"blue\");\n * setThemeColor(\"var(--audioui-theme-purple)\");\n * setThemeColor(\"hsl(200, 100%, 50%)\");\n * ```\n */\nexport function setThemeColor(color: string): void {\n if (typeof document !== \"undefined\") {\n document.documentElement.style.setProperty(\"--audioui-primary-color\", color);\n }\n}\n\n/**\n * Set the global theme roundness.\n * Updates the CSS variable `--audioui-roundness-base` on the document root.\n * Component-specific roundness values are automatically calculated from this base value.\n *\n * This function also updates the `--audioui-linecap-base` helper variable:\n * - value = 0.0 results in \"square\"\n * - value > 0.0 results in \"round\"\n *\n * @param value - Normalized roundness value between 0.0 and 1.0\n *\n * @example\n * ```ts\n * setThemeRoundness(0.3); // 30% roundness, linecap: round\n * setThemeRoundness(0.0); // Square corners, linecap: square\n * setThemeRoundness(1.0); // Maximum roundness, linecap: round\n * ```\n */\nexport function setThemeRoundness(value: number): void {\n if (typeof document !== \"undefined\") {\n document.documentElement.style.setProperty(\"--audioui-roundness-base\", value.toString());\n // Automatically infer linecap from roundness: 0 = square, >0 = round\n const linecap = value === 0 ? \"square\" : \"round\";\n document.documentElement.style.setProperty(\"--audioui-linecap-base\", linecap);\n }\n}\n\n/**\n * Theme configuration object for setting multiple theme values at once.\n */\nexport interface ThemeConfig {\n /** Theme color - any valid CSS color value */\n color?: string;\n /** Roundness (normalized 0.0-1.0) */\n roundness?: number;\n}\n\n/**\n * Set multiple theme values at once.\n * Convenience function that calls individual setter functions for each provided value.\n *\n * @param theme - Theme configuration object with optional color and roundness values\n *\n * @example\n * ```ts\n * setTheme({ color: \"blue\", roundness: 0.3 });\n * setTheme({ color: \"purple\" }); // Only sets color, leaves roundness unchanged\n * ```\n */\nexport function setTheme(theme: ThemeConfig): void {\n if (typeof document === \"undefined\") return;\n\n if (theme.color !== undefined) {\n setThemeColor(theme.color);\n }\n if (theme.roundness !== undefined) {\n setThemeRoundness(theme.roundness);\n }\n}\n\n/**\n * Get current theme color from CSS variable.\n * Reads the value of `--audioui-primary-color` from the document root.\n *\n * @returns The current theme color value as a string, or `null` if not set or in SSR context\n *\n * @example\n * ```ts\n * const currentColor = getThemeColor();\n * if (currentColor) {\n * console.log(`Current theme color: ${currentColor}`);\n * }\n * ```\n */\nexport function getThemeColor(): string | null {\n if (typeof document === \"undefined\" || typeof window === \"undefined\") return null;\n return window.getComputedStyle(document.documentElement).getPropertyValue(\"--audioui-primary-color\").trim() || null;\n}\n\n/**\n * Get current theme roundness from CSS variable.\n * Reads the value of `--audioui-roundness-base` from the document root.\n *\n * @returns The current theme roundness value as a number (0.0-1.0), or `null` if not set or in SSR context\n *\n * @example\n * ```ts\n * const roundness = getThemeRoundness();\n * if (roundness !== null) {\n * console.log(`Current roundness: ${roundness * 100}%`);\n * }\n * ```\n */\nexport function getThemeRoundness(): number | null {\n if (typeof document === \"undefined\" || typeof window === \"undefined\") return null;\n const value = window.getComputedStyle(document.documentElement).getPropertyValue(\"--audioui-roundness-base\").trim();\n return value ? parseFloat(value) : null;\n}\n"],"names":["OptionView","_props","ButtonView","normalizedValue","threshold","roundness","_color","className","isOn","useMemo","cornerRadius","translateButtonRoundness","DEFAULT_ROUNDNESS","buttonStyles","jsx","VIEW_BOX","ButtonViewMemo","React","abbreviateText","text","maxLength","BoxContext","createContext","useBoxContext","ctx","useContext","AdaptiveBox","style","displayMode","labelMode","labelHeightUnits","labelOverflow","viewBoxWidth","viewBoxHeight","minWidth","minHeight","debug","children","svgInfo","setSvgInfo","useState","labelInfo","setLabelInfo","registerSvg","useCallback","info","prev","next","registerLabel","labelHeightUnitsEffective","styleH","styleV","hAlign","vAlign","effectiveLabelPosition","isFill","showLabelSpace","mainContentGridRow","ctxValue","L","combinedHeightUnits","gridTemplateRows","svgPercent","labelPercent","Svg","onWheel","rest","useLayoutEffect","svgRef","useRef","useEffect","element","wheelHandler","e","preserveAspect","Label","position","align","shouldAbbreviate","labelContent","visibility","gridRow","HtmlOverlay","pointerEvents","useAudioParameter","value","onChange","parameterDef","userValueFormatter","userLabel","valueAsLabel","converter","AudioParameterConverter","valueRef","commitValue","newValue","normalized","midi","setNormalizedValue","newNormal","clamped","realValue","adjustValue","delta","sensitivity","currentReal","currentNormal","formattedValue","customValue","effectiveLabel","getDefaultNormalizedValue","resetToDefault","defaultNormalized","useBooleanInteraction","mode","onValueChange","disabled","userOnMouseDown","userOnMouseUp","userOnTouchStart","userOnTouchEnd","userOnKeyDown","userOnKeyUp","controllerRef","buttonElementRef","touchIsInsideRef","isGlobalPointerDownRef","BooleanInteractionController","handleGlobalMouseDown","handleGlobalTouchStart","_e","handleGlobalMouseUp","handleGlobalTouchEnd","handleGlobalTouchMove","controller","buttonElement","touch","elementAtPoint","isInside","handleMouseDown","handleMouseUp","handleMouseEnter","handleMouseLeave","setButtonElement","handleTouchStart","handleTouchEnd","handleTouchMove","handleKeyDown","handleKeyUp","useBooleanParameterResolution","parameter","paramId","label","latch","midiResolution","derivedParameter","AudioParameterFactory","BooleanControl","props","View","viewProps","htmlOverlay","labelPosition","labelAlign","viewBoxWidthUnits","viewBoxHeightUnits","onClick","onMouseDown","onMouseUp","onMouseEnter","onMouseLeave","fireChange","handleTouchEndForClick","syntheticEvent","handleTouchStartForClick","componentClassNames","classNames","CLASSNAMES","svgClassNames","svgStyle","handleMouseDownWithRef","handleTouchStartWithRef","jsxs","BooleanControl$1","useAdaptiveSize","adaptiveSize","size","componentType","orientation","sizeClassName","getSizeClassForComponent","sizeStyle","getSizeStyleForComponent","useThemableProps","color","vars","clampedRoundness","clampNormalized","generateTransparencyVariant","Button","themableStyle","adaptiveSizeStyle","Button_default","useArcAngle","openness","rotation","bipolar","positions","calculateArcAngles","RingArc","startAngle","endAngle","cx","cy","radius","thickness","strokeLinecap","isFullCircle","path","calculateArcPath","RingArc$1","ValueRing","fgArcStyle","bgArcStyle","valueToAngle","valueStartAngle","actualRadius","Fragment","ValueRing$1","RadialImage","imageHref","transform","RadialImage$1","RotaryImage","pivotX","pivotY","RotaryImage$1","KnobView","variant","svgOverlayRotary","svgOverlay","effectiveThickness","pixelThickness","translateKnobThickness","translateKnobRoundness","valueRing","finalLinecap","iconRadius","overlayContent","MemoKnobView","useContinuousInteraction","keyboardStep","DEFAULT_KEYBOARD_STEP","interactionMode","direction","wheelSensitivity","step","min","max","paramStep","editable","onDragStart","onDragEnd","userOnWheel","userOnDoubleClick","effectiveStep","effectiveSensitivity","base","DEFAULT_CONTINUOUS_SENSITIVITY","minSensitivityForStep","TARGET_PIXELS_PER_STEP","ContinuousInteractionController","ctrl","useContinuousParameterResolution","unit","scale","defaultValue","ContinuousControl","interactionDirection","interactionSensitivity","valueFormatter","isDragging","setIsDragging","isRecentlyActive","setIsRecentlyActive","activityTimerRef","handleDragStart","handleDragEnd","handleActivity","showValueAsLabel","wrappedAdjustValue","effectiveInteractionMode","effectiveDirection","interactiveProps","ContinuousControl$1","DEFAULT_OPENNESS","DEFAULT_ROTATION","Knob","clampedThickness","displayValueOverlay","Knob_default","useDiscreteInteraction","options","userOnClick","DiscreteInteractionController","cycleNext","stepNext","stepPrev","handleClick","useDiscreteParameterResolution","midiMapping","optionEls","visualContentMap","child","index","val","param","effectiveDefaultValue","getLabel","inferredOptions","DiscreteControl","effectiveValue","handleValueChange","DiscreteControl$1","CONTENT_WRAPPER_STYLE","ICON_WRAPPER_STYLE","CycleButton","renderOption","opt","content","wrapContent","node","CycleButton_default","LinearStrip","length","legacyRoundness","translateSliderRoundness","rectX","rectY","LinearStrip$1","ValueStrip","rectProps","cursorY","calculateLinearPosition","x","width","y","height","ValueStrip$1","LinearCursor","aspectRatio","isEllipse","cursorX","cursorYPos","LinearCursor$1","TICK_TRACK_SHIFT","LABEL_TRACK_SHIFT","SliderView","cursorSize","cursorAspectRatio","cursorRoundness","cursorImageHref","cursorClassName","cursorStyle","translateSliderThickness","stripPadding","cursorWidth","shouldRenderCursor","effectiveCursorRoundness","cursorHeight","cursorLength","valueStripLength","bgStripStyle","CSS_VARS","valueStripStyle","cursorStyleMemo","SLIDER_VIEWBOX","VerticalSliderViewComponent","VerticalSliderViewMemo","VerticalSliderView","HorizontalSliderViewComponent","HorizontalSliderViewMemo","HorizontalSliderView","SliderView_default","Slider","mergedClassName","ViewComponent","Slider_default","FilmstripImage","frameWidth","frameHeight","frameCount","frameRotation","invertValue","clampedValue","frameIndex","totalWidth","totalHeight","viewBoxX","viewBoxY","centerX","centerY","FilmstripImage$1","FilmstripView","FilmstripViewMemo","FilmStripContinuousControl","FilmStripContinuousControl_default","FilmStripDiscreteControl","FilmStripDiscreteControl_default","FilmStripBooleanControl","FilmStripBooleanControl_default","ImageKnobView","ImageKnobViewMemo","ImageKnob","ImageKnob_default","ImageRotarySwitch","ImageRotarySwitch_default","Image","Image$1","ImageSwitchView","imageHrefFalse","imageHrefTrue","ImageSwitchViewMemo","ImageSwitch","ImageSwitch_default","RadialHtmlOverlay","RadialHtmlOverlay_default","RevealingPath","resolution","numericResolution","calculatedOffset","RevealingPath_default","TickRing","count","renderTick","clampedOpenness","baseStart","baseEnd","totalAngle","numTicks","angleStep","results","r","n","i","angle","pos","polarToCartesian","pathData","commands","center","rx","ry","outer","inner","TickRing$1","LabelRing","labels","labelClassName","labelStyle","iconSize","isComponent","textRotation","normalizedRotation","rotationValue","offset","numericSize","LabelRing_default","useNoteInteraction","onNoteOn","onNoteOff","userOnPointerDown","userOnPointerMove","userOnPointerUp","userOnPointerCancel","NoteInteractionController","note","getNoteFromEvent","noteAttr","handlePointerDown","handlePointerMove","handlePointerUp","handlePointerCancel","notesCount","WHITE_KEY_NAMES","positiveModulo","number","modulus","Keys","nbKeys","startKey","octaveShift","notesOn","keyStyle","validNbKeys","onPointerDown","onPointerMove","onPointerUp","onPointerCancel","interactionStyle","keysDimensions","nbOctaves","keyRemainder","whiteKeysInRemainder","nbWhite","startKeyIndex","middleKeyIndex","octavesFromMiddle","octaveAdjustment","startOctave","whiteWidth","whiteHeight","blackWidth","blackXShift","blackHeight","outerStrokeWidth","innerStrokeWidth","halfInnerStrokeWidth","blackKeyShift","blackPass","translateKeysRoundness","resolvedColor","colorVariants","generateColorVariants","keyColors","activeNoteNumSet","createNoteNumSet","correctBlackPass","isNoteActive","noteNum","noteToNoteNum","renderWhiteKeys","startKeyChromatic","DIATONIC_TO_CHROMATIC","baseNoteNum","_","currentNoteIndex","octaveOffset","adjustedOffset","WHITE_KEY_TO_CHROMATIC","currentWhiteNote","noteNumToNote","strokeColor","fillColor","renderBlackKeys","octaveIndex","WHITE_KEY_POSITIONS","currentBlackNote","Keys_default","setThemeColor","setThemeRoundness","linecap","setTheme","theme","getThemeColor","getThemeRoundness"],"mappings":"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"}