@acronis-platform/ui-react 5.4.4 → 5.5.0

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.
Files changed (62) hide show
  1. package/dist/components/ui/area-chart/area-chart.js +114 -113
  2. package/dist/components/ui/area-chart/area-chart.js.map +1 -1
  3. package/dist/components/ui/bar-chart/bar-chart.js +53 -49
  4. package/dist/components/ui/bar-chart/bar-chart.js.map +1 -1
  5. package/dist/components/ui/chart/chart.js +108 -97
  6. package/dist/components/ui/chart/chart.js.map +1 -1
  7. package/dist/components/ui/composed-chart/composed-chart.js +134 -130
  8. package/dist/components/ui/composed-chart/composed-chart.js.map +1 -1
  9. package/dist/components/ui/confidence-cone/confidence-cone.js +36 -35
  10. package/dist/components/ui/confidence-cone/confidence-cone.js.map +1 -1
  11. package/dist/components/ui/dialog/dialog.js +69 -67
  12. package/dist/components/ui/dialog/dialog.js.map +1 -1
  13. package/dist/components/ui/funnel-chart/funnel-chart.js +38 -37
  14. package/dist/components/ui/funnel-chart/funnel-chart.js.map +1 -1
  15. package/dist/components/ui/line-chart/line-chart.js +35 -34
  16. package/dist/components/ui/line-chart/line-chart.js.map +1 -1
  17. package/dist/components/ui/pie-chart/pie-chart.js +28 -27
  18. package/dist/components/ui/pie-chart/pie-chart.js.map +1 -1
  19. package/dist/components/ui/radar-chart/radar-chart.js +68 -65
  20. package/dist/components/ui/radar-chart/radar-chart.js.map +1 -1
  21. package/dist/components/ui/radial-bar-chart/radial-bar-chart.js +64 -63
  22. package/dist/components/ui/radial-bar-chart/radial-bar-chart.js.map +1 -1
  23. package/dist/components/ui/scatter-chart/scatter-chart.js +26 -26
  24. package/dist/components/ui/scatter-chart/scatter-chart.js.map +1 -1
  25. package/dist/components/ui/treemap/treemap.js +22 -22
  26. package/dist/components/ui/treemap/treemap.js.map +1 -1
  27. package/dist/index.js +106 -106
  28. package/dist/llms/area-chart.md +1 -0
  29. package/dist/llms/bar-chart.md +1 -0
  30. package/dist/llms/chart.md +10 -0
  31. package/dist/llms/composed-chart.md +1 -0
  32. package/dist/llms/confidence-cone.md +1 -0
  33. package/dist/llms/funnel-chart.md +1 -0
  34. package/dist/llms/line-chart.md +1 -0
  35. package/dist/llms/pie-chart.md +1 -0
  36. package/dist/llms/radar-chart.md +1 -0
  37. package/dist/llms/radial-bar-chart.md +1 -0
  38. package/dist/llms/scatter-chart.md +1 -0
  39. package/dist/llms/treemap.md +1 -0
  40. package/dist/llms.txt +1 -1
  41. package/dist/react.js +106 -106
  42. package/dist/src/components/ui/area-chart/area-chart.d.ts +6 -1
  43. package/dist/src/components/ui/bar-chart/bar-chart.d.ts +8 -2
  44. package/dist/src/components/ui/category-bar/category-bar.d.ts +1 -1
  45. package/dist/src/components/ui/chart/__stories__/palette-control.d.ts +16 -0
  46. package/dist/src/components/ui/chart/chart.d.ts +12 -1
  47. package/dist/src/components/ui/chart/index.d.ts +1 -1
  48. package/dist/src/components/ui/composed-chart/composed-chart.d.ts +6 -1
  49. package/dist/src/components/ui/confidence-cone/confidence-cone.d.ts +6 -1
  50. package/dist/src/components/ui/funnel-chart/funnel-chart.d.ts +6 -1
  51. package/dist/src/components/ui/grid/grid.d.ts +1 -1
  52. package/dist/src/components/ui/line-chart/line-chart.d.ts +6 -1
  53. package/dist/src/components/ui/pie-chart/pie-chart.d.ts +6 -1
  54. package/dist/src/components/ui/progress-circle/progress-circle.d.ts +1 -1
  55. package/dist/src/components/ui/radar-chart/radar-chart.d.ts +9 -1
  56. package/dist/src/components/ui/radial-bar-chart/radial-bar-chart.d.ts +6 -1
  57. package/dist/src/components/ui/scatter-chart/scatter-chart.d.ts +6 -1
  58. package/dist/src/components/ui/spinner/spinner.d.ts +1 -1
  59. package/dist/src/components/ui/stack/stack.d.ts +1 -1
  60. package/dist/src/components/ui/treemap/treemap.d.ts +6 -1
  61. package/dist/ui-react.css +1 -1
  62. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"funnel-chart.js","names":[],"sources":["../../../../src/components/ui/funnel-chart/funnel-chart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n Cell,\n Funnel,\n FunnelChart as RechartsFunnelChart,\n LabelList,\n} from 'recharts';\nimport type { LegendPayload } from 'recharts/types/component/DefaultLegendContent';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartLegendContent,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n resolveChartColors,\n resolveLabelFillClass,\n toCssKey,\n type ChartAnimationProps,\n type ChartConfig,\n type ChartPalette,\n type TickFormatter,\n} from '../chart';\n\n// A typed recharts composition over the shared `Chart` primitives. The single\n// CVA axis is the design's Funnel-chart variant set: `lastShape` (does the\n// funnel narrow to a point — `triangle` — or end flat — `rectangle`). The class\n// stays empty because recharts' SVG — not CSS — draws the funnel: `lastShape`\n// drives the `<Funnel>`'s `lastShapeType`. CVA is kept so the variant set is a\n// first-class, spec-conformant part of the API (matched against ui-spec's\n// api.yaml enums) and exposed via `VariantProps`; the resolved value is also\n// mirrored onto `data-last-shape`.\nconst funnelChartVariants = cva('', {\n variants: {\n lastShape: {\n triangle: '',\n rectangle: '',\n },\n },\n defaultVariants: {\n lastShape: 'triangle',\n },\n});\n\n/** Where a stage's label sits relative to its segment. */\nexport type FunnelChartLabelPosition = 'right' | 'left' | 'inside';\n\n/** What a stage's label says. */\nexport type FunnelChartLabelFormat =\n | 'name'\n | 'value'\n | 'percent'\n | 'name-value'\n | 'name-percent'\n | 'value-percent';\n\n/** Override for a single stage, keyed by its `nameKey` value. */\nexport interface FunnelChartStageSettings {\n /**\n * Paint this stage with a colour of its own instead of its `palette` stop —\n * the same per-item escape hatch `PieChart`'s `sliceSettings` offers.\n * Reference an existing semantic `--ui-*` token.\n */\n color?: string;\n /**\n * Drop the stage from the funnel — and from the legend, labels and the\n * conversion percentages, which are computed over the visible stages only.\n */\n hidden?: boolean;\n}\n\n// recharts' `LabelList` positions that a funnel trapezoid understands. `inside`\n// is recharts' `center`: a trapezoid has no `insideLeft`/`insideTop` anchor, so\n// centring is the only on-segment placement. `outside` is not offered — for a\n// funnel it would mean \"beside the segment\", which is what `right`/`left`\n// already are, and recharts' polar `outside` doesn't apply to a trapezoid.\nconst LABEL_POSITION: Record<\n FunnelChartLabelPosition,\n 'right' | 'left' | 'center'\n> = {\n right: 'right',\n left: 'left',\n inside: 'center',\n};\n\n/**\n * Where the value labels sit when the caller doesn't say: opposite the names, so\n * the two lists never stack on the same edge. `inside` names sit on the segments\n * and leave both edges free, so the values take the default `right`.\n *\n * A static default can't do this — `valuePosition=\"left\"` beside\n * `labelPosition=\"left\"` draws both lists at the same anchor, overprinting the\n * name with its own value.\n */\nexport function funnelChartOppositeSide(\n labelPosition: FunnelChartLabelPosition\n): FunnelChartLabelPosition {\n switch (labelPosition) {\n case 'right':\n return 'left';\n case 'left':\n return 'right';\n case 'inside':\n return 'right';\n }\n}\n\n// Synthetic row fields the two label lists read. A `LabelList` resolves its text\n// from a `dataKey`, and its `formatter` only ever sees that one field's value —\n// so a composite label (\"Signups: 52.0%\") has to be composed onto the row before\n// recharts sees it, the same way `fill` already is.\nconst STAGE_LABEL_KEY = '__stageLabel';\nconst STAGE_VALUE_LABEL_KEY = '__stageValueLabel';\n\n// Which stage this row is. recharts hands a custom `shape` the data row as\n// `payload` but no index, and the first stage is the one that must *not* be\n// pushed down — its top edge is the top of the funnel.\nconst STAGE_INDEX_KEY = '__stageIndex';\n\n// Hover highlight for `showActiveShape` — an outline rather than a fill change,\n// so the segment keeps the colour that ties it to its legend entry and label.\nconst ACTIVE_STROKE = 'var(--ui-border-on-surface-border-active)';\nconst ACTIVE_STROKE_WIDTH = 2;\n\n/**\n * The design's funnel plot: a 120×120 square beside its legend — the same\n * geometry `ChartDonut` uses in Figma, which is why `PieChart` and\n * `RadialBarChart` size their plot exactly this way too.\n *\n * The *component* is still parent-responsive: the root is a flex row whose\n * legend column takes every pixel the plot doesn't, so a funnel widens with its\n * widget instead of the plot growing into a tall, narrow wedge.\n *\n * Figma: `8811:175245` → `Chart` is `size-[120px]`, `gap-[16px]`, legend\n * `flex-[1_0_0]`.\n */\nconst PLOT_SIZE_CLASS = 'size-[120px]';\n\n/**\n * Surface showing between two stages, in px. recharts draws the stages flush —\n * `Funnel` has no gap prop and `Trapezoid` no radius — so both come from the\n * custom `shape` below.\n *\n * Figma `8811:175245` stacks the stages at y = 4 / 34 / 64 / 94 with shapes 28\n * tall: a 2px gap, three times over for four stages.\n */\nconst STAGE_GAP = 2;\n\n/** Corner rounding on the two upper edges of each stage (Figma: ~2px clip along the top edge). */\nconst STAGE_RADIUS = 2;\n\n/**\n * Corner rounding on the two lower edges of each stage.\n *\n * Figma's bottom corners clip ~0.85px — significantly less than the upper\n * corners. The asymmetry is intentional: the diagonal-meets-flat-bottom\n * transition is shallow, so a smaller radius keeps it from looking over-rounded\n * at the narrow tail of the funnel. 1px is the nearest integer match.\n */\nconst STAGE_BOTTOM_RADIUS = 1;\n\n/**\n * The plot-area inset when nothing sits beside the funnel. Figma's widest stage\n * spans 102.67 of the 120px box and the stack runs y=4 → ~115.7, i.e. ~8px of\n * horizontal and 4px of vertical breathing room.\n */\nconst PLOT_MARGIN = { top: 4, right: 8, bottom: 4, left: 8 };\n\n/**\n * The palette Figma paints the funnel from: the sequential blue ramp, whose\n * stops read as one quantity falling down the funnel. Every other chart keeps\n * the shared categorical default — a funnel's stages are an *ordered* series,\n * which is what a sequential ramp is for.\n *\n * Figma `8811:175245` uses `dataviz/sequential/blue/{2,4,6,8}`.\n */\nexport const FUNNEL_CHART_DEFAULT_PALETTE: ChartPalette = Object.freeze({\n type: 'sequential',\n ramp: 'blue',\n});\n\ntype StagePoint = readonly [number, number];\n\nconst distance = (a: StagePoint, b: StagePoint) =>\n Math.hypot(b[0] - a[0], b[1] - a[1]);\n\n/** A point `d` px from `from` along the segment toward `to`. */\nfunction along(from: StagePoint, to: StagePoint, d: number): StagePoint {\n const length = distance(from, to);\n if (length === 0) return from;\n return [\n from[0] + ((to[0] - from[0]) * d) / length,\n from[1] + ((to[1] - from[1]) * d) / length,\n ];\n}\n\n// Trimmed to 3dp so the emitted `d` is stable across platforms — a raw float\n// makes every visual-regression baseline and DOM assertion renderer-dependent.\nconst coord = ([x, y]: StagePoint) =>\n `${Number(x.toFixed(3))},${Number(y.toFixed(3))}`;\n\n/**\n * A closed polygon with rounded corners, as an SVG path.\n *\n * Each corner is trimmed back along both of its edges and bridged with a\n * quadratic curve through the original vertex. The radius is clamped to half of\n * either adjacent edge, so two corners can never overrun each other on a short\n * edge — which is the normal case at the bottom of a funnel, where the last\n * stages are only a few px wide.\n */\nfunction roundedPolygonPath(\n points: readonly StagePoint[],\n radius: number | readonly number[]\n): string {\n const count = points.length;\n if (count < 3) return '';\n\n let path = '';\n for (let i = 0; i < count; i++) {\n const r = typeof radius === 'number' ? radius : radius[i];\n const previous = points[(i - 1 + count) % count];\n const current = points[i];\n const next = points[(i + 1) % count];\n const corner = Math.max(\n 0,\n Math.min(\n r,\n distance(previous, current) / 2,\n distance(current, next) / 2\n )\n );\n const entry = along(current, previous, corner);\n const exit = along(current, next, corner);\n path += `${i === 0 ? 'M' : ' L'} ${coord(entry)}`;\n path += ` Q ${coord(current)} ${coord(exit)}`;\n }\n return `${path} Z`;\n}\n\n/**\n * The `d` for one funnel stage — recharts' trapezoid geometry with the design's\n * rounded corners.\n *\n * `x`/`y` is the top-left of the *upper* edge and the shape is centred on\n * `x + upperWidth / 2`, matching recharts' own `getTrapezoidPath`.\n *\n * Either edge can collapse to a single rounded apex rather than a zero-length\n * one — the `triangle` last shape narrows to a point at the bottom, and a\n * `reversed` funnel stands that same triangle on its head at the top.\n */\nexport function funnelChartStagePath(options: {\n x: number;\n y: number;\n upperWidth: number;\n lowerWidth: number;\n height: number;\n /**\n * Uniform corner radius — applies to all four corners. Overridden per-side\n * by `topRadius` / `bottomRadius`. When absent, defaults to\n * `STAGE_RADIUS` (top) and `STAGE_BOTTOM_RADIUS` (bottom).\n */\n radius?: number;\n /** Radius for the two upper corners (default: `STAGE_RADIUS = 2`). */\n topRadius?: number;\n /**\n * Radius for the two lower corners (default: `STAGE_BOTTOM_RADIUS = 1`).\n *\n * Figma's bottom corners clip ~0.85px — much smaller than the upper ones —\n * so the default is intentionally lower than `topRadius`.\n */\n bottomRadius?: number;\n}): string {\n const { x, y, upperWidth, lowerWidth, height } = options;\n // `radius` is a backward-compat shorthand for all corners.\n // Per-side props win; if only `radius` is given it applies to both sides.\n // If nothing is given, use the asymmetric Figma defaults.\n const uniformRadius = options.radius;\n const topRadius = options.topRadius ?? uniformRadius ?? STAGE_RADIUS;\n const bottomRadius =\n options.bottomRadius ?? uniformRadius ?? STAGE_BOTTOM_RADIUS;\n\n if (height <= 0 || (upperWidth <= 0 && lowerWidth <= 0)) return '';\n\n const centre = x + upperWidth / 2;\n const upper: StagePoint[] =\n upperWidth <= 0\n ? [[centre, y]]\n : [\n [x, y],\n [x + upperWidth, y],\n ];\n const lower: StagePoint[] =\n lowerWidth <= 0\n ? [[centre, y + height]]\n : [\n [centre + lowerWidth / 2, y + height],\n [centre - lowerWidth / 2, y + height],\n ];\n\n // Upper points get topRadius, lower points get bottomRadius — matching\n // Figma's intentional asymmetry (the diagonal-meets-bottom transition\n // uses a much smaller clip than the diagonal-meets-top one).\n const radii: number[] = [\n ...upper.map(() => topRadius),\n ...lower.map(() => bottomRadius),\n ];\n return roundedPolygonPath([...upper, ...lower], radii);\n}\n\n/**\n * Pull a stage's **top** edge down by `gap`, so surface shows between it and the\n * stage above.\n *\n * The top rather than the bottom on purpose: the bottom edge is what\n * `lastShapeType` defines — the triangle's apex — so leaving it exactly where\n * recharts put it keeps a `triangle` funnel ending in a real point instead of a\n * blunt 2px edge. The upper edge slides down the funnel's own slope, so the\n * taper is unchanged rather than steepened.\n *\n * A stage with no room for the gap is returned untouched: better a missing gap\n * than an inverted shape.\n */\nexport function funnelChartStageInset(options: {\n x: number;\n y: number;\n upperWidth: number;\n lowerWidth: number;\n height: number;\n gap: number;\n}) {\n const { x, y, upperWidth, lowerWidth, height, gap } = options;\n if (gap <= 0 || height <= gap) {\n return { x, y, upperWidth, lowerWidth, height };\n }\n\n const insetUpperWidth =\n upperWidth + ((lowerWidth - upperWidth) * gap) / height;\n return {\n x: x + upperWidth / 2 - insetUpperWidth / 2,\n y: y + gap,\n upperWidth: insetUpperWidth,\n lowerWidth,\n height: height - gap,\n };\n}\n\n// The inset reserved on whichever side a label list sits. It is *not* label\n// room — see `funnelChartLabelReserve` for why — but a label whose text can't\n// wrap (one long word) does overflow past the plot edge, and this is what keeps\n// that overflow inside the SVG.\nconst LABEL_SIDE_INSET = 96;\n\n// The plot-area margin a funnel has always been drawn with: the right side\n// reserved, the left not.\nconst BASE_MARGIN = {\n top: 8,\n right: LABEL_SIDE_INSET,\n bottom: 8,\n left: 24,\n};\n\n/**\n * Share of the plot area handed to the funnel when a composite label sits to its\n * right. The rest becomes real label room — see `funnelChartLabelReserve`.\n *\n * A percentage, not a pixel count: the plot area is responsive, and recharts\n * resolves `Funnel`'s `width` against it, so a ratio keeps the reserve\n * proportional at every chart size.\n */\nconst COMPOSITE_FUNNEL_WIDTH = '75%';\n\n/** A label format that pairs two fields, so its text is roughly twice as wide. */\nfunction isCompositeFormat(format: FunnelChartLabelFormat): boolean {\n return format.includes('-');\n}\n\n/**\n * Reserve room for a composite label beside the funnel, by narrowing the\n * **funnel** rather than the plot area.\n *\n * recharts word-wraps a label against the space between its trapezoid's\n * mid-height edge and the *plot area* edge (`getCartesianPosition` clamps\n * `width` to `parentViewBox`, which `Funnel` sets to the plot box). A margin\n * shrinks the plot area and the funnel inside it in lockstep, so widening\n * `margin.right` moves the clamp edge *inward* and leaves a composite label\n * with less room, not more — the reserved strip sits outside `parentViewBox`\n * where the text can never reach it.\n *\n * `Funnel`'s `width` is the lever that works: it scales the funnel within the\n * plot area while `parentViewBox` stays put, and because recharts anchors the\n * funnel at the plot area's left edge (`offsetX = offset.left`), everything it\n * frees lands on the right. Narrowing the funnel to\n * `COMPOSITE_FUNNEL_WIDTH` turns ~35px of wrap width into ~140px on a 460px\n * chart.\n *\n * Returns `undefined` — i.e. leave the funnel at its full width — for every\n * other case, so a funnel with plain labels, no labels, or `inside` labels keeps\n * exactly the geometry it has always had.\n *\n * There is no equivalent lever for `labelPosition=\"left\"`: the widest trapezoid\n * always starts flush at the plot area's left edge, so a left-hand label is\n * bounded by how sharply the funnel narrows and narrowing it further only makes\n * that worse. Composite left-hand labels wrap; that's a recharts limitation, not\n * a tuning choice.\n *\n * A caller-supplied `funnelWidth` always wins — this is only the default.\n */\nexport function funnelChartLabelReserve(options: {\n showLabels: boolean;\n labelPosition: FunnelChartLabelPosition;\n labelFormat: FunnelChartLabelFormat;\n}): string | undefined {\n const { showLabels, labelPosition, labelFormat } = options;\n return showLabels &&\n labelPosition === 'right' &&\n isCompositeFormat(labelFormat)\n ? COMPOSITE_FUNNEL_WIDTH\n : undefined;\n}\n\n/**\n * The plot-area margin.\n *\n * With nothing beside the funnel — the default, and what Figma draws — the plot\n * is the design's own inset (`PLOT_MARGIN`): just the few px the rounded corners\n * and the apex need, so the funnel fills its 120px square instead of being\n * squeezed into the middle by label room it isn't using.\n *\n * As soon as a label list *does* sit beside the funnel, the side insets come\n * back: this is overflow protection, not label room — the wrap width comes from\n * `funnelChartLabelReserve`. A caller-supplied `margin` is merged over these\n * defaults per side, so `margin={{ right: 160 }}` keeps the default top, bottom\n * and left rather than collapsing them to zero.\n */\nexport function funnelChartLabelMargin(options: {\n showLabels: boolean;\n labelPosition: FunnelChartLabelPosition;\n showValueLabels: boolean;\n valuePosition: FunnelChartLabelPosition;\n}) {\n const { showLabels, labelPosition, showValueLabels, valuePosition } = options;\n const stageLabelAt = showLabels ? labelPosition : undefined;\n const valueLabelAt = showValueLabels ? valuePosition : undefined;\n const besideOnLeft = stageLabelAt === 'left' || valueLabelAt === 'left';\n const besideOnRight = stageLabelAt === 'right' || valueLabelAt === 'right';\n\n // `inside` labels sit on the segments, so they count as \"nothing beside the\n // funnel\" and keep the tight design margin too.\n if (!besideOnLeft && !besideOnRight) {\n return { ...PLOT_MARGIN };\n }\n\n return {\n ...BASE_MARGIN,\n left: besideOnLeft ? LABEL_SIDE_INSET : BASE_MARGIN.left,\n };\n}\n\n/**\n * Format a conversion share the way the default `percent` label reads it:\n * `0.52 → \"52.0%\"`. Exported so a `percentFormatter` can wrap or replace it.\n */\nexport const funnelChartPercent: TickFormatter = (value) => {\n const n = typeof value === 'number' ? value : Number(value);\n return Number.isFinite(n) ? `${(n * 100).toFixed(1)}%` : String(value);\n};\n\n/**\n * Compose one stage's label text.\n *\n * `base` is the value the percentage is measured against — the funnel's widest\n * stage, not the sum of every stage. A funnel's stages are nested subsets of\n * each other rather than parts of a whole, so their values don't add up to\n * anything meaningful; the share that reads as information is the conversion\n * from the top of the funnel. (A pie's `percent` is the other way round — there\n * a slice really is a share of the total.)\n *\n * Only the numeric part goes through `formatter`, so a label and its tooltip can\n * share one formatter; the share goes through `percentFormatter`, so a locale\n * that doesn't write a bare `%` can replace it. The percent formats degrade to\n * the name (or to the value) rather than printing `NaN%` when there is nothing\n * to divide by.\n */\nexport function funnelChartLabelText(options: {\n name: string;\n value: number | string;\n base: number;\n format: FunnelChartLabelFormat;\n formatter?: TickFormatter;\n percentFormatter?: TickFormatter;\n}): string {\n const {\n name,\n value,\n base,\n format,\n formatter,\n percentFormatter = funnelChartPercent,\n } = options;\n const text = formatter ? formatter(value) : String(value);\n const numeric = typeof value === 'number' ? value : Number(value);\n const share =\n base > 0 && Number.isFinite(numeric)\n ? percentFormatter(numeric / base)\n : '';\n\n switch (format) {\n case 'name':\n return name;\n case 'value':\n return text;\n case 'percent':\n return share;\n case 'name-value':\n return `${name}: ${text}`;\n case 'name-percent':\n return share ? `${name}: ${share}` : name;\n case 'value-percent':\n return share ? `${text} (${share})` : text;\n }\n}\n\n// What recharts hands a custom `Funnel` `shape`: the trapezoid box it measured,\n// the presentation props it resolved (`fill` from the `Cell`, `stroke`), the\n// data row, and whether this stage is the hovered one.\ntype FunnelStageShapeProps = {\n x?: number;\n y?: number;\n upperWidth?: number;\n lowerWidth?: number;\n height?: number;\n fill?: string;\n stroke?: string;\n strokeWidth?: string | number;\n className?: string;\n isActive?: boolean;\n payload?: Record<string, unknown>;\n showActiveShape?: boolean;\n /**\n * Which stage sits at the top of the plot area, and so takes no gap above it.\n * Not always the first row: `reversed` flips the stack, which puts the *last*\n * stage at the top.\n */\n topStageIndex?: number;\n};\n\n/**\n * One funnel stage. Replaces recharts' `Trapezoid` because the design's stage\n * has two things it can't express: a 2px gap to the stage above and 2px rounded\n * corners.\n *\n * Safe to substitute — recharts puts the tooltip/click handlers on the `<g\n * className=\"recharts-funnel-trapezoid\">` that wraps this, not on the shape, so\n * hover and the tooltip keep working without any event plumbing here.\n */\nfunction FunnelStageShape({\n x = 0,\n y = 0,\n upperWidth = 0,\n lowerWidth = 0,\n height = 0,\n fill,\n stroke,\n strokeWidth,\n className,\n isActive,\n payload,\n showActiveShape,\n topStageIndex = 0,\n}: FunnelStageShapeProps) {\n const isTopStage = payload?.[STAGE_INDEX_KEY] === topStageIndex;\n const inset = funnelChartStageInset({\n x,\n y,\n upperWidth,\n lowerWidth,\n height,\n // The funnel's own top edge is not a seam, so only the stages below the\n // topmost one are pushed down: n stages give exactly n-1 gaps.\n gap: isTopStage ? 0 : STAGE_GAP,\n });\n const d = funnelChartStagePath(inset);\n if (!d) return null;\n\n const outlined = Boolean(isActive && showActiveShape);\n return (\n <path\n className={cn('recharts-funnel-stage', className)}\n d={d}\n fill={fill}\n stroke={outlined ? ACTIVE_STROKE : stroke}\n strokeWidth={outlined ? ACTIVE_STROKE_WIDTH : strokeWidth}\n />\n );\n}\n\nexport interface FunnelChartProps\n extends\n Omit<React.ComponentProps<'div'>, 'children'>,\n VariantProps<typeof funnelChartVariants>,\n ChartAnimationProps {\n /**\n * The dataviz palette the stages are painted from — the only source of a\n * stage's colour, bar a per-stage `stageSettings.color`. Defaults to\n * `FUNNEL_CHART_DEFAULT_PALETTE` (the sequential blue ramp), which is what\n * Figma paints the funnel with; a funnel's stages are an ordered series, so a\n * ramp reads them better than the shared categorical default.\n * See `ChartPalette`.\n */\n palette?: ChartPalette;\n /** Row-per-stage data. Each object holds the stage's `nameKey` label + its `dataKey` numeric value. */\n data: ReadonlyArray<Record<string, string | number>>;\n /**\n * Per-stage map of `label` / `icon` / `tone`, keyed by the stage's `nameKey`\n * value (imported from the shared `Chart` primitives). Turned into\n * `--color-<name>` custom properties. Series take their colour from the\n * container's `palette`; each entry maps a key to a `label` and an optional\n * `tone`.\n */\n config: ChartConfig;\n /** Numeric field that sizes each stage (the funnel narrows as it drops). */\n dataKey: string;\n /**\n * Label field that names each stage (drives the legend, tooltip, on-chart\n * labels, and the `--color-<name>` lookup). Values should be unique per chart —\n * stages sharing a name share one `config`/color entry.\n */\n nameKey: string;\n /** Flip the funnel so it widens toward the bottom instead of narrowing. */\n reversed?: boolean;\n /** Render a label for each stage — see `labelFormat` and `labelPosition`. */\n showLabels?: boolean;\n /**\n * What each stage's label says. `percent` is the stage's share of the widest\n * stage — its conversion from the top of the funnel.\n */\n labelFormat?: FunnelChartLabelFormat;\n /**\n * Where each stage's label sits. Defaults to `right`, beside the segment.\n * `inside` centres it on the segment, which only works while every stage is\n * wide enough to hold the text — a funnel narrows, so its last stages often\n * are not. Keep the text short there (`percent`) or leave the labels beside\n * the funnel.\n */\n labelPosition?: FunnelChartLabelPosition;\n /**\n * Render a second label carrying the stage's value, so the name and the number\n * can sit on opposite sides of the funnel.\n */\n showValueLabels?: boolean;\n /**\n * Where the value label sits. Defaults to the side opposite `labelPosition`,\n * where every stage has room — so it follows the names rather than pinning\n * itself to one edge. See `labelPosition` before choosing `inside`.\n */\n valuePosition?: FunnelChartLabelPosition;\n /**\n * Colour for both label lists, overriding the contrast-matched default (the\n * on-surface token beside the funnel, the on-fill token on a segment). Pass a\n * `--ui-*` token; only use it when the default doesn't work on your surface.\n */\n labelFill?: string;\n /** Format the numeric part of a label — pass the same formatter used elsewhere. */\n labelFormatter?: TickFormatter;\n /**\n * Format the conversion share behind the `percent` formats. Receives the share\n * as a fraction (`0.52`); defaults to `funnelChartPercent` (`\"52.0%\"`). Pass\n * `createTickFormatter({ style: 'percent', minimumFractionDigits: 1 }, locale)`\n * for a locale that doesn't write a bare `%`.\n */\n percentFormatter?: TickFormatter;\n /**\n * Render the stage list beside the funnel. On by default: the design's funnel\n * carries no on-plot labels, so the legend is where the stages are named.\n */\n showLegend?: boolean;\n /** Format the value in each legend row — the stage's `dataKey` number. */\n legendValueFormatter?: (value: string | number) => string;\n /** Per-stage `color` / `hidden` overrides, keyed by the stage's `nameKey` value. */\n stageSettings?: Record<string, FunnelChartStageSettings>;\n /** Outline the hovered segment. */\n showActiveShape?: boolean;\n /**\n * Segment border colour. Reference an existing semantic `--ui-*` token.\n * Defaults to the border token when `strokeWidth` is set on its own.\n */\n stroke?: string;\n /** Segment border width, in px. Implies a default `stroke` when given alone. */\n strokeWidth?: number;\n /**\n * Funnel width — a number in px or a percentage of the plot area (`'70%'`).\n * Defaults to the full plot area, except that a composite `labelFormat` beside\n * the funnel narrows it to leave the label real room to wrap into.\n */\n funnelWidth?: number | string;\n /**\n * Plot-area margin, in px. Merged over the defaults per side, so passing one\n * side keeps the others.\n */\n margin?: { top?: number; right?: number; bottom?: number; left?: number };\n showTooltip?: boolean;\n /**\n * Replace the default tooltip. Pass a configured `ChartTooltipContent`\n * (imported from this library) — e.g. with a `formatter` / `labelFormatter` —\n * to customize formatting, per-series rows, or extra fields without composing\n * recharts yourself. Ignored when `showTooltip` is false.\n */\n tooltipContent?: React.ComponentProps<typeof ChartTooltip>['content'];\n}\n\nconst FunnelChart = React.forwardRef<HTMLDivElement, FunnelChartProps>(\n (\n {\n className,\n config,\n palette,\n data,\n dataKey,\n nameKey,\n lastShape = 'triangle',\n reversed = false,\n showLabels = false,\n labelFormat = 'name',\n labelPosition = 'right',\n showValueLabels = false,\n valuePosition,\n labelFill,\n labelFormatter,\n percentFormatter,\n showLegend = true,\n legendValueFormatter,\n stageSettings,\n showActiveShape = false,\n stroke,\n strokeWidth,\n funnelWidth,\n margin,\n showTooltip = true,\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n\n // Stamp each row with its `fill` and its label texts (the shadcn data-driven\n // pattern). A Funnel's default fill is grey (#808080) and recharts doesn't\n // carry a per-segment color on the tooltip/legend payload item, so putting the\n // color on the data row is what lets a real hover resolve each segment's\n // color. (The forced-open `defaultIndex` VR snapshot can still show a neutral\n // indicator — it synthesizes the open state without a pointer hover.)\n const seriesData = React.useMemo(() => {\n const visible = data.filter(\n (row) => !stageSettings?.[String(row[nameKey])]?.hidden\n );\n // The percentages are conversions from the funnel's widest stage, and\n // recharts sizes the trapezoids off `Math.max` of the values — so the base\n // is the largest visible value, not the first row. Taking the first row\n // would read as a conversion above 100% whenever the data isn't sorted\n // descending.\n const conversionBase = visible.reduce((widest, row) => {\n const value = Number(row[dataKey]);\n return Number.isFinite(value) && value > widest ? value : widest;\n }, 0);\n\n return visible.map((row, index) => {\n const name = String(row[nameKey]);\n const fill =\n stageSettings?.[name]?.color ?? `var(--color-${toCssKey(name)})`;\n const labelArgs = {\n name,\n value: row[dataKey],\n base: conversionBase,\n formatter: labelFormatter,\n percentFormatter,\n };\n\n return {\n ...row,\n fill,\n [STAGE_INDEX_KEY]: index,\n [STAGE_LABEL_KEY]: funnelChartLabelText({\n ...labelArgs,\n format: labelFormat,\n }),\n [STAGE_VALUE_LABEL_KEY]: funnelChartLabelText({\n ...labelArgs,\n format: 'value',\n }),\n } as Record<string, string | number>;\n });\n }, [\n data,\n dataKey,\n labelFormat,\n labelFormatter,\n nameKey,\n percentFormatter,\n stageSettings,\n ]);\n\n // The legend sits *outside* `ChartContainer`, beside the plot — so it can't\n // read the `--color-<name>` custom properties `ChartStyle` scopes to\n // `[data-chart=…]`. Resolving the palette here is what gives its markers the\n // same colours the stages paint with, the way `PieChart` and\n // `RadialBarChart` already do for their side legends.\n const resolvedConfigForLegend = React.useMemo(\n () =>\n showLegend\n ? resolveChartColors(config, palette ?? FUNNEL_CHART_DEFAULT_PALETTE)\n : null,\n [showLegend, config, palette]\n );\n\n // recharts 3 builds the legend payload from the graphical item, and `Funnel`\n // — unlike Bar/Line/Area/Pie/Radar/RadialBar/Scatter — never registers one,\n // so a `<Legend>` inside a `FunnelChart` renders empty. The payload is\n // synthesized from the visible stages instead and handed to the shared\n // `ChartLegendContent`, which keeps the funnel on the same legend markers,\n // labels and `config` lookup as every other chart. `payload` carries the row\n // so each entry resolves its own `config` entry via `nameKey`.\n //\n // One entry per *distinct* stage name: same-named stages deliberately share a\n // `config` entry, so a second entry would repeat the first verbatim — and\n // `ChartLegendContent` keys its entries on `value`, so it would also be a\n // duplicate React key.\n const legendPayload = React.useMemo<LegendPayload[]>(() => {\n if (!resolvedConfigForLegend) return [];\n const seen = new Set<string>();\n return seriesData.flatMap((row) => {\n const value = String(row[nameKey]);\n if (seen.has(value)) return [];\n seen.add(value);\n return [\n {\n value,\n dataKey: nameKey,\n type: 'rect' as const,\n // A `stageSettings` colour is a real colour and paints as-is; every\n // other stage takes its resolved palette stop.\n color:\n stageSettings?.[value]?.color ??\n resolvedConfigForLegend[value]?.color ??\n String(row.fill),\n payload: row,\n },\n ];\n });\n }, [nameKey, seriesData, resolvedConfigForLegend, stageSettings]);\n\n const resolvedValuePosition =\n valuePosition ?? funnelChartOppositeSide(labelPosition);\n\n // When labels sit beside the funnel (not on the segments), the 120px plot\n // is too narrow: the label margins eat the whole width and leave 0px for the\n // funnel. Make the plot grow to fill available width in that case; the fixed\n // 120px square is only for the default \"no outside labels\" layout.\n const hasOutsideLabels =\n (showLabels && labelPosition !== 'inside') ||\n (showValueLabels && resolvedValuePosition !== 'inside');\n\n const stagePosition = LABEL_POSITION[labelPosition];\n const valueLabelPosition = LABEL_POSITION[resolvedValuePosition];\n const plotMargin = {\n ...funnelChartLabelMargin({\n showLabels,\n labelPosition,\n showValueLabels,\n valuePosition: resolvedValuePosition,\n }),\n ...margin,\n };\n // A caller-supplied `funnelWidth` wins; otherwise a composite label beside the\n // funnel narrows it to leave itself room to wrap into.\n const resolvedFunnelWidth =\n funnelWidth ??\n funnelChartLabelReserve({ showLabels, labelPosition, labelFormat });\n // recharts defaults a Funnel's stroke to a hardcoded `#fff`.\n // ChartContainer's CSS neutralizer targets `.recharts-trapezoid`, but our\n // custom shape renders as `.recharts-funnel-stage`, so the neutralizer\n // never matches. Explicitly default to `'none'` to suppress the white\n // outline — and pair a bare `strokeWidth` with the border token so it\n // doesn't widen an invisible border.\n const resolvedStroke =\n stroke ??\n (strokeWidth != null ? 'var(--ui-border-on-surface-border)' : 'none');\n // One renderer for both slots: recharts swaps in `activeShape` for the\n // hovered stage, so leaving it unset would drop back to its own square-\n // cornered, gapless `Trapezoid` on hover.\n const renderStage = (stageProps: FunnelStageShapeProps) => (\n <FunnelStageShape\n {...stageProps}\n showActiveShape={showActiveShape}\n // `reversed` stacks the funnel the other way up, so the stage with no\n // gap above it is the last row rather than the first.\n topStageIndex={reversed ? seriesData.length - 1 : 0}\n />\n );\n\n return (\n <div\n ref={ref}\n data-last-shape={lastShape}\n className={cn(\n // The design's funnel is a row: a square plot, a 16px gutter, then the\n // legend taking whatever is left — so the component fills its parent's\n // width without the plot stretching into a tall, narrow wedge.\n 'flex flex-row items-center gap-4',\n !showLegend && 'justify-center',\n funnelChartVariants({ lastShape }),\n className\n )}\n {...props}\n >\n <div\n className={\n hasOutsideLabels\n ? 'h-[120px] flex-1'\n : cn(PLOT_SIZE_CLASS, 'shrink-0')\n }\n >\n <ChartContainer\n config={config}\n palette={palette ?? FUNNEL_CHART_DEFAULT_PALETTE}\n className=\"size-full\"\n >\n <RechartsFunnelChart margin={plotMargin}>\n {showTooltip && (\n <ChartTooltip\n content={\n tooltipContent ?? (\n <ChartTooltipContent nameKey={nameKey} hideLabel />\n )\n }\n />\n )}\n <Funnel\n dataKey={dataKey}\n nameKey={nameKey}\n data={seriesData}\n lastShapeType={lastShape ?? 'triangle'}\n reversed={reversed}\n width={resolvedFunnelWidth}\n stroke={resolvedStroke}\n strokeWidth={strokeWidth}\n shape={renderStage}\n activeShape={renderStage}\n {...animation}\n >\n {seriesData.map((entry, index) => (\n // Keyed by index, not the name: two stages could share a nameKey\n // value, which would collide as a React key. Same-named stages\n // intentionally share a color/config entry via `--color-<name>`.\n <Cell key={index} fill={String(entry.fill)} />\n ))}\n {showLabels && (\n <LabelList\n position={stagePosition}\n dataKey={STAGE_LABEL_KEY}\n className={\n labelFill\n ? undefined\n : resolveLabelFillClass(stagePosition)\n }\n fill={labelFill}\n stroke=\"none\"\n />\n )}\n {showValueLabels && (\n <LabelList\n position={valueLabelPosition}\n dataKey={STAGE_VALUE_LABEL_KEY}\n className={\n labelFill\n ? undefined\n : resolveLabelFillClass(valueLabelPosition)\n }\n fill={labelFill}\n stroke=\"none\"\n />\n )}\n </Funnel>\n </RechartsFunnelChart>\n </ChartContainer>\n </div>\n {showLegend && resolvedConfigForLegend && (\n <ChartLegendContent\n variant=\"list\"\n payload={legendPayload}\n config={resolvedConfigForLegend}\n nameKey={nameKey}\n valueKey={dataKey}\n valueFormatter={legendValueFormatter}\n // Figma paints the funnel legend's value in the primary text token,\n // not the link token the donut/radial legends use for theirs.\n valueClassName=\"text-[var(--ui-text-on-surface-primary)]\"\n className=\"min-w-0 flex-1\"\n />\n )}\n </div>\n );\n }\n);\nFunnelChart.displayName = 'FunnelChart';\n\nexport { FunnelChart, funnelChartVariants };\n"],"mappings":"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"}
1
+ {"version":3,"file":"funnel-chart.js","names":[],"sources":["../../../../src/components/ui/funnel-chart/funnel-chart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n Cell,\n Funnel,\n FunnelChart as RechartsFunnelChart,\n LabelList,\n} from 'recharts';\nimport type { LegendPayload } from 'recharts/types/component/DefaultLegendContent';\n\nimport { cn } from '@/lib/utils';\nimport {\n ChartContainer,\n ChartLegendContent,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n resolveChartColors,\n resolveLabelFillClass,\n toCssKey,\n type ChartAnimationProps,\n type ChartConfig,\n type ChartLegendFontSize,\n type ChartPalette,\n type TickFormatter,\n} from '../chart';\n\n// A typed recharts composition over the shared `Chart` primitives. The single\n// CVA axis is the design's Funnel-chart variant set: `lastShape` (does the\n// funnel narrow to a point — `triangle` — or end flat — `rectangle`). The class\n// stays empty because recharts' SVG — not CSS — draws the funnel: `lastShape`\n// drives the `<Funnel>`'s `lastShapeType`. CVA is kept so the variant set is a\n// first-class, spec-conformant part of the API (matched against ui-spec's\n// api.yaml enums) and exposed via `VariantProps`; the resolved value is also\n// mirrored onto `data-last-shape`.\nconst funnelChartVariants = cva('', {\n variants: {\n lastShape: {\n triangle: '',\n rectangle: '',\n },\n },\n defaultVariants: {\n lastShape: 'triangle',\n },\n});\n\n/** Where a stage's label sits relative to its segment. */\nexport type FunnelChartLabelPosition = 'right' | 'left' | 'inside';\n\n/** What a stage's label says. */\nexport type FunnelChartLabelFormat =\n | 'name'\n | 'value'\n | 'percent'\n | 'name-value'\n | 'name-percent'\n | 'value-percent';\n\n/** Override for a single stage, keyed by its `nameKey` value. */\nexport interface FunnelChartStageSettings {\n /**\n * Paint this stage with a colour of its own instead of its `palette` stop —\n * the same per-item escape hatch `PieChart`'s `sliceSettings` offers.\n * Reference an existing semantic `--ui-*` token.\n */\n color?: string;\n /**\n * Drop the stage from the funnel — and from the legend, labels and the\n * conversion percentages, which are computed over the visible stages only.\n */\n hidden?: boolean;\n}\n\n// recharts' `LabelList` positions that a funnel trapezoid understands. `inside`\n// is recharts' `center`: a trapezoid has no `insideLeft`/`insideTop` anchor, so\n// centring is the only on-segment placement. `outside` is not offered — for a\n// funnel it would mean \"beside the segment\", which is what `right`/`left`\n// already are, and recharts' polar `outside` doesn't apply to a trapezoid.\nconst LABEL_POSITION: Record<\n FunnelChartLabelPosition,\n 'right' | 'left' | 'center'\n> = {\n right: 'right',\n left: 'left',\n inside: 'center',\n};\n\n/**\n * Where the value labels sit when the caller doesn't say: opposite the names, so\n * the two lists never stack on the same edge. `inside` names sit on the segments\n * and leave both edges free, so the values take the default `right`.\n *\n * A static default can't do this — `valuePosition=\"left\"` beside\n * `labelPosition=\"left\"` draws both lists at the same anchor, overprinting the\n * name with its own value.\n */\nexport function funnelChartOppositeSide(\n labelPosition: FunnelChartLabelPosition\n): FunnelChartLabelPosition {\n switch (labelPosition) {\n case 'right':\n return 'left';\n case 'left':\n return 'right';\n case 'inside':\n return 'right';\n }\n}\n\n// Synthetic row fields the two label lists read. A `LabelList` resolves its text\n// from a `dataKey`, and its `formatter` only ever sees that one field's value —\n// so a composite label (\"Signups: 52.0%\") has to be composed onto the row before\n// recharts sees it, the same way `fill` already is.\nconst STAGE_LABEL_KEY = '__stageLabel';\nconst STAGE_VALUE_LABEL_KEY = '__stageValueLabel';\n\n// Which stage this row is. recharts hands a custom `shape` the data row as\n// `payload` but no index, and the first stage is the one that must *not* be\n// pushed down — its top edge is the top of the funnel.\nconst STAGE_INDEX_KEY = '__stageIndex';\n\n// Hover highlight for `showActiveShape` — an outline rather than a fill change,\n// so the segment keeps the colour that ties it to its legend entry and label.\nconst ACTIVE_STROKE = 'var(--ui-border-on-surface-border-active)';\nconst ACTIVE_STROKE_WIDTH = 2;\n\n/**\n * The design's funnel plot: a 120×120 square beside its legend — the same\n * geometry `ChartDonut` uses in Figma, which is why `PieChart` and\n * `RadialBarChart` size their plot exactly this way too.\n *\n * The *component* is still parent-responsive: the root is a flex row whose\n * legend column takes every pixel the plot doesn't, so a funnel widens with its\n * widget instead of the plot growing into a tall, narrow wedge.\n *\n * Figma: `8811:175245` → `Chart` is `size-[120px]`, `gap-[16px]`, legend\n * `flex-[1_0_0]`.\n */\nconst PLOT_SIZE_CLASS = 'size-[120px]';\n\n/**\n * Surface showing between two stages, in px. recharts draws the stages flush —\n * `Funnel` has no gap prop and `Trapezoid` no radius — so both come from the\n * custom `shape` below.\n *\n * Figma `8811:175245` stacks the stages at y = 4 / 34 / 64 / 94 with shapes 28\n * tall: a 2px gap, three times over for four stages.\n */\nconst STAGE_GAP = 2;\n\n/** Corner rounding on the two upper edges of each stage (Figma: ~2px clip along the top edge). */\nconst STAGE_RADIUS = 2;\n\n/**\n * Corner rounding on the two lower edges of each stage.\n *\n * Figma's bottom corners clip ~0.85px — significantly less than the upper\n * corners. The asymmetry is intentional: the diagonal-meets-flat-bottom\n * transition is shallow, so a smaller radius keeps it from looking over-rounded\n * at the narrow tail of the funnel. 1px is the nearest integer match.\n */\nconst STAGE_BOTTOM_RADIUS = 1;\n\n/**\n * The plot-area inset when nothing sits beside the funnel. Figma's widest stage\n * spans 102.67 of the 120px box and the stack runs y=4 → ~115.7, i.e. ~8px of\n * horizontal and 4px of vertical breathing room.\n */\nconst PLOT_MARGIN = { top: 4, right: 8, bottom: 4, left: 8 };\n\n/**\n * The palette Figma paints the funnel from: the sequential blue ramp, whose\n * stops read as one quantity falling down the funnel. Every other chart keeps\n * the shared categorical default — a funnel's stages are an *ordered* series,\n * which is what a sequential ramp is for.\n *\n * Figma `8811:175245` uses `dataviz/sequential/blue/{2,4,6,8}`.\n */\nexport const FUNNEL_CHART_DEFAULT_PALETTE: ChartPalette = Object.freeze({\n type: 'sequential',\n ramp: 'blue',\n});\n\ntype StagePoint = readonly [number, number];\n\nconst distance = (a: StagePoint, b: StagePoint) =>\n Math.hypot(b[0] - a[0], b[1] - a[1]);\n\n/** A point `d` px from `from` along the segment toward `to`. */\nfunction along(from: StagePoint, to: StagePoint, d: number): StagePoint {\n const length = distance(from, to);\n if (length === 0) return from;\n return [\n from[0] + ((to[0] - from[0]) * d) / length,\n from[1] + ((to[1] - from[1]) * d) / length,\n ];\n}\n\n// Trimmed to 3dp so the emitted `d` is stable across platforms — a raw float\n// makes every visual-regression baseline and DOM assertion renderer-dependent.\nconst coord = ([x, y]: StagePoint) =>\n `${Number(x.toFixed(3))},${Number(y.toFixed(3))}`;\n\n/**\n * A closed polygon with rounded corners, as an SVG path.\n *\n * Each corner is trimmed back along both of its edges and bridged with a\n * quadratic curve through the original vertex. The radius is clamped to half of\n * either adjacent edge, so two corners can never overrun each other on a short\n * edge — which is the normal case at the bottom of a funnel, where the last\n * stages are only a few px wide.\n */\nfunction roundedPolygonPath(\n points: readonly StagePoint[],\n radius: number | readonly number[]\n): string {\n const count = points.length;\n if (count < 3) return '';\n\n let path = '';\n for (let i = 0; i < count; i++) {\n const r = typeof radius === 'number' ? radius : radius[i];\n const previous = points[(i - 1 + count) % count];\n const current = points[i];\n const next = points[(i + 1) % count];\n const corner = Math.max(\n 0,\n Math.min(\n r,\n distance(previous, current) / 2,\n distance(current, next) / 2\n )\n );\n const entry = along(current, previous, corner);\n const exit = along(current, next, corner);\n path += `${i === 0 ? 'M' : ' L'} ${coord(entry)}`;\n path += ` Q ${coord(current)} ${coord(exit)}`;\n }\n return `${path} Z`;\n}\n\n/**\n * The `d` for one funnel stage — recharts' trapezoid geometry with the design's\n * rounded corners.\n *\n * `x`/`y` is the top-left of the *upper* edge and the shape is centred on\n * `x + upperWidth / 2`, matching recharts' own `getTrapezoidPath`.\n *\n * Either edge can collapse to a single rounded apex rather than a zero-length\n * one — the `triangle` last shape narrows to a point at the bottom, and a\n * `reversed` funnel stands that same triangle on its head at the top.\n */\nexport function funnelChartStagePath(options: {\n x: number;\n y: number;\n upperWidth: number;\n lowerWidth: number;\n height: number;\n /**\n * Uniform corner radius — applies to all four corners. Overridden per-side\n * by `topRadius` / `bottomRadius`. When absent, defaults to\n * `STAGE_RADIUS` (top) and `STAGE_BOTTOM_RADIUS` (bottom).\n */\n radius?: number;\n /** Radius for the two upper corners (default: `STAGE_RADIUS = 2`). */\n topRadius?: number;\n /**\n * Radius for the two lower corners (default: `STAGE_BOTTOM_RADIUS = 1`).\n *\n * Figma's bottom corners clip ~0.85px — much smaller than the upper ones —\n * so the default is intentionally lower than `topRadius`.\n */\n bottomRadius?: number;\n}): string {\n const { x, y, upperWidth, lowerWidth, height } = options;\n // `radius` is a backward-compat shorthand for all corners.\n // Per-side props win; if only `radius` is given it applies to both sides.\n // If nothing is given, use the asymmetric Figma defaults.\n const uniformRadius = options.radius;\n const topRadius = options.topRadius ?? uniformRadius ?? STAGE_RADIUS;\n const bottomRadius =\n options.bottomRadius ?? uniformRadius ?? STAGE_BOTTOM_RADIUS;\n\n if (height <= 0 || (upperWidth <= 0 && lowerWidth <= 0)) return '';\n\n const centre = x + upperWidth / 2;\n const upper: StagePoint[] =\n upperWidth <= 0\n ? [[centre, y]]\n : [\n [x, y],\n [x + upperWidth, y],\n ];\n const lower: StagePoint[] =\n lowerWidth <= 0\n ? [[centre, y + height]]\n : [\n [centre + lowerWidth / 2, y + height],\n [centre - lowerWidth / 2, y + height],\n ];\n\n // Upper points get topRadius, lower points get bottomRadius — matching\n // Figma's intentional asymmetry (the diagonal-meets-bottom transition\n // uses a much smaller clip than the diagonal-meets-top one).\n const radii: number[] = [\n ...upper.map(() => topRadius),\n ...lower.map(() => bottomRadius),\n ];\n return roundedPolygonPath([...upper, ...lower], radii);\n}\n\n/**\n * Pull a stage's **top** edge down by `gap`, so surface shows between it and the\n * stage above.\n *\n * The top rather than the bottom on purpose: the bottom edge is what\n * `lastShapeType` defines — the triangle's apex — so leaving it exactly where\n * recharts put it keeps a `triangle` funnel ending in a real point instead of a\n * blunt 2px edge. The upper edge slides down the funnel's own slope, so the\n * taper is unchanged rather than steepened.\n *\n * A stage with no room for the gap is returned untouched: better a missing gap\n * than an inverted shape.\n */\nexport function funnelChartStageInset(options: {\n x: number;\n y: number;\n upperWidth: number;\n lowerWidth: number;\n height: number;\n gap: number;\n}) {\n const { x, y, upperWidth, lowerWidth, height, gap } = options;\n if (gap <= 0 || height <= gap) {\n return { x, y, upperWidth, lowerWidth, height };\n }\n\n const insetUpperWidth =\n upperWidth + ((lowerWidth - upperWidth) * gap) / height;\n return {\n x: x + upperWidth / 2 - insetUpperWidth / 2,\n y: y + gap,\n upperWidth: insetUpperWidth,\n lowerWidth,\n height: height - gap,\n };\n}\n\n// The inset reserved on whichever side a label list sits. It is *not* label\n// room — see `funnelChartLabelReserve` for why — but a label whose text can't\n// wrap (one long word) does overflow past the plot edge, and this is what keeps\n// that overflow inside the SVG.\nconst LABEL_SIDE_INSET = 96;\n\n// The plot-area margin a funnel has always been drawn with: the right side\n// reserved, the left not.\nconst BASE_MARGIN = {\n top: 8,\n right: LABEL_SIDE_INSET,\n bottom: 8,\n left: 24,\n};\n\n/**\n * Share of the plot area handed to the funnel when a composite label sits to its\n * right. The rest becomes real label room — see `funnelChartLabelReserve`.\n *\n * A percentage, not a pixel count: the plot area is responsive, and recharts\n * resolves `Funnel`'s `width` against it, so a ratio keeps the reserve\n * proportional at every chart size.\n */\nconst COMPOSITE_FUNNEL_WIDTH = '75%';\n\n/** A label format that pairs two fields, so its text is roughly twice as wide. */\nfunction isCompositeFormat(format: FunnelChartLabelFormat): boolean {\n return format.includes('-');\n}\n\n/**\n * Reserve room for a composite label beside the funnel, by narrowing the\n * **funnel** rather than the plot area.\n *\n * recharts word-wraps a label against the space between its trapezoid's\n * mid-height edge and the *plot area* edge (`getCartesianPosition` clamps\n * `width` to `parentViewBox`, which `Funnel` sets to the plot box). A margin\n * shrinks the plot area and the funnel inside it in lockstep, so widening\n * `margin.right` moves the clamp edge *inward* and leaves a composite label\n * with less room, not more — the reserved strip sits outside `parentViewBox`\n * where the text can never reach it.\n *\n * `Funnel`'s `width` is the lever that works: it scales the funnel within the\n * plot area while `parentViewBox` stays put, and because recharts anchors the\n * funnel at the plot area's left edge (`offsetX = offset.left`), everything it\n * frees lands on the right. Narrowing the funnel to\n * `COMPOSITE_FUNNEL_WIDTH` turns ~35px of wrap width into ~140px on a 460px\n * chart.\n *\n * Returns `undefined` — i.e. leave the funnel at its full width — for every\n * other case, so a funnel with plain labels, no labels, or `inside` labels keeps\n * exactly the geometry it has always had.\n *\n * There is no equivalent lever for `labelPosition=\"left\"`: the widest trapezoid\n * always starts flush at the plot area's left edge, so a left-hand label is\n * bounded by how sharply the funnel narrows and narrowing it further only makes\n * that worse. Composite left-hand labels wrap; that's a recharts limitation, not\n * a tuning choice.\n *\n * A caller-supplied `funnelWidth` always wins — this is only the default.\n */\nexport function funnelChartLabelReserve(options: {\n showLabels: boolean;\n labelPosition: FunnelChartLabelPosition;\n labelFormat: FunnelChartLabelFormat;\n}): string | undefined {\n const { showLabels, labelPosition, labelFormat } = options;\n return showLabels &&\n labelPosition === 'right' &&\n isCompositeFormat(labelFormat)\n ? COMPOSITE_FUNNEL_WIDTH\n : undefined;\n}\n\n/**\n * The plot-area margin.\n *\n * With nothing beside the funnel — the default, and what Figma draws — the plot\n * is the design's own inset (`PLOT_MARGIN`): just the few px the rounded corners\n * and the apex need, so the funnel fills its 120px square instead of being\n * squeezed into the middle by label room it isn't using.\n *\n * As soon as a label list *does* sit beside the funnel, the side insets come\n * back: this is overflow protection, not label room — the wrap width comes from\n * `funnelChartLabelReserve`. A caller-supplied `margin` is merged over these\n * defaults per side, so `margin={{ right: 160 }}` keeps the default top, bottom\n * and left rather than collapsing them to zero.\n */\nexport function funnelChartLabelMargin(options: {\n showLabels: boolean;\n labelPosition: FunnelChartLabelPosition;\n showValueLabels: boolean;\n valuePosition: FunnelChartLabelPosition;\n}) {\n const { showLabels, labelPosition, showValueLabels, valuePosition } = options;\n const stageLabelAt = showLabels ? labelPosition : undefined;\n const valueLabelAt = showValueLabels ? valuePosition : undefined;\n const besideOnLeft = stageLabelAt === 'left' || valueLabelAt === 'left';\n const besideOnRight = stageLabelAt === 'right' || valueLabelAt === 'right';\n\n // `inside` labels sit on the segments, so they count as \"nothing beside the\n // funnel\" and keep the tight design margin too.\n if (!besideOnLeft && !besideOnRight) {\n return { ...PLOT_MARGIN };\n }\n\n return {\n ...BASE_MARGIN,\n left: besideOnLeft ? LABEL_SIDE_INSET : BASE_MARGIN.left,\n };\n}\n\n/**\n * Format a conversion share the way the default `percent` label reads it:\n * `0.52 → \"52.0%\"`. Exported so a `percentFormatter` can wrap or replace it.\n */\nexport const funnelChartPercent: TickFormatter = (value) => {\n const n = typeof value === 'number' ? value : Number(value);\n return Number.isFinite(n) ? `${(n * 100).toFixed(1)}%` : String(value);\n};\n\n/**\n * Compose one stage's label text.\n *\n * `base` is the value the percentage is measured against — the funnel's widest\n * stage, not the sum of every stage. A funnel's stages are nested subsets of\n * each other rather than parts of a whole, so their values don't add up to\n * anything meaningful; the share that reads as information is the conversion\n * from the top of the funnel. (A pie's `percent` is the other way round — there\n * a slice really is a share of the total.)\n *\n * Only the numeric part goes through `formatter`, so a label and its tooltip can\n * share one formatter; the share goes through `percentFormatter`, so a locale\n * that doesn't write a bare `%` can replace it. The percent formats degrade to\n * the name (or to the value) rather than printing `NaN%` when there is nothing\n * to divide by.\n */\nexport function funnelChartLabelText(options: {\n name: string;\n value: number | string;\n base: number;\n format: FunnelChartLabelFormat;\n formatter?: TickFormatter;\n percentFormatter?: TickFormatter;\n}): string {\n const {\n name,\n value,\n base,\n format,\n formatter,\n percentFormatter = funnelChartPercent,\n } = options;\n const text = formatter ? formatter(value) : String(value);\n const numeric = typeof value === 'number' ? value : Number(value);\n const share =\n base > 0 && Number.isFinite(numeric)\n ? percentFormatter(numeric / base)\n : '';\n\n switch (format) {\n case 'name':\n return name;\n case 'value':\n return text;\n case 'percent':\n return share;\n case 'name-value':\n return `${name}: ${text}`;\n case 'name-percent':\n return share ? `${name}: ${share}` : name;\n case 'value-percent':\n return share ? `${text} (${share})` : text;\n }\n}\n\n// What recharts hands a custom `Funnel` `shape`: the trapezoid box it measured,\n// the presentation props it resolved (`fill` from the `Cell`, `stroke`), the\n// data row, and whether this stage is the hovered one.\ntype FunnelStageShapeProps = {\n x?: number;\n y?: number;\n upperWidth?: number;\n lowerWidth?: number;\n height?: number;\n fill?: string;\n stroke?: string;\n strokeWidth?: string | number;\n className?: string;\n isActive?: boolean;\n payload?: Record<string, unknown>;\n showActiveShape?: boolean;\n /**\n * Which stage sits at the top of the plot area, and so takes no gap above it.\n * Not always the first row: `reversed` flips the stack, which puts the *last*\n * stage at the top.\n */\n topStageIndex?: number;\n};\n\n/**\n * One funnel stage. Replaces recharts' `Trapezoid` because the design's stage\n * has two things it can't express: a 2px gap to the stage above and 2px rounded\n * corners.\n *\n * Safe to substitute — recharts puts the tooltip/click handlers on the `<g\n * className=\"recharts-funnel-trapezoid\">` that wraps this, not on the shape, so\n * hover and the tooltip keep working without any event plumbing here.\n */\nfunction FunnelStageShape({\n x = 0,\n y = 0,\n upperWidth = 0,\n lowerWidth = 0,\n height = 0,\n fill,\n stroke,\n strokeWidth,\n className,\n isActive,\n payload,\n showActiveShape,\n topStageIndex = 0,\n}: FunnelStageShapeProps) {\n const isTopStage = payload?.[STAGE_INDEX_KEY] === topStageIndex;\n const inset = funnelChartStageInset({\n x,\n y,\n upperWidth,\n lowerWidth,\n height,\n // The funnel's own top edge is not a seam, so only the stages below the\n // topmost one are pushed down: n stages give exactly n-1 gaps.\n gap: isTopStage ? 0 : STAGE_GAP,\n });\n const d = funnelChartStagePath(inset);\n if (!d) return null;\n\n const outlined = Boolean(isActive && showActiveShape);\n return (\n <path\n className={cn('recharts-funnel-stage', className)}\n d={d}\n fill={fill}\n stroke={outlined ? ACTIVE_STROKE : stroke}\n strokeWidth={outlined ? ACTIVE_STROKE_WIDTH : strokeWidth}\n />\n );\n}\n\nexport interface FunnelChartProps\n extends\n Omit<React.ComponentProps<'div'>, 'children'>,\n VariantProps<typeof funnelChartVariants>,\n ChartAnimationProps {\n /**\n * The dataviz palette the stages are painted from — the only source of a\n * stage's colour, bar a per-stage `stageSettings.color`. Defaults to\n * `FUNNEL_CHART_DEFAULT_PALETTE` (the sequential blue ramp), which is what\n * Figma paints the funnel with; a funnel's stages are an ordered series, so a\n * ramp reads them better than the shared categorical default.\n * See `ChartPalette`.\n */\n palette?: ChartPalette;\n /** Row-per-stage data. Each object holds the stage's `nameKey` label + its `dataKey` numeric value. */\n data: ReadonlyArray<Record<string, string | number>>;\n /**\n * Per-stage map of `label` / `icon` / `tone`, keyed by the stage's `nameKey`\n * value (imported from the shared `Chart` primitives). Turned into\n * `--color-<name>` custom properties. Series take their colour from the\n * container's `palette`; each entry maps a key to a `label` and an optional\n * `tone`.\n */\n config: ChartConfig;\n /** Numeric field that sizes each stage (the funnel narrows as it drops). */\n dataKey: string;\n /**\n * Label field that names each stage (drives the legend, tooltip, on-chart\n * labels, and the `--color-<name>` lookup). Values should be unique per chart —\n * stages sharing a name share one `config`/color entry.\n */\n nameKey: string;\n /** Flip the funnel so it widens toward the bottom instead of narrowing. */\n reversed?: boolean;\n /** Render a label for each stage — see `labelFormat` and `labelPosition`. */\n showLabels?: boolean;\n /**\n * What each stage's label says. `percent` is the stage's share of the widest\n * stage — its conversion from the top of the funnel.\n */\n labelFormat?: FunnelChartLabelFormat;\n /**\n * Where each stage's label sits. Defaults to `right`, beside the segment.\n * `inside` centres it on the segment, which only works while every stage is\n * wide enough to hold the text — a funnel narrows, so its last stages often\n * are not. Keep the text short there (`percent`) or leave the labels beside\n * the funnel.\n */\n labelPosition?: FunnelChartLabelPosition;\n /**\n * Render a second label carrying the stage's value, so the name and the number\n * can sit on opposite sides of the funnel.\n */\n showValueLabels?: boolean;\n /**\n * Where the value label sits. Defaults to the side opposite `labelPosition`,\n * where every stage has room — so it follows the names rather than pinning\n * itself to one edge. See `labelPosition` before choosing `inside`.\n */\n valuePosition?: FunnelChartLabelPosition;\n /**\n * Colour for both label lists, overriding the contrast-matched default (the\n * on-surface token beside the funnel, the on-fill token on a segment). Pass a\n * `--ui-*` token; only use it when the default doesn't work on your surface.\n */\n labelFill?: string;\n /** Format the numeric part of a label — pass the same formatter used elsewhere. */\n labelFormatter?: TickFormatter;\n /**\n * Format the conversion share behind the `percent` formats. Receives the share\n * as a fraction (`0.52`); defaults to `funnelChartPercent` (`\"52.0%\"`). Pass\n * `createTickFormatter({ style: 'percent', minimumFractionDigits: 1 }, locale)`\n * for a locale that doesn't write a bare `%`.\n */\n percentFormatter?: TickFormatter;\n /**\n * Render the stage list beside the funnel. On by default: the design's funnel\n * carries no on-plot labels, so the legend is where the stages are named.\n */\n showLegend?: boolean;\n /**\n * Font size of the legend text (stage names and values). Defaults to `'xs'` —\n * the size the legend has always rendered at. See `ChartLegendFontSize`.\n */\n legendFontSize?: ChartLegendFontSize;\n /** Format the value in each legend row — the stage's `dataKey` number. */\n legendValueFormatter?: (value: string | number) => string;\n /** Per-stage `color` / `hidden` overrides, keyed by the stage's `nameKey` value. */\n stageSettings?: Record<string, FunnelChartStageSettings>;\n /** Outline the hovered segment. */\n showActiveShape?: boolean;\n /**\n * Segment border colour. Reference an existing semantic `--ui-*` token.\n * Defaults to the border token when `strokeWidth` is set on its own.\n */\n stroke?: string;\n /** Segment border width, in px. Implies a default `stroke` when given alone. */\n strokeWidth?: number;\n /**\n * Funnel width — a number in px or a percentage of the plot area (`'70%'`).\n * Defaults to the full plot area, except that a composite `labelFormat` beside\n * the funnel narrows it to leave the label real room to wrap into.\n */\n funnelWidth?: number | string;\n /**\n * Plot-area margin, in px. Merged over the defaults per side, so passing one\n * side keeps the others.\n */\n margin?: { top?: number; right?: number; bottom?: number; left?: number };\n showTooltip?: boolean;\n /**\n * Replace the default tooltip. Pass a configured `ChartTooltipContent`\n * (imported from this library) — e.g. with a `formatter` / `labelFormatter` —\n * to customize formatting, per-series rows, or extra fields without composing\n * recharts yourself. Ignored when `showTooltip` is false.\n */\n tooltipContent?: React.ComponentProps<typeof ChartTooltip>['content'];\n}\n\nconst FunnelChart = React.forwardRef<HTMLDivElement, FunnelChartProps>(\n (\n {\n className,\n config,\n palette,\n data,\n dataKey,\n nameKey,\n lastShape = 'triangle',\n reversed = false,\n showLabels = false,\n labelFormat = 'name',\n labelPosition = 'right',\n showValueLabels = false,\n valuePosition,\n labelFill,\n labelFormatter,\n percentFormatter,\n showLegend = true,\n legendFontSize,\n legendValueFormatter,\n stageSettings,\n showActiveShape = false,\n stroke,\n strokeWidth,\n funnelWidth,\n margin,\n showTooltip = true,\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n\n // Stamp each row with its `fill` and its label texts (the shadcn data-driven\n // pattern). A Funnel's default fill is grey (#808080) and recharts doesn't\n // carry a per-segment color on the tooltip/legend payload item, so putting the\n // color on the data row is what lets a real hover resolve each segment's\n // color. (The forced-open `defaultIndex` VR snapshot can still show a neutral\n // indicator — it synthesizes the open state without a pointer hover.)\n const seriesData = React.useMemo(() => {\n const visible = data.filter(\n (row) => !stageSettings?.[String(row[nameKey])]?.hidden\n );\n // The percentages are conversions from the funnel's widest stage, and\n // recharts sizes the trapezoids off `Math.max` of the values — so the base\n // is the largest visible value, not the first row. Taking the first row\n // would read as a conversion above 100% whenever the data isn't sorted\n // descending.\n const conversionBase = visible.reduce((widest, row) => {\n const value = Number(row[dataKey]);\n return Number.isFinite(value) && value > widest ? value : widest;\n }, 0);\n\n return visible.map((row, index) => {\n const name = String(row[nameKey]);\n const fill =\n stageSettings?.[name]?.color ?? `var(--color-${toCssKey(name)})`;\n const labelArgs = {\n name,\n value: row[dataKey],\n base: conversionBase,\n formatter: labelFormatter,\n percentFormatter,\n };\n\n return {\n ...row,\n fill,\n [STAGE_INDEX_KEY]: index,\n [STAGE_LABEL_KEY]: funnelChartLabelText({\n ...labelArgs,\n format: labelFormat,\n }),\n [STAGE_VALUE_LABEL_KEY]: funnelChartLabelText({\n ...labelArgs,\n format: 'value',\n }),\n } as Record<string, string | number>;\n });\n }, [\n data,\n dataKey,\n labelFormat,\n labelFormatter,\n nameKey,\n percentFormatter,\n stageSettings,\n ]);\n\n // The legend sits *outside* `ChartContainer`, beside the plot — so it can't\n // read the `--color-<name>` custom properties `ChartStyle` scopes to\n // `[data-chart=…]`. Resolving the palette here is what gives its markers the\n // same colours the stages paint with, the way `PieChart` and\n // `RadialBarChart` already do for their side legends.\n const resolvedConfigForLegend = React.useMemo(\n () =>\n showLegend\n ? resolveChartColors(config, palette ?? FUNNEL_CHART_DEFAULT_PALETTE)\n : null,\n [showLegend, config, palette]\n );\n\n // recharts 3 builds the legend payload from the graphical item, and `Funnel`\n // — unlike Bar/Line/Area/Pie/Radar/RadialBar/Scatter — never registers one,\n // so a `<Legend>` inside a `FunnelChart` renders empty. The payload is\n // synthesized from the visible stages instead and handed to the shared\n // `ChartLegendContent`, which keeps the funnel on the same legend markers,\n // labels and `config` lookup as every other chart. `payload` carries the row\n // so each entry resolves its own `config` entry via `nameKey`.\n //\n // One entry per *distinct* stage name: same-named stages deliberately share a\n // `config` entry, so a second entry would repeat the first verbatim — and\n // `ChartLegendContent` keys its entries on `value`, so it would also be a\n // duplicate React key.\n const legendPayload = React.useMemo<LegendPayload[]>(() => {\n if (!resolvedConfigForLegend) return [];\n const seen = new Set<string>();\n return seriesData.flatMap((row) => {\n const value = String(row[nameKey]);\n if (seen.has(value)) return [];\n seen.add(value);\n return [\n {\n value,\n dataKey: nameKey,\n type: 'rect' as const,\n // A `stageSettings` colour is a real colour and paints as-is; every\n // other stage takes its resolved palette stop.\n color:\n stageSettings?.[value]?.color ??\n resolvedConfigForLegend[value]?.color ??\n String(row.fill),\n payload: row,\n },\n ];\n });\n }, [nameKey, seriesData, resolvedConfigForLegend, stageSettings]);\n\n const resolvedValuePosition =\n valuePosition ?? funnelChartOppositeSide(labelPosition);\n\n // When labels sit beside the funnel (not on the segments), the 120px plot\n // is too narrow: the label margins eat the whole width and leave 0px for the\n // funnel. Make the plot grow to fill available width in that case; the fixed\n // 120px square is only for the default \"no outside labels\" layout.\n const hasOutsideLabels =\n (showLabels && labelPosition !== 'inside') ||\n (showValueLabels && resolvedValuePosition !== 'inside');\n\n const stagePosition = LABEL_POSITION[labelPosition];\n const valueLabelPosition = LABEL_POSITION[resolvedValuePosition];\n const plotMargin = {\n ...funnelChartLabelMargin({\n showLabels,\n labelPosition,\n showValueLabels,\n valuePosition: resolvedValuePosition,\n }),\n ...margin,\n };\n // A caller-supplied `funnelWidth` wins; otherwise a composite label beside the\n // funnel narrows it to leave itself room to wrap into.\n const resolvedFunnelWidth =\n funnelWidth ??\n funnelChartLabelReserve({ showLabels, labelPosition, labelFormat });\n // recharts defaults a Funnel's stroke to a hardcoded `#fff`.\n // ChartContainer's CSS neutralizer targets `.recharts-trapezoid`, but our\n // custom shape renders as `.recharts-funnel-stage`, so the neutralizer\n // never matches. Explicitly default to `'none'` to suppress the white\n // outline — and pair a bare `strokeWidth` with the border token so it\n // doesn't widen an invisible border.\n const resolvedStroke =\n stroke ??\n (strokeWidth != null ? 'var(--ui-border-on-surface-border)' : 'none');\n // One renderer for both slots: recharts swaps in `activeShape` for the\n // hovered stage, so leaving it unset would drop back to its own square-\n // cornered, gapless `Trapezoid` on hover.\n const renderStage = (stageProps: FunnelStageShapeProps) => (\n <FunnelStageShape\n {...stageProps}\n showActiveShape={showActiveShape}\n // `reversed` stacks the funnel the other way up, so the stage with no\n // gap above it is the last row rather than the first.\n topStageIndex={reversed ? seriesData.length - 1 : 0}\n />\n );\n\n return (\n <div\n ref={ref}\n data-last-shape={lastShape}\n className={cn(\n // The design's funnel is a row: a square plot, a 16px gutter, then the\n // legend taking whatever is left — so the component fills its parent's\n // width without the plot stretching into a tall, narrow wedge.\n 'flex flex-row items-center gap-4',\n !showLegend && 'justify-center',\n funnelChartVariants({ lastShape }),\n className\n )}\n {...props}\n >\n <div\n className={\n hasOutsideLabels\n ? 'h-[120px] flex-1'\n : cn(PLOT_SIZE_CLASS, 'shrink-0')\n }\n >\n <ChartContainer\n config={config}\n palette={palette ?? FUNNEL_CHART_DEFAULT_PALETTE}\n className=\"size-full\"\n >\n <RechartsFunnelChart margin={plotMargin}>\n {showTooltip && (\n <ChartTooltip\n content={\n tooltipContent ?? (\n <ChartTooltipContent nameKey={nameKey} hideLabel />\n )\n }\n />\n )}\n <Funnel\n dataKey={dataKey}\n nameKey={nameKey}\n data={seriesData}\n lastShapeType={lastShape ?? 'triangle'}\n reversed={reversed}\n width={resolvedFunnelWidth}\n stroke={resolvedStroke}\n strokeWidth={strokeWidth}\n shape={renderStage}\n activeShape={renderStage}\n {...animation}\n >\n {seriesData.map((entry, index) => (\n // Keyed by index, not the name: two stages could share a nameKey\n // value, which would collide as a React key. Same-named stages\n // intentionally share a color/config entry via `--color-<name>`.\n <Cell key={index} fill={String(entry.fill)} />\n ))}\n {showLabels && (\n <LabelList\n position={stagePosition}\n dataKey={STAGE_LABEL_KEY}\n className={\n labelFill\n ? undefined\n : resolveLabelFillClass(stagePosition)\n }\n fill={labelFill}\n stroke=\"none\"\n />\n )}\n {showValueLabels && (\n <LabelList\n position={valueLabelPosition}\n dataKey={STAGE_VALUE_LABEL_KEY}\n className={\n labelFill\n ? undefined\n : resolveLabelFillClass(valueLabelPosition)\n }\n fill={labelFill}\n stroke=\"none\"\n />\n )}\n </Funnel>\n </RechartsFunnelChart>\n </ChartContainer>\n </div>\n {showLegend && resolvedConfigForLegend && (\n <ChartLegendContent\n variant=\"list\"\n payload={legendPayload}\n config={resolvedConfigForLegend}\n nameKey={nameKey}\n fontSize={legendFontSize}\n valueKey={dataKey}\n valueFormatter={legendValueFormatter}\n // Figma paints the funnel legend's value in the primary text token,\n // not the link token the donut/radial legends use for theirs.\n valueClassName=\"text-[var(--ui-text-on-surface-primary)]\"\n className=\"min-w-0 flex-1\"\n />\n )}\n </div>\n );\n }\n);\nFunnelChart.displayName = 'FunnelChart';\n\nexport { FunnelChart, funnelChartVariants };\n"],"mappings":"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"}
@@ -86,13 +86,13 @@ function xe(e) {
86
86
  return typeof e == "function" ? /* @__PURE__ */ g(e, { ...n }) : m.cloneElement(e, n);
87
87
  };
88
88
  }
89
- var T = m.forwardRef(({ className: t, config: h, palette: y, data: b, dataKeys: T, comparisonKeys: E, deltaBands: Se, xKey: D, xAxisLabel: O, yAxisLabel: k, yUnit: Ce, curve: A = "monotone", lineStyle: j = "solid", strokeWidth: M = 2, showDots: we = !1, dotSize: Te = 3, showActiveDot: Ee, connectNulls: N = !1, lineSettings: P, referenceLine: De, projectionStart: F, showGrid: Oe = !0, showTooltip: ke = !0, showLegend: Ae = !0, showXAxis: je = !0, showYAxis: Me = !0, xTickFormatter: I, yTickFormatter: Ne, xAxisAngle: L, xAxisInterval: Pe, yAxisTickCount: Fe, yAxisDomain: Ie, gridDashed: Le, gridHorizontal: Re, gridVertical: ze, tooltipContent: R, animate: Be, animationDuration: Ve, animationBegin: z, animationEasing: He, showLabels: B = !1, labelPosition: Ue, labelFormatter: We, showBrush: Ge = !1, brushHeight: Ke, brushAriaLabel: qe, ...Je }, Ye) => {
89
+ var T = m.forwardRef(({ className: t, config: h, palette: y, data: b, dataKeys: T, comparisonKeys: E, deltaBands: Se, xKey: D, xAxisLabel: O, yAxisLabel: k, yUnit: Ce, curve: A = "monotone", lineStyle: j = "solid", strokeWidth: M = 2, showDots: we = !1, dotSize: Te = 3, showActiveDot: Ee, connectNulls: N = !1, lineSettings: P, referenceLine: De, projectionStart: F, showGrid: Oe = !0, showTooltip: ke = !0, showLegend: Ae = !0, legendFontSize: I, showXAxis: je = !0, showYAxis: Me = !0, xTickFormatter: L, yTickFormatter: Ne, xAxisAngle: R, xAxisInterval: Pe, yAxisTickCount: Fe, yAxisDomain: Ie, gridDashed: Le, gridHorizontal: Re, gridVertical: ze, tooltipContent: z, animate: Be, animationDuration: Ve, animationBegin: He, animationEasing: Ue, showLabels: B = !1, labelPosition: We, labelFormatter: Ge, showBrush: Ke = !1, brushHeight: qe, brushAriaLabel: Je, ...Ye }, Xe) => {
90
90
  let V = u({
91
91
  animate: Be,
92
92
  animationDuration: Ve,
93
- animationBegin: z,
94
- animationEasing: He
95
- }), Xe = Ue ?? "top", Ze = d(Ie), Qe = se(De), H = (e) => P?.[e]?.color ?? `var(--color-${s(e)})`, U = (e) => P?.[e]?.curveType ?? A ?? "monotone", $e = B || Object.values(P ?? {}).some((e) => e.showLabel), et = re(b.map((e) => e[D]), I), W = m.useRef(null), G = le(W, L, O, et.join("\0")), tt = ce($e ? c : void 0, G.margin), K = m.useMemo(() => F === void 0 ? -1 : b.findIndex((e) => e[D] === F), [
93
+ animationBegin: He,
94
+ animationEasing: Ue
95
+ }), Ze = We ?? "top", Qe = d(Ie), $e = se(De), H = (e) => P?.[e]?.color ?? `var(--color-${s(e)})`, U = (e) => P?.[e]?.curveType ?? A ?? "monotone", et = B || Object.values(P ?? {}).some((e) => e.showLabel), tt = re(b.map((e) => e[D]), L), W = m.useRef(null), G = le(W, R, O, tt.join("\0")), nt = ce(et ? c : void 0, G.margin), K = m.useMemo(() => F === void 0 ? -1 : b.findIndex((e) => e[D] === F), [
96
96
  F,
97
97
  b,
98
98
  D
@@ -103,14 +103,14 @@ var T = m.forwardRef(({ className: t, config: h, palette: y, data: b, dataKeys:
103
103
  ...r,
104
104
  fontSize: 12,
105
105
  className: e.has(t.value) ? "fill-[var(--ui-text-on-surface-disabled)]" : "fill-muted-foreground",
106
- children: I ? I(t.value, n) : t.value
106
+ children: L ? L(t.value, n) : t.value
107
107
  });
108
108
  }, [
109
109
  q,
110
110
  K,
111
111
  b,
112
112
  D,
113
- I
113
+ L
114
114
  ]), Y = m.useMemo(() => q ? b.map((e) => ({
115
115
  ...e,
116
116
  ...Object.fromEntries(T.map((t) => [`_proj_${t}`, e[t]]))
@@ -124,46 +124,47 @@ var T = m.forwardRef(({ className: t, config: h, palette: y, data: b, dataKeys:
124
124
  K,
125
125
  b,
126
126
  D
127
- ]), Z = `line-proj-${m.useId().replace(/:/g, "")}`, nt = m.useMemo(() => {
128
- if (!R) return;
129
- let e = w(R);
127
+ ]), Z = `line-proj-${m.useId().replace(/:/g, "")}`, rt = m.useMemo(() => {
128
+ if (!z) return;
129
+ let e = w(z);
130
130
  return q ? xe(e) : e;
131
- }, [R, q]), rt = ie(O), it = ae(k), Q = (Se ?? []).map(([e, t], n) => ({
131
+ }, [z, q]), it = ie(O), at = ae(k), Q = (Se ?? []).map(([e, t], n) => ({
132
132
  field: `${S}${n}`,
133
133
  current: e,
134
134
  comparison: t
135
- })), $ = Q.length > 0, at = nt ?? ($ || q ? ((e) => /* @__PURE__ */ g(o, {
135
+ })), $ = Q.length > 0, ot = rt ?? ($ || q ? ((e) => /* @__PURE__ */ g(o, {
136
136
  active: e.active,
137
137
  label: e.label,
138
138
  payload: l(C(e.payload))
139
- })) : /* @__PURE__ */ g(o, {})), ot = $ || q ? ((e) => /* @__PURE__ */ g(i, {
139
+ })) : /* @__PURE__ */ g(o, {})), st = $ || q ? ((e) => /* @__PURE__ */ g(i, {
140
140
  verticalAlign: e.verticalAlign,
141
+ fontSize: I,
141
142
  payload: l(C(e.payload))
142
- })) : /* @__PURE__ */ g(i, {}), st = Q.length ? Y.map((e) => {
143
+ })) : /* @__PURE__ */ g(i, { fontSize: I }), ct = Q.length ? Y.map((e) => {
143
144
  let t = { ...e };
144
145
  for (let { field: n, current: r, comparison: i } of Q) {
145
146
  let a = e[r], o = e[i];
146
147
  t[n] = typeof a == "number" && typeof o == "number" ? [Math.min(a, o), Math.max(a, o)] : void 0;
147
148
  }
148
149
  return t;
149
- }) : Y, ct = Q.length > 0 ? pe : he;
150
+ }) : Y, lt = Q.length > 0 ? pe : he;
150
151
  return /* @__PURE__ */ g("div", {
151
- ref: Ye,
152
+ ref: Xe,
152
153
  "data-curve": A,
153
154
  "data-line-style": j,
154
155
  className: e(x({
155
156
  curve: A,
156
157
  lineStyle: j
157
158
  }), t),
158
- ...Je,
159
+ ...Ye,
159
160
  children: /* @__PURE__ */ g(n, {
160
161
  ref: W,
161
162
  config: h,
162
163
  palette: y,
163
164
  className: "size-full [&_.recharts-label]:fill-foreground",
164
- children: /* @__PURE__ */ _(ct, {
165
- data: st,
166
- margin: tt,
165
+ children: /* @__PURE__ */ _(lt, {
166
+ data: ct,
167
+ margin: nt,
167
168
  children: [
168
169
  Oe && /* @__PURE__ */ g(fe, {
169
170
  horizontal: Re ?? !0,
@@ -177,13 +178,13 @@ var T = m.forwardRef(({ className: t, config: h, palette: y, data: b, dataKeys:
177
178
  tickLine: !1,
178
179
  axisLine: !1,
179
180
  tickMargin: 8,
180
- tickFormatter: J ? void 0 : I,
181
+ tickFormatter: J ? void 0 : L,
181
182
  tick: J,
182
- angle: L,
183
+ angle: R,
183
184
  interval: Pe,
184
- textAnchor: ne(L),
185
+ textAnchor: ne(R),
185
186
  height: G.height,
186
- label: rt
187
+ label: it
187
188
  }),
188
189
  /* @__PURE__ */ g(ye, {
189
190
  type: "number",
@@ -193,12 +194,12 @@ var T = m.forwardRef(({ className: t, config: h, palette: y, data: b, dataKeys:
193
194
  unit: Ce,
194
195
  tickFormatter: Ne,
195
196
  tickCount: Fe,
196
- domain: Ze,
197
+ domain: Qe,
197
198
  width: k ? 72 : void 0,
198
- label: it
199
+ label: at
199
200
  }),
200
- ke && /* @__PURE__ */ g(a, { content: at }),
201
- Ae && /* @__PURE__ */ g(r, { content: ot }),
201
+ ke && /* @__PURE__ */ g(a, { content: ot }),
202
+ Ae && /* @__PURE__ */ g(r, { content: st }),
202
203
  q && X != null && /* @__PURE__ */ g(be, {
203
204
  projectionStart: F,
204
205
  prevTick: X,
@@ -220,7 +221,7 @@ var T = m.forwardRef(({ className: t, config: h, palette: y, data: b, dataKeys:
220
221
  tooltipType: "none"
221
222
  }, e)),
222
223
  T.map((e, t) => {
223
- let n = E?.includes(e), r = P?.[e], i = !n && (r?.showDots ?? we), a = r?.dotSize ?? Te, o = !n && (Ee ?? i), s = r?.dashed ?? j === "dashed", c = r?.showLabel ?? B, l = r?.labelPosition ?? Xe;
224
+ let n = E?.includes(e), r = P?.[e], i = !n && (r?.showDots ?? we), a = r?.dotSize ?? Te, o = !n && (Ee ?? i), s = r?.dashed ?? j === "dashed", c = r?.showLabel ?? B, l = r?.labelPosition ?? Ze;
224
225
  return /* @__PURE__ */ g(v, {
225
226
  className: q && !n ? `actual-${Z}-${t}` : void 0,
226
227
  type: U(e),
@@ -236,7 +237,7 @@ var T = m.forwardRef(({ className: t, config: h, palette: y, data: b, dataKeys:
236
237
  children: c && /* @__PURE__ */ g(me, {
237
238
  dataKey: e,
238
239
  position: l,
239
- formatter: oe(We),
240
+ formatter: oe(Ge),
240
241
  className: ee(l),
241
242
  fontSize: 12
242
243
  })
@@ -260,19 +261,19 @@ var T = m.forwardRef(({ className: t, config: h, palette: y, data: b, dataKeys:
260
261
  ...V
261
262
  }, `_proj_${e}`);
262
263
  }),
263
- Qe.map((e, t) => {
264
+ $e.map((e, t) => {
264
265
  let n = p(e, b, T);
265
266
  return n === void 0 ? null : /* @__PURE__ */ g(ge, {
266
267
  y: n,
267
268
  ...te(e.label, e.labelPosition)
268
269
  }, `${e.label ?? "ref"}-${t}`);
269
270
  }),
270
- Ge && /* @__PURE__ */ g(de, {
271
+ Ke && /* @__PURE__ */ g(de, {
271
272
  dataKey: D,
272
- tickFormatter: I,
273
+ tickFormatter: L,
273
274
  ...f({
274
- brushHeight: Ke,
275
- brushAriaLabel: qe
275
+ brushHeight: qe,
276
+ brushAriaLabel: Je
276
277
  })
277
278
  })
278
279
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"line-chart.js","names":[],"sources":["../../../../src/components/ui/line-chart/line-chart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n Area,\n Brush,\n CartesianGrid,\n ComposedChart,\n LabelList,\n Line,\n LineChart as RechartsLineChart,\n ReferenceLine,\n Text,\n XAxis,\n YAxis,\n useXAxisScale,\n usePlotArea,\n} from 'recharts';\n\nimport { cn } from '@/lib/utils';\nimport {\n CHART_LABEL_FONT_SIZE,\n CHART_LABEL_MARGIN,\n ChartContainer,\n ChartLegend,\n ChartLegendContent,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n resolveAxisDomain,\n resolveBrushProps,\n dropProjectionPayload,\n resolveChartReferenceValue,\n resolveLabelFillClass,\n resolveReferenceLineProps,\n resolveRotatedTickAnchor,\n resolveXAxisTitle,\n resolveYAxisTitle,\n toLabelFormatter,\n toReferenceLineList,\n type ChartConfig,\n type ChartPalette,\n type ChartLegendContentProps,\n type ChartTooltipContentProps,\n type CartesianChartProps,\n type ChartAnimationProps,\n type ChartBrushProps,\n type ChartCurveType,\n type ChartDataLabelProps,\n toCssKey,\n type ChartReferenceLine,\n type CartesianLabelPosition,\n} from '../chart';\nimport {\n mergeXAxisLayoutMargin,\n useXAxisLayout,\n} from '../chart/use-x-axis-layout';\nimport { resolveXAxisTickLabels } from '../chart/chart-format';\n\n// A typed recharts composition over the shared `Chart` primitives. The two CVA\n// axes are the design's Line-chart variant set: `curve` (how the segments\n// interpolate between points — straight, smoothed, or stepped; the seven values\n// are documented on the shared `ChartCurveType`) and `lineStyle` (a solid or\n// dashed stroke). \"single\" vs \"multi\" line is not a variant — it\n// falls out of how many `dataKeys` the caller plots. The classes stay empty\n// because recharts' SVG — not CSS — draws the lines: `curve` drives each\n// `<Line type>` and `lineStyle` drives its `strokeDasharray`. CVA is kept so the\n// variant set is a first-class, spec-conformant part of the API (matched against\n// ui-spec's api.yaml enums) and exposed via `VariantProps`; the resolved values\n// are also mirrored onto `data-curve` / `data-line-style` for styling hooks and\n// tests.\nconst lineChartVariants = cva('', {\n variants: {\n curve: {\n linear: '',\n monotone: '',\n natural: '',\n basis: '',\n step: '',\n stepBefore: '',\n stepAfter: '',\n },\n lineStyle: {\n solid: '',\n dashed: '',\n },\n },\n defaultVariants: {\n curve: 'monotone',\n lineStyle: 'solid',\n },\n});\n\n// Renders two <clipPath> defs (actual / projection), a CSS <style> that clips\n// each recharts Line layer to its half, and a dashed separator line at the\n// visual midpoint between the last actual tick and the first projection tick.\n//\n// recharts' StaticCurve explicitly overwrites any clipPath prop passed to\n// <Line> with its own plot-area URL, so the prop-based approach used by\n// <Area> cannot be used here. Instead we apply CSS clip-path to the outer <g>\n// that recharts wraps each Line series in (targeted via a unique className we\n// set on the <Line> component). CSS clip-path on a class selector (specificity\n// 0,1,0) beats the SVG presentation attribute recharts sets on the inner\n// <path> (specificity 0), so the two clips compose: outer = our half, inner =\n// recharts' plot-area — net effect is the correct half within the plot area.\nfunction ProjectionClip({\n projectionStart,\n prevTick,\n clipId,\n dataKeys,\n}: {\n projectionStart: string | number;\n prevTick: string | number;\n clipId: string;\n dataKeys: string[];\n}) {\n const xScale = useXAxisScale();\n const plotArea = usePlotArea();\n if (!xScale || !plotArea) return null;\n const prevX = xScale(prevTick as string);\n const currX = xScale(projectionStart as string);\n if (prevX == null || currX == null) return null;\n const midX = (prevX + currX) / 2;\n const { x, y, width, height } = plotArea;\n const css = dataKeys\n .map(\n (_, i) =>\n `.actual-${clipId}-${i}{clip-path:url(#${clipId}-actual)}` +\n `.proj-${clipId}-${i}{clip-path:url(#${clipId}-projection)}`\n )\n .join('');\n return (\n <>\n <defs>\n <clipPath id={`${clipId}-actual`}>\n <rect x={x} y={y} width={midX - x} height={height} />\n </clipPath>\n <clipPath id={`${clipId}-projection`}>\n <rect x={midX} y={y} width={x + width - midX} height={height} />\n </clipPath>\n </defs>\n <style>{css}</style>\n <line\n x1={midX}\n y1={y}\n x2={midX}\n y2={y + height}\n stroke=\"var(--ui-border-on-surface-border)\"\n strokeDasharray=\"4 4\"\n />\n </>\n );\n}\n\n// Reserved field prefix for the synthetic delta-band range series. Each band\n// mints one `__band_<n>` field that feeds an <Area>; it must never surface in\n// the tooltip or legend (those describe only real, caller-supplied series).\nconst BAND_FIELD_PREFIX = '__band_';\n\n/**\n * Drop the synthetic delta-band range series from a recharts tooltip/legend\n * payload, keeping the real series (and their order). recharts already excludes\n * the band via `legendType`/`tooltipType=\"none\"`, so this is a second, explicit\n * guard — hence it's unit-tested rather than relying on that behavior.\n */\nexport function dropBandSeries<T extends { dataKey?: unknown }>(\n payload: readonly T[] | undefined\n): T[] | undefined {\n return payload?.filter(\n (item) => !String(item.dataKey).startsWith(BAND_FIELD_PREFIX)\n );\n}\n\ntype TooltipContentType = NonNullable<\n React.ComponentProps<typeof ChartTooltip>['content']\n>;\ntype TooltipContentFn = Extract<TooltipContentType, (...args: never[]) => unknown>;\ntype LegendContentFn = Extract<\n React.ComponentProps<typeof ChartLegend>['content'],\n (...args: never[]) => unknown\n>;\ntype LegendContentProps = Parameters<LegendContentFn>[0];\ntype TooltipRenderProps = Parameters<TooltipContentFn>[0];\n\n/**\n * Wrap a caller-supplied `tooltipContent` so the synthetic delta bands are\n * stripped from the payload before it renders, while preserving recharts' own\n * mount semantics — a function is mounted via `createElement` (its own component\n * identity + hook state), an element via `cloneElement`. This mirrors what the\n * pass-through charts get by handing `content` straight to recharts, so a\n * function-form tooltip keeps its state across re-renders here too. The returned\n * component is a stable content type (memoize it on `tooltipContent`).\n */\nexport function createBandStrippedTooltip(tooltipContent: TooltipContentType) {\n return function BandStrippedTooltip(props: TooltipRenderProps) {\n const merged = {\n ...props,\n payload: dropBandSeries(props.payload),\n } as TooltipRenderProps;\n if (typeof tooltipContent === 'function') {\n const Comp = tooltipContent as React.FunctionComponent<TooltipRenderProps>;\n return <Comp {...merged} />;\n }\n // Element-form tooltip: cloneElement is the React-idiomatic way to\n // re-render an existing element with new props (preserves refs & keys).\n return React.cloneElement(tooltipContent, merged);\n };\n}\n\nfunction createProjectionTooltip(tooltipContent: TooltipContentType) {\n return function ProjectionTooltip(props: TooltipRenderProps) {\n const merged = {\n ...props,\n payload: dropProjectionPayload(props.payload),\n } as TooltipRenderProps;\n if (typeof tooltipContent === 'function') {\n const Comp = tooltipContent as React.FunctionComponent<TooltipRenderProps>;\n return <Comp {...merged} />;\n }\n // Element-form tooltip: cloneElement is the React-idiomatic way to\n // re-render an existing element with new props (preserves refs & keys).\n return React.cloneElement(tooltipContent, merged);\n };\n}\n\n/**\n * Style override for one series, keyed by its `dataKeys` entry. Anything left\n * unset falls back to the chart-wide prop, so an entry only states what differs\n * — e.g. one thicker, dashed \"target\" line among otherwise identical series.\n *\n * A series listed in `comparisonKeys` keeps its dashed, dimmed, dot-less\n * treatment: `color`, `strokeWidth` and `curveType` still apply to it, but\n * `showDots` / `dotSize` do not (the overlay is defined by having no dots).\n */\nexport interface LineChartLineSettings {\n /** Stroke color. Defaults to the series' `config` color. */\n color?: string;\n /** Stroke width. Defaults to the chart's `strokeWidth`. */\n strokeWidth?: number;\n /** Dash this series' stroke, whatever the chart-wide `lineStyle` is. */\n dashed?: boolean;\n /** Interpolation for this series only. Defaults to the chart's `curve`. */\n curveType?: ChartCurveType;\n /** Render this series' per-point dots. Defaults to the chart's `showDots`. */\n showDots?: boolean;\n /** Dot radius for this series, in px. Defaults to the chart's `dotSize`. */\n dotSize?: number;\n /** Show/hide this series' value labels, overriding the chart's `showLabels`. */\n showLabel?: boolean;\n /** Position of this series' value labels. Defaults to the chart's `labelPosition`. */\n labelPosition?: CartesianLabelPosition;\n}\n\nexport interface LineChartProps\n extends Omit<React.ComponentProps<'div'>, 'children'>,\n VariantProps<typeof lineChartVariants>,\n CartesianChartProps,\n ChartAnimationProps,\n ChartBrushProps,\n ChartDataLabelProps {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /** Row-per-point data. Each object holds the category key + one numeric field per series (`null` breaks the line unless `connectNulls`). */\n data: ReadonlyArray<Record<string, string | number | null>>;\n /**\n * Per-series map of `label` / `icon` / `tone` (from the shared `Chart`\n * primitives). Series take their colour from the container's `palette`; each\n * entry maps a key to a `label` and an optional `tone`.\n */\n config: ChartConfig;\n /** Series to plot — one `<Line>` per key. Each must exist in `config` and in every data row. */\n dataKeys: string[];\n /**\n * Subset of `dataKeys` to render as comparison/trend overlays (e.g. a\n * previous quarter or year) — dashed, dimmed, and dot-less, so they read as\n * secondary to the current-period lines. Keeps each series' own `config` color.\n */\n comparisonKeys?: string[];\n /**\n * Pairs of `[currentKey, comparisonKey]` to shade a delta band between — a\n * dimmed area filling the gap between the two series at each point,\n * visualizing the QoQ/YoY difference. Tinted with the first key's `config`\n * color. Rows where either value isn't numeric are left un-banded.\n *\n * Each pair mints an internal `__band_<n>` field (kept out of the tooltip and\n * legend), so avoid data/`config` keys with that reserved prefix.\n */\n deltaBands?: Array<[string, string]>;\n /** Category axis key (the shared dimension across rows, e.g. `\"month\"`). */\n xKey: string;\n /** Stroke width of each line. */\n strokeWidth?: number;\n /** Render a dot at each data point. */\n showDots?: boolean;\n /** Radius of each point's dot, in px. Its hover dot is 2px larger. */\n dotSize?: number;\n /**\n * Enlarge the hovered point's dot. Defaults to following `showDots`, so pass\n * it explicitly to get hover dots on a dot-less line (`showDots={false}`\n * with `showActiveDot`) or a static-only line (dots without the hover one).\n */\n showActiveDot?: boolean;\n /** Bridge `null` gaps in the data instead of breaking the line. */\n connectNulls?: boolean;\n /**\n * Per-series style overrides, keyed by `dataKeys` entry. Series with no entry\n * render from the chart-wide props.\n */\n lineSettings?: Record<string, LineChartLineSettings>;\n /**\n * One or more dashed reference/average lines on the value axis — a target, a\n * threshold, or a series mean. Pass a single object or an array to draw\n * several at once.\n */\n referenceLine?: ChartReferenceLine | ChartReferenceLine[];\n /**\n * The category value (matching an `xKey` entry) at which the projection\n * zone starts. Ticks from this point onward render in the disabled text\n * color (`--ui-text-on-surface-disabled`). The chart data and series\n * themselves are not affected — only the X-axis tick appearance changes.\n *\n * Use this when displaying a forecast/projection range where future\n * data points should be visually de-emphasized on the axis.\n */\n projectionStart?: string | number;\n showLegend?: boolean;\n /** Position of the value labels when `showLabels` is on. Defaults to `top`. */\n labelPosition?: CartesianLabelPosition;\n}\n\nconst LineChart = React.forwardRef<HTMLDivElement, LineChartProps>(\n (\n {\n className,\n config,\n palette,\n data,\n dataKeys,\n comparisonKeys,\n deltaBands,\n xKey,\n xAxisLabel,\n yAxisLabel,\n yUnit,\n curve = 'monotone',\n lineStyle = 'solid',\n strokeWidth = 2,\n showDots = false,\n dotSize = 3,\n showActiveDot,\n connectNulls = false,\n lineSettings,\n referenceLine,\n projectionStart,\n showGrid = true,\n showTooltip = true,\n showLegend = true,\n showXAxis = true,\n showYAxis = true,\n xTickFormatter,\n yTickFormatter,\n xAxisAngle,\n xAxisInterval,\n yAxisTickCount,\n yAxisDomain,\n gridDashed,\n gridHorizontal,\n gridVertical,\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n showLabels = false,\n labelPosition,\n labelFormatter,\n showBrush = false,\n brushHeight,\n brushAriaLabel,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n const lineLabelPosition = labelPosition ?? 'top';\n const yDomain = resolveAxisDomain(yAxisDomain);\n const referenceLines = toReferenceLineList(referenceLine);\n\n // A series' own `color` and `curveType` overrides also drive its delta band,\n // so the band keeps following the line it belongs to instead of the\n // chart-wide defaults.\n const colorFor = (key: string) =>\n lineSettings?.[key]?.color ?? `var(--color-${toCssKey(key)})`;\n const curveFor = (key: string) =>\n lineSettings?.[key]?.curveType ?? curve ?? 'monotone';\n\n // The plot inset is needed as soon as *any* series carries outside labels —\n // a per-series `showLabel` counts, not only the chart-wide `showLabels`.\n const hasLabels =\n showLabels ||\n Object.values(lineSettings ?? {}).some((settings) => settings.showLabel);\n\n const xAxisTickLabels = resolveXAxisTickLabels(\n data.map((row) => row[xKey]),\n xTickFormatter\n );\n const chartContainerRef = React.useRef<HTMLDivElement>(null);\n const xAxisLayout = useXAxisLayout(\n chartContainerRef,\n xAxisAngle,\n xAxisLabel,\n xAxisTickLabels.join('\\u0000')\n );\n const chartMargin = mergeXAxisLayoutMargin(\n hasLabels ? CHART_LABEL_MARGIN : undefined,\n xAxisLayout.margin\n );\n\n const projStartIndex = React.useMemo(() => {\n if (projectionStart === undefined) return -1;\n return data.findIndex((row) => row[xKey] === projectionStart);\n }, [projectionStart, data, xKey]);\n // projStartIndex === 0 (boundary at the first tick) is excluded: there is no\n // \"previous tick\" to define the clip midpoint, so clipPath defs can't be\n // emitted, which would make all series invisible. Treat it as no-projection.\n const hasProjection = projStartIndex > 0;\n\n // Build the projection tick renderer when projectionStart is set. Each tick\n // past (and including) the start value renders in the disabled text color;\n // ticks before it stay in the muted-foreground color. Returns undefined when\n // projectionStart is absent or not found so recharts uses its own renderer.\n const projectionTick = React.useMemo(() => {\n if (!hasProjection) return undefined;\n const projectedValues = new Set(\n data.slice(projStartIndex).map((row) => row[xKey])\n );\n const ProjectionTick = ({\n payload,\n index,\n ...tickProps\n }: {\n payload: { value: string | number };\n index?: number;\n [key: string]: unknown;\n }) => (\n <Text\n {...(tickProps as React.ComponentProps<typeof Text>)}\n fontSize={12}\n className={\n projectedValues.has(payload.value)\n ? 'fill-[var(--ui-text-on-surface-disabled)]'\n : 'fill-muted-foreground'\n }\n >\n {xTickFormatter\n ? xTickFormatter(payload.value as never, index)\n : payload.value}\n </Text>\n );\n return ProjectionTick;\n }, [hasProjection, projStartIndex, data, xKey, xTickFormatter]);\n\n // When projection is active, copy each series value to `_proj_${key}` — same\n // data, no nulls. Both the actual and projection <Line> see the identical curve;\n // the clipPath (computed in ProjectionClip) restricts which half each paints.\n const projectionData = React.useMemo(() => {\n if (!hasProjection) return data;\n return data.map((row) => ({\n ...row,\n ...Object.fromEntries(\n dataKeys.map((k) => [`_proj_${k}`, row[k]])\n ),\n }));\n }, [hasProjection, data, dataKeys]);\n\n // The tick immediately before the projection boundary — needed to compute the\n // visual midpoint where the clip edge and separator line are placed.\n const prevTickValue = React.useMemo(() => {\n if (projStartIndex <= 0) return undefined;\n return data[projStartIndex - 1]?.[xKey];\n }, [projStartIndex, data, xKey]);\n\n const clipId = `line-proj-${React.useId().replace(/:/g, '')}`;\n\n // Memoized so recharts sees a stable content type across renders — a fresh\n // wrapper each render would remount the caller's tooltip and reset its state.\n const customTooltip = React.useMemo(() => {\n if (!tooltipContent) return undefined;\n const bandStripped = createBandStrippedTooltip(tooltipContent);\n return hasProjection ? createProjectionTooltip(bandStripped) : bandStripped;\n }, [tooltipContent, hasProjection]);\n\n // Axis titles: the X title sits below the ticks; the Y title is rotated in\n // the left gutter. Passed to recharts' native `label` (themed via the\n // `.recharts-label` fill selector on the container).\n const xAxisTitle = resolveXAxisTitle(xAxisLabel);\n const yAxisTitle = resolveYAxisTitle(yAxisLabel);\n\n // Each delta band becomes a synthetic `[min, max]` range field per row that\n // a recharts <Area> shades. Rows where either series isn't numeric are left\n // un-banded (the area breaks there).\n const bands = (deltaBands ?? []).map(([current, comparison], index) => ({\n field: `${BAND_FIELD_PREFIX}${index}`,\n current,\n comparison,\n }));\n\n // A delta band is a synthetic `__band_*` series feeding an <Area>, not a\n // trend of its own — so it has to be stripped from the tooltip rows and the\n // legend entries before either is rendered. A caller's own tooltip is\n // already wrapped for that above; these cover the two default contents.\n const hasBands = bands.length > 0;\n const tooltipNode =\n customTooltip ??\n (hasBands || hasProjection ? (\n (props: TooltipRenderProps) => (\n <ChartTooltipContent\n active={props.active}\n label={props.label}\n payload={\n dropProjectionPayload(\n dropBandSeries(props.payload)\n ) as ChartTooltipContentProps['payload']\n }\n />\n )\n ) : (\n <ChartTooltipContent />\n ));\n const legendNode = hasBands || hasProjection ? (\n (props: LegendContentProps) => (\n <ChartLegendContent\n verticalAlign={props.verticalAlign}\n payload={\n dropProjectionPayload(\n dropBandSeries(props.payload)\n ) as ChartLegendContentProps['payload']\n }\n />\n )\n ) : (\n <ChartLegendContent />\n );\n // Bands are computed over the full (projection-augmented) rows; when\n // projection is active the band <Area> is clipped to the actual zone.\n const chartData = bands.length\n ? projectionData.map((row) => {\n const augmented: Record<string, unknown> = { ...row };\n for (const { field, current, comparison } of bands) {\n const a = row[current];\n const b = row[comparison];\n augmented[field] =\n typeof a === 'number' && typeof b === 'number'\n ? [Math.min(a, b), Math.max(a, b)]\n : undefined;\n }\n return augmented;\n })\n : projectionData;\n\n // Only a delta band needs an <Area>, which recharts renders under\n // ComposedChart, not LineChart. Escalate to ComposedChart only then, so\n // plain line charts keep the LineChart base (and their baselines) untouched.\n const RootChart = bands.length > 0 ? ComposedChart : RechartsLineChart;\n\n return (\n <div\n ref={ref}\n data-curve={curve}\n data-line-style={lineStyle}\n className={cn(lineChartVariants({ curve, lineStyle }), className)}\n {...props}\n >\n <ChartContainer\n ref={chartContainerRef}\n config={config}\n palette={palette}\n className=\"size-full [&_.recharts-label]:fill-foreground\"\n >\n <RootChart\n data={chartData as readonly unknown[]}\n margin={chartMargin}\n >\n {showGrid && (\n <CartesianGrid\n horizontal={gridHorizontal ?? true}\n vertical={gridVertical ?? false}\n strokeDasharray={gridDashed ? '3 3' : undefined}\n />\n )}\n <XAxis\n dataKey={xKey}\n type=\"category\"\n hide={!showXAxis}\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n tickFormatter={projectionTick ? undefined : xTickFormatter}\n tick={projectionTick}\n angle={xAxisAngle}\n interval={xAxisInterval}\n textAnchor={resolveRotatedTickAnchor(xAxisAngle)}\n height={xAxisLayout.height}\n label={xAxisTitle}\n />\n <YAxis\n type=\"number\"\n hide={!showYAxis}\n tickLine={false}\n axisLine={false}\n unit={yUnit}\n tickFormatter={yTickFormatter}\n tickCount={yAxisTickCount}\n domain={yDomain}\n width={yAxisLabel ? 72 : undefined}\n label={yAxisTitle}\n />\n {showTooltip && <ChartTooltip content={tooltipNode} />}\n {showLegend && <ChartLegend content={legendNode} />}\n {hasProjection && prevTickValue != null && (\n <ProjectionClip\n projectionStart={projectionStart!}\n prevTick={prevTickValue}\n clipId={clipId}\n dataKeys={dataKeys}\n />\n )}\n {/* Delta bands render before the lines so the lines draw on top.\n When projection is active, clip bands to the actual zone — the\n comparison series they measure against is absent past the\n boundary, so an unclipped band would shade a region with no\n visible far edge. */}\n {bands.map(({ field, current }) => (\n <Area\n key={field}\n dataKey={field}\n // An <Area> takes one interpolation, so a pair whose two series\n // set different `curveType`s can only follow the current one.\n type={curveFor(current)}\n stroke=\"none\"\n fill={colorFor(current)}\n fillOpacity={0.12}\n connectNulls={connectNulls}\n dot={false}\n activeDot={false}\n clipPath={hasProjection ? `url(#${clipId}-actual)` : undefined}\n {...animation}\n legendType=\"none\"\n tooltipType=\"none\"\n />\n ))}\n {dataKeys.map((key, index) => {\n // Comparison series read as secondary: always dashed, dimmed, and\n // dot-less, regardless of the global lineStyle / showDots.\n const isComparison = comparisonKeys?.includes(key);\n const settings = lineSettings?.[key];\n const seriesDots =\n !isComparison && (settings?.showDots ?? showDots);\n const dotRadius = settings?.dotSize ?? dotSize;\n // The hover dot follows the static dots unless asked otherwise, so\n // a chart that sets neither renders exactly as before.\n const activeDot = !isComparison && (showActiveDot ?? seriesDots);\n const seriesDashed = settings?.dashed ?? lineStyle === 'dashed';\n const seriesLabel = settings?.showLabel ?? showLabels;\n const seriesLabelPosition =\n settings?.labelPosition ?? lineLabelPosition;\n // Comparison series have no projection counterpart — they render\n // unclipped across the full width so they don't vanish at the\n // projection boundary.\n return (\n <Line\n key={key}\n className={hasProjection && !isComparison ? `actual-${clipId}-${index}` : undefined}\n type={curveFor(key)}\n dataKey={key}\n stroke={colorFor(key)}\n strokeWidth={settings?.strokeWidth ?? strokeWidth}\n strokeDasharray={isComparison || seriesDashed ? '5 5' : undefined}\n strokeOpacity={isComparison ? 0.5 : undefined}\n dot={seriesDots ? { r: dotRadius } : false}\n activeDot={activeDot ? { r: dotRadius + 2 } : false}\n connectNulls={connectNulls}\n {...animation}\n >\n {seriesLabel && (\n <LabelList\n dataKey={key}\n position={seriesLabelPosition}\n formatter={toLabelFormatter(labelFormatter)}\n className={resolveLabelFillClass(seriesLabelPosition)}\n fontSize={CHART_LABEL_FONT_SIZE}\n />\n )}\n </Line>\n );\n })}\n {hasProjection &&\n dataKeys.map((key, index) => {\n const isComparison = comparisonKeys?.includes(key);\n if (isComparison) return null;\n const settings = lineSettings?.[key];\n return (\n <Line\n key={`_proj_${key}`}\n className={`proj-${clipId}-${index}`}\n type={curveFor(key)}\n dataKey={`_proj_${key}`}\n name={key}\n stroke={colorFor(key)}\n strokeWidth={settings?.strokeWidth ?? strokeWidth}\n strokeDasharray=\"5 5\"\n dot={false}\n activeDot={false}\n legendType=\"none\"\n tooltipType=\"none\"\n {...animation}\n />\n );\n })}\n {/* Annotations draw over the series they describe. */}\n {referenceLines.map((ref, index) => {\n const value = resolveChartReferenceValue(ref, data, dataKeys);\n if (value === undefined) return null;\n return (\n <ReferenceLine\n key={`${ref.label ?? 'ref'}-${index}`}\n y={value}\n {...resolveReferenceLineProps(ref.label, ref.labelPosition)}\n />\n );\n })}\n {showBrush && (\n <Brush\n dataKey={xKey}\n tickFormatter={xTickFormatter}\n {...resolveBrushProps({ brushHeight, brushAriaLabel })}\n />\n )}\n </RootChart>\n </ChartContainer>\n </div>\n );\n }\n);\nLineChart.displayName = 'LineChart';\n\nexport { LineChart, lineChartVariants };\n"],"mappings":"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"}
1
+ {"version":3,"file":"line-chart.js","names":[],"sources":["../../../../src/components/ui/line-chart/line-chart.tsx"],"sourcesContent":["'use client';\n\nimport * as React from 'react';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport {\n Area,\n Brush,\n CartesianGrid,\n ComposedChart,\n LabelList,\n Line,\n LineChart as RechartsLineChart,\n ReferenceLine,\n Text,\n XAxis,\n YAxis,\n useXAxisScale,\n usePlotArea,\n} from 'recharts';\n\nimport { cn } from '@/lib/utils';\nimport {\n CHART_LABEL_FONT_SIZE,\n CHART_LABEL_MARGIN,\n ChartContainer,\n ChartLegend,\n ChartLegendContent,\n ChartTooltip,\n ChartTooltipContent,\n resolveAnimation,\n resolveAxisDomain,\n resolveBrushProps,\n dropProjectionPayload,\n resolveChartReferenceValue,\n resolveLabelFillClass,\n resolveReferenceLineProps,\n resolveRotatedTickAnchor,\n resolveXAxisTitle,\n resolveYAxisTitle,\n toLabelFormatter,\n toReferenceLineList,\n type ChartConfig,\n type ChartLegendFontSize,\n type ChartPalette,\n type ChartLegendContentProps,\n type ChartTooltipContentProps,\n type CartesianChartProps,\n type ChartAnimationProps,\n type ChartBrushProps,\n type ChartCurveType,\n type ChartDataLabelProps,\n toCssKey,\n type ChartReferenceLine,\n type CartesianLabelPosition,\n} from '../chart';\nimport {\n mergeXAxisLayoutMargin,\n useXAxisLayout,\n} from '../chart/use-x-axis-layout';\nimport { resolveXAxisTickLabels } from '../chart/chart-format';\n\n// A typed recharts composition over the shared `Chart` primitives. The two CVA\n// axes are the design's Line-chart variant set: `curve` (how the segments\n// interpolate between points — straight, smoothed, or stepped; the seven values\n// are documented on the shared `ChartCurveType`) and `lineStyle` (a solid or\n// dashed stroke). \"single\" vs \"multi\" line is not a variant — it\n// falls out of how many `dataKeys` the caller plots. The classes stay empty\n// because recharts' SVG — not CSS — draws the lines: `curve` drives each\n// `<Line type>` and `lineStyle` drives its `strokeDasharray`. CVA is kept so the\n// variant set is a first-class, spec-conformant part of the API (matched against\n// ui-spec's api.yaml enums) and exposed via `VariantProps`; the resolved values\n// are also mirrored onto `data-curve` / `data-line-style` for styling hooks and\n// tests.\nconst lineChartVariants = cva('', {\n variants: {\n curve: {\n linear: '',\n monotone: '',\n natural: '',\n basis: '',\n step: '',\n stepBefore: '',\n stepAfter: '',\n },\n lineStyle: {\n solid: '',\n dashed: '',\n },\n },\n defaultVariants: {\n curve: 'monotone',\n lineStyle: 'solid',\n },\n});\n\n// Renders two <clipPath> defs (actual / projection), a CSS <style> that clips\n// each recharts Line layer to its half, and a dashed separator line at the\n// visual midpoint between the last actual tick and the first projection tick.\n//\n// recharts' StaticCurve explicitly overwrites any clipPath prop passed to\n// <Line> with its own plot-area URL, so the prop-based approach used by\n// <Area> cannot be used here. Instead we apply CSS clip-path to the outer <g>\n// that recharts wraps each Line series in (targeted via a unique className we\n// set on the <Line> component). CSS clip-path on a class selector (specificity\n// 0,1,0) beats the SVG presentation attribute recharts sets on the inner\n// <path> (specificity 0), so the two clips compose: outer = our half, inner =\n// recharts' plot-area — net effect is the correct half within the plot area.\nfunction ProjectionClip({\n projectionStart,\n prevTick,\n clipId,\n dataKeys,\n}: {\n projectionStart: string | number;\n prevTick: string | number;\n clipId: string;\n dataKeys: string[];\n}) {\n const xScale = useXAxisScale();\n const plotArea = usePlotArea();\n if (!xScale || !plotArea) return null;\n const prevX = xScale(prevTick as string);\n const currX = xScale(projectionStart as string);\n if (prevX == null || currX == null) return null;\n const midX = (prevX + currX) / 2;\n const { x, y, width, height } = plotArea;\n const css = dataKeys\n .map(\n (_, i) =>\n `.actual-${clipId}-${i}{clip-path:url(#${clipId}-actual)}` +\n `.proj-${clipId}-${i}{clip-path:url(#${clipId}-projection)}`\n )\n .join('');\n return (\n <>\n <defs>\n <clipPath id={`${clipId}-actual`}>\n <rect x={x} y={y} width={midX - x} height={height} />\n </clipPath>\n <clipPath id={`${clipId}-projection`}>\n <rect x={midX} y={y} width={x + width - midX} height={height} />\n </clipPath>\n </defs>\n <style>{css}</style>\n <line\n x1={midX}\n y1={y}\n x2={midX}\n y2={y + height}\n stroke=\"var(--ui-border-on-surface-border)\"\n strokeDasharray=\"4 4\"\n />\n </>\n );\n}\n\n// Reserved field prefix for the synthetic delta-band range series. Each band\n// mints one `__band_<n>` field that feeds an <Area>; it must never surface in\n// the tooltip or legend (those describe only real, caller-supplied series).\nconst BAND_FIELD_PREFIX = '__band_';\n\n/**\n * Drop the synthetic delta-band range series from a recharts tooltip/legend\n * payload, keeping the real series (and their order). recharts already excludes\n * the band via `legendType`/`tooltipType=\"none\"`, so this is a second, explicit\n * guard — hence it's unit-tested rather than relying on that behavior.\n */\nexport function dropBandSeries<T extends { dataKey?: unknown }>(\n payload: readonly T[] | undefined\n): T[] | undefined {\n return payload?.filter(\n (item) => !String(item.dataKey).startsWith(BAND_FIELD_PREFIX)\n );\n}\n\ntype TooltipContentType = NonNullable<\n React.ComponentProps<typeof ChartTooltip>['content']\n>;\ntype TooltipContentFn = Extract<TooltipContentType, (...args: never[]) => unknown>;\ntype LegendContentFn = Extract<\n React.ComponentProps<typeof ChartLegend>['content'],\n (...args: never[]) => unknown\n>;\ntype LegendContentProps = Parameters<LegendContentFn>[0];\ntype TooltipRenderProps = Parameters<TooltipContentFn>[0];\n\n/**\n * Wrap a caller-supplied `tooltipContent` so the synthetic delta bands are\n * stripped from the payload before it renders, while preserving recharts' own\n * mount semantics — a function is mounted via `createElement` (its own component\n * identity + hook state), an element via `cloneElement`. This mirrors what the\n * pass-through charts get by handing `content` straight to recharts, so a\n * function-form tooltip keeps its state across re-renders here too. The returned\n * component is a stable content type (memoize it on `tooltipContent`).\n */\nexport function createBandStrippedTooltip(tooltipContent: TooltipContentType) {\n return function BandStrippedTooltip(props: TooltipRenderProps) {\n const merged = {\n ...props,\n payload: dropBandSeries(props.payload),\n } as TooltipRenderProps;\n if (typeof tooltipContent === 'function') {\n const Comp = tooltipContent as React.FunctionComponent<TooltipRenderProps>;\n return <Comp {...merged} />;\n }\n // Element-form tooltip: cloneElement is the React-idiomatic way to\n // re-render an existing element with new props (preserves refs & keys).\n return React.cloneElement(tooltipContent, merged);\n };\n}\n\nfunction createProjectionTooltip(tooltipContent: TooltipContentType) {\n return function ProjectionTooltip(props: TooltipRenderProps) {\n const merged = {\n ...props,\n payload: dropProjectionPayload(props.payload),\n } as TooltipRenderProps;\n if (typeof tooltipContent === 'function') {\n const Comp = tooltipContent as React.FunctionComponent<TooltipRenderProps>;\n return <Comp {...merged} />;\n }\n // Element-form tooltip: cloneElement is the React-idiomatic way to\n // re-render an existing element with new props (preserves refs & keys).\n return React.cloneElement(tooltipContent, merged);\n };\n}\n\n/**\n * Style override for one series, keyed by its `dataKeys` entry. Anything left\n * unset falls back to the chart-wide prop, so an entry only states what differs\n * — e.g. one thicker, dashed \"target\" line among otherwise identical series.\n *\n * A series listed in `comparisonKeys` keeps its dashed, dimmed, dot-less\n * treatment: `color`, `strokeWidth` and `curveType` still apply to it, but\n * `showDots` / `dotSize` do not (the overlay is defined by having no dots).\n */\nexport interface LineChartLineSettings {\n /** Stroke color. Defaults to the series' `config` color. */\n color?: string;\n /** Stroke width. Defaults to the chart's `strokeWidth`. */\n strokeWidth?: number;\n /** Dash this series' stroke, whatever the chart-wide `lineStyle` is. */\n dashed?: boolean;\n /** Interpolation for this series only. Defaults to the chart's `curve`. */\n curveType?: ChartCurveType;\n /** Render this series' per-point dots. Defaults to the chart's `showDots`. */\n showDots?: boolean;\n /** Dot radius for this series, in px. Defaults to the chart's `dotSize`. */\n dotSize?: number;\n /** Show/hide this series' value labels, overriding the chart's `showLabels`. */\n showLabel?: boolean;\n /** Position of this series' value labels. Defaults to the chart's `labelPosition`. */\n labelPosition?: CartesianLabelPosition;\n}\n\nexport interface LineChartProps\n extends Omit<React.ComponentProps<'div'>, 'children'>,\n VariantProps<typeof lineChartVariants>,\n CartesianChartProps,\n ChartAnimationProps,\n ChartBrushProps,\n ChartDataLabelProps {\n /**\n * The dataviz palette this chart's series are painted from. Series that\n * state no `color` of their own take a stop of it. See `ChartPalette`.\n */\n palette?: ChartPalette;\n /** Row-per-point data. Each object holds the category key + one numeric field per series (`null` breaks the line unless `connectNulls`). */\n data: ReadonlyArray<Record<string, string | number | null>>;\n /**\n * Per-series map of `label` / `icon` / `tone` (from the shared `Chart`\n * primitives). Series take their colour from the container's `palette`; each\n * entry maps a key to a `label` and an optional `tone`.\n */\n config: ChartConfig;\n /** Series to plot — one `<Line>` per key. Each must exist in `config` and in every data row. */\n dataKeys: string[];\n /**\n * Subset of `dataKeys` to render as comparison/trend overlays (e.g. a\n * previous quarter or year) — dashed, dimmed, and dot-less, so they read as\n * secondary to the current-period lines. Keeps each series' own `config` color.\n */\n comparisonKeys?: string[];\n /**\n * Pairs of `[currentKey, comparisonKey]` to shade a delta band between — a\n * dimmed area filling the gap between the two series at each point,\n * visualizing the QoQ/YoY difference. Tinted with the first key's `config`\n * color. Rows where either value isn't numeric are left un-banded.\n *\n * Each pair mints an internal `__band_<n>` field (kept out of the tooltip and\n * legend), so avoid data/`config` keys with that reserved prefix.\n */\n deltaBands?: Array<[string, string]>;\n /** Category axis key (the shared dimension across rows, e.g. `\"month\"`). */\n xKey: string;\n /** Stroke width of each line. */\n strokeWidth?: number;\n /** Render a dot at each data point. */\n showDots?: boolean;\n /** Radius of each point's dot, in px. Its hover dot is 2px larger. */\n dotSize?: number;\n /**\n * Enlarge the hovered point's dot. Defaults to following `showDots`, so pass\n * it explicitly to get hover dots on a dot-less line (`showDots={false}`\n * with `showActiveDot`) or a static-only line (dots without the hover one).\n */\n showActiveDot?: boolean;\n /** Bridge `null` gaps in the data instead of breaking the line. */\n connectNulls?: boolean;\n /**\n * Per-series style overrides, keyed by `dataKeys` entry. Series with no entry\n * render from the chart-wide props.\n */\n lineSettings?: Record<string, LineChartLineSettings>;\n /**\n * One or more dashed reference/average lines on the value axis — a target, a\n * threshold, or a series mean. Pass a single object or an array to draw\n * several at once.\n */\n referenceLine?: ChartReferenceLine | ChartReferenceLine[];\n /**\n * The category value (matching an `xKey` entry) at which the projection\n * zone starts. Ticks from this point onward render in the disabled text\n * color (`--ui-text-on-surface-disabled`). The chart data and series\n * themselves are not affected — only the X-axis tick appearance changes.\n *\n * Use this when displaying a forecast/projection range where future\n * data points should be visually de-emphasized on the axis.\n */\n projectionStart?: string | number;\n showLegend?: boolean;\n /**\n * Font size of the legend text. Defaults to `'xs'` — the size the legend has\n * always rendered at. See `ChartLegendFontSize`.\n */\n legendFontSize?: ChartLegendFontSize;\n /** Position of the value labels when `showLabels` is on. Defaults to `top`. */\n labelPosition?: CartesianLabelPosition;\n}\n\nconst LineChart = React.forwardRef<HTMLDivElement, LineChartProps>(\n (\n {\n className,\n config,\n palette,\n data,\n dataKeys,\n comparisonKeys,\n deltaBands,\n xKey,\n xAxisLabel,\n yAxisLabel,\n yUnit,\n curve = 'monotone',\n lineStyle = 'solid',\n strokeWidth = 2,\n showDots = false,\n dotSize = 3,\n showActiveDot,\n connectNulls = false,\n lineSettings,\n referenceLine,\n projectionStart,\n showGrid = true,\n showTooltip = true,\n showLegend = true,\n legendFontSize,\n showXAxis = true,\n showYAxis = true,\n xTickFormatter,\n yTickFormatter,\n xAxisAngle,\n xAxisInterval,\n yAxisTickCount,\n yAxisDomain,\n gridDashed,\n gridHorizontal,\n gridVertical,\n tooltipContent,\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n showLabels = false,\n labelPosition,\n labelFormatter,\n showBrush = false,\n brushHeight,\n brushAriaLabel,\n ...props\n },\n ref\n ) => {\n const animation = resolveAnimation({\n animate,\n animationDuration,\n animationBegin,\n animationEasing,\n });\n const lineLabelPosition = labelPosition ?? 'top';\n const yDomain = resolveAxisDomain(yAxisDomain);\n const referenceLines = toReferenceLineList(referenceLine);\n\n // A series' own `color` and `curveType` overrides also drive its delta band,\n // so the band keeps following the line it belongs to instead of the\n // chart-wide defaults.\n const colorFor = (key: string) =>\n lineSettings?.[key]?.color ?? `var(--color-${toCssKey(key)})`;\n const curveFor = (key: string) =>\n lineSettings?.[key]?.curveType ?? curve ?? 'monotone';\n\n // The plot inset is needed as soon as *any* series carries outside labels —\n // a per-series `showLabel` counts, not only the chart-wide `showLabels`.\n const hasLabels =\n showLabels ||\n Object.values(lineSettings ?? {}).some((settings) => settings.showLabel);\n\n const xAxisTickLabels = resolveXAxisTickLabels(\n data.map((row) => row[xKey]),\n xTickFormatter\n );\n const chartContainerRef = React.useRef<HTMLDivElement>(null);\n const xAxisLayout = useXAxisLayout(\n chartContainerRef,\n xAxisAngle,\n xAxisLabel,\n xAxisTickLabels.join('\\u0000')\n );\n const chartMargin = mergeXAxisLayoutMargin(\n hasLabels ? CHART_LABEL_MARGIN : undefined,\n xAxisLayout.margin\n );\n\n const projStartIndex = React.useMemo(() => {\n if (projectionStart === undefined) return -1;\n return data.findIndex((row) => row[xKey] === projectionStart);\n }, [projectionStart, data, xKey]);\n // projStartIndex === 0 (boundary at the first tick) is excluded: there is no\n // \"previous tick\" to define the clip midpoint, so clipPath defs can't be\n // emitted, which would make all series invisible. Treat it as no-projection.\n const hasProjection = projStartIndex > 0;\n\n // Build the projection tick renderer when projectionStart is set. Each tick\n // past (and including) the start value renders in the disabled text color;\n // ticks before it stay in the muted-foreground color. Returns undefined when\n // projectionStart is absent or not found so recharts uses its own renderer.\n const projectionTick = React.useMemo(() => {\n if (!hasProjection) return undefined;\n const projectedValues = new Set(\n data.slice(projStartIndex).map((row) => row[xKey])\n );\n const ProjectionTick = ({\n payload,\n index,\n ...tickProps\n }: {\n payload: { value: string | number };\n index?: number;\n [key: string]: unknown;\n }) => (\n <Text\n {...(tickProps as React.ComponentProps<typeof Text>)}\n fontSize={12}\n className={\n projectedValues.has(payload.value)\n ? 'fill-[var(--ui-text-on-surface-disabled)]'\n : 'fill-muted-foreground'\n }\n >\n {xTickFormatter\n ? xTickFormatter(payload.value as never, index)\n : payload.value}\n </Text>\n );\n return ProjectionTick;\n }, [hasProjection, projStartIndex, data, xKey, xTickFormatter]);\n\n // When projection is active, copy each series value to `_proj_${key}` — same\n // data, no nulls. Both the actual and projection <Line> see the identical curve;\n // the clipPath (computed in ProjectionClip) restricts which half each paints.\n const projectionData = React.useMemo(() => {\n if (!hasProjection) return data;\n return data.map((row) => ({\n ...row,\n ...Object.fromEntries(\n dataKeys.map((k) => [`_proj_${k}`, row[k]])\n ),\n }));\n }, [hasProjection, data, dataKeys]);\n\n // The tick immediately before the projection boundary — needed to compute the\n // visual midpoint where the clip edge and separator line are placed.\n const prevTickValue = React.useMemo(() => {\n if (projStartIndex <= 0) return undefined;\n return data[projStartIndex - 1]?.[xKey];\n }, [projStartIndex, data, xKey]);\n\n const clipId = `line-proj-${React.useId().replace(/:/g, '')}`;\n\n // Memoized so recharts sees a stable content type across renders — a fresh\n // wrapper each render would remount the caller's tooltip and reset its state.\n const customTooltip = React.useMemo(() => {\n if (!tooltipContent) return undefined;\n const bandStripped = createBandStrippedTooltip(tooltipContent);\n return hasProjection ? createProjectionTooltip(bandStripped) : bandStripped;\n }, [tooltipContent, hasProjection]);\n\n // Axis titles: the X title sits below the ticks; the Y title is rotated in\n // the left gutter. Passed to recharts' native `label` (themed via the\n // `.recharts-label` fill selector on the container).\n const xAxisTitle = resolveXAxisTitle(xAxisLabel);\n const yAxisTitle = resolveYAxisTitle(yAxisLabel);\n\n // Each delta band becomes a synthetic `[min, max]` range field per row that\n // a recharts <Area> shades. Rows where either series isn't numeric are left\n // un-banded (the area breaks there).\n const bands = (deltaBands ?? []).map(([current, comparison], index) => ({\n field: `${BAND_FIELD_PREFIX}${index}`,\n current,\n comparison,\n }));\n\n // A delta band is a synthetic `__band_*` series feeding an <Area>, not a\n // trend of its own — so it has to be stripped from the tooltip rows and the\n // legend entries before either is rendered. A caller's own tooltip is\n // already wrapped for that above; these cover the two default contents.\n const hasBands = bands.length > 0;\n const tooltipNode =\n customTooltip ??\n (hasBands || hasProjection ? (\n (props: TooltipRenderProps) => (\n <ChartTooltipContent\n active={props.active}\n label={props.label}\n payload={\n dropProjectionPayload(\n dropBandSeries(props.payload)\n ) as ChartTooltipContentProps['payload']\n }\n />\n )\n ) : (\n <ChartTooltipContent />\n ));\n const legendNode = hasBands || hasProjection ? (\n (props: LegendContentProps) => (\n <ChartLegendContent\n verticalAlign={props.verticalAlign}\n fontSize={legendFontSize}\n payload={\n dropProjectionPayload(\n dropBandSeries(props.payload)\n ) as ChartLegendContentProps['payload']\n }\n />\n )\n ) : (\n <ChartLegendContent fontSize={legendFontSize} />\n );\n // Bands are computed over the full (projection-augmented) rows; when\n // projection is active the band <Area> is clipped to the actual zone.\n const chartData = bands.length\n ? projectionData.map((row) => {\n const augmented: Record<string, unknown> = { ...row };\n for (const { field, current, comparison } of bands) {\n const a = row[current];\n const b = row[comparison];\n augmented[field] =\n typeof a === 'number' && typeof b === 'number'\n ? [Math.min(a, b), Math.max(a, b)]\n : undefined;\n }\n return augmented;\n })\n : projectionData;\n\n // Only a delta band needs an <Area>, which recharts renders under\n // ComposedChart, not LineChart. Escalate to ComposedChart only then, so\n // plain line charts keep the LineChart base (and their baselines) untouched.\n const RootChart = bands.length > 0 ? ComposedChart : RechartsLineChart;\n\n return (\n <div\n ref={ref}\n data-curve={curve}\n data-line-style={lineStyle}\n className={cn(lineChartVariants({ curve, lineStyle }), className)}\n {...props}\n >\n <ChartContainer\n ref={chartContainerRef}\n config={config}\n palette={palette}\n className=\"size-full [&_.recharts-label]:fill-foreground\"\n >\n <RootChart\n data={chartData as readonly unknown[]}\n margin={chartMargin}\n >\n {showGrid && (\n <CartesianGrid\n horizontal={gridHorizontal ?? true}\n vertical={gridVertical ?? false}\n strokeDasharray={gridDashed ? '3 3' : undefined}\n />\n )}\n <XAxis\n dataKey={xKey}\n type=\"category\"\n hide={!showXAxis}\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n tickFormatter={projectionTick ? undefined : xTickFormatter}\n tick={projectionTick}\n angle={xAxisAngle}\n interval={xAxisInterval}\n textAnchor={resolveRotatedTickAnchor(xAxisAngle)}\n height={xAxisLayout.height}\n label={xAxisTitle}\n />\n <YAxis\n type=\"number\"\n hide={!showYAxis}\n tickLine={false}\n axisLine={false}\n unit={yUnit}\n tickFormatter={yTickFormatter}\n tickCount={yAxisTickCount}\n domain={yDomain}\n width={yAxisLabel ? 72 : undefined}\n label={yAxisTitle}\n />\n {showTooltip && <ChartTooltip content={tooltipNode} />}\n {showLegend && <ChartLegend content={legendNode} />}\n {hasProjection && prevTickValue != null && (\n <ProjectionClip\n projectionStart={projectionStart!}\n prevTick={prevTickValue}\n clipId={clipId}\n dataKeys={dataKeys}\n />\n )}\n {/* Delta bands render before the lines so the lines draw on top.\n When projection is active, clip bands to the actual zone — the\n comparison series they measure against is absent past the\n boundary, so an unclipped band would shade a region with no\n visible far edge. */}\n {bands.map(({ field, current }) => (\n <Area\n key={field}\n dataKey={field}\n // An <Area> takes one interpolation, so a pair whose two series\n // set different `curveType`s can only follow the current one.\n type={curveFor(current)}\n stroke=\"none\"\n fill={colorFor(current)}\n fillOpacity={0.12}\n connectNulls={connectNulls}\n dot={false}\n activeDot={false}\n clipPath={hasProjection ? `url(#${clipId}-actual)` : undefined}\n {...animation}\n legendType=\"none\"\n tooltipType=\"none\"\n />\n ))}\n {dataKeys.map((key, index) => {\n // Comparison series read as secondary: always dashed, dimmed, and\n // dot-less, regardless of the global lineStyle / showDots.\n const isComparison = comparisonKeys?.includes(key);\n const settings = lineSettings?.[key];\n const seriesDots =\n !isComparison && (settings?.showDots ?? showDots);\n const dotRadius = settings?.dotSize ?? dotSize;\n // The hover dot follows the static dots unless asked otherwise, so\n // a chart that sets neither renders exactly as before.\n const activeDot = !isComparison && (showActiveDot ?? seriesDots);\n const seriesDashed = settings?.dashed ?? lineStyle === 'dashed';\n const seriesLabel = settings?.showLabel ?? showLabels;\n const seriesLabelPosition =\n settings?.labelPosition ?? lineLabelPosition;\n // Comparison series have no projection counterpart — they render\n // unclipped across the full width so they don't vanish at the\n // projection boundary.\n return (\n <Line\n key={key}\n className={hasProjection && !isComparison ? `actual-${clipId}-${index}` : undefined}\n type={curveFor(key)}\n dataKey={key}\n stroke={colorFor(key)}\n strokeWidth={settings?.strokeWidth ?? strokeWidth}\n strokeDasharray={isComparison || seriesDashed ? '5 5' : undefined}\n strokeOpacity={isComparison ? 0.5 : undefined}\n dot={seriesDots ? { r: dotRadius } : false}\n activeDot={activeDot ? { r: dotRadius + 2 } : false}\n connectNulls={connectNulls}\n {...animation}\n >\n {seriesLabel && (\n <LabelList\n dataKey={key}\n position={seriesLabelPosition}\n formatter={toLabelFormatter(labelFormatter)}\n className={resolveLabelFillClass(seriesLabelPosition)}\n fontSize={CHART_LABEL_FONT_SIZE}\n />\n )}\n </Line>\n );\n })}\n {hasProjection &&\n dataKeys.map((key, index) => {\n const isComparison = comparisonKeys?.includes(key);\n if (isComparison) return null;\n const settings = lineSettings?.[key];\n return (\n <Line\n key={`_proj_${key}`}\n className={`proj-${clipId}-${index}`}\n type={curveFor(key)}\n dataKey={`_proj_${key}`}\n name={key}\n stroke={colorFor(key)}\n strokeWidth={settings?.strokeWidth ?? strokeWidth}\n strokeDasharray=\"5 5\"\n dot={false}\n activeDot={false}\n legendType=\"none\"\n tooltipType=\"none\"\n {...animation}\n />\n );\n })}\n {/* Annotations draw over the series they describe. */}\n {referenceLines.map((ref, index) => {\n const value = resolveChartReferenceValue(ref, data, dataKeys);\n if (value === undefined) return null;\n return (\n <ReferenceLine\n key={`${ref.label ?? 'ref'}-${index}`}\n y={value}\n {...resolveReferenceLineProps(ref.label, ref.labelPosition)}\n />\n );\n })}\n {showBrush && (\n <Brush\n dataKey={xKey}\n tickFormatter={xTickFormatter}\n {...resolveBrushProps({ brushHeight, brushAriaLabel })}\n />\n )}\n </RootChart>\n </ChartContainer>\n </div>\n );\n }\n);\nLineChart.displayName = 'LineChart';\n\nexport { LineChart, lineChartVariants };\n"],"mappings":";;;;;;;;;;AAyEA,IAAM,IAAoB,EAAI,IAAI;CAChC,UAAU;EACR,OAAO;GACL,QAAQ;GACR,UAAU;GACV,SAAS;GACT,OAAO;GACP,MAAM;GACN,YAAY;GACZ,WAAW;EACb;EACA,WAAW;GACT,OAAO;GACP,QAAQ;EACV;CACF;CACA,iBAAiB;EACf,OAAO;EACP,WAAW;CACb;AACF,CAAC;AAcD,SAAS,GAAe,EACtB,oBACA,aACA,WACA,eAMC;CACD,IAAM,IAAS,EAAc,GACvB,IAAW,EAAY;CAC7B,IAAI,CAAC,KAAU,CAAC,GAAU,OAAO;CACjC,IAAM,IAAQ,EAAO,CAAkB,GACjC,IAAQ,EAAO,CAAyB;CAC9C,IAAI,KAAS,QAAQ,KAAS,MAAM,OAAO;CAC3C,IAAM,KAAQ,IAAQ,KAAS,GACzB,EAAE,MAAG,MAAG,UAAO,cAAW,GAC1B,IAAM,EACT,KACE,GAAG,MACF,WAAW,EAAO,GAAG,EAAE,kBAAkB,EAAO,iBACvC,EAAO,GAAG,EAAE,kBAAkB,EAAO,cAClD,CAAC,CACA,KAAK,EAAE;CACV,OACE,kBAAA,GAAA,EAAA,UAAA;EACE,kBAAC,QAAD,EAAA,UAAA,CACE,kBAAC,YAAD;GAAU,IAAI,GAAG,EAAO;GACtB,UAAA,kBAAC,QAAD;IAAS;IAAM;IAAG,OAAO,IAAO;IAAW;GAAS,CAAA;EAC5C,CAAA,GACV,kBAAC,YAAD;GAAU,IAAI,GAAG,EAAO;GACtB,UAAA,kBAAC,QAAD;IAAM,GAAG;IAAS;IAAG,OAAO,IAAI,IAAQ;IAAc;GAAS,CAAA;EACvD,CAAA,CACN,EAAA,CAAA;EACN,kBAAC,SAAD,EAAA,UAAQ,EAAW,CAAA;EACnB,kBAAC,QAAD;GACE,IAAI;GACJ,IAAI;GACJ,IAAI;GACJ,IAAI,IAAI;GACR,QAAO;GACP,iBAAgB;EACjB,CAAA;CACD,EAAA,CAAA;AAEN;AAKA,IAAM,IAAoB;AAQ1B,SAAgB,EACd,GACiB;CACjB,OAAO,GAAS,QACb,MAAS,CAAC,OAAO,EAAK,OAAO,CAAC,CAAC,WAAW,CAAiB,CAC9D;AACF;AAsBA,SAAgB,EAA0B,GAAoC;CAC5E,OAAO,SAA6B,GAA2B;EAC7D,IAAM,IAAS;GACb,GAAG;GACH,SAAS,EAAe,EAAM,OAAO;EACvC;EAOA,OANI,OAAO,KAAmB,aAErB,kBAAC,GAAD,EAAM,GAAI,EAAS,CAAA,IAIrB,EAAM,aAAa,GAAgB,CAAM;CAClD;AACF;AAEA,SAAS,GAAwB,GAAoC;CACnE,OAAO,SAA2B,GAA2B;EAC3D,IAAM,IAAS;GACb,GAAG;GACH,SAAS,EAAsB,EAAM,OAAO;EAC9C;EAOA,OANI,OAAO,KAAmB,aAErB,kBAAC,GAAD,EAAM,GAAI,EAAS,CAAA,IAIrB,EAAM,aAAa,GAAgB,CAAM;CAClD;AACF;AAmHA,IAAM,IAAY,EAAM,YAEpB,EACE,cACA,WACA,YACA,SACA,aACA,mBACA,gBACA,SACA,eACA,eACA,WACA,WAAQ,YACR,eAAY,SACZ,iBAAc,GACd,eAAW,IACX,cAAU,GACV,mBACA,kBAAe,IACf,iBACA,mBACA,oBACA,eAAW,IACX,kBAAc,IACd,iBAAa,IACb,mBACA,gBAAY,IACZ,gBAAY,IACZ,mBACA,oBACA,eACA,mBACA,oBACA,iBACA,gBACA,oBACA,kBACA,mBACA,aACA,uBACA,oBACA,qBACA,gBAAa,IACb,mBACA,oBACA,gBAAY,IACZ,iBACA,oBACA,GAAG,MAEL,OACG;CACH,IAAM,IAAY,EAAiB;EACjC;EACA;EACA;EACA;CACF,CAAC,GACK,KAAoB,MAAiB,OACrC,KAAU,EAAkB,EAAW,GACvC,KAAiB,GAAoB,EAAa,GAKlD,KAAY,MAChB,IAAe,EAAI,EAAE,SAAS,eAAe,EAAS,CAAG,EAAE,IACvD,KAAY,MAChB,IAAe,EAAI,EAAE,aAAa,KAAS,YAIvC,KACJ,KACA,OAAO,OAAO,KAAgB,CAAC,CAAC,CAAC,CAAC,MAAM,MAAa,EAAS,SAAS,GAEnE,KAAkB,GACtB,EAAK,KAAK,MAAQ,EAAI,EAAK,GAC3B,CACF,GACM,IAAoB,EAAM,OAAuB,IAAI,GACrD,IAAc,GAClB,GACA,GACA,GACA,GAAgB,KAAK,IAAQ,CAC/B,GACM,KAAc,GAClB,KAAY,IAAqB,KAAA,GACjC,EAAY,MACd,GAEM,IAAiB,EAAM,cACvB,MAAoB,KAAA,IAAkB,KACnC,EAAK,WAAW,MAAQ,EAAI,OAAU,CAAe,GAC3D;EAAC;EAAiB;EAAM;CAAI,CAAC,GAI1B,IAAgB,IAAiB,GAMjC,IAAiB,EAAM,cAAc;EACzC,IAAI,CAAC,GAAe;EACpB,IAAM,IAAkB,IAAI,IAC1B,EAAK,MAAM,CAAc,CAAC,CAAC,KAAK,MAAQ,EAAI,EAAK,CACnD;EAwBA,QAvBwB,EACtB,YACA,UACA,GAAG,QAMH,kBAAC,IAAD;GACE,GAAK;GACL,UAAU;GACV,WACE,EAAgB,IAAI,EAAQ,KAAK,IAC7B,8CACA;GAGL,UAAA,IACG,EAAe,EAAQ,OAAgB,CAAK,IAC5C,EAAQ;EACR,CAAA;CAGV,GAAG;EAAC;EAAe;EAAgB;EAAM;EAAM;CAAc,CAAC,GAKxD,IAAiB,EAAM,cACtB,IACE,EAAK,KAAK,OAAS;EACxB,GAAG;EACH,GAAG,OAAO,YACR,EAAS,KAAK,MAAM,CAAC,SAAS,KAAK,EAAI,EAAE,CAAC,CAC5C;CACF,EAAE,IANyB,GAO1B;EAAC;EAAe;EAAM;CAAQ,CAAC,GAI5B,IAAgB,EAAM,cAAc;EACpC,WAAkB,IACtB,OAAO,EAAK,IAAiB,EAAE,GAAG;CACpC,GAAG;EAAC;EAAgB;EAAM;CAAI,CAAC,GAEzB,IAAS,aAAa,EAAM,MAAM,CAAC,CAAC,QAAQ,MAAM,EAAE,KAIpD,KAAgB,EAAM,cAAc;EACxC,IAAI,CAAC,GAAgB;EACrB,IAAM,IAAe,EAA0B,CAAc;EAC7D,OAAO,IAAgB,GAAwB,CAAY,IAAI;CACjE,GAAG,CAAC,GAAgB,CAAa,CAAC,GAK5B,KAAa,GAAkB,CAAU,GACzC,KAAa,GAAkB,CAAU,GAKzC,KAAS,MAAc,CAAC,EAAA,CAAG,KAAK,CAAC,GAAS,IAAa,OAAW;EACtE,OAAO,GAAG,IAAoB;EAC9B;EACA;CACF,EAAE,GAMI,IAAW,EAAM,SAAS,GAC1B,KACJ,OACC,KAAY,MACV,MACC,kBAAC,GAAD;EACE,QAAQ,EAAM;EACd,OAAO,EAAM;EACb,SACE,EACE,EAAe,EAAM,OAAO,CAC9B;CAEH,CAAA,KAGH,kBAAC,GAAD,CAAsB,CAAA,IAEpB,KAAa,KAAY,MAC5B,MACC,kBAAC,GAAD;EACE,eAAe,EAAM;EACrB,UAAU;EACV,SACE,EACE,EAAe,EAAM,OAAO,CAC9B;CAEH,CAAA,KAGH,kBAAC,GAAD,EAAoB,UAAU,EAAiB,CAAA,GAI3C,KAAY,EAAM,SACpB,EAAe,KAAK,MAAQ;EAC1B,IAAM,IAAqC,EAAE,GAAG,EAAI;EACpD,KAAK,IAAM,EAAE,UAAO,YAAS,mBAAgB,GAAO;GAClD,IAAM,IAAI,EAAI,IACR,IAAI,EAAI;GACd,EAAU,KACR,OAAO,KAAM,YAAY,OAAO,KAAM,WAClC,CAAC,KAAK,IAAI,GAAG,CAAC,GAAG,KAAK,IAAI,GAAG,CAAC,CAAC,IAC/B,KAAA;EACR;EACA,OAAO;CACT,CAAC,IACD,GAKE,KAAY,EAAM,SAAS,IAAI,KAAgB;CAErD,OACE,kBAAC,OAAD;EACO;EACL,cAAY;EACZ,mBAAiB;EACjB,WAAW,EAAG,EAAkB;GAAE;GAAO;EAAU,CAAC,GAAG,CAAS;EAChE,GAAI;EAEJ,UAAA,kBAAC,GAAD;GACE,KAAK;GACG;GACC;GACT,WAAU;GAEV,UAAA,kBAAC,IAAD;IACE,MAAM;IACN,QAAQ;IAFV,UAAA;KAIG,MACC,kBAAC,IAAD;MACE,YAAY,MAAkB;MAC9B,UAAU,MAAgB;MAC1B,iBAAiB,KAAa,QAAQ,KAAA;KACvC,CAAA;KAEH,kBAAC,IAAD;MACE,SAAS;MACT,MAAK;MACL,MAAM,CAAC;MACP,UAAU;MACV,UAAU;MACV,YAAY;MACZ,eAAe,IAAiB,KAAA,IAAY;MAC5C,MAAM;MACN,OAAO;MACP,UAAU;MACV,YAAY,GAAyB,CAAU;MAC/C,QAAQ,EAAY;MACpB,OAAO;KACR,CAAA;KACD,kBAAC,IAAD;MACE,MAAK;MACL,MAAM,CAAC;MACP,UAAU;MACV,UAAU;MACV,MAAM;MACN,eAAe;MACf,WAAW;MACX,QAAQ;MACR,OAAO,IAAa,KAAK,KAAA;MACzB,OAAO;KACR,CAAA;KACA,MAAe,kBAAC,GAAD,EAAc,SAAS,GAAc,CAAA;KACpD,MAAc,kBAAC,GAAD,EAAa,SAAS,GAAa,CAAA;KACjD,KAAiB,KAAiB,QACjC,kBAAC,IAAD;MACmB;MACjB,UAAU;MACF;MACE;KACX,CAAA;KAOF,EAAM,KAAK,EAAE,UAAO,iBACnB,kBAAC,IAAD;MAEE,SAAS;MAGT,MAAM,EAAS,CAAO;MACtB,QAAO;MACP,MAAM,EAAS,CAAO;MACtB,aAAa;MACC;MACd,KAAK;MACL,WAAW;MACX,UAAU,IAAgB,QAAQ,EAAO,YAAY,KAAA;MACrD,GAAI;MACJ,YAAW;MACX,aAAY;KACb,GAfM,CAeN,CACF;KACA,EAAS,KAAK,GAAK,MAAU;MAG5B,IAAM,IAAe,GAAgB,SAAS,CAAG,GAC3C,IAAW,IAAe,IAC1B,IACJ,CAAC,MAAiB,GAAU,YAAY,KACpC,IAAY,GAAU,WAAW,IAGjC,IAAY,CAAC,MAAiB,MAAiB,IAC/C,IAAe,GAAU,UAAU,MAAc,UACjD,IAAc,GAAU,aAAa,GACrC,IACJ,GAAU,iBAAiB;MAI7B,OACE,kBAAC,GAAD;OAEE,WAAW,KAAiB,CAAC,IAAe,UAAU,EAAO,GAAG,MAAU,KAAA;OAC1E,MAAM,EAAS,CAAG;OAClB,SAAS;OACT,QAAQ,EAAS,CAAG;OACpB,aAAa,GAAU,eAAe;OACtC,iBAAiB,KAAgB,IAAe,QAAQ,KAAA;OACxD,eAAe,IAAe,KAAM,KAAA;OACpC,KAAK,IAAa,EAAE,GAAG,EAAU,IAAI;OACrC,WAAW,IAAY,EAAE,GAAG,IAAY,EAAE,IAAI;OAChC;OACd,GAAI;OAEH,UAAA,KACC,kBAAC,IAAD;QACE,SAAS;QACT,UAAU;QACV,WAAW,GAAiB,EAAc;QAC1C,WAAW,GAAsB,CAAmB;QACpD,UAAA;OACD,CAAA;MAEC,GAtBC,CAsBD;KAEV,CAAC;KACA,KACC,EAAS,KAAK,GAAK,MAAU;MAE3B,IADqB,GAAgB,SAAS,CAAG,GAC/B,OAAO;MACzB,IAAM,IAAW,IAAe;MAChC,OACE,kBAAC,GAAD;OAEE,WAAW,QAAQ,EAAO,GAAG;OAC7B,MAAM,EAAS,CAAG;OAClB,SAAS,SAAS;OAClB,MAAM;OACN,QAAQ,EAAS,CAAG;OACpB,aAAa,GAAU,eAAe;OACtC,iBAAgB;OAChB,KAAK;OACL,WAAW;OACX,YAAW;OACX,aAAY;OACZ,GAAI;MACL,GAbM,SAAS,GAaf;KAEL,CAAC;KAEF,GAAe,KAAK,GAAK,MAAU;MAClC,IAAM,IAAQ,EAA2B,GAAK,GAAM,CAAQ;MAE5D,OADI,MAAU,KAAA,IAAkB,OAE9B,kBAAC,IAAD;OAEE,GAAG;OACH,GAAI,GAA0B,EAAI,OAAO,EAAI,aAAa;MAC3D,GAHM,GAAG,EAAI,SAAS,MAAM,GAAG,GAG/B;KAEL,CAAC;KACA,MACC,kBAAC,IAAD;MACE,SAAS;MACT,eAAe;MACf,GAAI,EAAkB;OAAE;OAAa;MAAe,CAAC;KACtD,CAAA;IAEM;;EACG,CAAA;CACb,CAAA;AAET,CACF;AACA,EAAU,cAAc"}