{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "liveline",
  "type": "registry:ui",
  "title": "Liveline",
  "description": "Real-time React canvas chart with line, multi-series, candlestick, and market-data modes.",
  "devDependencies": [
    "sass"
  ],
  "registryDependencies": [
    "@tile-ui/liveline-core",
    "@tile-ui/styles"
  ],
  "files": [
    {
      "path": "packages/react/src/components/liveline/liveline.tsx",
      "type": "registry:ui",
      "target": "components/ui/liveline/liveline.tsx",
      "content": "import React, { useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport {\n\tSERIES_COLORS,\n\tcreateLivelineEngine,\n\tobserveLivelineAppearance,\n\tresolveLivelinePalette,\n\ttype LivelineAppearance,\n\ttype LivelineEngine,\n\ttype LivelineEngineConfig,\n\ttype LivelineOptions,\n\ttype Momentum,\n} from '../lib/liveline';\nimport styles from './liveline.module.scss';\n\nconst defaultFormatValue = (value: number) => value.toFixed(2);\nconst defaultFormatTime = (time: number) => {\n\tconst date = new Date(time * 1000);\n\treturn [date.getHours(), date.getMinutes(), date.getSeconds()].map((part) => part.toString().padStart(2, '0')).join(':');\n};\n\nexport interface LivelineProps extends LivelineOptions, Omit<React.HTMLAttributes<HTMLDivElement>, keyof LivelineOptions | 'children'> {\n\t/** Styles the engine's chart surface; style sizes the complete widget, including controls. */\n\tsurfaceStyle?: React.CSSProperties;\n\tsurfaceClassName?: string;\n}\n\nfunction setIndicator(bar: HTMLDivElement | null, button: HTMLButtonElement | undefined, setState: React.Dispatch<React.SetStateAction<{ left: number; width: number } | null>>) {\n\tif (!bar || !button) return;\n\tconst barRect = bar.getBoundingClientRect();\n\tconst buttonRect = button.getBoundingClientRect();\n\tsetState({ left: buttonRect.left - barRect.left, width: buttonRect.width });\n}\n\nconst Liveline = React.forwardRef<HTMLDivElement, LivelineProps>(\n\t(\n\t\t{\n\t\t\tdata,\n\t\t\tvalue,\n\t\t\tseries: seriesProp,\n\t\t\ttheme,\n\t\t\tcolor = '#3b82f6',\n\t\t\twindow: windowSecs = 30,\n\t\t\tgrid = true,\n\t\t\tbadge = true,\n\t\t\tmomentum = true,\n\t\t\tfill = true,\n\t\t\tscrub = true,\n\t\t\tloading = false,\n\t\t\tpaused = false,\n\t\t\temptyText,\n\t\t\texaggerate = false,\n\t\t\tdegen,\n\t\t\tbadgeTail = true,\n\t\t\tbadgeVariant = 'default',\n\t\t\tshowValue = false,\n\t\t\tvalueMomentumColor = false,\n\t\t\twindows,\n\t\t\tonWindowChange,\n\t\t\twindowStyle = 'default',\n\t\t\ttooltipY = 14,\n\t\t\ttooltipOutline = true,\n\t\t\torderbook,\n\t\t\treferenceLine,\n\t\t\tformatValue = defaultFormatValue,\n\t\t\tformatTime = defaultFormatTime,\n\t\t\tlerpSpeed = 0.08,\n\t\t\tpadding: paddingOverride,\n\t\t\tonHover,\n\t\t\tcursor = 'crosshair',\n\t\t\tpulse = true,\n\t\t\tmode = 'line',\n\t\t\tcandles,\n\t\t\tcandleWidth,\n\t\t\tliveCandle,\n\t\t\tlineMode,\n\t\t\tlineData,\n\t\t\tlineValue,\n\t\t\tonModeChange,\n\t\t\tonSeriesToggle,\n\t\t\tseriesToggleCompact = false,\n\t\t\tlineWidth,\n\t\t\tclassName = '',\n\t\t\tstyle,\n\t\t\tsurfaceStyle,\n\t\t\tsurfaceClassName = '',\n\t\t\t'aria-label': ariaLabel,\n\t\t\t'aria-describedby': ariaDescribedBy,\n\t\t\t...rootProps\n\t\t},\n\t\tforwardedRef,\n\t) => {\n\t\tconst rootRef = useRef<HTMLDivElement>(null);\n\t\tconst surfaceRef = useRef<HTMLDivElement>(null);\n\t\tconst canvasRef = useRef<HTMLCanvasElement>(null);\n\t\tconst valueRef = useRef<HTMLSpanElement>(null);\n\t\tconst engineRef = useRef<LivelineEngine | null>(null);\n\t\tconst windowBarRef = useRef<HTMLDivElement>(null);\n\t\tconst windowButtonRefs = useRef(new Map<number, HTMLButtonElement>());\n\t\tconst modeBarRef = useRef<HTMLDivElement>(null);\n\t\tconst modeButtonRefs = useRef(new Map<string, HTMLButtonElement>());\n\t\tconst [windowIndicator, setWindowIndicator] = useState<{ left: number; width: number } | null>(null);\n\t\tconst [modeIndicator, setModeIndicator] = useState<{ left: number; width: number } | null>(null);\n\t\tconst [hiddenSeries, setHiddenSeries] = useState<Set<string>>(() => new Set());\n\t\tconst [appearance, setAppearance] = useState<LivelineAppearance>(() => ({ mode: theme ?? 'dark', palette: {}, colors: {} }));\n\t\tconst resolvedAppearance: LivelineAppearance = theme && appearance.mode !== theme ? { mode: theme, palette: {}, colors: appearance.colors } : appearance;\n\t\tconst [activeWindowSecs, setActiveWindowSecs] = useState(windows?.[0]?.secs ?? windowSecs);\n\t\tconst lastSeriesRef = useRef(seriesProp);\n\n\t\tuseLayoutEffect(() => {\n\t\t\tif (seriesProp?.length) lastSeriesRef.current = seriesProp;\n\t\t}, [seriesProp]);\n\n\t\tconst isMultiSeries = Boolean(seriesProp?.length);\n\t\tconst shownSeries = (seriesProp?.length ? seriesProp : lastSeriesRef.current) ?? [];\n\t\tconst showSeriesControls = shownSeries.length > 1;\n\t\tconst effectiveWindowSecs = windows?.length ? activeWindowSecs : windowSecs;\n\t\tconst activeMode = mode === 'candle' && lineMode ? 'line' : mode;\n\t\tconst defaultRight = badge ? 80 : grid ? 54 : 12;\n\t\tconst padding = {\n\t\t\ttop: paddingOverride?.top ?? 12,\n\t\t\tright: paddingOverride?.right ?? defaultRight,\n\t\t\tbottom: paddingOverride?.bottom ?? 28,\n\t\t\tleft: paddingOverride?.left ?? 12,\n\t\t};\n\t\tconst palette = { ...resolveLivelinePalette(color, resolvedAppearance), ...(lineWidth == null ? {} : { lineWidth }) };\n\t\tconst multiSeries = seriesProp?.map((series, index) => ({\n\t\t\tid: series.id,\n\t\t\tdata: series.data,\n\t\t\tvalue: series.value,\n\t\t\tpalette: resolveLivelinePalette(series.color || SERIES_COLORS[index % SERIES_COLORS.length], resolvedAppearance),\n\t\t\tlabel: series.label,\n\t\t}));\n\t\tconst config: LivelineEngineConfig = {\n\t\t\tdata,\n\t\t\tvalue,\n\t\t\tpalette,\n\t\t\twindowSecs: effectiveWindowSecs,\n\t\t\tlerpSpeed,\n\t\t\tshowGrid: grid,\n\t\t\tshowBadge: isMultiSeries ? false : badge,\n\t\t\tshowMomentum: isMultiSeries ? false : momentum !== false,\n\t\t\tmomentumOverride: typeof momentum === 'string' ? (momentum as Momentum) : undefined,\n\t\t\tshowFill: isMultiSeries ? false : fill,\n\t\t\treferenceLine,\n\t\t\tformatValue,\n\t\t\tformatTime,\n\t\t\tpadding,\n\t\t\tonHover,\n\t\t\tshowPulse: pulse,\n\t\t\tscrub,\n\t\t\texaggerate,\n\t\t\tdegenOptions: isMultiSeries || !degen ? undefined : typeof degen === 'object' ? degen : {},\n\t\t\tbadgeTail,\n\t\t\tbadgeVariant,\n\t\t\ttooltipY,\n\t\t\ttooltipOutline,\n\t\t\tvalueMomentumColor,\n\t\t\tvalueElement: showValue ? valueRef.current : undefined,\n\t\t\torderbookData: orderbook,\n\t\t\tloading,\n\t\t\tpaused,\n\t\t\temptyText,\n\t\t\tmode,\n\t\t\tcandles,\n\t\t\tcandleWidth,\n\t\t\tliveCandle,\n\t\t\tlineMode,\n\t\t\tlineData,\n\t\t\tlineValue,\n\t\t\tmultiSeries,\n\t\t\tisMultiSeries,\n\t\t\thiddenSeriesIds: hiddenSeries,\n\t\t};\n\t\tconst initialConfigRef = useRef(config);\n\t\tconst initialShowValueRef = useRef(showValue);\n\n\t\tuseLayoutEffect(() => {\n\t\t\tsetIndicator(windowBarRef.current, windowButtonRefs.current.get(activeWindowSecs), setWindowIndicator);\n\t\t}, [activeWindowSecs, windows]);\n\n\t\tuseEffect(() => {\n\t\t\tsetActiveWindowSecs((current) => {\n\t\t\t\tif (!windows?.length) return windowSecs;\n\t\t\t\treturn windows.some((option) => option.secs === current) ? current : windows[0].secs;\n\t\t\t});\n\t\t}, [windows, windowSecs]);\n\n\t\tuseEffect(() => {\n\t\t\tif (!seriesProp?.length) {\n\t\t\t\tsetHiddenSeries((current) => (current.size ? new Set() : current));\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst ids = new Set(seriesProp.map((series) => series.id));\n\t\t\tsetHiddenSeries((current) => {\n\t\t\t\tconst next = new Set([...current].filter((id) => ids.has(id)));\n\t\t\t\tif (next.size === ids.size) next.delete(seriesProp[0].id);\n\t\t\t\treturn next.size === current.size && [...next].every((id) => current.has(id)) ? current : next;\n\t\t\t});\n\t\t}, [seriesProp]);\n\n\t\tuseLayoutEffect(() => {\n\t\t\tif (onModeChange) setIndicator(modeBarRef.current, modeButtonRefs.current.get(activeMode), setModeIndicator);\n\t\t}, [activeMode, onModeChange]);\n\n\t\tuseLayoutEffect(() => {\n\t\t\tconst root = rootRef.current;\n\t\t\tif (!root) return;\n\t\t\treturn observeLivelineAppearance(\n\t\t\t\troot,\n\t\t\t\t() => ({ theme, colors: [color, ...(seriesProp?.map((series, index) => series.color || SERIES_COLORS[index % SERIES_COLORS.length]) ?? [])] }),\n\t\t\t\tsetAppearance,\n\t\t\t);\n\t\t}, [theme, color, seriesProp]);\n\n\t\tuseEffect(() => {\n\t\t\tconst container = surfaceRef.current;\n\t\t\tconst canvas = canvasRef.current;\n\t\t\tif (!container || !canvas) return;\n\t\t\tconst engine = createLivelineEngine({ container, canvas }, { ...initialConfigRef.current, valueElement: initialShowValueRef.current ? valueRef.current : undefined });\n\t\t\tengineRef.current = engine;\n\t\t\treturn () => {\n\t\t\t\tengine.destroy();\n\t\t\t\tif (engineRef.current === engine) engineRef.current = null;\n\t\t\t};\n\t\t}, []);\n\n\t\tuseEffect(() => {\n\t\t\tengineRef.current?.update({ ...config, valueElement: showValue ? valueRef.current : undefined });\n\t\t});\n\n\t\tfunction setRootRef(element: HTMLDivElement | null) {\n\t\t\trootRef.current = element;\n\t\t\tif (typeof forwardedRef === 'function') forwardedRef(element);\n\t\t\telse if (forwardedRef) forwardedRef.current = element;\n\t\t}\n\n\t\tfunction toggleSeries(id: string) {\n\t\t\tif (!isMultiSeries) return;\n\t\t\tconst next = new Set(hiddenSeries);\n\t\t\tconst visible = next.has(id);\n\t\t\tif (visible) next.delete(id);\n\t\t\telse {\n\t\t\t\tif ((seriesProp?.length ?? 0) - next.size <= 1) return;\n\t\t\t\tnext.add(id);\n\t\t\t}\n\t\t\tsetHiddenSeries(next);\n\t\t\tonSeriesToggle?.(id, visible);\n\t\t}\n\n\t\tconst summary = ariaLabel ?? `Live chart, current value ${formatValue(value)}`;\n\t\tconst hasControls = Boolean(windows?.length || onModeChange || showSeriesControls);\n\n\t\treturn (\n\t\t\t<div\n\t\t\t\t{...rootProps}\n\t\t\t\tref={setRootRef}\n\t\t\t\tdata-slot=\"liveline\"\n\t\t\t\tdata-theme={resolvedAppearance.inherited ? undefined : resolvedAppearance.mode}\n\t\t\t\tdata-color-mode={resolvedAppearance.mode}\n\t\t\t\tclassName={`${styles.root} ${className}`}\n\t\t\t\tstyle={style}>\n\t\t\t\t{showValue && <span ref={valueRef} data-slot=\"liveline-value\" className={styles.value} style={{ paddingLeft: padding.left }} aria-hidden=\"true\" />}\n\t\t\t\t{hasControls && (\n\t\t\t\t\t<div data-slot=\"liveline-controls\" className={styles.controls} style={{ paddingLeft: padding.left }}>\n\t\t\t\t\t\t{Boolean(windows?.length) && (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tref={windowBarRef}\n\t\t\t\t\t\t\t\trole=\"group\"\n\t\t\t\t\t\t\t\tdata-slot=\"liveline-window-controls\"\n\t\t\t\t\t\t\t\tdata-style={windowStyle}\n\t\t\t\t\t\t\t\tclassName={styles.controlGroup}\n\t\t\t\t\t\t\t\taria-label=\"Chart time range\">\n\t\t\t\t\t\t\t\t{windowStyle !== 'text' && windowIndicator && (\n\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\tdata-slot=\"liveline-control-indicator\"\n\t\t\t\t\t\t\t\t\t\tclassName={styles.indicator}\n\t\t\t\t\t\t\t\t\t\tstyle={{ left: windowIndicator.left, width: windowIndicator.width }}\n\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{windows?.map((option) => {\n\t\t\t\t\t\t\t\t\tconst active = option.secs === activeWindowSecs;\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\tkey={option.secs}\n\t\t\t\t\t\t\t\t\t\t\tref={(element) => {\n\t\t\t\t\t\t\t\t\t\t\t\tif (element) windowButtonRefs.current.set(option.secs, element);\n\t\t\t\t\t\t\t\t\t\t\t\telse windowButtonRefs.current.delete(option.secs);\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\tdata-slot=\"liveline-window-control\"\n\t\t\t\t\t\t\t\t\t\t\tdata-active={active || undefined}\n\t\t\t\t\t\t\t\t\t\t\tclassName={styles.control}\n\t\t\t\t\t\t\t\t\t\t\taria-label={`Show ${option.label} time range`}\n\t\t\t\t\t\t\t\t\t\t\taria-pressed={active}\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => {\n\t\t\t\t\t\t\t\t\t\t\t\tsetActiveWindowSecs(option.secs);\n\t\t\t\t\t\t\t\t\t\t\t\tonWindowChange?.(option.secs);\n\t\t\t\t\t\t\t\t\t\t\t}}>\n\t\t\t\t\t\t\t\t\t\t\t{option.label}\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{onModeChange && (\n\t\t\t\t\t\t\t<div ref={modeBarRef} role=\"group\" data-slot=\"liveline-mode-controls\" data-style={windowStyle} className={styles.controlGroup} aria-label=\"Chart type\">\n\t\t\t\t\t\t\t\t{windowStyle !== 'text' && modeIndicator && (\n\t\t\t\t\t\t\t\t\t<span data-slot=\"liveline-control-indicator\" className={styles.indicator} style={{ left: modeIndicator.left, width: modeIndicator.width }} />\n\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t{(['line', 'candle'] as const).map((item) => {\n\t\t\t\t\t\t\t\t\tconst active = activeMode === item;\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\tkey={item}\n\t\t\t\t\t\t\t\t\t\t\tref={(element) => {\n\t\t\t\t\t\t\t\t\t\t\t\tif (element) modeButtonRefs.current.set(item, element);\n\t\t\t\t\t\t\t\t\t\t\t\telse modeButtonRefs.current.delete(item);\n\t\t\t\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\tdata-slot=\"liveline-mode-control\"\n\t\t\t\t\t\t\t\t\t\t\tdata-active={active || undefined}\n\t\t\t\t\t\t\t\t\t\t\tclassName={`${styles.control} ${styles.iconControl}`}\n\t\t\t\t\t\t\t\t\t\t\taria-label={`Show ${item} chart`}\n\t\t\t\t\t\t\t\t\t\t\taria-pressed={active}\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => onModeChange(item)}>\n\t\t\t\t\t\t\t\t\t\t\t{item === 'line' ? (\n\t\t\t\t\t\t\t\t\t\t\t\t<svg aria-hidden=\"true\" width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<path\n\t\t\t\t\t\t\t\t\t\t\t\t\t\td=\"M1 8.5C2.5 8.5 3 4 5.5 4S7.5 7 8.5 7C9.5 7 10 3.5 11 3.5\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstroke=\"currentColor\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrokeWidth={active ? 1.5 : 1.2}\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t) : (\n\t\t\t\t\t\t\t\t\t\t\t\t<svg aria-hidden=\"true\" width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" fill=\"none\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<line x1=\"3.5\" y1=\"1\" x2=\"3.5\" y2=\"11\" stroke=\"currentColor\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<rect x=\"2\" y=\"3\" width=\"3\" height=\"5\" rx=\"0.5\" fill=\"currentColor\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<line x1=\"8.5\" y1=\"2\" x2=\"8.5\" y2=\"10\" stroke=\"currentColor\" />\n\t\t\t\t\t\t\t\t\t\t\t\t\t<rect x=\"7\" y=\"4\" width=\"3\" height=\"4\" rx=\"0.5\" fill=\"currentColor\" />\n\t\t\t\t\t\t\t\t\t\t\t\t</svg>\n\t\t\t\t\t\t\t\t\t\t\t)}\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t\t{showSeriesControls && (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\trole=\"group\"\n\t\t\t\t\t\t\t\tdata-slot=\"liveline-series-controls\"\n\t\t\t\t\t\t\t\tdata-style={windowStyle}\n\t\t\t\t\t\t\t\tdata-visible={isMultiSeries}\n\t\t\t\t\t\t\t\taria-hidden={!isMultiSeries}\n\t\t\t\t\t\t\t\tclassName={styles.controlGroup}\n\t\t\t\t\t\t\t\taria-label=\"Chart series\">\n\t\t\t\t\t\t\t\t{shownSeries.map((series, index) => {\n\t\t\t\t\t\t\t\t\tconst visible = !hiddenSeries.has(series.id);\n\t\t\t\t\t\t\t\t\tconst label = series.label ?? series.id;\n\t\t\t\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t\t\t\t<button\n\t\t\t\t\t\t\t\t\t\t\tkey={series.id}\n\t\t\t\t\t\t\t\t\t\t\ttype=\"button\"\n\t\t\t\t\t\t\t\t\t\t\tdata-slot=\"liveline-series-control\"\n\t\t\t\t\t\t\t\t\t\t\tdisabled={!isMultiSeries}\n\t\t\t\t\t\t\t\t\t\t\tdata-active={visible || undefined}\n\t\t\t\t\t\t\t\t\t\t\tclassName={`${styles.control} ${seriesToggleCompact ? styles.compactControl : ''}`}\n\t\t\t\t\t\t\t\t\t\t\taria-label={`${visible ? 'Hide' : 'Show'} ${label} series`}\n\t\t\t\t\t\t\t\t\t\t\taria-pressed={visible}\n\t\t\t\t\t\t\t\t\t\t\tonClick={() => toggleSeries(series.id)}>\n\t\t\t\t\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\t\t\t\t\tdata-slot=\"liveline-series-swatch\"\n\t\t\t\t\t\t\t\t\t\t\t\tclassName={styles.swatch}\n\t\t\t\t\t\t\t\t\t\t\t\tstyle={{ background: series.color || SERIES_COLORS[index % SERIES_COLORS.length] }}\n\t\t\t\t\t\t\t\t\t\t\t/>\n\t\t\t\t\t\t\t\t\t\t\t{!seriesToggleCompact && label}\n\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t})}\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t)}\n\t\t\t\t\t</div>\n\t\t\t\t)}\n\t\t\t\t<div ref={surfaceRef} data-slot=\"liveline-surface\" className={`${styles.surface} ${surfaceClassName}`} style={surfaceStyle}>\n\t\t\t\t\t<canvas\n\t\t\t\t\t\tref={canvasRef}\n\t\t\t\t\t\tdata-slot=\"liveline-canvas\"\n\t\t\t\t\t\tclassName={styles.canvas}\n\t\t\t\t\t\tstyle={{ cursor: scrub ? cursor : 'default' }}\n\t\t\t\t\t\trole=\"img\"\n\t\t\t\t\t\taria-label={summary}\n\t\t\t\t\t\taria-describedby={ariaDescribedBy}>\n\t\t\t\t\t\t{summary}\n\t\t\t\t\t</canvas>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t);\n\t},\n);\nLiveline.displayName = 'Liveline';\n\nexport { Liveline };\n"
    },
    {
      "path": "packages/react/src/components/liveline/liveline-transition.tsx",
      "type": "registry:ui",
      "target": "components/ui/liveline/liveline-transition.tsx",
      "content": "import React, { useEffect, useRef, useState } from 'react';\nimport styles from './liveline.module.scss';\n\nexport interface LivelineTransitionProps {\n\t/** Key of the active child to display. Must match a child's `key` prop. */\n\tactive: string;\n\t/** Chart elements with unique `key` props. */\n\tchildren: React.ReactElement | React.ReactElement[];\n\t/** Cross-fade duration in milliseconds. */\n\tduration?: number;\n\tclassName?: string;\n\tstyle?: React.CSSProperties;\n}\n\nfunction prefersReducedMotion() {\n\treturn typeof window !== 'undefined' && typeof window.matchMedia === 'function' && window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\nconst LivelineTransition = React.forwardRef<HTMLDivElement, LivelineTransitionProps>(({ active, children, duration = 300, className = '', style }, ref) => {\n\tconst childArray = Array.isArray(children) ? children : [children];\n\tconst [mounted, setMounted] = useState<Set<string>>(() => new Set([active]));\n\tconst [visible, setVisible] = useState(active);\n\tconst initializedRef = useRef(false);\n\n\tuseEffect(() => {\n\t\tif (!initializedRef.current) {\n\t\t\tinitializedRef.current = true;\n\t\t\treturn;\n\t\t}\n\t\tif (prefersReducedMotion()) {\n\t\t\tsetMounted(new Set([active]));\n\t\t\tsetVisible(active);\n\t\t\treturn;\n\t\t}\n\n\t\tsetMounted((current) => new Set([...current, active]));\n\t\tlet secondFrame = 0;\n\t\tconst firstFrame = requestAnimationFrame(() => {\n\t\t\tsecondFrame = requestAnimationFrame(() => setVisible(active));\n\t\t});\n\t\tconst timer = window.setTimeout(() => {\n\t\t\tsetMounted(new Set([active]));\n\t\t\tsetVisible(active);\n\t\t}, duration + 50);\n\n\t\treturn () => {\n\t\t\tcancelAnimationFrame(firstFrame);\n\t\t\tcancelAnimationFrame(secondFrame);\n\t\t\tclearTimeout(timer);\n\t\t};\n\t}, [active, duration]);\n\n\treturn (\n\t\t<div ref={ref} data-slot=\"liveline-transition\" className={`${styles.transition} ${className}`} style={style}>\n\t\t\t{childArray.map((child) => {\n\t\t\t\tconst key = String(child.key ?? '');\n\t\t\t\tif (!mounted.has(key)) return null;\n\t\t\t\tconst isActive = key === visible;\n\t\t\t\treturn (\n\t\t\t\t\t<div\n\t\t\t\t\t\tkey={key}\n\t\t\t\t\t\tdata-slot=\"liveline-transition-layer\"\n\t\t\t\t\t\tdata-active={isActive || undefined}\n\t\t\t\t\t\taria-hidden={!isActive}\n\t\t\t\t\t\t// A nonempty attribute works in both React 18 (unknown) and 19 (boolean).\n\t\t\t\t\t\t{...(!isActive ? { inert: 'inert' as unknown as boolean } : {})}\n\t\t\t\t\t\tclassName={styles.transitionLayer}\n\t\t\t\t\t\tstyle={{ transitionDuration: `${duration}ms` }}>\n\t\t\t\t\t\t{child}\n\t\t\t\t\t</div>\n\t\t\t\t);\n\t\t\t})}\n\t\t</div>\n\t);\n});\nLivelineTransition.displayName = 'LivelineTransition';\n\nexport { LivelineTransition };\n"
    },
    {
      "path": "packages/styles/scss/components/liveline.module.scss",
      "type": "registry:file",
      "target": "components/ui/liveline/liveline.module.scss",
      "content": ".root {\n\tdisplay: grid;\n\tgrid-template-rows: auto auto minmax(0, 1fr);\n\twidth: 100%;\n\theight: 100%;\n\tmin-width: 0;\n\tmin-height: 0;\n}\n\n.value {\n\tgrid-row: 1;\n\tdisplay: block;\n\tpadding-top: 4px;\n\tmargin-bottom: 8px;\n\tfont-family: var(--tile-liveline-value-font, 'SF Mono', Menlo, monospace);\n\tfont-size: 20px;\n\tfont-weight: 500;\n\tfont-variant-numeric: tabular-nums;\n\tletter-spacing: -0.01em;\n\tcolor: var(--tile-liveline-value-color, #111);\n\ttransition: color 0.3s;\n}\n\n.root[data-color-mode='dark'] .value {\n\tcolor: var(--tile-liveline-value-color, rgba(255, 255, 255, 0.85));\n}\n\n.controls {\n\tgrid-row: 2;\n\tdisplay: flex;\n\tmax-width: 100%;\n\tmin-width: 0;\n\talign-items: center;\n\tgap: 6px;\n\tpadding-right: 4px;\n\tmargin-bottom: 6px;\n\toverflow-x: auto;\n\toverflow-y: hidden;\n\toverscroll-behavior-inline: contain;\n\tscrollbar-width: thin;\n}\n\n.controlGroup {\n\tposition: relative;\n\tdisplay: inline-flex;\n\tflex: 0 0 auto;\n\tgap: 2px;\n\tpadding: 2px;\n\tborder-radius: var(--tile-liveline-control-radius, 6px);\n\tbackground: var(--tile-liveline-control-background, rgba(0, 0, 0, 0.02));\n\ttransition: opacity 0.4s;\n}\n\n.root[data-color-mode='dark'] .controlGroup {\n\tbackground: var(--tile-liveline-control-background, rgba(255, 255, 255, 0.03));\n}\n\n.controlGroup[data-style='rounded'] {\n\tpadding: 3px;\n\tborder-radius: 999px;\n}\n\n.root .controlGroup[data-style='text'] {\n\tgap: 4px;\n\tpadding: 0;\n\tbackground: transparent;\n}\n\n.controlGroup[data-visible='false'] {\n\tpointer-events: none;\n\topacity: 0;\n}\n\n.indicator {\n\tpointer-events: none;\n\tposition: absolute;\n\ttop: 2px;\n\theight: calc(100% - 4px);\n\tborder-radius: 4px;\n\tbackground: var(--tile-liveline-indicator-background, rgba(0, 0, 0, 0.035));\n\ttransition:\n\t\tleft 0.25s cubic-bezier(0.4, 0, 0.2, 1),\n\t\twidth 0.25s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.root[data-color-mode='dark'] .indicator {\n\tbackground: var(--tile-liveline-indicator-background, rgba(255, 255, 255, 0.06));\n}\n\n.controlGroup[data-style='rounded'] .indicator {\n\ttop: 3px;\n\theight: calc(100% - 6px);\n\tborder-radius: 999px;\n}\n\n.control {\n\tposition: relative;\n\tz-index: 1;\n\tdisplay: inline-flex;\n\tmin-width: 0;\n\talign-items: center;\n\tgap: 4px;\n\tpadding: 3px 8px;\n\tborder: 0;\n\tborder-radius: 4px;\n\tbackground: transparent;\n\tcolor: var(--tile-liveline-control-foreground, rgba(0, 0, 0, 0.22));\n\tfont: var(--tile-liveline-control-font, 400 11px/16px system-ui, -apple-system, sans-serif);\n\twhite-space: nowrap;\n\tcursor: pointer;\n\ttransition:\n\t\topacity 0.2s,\n\t\tcolor 0.2s,\n\t\tbackground 0.15s;\n}\n\n.controlGroup[data-style='rounded'] .control {\n\tborder-radius: 999px;\n}\n\n.controlGroup[data-style='text'] .control {\n\tpadding: 2px 6px;\n}\n\n.root[data-color-mode='dark'] .control {\n\tcolor: var(--tile-liveline-control-foreground, rgba(255, 255, 255, 0.25));\n}\n\n.control[data-active] {\n\tfont-weight: 600;\n\tcolor: var(--tile-liveline-control-active-foreground, rgba(0, 0, 0, 0.55));\n}\n\n.root[data-color-mode='dark'] .control[data-active] {\n\tcolor: var(--tile-liveline-control-active-foreground, rgba(255, 255, 255, 0.7));\n}\n\n.control:focus-visible {\n\toutline: 2px solid currentColor;\n\toutline-offset: 1px;\n}\n\n.controlGroup[data-style] .iconControl {\n\tpadding: 5px 7px;\n}\n\n.compactControl {\n\tpadding: 5px 7px;\n\tgap: 0;\n}\n\n.controlGroup[data-style='text'] .compactControl {\n\tpadding: 2px 4px;\n}\n\n.controlGroup:not([data-style='text']) [data-slot='liveline-window-control'] {\n\tpadding: 3px 10px;\n}\n\n.root [data-slot='liveline-series-control'] {\n\tfont-weight: 500;\n}\n\n.swatch {\n\twidth: 6px;\n\theight: 6px;\n\tflex: 0 0 auto;\n\tborder-radius: 50%;\n\ttransition: opacity 0.2s;\n}\n\n.root [data-slot='liveline-series-control']:not([data-active]),\n.root [data-slot='liveline-series-control']:not([data-active]) .swatch {\n\topacity: 0.4;\n}\n\n.root [data-slot='liveline-series-control'][data-active] {\n\tbackground: var(--tile-liveline-indicator-background, rgba(0, 0, 0, 0.035));\n}\n\n.root[data-color-mode='dark'] [data-slot='liveline-series-control'][data-active] {\n\tbackground: var(--tile-liveline-indicator-background, rgba(255, 255, 255, 0.06));\n}\n\n.root .controlGroup[data-style='text'] [data-slot='liveline-series-control'][data-active] {\n\tbackground: transparent;\n}\n\n.compactControl .swatch {\n\twidth: 8px;\n\theight: 8px;\n}\n\n.surface {\n\tposition: relative;\n\tgrid-row: 3;\n\twidth: 100%;\n\theight: 100%;\n\tmin-width: 0;\n\tmin-height: 0;\n\ttouch-action: pan-y;\n}\n\n.canvas {\n\tdisplay: block;\n}\n\n.transition {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n}\n\n.transitionLayer {\n\tpointer-events: none;\n\tposition: absolute;\n\tinset: 0;\n\topacity: 0;\n\ttransition-property: opacity;\n\ttransition-timing-function: ease;\n}\n\n.transitionLayer[data-active] {\n\tpointer-events: auto;\n\topacity: 1;\n}\n\n@media (prefers-reduced-motion: reduce) {\n\t.value,\n\t.indicator,\n\t.control,\n\t.controlGroup,\n\t.transitionLayer {\n\t\ttransition-duration: 0.01ms !important;\n\t}\n}\n"
    },
    {
      "path": "packages/react/src/components/liveline/index.ts",
      "type": "registry:ui",
      "target": "components/ui/liveline/index.ts",
      "content": "export { Liveline, type LivelineProps } from './liveline';\nexport { LivelineTransition, type LivelineTransitionProps } from './liveline-transition';\nexport type {\n\tBadgeVariant,\n\tCandlePoint,\n\tDegenOptions,\n\tHoverPoint,\n\tLivelineOptions,\n\tLivelinePoint,\n\tLivelineSeries,\n\tMomentum,\n\tOrderbookData,\n\tPadding,\n\tReferenceLine,\n\tThemeMode,\n\tWindowOption,\n\tWindowStyle,\n} from '../lib/liveline';\n"
    }
  ]
}
