{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "core-parameter-select",
  "type": "registry:ui",
  "title": "Parameter Select",
  "description": "Schema-backed enum selection with Base UI and validated presentation groups.",
  "dependencies": [
    "@base-ui/react",
    "maruaudio@>=0.0.3 <0.1.0"
  ],
  "registryDependencies": [
    "https://pluginui.com/r/core-cc-control-chrome.json"
  ],
  "files": [
    {
      "path": "components/ui/parameter-select.tsx",
      "content": "/**\n * Source: https://maru.audio/ui-kit/parameter-select\n * Copyright © [Maru](https://maru.audio)\n *\n * Maru UI Kit — https://maru.audio/ui-kit\n *\n * Installed from the Maru UI registry. This file is yours now: edit it\n * freely, it will not be overwritten unless you re-run the add command.\n *\n * Keep this item’s companion files and CSS imports together. Dependencies\n * declared by the registry are installed with the source.\n */\nimport { useRef, useId, useState, type ReactNode } from \"react\"\nimport { Select } from \"@base-ui/react/select\"\nimport {\n  useBoundParameterControl,\n  parameterArgument,\n  faceParamBinding,\n  useFaceParamEditGesture,\n  type ParameterBinding,\n  type ParameterSource,\n} from \"maruaudio/ui/shared/parameter-binding\"\nimport { CcControlChromeTarget } from \"@/components/ui/cc-control-chrome\"\nimport { token } from \"maruaudio/ui/shared/theme\"\nimport { ConsoleIcon } from \"maruaudio/ui/shared/console-icons\"\nimport {\n  OVERLAY_POSITIONER_DEFAULTS,\n  overlayPopupStyle,\n  useOverlayLayer,\n} from \"maruaudio/ui/shared/overlay-container\"\n\nexport interface FaceParamSelectOption {\n  readonly value: number\n  readonly label: string\n  /** Present but unavailable choices remain visible and cannot be selected. */\n  readonly disabled?: boolean\n  /** Accessible description when the option is disabled. */\n  readonly disabledReason?: string\n}\n\n/** A labelled family of enum choices in a styled module-face menu. */\nexport interface FaceParamSelectGroup {\n  readonly label: string\n  readonly options: readonly FaceParamSelectOption[]\n}\n\nexport interface FaceParamSelectProps {\n  readonly positionerProps?: Omit<Select.Positioner.Props, \"children\">\n  readonly face: ParameterSource\n  /** Stable schema path. The control derives its value and edit gesture here. */\n  readonly path: string\n  readonly label: string\n  readonly groups: readonly FaceParamSelectGroup[]\n  readonly disabled?: boolean\n  /** Shown only when a host reports an enum value that is no longer admitted. */\n  readonly unknownValueLabel?: (value: number) => string\n  /** Module-scoped classes only skin the shared structure. */\n  readonly className?: string\n  readonly triggerClassName?: string\n  readonly popupClassName?: string\n  /** Visual presentation only; plain labels still own accessibility and typeahead. */\n  readonly renderValue?: (option: FaceParamSelectOption) => ReactNode\n  readonly renderOption?: (option: FaceParamSelectOption) => ReactNode\n}\n\nexport type ParameterSelectOption = FaceParamSelectOption\nexport type ParameterSelectGroup = FaceParamSelectGroup\nexport type ParameterSelectProps = Omit<\n  FaceParamSelectProps,\n  \"face\" | \"path\" | \"label\" | \"groups\"\n> &\n  ParameterBinding<\"enum\"> & {\n    label?: string\n    groups?: readonly FaceParamSelectGroup[]\n  }\n\n/** Descriptor defaults and validated grouping, with existing Base UI interaction. */\nexport function ParameterSelect({\n  parameter,\n  source,\n  path,\n  label,\n  groups,\n  ...appearance\n}: ParameterSelectProps) {\n  const {\n    control: binding,\n    descriptor,\n    source: resolved,\n  } = useBoundParameterControl(parameterArgument(parameter, path), source)\n  if (descriptor.type !== \"enum\")\n    throw new TypeError(`Selector requires an enum parameter: ${binding.path}`)\n  const options = descriptor.options ?? []\n  const actualGroups = groups ?? [\n    {\n      label: descriptor.group,\n      options: options.map((label, value) => ({ label, value })),\n    },\n  ]\n  const seen = new Set<number>()\n  for (const option of actualGroups.flatMap((group) => group.options)) {\n    if (\n      !Number.isInteger(option.value) ||\n      option.value < 0 ||\n      option.value >= options.length ||\n      seen.has(option.value)\n    )\n      throw new TypeError(\n        `Invalid selector option for ${binding.path}: ${option.value}`\n      )\n    seen.add(option.value)\n  }\n  return (\n    <FaceParamSelect\n      {...appearance}\n      face={resolved!}\n      path={binding.path}\n      label={label ?? binding.label}\n      groups={actualGroups}\n    />\n  )\n}\n\nexport const FACE_PARAM_SELECT_PORTAL_VARIABLES = [\n  \"--uikit-control-h-select\",\n  \"--uikit-accent\",\n  \"--uikit-surface\",\n  \"--uikit-cap\",\n  \"--uikit-border\",\n  \"--uikit-text\",\n  \"--uikit-text-muted\",\n  \"--uikit-focus\",\n  \"--uikit-font\",\n  \"--uikit-menu-surface\",\n  \"--uikit-menu-text\",\n  \"--uikit-menu-border\",\n  \"--uikit-menu-hover\",\n] as const\n\n/**\n * A controlled grouped parameter selector for module faces.\n *\n * `FaceSelect` stays the compact native control for existing modules. This\n * narrow companion uses Base UI's select semantics when a face needs a\n * grouped, portaled popup while preserving the same schema path, CC target and\n * host edit lifecycle as every other parameter control.\n */\nexport function FaceParamSelect({\n  face,\n  path,\n  label,\n  groups,\n  disabled = false,\n  unknownValueLabel = (value) => `Unavailable (${value})`,\n  className,\n  triggerClassName,\n  popupClassName,\n  positionerProps,\n  renderValue,\n  renderOption,\n}: FaceParamSelectProps) {\n  const binding = faceParamBinding(face, path)\n  const edit = useFaceParamEditGesture(face)\n  const rootRef = useRef<HTMLDivElement | null>(null)\n  const triggerId = useId()\n  const [open, setOpen] = useState(false)\n  const layer = useOverlayLayer(\n    rootRef,\n    open,\n    FACE_PARAM_SELECT_PORTAL_VARIABLES\n  )\n  const options = groups.flatMap((group) => group.options)\n  // `readModuleFaceValue` deliberately falls back to a descriptor default for\n  // invalid host values. A selector must still report the raw finite snapshot:\n  // otherwise malformed `1.5` silently appears as a valid enum default.\n  const rawHostValue = face.values[path]\n  const displayedValue =\n    typeof rawHostValue === \"number\" && Number.isFinite(rawHostValue)\n      ? rawHostValue\n      : binding.value\n  const current = options.find((option) => option.value === displayedValue)\n  const currentValue = String(displayedValue)\n  const selectedLabel = current?.label ?? unknownValueLabel(displayedValue)\n  const unavailable = disabled || options.every((option) => option.disabled)\n  // Removing a recalled, disabled choice during selection changes Base UI's\n  // registered item collection. Recreate that collection atomically; otherwise\n  // its reconciliation can emit the first option after the user's chosen value.\n  const collectionKey = options\n    .map((option) => `${option.value}:${option.disabled ? 1 : 0}`)\n    .join(\"|\")\n\n  return (\n    <CcControlChromeTarget\n      path={path}\n      label={label}\n      range={{ min: binding.min, max: binding.max, step: binding.step }}\n      presentation={{\n        label,\n        value: selectedLabel,\n        path,\n        range: options.map((option) => option.label).join(\" · \"),\n      }}\n      allowEditableContextMenu\n      data-audio-param-path={path}\n      className={className}\n    >\n      <Select.Root<string>\n        key={collectionKey}\n        value={currentValue}\n        disabled={unavailable}\n        modal={false}\n        items={options.map((option) => ({\n          value: String(option.value),\n          label: option.label,\n        }))}\n        open={open}\n        onOpenChange={setOpen}\n        onValueChange={(next) => {\n          if (next === null) return\n          const value = Number(next)\n          const option = options.find((candidate) => candidate.value === value)\n          if (!option || option.disabled || !Number.isFinite(value)) return\n          edit.change(path, value)\n          edit.end(path, value)\n        }}\n      >\n        <div\n          ref={rootRef}\n          data-face-param-select=\"\"\n          style={{\n            minWidth: 0,\n            fontFamily:\n              \"var(--uikit-font, ui-sans-serif, system-ui, sans-serif)\",\n          }}\n        >\n          <Select.Trigger\n            id={triggerId}\n            aria-label={`${label}: ${selectedLabel}`}\n            data-face-param-select-trigger=\"\"\n            className={`uikit-control${triggerClassName ? ` ${triggerClassName}` : \"\"}`}\n            style={{\n              boxSizing: \"border-box\",\n              display: \"flex\",\n              width: \"100%\",\n              minHeight: \"var(--uikit-control-h-select, 38px)\",\n              alignItems: \"center\",\n              justifyContent: \"space-between\",\n              gap: 8,\n              padding: \"0 10px 0 12px\",\n              border: `1px solid var(--uikit-border, ${token.border})`,\n              borderRadius: \"var(--uikit-radius, 8px)\",\n              background: \"var(--uikit-surface, transparent)\",\n              color: \"var(--uikit-text, currentColor)\",\n              font: \"inherit\",\n              fontSize: 12,\n              fontWeight: 650,\n              letterSpacing: \"0.06em\",\n              textAlign: \"left\",\n              textTransform: \"uppercase\",\n              cursor: unavailable ? \"not-allowed\" : \"pointer\",\n            }}\n          >\n            <Select.Value>\n              {current && renderValue ? renderValue(current) : selectedLabel}\n            </Select.Value>\n            <Select.Icon\n              aria-hidden=\"true\"\n              style={{\n                display: \"inline-flex\",\n                color: \"var(--uikit-text-muted, currentColor)\",\n                fontSize: 12,\n              }}\n            >\n              <ConsoleIcon name=\"chevron-down\" size={14} />\n            </Select.Icon>\n          </Select.Trigger>\n          <Select.Portal container={layer.container}>\n            <Select.Positioner\n              {...OVERLAY_POSITIONER_DEFAULTS}\n              side=\"bottom\"\n              align=\"center\"\n              sideOffset={6}\n              collisionPadding={8}\n              {...positionerProps}\n              style={(state) => ({\n                ...layer.theme,\n                zIndex: \"var(--uikit-z-overlay, 2147483647)\",\n                outline: \"none\",\n                ...(typeof positionerProps?.style === \"function\"\n                  ? positionerProps.style(state)\n                  : positionerProps?.style),\n              })}\n            >\n              <Select.Popup\n                data-face-param-select-popup=\"\"\n                className={popupClassName}\n                style={{\n                  ...overlayPopupStyle({\n                    maxWidth: \"22rem\",\n                    maxHeight: \"22rem\",\n                  }),\n                  minWidth: \"min(var(--anchor-width), var(--available-width))\",\n                  padding: 5,\n                  border: `1px solid var(--uikit-menu-border, ${token.border})`,\n                  borderRadius: \"var(--uikit-radius, 8px)\",\n                  background: `var(--uikit-menu-surface, ${token.cap})`,\n                  color: `var(--uikit-menu-text, ${token.text})`,\n                  boxShadow: \"0 18px 42px rgb(0 0 0 / .38)\",\n                  fontFamily:\n                    \"var(--uikit-font, ui-sans-serif, system-ui, sans-serif)\",\n                }}\n              >\n                <Select.List aria-label={`${label} choices`}>\n                  {groups.map((group) => (\n                    <Select.Group\n                      key={group.label}\n                      data-face-param-select-group={group.label}\n                    >\n                      <Select.GroupLabel\n                        data-face-param-select-group-label=\"\"\n                        style={{\n                          display: \"block\",\n                          padding: \"7px 8px 3px\",\n                          color: \"var(--uikit-text-muted, currentColor)\",\n                          fontSize: 10,\n                          fontWeight: 700,\n                          letterSpacing: \"0.1em\",\n                          textTransform: \"uppercase\",\n                        }}\n                      >\n                        {group.label}\n                      </Select.GroupLabel>\n                      {group.options.map((option) => {\n                        const optionValue = String(option.value)\n                        return (\n                          <Select.Item\n                            key={optionValue}\n                            value={optionValue}\n                            label={option.label}\n                            disabled={option.disabled}\n                            data-face-param-select-option=\"\"\n                            data-unavailable-reason={option.disabledReason}\n                            aria-description={option.disabledReason}\n                            className=\"uikit-control\"\n                            style={{\n                              boxSizing: \"border-box\",\n                              display: \"grid\",\n                              gridTemplateColumns: \"14px minmax(0, 1fr)\",\n                              minHeight: \"var(--uikit-control-h-select, 32px)\",\n                              alignItems: \"center\",\n                              gap: 6,\n                              padding: \"0 10px 0 6px\",\n                              borderRadius: 5,\n                              color: option.disabled\n                                ? \"var(--uikit-text-muted, currentColor)\"\n                                : \"var(--uikit-menu-text, currentColor)\",\n                              cursor: option.disabled\n                                ? \"not-allowed\"\n                                : \"pointer\",\n                              fontSize: 12,\n                              opacity: option.disabled ? 0.55 : 1,\n                            }}\n                          >\n                            <Select.ItemIndicator\n                              aria-hidden=\"true\"\n                              style={{\n                                display: \"inline-flex\",\n                                gridColumn: 1,\n                                color: \"var(--uikit-accent, currentColor)\",\n                              }}\n                            >\n                              ✓\n                            </Select.ItemIndicator>\n                            <Select.ItemText\n                              data-face-param-select-option-text=\"\"\n                              style={{\n                                gridColumn: 2,\n                                minWidth: 0,\n                                overflow: \"hidden\",\n                                textOverflow: \"ellipsis\",\n                                whiteSpace: \"nowrap\",\n                              }}\n                            >\n                              {renderOption\n                                ? renderOption(option)\n                                : option.label}\n                            </Select.ItemText>\n                          </Select.Item>\n                        )\n                      })}\n                    </Select.Group>\n                  ))}\n                </Select.List>\n              </Select.Popup>\n            </Select.Positioner>\n          </Select.Portal>\n        </div>\n      </Select.Root>\n    </CcControlChromeTarget>\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/parameter-select.tsx"
    }
  ]
}
