{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "smooth-funnel",
  "title": "Smooth Funnel",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-angular/components/charts/smooth-funnel/smooth-funnel.component.ts",
      "content": "import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'\nimport { cn } from '@/lib/utils'\n\n// ─────────────────────────────────────────────────────────────────────────\n// SmoothFunnel — pure SVG, no ECharts\n// ─────────────────────────────────────────────────────────────────────────\n\nexport interface FunnelStage {\n  name: string\n  value: number\n  /** Optional override; defaults to chart-1..N from the registry palette. */\n  color?: string\n}\n\nexport interface SmoothFunnelSegment {\n  d: string\n  color: string\n  percent: number\n  name: string\n  value: number\n  labelX: number\n  labelY: number\n}\n\n// SVG geometry. Width/height are virtual (the SVG fits to container\n// via viewBox). The aspect ratio (W:H ≈ 4:1) matches the horizontal\n// \"flow\" layout consumers typically want for a 4-6 stage funnel; if\n// you need taller bands, override `height` and the curves stretch\n// vertically without distorting horizontally.\nexport const SF_W = 720\nexport const SF_H = 180\nconst SF_CY = SF_H / 2\n\nconst DEFAULT_COLORS = ['var(--chart-1)', 'var(--chart-2)', 'var(--chart-3)', 'var(--chart-4)', 'var(--chart-5)']\n\n/** Pure geometry: one S-curved band per stage, sized by share of the LARGEST stage. */\nexport function buildSmoothFunnelSegments(\n  stages: FunnelStage[],\n  minHeight: number,\n  colors: string[],\n): SmoothFunnelSegment[] {\n  const n = stages.length\n  if (n === 0) return []\n  const segW = SF_W / n\n  const max = Math.max(...stages.map((s) => s.value))\n  const pctOf = (v: number) => (max > 0 ? (v / max) * 100 : 0)\n  const heightFor = (pct: number) => Math.max((pct / 100) * SF_H, minHeight)\n\n  return stages.map((s, i) => {\n    const next = stages[i + 1] ?? s\n    const startPct = pctOf(s.value)\n    const endPct = pctOf(next.value)\n    const h0 = heightFor(startPct)\n    const h1 = heightFor(endPct)\n    const x0 = i * segW\n    const x1 = x0 + segW\n    const yTop0 = SF_CY - h0 / 2\n    const yTop1 = SF_CY - h1 / 2\n    const yBot0 = SF_CY + h0 / 2\n    const yBot1 = SF_CY + h1 / 2\n\n    // Cubic bezier control points at 38% / 62% of segment width produce\n    // a soft S-curve transition between stages rather than the\n    // trapezoidal default of an ECharts funnel.\n    const cx1 = x0 + segW * 0.38\n    const cx2 = x0 + segW * 0.62\n\n    const d = [\n      `M ${x0.toFixed(1)} ${yTop0.toFixed(1)}`,\n      `C ${cx1.toFixed(1)} ${yTop0.toFixed(1)}, ${cx2.toFixed(1)} ${yTop1.toFixed(1)}, ${x1.toFixed(1)} ${yTop1.toFixed(1)}`,\n      `L ${x1.toFixed(1)} ${yBot1.toFixed(1)}`,\n      `C ${cx2.toFixed(1)} ${yBot1.toFixed(1)}, ${cx1.toFixed(1)} ${yBot0.toFixed(1)}, ${x0.toFixed(1)} ${yBot0.toFixed(1)}`,\n      'Z',\n    ].join(' ')\n\n    return {\n      d,\n      color: s.color ?? colors[i % colors.length]!,\n      percent: startPct,\n      name: s.name,\n      value: s.value,\n      labelX: x0 + segW * 0.42,\n      labelY: SF_CY,\n    }\n  })\n}\n\n/**\n * Angular port of the UIPKGE SmoothFunnel: a horizontal, smoothly tapering\n * pure-SVG funnel. Each stage is a cubic-bezier band whose height is its\n * share of the largest stage, with a percent pill (share of the top stage).\n */\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-smooth-funnel, [ui-smooth-funnel]',\n  standalone: true,\n  host: {\n    '[attr.data-uipkge]': '\"\"',\n    '[attr.data-slot]': '\"smooth-funnel\"',\n    '[attr.tabindex]': '0',\n    '[style.height]': 'heightStyle',\n    '[class]': 'hostClass',\n  },\n  template: `\n    <svg\n      [attr.viewBox]=\"viewBox\"\n      class=\"block h-full w-full\"\n      preserveAspectRatio=\"none\"\n      role=\"img\"\n      [attr.aria-label]=\"ariaLabel || 'Chart'\"\n    >\n      @for (seg of segments; track $index) {\n        <g>\n          <path [attr.d]=\"seg.d\" [attr.fill]=\"seg.color\" />\n          @if (showLabels) {\n            <foreignObject [attr.x]=\"seg.labelX - 28\" [attr.y]=\"seg.labelY - 12\" width=\"56\" height=\"24\">\n              <div\n                class=\"bg-background text-foreground inline-flex h-6 items-center rounded-full border px-2 text-xs font-semibold shadow-sm\"\n              >\n                {{ pillText(seg.percent) }}%\n              </div>\n            </foreignObject>\n          }\n        </g>\n      }\n    </svg>\n  `,\n})\nexport class UiSmoothFunnelComponent implements OnChanges {\n  @Input() data: FunnelStage[] = []\n  /** Container height (px when numeric, raw CSS when string). Defaults to 240. */\n  @Input() height: number | string = 240\n  /** Show the percent pill on each stage. Defaults to true. */\n  @Input() showLabels = true\n  /** Minimum segment height in px so tail stages stay visible at tiny percents. Default 18. */\n  @Input() minHeight = 18\n  /** Optional fallback palette when `color` is omitted on a stage. */\n  @Input() colors: string[] = DEFAULT_COLORS\n  /** Accessible name announced for the chart image. Defaults to \"Chart\". */\n  @Input() ariaLabel?: string\n  @Input('class') className?: string\n\n  readonly viewBox = `0 0 ${SF_W} ${SF_H}`\n\n  segments: SmoothFunnelSegment[] = []\n\n  get hostClass(): string {\n    // `block` stands in for the React root <div>'s default display.\n    return cn('block focus-visible:ring-ring w-full focus-visible:ring-2 focus-visible:outline-none', this.className)\n  }\n\n  get heightStyle(): string {\n    return /^\\d+$/.test(String(this.height)) ? `${this.height}px` : String(this.height)\n  }\n\n  ngOnChanges(): void {\n    this.segments = buildSmoothFunnelSegments(this.data ?? [], this.minHeight, this.colors ?? DEFAULT_COLORS)\n  }\n\n  pillText(percent: number): number {\n    return Math.round(percent * 10) / 10\n  }\n}\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/charts/smooth-funnel/smooth-funnel.component.ts"
    },
    {
      "path": "packages/registry-angular/components/charts/smooth-funnel/index.ts",
      "content": "export {\n  UiSmoothFunnelComponent,\n  buildSmoothFunnelSegments,\n  type FunnelStage,\n  type SmoothFunnelSegment,\n} from './smooth-funnel.component'\n",
      "type": "registry:ui",
      "target": "~/src/app/components/ui/charts/smooth-funnel/index.ts"
    }
  ],
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [],
  "description": "Smoothly tapering SVG funnel with cubic-bezier transitions between stages. Pure SVG (no ECharts). Each stage is colored independently and shows its own percent pill; a minHeight floor keeps tail stages visible at tiny percents.",
  "categories": [
    "chart"
  ]
}