{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "core-cc-control-chrome",
  "type": "registry:ui",
  "title": "CC Control Chrome",
  "description": "One reusable frame for custom controls: parameter-path resolution, drag target, activity state, editable badge, and the shared MIDI context menu.",
  "dependencies": [
    "maruaudio@>=0.0.3 <0.1.0"
  ],
  "registryDependencies": [
    "https://pluginui.com/r/core-cc-binding-editor.json"
  ],
  "files": [
    {
      "path": "components/ui/cc-control-chrome.tsx",
      "content": "/**\n * Source: https://maru.audio/ui-kit/cc-control-chrome\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 { useContext, useEffect, useRef } from \"react\"\nimport type {\n  ButtonHTMLAttributes,\n  CSSProperties,\n  HTMLAttributes,\n  ReactNode,\n} from \"react\"\nimport {\n  CcBadge,\n  ccBadgeSlot,\n  ccDropTargetStyle,\n  useCcActivityPulse,\n  useCcBlink,\n  useCcDropTarget,\n  useCcMappingFlash,\n  useControlMidi,\n  type CcDropTargetBinding,\n  type CcParamRange,\n  type ControlMidiProps,\n} from \"maruaudio/ui/shared/cc-dnd\"\nimport {\n  CcControlMenuTarget,\n  type CcControlPresentation,\n} from \"@/components/ui/cc-binding-editor\"\nimport {\n  CcControlChromeClaimContext,\n  CcControlChromeDepth,\n} from \"maruaudio/ui/shared/cc-control-chrome-claim\"\nimport {\n  claimCcChromeOwner,\n  releaseCcChromeOwner,\n} from \"maruaudio/ui/shared/cc-control-chrome-core\"\n\nexport interface CcControlChromeOptions {\n  /** The parameter path resolved by the ambient control-midi provider. */\n  path?: string\n  /** Explicit MIDI wiring wins over a path-resolved binding. */\n  midi?: ControlMidiProps\n  /** Spoken name used by the target and learn affordance. */\n  label?: string\n  /** Parameter bounds used when an enum or boolean control is mapped. */\n  range?: CcParamRange\n}\n\nexport interface CcControlChrome {\n  /** The resolved binding, if this control is inside a CC-enabled host. */\n  cc: ControlMidiProps | undefined\n  /** Incoming CC activity pulse for the control's badge. */\n  pulse: boolean\n  /** A mapping just landed on this control (`useCcMappingFlash`). */\n  flash: boolean\n  /** Shared drop-target registration and interaction props. */\n  target: CcDropTargetBinding\n  /** Shared hover/learn ring for the control's wrapper. */\n  wrapperStyle: CSSProperties\n  /** The shared editable badge, or the plain fallback badge. */\n  badge: ReactNode\n  /**\n   * False when an outer caller already owns this control's target id.\n   *\n   * A caller that renders its own element must then omit `target.ref` and\n   * `target.targetProps`, because registering the id a second time destroys\n   * the first registration. `CcControlChromeTarget` handles this already; a\n   * caller keeping its own wrapper reads the flag directly.\n   */\n  owns: boolean\n}\n\n/**\n * Shared CC chrome for controls that own their visual body and badge layout.\n *\n * The caller remains responsible for its wrapper element and the badge's\n * placement. This keeps custom controls' existing DOM and visuals stable while\n * giving them one implementation of MIDI resolution, drop/learn behavior,\n * activity feedback, and badge fallback.\n */\nexport function useCcControlChrome({\n  path,\n  midi,\n  label,\n  range,\n}: CcControlChromeOptions): CcControlChrome {\n  const cc = useControlMidi({ path, midi, label, range })\n  const pulse = useCcActivityPulse(cc?.activity)\n\n  // Ownership is resolved during render because `useCcDropTarget`'s own\n  // registration effect must already know whether to run: deciding later, in an\n  // effect of our own, would let the loser register for one commit and take the\n  // id away from the winner in exactly the interleaving this guards against.\n  const claim = useContext(CcControlChromeClaimContext)\n  const ownerToken = useRef<symbol>(undefined as unknown as symbol)\n  if (ownerToken.current === undefined)\n    ownerToken.current = Symbol(\"cc-control-chrome\")\n  const owns = claimCcChromeOwner(claim.ledger, {\n    targetId: cc?.targetId,\n    owner: ownerToken.current,\n    depth: claim.depth,\n  })\n  useEffect(() => {\n    const ledger = claim.ledger\n    const targetId = cc?.targetId\n    const owner = ownerToken.current\n    return () => releaseCcChromeOwner(ledger, targetId, owner)\n  }, [claim.ledger, cc?.targetId])\n\n  const target = useCcDropTarget({\n    id: cc?.targetId,\n    path,\n    label: cc?.targetLabel ?? label,\n    onCcDrop: cc?.onAssign,\n    accepts: cc?.accepts,\n    enabled: Boolean(cc) && !cc?.disabled && owns,\n    controller: cc?.controller,\n    onLearnRequest: cc?.onLearn,\n    learning: cc?.learning ?? false,\n  })\n  const blink = useCcBlink(target.learning)\n  const flash = useCcMappingFlash(cc?.assignment, target, cc?.controller)\n\n  const badge = cc?.assignment\n    ? (cc.renderBadge?.(ccBadgeSlot(cc, cc.assignment, pulse)) ?? (\n        <CcBadge\n          assignment={cc.assignment}\n          onClear={cc.onClear}\n          active={pulse}\n          learning={cc.learning ?? false}\n          controller={cc.controller}\n          sourceTargetId={cc.targetId}\n          onMove={cc.onClear}\n          movesAtomically={cc.movesAtomically}\n          designer={cc.designer === true}\n          {...(cc.binding ? { binding: cc.binding } : {})}\n          {...(cc.range ? { range: cc.range } : {})}\n        />\n      ))\n    : null\n\n  return {\n    cc,\n    pulse,\n    flash,\n    target,\n    wrapperStyle: ccDropTargetStyle({ ...target, blink, flash }),\n    badge,\n    owns,\n  }\n}\n\nexport interface CcControlChromeTargetProps\n  extends\n    CcControlChromeOptions,\n    Omit<HTMLAttributes<HTMLElement>, \"children\" | \"style\">,\n    Pick<\n      ButtonHTMLAttributes<HTMLButtonElement>,\n      \"type\" | \"disabled\" | \"form\" | \"formAction\" | \"name\" | \"value\"\n    > {\n  children: ReactNode | ((chrome: CcControlChrome) => ReactNode)\n  /** Preserve the caller's semantic/layout frame instead of forcing a span. */\n  as?: \"button\" | \"div\" | \"span\" | \"label\"\n  /** Extra style merged after the shared hover/learn ring. */\n  style?: CSSProperties | ((chrome: CcControlChrome) => CSSProperties)\n  className?: string\n  /** Placed by the caller when it wants the badge somewhere of its own. */\n  renderBadge?:\n    false | ((badge: ReactNode, chrome: CcControlChrome) => ReactNode)\n  /** Text-editing controls keep the platform menu; see ContextMenuTarget. */\n  allowEditableContextMenu?: boolean\n  /** Name/value/path shown in the right-click menu before mapping actions. */\n  presentation?: CcControlPresentation\n  /** Echoed onto the wrapper so param-addressed tooling keeps working. */\n  \"data-audio-param-path\"?: string\n  readonly [dataAttribute: `data-${string}`]: unknown\n}\n\n/**\n * A control's CC chrome as one element: drop target, badge, and the right-click\n * menu that assigns, edits, learns or removes the binding.\n *\n * This exists because the menu was previously reachable only through ui-kit's\n * own Knob, Fader, XYPad and ToggleSwitch, each of which renders\n * `CcControlMenuTarget` itself. Every *other* CC-aware control — a plugin knob\n * that draws its own body, a host-side adapter wrapping one — called\n * `useCcControlChrome` and then spread `targetProps` onto a bare element, which\n * made it a drop target and nothing else. Right-clicking such a control fell\n * through to whatever menu the face surface offered, so the user met the\n * diagnostics menu (or the browser's) instead of the CC one.\n *\n * Routing those callers through here rather than asking each to import\n * `CcControlMenuTarget` keeps one answer to \"what is the chrome around a CC\n * control\": the hook resolves the binding, this renders it, and the ownership\n * claim above means a wrapper and the control it wraps cannot both register.\n * A caller that must keep its own wrapper element still can — it reads `owns`\n * from the hook and drops its registration when false.\n */\nexport function CcControlChromeTarget({\n  children,\n  as = \"span\",\n  style,\n  className,\n  renderBadge,\n  allowEditableContextMenu,\n  presentation,\n  \"data-audio-param-path\": paramPath,\n  path,\n  midi,\n  label,\n  range,\n  ...frameProps\n}: CcControlChromeTargetProps) {\n  const chrome = useCcControlChrome({ path, midi, label, range })\n  const { cc, target, wrapperStyle, badge, owns } = chrome\n  // A nested claimant still needs the resolved CC object for its value frame,\n  // but it must never leak a second badge/activity skin after standing down.\n  const renderedChrome: CcControlChrome = owns\n    ? chrome\n    : { ...chrome, pulse: false, flash: false, wrapperStyle: {}, badge: null }\n  const content =\n    typeof children === \"function\" ? children(renderedChrome) : children\n  const frameStyle = typeof style === \"function\" ? style(renderedChrome) : style\n  const defaultFrameStyle =\n    as === \"span\"\n      ? ({ position: \"relative\", display: \"inline-flex\" } as const)\n      : ({ position: \"relative\" } as const)\n\n  // Not CC-aware, or an outer claimant already owns this id: render the control\n  // untouched. The nested depth still goes up, so anything further in also\n  // stands down rather than claiming the id this one just declined.\n  if (!cc || !owns) {\n    const Frame = as\n    return (\n      <CcControlChromeDepth>\n        <Frame\n          {...frameProps}\n          {...(paramPath ? { \"data-audio-param-path\": paramPath } : {})}\n          className={className}\n          style={{ ...defaultFrameStyle, ...frameStyle }}\n        >\n          {content}\n        </Frame>\n      </CcControlChromeDepth>\n    )\n  }\n\n  const placedBadge = cc.assignment\n    ? renderBadge === false\n      ? null\n      : (renderBadge?.(badge, chrome) ?? (\n          <span style={{ position: \"absolute\", top: -5, right: -7, zIndex: 1 }}>\n            {badge}\n          </span>\n        ))\n    : null\n\n  return (\n    <CcControlMenuTarget\n      midi={cc}\n      target={target}\n      as={as}\n      presentation={presentation}\n      {...(allowEditableContextMenu !== undefined\n        ? { allowEditableContextMenu }\n        : {})}\n      style={{ ...defaultFrameStyle, ...wrapperStyle, ...frameStyle }}\n      {...frameProps}\n      {...(paramPath ? { \"data-audio-param-path\": paramPath } : {})}\n      {...(chrome.flash ? { \"data-uikit-cc-flash\": \"\" } : {})}\n      {...(className ? { className } : {})}\n    >\n      <CcControlChromeDepth>{content}</CcControlChromeDepth>\n      {placedBadge}\n    </CcControlMenuTarget>\n  )\n}\n",
      "type": "registry:component",
      "target": "@ui/cc-control-chrome.tsx"
    }
  ]
}
