{
  "name": "chart",
  "type": "registry:ui",
  "dependencies": [
    "@unovis/vue",
    "@vueuse/core",
    "reka-ui"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "ui/chart/ChartContainer.vue",
      "type": "registry:ui",
      "content": "<script lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport type { ChartConfig } from \".\"\nimport { useId } from \"reka-ui\"\nimport { computed, toRefs } from \"vue\"\nimport { cn } from \"@/lib/utils\"\nimport { provideChartContext } from \".\"\nimport ChartStyle from \"./ChartStyle.vue\"\n</script>\n\n<script setup lang=\"ts\">\nimport { computed, toRefs } from \"vue\"\nconst props = defineProps<{\n  id?: HTMLAttributes[\"id\"]\n  class?: HTMLAttributes[\"class\"]\n  config: ChartConfig\n  cursor?: boolean\n}>()\n\ndefineSlots<{\n  default: {\n    id: string\n    config: ChartConfig\n  }\n}>()\n\nconst { config } = toRefs(props)\nconst uniqueId = useId()\nconst chartId = computed(() => `chart-${props.id || uniqueId.replace(/:/g, \"\")}`)\n\nprovideChartContext({\n  id: uniqueId,\n  config,\n})\n</script>\n\n<template>\n  <div\n    data-slot=\"chart\"\n    :data-chart=\"chartId\"\n    :class=\"cn(\n      `cn-chart [&_.tick_text]:!fill-muted-foreground [&_.tick_line]:!stroke-border/50 [&_.recharts-curve.recharts-tooltip-cursor]:stroke-border [&_.recharts-polar-grid_[stroke='#ccc']]:stroke-border [&_.recharts-radial-bar-background-sector]:fill-muted [&_.recharts-rectangle.recharts-tooltip-cursor]:fill-muted [&_.recharts-reference-line_[stroke='#ccc']]:stroke-border flex flex-col aspect-video justify-center text-xs [&_.recharts-dot[stroke='#fff']]:stroke-transparent [&_.recharts-layer]:outline-hidden [&_.recharts-sector]:outline-hidden [&_.recharts-sector[stroke='#fff']]:stroke-transparent [&_.recharts-surface]:outline-hidden [&_[data-vis-xy-container]]:h-full [&_[data-vis-single-container]]:h-full h-full [&_[data-vis-xy-container]]:w-full [&_[data-vis-single-container]]:w-full w-full `,\n      props.class,\n    )\"\n    :style=\"{\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': cursor ? '1px' : '0px',\n      '--vis-font-family': 'var(--font-sans)',\n    }\"\n  >\n    <slot :id=\"uniqueId\" :config=\"config\" />\n    <ChartStyle :id=\"chartId\" />\n  </div>\n</template>\n"
    },
    {
      "path": "ui/chart/ChartLegendContent.vue",
      "type": "registry:ui",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { computed, onMounted, ref } from \"vue\"\nimport { cn } from \"@/lib/utils\"\nimport { useChart } from \".\"\n\nconst props = withDefaults(defineProps<{\n  hideIcon?: boolean\n  nameKey?: string\n  verticalAlign?: \"bottom\" | \"top\"\n  // payload?: any[]\n  class?: HTMLAttributes[\"class\"]\n}>(), {\n  verticalAlign: \"bottom\",\n})\n\nconst { id, config } = useChart()\n\nconst payload = computed(() => Object.entries(config.value).map(([key, value]) => {\n  return {\n    key: props.nameKey || key,\n    itemConfig: config.value[key],\n  }\n}))\n\nconst containerSelector = ref(\"\")\nonMounted(() => {\n  containerSelector.value = `[data-chart=\"chart-${id}\"]>[data-vis-xy-container]`\n})\n</script>\n\n<template>\n  <div\n    v-if=\"containerSelector\"\n    :class=\"cn(\n      'flex items-center justify-center gap-4',\n      verticalAlign === 'top' ? 'pb-3' : 'pt-3',\n      props.class,\n    )\"\n  >\n    <div\n      v-for=\"{ key, itemConfig } in payload\"\n      :key=\"key\"\n      :class=\"cn(\n        '[&>svg]:text-muted-foreground flex items-center gap-1.5 [&>svg]:h-3 [&>svg]:w-3',\n      )\"\n    >\n      <component :is=\"itemConfig?.icon\" v-if=\"itemConfig?.icon\" />\n      <div\n        v-else\n        class=\"h-2 w-2 shrink-0 rounded-xs\"\n        :style=\"{\n          backgroundColor: itemConfig?.color,\n        }\"\n      />\n\n      {{ itemConfig?.label }}\n    </div>\n  </div>\n</template>\n"
    },
    {
      "path": "ui/chart/ChartStyle.vue",
      "type": "registry:ui",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport { Primitive } from \"reka-ui\"\nimport { computed } from \"vue\"\nimport { THEMES, useChart } from \".\"\n\ndefineProps<{\n  id?: HTMLAttributes[\"id\"]\n}>()\n\nconst { config } = useChart()\n\nconst colorConfig = computed(() => {\n  return Object.entries(config.value).filter(\n    ([, config]) => config.theme || config.color,\n  )\n})\n</script>\n\n<template>\n  <Primitive\n    v-if=\"colorConfig.length\"\n    as=\"style\"\n  >\n    {{ Object.entries(THEMES)\n      .map(\n        ([theme, prefix]) => `\n${prefix} [data-chart=${id}] {\n${colorConfig\n  .map(([key, itemConfig]) => {\n    const color\n      = itemConfig.theme?.[theme as keyof typeof itemConfig.theme]\n      || itemConfig.color\n    return color ? `  --color-${key}: ${color};` : null\n  })\n        .join(\"\\n\")}\n}\n`,\n      )\n      .join(\"\\n\") }}\n  </Primitive>\n</template>\n"
    },
    {
      "path": "ui/chart/ChartTooltipContent.vue",
      "type": "registry:ui",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from \"vue\"\nimport type { ChartConfig } from \".\"\nimport { computed } from \"vue\"\nimport { cn } from \"@/lib/utils\"\n\nconst props = withDefaults(defineProps<{\n  hideLabel?: boolean\n  hideIndicator?: boolean\n  indicator?: \"line\" | \"dot\" | \"dashed\"\n  nameKey?: string\n  labelKey?: string\n  labelFormatter?: (d: number | Date) => string\n  payload?: Record<string, any>\n  config?: ChartConfig\n  class?: HTMLAttributes[\"class\"]\n  color?: string\n  x?: number | Date\n}>(), {\n  payload: () => ({}),\n  config: () => ({}),\n  indicator: \"dot\",\n})\n\n// TODO: currently we use `createElement` and `render` to render the\n// const chartContext = useChart(null)\n\nconst payload = computed(() => {\n  return Object.entries(props.payload).map(([key, value]) => {\n    // const key = `${props.nameKey || item.name || item.dataKey || \"value\"}`\n    const itemConfig = props.config[key]\n    const indicatorColor = props.config[key]?.color ?? props.payload.fill\n\n    return { key, value, itemConfig, indicatorColor }\n  }).filter(i => i.itemConfig)\n})\n\nconst nestLabel = computed(() => Object.keys(props.payload).length === 1 && props.indicator !== \"dot\")\nconst tooltipLabel = computed(() => {\n  if (props.hideLabel)\n    return null\n  if (props.labelFormatter && props.x !== undefined) {\n    return props.labelFormatter(props.x)\n  }\n  return props.labelKey ? props.config[props.labelKey]?.label || props.payload[props.labelKey] : props.x\n})\n</script>\n\n<template>\n  <div\n    :class=\"cn(\n      'cn-chart-tooltip grid min-w-32 items-start',\n      props.class,\n    )\"\n  >\n    <slot>\n      <div v-if=\"!nestLabel && tooltipLabel\" class=\"font-medium\">\n        {{ tooltipLabel }}\n      </div>\n      <div class=\"grid gap-1.5\">\n        <div\n          v-for=\"{ value, itemConfig, indicatorColor, key } in payload\"\n          :key=\"key\"\n          :class=\"\n            cn('[&>svg]:text-muted-foreground flex w-full flex-wrap items-stretch gap-2 [&>svg]:h-2.5 [&>svg]:w-2.5',\n               indicator === 'dot' && 'items-center')\"\n        >\n          <component :is=\"itemConfig.icon\" v-if=\"itemConfig?.icon\" />\n          <template v-else-if=\"!hideIndicator\">\n            <div\n              :class=\"cn(\n                'shrink-0 rounded-xs border-(--color-border) bg-(--color-bg)',\n                {\n                  'h-2.5 w-2.5': indicator === 'dot',\n                  'w-1': indicator === 'line',\n                  'w-0 border-[1.5px] border-dashed bg-transparent':\n                    indicator === 'dashed',\n                  'my-0.5': nestLabel && indicator === 'dashed',\n                },\n              )\"\n              :style=\"{\n                '--color-bg': indicatorColor,\n                '--color-border': indicatorColor,\n              }\"\n            />\n          </template>\n\n          <div :class=\"cn('flex flex-1 justify-between leading-none', nestLabel ? 'items-end' : 'items-center')\">\n            <div class=\"grid gap-1.5\">\n              <div v-if=\"nestLabel\" class=\"font-medium\">\n                {{ tooltipLabel }}\n              </div>\n              <span class=\"text-muted-foreground\">\n                {{ itemConfig?.label || value }}\n              </span>\n            </div>\n            <span v-if=\"value\" class=\"text-foreground font-mono font-medium tabular-nums\">\n              {{ value.toLocaleString() }}\n            </span>\n          </div>\n        </div>\n      </div>\n    </slot>\n  </div>\n</template>\n"
    },
    {
      "path": "ui/chart/index.ts",
      "type": "registry:ui",
      "content": "import type { Component, Ref } from \"vue\"\nimport { createContext } from \"reka-ui\"\n\nexport { default as ChartContainer } from \"./ChartContainer.vue\"\nexport { default as ChartLegendContent } from \"./ChartLegendContent.vue\"\nexport { default as ChartTooltipContent } from \"./ChartTooltipContent.vue\"\nexport { componentToString } from \"./utils\"\n\n// Format: { THEME_NAME: CSS_SELECTOR }\nexport const THEMES = { light: \"\", dark: \".dark\" } as const\n\nexport type ChartConfig = {\n  [k in string]: {\n    label?: string | Component\n    icon?: string | Component\n  } & (\n    | { color?: string, theme?: never }\n    | { color?: never, theme: Record<keyof typeof THEMES, string> }\n  )\n}\n\ninterface ChartContextProps {\n  id: string\n  config: Ref<ChartConfig>\n}\n\nexport const [useChart, provideChartContext] = createContext<ChartContextProps>(\"Chart\")\n\nexport { VisCrosshair as ChartCrosshair, VisTooltip as ChartTooltip } from \"@unovis/vue\"\n"
    },
    {
      "path": "ui/chart/utils.ts",
      "type": "registry:ui",
      "content": "import type { ChartConfig } from \".\"\nimport { isClient } from \"@vueuse/core\"\nimport { useId } from \"reka-ui\"\nimport { h, render } from \"vue\"\n\n// Simple cache using a Map to store serialized object keys\nconst cache = new Map<string, string>()\n\n// Convert object to a consistent string key\nfunction serializeKey(key: Record<string, any>): string {\n  return JSON.stringify(key, Object.keys(key).sort())\n}\n\ninterface Constructor<P = any> {\n  __isFragment?: never\n  __isTeleport?: never\n  __isSuspense?: never\n  new (...args: any[]): {\n    $props: P\n  }\n}\n\nexport function componentToString<P>(config: ChartConfig, component: Constructor<P>, props?: P) {\n  if (!isClient)\n    return\n\n  // This function will be called once during mount lifecycle\n  const id = useId()\n\n  // https://unovis.dev/docs/auxiliary/Crosshair#component-props\n  return (_data: any, x: number | Date) => {\n    const data = \"data\" in _data ? _data.data : _data\n    const serializedKey = `${id}-${serializeKey(data)}`\n    const cachedContent = cache.get(serializedKey)\n    if (cachedContent)\n      return cachedContent\n\n    const vnode = h<unknown>(component, { ...props, payload: data, config, x })\n    const div = document.createElement(\"div\")\n    render(vnode, div)\n    cache.set(serializedKey, div.innerHTML)\n    return div.innerHTML\n  }\n}\n"
    }
  ]
}
