{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "selection-halo",
  "type": "registry:component",
  "title": "Selection Halo",
  "description": "Local-user selection halo with corner handles + label slot for spatial canvases.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/selection-halo/selection-halo.tsx",
      "content": "\"use client\";\n\nimport {\n  type ComponentPropsWithoutRef,\n  forwardRef,\n  type ReactNode,\n} from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\n/**\n * Geometric bounds of the selection in container px.\n *\n * @public\n */\nexport type SelectionBounds = {\n  height: number;\n  width: number;\n  x: number;\n  y: number;\n};\n\n/**\n * Localizable strings.\n *\n * @public\n */\nexport type SelectionHaloLabels = {\n  /** Aria-label for the halo. Defaults to `\"Selection\"`. */\n  region?: string;\n};\n\nconst DEFAULT_LABELS = {\n  region: \"Selection\",\n} as const satisfies Required<SelectionHaloLabels>;\n\n/**\n * Props for {@link SelectionHalo}.\n *\n * @public\n */\nexport type SelectionHaloProps = {\n  /** Selection bounds in container pixels. */\n  bounds: SelectionBounds;\n  /** Optional label rendered above the top-left corner. */\n  label?: ReactNode;\n  /** Localizable strings. */\n  labels?: SelectionHaloLabels;\n  /** When `true`, the ring pulses (use during multi-step transitions). */\n  pulsing?: boolean;\n} & Omit<ComponentPropsWithoutRef<\"div\">, \"style\">;\n\n/**\n * Local-user selection halo for a canvas. Outlines a rectangular\n * region with a primary ring + handles at every corner. Pure\n * presentation — the host computes bounds (single object, group\n * bounding box, lasso result) and toggles the wrapper.\n *\n * Distinct from {@link SelectionPresence}: this halo represents the\n * **local** user's selection (with corner handles + label slot), while\n * `SelectionPresence` represents a remote participant's selection.\n *\n * @example\n * ```tsx\n * <SelectionHalo bounds={{ x: 80, y: 60, width: 200, height: 120 }} label=\"3 selected\" />\n * ```\n *\n * @public\n */\nexport const SelectionHalo = forwardRef<HTMLDivElement, SelectionHaloProps>(\n  (props, ref) => {\n    const {\n      bounds,\n      className,\n      label,\n      labels,\n      pulsing = false,\n      ...rest\n    } = props;\n    const resolvedLabels = { ...DEFAULT_LABELS, ...labels };\n    return (\n      <div\n        aria-label={resolvedLabels.region}\n        className={cn(\n          \"pointer-events-none absolute z-20 rounded-md ring-2 ring-primary\",\n          pulsing ? \"animate-pulse\" : \"\",\n          className,\n        )}\n        data-pulsing={pulsing ? \"true\" : undefined}\n        data-selection-halo\n        ref={ref}\n        style={{\n          height: `${bounds.height.toString()}px`,\n          left: `${bounds.x.toString()}px`,\n          top: `${bounds.y.toString()}px`,\n          width: `${bounds.width.toString()}px`,\n        }}\n        {...rest}\n      >\n        {([\"nw\", \"ne\", \"se\", \"sw\"] as const).map((corner) => (\n          <span\n            aria-hidden=\"true\"\n            className={cn(\n              \"absolute size-2 rounded-sm border-2 border-primary bg-background\",\n              corner === \"nw\" && \"-left-1 -top-1\",\n              corner === \"ne\" && \"-right-1 -top-1\",\n              corner === \"se\" && \"-bottom-1 -right-1\",\n              corner === \"sw\" && \"-bottom-1 -left-1\",\n            )}\n            data-handle-corner={corner}\n            key={corner}\n          />\n        ))}\n        {label ? (\n          <span\n            className=\"absolute -top-6 left-0 inline-flex items-center rounded-md bg-primary px-1.5 py-0.5 text-[10px] font-semibold text-primary-foreground shadow-sm\"\n            data-selection-label\n          >\n            {label}\n          </span>\n        ) : null}\n      </div>\n    );\n  },\n);\nSelectionHalo.displayName = \"SelectionHalo\";\n",
      "type": "registry:component"
    }
  ],
  "version": "0.2.1",
  "stability": "stable"
}
