{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "tooltip-chart",
  "title": "TooltipChart",
  "description": "Chart-owned, non-interactive label and value callout positioned from pointer or focused-mark coordinates inside a visualization wrapper.",
  "type": "registry:ui",
  "meta": {
    "library": "@ds-mo/ui",
    "distribution": "npm",
    "storybook": "https://zainadeel.github.io/compomo/?path=/story/tooltip-chart",
    "source": "src/wc/components/TooltipChart/TooltipChart.tsx",
    "intentStatus": "complete",
    "intent": {
      "audience": "specialized",
      "status": "stable",
      "summary": "Chart-owned, non-interactive label and value callout positioned from pointer or focused-mark coordinates inside a visualization wrapper.",
      "useWhen": [
        "A chart needs immediate visual value feedback for a genuinely hovered or keyboard-focused mark.",
        "The anchor lives inside SVG or chart coordinates rather than on a slotted HTML trigger.",
        "The owning chart can mount the callout for one interaction session and update its coordinates while tracking."
      ],
      "avoidWhen": [
        "A normal control or text trigger needs supplemental explanation; use Tooltip.",
        "The content needs links, actions, focus, or touch-primary disclosure.",
        "An externally synchronized highlight has no genuine local pointer or keyboard interaction.",
        "The callout would be the only accessible source of an essential value."
      ],
      "alternatives": [
        {
          "when": "A slotted control or text trigger needs a conventional tooltip.",
          "component": "component:ds-tooltip",
          "reason": "Tooltip owns trigger semantics, focus behavior, and anchored overlay lifecycle."
        },
        {
          "when": "Donut details should remain continuously visible beside the chart.",
          "component": "component:ds-chart-legend",
          "reason": "A visible legend avoids redundant transient slice callouts."
        }
      ],
      "commonlyComposedWith": [
        "component:ds-chart"
      ],
      "accessibility": [
        "The callout is presentation-only and never replaces the owning chart mark's accessible label or an adjacent data summary.",
        "Keyboard-focused marks receive the same visual feedback as pointer-hovered marks.",
        "The callout never receives focus or contains interactive descendants."
      ],
      "states": [
        "The owning chart mounts the callout when a local interaction starts and unmounts it when that interaction ends.",
        "Coordinate updates track within one mounted interaction session without restarting the show delay.",
        "Placement flips horizontally or vertically when the rendered callout approaches a viewport edge.",
        "The heading and rows use the same medium-density section-heading and control-frame anatomy as menu-style choice content.",
        "When any grouped row supplies a swatch, every row reserves the shared leading icon zone so mixed-swatch labels and trailing values stay aligned.",
        "Placement is chosen as a quadrant rather than computed as coordinates: the component measures its own rendered rect and flips its side and align props, which CSS consumes. It therefore does not use the shared anchored-position utilities, which resolve an anchor element into x and y.",
        "This separation is deliberate. The callout follows the pointer instead of an element, so it has no anchor rect to resolve; adopting the shared geometry would mean rewriting its CSS to consume coordinates for no behavioral gain.",
        "Reduced motion preserves the final visible state without an opacity transition."
      ],
      "responsiveBehavior": [
        "Render inside a positioned chart wrapper and pass coordinates relative to that wrapper.",
        "Keep labels concise enough for the single-line callout; persistent or long explanations belong outside the chart."
      ],
      "frameworkCaveats": {
        "customElements": [
          "The owning chart computes coordinates and conditionally mounts the component; applications should not use it as a standalone overlay."
        ],
        "react": [
          "Keep callout lifecycle and coordinates inside the chart wrapper rather than application-global tooltip state."
        ],
        "angular": [
          "Keep callout lifecycle and coordinates inside the chart wrapper rather than application-global tooltip state."
        ]
      },
      "references": [
        {
          "label": "Storybook example",
          "path": "src/wc/components/TooltipChart/TooltipChart.stories.ts"
        }
      ]
    },
    "api": {
      "props": {
        "delay": {
          "type": "number | string",
          "resolvedType": "number | string",
          "attribute": "delay",
          "default": "TOKEN_DEFAULTS.animationDelayInstant",
          "required": false,
          "mutable": false,
          "description": "Show delay after mount before the callout appears.\nDefault: `--effect-animation-delay-instant` (0ms). Accepts a number (ms)\nor a TokoMo time token / `var(--effect-animation-delay-*)`. Charts need\nimmediate feedback while scrubbing; prefer the default. Mount once per\nhover session so any non-zero delay runs once, then track `x`/`y` instantly."
        },
        "heading": {
          "type": "string",
          "resolvedType": "string | undefined",
          "attribute": "heading",
          "required": false,
          "mutable": false,
          "description": "Optional heading for grouped chart focus."
        },
        "items": {
          "type": "TooltipChartItem[]",
          "resolvedType": "TooltipChartItem[] | undefined",
          "required": false,
          "mutable": false,
          "description": "Multi-row content. When supplied, this replaces the legacy label/value row."
        },
        "label": {
          "type": "string",
          "resolvedType": "string",
          "attribute": "label",
          "default": "''",
          "required": false,
          "mutable": false
        },
        "value": {
          "type": "string | number",
          "resolvedType": "number | string",
          "attribute": "value",
          "default": "''",
          "required": false,
          "mutable": false
        },
        "x": {
          "type": "number",
          "resolvedType": "number",
          "attribute": "x",
          "default": "0",
          "required": false,
          "mutable": false,
          "description": "Anchor point in px, relative to the nearest `position: relative` ancestor — e.g. the cursor."
        },
        "y": {
          "type": "number",
          "resolvedType": "number",
          "attribute": "y",
          "default": "0",
          "required": false,
          "mutable": false
        }
      },
      "events": [],
      "methods": [],
      "slots": []
    },
    "props": {
      "delay": {
        "type": "number | string",
        "resolvedType": "number | string",
        "attribute": "delay",
        "default": "TOKEN_DEFAULTS.animationDelayInstant",
        "required": false,
        "mutable": false,
        "description": "Show delay after mount before the callout appears.\nDefault: `--effect-animation-delay-instant` (0ms). Accepts a number (ms)\nor a TokoMo time token / `var(--effect-animation-delay-*)`. Charts need\nimmediate feedback while scrubbing; prefer the default. Mount once per\nhover session so any non-zero delay runs once, then track `x`/`y` instantly."
      },
      "heading": {
        "type": "string",
        "resolvedType": "string | undefined",
        "attribute": "heading",
        "required": false,
        "mutable": false,
        "description": "Optional heading for grouped chart focus."
      },
      "items": {
        "type": "TooltipChartItem[]",
        "resolvedType": "TooltipChartItem[] | undefined",
        "required": false,
        "mutable": false,
        "description": "Multi-row content. When supplied, this replaces the legacy label/value row."
      },
      "label": {
        "type": "string",
        "resolvedType": "string",
        "attribute": "label",
        "default": "''",
        "required": false,
        "mutable": false
      },
      "value": {
        "type": "string | number",
        "resolvedType": "number | string",
        "attribute": "value",
        "default": "''",
        "required": false,
        "mutable": false
      },
      "x": {
        "type": "number",
        "resolvedType": "number",
        "attribute": "x",
        "default": "0",
        "required": false,
        "mutable": false,
        "description": "Anchor point in px, relative to the nearest `position: relative` ancestor — e.g. the cursor."
      },
      "y": {
        "type": "number",
        "resolvedType": "number",
        "attribute": "y",
        "default": "0",
        "required": false,
        "mutable": false
      }
    },
    "events": [],
    "methods": [],
    "slots": [],
    "exports": {
      "customElement": "ds-tooltip-chart",
      "react": "DsTooltipChart",
      "vue": "DsTooltipChart",
      "angular": "DsTooltipChart"
    },
    "consumption": {
      "install": "npm install @ds-mo/ui @ds-mo/tokens @ds-mo/icons",
      "cssSetup": "import '@ds-mo/tokens';\nimport '@ds-mo/tokens/reset';\nimport '@ds-mo/tokens/globals';",
      "customElements": {
        "import": "import '@ds-mo/ui/dist/components/ds-tooltip-chart.js';",
        "example": "<ds-tooltip-chart></ds-tooltip-chart>"
      },
      "react": {
        "import": "import { DsTooltipChart } from '@ds-mo/ui/react';",
        "example": "<DsTooltipChart />"
      },
      "vue": {
        "import": "import { DsTooltipChart } from '@ds-mo/ui/vue';",
        "example": "<DsTooltipChart />"
      },
      "angular": {
        "import": "import { DsTooltipChart } from '@ds-mo/ui/angular/ds-tooltip-chart';",
        "example": "<ds-tooltip-chart></ds-tooltip-chart>"
      },
      "complexPropertyNote": "Assign these non-primitive values as JavaScript properties: items.",
      "peerDependencies": {
        "required": [
          "@ds-mo/tokens ^6.5.0",
          "@ds-mo/icons ^7.0.0"
        ],
        "frameworks": "Custom Elements; React 18/19 wrappers; Vue 3 wrappers; Angular 19-22 standalone adapters."
      }
    }
  },
  "dependencies": [
    "@ds-mo/ui",
    "@ds-mo/tokens"
  ],
  "registryDependencies": [
    "text"
  ],
  "files": [
    {
      "path": "src/wc/components/TooltipChart/TooltipChart.css",
      "content": "@import '../../utils/control-density.css';\n@import '../../utils/control-parts.css';\n@import '../../utils/chrome-layout.css';\n@import '../../utils/forced-colors.css';\n\n:host {\n  position: absolute;\n  z-index: var(--dimension-z-index-tooltip);\n  background-color: var(--color-translucent-translucent);\n  backdrop-filter: blur(var(--effect-blur-md));\n  -webkit-backdrop-filter: blur(var(--effect-blur-md));\n  border-radius: var(--dimension-radius-075);\n  box-shadow: var(--effect-elevation-elevated-floating);\n  white-space: nowrap;\n  pointer-events: none;\n  opacity: 0;\n}\n\n:host(.tooltip-chart--visible) {\n  animation: tooltip-chart-fade-in var(--effect-motion-short-2) forwards;\n}\n\n.tooltip-chart__item {\n  width: 100%;\n  border-radius: var(--ds-control-radius, var(--dimension-radius-025));\n}\n\n.tooltip-chart__items {\n  display: contents;\n}\n\n.tooltip-chart__swatch-box {\n  pointer-events: none;\n}\n\n.tooltip-chart__swatch {\n  width: var(--dimension-size-100);\n  height: var(--dimension-size-100);\n  flex: none;\n  border-radius: var(--dimension-radius-half);\n  background: var(--ds-tooltip-chart-swatch);\n}\n\n.tooltip-chart__value {\n  margin-inline-start: auto;\n  text-align: end;\n}\n\n.tooltip-chart__label,\n.tooltip-chart__value {\n  flex: none;\n}\n\n@keyframes tooltip-chart-fade-in {\n  from { opacity: 0; }\n  to   { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  :host(.tooltip-chart--visible) {\n    animation: none;\n    opacity: 1;\n  }\n}\n\n@media (forced-colors: active) {\n  :host {\n    box-shadow: none;\n    outline: var(--dimension-stroke-width-012) solid var(--ds-forced-color-content);\n    outline-offset: calc(-1 * var(--dimension-stroke-width-012));\n  }\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "src/wc/components/TooltipChart/TooltipChart.tsx",
      "content": "import { Component, Prop, State, Element, Watch, h, Host } from '@stencil/core';\nimport {\n  CONTROL_SUPPORTING_TEXT_VARIANT,\n  CONTROL_TEXT_VARIANT,\n  resolveCssTimeMs,\n  TOKEN_DEFAULTS,\n} from '../../utils';\n\nconst CURSOR_OFFSET_PX = 12;\nconst VIEWPORT_PAD_PX = 8;\n\nexport type TooltipChartSide = 'left' | 'right';\nexport type TooltipChartAlign = 'top' | 'bottom';\n\nexport interface TooltipChartItem {\n  label: string;\n  value: string | number;\n  color?: string;\n}\n\n/**\n * Positioned value/label callout for chart hover interactions (bar, line point,\n * polar arc, …). A composition may suppress it when a visible legend already\n * surfaces the same value.\n * Unlike `ds-tooltip`, this doesn't bind to a slotted anchor element —\n * charts hover-highlight data that lives inside an SVG, so the chart itself computes\n * the anchor point (e.g. the cursor position while hovering) and passes it in as `x`/`y`.\n *\n * Renders `position: absolute` — place inside a `position: relative` chart wrapper,\n * with `x`/`y` as pixel coordinates within that wrapper. Defaults to sitting below-right\n * of the anchor (matching cursor-following tooltips), flipping to whichever side/edge\n * keeps it on-screen.\n *\n * Mount (or remount) when a hover session starts so `delay` applies once per hover;\n * keep the instance mounted while the cursor moves so tracking stays instant.\n */\n@Component({\n  tag: 'ds-tooltip-chart',\n  styleUrl: 'TooltipChart.css',\n  scoped: true,\n})\nexport class TooltipChart {\n  @Element() el!: HTMLElement;\n\n  @Prop() value: string | number = '';\n  @Prop() label: string = '';\n  /** Optional heading for grouped chart focus. */\n  @Prop() heading?: string;\n  /** Multi-row content. When supplied, this replaces the legacy label/value row. */\n  @Prop() items?: TooltipChartItem[];\n  /** Anchor point in px, relative to the nearest `position: relative` ancestor — e.g. the cursor. */\n  @Prop() x: number = 0;\n  @Prop() y: number = 0;\n\n  /**\n   * Show delay after mount before the callout appears.\n   * Default: `--effect-animation-delay-instant` (0ms). Accepts a number (ms)\n   * or a TokoMo time token / `var(--effect-animation-delay-*)`. Charts need\n   * immediate feedback while scrubbing; prefer the default. Mount once per\n   * hover session so any non-zero delay runs once, then track `x`/`y` instantly.\n   */\n  @Prop() delay: number | string = TOKEN_DEFAULTS.animationDelayInstant;\n\n  @State() private side: TooltipChartSide = 'right';\n  @State() private align: TooltipChartAlign = 'bottom';\n  @State() private visible: boolean = false;\n\n  private delayTimer: ReturnType<typeof setTimeout> | null = null;\n\n  componentDidLoad() {\n    this.scheduleShow();\n    this.calculatePlacement();\n  }\n\n  disconnectedCallback() {\n    this.clearDelayTimer();\n  }\n\n  @Watch('x')\n  @Watch('y')\n  onAnchorChange() {\n    requestAnimationFrame(() => this.calculatePlacement());\n  }\n\n  @Watch('delay')\n  onDelayChange() {\n    if (!this.visible) this.scheduleShow();\n  }\n\n  private get showDelayMs(): number {\n    return resolveCssTimeMs(this.delay, TOKEN_DEFAULTS.animationDelayInstant);\n  }\n\n  private clearDelayTimer() {\n    if (this.delayTimer) {\n      clearTimeout(this.delayTimer);\n      this.delayTimer = null;\n    }\n  }\n\n  private scheduleShow() {\n    this.clearDelayTimer();\n    this.visible = false;\n    const ms = this.showDelayMs;\n    if (ms <= 0) {\n      this.visible = true;\n      return;\n    }\n    this.delayTimer = setTimeout(() => {\n      this.delayTimer = null;\n      this.visible = true;\n      requestAnimationFrame(() => this.calculatePlacement());\n    }, ms);\n  }\n\n  private calculatePlacement() {\n    const rect = this.el.getBoundingClientRect();\n    if (!rect.width || !rect.height) return;\n\n    const nextSide: TooltipChartSide = rect.right > window.innerWidth - VIEWPORT_PAD_PX ? 'left' : 'right';\n    const nextAlign: TooltipChartAlign = rect.bottom > window.innerHeight - VIEWPORT_PAD_PX ? 'top' : 'bottom';\n\n    if (nextSide !== this.side) this.side = nextSide;\n    if (nextAlign !== this.align) this.align = nextAlign;\n  }\n\n  render() {\n    const translateX = this.side === 'right' ? `${CURSOR_OFFSET_PX}px` : `calc(-100% - ${CURSOR_OFFSET_PX}px)`;\n    const translateY = this.align === 'bottom' ? `${CURSOR_OFFSET_PX}px` : `calc(-100% - ${CURSOR_OFFSET_PX}px)`;\n    const rows = this.items ?? [{ label: this.label, value: this.value }];\n    const usesSwatches = rows.some(item => Boolean(item.color));\n\n    return (\n      <Host\n        class={{\n          'tooltip-chart': true,\n          'tooltip-chart--visible': this.visible,\n          'ds-chrome-column': true,\n          'ds-chrome-space--sm': true,\n        }}\n        style={{ left: `${this.x}px`, top: `${this.y}px`, transform: `translate(${translateX}, ${translateY})` }}\n      >\n        {this.heading && (\n          <ds-text\n            class=\"tooltip-chart__heading ds-control-section-heading ds-control--md\"\n            as=\"span\"\n            variant={CONTROL_SUPPORTING_TEXT_VARIANT.md}\n            emphasis\n            color=\"var(--color-foreground-primary)\"\n          >\n            {this.heading}\n          </ds-text>\n        )}\n        <div class=\"tooltip-chart__items\">\n          {rows.map((item, index) => (\n            <div class=\"tooltip-chart__item ds-control-frame ds-control--md\" key={`${item.label}-${index}`}>\n              {usesSwatches && (\n                <span class=\"tooltip-chart__swatch-box ds-control-icon-box\" aria-hidden=\"true\">\n                  {item.color && (\n                    <span\n                      class=\"tooltip-chart__swatch\"\n                      style={{ '--ds-tooltip-chart-swatch': item.color }}\n                    />\n                  )}\n                </span>\n              )}\n              <ds-text\n                class=\"tooltip-chart__label ds-control-label-box\"\n                as=\"span\"\n                variant={CONTROL_TEXT_VARIANT.md}\n                color=\"var(--color-foreground-secondary)\"\n              >\n                {item.label}\n              </ds-text>\n              <ds-text\n                class=\"tooltip-chart__value ds-control-label-box\"\n                as=\"span\"\n                variant={CONTROL_TEXT_VARIANT.md}\n                emphasis\n                color=\"var(--color-foreground-primary)\"\n              >\n                {item.value}\n              </ds-text>\n            </div>\n          ))}\n        </div>\n      </Host>\n    );\n  }\n}\n",
      "type": "registry:ui"
    }
  ]
}
