{
  "name": "chart",
  "dependencies": ["@soybeanjs/headless", "@soybeanjs/utils", "@unovis/vue", "@unovis/ts"],
  "files": [
    {
      "path": "packages/chart/src/components/chart-container/chart-container.vue",
      "type": "registry:ui",
      "content": "<script setup lang=\"ts\">\nimport { computed, toRefs, useId } from 'vue';\nimport { provideChartContext } from '../../context';\nimport { chartContainerVariants } from '../../styles/chart-container';\nimport SChartStyle from '../chart-style/chart-style.vue';\nimport type { ChartContainerProps, ChartContainerSlots } from './types';\n\ndefineOptions({\n  name: 'SChartContainer'\n});\n\nconst props = withDefaults(defineProps<ChartContainerProps>(), {\n  cursor: true\n});\n\ndefineSlots<ChartContainerSlots>();\n\nconst { config } = toRefs(props);\nconst uniqueId = useId();\n\nconst chartId = computed(() => `chart-${props.id ?? uniqueId.replace(/:/g, '')}`);\n\nconst cls = computed(() => chartContainerVariants(undefined, props.class));\n\nconst containerStyle = computed(() => ({\n  '--vis-tooltip-padding': '0px',\n  '--vis-tooltip-background-color': 'transparent',\n  '--vis-tooltip-border-color': 'transparent',\n  '--vis-tooltip-text-color': 'none',\n  '--vis-tooltip-shadow-color': 'none',\n  '--vis-tooltip-backdrop-filter': 'none',\n  '--vis-crosshair-circle-stroke-color': '#0000',\n  '--vis-crosshair-line-stroke-width': props.cursor ? '1px' : '0px',\n  '--vis-font-family': 'var(--font-sans)'\n}));\n\nprovideChartContext({ id: chartId.value, config });\n</script>\n\n<template>\n  <div data-slot=\"chart\" :data-chart=\"chartId\" :class=\"cls\" :style=\"containerStyle\">\n    <slot :id=\"chartId\" :config=\"config\" />\n    <SChartStyle :id=\"chartId\" />\n  </div>\n</template>\n"
    },
    {
      "path": "packages/chart/src/components/chart-container/index.ts",
      "type": "registry:lib",
      "content": "export { default as SChartContainer } from './chart-container.vue';\n\nexport type * from './types';\n"
    },
    {
      "path": "packages/chart/src/components/chart-tooltip-content/chart-tooltip-content.vue",
      "type": "registry:ui",
      "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\nimport { chartTooltipContentVariants } from '../../styles/chart-tooltip-content';\nimport type { ChartTooltipContentProps } from './types';\n\ndefineOptions({\n  name: 'SChartTooltipContent'\n});\n\nconst props = withDefaults(defineProps<ChartTooltipContentProps>(), {\n  payload: () => ({}),\n  config: () => ({}),\n  indicator: 'dot'\n});\n\nfunction toDisplayValue(value: unknown): number | string | Date {\n  if (value instanceof Date || typeof value === 'number') {\n    return value;\n  }\n\n  return String(value);\n}\n\nconst fill = computed(() => (typeof props.payload.fill === 'string' ? props.payload.fill : undefined));\n\nconst payload = computed(() =>\n  Object.entries(props.payload)\n    .map(([key, value]) => {\n      const itemConfig = props.config[key];\n      const indicatorColor = itemConfig?.color ?? fill.value;\n\n      return { key, value: toDisplayValue(value), itemConfig, indicatorColor };\n    })\n    .filter(item => item.itemConfig)\n);\n\nconst nestLabel = computed(() => Object.keys(props.payload).length === 1 && props.indicator !== 'dot');\n\nconst tooltipLabel = computed(() => {\n  if (props.hideLabel) {\n    return undefined;\n  }\n\n  if (props.labelFormatter && props.x !== undefined) {\n    return props.labelFormatter(props.x);\n  }\n\n  if (props.labelKey) {\n    return props.config[props.labelKey]?.label ?? props.payload[props.labelKey];\n  }\n\n  return props.x;\n});\n\nconst ui = computed(() =>\n  chartTooltipContentVariants({ indicator: props.indicator, nestLabel: nestLabel.value }, undefined, {\n    root: props.class\n  })\n);\n</script>\n\n<template>\n  <div :class=\"ui.root\">\n    <slot>\n      <div v-if=\"!nestLabel && tooltipLabel\" :class=\"ui.label\">\n        {{ tooltipLabel }}\n      </div>\n      <div :class=\"ui.items\">\n        <div v-for=\"{ key, value, itemConfig, indicatorColor } in payload\" :key=\"key\" :class=\"ui.item\">\n          <component :is=\"itemConfig.icon\" v-if=\"itemConfig?.icon\" />\n          <template v-else-if=\"!hideIndicator\">\n            <div :class=\"ui.indicator\" :style=\"{ '--color-bg': indicatorColor, '--color-border': indicatorColor }\" />\n          </template>\n          <div :class=\"ui.itemContent\">\n            <div :class=\"ui.itemLabelWrap\">\n              <div v-if=\"nestLabel\" :class=\"ui.itemTitle\">{{ tooltipLabel }}</div>\n              <span :class=\"ui.itemLabel\">{{ itemConfig?.label || value }}</span>\n            </div>\n            <span v-if=\"value\" :class=\"ui.itemValue\">{{ value.toLocaleString() }}</span>\n          </div>\n        </div>\n      </div>\n    </slot>\n  </div>\n</template>\n"
    },
    {
      "path": "packages/chart/src/components/chart-tooltip-content/index.ts",
      "type": "registry:lib",
      "content": "export { default as SChartTooltipContent } from './chart-tooltip-content.vue';\n\nexport type * from './types';\n"
    },
    {
      "path": "packages/chart/src/components/chart-legend-content/chart-legend-content.vue",
      "type": "registry:ui",
      "content": "<script setup lang=\"ts\">\nimport { computed, onMounted, ref } from 'vue';\nimport { useChartContext } from '../../context';\nimport { chartLegendContentVariants } from '../../styles/chart-legend-content';\nimport type { ChartLegendContentProps } from './types';\n\ndefineOptions({\n  name: 'SChartLegendContent'\n});\n\nconst props = withDefaults(defineProps<ChartLegendContentProps>(), {\n  verticalAlign: 'bottom'\n});\n\nconst { id, config } = useChartContext('SChartLegendContent');\n\nconst containerSelector = ref('');\n\nconst payload = computed(() =>\n  Object.entries(config.value).map(([key, value]) => ({\n    key: props.nameKey || key,\n    itemConfig: value\n  }))\n);\n\nconst ui = computed(() =>\n  chartLegendContentVariants({ verticalAlign: props.verticalAlign }, undefined, { root: props.class })\n);\n\nonMounted(() => {\n  containerSelector.value = `[data-chart=\"${id}\"]>[data-vis-xy-container]`;\n});\n</script>\n\n<template>\n  <div v-if=\"containerSelector\" :class=\"ui.root\">\n    <div v-for=\"{ key, itemConfig } in payload\" :key=\"key\" :class=\"ui.item\">\n      <component :is=\"itemConfig.icon\" v-if=\"itemConfig.icon && !hideIcon\" />\n      <div v-else :class=\"ui.indicator\" :style=\"{ backgroundColor: itemConfig?.color }\" />\n      {{ itemConfig.label }}\n    </div>\n  </div>\n</template>\n"
    },
    {
      "path": "packages/chart/src/components/chart-legend-content/index.ts",
      "type": "registry:lib",
      "content": "export { default as SChartLegendContent } from './chart-legend-content.vue';\n\nexport type * from './types';\n"
    },
    {
      "path": "packages/chart/src/components/chart-style/chart-style.vue",
      "type": "registry:ui",
      "content": "<script setup lang=\"ts\">\nimport { computed } from 'vue';\nimport { Primitive } from '@soybeanjs/headless';\nimport { buildChartColorCss } from '../../utils/chart-style';\nimport { useChartContext } from '../../context';\nimport type { ChartStyleProps } from './types';\n\ndefineOptions({\n  name: 'SChartStyle'\n});\n\nconst props = defineProps<ChartStyleProps>();\n\nconst { config } = useChartContext('SChartStyle');\n\nconst cssText = computed(() => buildChartColorCss(props.id ?? '', config.value));\n</script>\n\n<template>\n  <Primitive v-if=\"cssText\" as=\"style\">{{ cssText }}</Primitive>\n</template>\n"
    },
    {
      "path": "packages/chart/src/components/chart-style/index.ts",
      "type": "registry:lib",
      "content": "export { default as SChartStyle } from './chart-style.vue';\n\nexport type * from './types';\n"
    },
    {
      "path": "packages/chart/src/context.ts",
      "type": "registry:lib",
      "content": "import { useContext } from '@soybeanjs/headless/composables';\nimport type { ChartContext } from './types';\n\n/**\n * Chart context bridge.\n *\n * `SChartContainer` provides `{ id, config }`; `SChartLegendContent` and\n * `SChartStyle` consume it to resolve colors and scoped selectors.\n */\nexport const [provideChartContext, useChartContext] = useContext<ChartContext>('Chart');\n"
    },
    {
      "path": "packages/chart/src/types/index.ts",
      "type": "registry:lib",
      "content": "import type { Component, Ref } from 'vue';\n\n/**\n * Chart color theme contexts.\n *\n * `''` matches the root selector (light), `.dark` matches the dark root.\n * Mirrors the theme engine's `darkSelector` contract used by `@soybeanjs/ui`.\n */\nexport const chartThemes = { light: '', dark: '.dark' } as const;\n\nexport type ChartThemeKey = keyof typeof chartThemes;\n\n/**\n * The theme's chart palette, ready for `ChartConfig` `color` values.\n *\n * The theme engine stores `--chart-N` as raw HSL channel triplets\n * (`--chart-1: 20.5 90.2% 48.2%`), so each reference is wrapped in `hsl()`\n * to be a valid CSS color. Use these instead of writing `var(--chart-N)`.\n */\nexport const chartColors = [\n  'hsl(var(--chart-1))',\n  'hsl(var(--chart-2))',\n  'hsl(var(--chart-3))',\n  'hsl(var(--chart-4))',\n  'hsl(var(--chart-5))'\n] as const;\n\n/**\n * Declarative configuration for a chart's data series.\n *\n * Each key maps a series/`dataKey` to its label, optional icon and color.\n * Color can be a single CSS color or a per-theme map resolved at render time.\n */\nexport type ChartConfig = {\n  [k in string]: {\n    label?: string | Component;\n    icon?: string | Component;\n  } & ({ color?: string; theme?: never } | { color?: never; theme: Record<ChartThemeKey, string> });\n};\n\n/** Reactive context shared between `SChartContainer` and its chart parts. */\nexport interface ChartContext {\n  /** Sanitized chart id bound to `data-chart`, used by legend/style selectors. */\n  id: string;\n  /** Reactive chart config consumed by tooltip/legend/style parts. */\n  config: Ref<ChartConfig>;\n}\n"
    },
    {
      "path": "packages/chart/src/utils/component-to-string.ts",
      "type": "registry:lib",
      "content": "import { h, render } from 'vue';\nimport type { Component } from 'vue';\nimport type { ChartConfig } from '../types';\n\n/**\n * Module-level render cache keyed by the serialized data payload.\n *\n * Bounded by the number of distinct hovered datum per chart; avoids re-rendering\n * a tooltip/legend Vue component to an HTML string on every pointer move.\n */\nconst cache = new Map<string, string>();\n\nlet counter = 0;\n\n/** Stable string key for a data payload, order-independent for top-level keys. */\nfunction serializeKey(key: Record<string, unknown>): string {\n  return JSON.stringify(key, Object.keys(key).sort());\n}\n\n/**\n * Render a Vue component to an HTML string for chart-library `template` props.\n *\n * Unovis tooltips/crosshairs accept a string template; this renders\n * `SChartTooltipContent` (with `payload`/`config`/`x`) into the DOM once per\n * datum and caches the result. Returns `undefined` on the server.\n */\nexport function componentToString(config: ChartConfig, component: Component, props?: object) {\n  if (typeof document === 'undefined') {\n    return undefined;\n  }\n\n  const id = `chart-cache-${++counter}`;\n\n  return (_data: unknown, x: number | Date) => {\n    const data = 'data' in (_data as Record<string, unknown>) ? (_data as { data: unknown }).data : _data;\n    const serializedKey = `${id}-${serializeKey(data as Record<string, unknown>)}`;\n    const cachedContent = cache.get(serializedKey);\n    if (cachedContent) {\n      return cachedContent;\n    }\n\n    const vnode = h(component, { ...props, payload: data, config, x });\n    const div = document.createElement('div');\n    render(vnode, div);\n    const content = div.innerHTML;\n    cache.set(serializedKey, content);\n    return content;\n  };\n}\n"
    },
    {
      "path": "packages/chart/src/utils/chart-style.ts",
      "type": "registry:lib",
      "content": "import { chartThemes } from '../types';\nimport type { ChartConfig, ChartThemeKey } from '../types';\n\n/**\n * Build the scoped CSS that maps each config key to a `--color-{key}` variable.\n *\n * The output is scoped to `[data-chart={id}]` per theme selector (`''` for\n * light, `.dark` for dark), letting chart library parts reference\n * `var(--color-{key})` for series colors. Pure and SSR-safe.\n */\nexport function buildChartColorCss(id: string, config: ChartConfig): string {\n  const colorConfig = Object.entries(config).filter(([, item]) => item.theme || item.color);\n  if (colorConfig.length === 0) {\n    return '';\n  }\n\n  return (Object.keys(chartThemes) as ChartThemeKey[])\n    .map(theme => {\n      const prefix = chartThemes[theme];\n      const cssVars = colorConfig\n        .map(([key, item]) => {\n          const color = item.theme?.[theme] ?? item.color;\n          return color ? `  --color-${key}: ${color};` : null;\n        })\n        .filter((line): line is string => Boolean(line))\n        .join('\\n');\n\n      return `${prefix} [data-chart=${id}] {\\n${cssVars}\\n}`;\n    })\n    .join('\\n');\n}\n"
    },
    {
      "path": "packages/chart/src/styles/chart-container.ts",
      "type": "registry:style",
      "content": "// @unocss-include\nimport { cv } from '@soybeanjs/cva';\n\n/**\n * Root chart container recipe.\n *\n * The arbitrary selectors override the chart library's internal SVG parts\n * (tick labels/lines, crosshair cursor, radial/polar sectors, surface) to match\n * the SoybeanUI theme, mirroring the upstream shadcn-vue chart recipe.\n */\nexport const chartContainerVariants = cv({\n  base: `flex aspect-video h-full w-full flex-col justify-center text-xs\n[&_.tick_text]:!fill-muted-foreground\n[&_.tick_line]:!stroke-border/50\n[&_.recharts-curve.recharts-tooltip-cursor]:stroke-border\n[&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border\n[&_.recharts-radial-bar-background-sector]:fill-muted\n[&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted\n[&_.recharts-reference-line_[stroke='#ccc']]:stroke-border\n[&_.recharts-dot[stroke='#fff']]:stroke-transparent\n[&_.recharts-layer]:outline-hidden\n[&_.recharts-sector]:outline-hidden\n[&_.recharts-sector[stroke='#fff']]:stroke-transparent\n[&_.recharts-surface]:outline-hidden\n[&_[data-vis-xy-container]]:h-full\n[&_[data-vis-xy-container]]:w-full\n[&_[data-vis-single-container]]:h-full\n[&_[data-vis-single-container]]:w-full`\n});\n"
    },
    {
      "path": "packages/chart/src/styles/chart-tooltip-content.ts",
      "type": "registry:style",
      "content": "// @unocss-include\nimport { scv } from '@soybeanjs/cva';\n\n/**\n * Chart tooltip content recipe.\n *\n * Indicator variants drive both the row alignment and the indicator size/shape;\n * `nestLabel` switches the value column to end-alignment when a single nested\n * series is rendered.\n */\nexport const chartTooltipContentVariants = scv({\n  slots: {\n    root: 'grid min-w-[8rem] items-start gap-1.5 rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl',\n    label: 'font-medium',\n    items: 'grid gap-1.5',\n    item: 'flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5 [&>svg]:text-muted-foreground',\n    indicator: 'shrink-0 rounded-xs border-[var(--color-border)] bg-[var(--color-bg)]',\n    itemContent: 'flex flex-1 justify-between leading-none',\n    itemLabelWrap: 'grid gap-1.5',\n    itemTitle: 'font-medium',\n    itemLabel: 'text-muted-foreground',\n    itemValue: 'font-mono font-medium tabular-nums text-foreground'\n  },\n  variants: {\n    indicator: {\n      dot: {\n        item: 'items-center',\n        indicator: 'h-2.5 w-2.5'\n      },\n      line: {\n        indicator: 'w-1'\n      },\n      dashed: {\n        indicator: 'w-0 border-[1.5px] border-dashed bg-transparent'\n      }\n    },\n    nestLabel: {\n      true: {\n        item: 'items-end',\n        indicator: 'my-0.5'\n      },\n      false: {\n        item: 'items-center'\n      }\n    }\n  },\n  defaultVariants: {\n    indicator: 'dot',\n    nestLabel: false\n  }\n});\n"
    },
    {
      "path": "packages/chart/src/styles/chart-legend-content.ts",
      "type": "registry:style",
      "content": "// @unocss-include\nimport { scv } from '@soybeanjs/cva';\n\n/**\n * Chart legend content recipe.\n *\n * `verticalAlign` controls top/bottom padding relative to the chart area.\n */\nexport const chartLegendContentVariants = scv({\n  slots: {\n    root: 'flex items-center justify-center gap-4',\n    item: 'flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3 [&>svg]:text-muted-foreground',\n    indicator: 'h-2 w-2 shrink-0 rounded-xs',\n    label: ''\n  },\n  variants: {\n    verticalAlign: {\n      top: {\n        root: 'pb-3'\n      },\n      bottom: {\n        root: 'pt-3'\n      }\n    }\n  },\n  defaultVariants: {\n    verticalAlign: 'bottom'\n  }\n});\n"
    },
    {
      "path": "packages/chart/src/components/chart-container/types.ts",
      "type": "registry:lib",
      "content": "import type { HTMLAttributes } from 'vue';\nimport type { ClassValue } from '@soybeanjs/headless/types';\nimport type { ChartConfig } from '../../types';\n\n/**\n * Properties for the SChartContainer component.\n */\nexport interface ChartContainerProps extends /** @vue-ignore */ HTMLAttributes {\n  /**\n   * Declarative chart configuration.\n   */\n  config: ChartConfig;\n  /**\n   * Whether to render a crosshair cursor on hover.\n   * @default true\n   */\n  cursor?: boolean;\n  /**\n   * Additional class names applied to the root element.\n   */\n  class?: ClassValue;\n}\n\n/**\n * Slot bindings for the SChartContainer component.\n */\nexport type ChartContainerSlots = {\n  default: (props: {\n    /** Sanitized chart id bound to `data-chart`. */\n    id: string;\n    /** Chart config provided to chart parts. */\n    config: ChartConfig;\n  }) => any;\n};\n"
    },
    {
      "path": "packages/chart/src/components/chart-tooltip-content/types.ts",
      "type": "registry:lib",
      "content": "import type { HTMLAttributes } from 'vue';\nimport type { ClassValue } from '@soybeanjs/headless/types';\nimport type { ChartConfig } from '../../types';\n\n/**\n * Properties for the SChartTooltipContent component.\n */\nexport interface ChartTooltipContentProps extends /** @vue-ignore */ HTMLAttributes {\n  /**\n   * Whether to hide the tooltip label.\n   */\n  hideLabel?: boolean;\n  /**\n   * Whether to hide the series indicator.\n   */\n  hideIndicator?: boolean;\n  /**\n   * Indicator style.\n   * @default 'dot'\n   */\n  indicator?: 'line' | 'dot' | 'dashed';\n  /**\n   * Key used to resolve the tooltip label from the config.\n   */\n  labelKey?: string;\n  /**\n   * Custom formatter for the tooltip label.\n   */\n  labelFormatter?: (d: number | Date) => string;\n  /**\n   * Payload entry provided by the chart library.\n   */\n  payload?: Record<string, unknown>;\n  /**\n   * Chart config used to resolve series metadata.\n   */\n  config?: ChartConfig;\n  /**\n   * X value of the hovered datum.\n   */\n  x?: number | Date;\n  /**\n   * Additional class names applied to the root element.\n   */\n  class?: ClassValue;\n}\n"
    },
    {
      "path": "packages/chart/src/components/chart-legend-content/types.ts",
      "type": "registry:lib",
      "content": "import type { HTMLAttributes } from 'vue';\nimport type { ClassValue } from '@soybeanjs/headless/types';\n\n/**\n * Properties for the SChartLegendContent component.\n */\nexport interface ChartLegendContentProps extends /** @vue-ignore */ HTMLAttributes {\n  /**\n   * Whether to hide the series icon.\n   */\n  hideIcon?: boolean;\n  /**\n   * Key of the series name within each config entry.\n   */\n  nameKey?: string;\n  /**\n   * Legend placement relative to the chart area.\n   * @default 'bottom'\n   */\n  verticalAlign?: 'bottom' | 'top';\n  /**\n   * Additional class names applied to the root element.\n   */\n  class?: ClassValue;\n}\n"
    },
    {
      "path": "packages/chart/src/components/chart-style/types.ts",
      "type": "registry:lib",
      "content": "/**\n * Properties for the SChartStyle component.\n *\n * Internal helper rendered by `SChartContainer`; it injects the scoped\n * `--color-{key}` CSS variables for a single chart via a `<style>` element.\n */\nexport interface ChartStyleProps {\n  /**\n   * Chart id used to scope the generated CSS selectors.\n   */\n  id?: string;\n}\n"
    }
  ],
  "type": "registry:ui"
}
