@opendata-ai/openchart-react 6.5.2 → 6.6.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.
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  import * as _opendata_ai_openchart_core from '@opendata-ai/openchart-core';
2
- import { ChartEventHandlers, ChartSpec, LayerSpec, GraphSpec, ThemeConfig, DarkMode, ElementRef, TableSpec, SortState, ChartLayout, VizSpec } from '@opendata-ai/openchart-core';
2
+ import { ChartEventHandlers, ChartSpec, LayerSpec, GraphSpec, ThemeConfig, DarkMode, ElementRef, TableSpec, SortState, ChartLayout, SankeySpec, VizSpec } from '@opendata-ai/openchart-core';
3
3
  export * from '@opendata-ai/openchart-core';
4
- import { ChartInstance, GraphInstance, MountOptions, TableInstance, TableState, TableMountOptions } from '@opendata-ai/openchart-vanilla';
4
+ import { ChartInstance, GraphInstance, MountOptions, TableInstance, TableState, TableMountOptions, SankeyInstance } from '@opendata-ai/openchart-vanilla';
5
5
  export { JPGExportOptions, PNGExportOptions, SVGExportOptions, exportCSV, exportJPG, exportPNG, exportSVG, exportSVGWithFonts } from '@opendata-ai/openchart-vanilla';
6
- export { ChartRenderer, CompileResult, CompiledGraphEdge, CompiledGraphNode, GraphCompilation, NormalizedChartSpec, NormalizedChrome, NormalizedGraphSpec, NormalizedSpec, NormalizedTableSpec, SimulationConfig, ValidationError, ValidationErrorCode, ValidationResult, clearRenderers, compile, compileChart, compileGraph, compileTable, getChartRenderer, normalizeSpec, registerChartRenderer, validateSpec } from '@opendata-ai/openchart-engine';
6
+ export { ChartRenderer, CompileResult, CompiledGraphEdge, CompiledGraphNode, GraphCompilation, NormalizedChartSpec, NormalizedChrome, NormalizedGraphSpec, NormalizedSankeySpec, NormalizedSpec, NormalizedTableSpec, SimulationConfig, ValidationError, ValidationErrorCode, ValidationResult, clearRenderers, compile, compileChart, compileGraph, compileSankey, compileTable, getChartRenderer, normalizeSpec, registerChartRenderer, validateSpec } from '@opendata-ai/openchart-engine';
7
7
  import * as react from 'react';
8
8
  import { CSSProperties, ReactNode } from 'react';
9
9
  import * as react_jsx_runtime from 'react/jsx-runtime';
@@ -273,6 +273,39 @@ interface UseTableStateOptions {
273
273
  */
274
274
  declare function useTableState(initialState?: UseTableStateOptions): UseTableStateReturn;
275
275
 
276
+ interface SankeyProps {
277
+ /** The sankey spec to render. */
278
+ spec: SankeySpec;
279
+ /** Theme overrides. */
280
+ theme?: ThemeConfig;
281
+ /** Dark mode: "auto", "force", or "off". */
282
+ darkMode?: DarkMode;
283
+ /** Callback when a node is clicked. */
284
+ onNodeClick?: (node: Record<string, unknown>) => void;
285
+ /** Callback when a link is clicked. */
286
+ onLinkClick?: (link: Record<string, unknown>) => void;
287
+ /** Callback when a node is hovered (null when hover ends). */
288
+ onNodeHover?: (node: Record<string, unknown> | null) => void;
289
+ /** Callback when a link is hovered (null when hover ends). */
290
+ onLinkHover?: (link: Record<string, unknown> | null) => void;
291
+ /** CSS class name for the wrapper div. */
292
+ className?: string;
293
+ /** Inline styles for the wrapper div. */
294
+ style?: CSSProperties;
295
+ }
296
+ interface SankeyHandle {
297
+ /** The underlying sankey instance (null until mounted). */
298
+ readonly instance: SankeyInstance | null;
299
+ }
300
+ /**
301
+ * React component that renders a sankey diagram from a SankeySpec.
302
+ *
303
+ * Uses the vanilla adapter internally. The spec is compiled and rendered
304
+ * as SVG inside a wrapper div. Spec changes trigger re-renders via the
305
+ * vanilla adapter's update() method.
306
+ */
307
+ declare const Sankey: react.ForwardRefExoticComponent<SankeyProps & react.RefAttributes<SankeyHandle>>;
308
+
276
309
  /** Read the current theme from the nearest VizThemeProvider. */
277
310
  declare function useVizTheme(): ThemeConfig | undefined;
278
311
  /** Read the current dark mode preference from the nearest VizThemeProvider. */
@@ -307,4 +340,4 @@ interface VisualizationProps {
307
340
  */
308
341
  declare function Visualization({ spec, theme, darkMode, className, style }: VisualizationProps): react_jsx_runtime.JSX.Element;
309
342
 
310
- export { Chart, type ChartHandle, type ChartProps, DataTable, type DataTableProps, Graph, type GraphHandle, type GraphProps, type UseChartOptions, type UseChartReturn, type UseGraphReturn, type UseTableReturn, type UseTableStateOptions, type UseTableStateReturn, Visualization, type VisualizationProps, VizThemeProvider, type VizThemeProviderProps, useChart, useDarkMode, useGraph, useTable, useTableState, useVizDarkMode, useVizTheme };
343
+ export { Chart, type ChartHandle, type ChartProps, DataTable, type DataTableProps, Graph, type GraphHandle, type GraphProps, Sankey, type SankeyHandle, type SankeyProps, type UseChartOptions, type UseChartReturn, type UseGraphReturn, type UseTableReturn, type UseTableStateOptions, type UseTableStateReturn, Visualization, type VisualizationProps, VizThemeProvider, type VizThemeProviderProps, useChart, useDarkMode, useGraph, useTable, useTableState, useVizDarkMode, useVizTheme };
package/dist/index.js CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  compile,
13
13
  compileChart,
14
14
  compileGraph,
15
+ compileSankey,
15
16
  compileTable,
16
17
  getChartRenderer,
17
18
  normalizeSpec,
@@ -660,12 +661,118 @@ function useTableState(initialState) {
660
661
  };
661
662
  }
662
663
 
663
- // src/Visualization.tsx
664
- import { isGraphSpec, isTableSpec } from "@opendata-ai/openchart-core";
664
+ // src/Sankey.tsx
665
+ import {
666
+ createSankey
667
+ } from "@opendata-ai/openchart-vanilla";
668
+ import {
669
+ forwardRef as forwardRef3,
670
+ useCallback as useCallback7,
671
+ useEffect as useEffect6,
672
+ useImperativeHandle as useImperativeHandle3,
673
+ useRef as useRef7
674
+ } from "react";
665
675
  import { jsx as jsx5 } from "react/jsx-runtime";
676
+ var Sankey = forwardRef3(function Sankey2({
677
+ spec,
678
+ theme: themeProp,
679
+ darkMode,
680
+ onNodeClick,
681
+ onLinkClick,
682
+ onNodeHover,
683
+ onLinkHover,
684
+ className,
685
+ style
686
+ }, ref) {
687
+ const contextTheme = useVizTheme();
688
+ const contextDarkMode = useVizDarkMode();
689
+ const theme = themeProp ?? contextTheme;
690
+ const resolvedDarkMode = darkMode ?? contextDarkMode;
691
+ const containerRef = useRef7(null);
692
+ const instanceRef = useRef7(null);
693
+ const specRef = useRef7("");
694
+ const handlersRef = useRef7({});
695
+ handlersRef.current = {
696
+ onNodeClick,
697
+ onLinkClick,
698
+ onNodeHover,
699
+ onLinkHover
700
+ };
701
+ const stableOnNodeClick = useCallback7(
702
+ (node) => handlersRef.current.onNodeClick?.(node),
703
+ []
704
+ );
705
+ const stableOnLinkClick = useCallback7(
706
+ (link) => handlersRef.current.onLinkClick?.(link),
707
+ []
708
+ );
709
+ const stableOnNodeHover = useCallback7(
710
+ (node) => handlersRef.current.onNodeHover?.(node),
711
+ []
712
+ );
713
+ const stableOnLinkHover = useCallback7(
714
+ (link) => handlersRef.current.onLinkHover?.(link),
715
+ []
716
+ );
717
+ useImperativeHandle3(
718
+ ref,
719
+ () => ({
720
+ get instance() {
721
+ return instanceRef.current;
722
+ }
723
+ }),
724
+ []
725
+ );
726
+ useEffect6(() => {
727
+ const container = containerRef.current;
728
+ if (!container) return;
729
+ const options = {
730
+ theme,
731
+ darkMode: resolvedDarkMode,
732
+ onNodeClick: stableOnNodeClick,
733
+ onLinkClick: stableOnLinkClick,
734
+ onNodeHover: stableOnNodeHover,
735
+ onLinkHover: stableOnLinkHover,
736
+ responsive: true
737
+ };
738
+ instanceRef.current = createSankey(container, spec, options);
739
+ specRef.current = JSON.stringify(spec);
740
+ return () => {
741
+ instanceRef.current?.destroy();
742
+ instanceRef.current = null;
743
+ };
744
+ }, [
745
+ theme,
746
+ resolvedDarkMode,
747
+ stableOnNodeClick,
748
+ stableOnLinkClick,
749
+ stableOnNodeHover,
750
+ stableOnLinkHover
751
+ ]);
752
+ useEffect6(() => {
753
+ const instance = instanceRef.current;
754
+ if (!instance) return;
755
+ const specString = JSON.stringify(spec);
756
+ if (specString === specRef.current) return;
757
+ specRef.current = specString;
758
+ instance.update(spec);
759
+ }, [spec]);
760
+ return /* @__PURE__ */ jsx5(
761
+ "div",
762
+ {
763
+ ref: containerRef,
764
+ className: className ? `oc-sankey-root ${className}` : "oc-sankey-root",
765
+ style
766
+ }
767
+ );
768
+ });
769
+
770
+ // src/Visualization.tsx
771
+ import { isGraphSpec, isSankeySpec, isTableSpec } from "@opendata-ai/openchart-core";
772
+ import { jsx as jsx6 } from "react/jsx-runtime";
666
773
  function Visualization({ spec, theme, darkMode, className, style }) {
667
774
  if (isTableSpec(spec)) {
668
- return /* @__PURE__ */ jsx5(
775
+ return /* @__PURE__ */ jsx6(
669
776
  DataTable,
670
777
  {
671
778
  spec,
@@ -677,20 +784,25 @@ function Visualization({ spec, theme, darkMode, className, style }) {
677
784
  );
678
785
  }
679
786
  if (isGraphSpec(spec)) {
680
- return /* @__PURE__ */ jsx5(Graph, { spec, theme, darkMode, className, style });
787
+ return /* @__PURE__ */ jsx6(Graph, { spec, theme, darkMode, className, style });
788
+ }
789
+ if (isSankeySpec(spec)) {
790
+ return /* @__PURE__ */ jsx6(Sankey, { spec, theme, darkMode, className, style });
681
791
  }
682
- return /* @__PURE__ */ jsx5(Chart, { spec, theme, darkMode, className, style });
792
+ return /* @__PURE__ */ jsx6(Chart, { spec, theme, darkMode, className, style });
683
793
  }
684
794
  export {
685
795
  Chart,
686
796
  DataTable,
687
797
  Graph,
798
+ Sankey,
688
799
  Visualization,
689
800
  VizThemeProvider,
690
801
  clearRenderers,
691
802
  compile,
692
803
  compileChart,
693
804
  compileGraph,
805
+ compileSankey,
694
806
  compileTable,
695
807
  exportCSV,
696
808
  exportJPG,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/Chart.tsx","../src/ThemeContext.tsx","../src/DataTable.tsx","../src/Graph.tsx","../src/hooks.ts","../src/hooks/useGraph.ts","../src/hooks/useTable.ts","../src/hooks/useTableState.ts","../src/Visualization.tsx"],"sourcesContent":["/**\n * @opendata-ai/openchart-react\n *\n * React adapter for openchart. Provides <Chart /> and <DataTable />\n * components that wrap the vanilla adapter with React lifecycle management.\n *\n * Re-exports the full core type system and utilities so React consumers\n * only need a single @opendata-ai/openchart-react dependency.\n */\n\n// ---------------------------------------------------------------------------\n// Core: full type system, theme, colors, locale, accessibility, helpers\n// ---------------------------------------------------------------------------\n\nexport * from '@opendata-ai/openchart-core';\n\n// ---------------------------------------------------------------------------\n// Vanilla: export utilities (SVG/PNG/JPG/CSV)\n// ---------------------------------------------------------------------------\n\nexport type {\n JPGExportOptions,\n PNGExportOptions,\n SVGExportOptions,\n} from '@opendata-ai/openchart-vanilla';\nexport {\n exportCSV,\n exportJPG,\n exportPNG,\n exportSVG,\n exportSVGWithFonts,\n} from '@opendata-ai/openchart-vanilla';\n\n// ---------------------------------------------------------------------------\n// Engine: compile API and types not covered by core\n// ---------------------------------------------------------------------------\n\nexport type {\n ChartRenderer,\n CompiledGraphEdge,\n CompiledGraphNode,\n CompileResult,\n GraphCompilation,\n NormalizedChartSpec,\n NormalizedChrome,\n NormalizedGraphSpec,\n NormalizedSpec,\n NormalizedTableSpec,\n SimulationConfig,\n ValidationError,\n ValidationErrorCode,\n ValidationResult,\n} from '@opendata-ai/openchart-engine';\nexport {\n clearRenderers,\n compile,\n compileChart,\n compileGraph,\n compileTable,\n getChartRenderer,\n normalizeSpec,\n registerChartRenderer,\n validateSpec,\n} from '@opendata-ai/openchart-engine';\n\nexport type { ChartHandle, ChartProps } from './Chart';\n// Components\nexport { Chart } from './Chart';\nexport type { DataTableProps } from './DataTable';\nexport { DataTable } from './DataTable';\nexport type { GraphProps } from './Graph';\nexport { Graph } from './Graph';\nexport type { UseChartOptions, UseChartReturn } from './hooks';\n// Hooks\nexport { useChart, useDarkMode } from './hooks';\nexport type { GraphHandle, UseGraphReturn } from './hooks/useGraph';\nexport { useGraph } from './hooks/useGraph';\nexport type { UseTableReturn } from './hooks/useTable';\nexport { useTable } from './hooks/useTable';\nexport type { UseTableStateOptions, UseTableStateReturn } from './hooks/useTableState';\nexport { useTableState } from './hooks/useTableState';\nexport type { VizThemeProviderProps } from './ThemeContext';\n// Theme context\nexport { useVizDarkMode, useVizTheme, VizThemeProvider } from './ThemeContext';\nexport type { VisualizationProps } from './Visualization';\nexport { Visualization } from './Visualization';\n","/**\n * React Chart component: thin wrapper around the vanilla adapter.\n *\n * Mounts a chart instance on render, updates when spec changes,\n * and cleans up on unmount. All heavy lifting is done by the vanilla\n * createChart() function.\n */\n\nimport type {\n ChartEventHandlers,\n ChartSpec,\n DarkMode,\n ElementRef,\n GraphSpec,\n LayerSpec,\n ThemeConfig,\n} from '@opendata-ai/openchart-core';\nimport { type ChartInstance, createChart, type MountOptions } from '@opendata-ai/openchart-vanilla';\nimport {\n type CSSProperties,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n} from 'react';\nimport { useVizDarkMode, useVizTheme } from './ThemeContext';\n\nexport interface ChartHandle {\n /** Get the currently selected element, or null if none. */\n getSelectedElement(): ElementRef | null;\n /** Programmatically select an element. */\n select(ref: ElementRef): void;\n /** Deselect the current element. */\n deselect(): void;\n /** The underlying chart instance (null until mounted). */\n readonly instance: ChartInstance | null;\n}\n\nexport interface ChartProps extends ChartEventHandlers {\n /** The visualization spec to render. */\n spec: ChartSpec | LayerSpec | GraphSpec;\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode: \"auto\", \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** Callback when a data point is clicked. @deprecated Use onMarkClick instead. */\n onDataPointClick?: (data: Record<string, unknown>) => void;\n /** The currently selected element (controlled). */\n selectedElement?: ElementRef;\n /** CSS class name for the wrapper div. */\n className?: string;\n /** Inline styles for the wrapper div. */\n style?: CSSProperties;\n}\n\n/**\n * React component that renders a visualization from a spec.\n *\n * Uses the vanilla adapter internally. The spec is compiled and rendered\n * as SVG inside a wrapper div. Spec changes trigger re-renders via the\n * vanilla adapter's update() method.\n */\nexport const Chart = forwardRef<ChartHandle, ChartProps>(function Chart(\n {\n spec,\n theme: themeProp,\n darkMode,\n onDataPointClick,\n onMarkClick,\n onMarkHover,\n onMarkLeave,\n onLegendToggle,\n onAnnotationClick,\n onAnnotationEdit,\n onEdit,\n onSelect,\n onDeselect,\n onTextEdit,\n selectedElement: selectedElementProp,\n className,\n style,\n },\n ref,\n) {\n const contextTheme = useVizTheme();\n const contextDarkMode = useVizDarkMode();\n const theme = themeProp ?? contextTheme;\n const resolvedDarkMode = darkMode ?? contextDarkMode;\n const containerRef = useRef<HTMLDivElement>(null);\n const chartRef = useRef<ChartInstance | null>(null);\n const specRef = useRef<string>('');\n\n // Store event handlers in refs so they don't trigger chart recreation.\n // Inline arrow functions create new references every render, which would\n // destroy and recreate the entire chart instance without this pattern.\n const handlersRef = useRef<{\n onDataPointClick?: ChartProps['onDataPointClick'];\n onMarkClick?: ChartProps['onMarkClick'];\n onMarkHover?: ChartProps['onMarkHover'];\n onMarkLeave?: ChartProps['onMarkLeave'];\n onLegendToggle?: ChartProps['onLegendToggle'];\n onAnnotationClick?: ChartProps['onAnnotationClick'];\n onAnnotationEdit?: ChartProps['onAnnotationEdit'];\n onEdit?: ChartProps['onEdit'];\n onSelect?: ChartProps['onSelect'];\n onDeselect?: ChartProps['onDeselect'];\n onTextEdit?: ChartProps['onTextEdit'];\n }>({});\n handlersRef.current = {\n onDataPointClick,\n onMarkClick,\n onMarkHover,\n onMarkLeave,\n onLegendToggle,\n onAnnotationClick,\n onAnnotationEdit,\n onEdit,\n onSelect,\n onDeselect,\n onTextEdit,\n };\n\n // Stable callback wrappers that read from refs\n const stableOnDataPointClick = useCallback(\n (data: Record<string, unknown>) => handlersRef.current.onDataPointClick?.(data),\n [],\n );\n const stableOnMarkClick = useCallback(\n (event: import('@opendata-ai/openchart-core').MarkEvent) =>\n handlersRef.current.onMarkClick?.(event),\n [],\n );\n const stableOnMarkHover = useCallback(\n (event: import('@opendata-ai/openchart-core').MarkEvent) =>\n handlersRef.current.onMarkHover?.(event),\n [],\n );\n const stableOnMarkLeave = useCallback(() => handlersRef.current.onMarkLeave?.(), []);\n const stableOnLegendToggle = useCallback(\n (series: string, visible: boolean) => handlersRef.current.onLegendToggle?.(series, visible),\n [],\n );\n const stableOnAnnotationClick = useCallback(\n (annotation: import('@opendata-ai/openchart-core').Annotation, event: MouseEvent) =>\n handlersRef.current.onAnnotationClick?.(annotation, event),\n [],\n );\n const stableOnAnnotationEdit = useCallback(\n (\n annotation: import('@opendata-ai/openchart-core').TextAnnotation,\n updatedOffset: import('@opendata-ai/openchart-core').AnnotationOffset,\n ) => handlersRef.current.onAnnotationEdit?.(annotation, updatedOffset),\n [],\n );\n const stableOnEdit = useCallback(\n (edit: import('@opendata-ai/openchart-core').ElementEdit) => handlersRef.current.onEdit?.(edit),\n [],\n );\n const stableOnSelect = useCallback(\n (element: ElementRef) => handlersRef.current.onSelect?.(element),\n [],\n );\n const stableOnDeselect = useCallback(\n (element: ElementRef) => handlersRef.current.onDeselect?.(element),\n [],\n );\n const stableOnTextEdit = useCallback(\n (element: ElementRef, oldText: string, newText: string) =>\n handlersRef.current.onTextEdit?.(element, oldText, newText),\n [],\n );\n\n // Expose imperative handle for ref-based control\n useImperativeHandle(\n ref,\n () => ({\n getSelectedElement() {\n return chartRef.current?.getSelectedElement() ?? null;\n },\n select(elementRef: ElementRef) {\n chartRef.current?.select(elementRef);\n },\n deselect() {\n chartRef.current?.deselect();\n },\n get instance() {\n return chartRef.current;\n },\n }),\n [],\n );\n\n // Mount chart and recreate when theme/darkMode change.\n // Event handlers use stable refs so they don't trigger recreation.\n // biome-ignore lint/correctness/useExhaustiveDependencies: spec intentionally excluded - spec changes handled via update() in Effect 2\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const options: MountOptions = {\n theme,\n darkMode: resolvedDarkMode,\n onDataPointClick: stableOnDataPointClick,\n onMarkClick: stableOnMarkClick,\n onMarkHover: stableOnMarkHover,\n onMarkLeave: stableOnMarkLeave,\n onLegendToggle: stableOnLegendToggle,\n onAnnotationClick: stableOnAnnotationClick,\n // Only include editing callbacks when the consumer provides them.\n // The stable wrappers are always truthy, so gating on handlersRef\n // avoids adding unstable prop references to the effect deps.\n ...(handlersRef.current.onAnnotationEdit ? { onAnnotationEdit: stableOnAnnotationEdit } : {}),\n ...(handlersRef.current.onEdit ? { onEdit: stableOnEdit } : {}),\n ...(handlersRef.current.onSelect ? { onSelect: stableOnSelect } : {}),\n ...(handlersRef.current.onDeselect ? { onDeselect: stableOnDeselect } : {}),\n ...(handlersRef.current.onTextEdit ? { onTextEdit: stableOnTextEdit } : {}),\n ...(selectedElementProp ? { selectedElement: selectedElementProp } : {}),\n responsive: true,\n };\n\n chartRef.current = createChart(container, spec, options);\n specRef.current = JSON.stringify(spec);\n\n return () => {\n chartRef.current?.destroy();\n chartRef.current = null;\n };\n // Only recreate when theme or darkMode change. Event handlers use stable refs.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n theme,\n resolvedDarkMode,\n stableOnAnnotationClick,\n stableOnDataPointClick,\n stableOnEdit,\n stableOnLegendToggle,\n stableOnMarkClick,\n stableOnMarkHover,\n stableOnMarkLeave,\n stableOnAnnotationEdit,\n stableOnSelect,\n stableOnDeselect,\n stableOnTextEdit,\n ]);\n\n // Update chart when spec changes\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart) return;\n\n const specString = JSON.stringify(spec);\n if (specString !== specRef.current) {\n specRef.current = specString;\n chart.update(spec);\n }\n }, [spec]);\n\n // Handle selectedElement prop changes separately (like Vue/Svelte adapters)\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart || !chart.select) return;\n\n if (selectedElementProp) {\n chart.select(selectedElementProp);\n } else if (chart.getSelectedElement?.()) {\n chart.deselect();\n }\n }, [selectedElementProp]);\n\n return (\n <div\n ref={containerRef}\n className={className ? `oc-chart-root ${className}` : 'oc-chart-root'}\n style={style}\n />\n );\n});\n","/**\n * Theme context: provides a theme and dark mode preference to all\n * descendant Chart, DataTable, and Graph components without prop drilling.\n *\n * Components use the context values as fallbacks when no explicit\n * `theme` or `darkMode` prop is passed.\n */\n\nimport type { DarkMode, ThemeConfig } from '@opendata-ai/openchart-core';\nimport { createContext, type ReactNode, useContext } from 'react';\n\nconst VizThemeContext = createContext<ThemeConfig | undefined>(undefined);\nconst VizDarkModeContext = createContext<DarkMode | undefined>(undefined);\n\n/** Read the current theme from the nearest VizThemeProvider. */\nexport function useVizTheme(): ThemeConfig | undefined {\n return useContext(VizThemeContext);\n}\n\n/** Read the current dark mode preference from the nearest VizThemeProvider. */\nexport function useVizDarkMode(): DarkMode | undefined {\n return useContext(VizDarkModeContext);\n}\n\nexport interface VizThemeProviderProps {\n /** Theme config to provide to descendant viz components. */\n theme: ThemeConfig | undefined;\n /** Dark mode preference to provide to descendant viz components. */\n darkMode?: DarkMode;\n children: ReactNode;\n}\n\n/** Provides a theme and dark mode preference to all nested Chart, DataTable, and Graph components. */\nexport function VizThemeProvider({ theme, darkMode, children }: VizThemeProviderProps) {\n return (\n <VizThemeContext.Provider value={theme}>\n <VizDarkModeContext.Provider value={darkMode}>{children}</VizDarkModeContext.Provider>\n </VizThemeContext.Provider>\n );\n}\n","/**\n * DataTable component: React wrapper around the vanilla table adapter.\n *\n * Mounts a table instance on render, updates when spec changes,\n * and cleans up on unmount. Supports both controlled and uncontrolled modes\n * for sort, search, and pagination state.\n */\n\nimport type { DarkMode, SortState, TableSpec, ThemeConfig } from '@opendata-ai/openchart-core';\nimport {\n createTable,\n type TableInstance,\n type TableMountOptions,\n} from '@opendata-ai/openchart-vanilla';\nimport { type CSSProperties, useCallback, useEffect, useRef } from 'react';\nimport { useVizDarkMode, useVizTheme } from './ThemeContext';\n\nexport interface DataTableProps {\n /** The table spec to render. */\n spec: TableSpec;\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode: \"auto\", \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** Row click handler. */\n onRowClick?: (row: Record<string, unknown>) => void;\n /** Callback when sort changes. */\n onSortChange?: (sort: SortState | null) => void;\n /** Callback when search changes. */\n onSearchChange?: (query: string) => void;\n /** Callback when page changes. */\n onPageChange?: (page: number) => void;\n /** CSS class name for the wrapper div. */\n className?: string;\n /** Inline styles for the wrapper div. */\n style?: CSSProperties;\n /** Controlled sort state. */\n sort?: SortState | null;\n /** Controlled search query. */\n search?: string;\n /** Controlled page number. */\n page?: number;\n}\n\n/**\n * React component that renders a data table from a TableSpec.\n *\n * Uses the vanilla adapter internally. Supports controlled and uncontrolled\n * modes for sort, search, and pagination state.\n */\nexport function DataTable({\n spec,\n theme: themeProp,\n darkMode,\n onRowClick,\n onSortChange,\n onSearchChange,\n onPageChange,\n className,\n style,\n sort,\n search,\n page,\n}: DataTableProps) {\n const contextTheme = useVizTheme();\n const contextDarkMode = useVizDarkMode();\n const theme = themeProp ?? contextTheme;\n const resolvedDarkMode = darkMode ?? contextDarkMode;\n const containerRef = useRef<HTMLDivElement>(null);\n const tableRef = useRef<TableInstance | null>(null);\n\n // Store event handlers in refs so they don't trigger table recreation.\n const handlersRef = useRef<{\n onRowClick?: DataTableProps['onRowClick'];\n onSortChange?: DataTableProps['onSortChange'];\n onSearchChange?: DataTableProps['onSearchChange'];\n onPageChange?: DataTableProps['onPageChange'];\n }>({});\n handlersRef.current = { onRowClick, onSortChange, onSearchChange, onPageChange };\n\n // Stable callback wrappers that read from refs\n const stableOnRowClick = useCallback(\n (row: Record<string, unknown>) => handlersRef.current.onRowClick?.(row),\n [],\n );\n const stableOnStateChange = useCallback(\n (state: { sort?: SortState | null; search?: string; page?: number }) => {\n if (state.sort !== undefined) handlersRef.current.onSortChange?.(state.sort);\n if (state.search !== undefined) handlersRef.current.onSearchChange?.(state.search);\n if (state.page !== undefined) handlersRef.current.onPageChange?.(state.page);\n },\n [],\n );\n\n const prevSpecRef = useRef<string>('');\n\n // Determine if we're in controlled mode\n const isControlled = sort !== undefined || search !== undefined || page !== undefined;\n\n // Effect 1: Mount/unmount. Only recreate when structural options change.\n // biome-ignore lint/correctness/useExhaustiveDependencies: spec, sort, search, page intentionally excluded - handled via update()/setState() in Effects 2-3\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const mountOptions: TableMountOptions = {\n theme,\n darkMode: resolvedDarkMode,\n onRowClick: stableOnRowClick,\n responsive: true,\n onStateChange: stableOnStateChange,\n };\n\n if (isControlled) {\n mountOptions.externalState = {\n sort: sort ?? null,\n search: search ?? '',\n page: page ?? 0,\n };\n }\n\n tableRef.current = createTable(container, spec, mountOptions);\n prevSpecRef.current = JSON.stringify(spec);\n\n return () => {\n tableRef.current?.destroy();\n tableRef.current = null;\n };\n // Only recreate on structural option changes (theme, darkMode, onRowClick).\n // Controlled state updates are handled in Effect 2.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [theme, resolvedDarkMode, isControlled, stableOnRowClick, stableOnStateChange]);\n\n // Effect 2: Sync controlled state without remounting.\n useEffect(() => {\n const table = tableRef.current;\n if (!table || !isControlled) return;\n\n table.setState({\n sort: sort ?? null,\n search: search ?? '',\n page: page ?? 0,\n });\n }, [sort, search, page, isControlled]);\n\n // Effect 3: Sync spec changes via update().\n useEffect(() => {\n const table = tableRef.current;\n if (!table) return;\n\n const specString = JSON.stringify(spec);\n if (specString !== prevSpecRef.current) {\n prevSpecRef.current = specString;\n table.update(spec);\n }\n }, [spec]);\n\n return (\n <div\n ref={containerRef}\n className={className ? `oc-table-root ${className}` : 'oc-table-root'}\n style={style}\n />\n );\n}\n","/**\n * React Graph component: thin wrapper around the vanilla adapter.\n *\n * Mounts a graph instance on render, updates when spec changes,\n * and cleans up on unmount. All heavy lifting is done by the vanilla\n * createGraph() function.\n *\n * Supports forwardRef for imperative control via useGraph() hook.\n */\n\nimport type { DarkMode, GraphSpec, ThemeConfig } from '@opendata-ai/openchart-core';\nimport {\n createGraph,\n type GraphInstance,\n type GraphMountOptions,\n} from '@opendata-ai/openchart-vanilla';\nimport {\n type CSSProperties,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n} from 'react';\nimport type { GraphHandle } from './hooks/useGraph';\nimport { useVizDarkMode, useVizTheme } from './ThemeContext';\n\nexport interface GraphProps {\n /** The graph spec to render. */\n spec: GraphSpec;\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode: \"auto\", \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** Callback when a node is clicked. */\n onNodeClick?: (node: Record<string, unknown>) => void;\n /** Callback when a node is double-clicked. */\n onNodeDoubleClick?: (node: Record<string, unknown>) => void;\n /** Callback when a node is hovered (null when hover ends). */\n onNodeHover?: (node: Record<string, unknown> | null) => void;\n /** Callback when an edge is hovered (null when hover ends). */\n onEdgeHover?: (edge: Record<string, unknown> | null) => void;\n /** Callback when selection changes. */\n onSelectionChange?: (nodeIds: string[]) => void;\n /** Show built-in tooltip on node/edge hover. Defaults to true. */\n tooltip?: boolean;\n /** Show built-in legend. Defaults to true. */\n legend?: boolean;\n /** CSS class name for the wrapper div. */\n className?: string;\n /** Inline styles for the wrapper div. */\n style?: CSSProperties;\n}\n\n/**\n * React component that renders a force-directed graph from a GraphSpec.\n *\n * Uses the vanilla adapter internally. The spec is compiled and rendered\n * on a canvas inside a wrapper div. Spec changes trigger re-renders via the\n * vanilla adapter's update() method.\n *\n * Supports ref for imperative control via useGraph() hook:\n * ```tsx\n * const { ref, search, zoomToFit } = useGraph();\n * return <Graph ref={ref} spec={spec} />;\n * ```\n */\nexport const Graph = forwardRef<GraphHandle, GraphProps>(function Graph(\n {\n spec,\n theme: themeProp,\n darkMode,\n onNodeClick,\n onNodeDoubleClick,\n onNodeHover,\n onEdgeHover,\n onSelectionChange,\n tooltip,\n legend,\n className,\n style,\n },\n ref,\n) {\n const contextTheme = useVizTheme();\n const contextDarkMode = useVizDarkMode();\n const theme = themeProp ?? contextTheme;\n const resolvedDarkMode = darkMode ?? contextDarkMode;\n const containerRef = useRef<HTMLDivElement>(null);\n const graphRef = useRef<GraphInstance | null>(null);\n const specRef = useRef<string>('');\n\n // Store event handlers in refs so they don't trigger graph recreation.\n // Inline arrow functions create new references every render, which would\n // destroy and recreate the entire graph instance without this pattern.\n const handlersRef = useRef<{\n onNodeClick?: GraphProps['onNodeClick'];\n onNodeDoubleClick?: GraphProps['onNodeDoubleClick'];\n onNodeHover?: GraphProps['onNodeHover'];\n onEdgeHover?: GraphProps['onEdgeHover'];\n onSelectionChange?: GraphProps['onSelectionChange'];\n }>({});\n handlersRef.current = {\n onNodeClick,\n onNodeDoubleClick,\n onNodeHover,\n onEdgeHover,\n onSelectionChange,\n };\n\n // Stable callback wrappers that read from refs\n const stableOnNodeClick = useCallback(\n (node: Record<string, unknown>) => handlersRef.current.onNodeClick?.(node),\n [],\n );\n const stableOnNodeDoubleClick = useCallback(\n (node: Record<string, unknown>) => handlersRef.current.onNodeDoubleClick?.(node),\n [],\n );\n const stableOnNodeHover = useCallback(\n (node: Record<string, unknown> | null) => handlersRef.current.onNodeHover?.(node),\n [],\n );\n const stableOnEdgeHover = useCallback(\n (edge: Record<string, unknown> | null) => handlersRef.current.onEdgeHover?.(edge),\n [],\n );\n const stableOnSelectionChange = useCallback(\n (nodeIds: string[]) => handlersRef.current.onSelectionChange?.(nodeIds),\n [],\n );\n\n // Expose imperative handle for useGraph() hook\n useImperativeHandle(\n ref,\n () => ({\n search(query: string) {\n graphRef.current?.search(query);\n },\n clearSearch() {\n graphRef.current?.clearSearch();\n },\n zoomToFit() {\n graphRef.current?.zoomToFit();\n },\n zoomToNode(nodeId: string) {\n graphRef.current?.zoomToNode(nodeId);\n },\n selectNode(nodeId: string) {\n graphRef.current?.selectNode(nodeId);\n },\n getSelectedNodes() {\n return graphRef.current?.getSelectedNodes() ?? [];\n },\n updateVisuals(spec: GraphSpec) {\n graphRef.current?.updateVisuals(spec);\n },\n get instance() {\n return graphRef.current;\n },\n }),\n [],\n );\n\n // Mount graph and recreate when theme/darkMode change.\n // Event handlers use stable refs so they don't trigger recreation.\n // biome-ignore lint/correctness/useExhaustiveDependencies: spec intentionally excluded - spec changes handled via update() in Effect 2\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const options: GraphMountOptions = {\n theme,\n darkMode: resolvedDarkMode,\n tooltip,\n legend,\n onNodeClick: stableOnNodeClick,\n onNodeDoubleClick: stableOnNodeDoubleClick,\n onNodeHover: stableOnNodeHover,\n onEdgeHover: stableOnEdgeHover,\n onSelectionChange: stableOnSelectionChange,\n responsive: true,\n };\n\n graphRef.current = createGraph(container, spec, options);\n specRef.current = JSON.stringify(spec);\n\n return () => {\n graphRef.current?.destroy();\n graphRef.current = null;\n };\n // Only recreate when theme or darkMode change. Event handlers use stable refs.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n theme,\n resolvedDarkMode,\n tooltip,\n legend,\n stableOnNodeClick,\n stableOnNodeDoubleClick,\n stableOnNodeHover,\n stableOnEdgeHover,\n stableOnSelectionChange,\n ]);\n\n // Update graph when spec changes.\n // If only encoding/chrome/nodeOverrides changed (same node/edge IDs), use\n // updateVisuals() to avoid restarting the simulation.\n useEffect(() => {\n const graph = graphRef.current;\n if (!graph) return;\n\n const specString = JSON.stringify(spec);\n if (specString === specRef.current) return;\n\n // Check if this is a visual-only change (same node/edge IDs)\n const prevSpec = specRef.current;\n specRef.current = specString;\n\n if (prevSpec) {\n try {\n const prev = JSON.parse(prevSpec) as GraphSpec;\n const sameNodes =\n prev.nodes.length === spec.nodes.length &&\n prev.nodes.every((n, i) => n.id === spec.nodes[i].id);\n const sameEdges =\n prev.edges.length === spec.edges.length &&\n prev.edges.every(\n (e, i) => e.source === spec.edges[i].source && e.target === spec.edges[i].target,\n );\n\n const sameLayout = prev.layout?.clustering?.field === spec.layout?.clustering?.field;\n\n if (sameNodes && sameEdges && sameLayout) {\n graph.updateVisuals(spec);\n return;\n }\n } catch {\n // Parse failed, fall through to full update\n }\n }\n\n graph.update(spec);\n }, [spec]);\n\n return (\n <div\n ref={containerRef}\n className={className ? `oc-graph-root ${className}` : 'oc-graph-root'}\n style={style}\n />\n );\n});\n","/**\n * React hooks for chart lifecycle and dark mode resolution.\n *\n * useChart: manual control over a chart instance (for advanced usage).\n * useDarkMode: resolves the DarkMode preference to a boolean.\n */\n\nimport type {\n ChartLayout,\n ChartSpec,\n DarkMode,\n GraphSpec,\n LayerSpec,\n} from '@opendata-ai/openchart-core';\nimport { type ChartInstance, createChart, type MountOptions } from '@opendata-ai/openchart-vanilla';\nimport { useEffect, useRef, useState } from 'react';\n\n// ---------------------------------------------------------------------------\n// useChart\n// ---------------------------------------------------------------------------\n\nexport interface UseChartOptions {\n /** Theme overrides. */\n theme?: MountOptions['theme'];\n /** Dark mode setting. */\n darkMode?: MountOptions['darkMode'];\n /** Data point click handler. */\n onDataPointClick?: MountOptions['onDataPointClick'];\n /** Enable responsive resizing. Defaults to true. */\n responsive?: boolean;\n}\n\nexport interface UseChartReturn {\n /** Ref to attach to the container div. */\n ref: React.RefObject<HTMLDivElement | null>;\n /** The chart instance (null until mounted). */\n chart: ChartInstance | null;\n /** The current compiled layout (null until mounted). */\n layout: ChartLayout | null;\n}\n\n/**\n * Hook for manual chart lifecycle control.\n *\n * Attach the returned ref to a container div. The chart mounts\n * automatically and updates when the spec changes.\n *\n * @param spec - The visualization spec.\n * @param options - Mount options.\n * @returns { ref, chart, layout }\n */\nexport function useChart(\n spec: ChartSpec | LayerSpec | GraphSpec,\n options?: UseChartOptions,\n): UseChartReturn {\n const ref = useRef<HTMLDivElement | null>(null);\n const chartRef = useRef<ChartInstance | null>(null);\n const [layout, setLayout] = useState<ChartLayout | null>(null);\n const specRef = useRef<string>('');\n\n // Mount / unmount\n // biome-ignore lint/correctness/useExhaustiveDependencies: spec intentionally excluded - spec changes handled via update() in the update effect\n useEffect(() => {\n const container = ref.current;\n if (!container) return;\n\n const mountOpts: MountOptions = {\n theme: options?.theme,\n darkMode: options?.darkMode,\n onDataPointClick: options?.onDataPointClick,\n responsive: options?.responsive,\n };\n\n const chart = createChart(container, spec, mountOpts);\n chartRef.current = chart;\n setLayout(chart.layout);\n specRef.current = JSON.stringify(spec);\n\n return () => {\n chart.destroy();\n chartRef.current = null;\n setLayout(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [options?.theme, options?.darkMode, options?.onDataPointClick, options?.responsive]);\n\n // Update on spec change\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart) return;\n\n const specString = JSON.stringify(spec);\n if (specString !== specRef.current) {\n specRef.current = specString;\n chart.update(spec);\n setLayout(chart.layout);\n }\n }, [spec]);\n\n return {\n ref,\n chart: chartRef.current,\n layout,\n };\n}\n\n// ---------------------------------------------------------------------------\n// useDarkMode\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a DarkMode preference to a boolean.\n *\n * - \"force\" -> true\n * - \"off\" -> false\n * - \"auto\" -> matches system preference (reactive to changes)\n *\n * @param mode - The dark mode preference.\n * @returns Whether dark mode is active.\n */\nexport function useDarkMode(mode?: DarkMode): boolean {\n const [isDark, setIsDark] = useState(() => resolveInitial(mode));\n\n useEffect(() => {\n if (mode !== 'auto') {\n setIsDark(mode === 'force');\n return;\n }\n\n if (typeof window === 'undefined' || !window.matchMedia) {\n setIsDark(false);\n return;\n }\n\n const mq = window.matchMedia('(prefers-color-scheme: dark)');\n setIsDark(mq.matches);\n\n const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);\n mq.addEventListener('change', handler);\n return () => mq.removeEventListener('change', handler);\n }, [mode]);\n\n return isDark;\n}\n\nfunction resolveInitial(mode?: DarkMode): boolean {\n if (mode === 'force') return true;\n if (mode === 'off' || mode === undefined) return false;\n // \"auto\"\n if (typeof window !== 'undefined' && window.matchMedia) {\n return window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n return false;\n}\n","/**\n * useGraph: hook for imperative graph control.\n *\n * Provides a ref to pass to <Graph /> and exposes graph methods\n * (search, zoom, select) for programmatic control of the graph instance.\n */\n\nimport type { GraphInstance } from '@opendata-ai/openchart-vanilla';\nimport { useCallback, useRef } from 'react';\n\nexport interface UseGraphReturn {\n /** Ref to pass to <Graph ref={ref} />. */\n ref: React.RefObject<GraphHandle | null>;\n /** Search for nodes matching a query string. */\n search: (query: string) => void;\n /** Clear the current search. */\n clearSearch: () => void;\n /** Zoom to fit all nodes in view. */\n zoomToFit: () => void;\n /** Zoom and center on a specific node. */\n zoomToNode: (nodeId: string) => void;\n /** Select a node by id. */\n selectNode: (nodeId: string) => void;\n /** Get the currently selected node ids. */\n getSelectedNodes: () => string[];\n}\n\n/** Handle exposed by Graph component via forwardRef. */\nexport interface GraphHandle {\n search: (query: string) => void;\n clearSearch: () => void;\n zoomToFit: () => void;\n zoomToNode: (nodeId: string) => void;\n selectNode: (nodeId: string) => void;\n getSelectedNodes: () => string[];\n /** Re-compile encoding/legend/chrome without restarting the simulation. */\n updateVisuals: (spec: import('@opendata-ai/openchart-core').GraphSpec) => void;\n /** The underlying GraphInstance from the vanilla adapter. */\n instance: GraphInstance | null;\n}\n\n/**\n * Hook for imperative graph control.\n *\n * Usage:\n * ```tsx\n * const { ref, search, zoomToFit } = useGraph();\n * return <Graph ref={ref} spec={spec} />;\n * ```\n */\nexport function useGraph(): UseGraphReturn {\n const ref = useRef<GraphHandle | null>(null);\n\n const search = useCallback((query: string) => {\n ref.current?.search(query);\n }, []);\n\n const clearSearch = useCallback(() => {\n ref.current?.clearSearch();\n }, []);\n\n const zoomToFit = useCallback(() => {\n ref.current?.zoomToFit();\n }, []);\n\n const zoomToNode = useCallback((nodeId: string) => {\n ref.current?.zoomToNode(nodeId);\n }, []);\n\n const selectNode = useCallback((nodeId: string) => {\n ref.current?.selectNode(nodeId);\n }, []);\n\n const getSelectedNodes = useCallback((): string[] => {\n return ref.current?.getSelectedNodes() ?? [];\n }, []);\n\n return {\n ref,\n search,\n clearSearch,\n zoomToFit,\n zoomToNode,\n selectNode,\n getSelectedNodes,\n };\n}\n","/**\n * useTable: hook for manual table lifecycle control.\n *\n * Attaches to a container ref, mounts a vanilla table instance,\n * and exposes the instance and current state.\n */\n\nimport type { TableSpec } from '@opendata-ai/openchart-core';\nimport {\n createTable,\n type TableInstance,\n type TableMountOptions,\n type TableState,\n} from '@opendata-ai/openchart-vanilla';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nexport interface UseTableReturn {\n /** Ref to attach to the container div. */\n ref: React.RefObject<HTMLDivElement | null>;\n /** The table instance (null until mounted). */\n table: TableInstance | null;\n /** The current table state (sort, search, page). */\n state: TableState;\n}\n\n/**\n * Hook for manual table lifecycle control.\n *\n * Attach the returned ref to a container div. The table mounts\n * automatically and updates when the spec changes.\n *\n * @param spec - The table spec.\n * @param options - Mount options.\n * @returns { ref, table, state }\n */\nexport function useTable(spec: TableSpec, options?: TableMountOptions): UseTableReturn {\n const ref = useRef<HTMLDivElement | null>(null);\n const tableRef = useRef<TableInstance | null>(null);\n const [state, setState] = useState<TableState>({\n sort: null,\n search: '',\n page: 0,\n });\n\n const originalOnStateChange = options?.onStateChange;\n\n const handleStateChange = useCallback(\n (newState: TableState) => {\n setState(newState);\n originalOnStateChange?.(newState);\n },\n [originalOnStateChange],\n );\n\n // Mount / unmount\n useEffect(() => {\n const container = ref.current;\n if (!container) return;\n\n const mountOpts: TableMountOptions = {\n ...options,\n onStateChange: handleStateChange,\n };\n\n const table = createTable(container, spec, mountOpts);\n tableRef.current = table;\n setState(table.getState());\n\n return () => {\n table.destroy();\n tableRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n options?.theme,\n options?.darkMode,\n options?.onRowClick,\n options?.responsive,\n handleStateChange,\n options,\n spec,\n ]);\n\n // Update on spec change\n useEffect(() => {\n const table = tableRef.current;\n if (!table) return;\n\n table.update(spec);\n setState(table.getState());\n }, [spec]);\n\n return {\n ref,\n table: tableRef.current,\n state,\n };\n}\n","/**\n * useTableState: managed state hook for controlled table usage.\n *\n * Provides individual sort/search/page state with setters and a\n * resetState function to return to initial values.\n */\n\nimport type { SortState } from '@opendata-ai/openchart-core';\nimport { useCallback, useState } from 'react';\n\nexport interface UseTableStateReturn {\n sort: SortState | null;\n setSort: (sort: SortState | null) => void;\n search: string;\n setSearch: (query: string) => void;\n page: number;\n setPage: (page: number) => void;\n resetState: () => void;\n}\n\nexport interface UseTableStateOptions {\n sort?: SortState | null;\n search?: string;\n page?: number;\n}\n\n/**\n * Hook for managing table state (sort, search, page).\n *\n * Use with the DataTable component's controlled props:\n * ```tsx\n * const { sort, search, page, setSort, setSearch, setPage } = useTableState();\n * <DataTable\n * spec={spec}\n * sort={sort}\n * search={search}\n * page={page}\n * onSortChange={setSort}\n * onSearchChange={setSearch}\n * onPageChange={setPage}\n * />\n * ```\n */\nexport function useTableState(initialState?: UseTableStateOptions): UseTableStateReturn {\n const [sort, setSortInternal] = useState<SortState | null>(initialState?.sort ?? null);\n const [search, setSearchInternal] = useState(initialState?.search ?? '');\n const [page, setPageInternal] = useState(initialState?.page ?? 0);\n\n const setSort = useCallback((newSort: SortState | null) => {\n setSortInternal(newSort);\n }, []);\n\n const setSearch = useCallback((query: string) => {\n setSearchInternal(query);\n }, []);\n\n const setPage = useCallback((newPage: number) => {\n setPageInternal(newPage);\n }, []);\n\n const resetState = useCallback(() => {\n setSortInternal(initialState?.sort ?? null);\n setSearchInternal(initialState?.search ?? '');\n setPageInternal(initialState?.page ?? 0);\n }, [initialState?.sort, initialState?.search, initialState?.page]);\n\n return {\n sort,\n setSort,\n search,\n setSearch,\n page,\n setPage,\n resetState,\n };\n}\n","/**\n * Visualization routing component: renders Chart, DataTable, or Graph\n * based on the spec type. Use this when rendering arbitrary VizSpec values.\n *\n * For event handlers, use the specific component (Chart, DataTable, Graph) directly.\n */\n\nimport type { DarkMode, ThemeConfig, VizSpec } from '@opendata-ai/openchart-core';\nimport { isGraphSpec, isTableSpec } from '@opendata-ai/openchart-core';\nimport type { CSSProperties } from 'react';\nimport { Chart } from './Chart';\nimport { DataTable } from './DataTable';\nimport { Graph } from './Graph';\n\nexport interface VisualizationProps {\n /** The visualization spec to render. */\n spec: VizSpec;\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode: \"auto\", \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** CSS class name for the wrapper div. */\n className?: string;\n /** Inline styles for the wrapper div. */\n style?: CSSProperties;\n}\n\n/**\n * Routes a VizSpec to the appropriate rendering component.\n *\n * Accepts any VizSpec and renders it with the correct component based on the\n * spec's type field. For event handlers, use Chart, DataTable, or Graph directly.\n */\nexport function Visualization({ spec, theme, darkMode, className, style }: VisualizationProps) {\n if (isTableSpec(spec)) {\n return (\n <DataTable\n spec={spec}\n theme={theme}\n darkMode={darkMode}\n className={className}\n style={style}\n />\n );\n }\n if (isGraphSpec(spec)) {\n return (\n <Graph spec={spec} theme={theme} darkMode={darkMode} className={className} style={style} />\n );\n }\n return (\n <Chart spec={spec} theme={theme} darkMode={darkMode} className={className} style={style} />\n );\n}\n"],"mappings":";AAcA,cAAc;AAWd;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAsBP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;AC9CP,SAA6B,mBAAsC;AACnE;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;;;AChBP,SAAS,eAA+B,kBAAkB;AA2BpD;AAzBN,IAAM,kBAAkB,cAAuC,MAAS;AACxE,IAAM,qBAAqB,cAAoC,MAAS;AAGjE,SAAS,cAAuC;AACrD,SAAO,WAAW,eAAe;AACnC;AAGO,SAAS,iBAAuC;AACrD,SAAO,WAAW,kBAAkB;AACtC;AAWO,SAAS,iBAAiB,EAAE,OAAO,UAAU,SAAS,GAA0B;AACrF,SACE,oBAAC,gBAAgB,UAAhB,EAAyB,OAAO,OAC/B,8BAAC,mBAAmB,UAAnB,EAA4B,OAAO,UAAW,UAAS,GAC1D;AAEJ;;;ADwOI,gBAAAA,YAAA;AAhNG,IAAM,QAAQ,WAAoC,SAASC,OAChE;AAAA,EACE;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB;AAAA,EACA;AACF,GACA,KACA;AACA,QAAM,eAAe,YAAY;AACjC,QAAM,kBAAkB,eAAe;AACvC,QAAM,QAAQ,aAAa;AAC3B,QAAM,mBAAmB,YAAY;AACrC,QAAM,eAAe,OAAuB,IAAI;AAChD,QAAM,WAAW,OAA6B,IAAI;AAClD,QAAM,UAAU,OAAe,EAAE;AAKjC,QAAM,cAAc,OAYjB,CAAC,CAAC;AACL,cAAY,UAAU;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,QAAM,yBAAyB;AAAA,IAC7B,CAAC,SAAkC,YAAY,QAAQ,mBAAmB,IAAI;AAAA,IAC9E,CAAC;AAAA,EACH;AACA,QAAM,oBAAoB;AAAA,IACxB,CAAC,UACC,YAAY,QAAQ,cAAc,KAAK;AAAA,IACzC,CAAC;AAAA,EACH;AACA,QAAM,oBAAoB;AAAA,IACxB,CAAC,UACC,YAAY,QAAQ,cAAc,KAAK;AAAA,IACzC,CAAC;AAAA,EACH;AACA,QAAM,oBAAoB,YAAY,MAAM,YAAY,QAAQ,cAAc,GAAG,CAAC,CAAC;AACnF,QAAM,uBAAuB;AAAA,IAC3B,CAAC,QAAgB,YAAqB,YAAY,QAAQ,iBAAiB,QAAQ,OAAO;AAAA,IAC1F,CAAC;AAAA,EACH;AACA,QAAM,0BAA0B;AAAA,IAC9B,CAAC,YAA8D,UAC7D,YAAY,QAAQ,oBAAoB,YAAY,KAAK;AAAA,IAC3D,CAAC;AAAA,EACH;AACA,QAAM,yBAAyB;AAAA,IAC7B,CACE,YACA,kBACG,YAAY,QAAQ,mBAAmB,YAAY,aAAa;AAAA,IACrE,CAAC;AAAA,EACH;AACA,QAAM,eAAe;AAAA,IACnB,CAAC,SAA4D,YAAY,QAAQ,SAAS,IAAI;AAAA,IAC9F,CAAC;AAAA,EACH;AACA,QAAM,iBAAiB;AAAA,IACrB,CAAC,YAAwB,YAAY,QAAQ,WAAW,OAAO;AAAA,IAC/D,CAAC;AAAA,EACH;AACA,QAAM,mBAAmB;AAAA,IACvB,CAAC,YAAwB,YAAY,QAAQ,aAAa,OAAO;AAAA,IACjE,CAAC;AAAA,EACH;AACA,QAAM,mBAAmB;AAAA,IACvB,CAAC,SAAqB,SAAiB,YACrC,YAAY,QAAQ,aAAa,SAAS,SAAS,OAAO;AAAA,IAC5D,CAAC;AAAA,EACH;AAGA;AAAA,IACE;AAAA,IACA,OAAO;AAAA,MACL,qBAAqB;AACnB,eAAO,SAAS,SAAS,mBAAmB,KAAK;AAAA,MACnD;AAAA,MACA,OAAO,YAAwB;AAC7B,iBAAS,SAAS,OAAO,UAAU;AAAA,MACrC;AAAA,MACA,WAAW;AACT,iBAAS,SAAS,SAAS;AAAA,MAC7B;AAAA,MACA,IAAI,WAAW;AACb,eAAO,SAAS;AAAA,MAClB;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EACH;AAKA,YAAU,MAAM;AACd,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAEhB,UAAM,UAAwB;AAAA,MAC5B;AAAA,MACA,UAAU;AAAA,MACV,kBAAkB;AAAA,MAClB,aAAa;AAAA,MACb,aAAa;AAAA,MACb,aAAa;AAAA,MACb,gBAAgB;AAAA,MAChB,mBAAmB;AAAA;AAAA;AAAA;AAAA,MAInB,GAAI,YAAY,QAAQ,mBAAmB,EAAE,kBAAkB,uBAAuB,IAAI,CAAC;AAAA,MAC3F,GAAI,YAAY,QAAQ,SAAS,EAAE,QAAQ,aAAa,IAAI,CAAC;AAAA,MAC7D,GAAI,YAAY,QAAQ,WAAW,EAAE,UAAU,eAAe,IAAI,CAAC;AAAA,MACnE,GAAI,YAAY,QAAQ,aAAa,EAAE,YAAY,iBAAiB,IAAI,CAAC;AAAA,MACzE,GAAI,YAAY,QAAQ,aAAa,EAAE,YAAY,iBAAiB,IAAI,CAAC;AAAA,MACzE,GAAI,sBAAsB,EAAE,iBAAiB,oBAAoB,IAAI,CAAC;AAAA,MACtE,YAAY;AAAA,IACd;AAEA,aAAS,UAAU,YAAY,WAAW,MAAM,OAAO;AACvD,YAAQ,UAAU,KAAK,UAAU,IAAI;AAErC,WAAO,MAAM;AACX,eAAS,SAAS,QAAQ;AAC1B,eAAS,UAAU;AAAA,IACrB;AAAA,EAGF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAGD,YAAU,MAAM;AACd,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,MAAO;AAEZ,UAAM,aAAa,KAAK,UAAU,IAAI;AACtC,QAAI,eAAe,QAAQ,SAAS;AAClC,cAAQ,UAAU;AAClB,YAAM,OAAO,IAAI;AAAA,IACnB;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAGT,YAAU,MAAM;AACd,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,SAAS,CAAC,MAAM,OAAQ;AAE7B,QAAI,qBAAqB;AACvB,YAAM,OAAO,mBAAmB;AAAA,IAClC,WAAW,MAAM,qBAAqB,GAAG;AACvC,YAAM,SAAS;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,mBAAmB,CAAC;AAExB,SACE,gBAAAD;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,YAAY,iBAAiB,SAAS,KAAK;AAAA,MACtD;AAAA;AAAA,EACF;AAEJ,CAAC;;;AE5QD;AAAA,EACE;AAAA,OAGK;AACP,SAA6B,eAAAE,cAAa,aAAAC,YAAW,UAAAC,eAAc;AAgJ/D,gBAAAC,YAAA;AA5GG,SAAS,UAAU;AAAA,EACxB;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAmB;AACjB,QAAM,eAAe,YAAY;AACjC,QAAM,kBAAkB,eAAe;AACvC,QAAM,QAAQ,aAAa;AAC3B,QAAM,mBAAmB,YAAY;AACrC,QAAM,eAAeC,QAAuB,IAAI;AAChD,QAAM,WAAWA,QAA6B,IAAI;AAGlD,QAAM,cAAcA,QAKjB,CAAC,CAAC;AACL,cAAY,UAAU,EAAE,YAAY,cAAc,gBAAgB,aAAa;AAG/E,QAAM,mBAAmBC;AAAA,IACvB,CAAC,QAAiC,YAAY,QAAQ,aAAa,GAAG;AAAA,IACtE,CAAC;AAAA,EACH;AACA,QAAM,sBAAsBA;AAAA,IAC1B,CAAC,UAAuE;AACtE,UAAI,MAAM,SAAS,OAAW,aAAY,QAAQ,eAAe,MAAM,IAAI;AAC3E,UAAI,MAAM,WAAW,OAAW,aAAY,QAAQ,iBAAiB,MAAM,MAAM;AACjF,UAAI,MAAM,SAAS,OAAW,aAAY,QAAQ,eAAe,MAAM,IAAI;AAAA,IAC7E;AAAA,IACA,CAAC;AAAA,EACH;AAEA,QAAM,cAAcD,QAAe,EAAE;AAGrC,QAAM,eAAe,SAAS,UAAa,WAAW,UAAa,SAAS;AAI5E,EAAAE,WAAU,MAAM;AACd,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAEhB,UAAM,eAAkC;AAAA,MACtC;AAAA,MACA,UAAU;AAAA,MACV,YAAY;AAAA,MACZ,YAAY;AAAA,MACZ,eAAe;AAAA,IACjB;AAEA,QAAI,cAAc;AAChB,mBAAa,gBAAgB;AAAA,QAC3B,MAAM,QAAQ;AAAA,QACd,QAAQ,UAAU;AAAA,QAClB,MAAM,QAAQ;AAAA,MAChB;AAAA,IACF;AAEA,aAAS,UAAU,YAAY,WAAW,MAAM,YAAY;AAC5D,gBAAY,UAAU,KAAK,UAAU,IAAI;AAEzC,WAAO,MAAM;AACX,eAAS,SAAS,QAAQ;AAC1B,eAAS,UAAU;AAAA,IACrB;AAAA,EAIF,GAAG,CAAC,OAAO,kBAAkB,cAAc,kBAAkB,mBAAmB,CAAC;AAGjF,EAAAA,WAAU,MAAM;AACd,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,SAAS,CAAC,aAAc;AAE7B,UAAM,SAAS;AAAA,MACb,MAAM,QAAQ;AAAA,MACd,QAAQ,UAAU;AAAA,MAClB,MAAM,QAAQ;AAAA,IAChB,CAAC;AAAA,EACH,GAAG,CAAC,MAAM,QAAQ,MAAM,YAAY,CAAC;AAGrC,EAAAA,WAAU,MAAM;AACd,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,MAAO;AAEZ,UAAM,aAAa,KAAK,UAAU,IAAI;AACtC,QAAI,eAAe,YAAY,SAAS;AACtC,kBAAY,UAAU;AACtB,YAAM,OAAO,IAAI;AAAA,IACnB;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SACE,gBAAAH;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,YAAY,iBAAiB,SAAS,KAAK;AAAA,MACtD;AAAA;AAAA,EACF;AAEJ;;;ACzJA;AAAA,EACE;AAAA,OAGK;AACP;AAAA,EAEE,cAAAI;AAAA,EACA,eAAAC;AAAA,EACA,aAAAC;AAAA,EACA,uBAAAC;AAAA,EACA,UAAAC;AAAA,OACK;AA+NH,gBAAAC,YAAA;AAnLG,IAAM,QAAQC,YAAoC,SAASC,OAChE;AAAA,EACE;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GACA,KACA;AACA,QAAM,eAAe,YAAY;AACjC,QAAM,kBAAkB,eAAe;AACvC,QAAM,QAAQ,aAAa;AAC3B,QAAM,mBAAmB,YAAY;AACrC,QAAM,eAAeC,QAAuB,IAAI;AAChD,QAAM,WAAWA,QAA6B,IAAI;AAClD,QAAM,UAAUA,QAAe,EAAE;AAKjC,QAAM,cAAcA,QAMjB,CAAC,CAAC;AACL,cAAY,UAAU;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAGA,QAAM,oBAAoBC;AAAA,IACxB,CAAC,SAAkC,YAAY,QAAQ,cAAc,IAAI;AAAA,IACzE,CAAC;AAAA,EACH;AACA,QAAM,0BAA0BA;AAAA,IAC9B,CAAC,SAAkC,YAAY,QAAQ,oBAAoB,IAAI;AAAA,IAC/E,CAAC;AAAA,EACH;AACA,QAAM,oBAAoBA;AAAA,IACxB,CAAC,SAAyC,YAAY,QAAQ,cAAc,IAAI;AAAA,IAChF,CAAC;AAAA,EACH;AACA,QAAM,oBAAoBA;AAAA,IACxB,CAAC,SAAyC,YAAY,QAAQ,cAAc,IAAI;AAAA,IAChF,CAAC;AAAA,EACH;AACA,QAAM,0BAA0BA;AAAA,IAC9B,CAAC,YAAsB,YAAY,QAAQ,oBAAoB,OAAO;AAAA,IACtE,CAAC;AAAA,EACH;AAGA,EAAAC;AAAA,IACE;AAAA,IACA,OAAO;AAAA,MACL,OAAO,OAAe;AACpB,iBAAS,SAAS,OAAO,KAAK;AAAA,MAChC;AAAA,MACA,cAAc;AACZ,iBAAS,SAAS,YAAY;AAAA,MAChC;AAAA,MACA,YAAY;AACV,iBAAS,SAAS,UAAU;AAAA,MAC9B;AAAA,MACA,WAAW,QAAgB;AACzB,iBAAS,SAAS,WAAW,MAAM;AAAA,MACrC;AAAA,MACA,WAAW,QAAgB;AACzB,iBAAS,SAAS,WAAW,MAAM;AAAA,MACrC;AAAA,MACA,mBAAmB;AACjB,eAAO,SAAS,SAAS,iBAAiB,KAAK,CAAC;AAAA,MAClD;AAAA,MACA,cAAcC,OAAiB;AAC7B,iBAAS,SAAS,cAAcA,KAAI;AAAA,MACtC;AAAA,MACA,IAAI,WAAW;AACb,eAAO,SAAS;AAAA,MAClB;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EACH;AAKA,EAAAC,WAAU,MAAM;AACd,UAAM,YAAY,aAAa;AAC/B,QAAI,CAAC,UAAW;AAEhB,UAAM,UAA6B;AAAA,MACjC;AAAA,MACA,UAAU;AAAA,MACV;AAAA,MACA;AAAA,MACA,aAAa;AAAA,MACb,mBAAmB;AAAA,MACnB,aAAa;AAAA,MACb,aAAa;AAAA,MACb,mBAAmB;AAAA,MACnB,YAAY;AAAA,IACd;AAEA,aAAS,UAAU,YAAY,WAAW,MAAM,OAAO;AACvD,YAAQ,UAAU,KAAK,UAAU,IAAI;AAErC,WAAO,MAAM;AACX,eAAS,SAAS,QAAQ;AAC1B,eAAS,UAAU;AAAA,IACrB;AAAA,EAGF,GAAG;AAAA,IACD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAKD,EAAAA,WAAU,MAAM;AACd,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,MAAO;AAEZ,UAAM,aAAa,KAAK,UAAU,IAAI;AACtC,QAAI,eAAe,QAAQ,QAAS;AAGpC,UAAM,WAAW,QAAQ;AACzB,YAAQ,UAAU;AAElB,QAAI,UAAU;AACZ,UAAI;AACF,cAAM,OAAO,KAAK,MAAM,QAAQ;AAChC,cAAM,YACJ,KAAK,MAAM,WAAW,KAAK,MAAM,UACjC,KAAK,MAAM,MAAM,CAAC,GAAG,MAAM,EAAE,OAAO,KAAK,MAAM,CAAC,EAAE,EAAE;AACtD,cAAM,YACJ,KAAK,MAAM,WAAW,KAAK,MAAM,UACjC,KAAK,MAAM;AAAA,UACT,CAAC,GAAG,MAAM,EAAE,WAAW,KAAK,MAAM,CAAC,EAAE,UAAU,EAAE,WAAW,KAAK,MAAM,CAAC,EAAE;AAAA,QAC5E;AAEF,cAAM,aAAa,KAAK,QAAQ,YAAY,UAAU,KAAK,QAAQ,YAAY;AAE/E,YAAI,aAAa,aAAa,YAAY;AACxC,gBAAM,cAAc,IAAI;AACxB;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,UAAM,OAAO,IAAI;AAAA,EACnB,GAAG,CAAC,IAAI,CAAC;AAET,SACE,gBAAAP;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,YAAY,iBAAiB,SAAS,KAAK;AAAA,MACtD;AAAA;AAAA,EACF;AAEJ,CAAC;;;AC9OD,SAA6B,eAAAQ,oBAAsC;AACnE,SAAS,aAAAC,YAAW,UAAAC,SAAQ,gBAAgB;AAoCrC,SAAS,SACd,MACA,SACgB;AAChB,QAAM,MAAMA,QAA8B,IAAI;AAC9C,QAAM,WAAWA,QAA6B,IAAI;AAClD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAA6B,IAAI;AAC7D,QAAM,UAAUA,QAAe,EAAE;AAIjC,EAAAD,WAAU,MAAM;AACd,UAAM,YAAY,IAAI;AACtB,QAAI,CAAC,UAAW;AAEhB,UAAM,YAA0B;AAAA,MAC9B,OAAO,SAAS;AAAA,MAChB,UAAU,SAAS;AAAA,MACnB,kBAAkB,SAAS;AAAA,MAC3B,YAAY,SAAS;AAAA,IACvB;AAEA,UAAM,QAAQD,aAAY,WAAW,MAAM,SAAS;AACpD,aAAS,UAAU;AACnB,cAAU,MAAM,MAAM;AACtB,YAAQ,UAAU,KAAK,UAAU,IAAI;AAErC,WAAO,MAAM;AACX,YAAM,QAAQ;AACd,eAAS,UAAU;AACnB,gBAAU,IAAI;AAAA,IAChB;AAAA,EAEF,GAAG,CAAC,SAAS,OAAO,SAAS,UAAU,SAAS,kBAAkB,SAAS,UAAU,CAAC;AAGtF,EAAAC,WAAU,MAAM;AACd,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,MAAO;AAEZ,UAAM,aAAa,KAAK,UAAU,IAAI;AACtC,QAAI,eAAe,QAAQ,SAAS;AAClC,cAAQ,UAAU;AAClB,YAAM,OAAO,IAAI;AACjB,gBAAU,MAAM,MAAM;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,IAAI,CAAC;AAET,SAAO;AAAA,IACL;AAAA,IACA,OAAO,SAAS;AAAA,IAChB;AAAA,EACF;AACF;AAgBO,SAAS,YAAY,MAA0B;AACpD,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,MAAM,eAAe,IAAI,CAAC;AAE/D,EAAAA,WAAU,MAAM;AACd,QAAI,SAAS,QAAQ;AACnB,gBAAU,SAAS,OAAO;AAC1B;AAAA,IACF;AAEA,QAAI,OAAO,WAAW,eAAe,CAAC,OAAO,YAAY;AACvD,gBAAU,KAAK;AACf;AAAA,IACF;AAEA,UAAM,KAAK,OAAO,WAAW,8BAA8B;AAC3D,cAAU,GAAG,OAAO;AAEpB,UAAM,UAAU,CAAC,MAA2B,UAAU,EAAE,OAAO;AAC/D,OAAG,iBAAiB,UAAU,OAAO;AACrC,WAAO,MAAM,GAAG,oBAAoB,UAAU,OAAO;AAAA,EACvD,GAAG,CAAC,IAAI,CAAC;AAET,SAAO;AACT;AAEA,SAAS,eAAe,MAA0B;AAChD,MAAI,SAAS,QAAS,QAAO;AAC7B,MAAI,SAAS,SAAS,SAAS,OAAW,QAAO;AAEjD,MAAI,OAAO,WAAW,eAAe,OAAO,YAAY;AACtD,WAAO,OAAO,WAAW,8BAA8B,EAAE;AAAA,EAC3D;AACA,SAAO;AACT;;;ACjJA,SAAS,eAAAE,cAAa,UAAAC,eAAc;AA0C7B,SAAS,WAA2B;AACzC,QAAM,MAAMA,QAA2B,IAAI;AAE3C,QAAM,SAASD,aAAY,CAAC,UAAkB;AAC5C,QAAI,SAAS,OAAO,KAAK;AAAA,EAC3B,GAAG,CAAC,CAAC;AAEL,QAAM,cAAcA,aAAY,MAAM;AACpC,QAAI,SAAS,YAAY;AAAA,EAC3B,GAAG,CAAC,CAAC;AAEL,QAAM,YAAYA,aAAY,MAAM;AAClC,QAAI,SAAS,UAAU;AAAA,EACzB,GAAG,CAAC,CAAC;AAEL,QAAM,aAAaA,aAAY,CAAC,WAAmB;AACjD,QAAI,SAAS,WAAW,MAAM;AAAA,EAChC,GAAG,CAAC,CAAC;AAEL,QAAM,aAAaA,aAAY,CAAC,WAAmB;AACjD,QAAI,SAAS,WAAW,MAAM;AAAA,EAChC,GAAG,CAAC,CAAC;AAEL,QAAM,mBAAmBA,aAAY,MAAgB;AACnD,WAAO,IAAI,SAAS,iBAAiB,KAAK,CAAC;AAAA,EAC7C,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AC9EA;AAAA,EACE,eAAAE;AAAA,OAIK;AACP,SAAS,eAAAC,cAAa,aAAAC,YAAW,UAAAC,SAAQ,YAAAC,iBAAgB;AAqBlD,SAAS,SAAS,MAAiB,SAA6C;AACrF,QAAM,MAAMD,QAA8B,IAAI;AAC9C,QAAM,WAAWA,QAA6B,IAAI;AAClD,QAAM,CAAC,OAAO,QAAQ,IAAIC,UAAqB;AAAA,IAC7C,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,MAAM;AAAA,EACR,CAAC;AAED,QAAM,wBAAwB,SAAS;AAEvC,QAAM,oBAAoBH;AAAA,IACxB,CAAC,aAAyB;AACxB,eAAS,QAAQ;AACjB,8BAAwB,QAAQ;AAAA,IAClC;AAAA,IACA,CAAC,qBAAqB;AAAA,EACxB;AAGA,EAAAC,WAAU,MAAM;AACd,UAAM,YAAY,IAAI;AACtB,QAAI,CAAC,UAAW;AAEhB,UAAM,YAA+B;AAAA,MACnC,GAAG;AAAA,MACH,eAAe;AAAA,IACjB;AAEA,UAAM,QAAQF,aAAY,WAAW,MAAM,SAAS;AACpD,aAAS,UAAU;AACnB,aAAS,MAAM,SAAS,CAAC;AAEzB,WAAO,MAAM;AACX,YAAM,QAAQ;AACd,eAAS,UAAU;AAAA,IACrB;AAAA,EAEF,GAAG;AAAA,IACD,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAGD,EAAAE,WAAU,MAAM;AACd,UAAM,QAAQ,SAAS;AACvB,QAAI,CAAC,MAAO;AAEZ,UAAM,OAAO,IAAI;AACjB,aAAS,MAAM,SAAS,CAAC;AAAA,EAC3B,GAAG,CAAC,IAAI,CAAC;AAET,SAAO;AAAA,IACL;AAAA,IACA,OAAO,SAAS;AAAA,IAChB;AAAA,EACF;AACF;;;ACzFA,SAAS,eAAAG,cAAa,YAAAC,iBAAgB;AAmC/B,SAAS,cAAc,cAA0D;AACtF,QAAM,CAAC,MAAM,eAAe,IAAIA,UAA2B,cAAc,QAAQ,IAAI;AACrF,QAAM,CAAC,QAAQ,iBAAiB,IAAIA,UAAS,cAAc,UAAU,EAAE;AACvE,QAAM,CAAC,MAAM,eAAe,IAAIA,UAAS,cAAc,QAAQ,CAAC;AAEhE,QAAM,UAAUD,aAAY,CAAC,YAA8B;AACzD,oBAAgB,OAAO;AAAA,EACzB,GAAG,CAAC,CAAC;AAEL,QAAM,YAAYA,aAAY,CAAC,UAAkB;AAC/C,sBAAkB,KAAK;AAAA,EACzB,GAAG,CAAC,CAAC;AAEL,QAAM,UAAUA,aAAY,CAAC,YAAoB;AAC/C,oBAAgB,OAAO;AAAA,EACzB,GAAG,CAAC,CAAC;AAEL,QAAM,aAAaA,aAAY,MAAM;AACnC,oBAAgB,cAAc,QAAQ,IAAI;AAC1C,sBAAkB,cAAc,UAAU,EAAE;AAC5C,oBAAgB,cAAc,QAAQ,CAAC;AAAA,EACzC,GAAG,CAAC,cAAc,MAAM,cAAc,QAAQ,cAAc,IAAI,CAAC;AAEjE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACnEA,SAAS,aAAa,mBAAmB;AA4BnC,gBAAAE,YAAA;AAHC,SAAS,cAAc,EAAE,MAAM,OAAO,UAAU,WAAW,MAAM,GAAuB;AAC7F,MAAI,YAAY,IAAI,GAAG;AACrB,WACE,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF;AAAA,EAEJ;AACA,MAAI,YAAY,IAAI,GAAG;AACrB,WACE,gBAAAA,KAAC,SAAM,MAAY,OAAc,UAAoB,WAAsB,OAAc;AAAA,EAE7F;AACA,SACE,gBAAAA,KAAC,SAAM,MAAY,OAAc,UAAoB,WAAsB,OAAc;AAE7F;","names":["jsx","Chart","useCallback","useEffect","useRef","jsx","useRef","useCallback","useEffect","forwardRef","useCallback","useEffect","useImperativeHandle","useRef","jsx","forwardRef","Graph","useRef","useCallback","useImperativeHandle","spec","useEffect","createChart","useEffect","useRef","useCallback","useRef","createTable","useCallback","useEffect","useRef","useState","useCallback","useState","jsx"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/Chart.tsx","../src/ThemeContext.tsx","../src/DataTable.tsx","../src/Graph.tsx","../src/hooks.ts","../src/hooks/useGraph.ts","../src/hooks/useTable.ts","../src/hooks/useTableState.ts","../src/Sankey.tsx","../src/Visualization.tsx"],"sourcesContent":["/**\n * @opendata-ai/openchart-react\n *\n * React adapter for openchart. Provides <Chart /> and <DataTable />\n * components that wrap the vanilla adapter with React lifecycle management.\n *\n * Re-exports the full core type system and utilities so React consumers\n * only need a single @opendata-ai/openchart-react dependency.\n */\n\n// ---------------------------------------------------------------------------\n// Core: full type system, theme, colors, locale, accessibility, helpers\n// ---------------------------------------------------------------------------\n\nexport * from '@opendata-ai/openchart-core';\n\n// ---------------------------------------------------------------------------\n// Vanilla: export utilities (SVG/PNG/JPG/CSV)\n// ---------------------------------------------------------------------------\n\nexport type {\n JPGExportOptions,\n PNGExportOptions,\n SVGExportOptions,\n} from '@opendata-ai/openchart-vanilla';\nexport {\n exportCSV,\n exportJPG,\n exportPNG,\n exportSVG,\n exportSVGWithFonts,\n} from '@opendata-ai/openchart-vanilla';\n\n// ---------------------------------------------------------------------------\n// Engine: compile API and types not covered by core\n// ---------------------------------------------------------------------------\n\nexport type {\n ChartRenderer,\n CompiledGraphEdge,\n CompiledGraphNode,\n CompileResult,\n GraphCompilation,\n NormalizedChartSpec,\n NormalizedChrome,\n NormalizedGraphSpec,\n NormalizedSankeySpec,\n NormalizedSpec,\n NormalizedTableSpec,\n SimulationConfig,\n ValidationError,\n ValidationErrorCode,\n ValidationResult,\n} from '@opendata-ai/openchart-engine';\nexport {\n clearRenderers,\n compile,\n compileChart,\n compileGraph,\n compileSankey,\n compileTable,\n getChartRenderer,\n normalizeSpec,\n registerChartRenderer,\n validateSpec,\n} from '@opendata-ai/openchart-engine';\n\nexport type { ChartHandle, ChartProps } from './Chart';\n// Components\nexport { Chart } from './Chart';\nexport type { DataTableProps } from './DataTable';\nexport { DataTable } from './DataTable';\nexport type { GraphProps } from './Graph';\nexport { Graph } from './Graph';\nexport type { UseChartOptions, UseChartReturn } from './hooks';\n// Hooks\nexport { useChart, useDarkMode } from './hooks';\nexport type { GraphHandle, UseGraphReturn } from './hooks/useGraph';\nexport { useGraph } from './hooks/useGraph';\nexport type { UseTableReturn } from './hooks/useTable';\nexport { useTable } from './hooks/useTable';\nexport type { UseTableStateOptions, UseTableStateReturn } from './hooks/useTableState';\nexport { useTableState } from './hooks/useTableState';\nexport type { SankeyHandle, SankeyProps } from './Sankey';\nexport { Sankey } from './Sankey';\nexport type { VizThemeProviderProps } from './ThemeContext';\n// Theme context\nexport { useVizDarkMode, useVizTheme, VizThemeProvider } from './ThemeContext';\nexport type { VisualizationProps } from './Visualization';\nexport { Visualization } from './Visualization';\n","/**\n * React Chart component: thin wrapper around the vanilla adapter.\n *\n * Mounts a chart instance on render, updates when spec changes,\n * and cleans up on unmount. All heavy lifting is done by the vanilla\n * createChart() function.\n */\n\nimport type {\n ChartEventHandlers,\n ChartSpec,\n DarkMode,\n ElementRef,\n GraphSpec,\n LayerSpec,\n ThemeConfig,\n} from '@opendata-ai/openchart-core';\nimport { type ChartInstance, createChart, type MountOptions } from '@opendata-ai/openchart-vanilla';\nimport {\n type CSSProperties,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n} from 'react';\nimport { useVizDarkMode, useVizTheme } from './ThemeContext';\n\nexport interface ChartHandle {\n /** Get the currently selected element, or null if none. */\n getSelectedElement(): ElementRef | null;\n /** Programmatically select an element. */\n select(ref: ElementRef): void;\n /** Deselect the current element. */\n deselect(): void;\n /** The underlying chart instance (null until mounted). */\n readonly instance: ChartInstance | null;\n}\n\nexport interface ChartProps extends ChartEventHandlers {\n /** The visualization spec to render. */\n spec: ChartSpec | LayerSpec | GraphSpec;\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode: \"auto\", \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** Callback when a data point is clicked. @deprecated Use onMarkClick instead. */\n onDataPointClick?: (data: Record<string, unknown>) => void;\n /** The currently selected element (controlled). */\n selectedElement?: ElementRef;\n /** CSS class name for the wrapper div. */\n className?: string;\n /** Inline styles for the wrapper div. */\n style?: CSSProperties;\n}\n\n/**\n * React component that renders a visualization from a spec.\n *\n * Uses the vanilla adapter internally. The spec is compiled and rendered\n * as SVG inside a wrapper div. Spec changes trigger re-renders via the\n * vanilla adapter's update() method.\n */\nexport const Chart = forwardRef<ChartHandle, ChartProps>(function Chart(\n {\n spec,\n theme: themeProp,\n darkMode,\n onDataPointClick,\n onMarkClick,\n onMarkHover,\n onMarkLeave,\n onLegendToggle,\n onAnnotationClick,\n onAnnotationEdit,\n onEdit,\n onSelect,\n onDeselect,\n onTextEdit,\n selectedElement: selectedElementProp,\n className,\n style,\n },\n ref,\n) {\n const contextTheme = useVizTheme();\n const contextDarkMode = useVizDarkMode();\n const theme = themeProp ?? contextTheme;\n const resolvedDarkMode = darkMode ?? contextDarkMode;\n const containerRef = useRef<HTMLDivElement>(null);\n const chartRef = useRef<ChartInstance | null>(null);\n const specRef = useRef<string>('');\n\n // Store event handlers in refs so they don't trigger chart recreation.\n // Inline arrow functions create new references every render, which would\n // destroy and recreate the entire chart instance without this pattern.\n const handlersRef = useRef<{\n onDataPointClick?: ChartProps['onDataPointClick'];\n onMarkClick?: ChartProps['onMarkClick'];\n onMarkHover?: ChartProps['onMarkHover'];\n onMarkLeave?: ChartProps['onMarkLeave'];\n onLegendToggle?: ChartProps['onLegendToggle'];\n onAnnotationClick?: ChartProps['onAnnotationClick'];\n onAnnotationEdit?: ChartProps['onAnnotationEdit'];\n onEdit?: ChartProps['onEdit'];\n onSelect?: ChartProps['onSelect'];\n onDeselect?: ChartProps['onDeselect'];\n onTextEdit?: ChartProps['onTextEdit'];\n }>({});\n handlersRef.current = {\n onDataPointClick,\n onMarkClick,\n onMarkHover,\n onMarkLeave,\n onLegendToggle,\n onAnnotationClick,\n onAnnotationEdit,\n onEdit,\n onSelect,\n onDeselect,\n onTextEdit,\n };\n\n // Stable callback wrappers that read from refs\n const stableOnDataPointClick = useCallback(\n (data: Record<string, unknown>) => handlersRef.current.onDataPointClick?.(data),\n [],\n );\n const stableOnMarkClick = useCallback(\n (event: import('@opendata-ai/openchart-core').MarkEvent) =>\n handlersRef.current.onMarkClick?.(event),\n [],\n );\n const stableOnMarkHover = useCallback(\n (event: import('@opendata-ai/openchart-core').MarkEvent) =>\n handlersRef.current.onMarkHover?.(event),\n [],\n );\n const stableOnMarkLeave = useCallback(() => handlersRef.current.onMarkLeave?.(), []);\n const stableOnLegendToggle = useCallback(\n (series: string, visible: boolean) => handlersRef.current.onLegendToggle?.(series, visible),\n [],\n );\n const stableOnAnnotationClick = useCallback(\n (annotation: import('@opendata-ai/openchart-core').Annotation, event: MouseEvent) =>\n handlersRef.current.onAnnotationClick?.(annotation, event),\n [],\n );\n const stableOnAnnotationEdit = useCallback(\n (\n annotation: import('@opendata-ai/openchart-core').TextAnnotation,\n updatedOffset: import('@opendata-ai/openchart-core').AnnotationOffset,\n ) => handlersRef.current.onAnnotationEdit?.(annotation, updatedOffset),\n [],\n );\n const stableOnEdit = useCallback(\n (edit: import('@opendata-ai/openchart-core').ElementEdit) => handlersRef.current.onEdit?.(edit),\n [],\n );\n const stableOnSelect = useCallback(\n (element: ElementRef) => handlersRef.current.onSelect?.(element),\n [],\n );\n const stableOnDeselect = useCallback(\n (element: ElementRef) => handlersRef.current.onDeselect?.(element),\n [],\n );\n const stableOnTextEdit = useCallback(\n (element: ElementRef, oldText: string, newText: string) =>\n handlersRef.current.onTextEdit?.(element, oldText, newText),\n [],\n );\n\n // Expose imperative handle for ref-based control\n useImperativeHandle(\n ref,\n () => ({\n getSelectedElement() {\n return chartRef.current?.getSelectedElement() ?? null;\n },\n select(elementRef: ElementRef) {\n chartRef.current?.select(elementRef);\n },\n deselect() {\n chartRef.current?.deselect();\n },\n get instance() {\n return chartRef.current;\n },\n }),\n [],\n );\n\n // Mount chart and recreate when theme/darkMode change.\n // Event handlers use stable refs so they don't trigger recreation.\n // biome-ignore lint/correctness/useExhaustiveDependencies: spec intentionally excluded - spec changes handled via update() in Effect 2\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const options: MountOptions = {\n theme,\n darkMode: resolvedDarkMode,\n onDataPointClick: stableOnDataPointClick,\n onMarkClick: stableOnMarkClick,\n onMarkHover: stableOnMarkHover,\n onMarkLeave: stableOnMarkLeave,\n onLegendToggle: stableOnLegendToggle,\n onAnnotationClick: stableOnAnnotationClick,\n // Only include editing callbacks when the consumer provides them.\n // The stable wrappers are always truthy, so gating on handlersRef\n // avoids adding unstable prop references to the effect deps.\n ...(handlersRef.current.onAnnotationEdit ? { onAnnotationEdit: stableOnAnnotationEdit } : {}),\n ...(handlersRef.current.onEdit ? { onEdit: stableOnEdit } : {}),\n ...(handlersRef.current.onSelect ? { onSelect: stableOnSelect } : {}),\n ...(handlersRef.current.onDeselect ? { onDeselect: stableOnDeselect } : {}),\n ...(handlersRef.current.onTextEdit ? { onTextEdit: stableOnTextEdit } : {}),\n ...(selectedElementProp ? { selectedElement: selectedElementProp } : {}),\n responsive: true,\n };\n\n chartRef.current = createChart(container, spec, options);\n specRef.current = JSON.stringify(spec);\n\n return () => {\n chartRef.current?.destroy();\n chartRef.current = null;\n };\n // Only recreate when theme or darkMode change. Event handlers use stable refs.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n theme,\n resolvedDarkMode,\n stableOnAnnotationClick,\n stableOnDataPointClick,\n stableOnEdit,\n stableOnLegendToggle,\n stableOnMarkClick,\n stableOnMarkHover,\n stableOnMarkLeave,\n stableOnAnnotationEdit,\n stableOnSelect,\n stableOnDeselect,\n stableOnTextEdit,\n ]);\n\n // Update chart when spec changes\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart) return;\n\n const specString = JSON.stringify(spec);\n if (specString !== specRef.current) {\n specRef.current = specString;\n chart.update(spec);\n }\n }, [spec]);\n\n // Handle selectedElement prop changes separately (like Vue/Svelte adapters)\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart || !chart.select) return;\n\n if (selectedElementProp) {\n chart.select(selectedElementProp);\n } else if (chart.getSelectedElement?.()) {\n chart.deselect();\n }\n }, [selectedElementProp]);\n\n return (\n <div\n ref={containerRef}\n className={className ? `oc-chart-root ${className}` : 'oc-chart-root'}\n style={style}\n />\n );\n});\n","/**\n * Theme context: provides a theme and dark mode preference to all\n * descendant Chart, DataTable, and Graph components without prop drilling.\n *\n * Components use the context values as fallbacks when no explicit\n * `theme` or `darkMode` prop is passed.\n */\n\nimport type { DarkMode, ThemeConfig } from '@opendata-ai/openchart-core';\nimport { createContext, type ReactNode, useContext } from 'react';\n\nconst VizThemeContext = createContext<ThemeConfig | undefined>(undefined);\nconst VizDarkModeContext = createContext<DarkMode | undefined>(undefined);\n\n/** Read the current theme from the nearest VizThemeProvider. */\nexport function useVizTheme(): ThemeConfig | undefined {\n return useContext(VizThemeContext);\n}\n\n/** Read the current dark mode preference from the nearest VizThemeProvider. */\nexport function useVizDarkMode(): DarkMode | undefined {\n return useContext(VizDarkModeContext);\n}\n\nexport interface VizThemeProviderProps {\n /** Theme config to provide to descendant viz components. */\n theme: ThemeConfig | undefined;\n /** Dark mode preference to provide to descendant viz components. */\n darkMode?: DarkMode;\n children: ReactNode;\n}\n\n/** Provides a theme and dark mode preference to all nested Chart, DataTable, and Graph components. */\nexport function VizThemeProvider({ theme, darkMode, children }: VizThemeProviderProps) {\n return (\n <VizThemeContext.Provider value={theme}>\n <VizDarkModeContext.Provider value={darkMode}>{children}</VizDarkModeContext.Provider>\n </VizThemeContext.Provider>\n );\n}\n","/**\n * DataTable component: React wrapper around the vanilla table adapter.\n *\n * Mounts a table instance on render, updates when spec changes,\n * and cleans up on unmount. Supports both controlled and uncontrolled modes\n * for sort, search, and pagination state.\n */\n\nimport type { DarkMode, SortState, TableSpec, ThemeConfig } from '@opendata-ai/openchart-core';\nimport {\n createTable,\n type TableInstance,\n type TableMountOptions,\n} from '@opendata-ai/openchart-vanilla';\nimport { type CSSProperties, useCallback, useEffect, useRef } from 'react';\nimport { useVizDarkMode, useVizTheme } from './ThemeContext';\n\nexport interface DataTableProps {\n /** The table spec to render. */\n spec: TableSpec;\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode: \"auto\", \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** Row click handler. */\n onRowClick?: (row: Record<string, unknown>) => void;\n /** Callback when sort changes. */\n onSortChange?: (sort: SortState | null) => void;\n /** Callback when search changes. */\n onSearchChange?: (query: string) => void;\n /** Callback when page changes. */\n onPageChange?: (page: number) => void;\n /** CSS class name for the wrapper div. */\n className?: string;\n /** Inline styles for the wrapper div. */\n style?: CSSProperties;\n /** Controlled sort state. */\n sort?: SortState | null;\n /** Controlled search query. */\n search?: string;\n /** Controlled page number. */\n page?: number;\n}\n\n/**\n * React component that renders a data table from a TableSpec.\n *\n * Uses the vanilla adapter internally. Supports controlled and uncontrolled\n * modes for sort, search, and pagination state.\n */\nexport function DataTable({\n spec,\n theme: themeProp,\n darkMode,\n onRowClick,\n onSortChange,\n onSearchChange,\n onPageChange,\n className,\n style,\n sort,\n search,\n page,\n}: DataTableProps) {\n const contextTheme = useVizTheme();\n const contextDarkMode = useVizDarkMode();\n const theme = themeProp ?? contextTheme;\n const resolvedDarkMode = darkMode ?? contextDarkMode;\n const containerRef = useRef<HTMLDivElement>(null);\n const tableRef = useRef<TableInstance | null>(null);\n\n // Store event handlers in refs so they don't trigger table recreation.\n const handlersRef = useRef<{\n onRowClick?: DataTableProps['onRowClick'];\n onSortChange?: DataTableProps['onSortChange'];\n onSearchChange?: DataTableProps['onSearchChange'];\n onPageChange?: DataTableProps['onPageChange'];\n }>({});\n handlersRef.current = { onRowClick, onSortChange, onSearchChange, onPageChange };\n\n // Stable callback wrappers that read from refs\n const stableOnRowClick = useCallback(\n (row: Record<string, unknown>) => handlersRef.current.onRowClick?.(row),\n [],\n );\n const stableOnStateChange = useCallback(\n (state: { sort?: SortState | null; search?: string; page?: number }) => {\n if (state.sort !== undefined) handlersRef.current.onSortChange?.(state.sort);\n if (state.search !== undefined) handlersRef.current.onSearchChange?.(state.search);\n if (state.page !== undefined) handlersRef.current.onPageChange?.(state.page);\n },\n [],\n );\n\n const prevSpecRef = useRef<string>('');\n\n // Determine if we're in controlled mode\n const isControlled = sort !== undefined || search !== undefined || page !== undefined;\n\n // Effect 1: Mount/unmount. Only recreate when structural options change.\n // biome-ignore lint/correctness/useExhaustiveDependencies: spec, sort, search, page intentionally excluded - handled via update()/setState() in Effects 2-3\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const mountOptions: TableMountOptions = {\n theme,\n darkMode: resolvedDarkMode,\n onRowClick: stableOnRowClick,\n responsive: true,\n onStateChange: stableOnStateChange,\n };\n\n if (isControlled) {\n mountOptions.externalState = {\n sort: sort ?? null,\n search: search ?? '',\n page: page ?? 0,\n };\n }\n\n tableRef.current = createTable(container, spec, mountOptions);\n prevSpecRef.current = JSON.stringify(spec);\n\n return () => {\n tableRef.current?.destroy();\n tableRef.current = null;\n };\n // Only recreate on structural option changes (theme, darkMode, onRowClick).\n // Controlled state updates are handled in Effect 2.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [theme, resolvedDarkMode, isControlled, stableOnRowClick, stableOnStateChange]);\n\n // Effect 2: Sync controlled state without remounting.\n useEffect(() => {\n const table = tableRef.current;\n if (!table || !isControlled) return;\n\n table.setState({\n sort: sort ?? null,\n search: search ?? '',\n page: page ?? 0,\n });\n }, [sort, search, page, isControlled]);\n\n // Effect 3: Sync spec changes via update().\n useEffect(() => {\n const table = tableRef.current;\n if (!table) return;\n\n const specString = JSON.stringify(spec);\n if (specString !== prevSpecRef.current) {\n prevSpecRef.current = specString;\n table.update(spec);\n }\n }, [spec]);\n\n return (\n <div\n ref={containerRef}\n className={className ? `oc-table-root ${className}` : 'oc-table-root'}\n style={style}\n />\n );\n}\n","/**\n * React Graph component: thin wrapper around the vanilla adapter.\n *\n * Mounts a graph instance on render, updates when spec changes,\n * and cleans up on unmount. All heavy lifting is done by the vanilla\n * createGraph() function.\n *\n * Supports forwardRef for imperative control via useGraph() hook.\n */\n\nimport type { DarkMode, GraphSpec, ThemeConfig } from '@opendata-ai/openchart-core';\nimport {\n createGraph,\n type GraphInstance,\n type GraphMountOptions,\n} from '@opendata-ai/openchart-vanilla';\nimport {\n type CSSProperties,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n} from 'react';\nimport type { GraphHandle } from './hooks/useGraph';\nimport { useVizDarkMode, useVizTheme } from './ThemeContext';\n\nexport interface GraphProps {\n /** The graph spec to render. */\n spec: GraphSpec;\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode: \"auto\", \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** Callback when a node is clicked. */\n onNodeClick?: (node: Record<string, unknown>) => void;\n /** Callback when a node is double-clicked. */\n onNodeDoubleClick?: (node: Record<string, unknown>) => void;\n /** Callback when a node is hovered (null when hover ends). */\n onNodeHover?: (node: Record<string, unknown> | null) => void;\n /** Callback when an edge is hovered (null when hover ends). */\n onEdgeHover?: (edge: Record<string, unknown> | null) => void;\n /** Callback when selection changes. */\n onSelectionChange?: (nodeIds: string[]) => void;\n /** Show built-in tooltip on node/edge hover. Defaults to true. */\n tooltip?: boolean;\n /** Show built-in legend. Defaults to true. */\n legend?: boolean;\n /** CSS class name for the wrapper div. */\n className?: string;\n /** Inline styles for the wrapper div. */\n style?: CSSProperties;\n}\n\n/**\n * React component that renders a force-directed graph from a GraphSpec.\n *\n * Uses the vanilla adapter internally. The spec is compiled and rendered\n * on a canvas inside a wrapper div. Spec changes trigger re-renders via the\n * vanilla adapter's update() method.\n *\n * Supports ref for imperative control via useGraph() hook:\n * ```tsx\n * const { ref, search, zoomToFit } = useGraph();\n * return <Graph ref={ref} spec={spec} />;\n * ```\n */\nexport const Graph = forwardRef<GraphHandle, GraphProps>(function Graph(\n {\n spec,\n theme: themeProp,\n darkMode,\n onNodeClick,\n onNodeDoubleClick,\n onNodeHover,\n onEdgeHover,\n onSelectionChange,\n tooltip,\n legend,\n className,\n style,\n },\n ref,\n) {\n const contextTheme = useVizTheme();\n const contextDarkMode = useVizDarkMode();\n const theme = themeProp ?? contextTheme;\n const resolvedDarkMode = darkMode ?? contextDarkMode;\n const containerRef = useRef<HTMLDivElement>(null);\n const graphRef = useRef<GraphInstance | null>(null);\n const specRef = useRef<string>('');\n\n // Store event handlers in refs so they don't trigger graph recreation.\n // Inline arrow functions create new references every render, which would\n // destroy and recreate the entire graph instance without this pattern.\n const handlersRef = useRef<{\n onNodeClick?: GraphProps['onNodeClick'];\n onNodeDoubleClick?: GraphProps['onNodeDoubleClick'];\n onNodeHover?: GraphProps['onNodeHover'];\n onEdgeHover?: GraphProps['onEdgeHover'];\n onSelectionChange?: GraphProps['onSelectionChange'];\n }>({});\n handlersRef.current = {\n onNodeClick,\n onNodeDoubleClick,\n onNodeHover,\n onEdgeHover,\n onSelectionChange,\n };\n\n // Stable callback wrappers that read from refs\n const stableOnNodeClick = useCallback(\n (node: Record<string, unknown>) => handlersRef.current.onNodeClick?.(node),\n [],\n );\n const stableOnNodeDoubleClick = useCallback(\n (node: Record<string, unknown>) => handlersRef.current.onNodeDoubleClick?.(node),\n [],\n );\n const stableOnNodeHover = useCallback(\n (node: Record<string, unknown> | null) => handlersRef.current.onNodeHover?.(node),\n [],\n );\n const stableOnEdgeHover = useCallback(\n (edge: Record<string, unknown> | null) => handlersRef.current.onEdgeHover?.(edge),\n [],\n );\n const stableOnSelectionChange = useCallback(\n (nodeIds: string[]) => handlersRef.current.onSelectionChange?.(nodeIds),\n [],\n );\n\n // Expose imperative handle for useGraph() hook\n useImperativeHandle(\n ref,\n () => ({\n search(query: string) {\n graphRef.current?.search(query);\n },\n clearSearch() {\n graphRef.current?.clearSearch();\n },\n zoomToFit() {\n graphRef.current?.zoomToFit();\n },\n zoomToNode(nodeId: string) {\n graphRef.current?.zoomToNode(nodeId);\n },\n selectNode(nodeId: string) {\n graphRef.current?.selectNode(nodeId);\n },\n getSelectedNodes() {\n return graphRef.current?.getSelectedNodes() ?? [];\n },\n updateVisuals(spec: GraphSpec) {\n graphRef.current?.updateVisuals(spec);\n },\n get instance() {\n return graphRef.current;\n },\n }),\n [],\n );\n\n // Mount graph and recreate when theme/darkMode change.\n // Event handlers use stable refs so they don't trigger recreation.\n // biome-ignore lint/correctness/useExhaustiveDependencies: spec intentionally excluded - spec changes handled via update() in Effect 2\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const options: GraphMountOptions = {\n theme,\n darkMode: resolvedDarkMode,\n tooltip,\n legend,\n onNodeClick: stableOnNodeClick,\n onNodeDoubleClick: stableOnNodeDoubleClick,\n onNodeHover: stableOnNodeHover,\n onEdgeHover: stableOnEdgeHover,\n onSelectionChange: stableOnSelectionChange,\n responsive: true,\n };\n\n graphRef.current = createGraph(container, spec, options);\n specRef.current = JSON.stringify(spec);\n\n return () => {\n graphRef.current?.destroy();\n graphRef.current = null;\n };\n // Only recreate when theme or darkMode change. Event handlers use stable refs.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n theme,\n resolvedDarkMode,\n tooltip,\n legend,\n stableOnNodeClick,\n stableOnNodeDoubleClick,\n stableOnNodeHover,\n stableOnEdgeHover,\n stableOnSelectionChange,\n ]);\n\n // Update graph when spec changes.\n // If only encoding/chrome/nodeOverrides changed (same node/edge IDs), use\n // updateVisuals() to avoid restarting the simulation.\n useEffect(() => {\n const graph = graphRef.current;\n if (!graph) return;\n\n const specString = JSON.stringify(spec);\n if (specString === specRef.current) return;\n\n // Check if this is a visual-only change (same node/edge IDs)\n const prevSpec = specRef.current;\n specRef.current = specString;\n\n if (prevSpec) {\n try {\n const prev = JSON.parse(prevSpec) as GraphSpec;\n const sameNodes =\n prev.nodes.length === spec.nodes.length &&\n prev.nodes.every((n, i) => n.id === spec.nodes[i].id);\n const sameEdges =\n prev.edges.length === spec.edges.length &&\n prev.edges.every(\n (e, i) => e.source === spec.edges[i].source && e.target === spec.edges[i].target,\n );\n\n const sameLayout = prev.layout?.clustering?.field === spec.layout?.clustering?.field;\n\n if (sameNodes && sameEdges && sameLayout) {\n graph.updateVisuals(spec);\n return;\n }\n } catch {\n // Parse failed, fall through to full update\n }\n }\n\n graph.update(spec);\n }, [spec]);\n\n return (\n <div\n ref={containerRef}\n className={className ? `oc-graph-root ${className}` : 'oc-graph-root'}\n style={style}\n />\n );\n});\n","/**\n * React hooks for chart lifecycle and dark mode resolution.\n *\n * useChart: manual control over a chart instance (for advanced usage).\n * useDarkMode: resolves the DarkMode preference to a boolean.\n */\n\nimport type {\n ChartLayout,\n ChartSpec,\n DarkMode,\n GraphSpec,\n LayerSpec,\n} from '@opendata-ai/openchart-core';\nimport { type ChartInstance, createChart, type MountOptions } from '@opendata-ai/openchart-vanilla';\nimport { useEffect, useRef, useState } from 'react';\n\n// ---------------------------------------------------------------------------\n// useChart\n// ---------------------------------------------------------------------------\n\nexport interface UseChartOptions {\n /** Theme overrides. */\n theme?: MountOptions['theme'];\n /** Dark mode setting. */\n darkMode?: MountOptions['darkMode'];\n /** Data point click handler. */\n onDataPointClick?: MountOptions['onDataPointClick'];\n /** Enable responsive resizing. Defaults to true. */\n responsive?: boolean;\n}\n\nexport interface UseChartReturn {\n /** Ref to attach to the container div. */\n ref: React.RefObject<HTMLDivElement | null>;\n /** The chart instance (null until mounted). */\n chart: ChartInstance | null;\n /** The current compiled layout (null until mounted). */\n layout: ChartLayout | null;\n}\n\n/**\n * Hook for manual chart lifecycle control.\n *\n * Attach the returned ref to a container div. The chart mounts\n * automatically and updates when the spec changes.\n *\n * @param spec - The visualization spec.\n * @param options - Mount options.\n * @returns { ref, chart, layout }\n */\nexport function useChart(\n spec: ChartSpec | LayerSpec | GraphSpec,\n options?: UseChartOptions,\n): UseChartReturn {\n const ref = useRef<HTMLDivElement | null>(null);\n const chartRef = useRef<ChartInstance | null>(null);\n const [layout, setLayout] = useState<ChartLayout | null>(null);\n const specRef = useRef<string>('');\n\n // Mount / unmount\n // biome-ignore lint/correctness/useExhaustiveDependencies: spec intentionally excluded - spec changes handled via update() in the update effect\n useEffect(() => {\n const container = ref.current;\n if (!container) return;\n\n const mountOpts: MountOptions = {\n theme: options?.theme,\n darkMode: options?.darkMode,\n onDataPointClick: options?.onDataPointClick,\n responsive: options?.responsive,\n };\n\n const chart = createChart(container, spec, mountOpts);\n chartRef.current = chart;\n setLayout(chart.layout);\n specRef.current = JSON.stringify(spec);\n\n return () => {\n chart.destroy();\n chartRef.current = null;\n setLayout(null);\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [options?.theme, options?.darkMode, options?.onDataPointClick, options?.responsive]);\n\n // Update on spec change\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart) return;\n\n const specString = JSON.stringify(spec);\n if (specString !== specRef.current) {\n specRef.current = specString;\n chart.update(spec);\n setLayout(chart.layout);\n }\n }, [spec]);\n\n return {\n ref,\n chart: chartRef.current,\n layout,\n };\n}\n\n// ---------------------------------------------------------------------------\n// useDarkMode\n// ---------------------------------------------------------------------------\n\n/**\n * Resolve a DarkMode preference to a boolean.\n *\n * - \"force\" -> true\n * - \"off\" -> false\n * - \"auto\" -> matches system preference (reactive to changes)\n *\n * @param mode - The dark mode preference.\n * @returns Whether dark mode is active.\n */\nexport function useDarkMode(mode?: DarkMode): boolean {\n const [isDark, setIsDark] = useState(() => resolveInitial(mode));\n\n useEffect(() => {\n if (mode !== 'auto') {\n setIsDark(mode === 'force');\n return;\n }\n\n if (typeof window === 'undefined' || !window.matchMedia) {\n setIsDark(false);\n return;\n }\n\n const mq = window.matchMedia('(prefers-color-scheme: dark)');\n setIsDark(mq.matches);\n\n const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);\n mq.addEventListener('change', handler);\n return () => mq.removeEventListener('change', handler);\n }, [mode]);\n\n return isDark;\n}\n\nfunction resolveInitial(mode?: DarkMode): boolean {\n if (mode === 'force') return true;\n if (mode === 'off' || mode === undefined) return false;\n // \"auto\"\n if (typeof window !== 'undefined' && window.matchMedia) {\n return window.matchMedia('(prefers-color-scheme: dark)').matches;\n }\n return false;\n}\n","/**\n * useGraph: hook for imperative graph control.\n *\n * Provides a ref to pass to <Graph /> and exposes graph methods\n * (search, zoom, select) for programmatic control of the graph instance.\n */\n\nimport type { GraphInstance } from '@opendata-ai/openchart-vanilla';\nimport { useCallback, useRef } from 'react';\n\nexport interface UseGraphReturn {\n /** Ref to pass to <Graph ref={ref} />. */\n ref: React.RefObject<GraphHandle | null>;\n /** Search for nodes matching a query string. */\n search: (query: string) => void;\n /** Clear the current search. */\n clearSearch: () => void;\n /** Zoom to fit all nodes in view. */\n zoomToFit: () => void;\n /** Zoom and center on a specific node. */\n zoomToNode: (nodeId: string) => void;\n /** Select a node by id. */\n selectNode: (nodeId: string) => void;\n /** Get the currently selected node ids. */\n getSelectedNodes: () => string[];\n}\n\n/** Handle exposed by Graph component via forwardRef. */\nexport interface GraphHandle {\n search: (query: string) => void;\n clearSearch: () => void;\n zoomToFit: () => void;\n zoomToNode: (nodeId: string) => void;\n selectNode: (nodeId: string) => void;\n getSelectedNodes: () => string[];\n /** Re-compile encoding/legend/chrome without restarting the simulation. */\n updateVisuals: (spec: import('@opendata-ai/openchart-core').GraphSpec) => void;\n /** The underlying GraphInstance from the vanilla adapter. */\n instance: GraphInstance | null;\n}\n\n/**\n * Hook for imperative graph control.\n *\n * Usage:\n * ```tsx\n * const { ref, search, zoomToFit } = useGraph();\n * return <Graph ref={ref} spec={spec} />;\n * ```\n */\nexport function useGraph(): UseGraphReturn {\n const ref = useRef<GraphHandle | null>(null);\n\n const search = useCallback((query: string) => {\n ref.current?.search(query);\n }, []);\n\n const clearSearch = useCallback(() => {\n ref.current?.clearSearch();\n }, []);\n\n const zoomToFit = useCallback(() => {\n ref.current?.zoomToFit();\n }, []);\n\n const zoomToNode = useCallback((nodeId: string) => {\n ref.current?.zoomToNode(nodeId);\n }, []);\n\n const selectNode = useCallback((nodeId: string) => {\n ref.current?.selectNode(nodeId);\n }, []);\n\n const getSelectedNodes = useCallback((): string[] => {\n return ref.current?.getSelectedNodes() ?? [];\n }, []);\n\n return {\n ref,\n search,\n clearSearch,\n zoomToFit,\n zoomToNode,\n selectNode,\n getSelectedNodes,\n };\n}\n","/**\n * useTable: hook for manual table lifecycle control.\n *\n * Attaches to a container ref, mounts a vanilla table instance,\n * and exposes the instance and current state.\n */\n\nimport type { TableSpec } from '@opendata-ai/openchart-core';\nimport {\n createTable,\n type TableInstance,\n type TableMountOptions,\n type TableState,\n} from '@opendata-ai/openchart-vanilla';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nexport interface UseTableReturn {\n /** Ref to attach to the container div. */\n ref: React.RefObject<HTMLDivElement | null>;\n /** The table instance (null until mounted). */\n table: TableInstance | null;\n /** The current table state (sort, search, page). */\n state: TableState;\n}\n\n/**\n * Hook for manual table lifecycle control.\n *\n * Attach the returned ref to a container div. The table mounts\n * automatically and updates when the spec changes.\n *\n * @param spec - The table spec.\n * @param options - Mount options.\n * @returns { ref, table, state }\n */\nexport function useTable(spec: TableSpec, options?: TableMountOptions): UseTableReturn {\n const ref = useRef<HTMLDivElement | null>(null);\n const tableRef = useRef<TableInstance | null>(null);\n const [state, setState] = useState<TableState>({\n sort: null,\n search: '',\n page: 0,\n });\n\n const originalOnStateChange = options?.onStateChange;\n\n const handleStateChange = useCallback(\n (newState: TableState) => {\n setState(newState);\n originalOnStateChange?.(newState);\n },\n [originalOnStateChange],\n );\n\n // Mount / unmount\n useEffect(() => {\n const container = ref.current;\n if (!container) return;\n\n const mountOpts: TableMountOptions = {\n ...options,\n onStateChange: handleStateChange,\n };\n\n const table = createTable(container, spec, mountOpts);\n tableRef.current = table;\n setState(table.getState());\n\n return () => {\n table.destroy();\n tableRef.current = null;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n options?.theme,\n options?.darkMode,\n options?.onRowClick,\n options?.responsive,\n handleStateChange,\n options,\n spec,\n ]);\n\n // Update on spec change\n useEffect(() => {\n const table = tableRef.current;\n if (!table) return;\n\n table.update(spec);\n setState(table.getState());\n }, [spec]);\n\n return {\n ref,\n table: tableRef.current,\n state,\n };\n}\n","/**\n * useTableState: managed state hook for controlled table usage.\n *\n * Provides individual sort/search/page state with setters and a\n * resetState function to return to initial values.\n */\n\nimport type { SortState } from '@opendata-ai/openchart-core';\nimport { useCallback, useState } from 'react';\n\nexport interface UseTableStateReturn {\n sort: SortState | null;\n setSort: (sort: SortState | null) => void;\n search: string;\n setSearch: (query: string) => void;\n page: number;\n setPage: (page: number) => void;\n resetState: () => void;\n}\n\nexport interface UseTableStateOptions {\n sort?: SortState | null;\n search?: string;\n page?: number;\n}\n\n/**\n * Hook for managing table state (sort, search, page).\n *\n * Use with the DataTable component's controlled props:\n * ```tsx\n * const { sort, search, page, setSort, setSearch, setPage } = useTableState();\n * <DataTable\n * spec={spec}\n * sort={sort}\n * search={search}\n * page={page}\n * onSortChange={setSort}\n * onSearchChange={setSearch}\n * onPageChange={setPage}\n * />\n * ```\n */\nexport function useTableState(initialState?: UseTableStateOptions): UseTableStateReturn {\n const [sort, setSortInternal] = useState<SortState | null>(initialState?.sort ?? null);\n const [search, setSearchInternal] = useState(initialState?.search ?? '');\n const [page, setPageInternal] = useState(initialState?.page ?? 0);\n\n const setSort = useCallback((newSort: SortState | null) => {\n setSortInternal(newSort);\n }, []);\n\n const setSearch = useCallback((query: string) => {\n setSearchInternal(query);\n }, []);\n\n const setPage = useCallback((newPage: number) => {\n setPageInternal(newPage);\n }, []);\n\n const resetState = useCallback(() => {\n setSortInternal(initialState?.sort ?? null);\n setSearchInternal(initialState?.search ?? '');\n setPageInternal(initialState?.page ?? 0);\n }, [initialState?.sort, initialState?.search, initialState?.page]);\n\n return {\n sort,\n setSort,\n search,\n setSearch,\n page,\n setPage,\n resetState,\n };\n}\n","/**\n * React Sankey component: thin wrapper around the vanilla adapter.\n *\n * Mounts a sankey instance on render, updates when spec changes,\n * and cleans up on unmount. All heavy lifting is done by the vanilla\n * createSankey() function.\n *\n * Supports forwardRef for imperative control via the instance getter.\n */\n\nimport type { DarkMode, SankeySpec, ThemeConfig } from '@opendata-ai/openchart-core';\nimport {\n createSankey,\n type SankeyInstance,\n type SankeyMountOptions,\n} from '@opendata-ai/openchart-vanilla';\nimport {\n type CSSProperties,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useRef,\n} from 'react';\nimport { useVizDarkMode, useVizTheme } from './ThemeContext';\n\nexport interface SankeyProps {\n /** The sankey spec to render. */\n spec: SankeySpec;\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode: \"auto\", \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** Callback when a node is clicked. */\n onNodeClick?: (node: Record<string, unknown>) => void;\n /** Callback when a link is clicked. */\n onLinkClick?: (link: Record<string, unknown>) => void;\n /** Callback when a node is hovered (null when hover ends). */\n onNodeHover?: (node: Record<string, unknown> | null) => void;\n /** Callback when a link is hovered (null when hover ends). */\n onLinkHover?: (link: Record<string, unknown> | null) => void;\n /** CSS class name for the wrapper div. */\n className?: string;\n /** Inline styles for the wrapper div. */\n style?: CSSProperties;\n}\n\nexport interface SankeyHandle {\n /** The underlying sankey instance (null until mounted). */\n readonly instance: SankeyInstance | null;\n}\n\n/**\n * React component that renders a sankey diagram from a SankeySpec.\n *\n * Uses the vanilla adapter internally. The spec is compiled and rendered\n * as SVG inside a wrapper div. Spec changes trigger re-renders via the\n * vanilla adapter's update() method.\n */\nexport const Sankey = forwardRef<SankeyHandle, SankeyProps>(function Sankey(\n {\n spec,\n theme: themeProp,\n darkMode,\n onNodeClick,\n onLinkClick,\n onNodeHover,\n onLinkHover,\n className,\n style,\n },\n ref,\n) {\n const contextTheme = useVizTheme();\n const contextDarkMode = useVizDarkMode();\n const theme = themeProp ?? contextTheme;\n const resolvedDarkMode = darkMode ?? contextDarkMode;\n const containerRef = useRef<HTMLDivElement>(null);\n const instanceRef = useRef<SankeyInstance | null>(null);\n const specRef = useRef<string>('');\n\n // Store event handlers in refs so they don't trigger recreation.\n const handlersRef = useRef<{\n onNodeClick?: SankeyProps['onNodeClick'];\n onLinkClick?: SankeyProps['onLinkClick'];\n onNodeHover?: SankeyProps['onNodeHover'];\n onLinkHover?: SankeyProps['onLinkHover'];\n }>({});\n handlersRef.current = {\n onNodeClick,\n onLinkClick,\n onNodeHover,\n onLinkHover,\n };\n\n // Stable callback wrappers that read from refs\n const stableOnNodeClick = useCallback(\n (node: Record<string, unknown>) => handlersRef.current.onNodeClick?.(node),\n [],\n );\n const stableOnLinkClick = useCallback(\n (link: Record<string, unknown>) => handlersRef.current.onLinkClick?.(link),\n [],\n );\n const stableOnNodeHover = useCallback(\n (node: Record<string, unknown> | null) => handlersRef.current.onNodeHover?.(node),\n [],\n );\n const stableOnLinkHover = useCallback(\n (link: Record<string, unknown> | null) => handlersRef.current.onLinkHover?.(link),\n [],\n );\n\n // Expose imperative handle\n useImperativeHandle(\n ref,\n () => ({\n get instance() {\n return instanceRef.current;\n },\n }),\n [],\n );\n\n // Mount sankey and recreate when theme/darkMode change.\n // biome-ignore lint/correctness/useExhaustiveDependencies: spec intentionally excluded - spec changes handled via update() in Effect 2\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const options: SankeyMountOptions = {\n theme,\n darkMode: resolvedDarkMode,\n onNodeClick: stableOnNodeClick,\n onLinkClick: stableOnLinkClick,\n onNodeHover: stableOnNodeHover,\n onLinkHover: stableOnLinkHover,\n responsive: true,\n };\n\n instanceRef.current = createSankey(container, spec, options);\n specRef.current = JSON.stringify(spec);\n\n return () => {\n instanceRef.current?.destroy();\n instanceRef.current = null;\n };\n }, [\n theme,\n resolvedDarkMode,\n stableOnNodeClick,\n stableOnLinkClick,\n stableOnNodeHover,\n stableOnLinkHover,\n ]);\n\n // Update sankey when spec changes.\n useEffect(() => {\n const instance = instanceRef.current;\n if (!instance) return;\n\n const specString = JSON.stringify(spec);\n if (specString === specRef.current) return;\n\n specRef.current = specString;\n instance.update(spec);\n }, [spec]);\n\n return (\n <div\n ref={containerRef}\n className={className ? `oc-sankey-root ${className}` : 'oc-sankey-root'}\n style={style}\n />\n );\n});\n","/**\n * Visualization routing component: renders Chart, DataTable, or Graph\n * based on the spec type. Use this when rendering arbitrary VizSpec values.\n *\n * For event handlers, use the specific component (Chart, DataTable, Graph) directly.\n */\n\nimport type { DarkMode, ThemeConfig, VizSpec } from '@opendata-ai/openchart-core';\nimport { isGraphSpec, isSankeySpec, isTableSpec } from '@opendata-ai/openchart-core';\nimport type { CSSProperties } from 'react';\nimport { Chart } from './Chart';\nimport { DataTable } from './DataTable';\nimport { Graph } from './Graph';\nimport { Sankey } from './Sankey';\n\nexport interface VisualizationProps {\n /** The visualization spec to render. */\n spec: VizSpec;\n /** Theme overrides. */\n theme?: ThemeConfig;\n /** Dark mode: \"auto\", \"force\", or \"off\". */\n darkMode?: DarkMode;\n /** CSS class name for the wrapper div. */\n className?: string;\n /** Inline styles for the wrapper div. */\n style?: CSSProperties;\n}\n\n/**\n * Routes a VizSpec to the appropriate rendering component.\n *\n * Accepts any VizSpec and renders it with the correct component based on the\n * spec's type field. For event handlers, use Chart, DataTable, or Graph directly.\n */\nexport function Visualization({ spec, theme, darkMode, className, style }: VisualizationProps) {\n if (isTableSpec(spec)) {\n return (\n <DataTable\n spec={spec}\n theme={theme}\n darkMode={darkMode}\n className={className}\n style={style}\n />\n );\n }\n if (isGraphSpec(spec)) {\n return (\n <Graph spec={spec} theme={theme} darkMode={darkMode} className={className} style={style} />\n );\n }\n if (isSankeySpec(spec)) {\n return (\n <Sankey spec={spec} theme={theme} darkMode={darkMode} className={className} style={style} />\n );\n }\n return (\n <Chart spec={spec} theme={theme} darkMode={darkMode} className={className} style={style} />\n );\n}\n"],"mappings":"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","names":["jsx","Chart","useCallback","useEffect","useRef","jsx","useRef","useCallback","useEffect","forwardRef","useCallback","useEffect","useImperativeHandle","useRef","jsx","forwardRef","Graph","useRef","useCallback","useImperativeHandle","spec","useEffect","createChart","useEffect","useRef","useCallback","useRef","createTable","useCallback","useEffect","useRef","useState","useCallback","useState","forwardRef","useCallback","useEffect","useImperativeHandle","useRef","jsx","forwardRef","Sankey","useRef","useCallback","useImperativeHandle","useEffect","jsx"]}
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- .oc-root,.oc-chart-root,.oc-table-wrapper,.oc-table-root,.oc-graph-wrapper,.oc-graph-root{--oc-font-family:Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--oc-font-mono:"JetBrains Mono", "Fira Code", "Cascadia Code", monospace;--oc-ease-smooth:linear(0, .157, .438, .64, .766, .85, .906, .941, .964, .978, .988, .994, .998, 1);--oc-ease-snappy:linear(0, .012, .048, .108, .194, .302, .426, .559, .69, .808, .905, .973, 1.013, 1.028, 1.023, 1.006, .984, .966, .957, .957, .964, .975, .986, .995, 1, 1.003, 1.002, 1, .998, .998, .999, 1);--oc-animation-duration:.5s;--oc-animation-stagger:80ms;--oc-annotation-delay:.2s;--oc-title-size:22px;--oc-title-weight:700;--oc-title-tracking:-.02em;--oc-subtitle-size:14px;--oc-subtitle-weight:400;--oc-source-size:12px;--oc-source-weight:400;--oc-body-size:13px;--oc-bg:#fff;--oc-text:#1d1d1d;--oc-text-secondary:#5c5c5c;--oc-text-muted:#999;--oc-gridline:#e8e8e8;--oc-axis:#888;--oc-border:#e2e2e2;--oc-border-radius:4px;--oc-focus:#3b82f6;--oc-hover-bg:rgba(0,0,0,.024);--oc-tooltip-bg:rgba(255,255,255,.88);--oc-tooltip-border:rgba(0,0,0,.08);--oc-tooltip-shadow:0 2px 8px rgba(0,0,0,.08), 0 0 1px rgba(0,0,0,.12);--oc-tooltip-text:#1d1d1d;--oc-legend-text:#555}.oc-dark{--oc-bg:#1a1a2e;--oc-text:#e0e0e0;--oc-text-secondary:#b0b0b0;--oc-text-muted:gray;--oc-gridline:#335;--oc-axis:#999;--oc-border:#446;--oc-focus:#60a5fa;--oc-hover-bg:rgba(255,255,255,.05);--oc-tooltip-bg:rgba(30,30,50,.85);--oc-tooltip-border:rgba(255,255,255,.08);--oc-tooltip-shadow:0 2px 8px rgba(0,0,0,.3), 0 0 1px rgba(0,0,0,.4);--oc-tooltip-text:#e0e0e0;--oc-legend-text:#b0b0b0}.oc-chart-root{width:100%}.oc-table-root,.oc-graph-root{width:100%;height:100%}.oc-table-root{overflow:auto}.oc-chart{font-family:var(--oc-font-family);width:100%;display:block}.oc-sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.oc-editable-hover{outline-offset:2px;border-radius:2px;outline:1.5px solid rgba(79,70,229,.35)}.oc-chrome{font-family:var(--oc-font-family)}.oc-title{font-size:var(--oc-title-size);font-weight:var(--oc-title-weight);letter-spacing:var(--oc-title-tracking);fill:var(--oc-text)}.oc-subtitle{font-size:var(--oc-subtitle-size);font-weight:var(--oc-subtitle-weight);fill:var(--oc-text-secondary)}.oc-source,.oc-byline,.oc-footer{font-size:var(--oc-source-size);font-weight:var(--oc-source-weight);fill:var(--oc-text-muted)}.oc-chrome-footer{padding-top:16px}.oc-tooltip{pointer-events:none;z-index:1000;background:var(--oc-tooltip-bg);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid var(--oc-tooltip-border);box-shadow:var(--oc-tooltip-shadow);color:var(--oc-tooltip-text);font-family:var(--oc-font-family);border-radius:8px;min-width:140px;max-width:260px;padding:0;font-size:12px;line-height:1.4;animation:.12s ease-out oc-tooltip-in;display:none;position:absolute}.oc-tooltip .oc-tooltip-header{align-items:center;gap:6px;padding:8px 12px 6px;display:flex}.oc-tooltip .oc-tooltip-dot{border-radius:50%;flex-shrink:0;width:8px;height:8px}.oc-tooltip .oc-tooltip-title{letter-spacing:-.01em;color:var(--oc-tooltip-text);white-space:nowrap;text-overflow:ellipsis;font-size:12px;font-weight:600;overflow:hidden}.oc-tooltip .oc-tooltip-body{border-top:1px solid var(--oc-tooltip-border);padding:4px 12px 8px}.oc-tooltip .oc-tooltip-header+.oc-tooltip-body{padding-top:6px}.oc-tooltip .oc-tooltip-body:first-child{border-top:none;padding-top:8px}.oc-tooltip .oc-tooltip-row{justify-content:space-between;align-items:baseline;gap:12px;padding:1px 0;display:flex}.oc-tooltip .oc-tooltip-label{color:var(--oc-text-muted);white-space:nowrap;flex-shrink:0;font-size:11px}.oc-tooltip .oc-tooltip-value{font-variant-numeric:tabular-nums;text-align:right;text-overflow:ellipsis;white-space:nowrap;font-size:11px;font-weight:500;overflow:hidden}.oc-legend{font-family:var(--oc-font-family);font-size:var(--oc-body-size)}.oc-legend-entry{cursor:default}.oc-legend text{fill:var(--oc-legend-text)}.oc-table-wrapper{font-family:var(--oc-font-family);color:var(--oc-text);background:var(--oc-bg)}.oc-table-wrapper>.oc-chrome{margin-bottom:16px}.oc-table-wrapper table{border-collapse:collapse;width:100%}.oc-table-wrapper th{text-align:left;border-bottom:1px solid var(--oc-border);padding:10px 16px}.oc-table-wrapper td{text-align:left;border-bottom:1px solid var(--oc-border);padding:10px 16px}.oc-table-wrapper th{text-transform:uppercase;letter-spacing:.05em;color:var(--oc-text-secondary);white-space:nowrap;font-size:12px;font-weight:600}.oc-table-wrapper thead{z-index:2;background:var(--oc-bg);position:sticky;top:0}.oc-table-wrapper thead th{border-bottom-width:2px}.oc-table-wrapper td{font-variant-numeric:tabular-nums;font-size:14px}.oc-table-wrapper th:focus{outline:2px solid var(--oc-focus);outline-offset:-2px}.oc-table-wrapper tbody:focus{outline:none}.oc-table-title{font-size:var(--oc-title-computed-size,var(--oc-title-size));font-weight:var(--oc-title-computed-weight,var(--oc-title-weight));color:var(--oc-title-computed-color,var(--oc-text));margin-bottom:4px}.oc-table-subtitle{font-size:var(--oc-subtitle-computed-size,var(--oc-subtitle-size));font-weight:var(--oc-subtitle-computed-weight,var(--oc-subtitle-weight));color:var(--oc-subtitle-computed-color,var(--oc-text-secondary));margin-bottom:8px}.oc-table-source{font-size:var(--oc-source-computed-size,var(--oc-source-size));color:var(--oc-source-computed-color,var(--oc-text-muted))}.oc-table-footer-text{font-size:var(--oc-footer-computed-size,var(--oc-source-size));color:var(--oc-footer-computed-color,var(--oc-text-muted))}.oc-table-scroll{overflow-x:auto}.oc-table--sticky th:first-child{z-index:1;background:var(--oc-bg);position:sticky;left:0}.oc-table--sticky td:first-child{z-index:1;background:var(--oc-bg);position:sticky;left:0}.oc-table-sort-btn{cursor:pointer;vertical-align:middle;background:0 0;border:none;flex-direction:column;align-items:center;gap:2px;margin-left:6px;padding:2px;display:inline-flex}.oc-table-sort-btn:before{content:"";border-left:5px solid transparent;border-right:5px solid transparent;width:0;height:0;transition:opacity .15s,border-color .15s;display:block}.oc-table-sort-btn:after{content:"";border-left:5px solid transparent;border-right:5px solid transparent;width:0;height:0;transition:opacity .15s,border-color .15s;display:block}.oc-table-sort-btn:before{border-bottom:4.5px solid var(--oc-text-secondary);opacity:.45}.oc-table-sort-btn:after{border-top:4.5px solid var(--oc-text-secondary);opacity:.45}.oc-table-sort-btn:hover:before{opacity:.75}.oc-table-sort-btn:hover:after{opacity:.75}th[aria-sort=ascending] .oc-table-sort-btn:before{opacity:1;border-bottom-color:var(--oc-text)}th[aria-sort=ascending] .oc-table-sort-btn:after{opacity:.15}th[aria-sort=descending] .oc-table-sort-btn:after{opacity:1;border-top-color:var(--oc-text)}th[aria-sort=descending] .oc-table-sort-btn:before{opacity:.15}.oc-table-search{padding:8px 0}.oc-table-search input{border:1px solid var(--oc-border);background:var(--oc-bg);width:100%;color:var(--oc-text);box-sizing:border-box;border-radius:6px;padding:8px 12px;font-family:inherit;font-size:13px;transition:border-color .15s}.oc-table-search input::-ms-input-placeholder{color:var(--oc-text-muted);font-size:13px}.oc-table-search input::placeholder{color:var(--oc-text-muted);font-size:13px}.oc-table-search input:focus{border-color:var(--oc-focus);outline:none;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.oc-table-pagination{color:var(--oc-text-secondary);justify-content:space-between;align-items:center;padding:12px 0 4px;font-size:13px;display:flex}.oc-table-pagination button{border:1px solid var(--oc-border);background:var(--oc-bg);color:var(--oc-text);cursor:pointer;border-radius:6px;padding:6px 14px;font-family:inherit;font-size:13px;transition:background .15s,border-color .15s}.oc-table-pagination button:disabled{opacity:.35;cursor:not-allowed}.oc-table-pagination button:hover:not(:disabled){background:var(--oc-hover-bg);border-color:var(--oc-axis)}.oc-table-pagination button:focus-visible{outline:2px solid var(--oc-focus);outline-offset:1px}.oc-table-pagination-info{font-variant-numeric:tabular-nums}.oc-table-pagination-btns{gap:8px;display:flex}.oc-table-bar{position:relative}.oc-table-bar-fill{opacity:.15;pointer-events:none;border-radius:2px;position:absolute;top:6px;bottom:6px;left:0}.oc-table-bar-value{z-index:1;position:relative}.oc-table-sparkline{width:100%;display:block;position:relative}.oc-table-sparkline svg{width:100%;display:block;overflow:visible}.oc-table-sparkline-dot{border-radius:50%;width:5px;height:5px;position:absolute}.oc-table-sparkline-labels{justify-content:space-between;font-size:11px;line-height:1;display:flex}.oc-table-image{vertical-align:middle;display:inline-block}.oc-table-image img{object-fit:cover}.oc-table-image-rounded img{border-radius:50%}.oc-table-flag{font-size:1.2em}.oc-table--compact th{padding:4px 8px;font-size:13px}.oc-table--compact td{padding:4px 8px;font-size:13px}.oc-table--compact th{font-size:11px}.oc-table--clickable tbody tr{cursor:pointer}.oc-table--clickable tbody tr:hover{background:var(--oc-hover-bg)}.oc-table-cell-focus{outline:2px solid var(--oc-focus);outline-offset:-2px}.oc-table-empty{text-align:center;color:var(--oc-text-secondary);padding:32px 16px;font-size:14px;font-style:italic}.oc-table-wrapper.oc-animate>.oc-chrome{animation:oc-enter-fade calc(var(--oc-animation-duration) * .6) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-table-wrapper.oc-animate thead{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .4) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-table-wrapper.oc-animate tbody tr{animation:oc-table-enter-row var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0))}.oc-table-wrapper.oc-animate tbody td{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0))}.oc-table-wrapper.oc-animate td.oc-table-heatmap{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .7) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .3)}.oc-table-wrapper.oc-animate td.oc-table-category{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .7) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .3)}.oc-table-wrapper.oc-animate .oc-table-bar-fill{animation:oc-table-enter-bar-fill calc(var(--oc-animation-duration) * .8) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .3)}.oc-table-wrapper.oc-animate .oc-table-sparkline>svg{animation:oc-enter-line calc(var(--oc-animation-duration) * .8) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .4)}.oc-table-wrapper.oc-animate .oc-table-sparkline-dot{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .3) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .8)}.oc-table-wrapper.oc-animate .oc-table-sparkline-labels{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .3) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .8)}.oc-table-wrapper.oc-animate .oc-table-search{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-table-wrapper.oc-animate .oc-table-pagination{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-graph-wrapper{background:var(--oc-bg);font-family:var(--oc-font-family);width:100%;height:100%;position:relative;overflow:hidden}.oc-graph-canvas{cursor:grab;display:block}.oc-graph-canvas--dragging{cursor:grabbing}.oc-graph-chrome{padding:16px 16px 8px}.oc-graph-chrome .oc-title{font-size:var(--oc-title-size);font-weight:var(--oc-title-weight);letter-spacing:var(--oc-title-tracking);color:var(--oc-text);margin:0 0 4px}.oc-graph-chrome .oc-subtitle{font-size:var(--oc-subtitle-size);color:var(--oc-text-secondary);margin:0}.oc-graph-legend{background:var(--oc-bg);border:1px solid var(--oc-border);border-radius:var(--oc-border-radius);color:var(--oc-text-secondary);max-height:200px;padding:8px 12px;font-size:12px;position:absolute;top:8px;right:8px;overflow-y:auto}.oc-graph-legend-item{align-items:center;gap:6px;padding:2px 0;display:flex}.oc-graph-legend-swatch{border-radius:50%;flex-shrink:0;width:10px;height:10px}.oc-graph-search{position:absolute;top:8px;left:8px}.oc-graph-search input{font-family:var(--oc-font-family);font-size:var(--oc-body-size);border:1px solid var(--oc-border);border-radius:var(--oc-border-radius);background:var(--oc-bg);color:var(--oc-text);outline:none;padding:6px 10px}.oc-graph-search input:focus{border-color:var(--oc-focus);box-shadow:0 0 0 2px rgba(59,130,246,.25)}.oc-dark .oc-graph-wrapper,.oc-graph-wrapper.oc-dark{--oc-bg:#0d1117}.oc-dark .oc-graph-legend,.oc-dark.oc-graph-wrapper .oc-graph-legend,.oc-dark .oc-graph-search input{background:rgba(13,17,23,.85);border-color:rgba(255,255,255,.1)}@keyframes oc-enter-bar{0%{clip-path:inset(100% 0 0);opacity:0}75%{opacity:1}to{clip-path:inset(0);opacity:1}}@keyframes oc-enter-bar-h{0%{clip-path:inset(0 100% 0 0);opacity:0}75%{opacity:1}to{clip-path:inset(0);opacity:1}}@keyframes oc-enter-line{0%{clip-path:inset(0 100% 0 0);opacity:0}15%{opacity:1}to{clip-path:inset(0);opacity:1}}@keyframes oc-enter-point{0%{opacity:0;transform:scale(.3)}to{opacity:1;transform:scale(1)}}@keyframes oc-enter-fade-only{0%{opacity:0}to{opacity:1}}@keyframes oc-enter-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes oc-table-enter-row{0%{transform:translateY(6px)}to{transform:translateY(0)}}@keyframes oc-table-enter-bar-fill{0%{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0)}}@keyframes oc-tooltip-in{0%{opacity:0;transform:translateY(2px)}to{opacity:1;transform:translateY(0)}}.oc-animate .oc-mark-rect rect{animation:oc-enter-bar var(--oc-animation-duration) var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-bar rect{animation:oc-enter-bar var(--oc-animation-duration) var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-rect[data-orient=horizontal] rect{animation-name:oc-enter-bar-h}.oc-animate .oc-mark-bar[data-orient=horizontal] rect{animation-name:oc-enter-bar-h}.oc-animate .oc-mark-rect[data-stack-pos] rect{animation-duration:var(--oc-stack-segment-duration,.15s);animation-timing-function:linear;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0) + var(--oc-stack-pos,0) * var(--oc-stack-segment-duration,.15s))}.oc-animate .oc-mark-line{animation:oc-enter-line var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-area{animation:oc-enter-line var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-arc{animation:oc-enter-fade-only var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate circle.oc-mark-point{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .4) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate circle.oc-mark-circle{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .4) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-line~circle.oc-mark-point{animation-delay:calc(var(--oc-animation-duration) * .35 + var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-area~circle.oc-mark-point{animation-delay:calc(var(--oc-animation-duration) * .35 + var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-text text{animation:oc-enter-fade calc(var(--oc-animation-duration) * .6) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-rule line{animation:oc-enter-fade calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-tick line{animation:oc-enter-fade calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-label{animation:oc-enter-fade .3s var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0) + var(--oc-animation-duration) * .7)}.oc-animate .oc-annotation{animation:oc-enter-fade .4s var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-duration) + var(--oc-annotation-delay,.2s))}@media (prefers-reduced-motion:reduce){.oc-table-sort-btn:before,.oc-table-sort-btn:after,.oc-table-search input,.oc-table-pagination button{transition:none}.oc-animate .oc-mark-rect rect,.oc-animate .oc-mark-bar rect,.oc-animate .oc-mark-arc,.oc-animate .oc-mark-line,.oc-animate .oc-mark-area,.oc-animate circle.oc-mark-point,.oc-animate circle.oc-mark-circle,.oc-animate .oc-mark-text text,.oc-animate .oc-mark-rule line,.oc-animate .oc-mark-tick line,.oc-animate .oc-mark-label,.oc-animate .oc-annotation,.oc-table-wrapper.oc-animate>.oc-chrome,.oc-table-wrapper.oc-animate thead,.oc-table-wrapper.oc-animate tbody tr,.oc-table-wrapper.oc-animate tbody td,.oc-table-wrapper.oc-animate td.oc-table-heatmap,.oc-table-wrapper.oc-animate td.oc-table-category,.oc-table-wrapper.oc-animate .oc-table-bar-fill,.oc-table-wrapper.oc-animate .oc-table-sparkline>svg,.oc-table-wrapper.oc-animate .oc-table-sparkline-dot,.oc-table-wrapper.oc-animate .oc-table-sparkline-labels,.oc-table-wrapper.oc-animate .oc-table-search,.oc-table-wrapper.oc-animate .oc-table-pagination{animation:none}}
1
+ .oc-root,.oc-chart-root,.oc-table-wrapper,.oc-table-root,.oc-graph-wrapper,.oc-graph-root,.oc-sankey-root{--oc-font-family:Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--oc-font-mono:"JetBrains Mono", "Fira Code", "Cascadia Code", monospace;--oc-ease-smooth:linear(0, .157, .438, .64, .766, .85, .906, .941, .964, .978, .988, .994, .998, 1);--oc-ease-snappy:linear(0, .012, .048, .108, .194, .302, .426, .559, .69, .808, .905, .973, 1.013, 1.028, 1.023, 1.006, .984, .966, .957, .957, .964, .975, .986, .995, 1, 1.003, 1.002, 1, .998, .998, .999, 1);--oc-animation-duration:.5s;--oc-animation-stagger:80ms;--oc-annotation-delay:.2s;--oc-title-size:22px;--oc-title-weight:700;--oc-title-tracking:-.02em;--oc-subtitle-size:14px;--oc-subtitle-weight:400;--oc-source-size:12px;--oc-source-weight:400;--oc-body-size:13px;--oc-bg:#fff;--oc-text:#1d1d1d;--oc-text-secondary:#5c5c5c;--oc-text-muted:#999;--oc-gridline:#e8e8e8;--oc-axis:#888;--oc-border:#e2e2e2;--oc-border-radius:4px;--oc-focus:#3b82f6;--oc-hover-bg:rgba(0,0,0,.024);--oc-tooltip-bg:rgba(255,255,255,.88);--oc-tooltip-border:rgba(0,0,0,.08);--oc-tooltip-shadow:0 2px 8px rgba(0,0,0,.08), 0 0 1px rgba(0,0,0,.12);--oc-tooltip-text:#1d1d1d;--oc-legend-text:#555}.oc-dark{--oc-bg:#1a1a2e;--oc-text:#e0e0e0;--oc-text-secondary:#b0b0b0;--oc-text-muted:gray;--oc-gridline:#335;--oc-axis:#999;--oc-border:#446;--oc-focus:#60a5fa;--oc-hover-bg:rgba(255,255,255,.05);--oc-tooltip-bg:rgba(30,30,50,.85);--oc-tooltip-border:rgba(255,255,255,.08);--oc-tooltip-shadow:0 2px 8px rgba(0,0,0,.3), 0 0 1px rgba(0,0,0,.4);--oc-tooltip-text:#e0e0e0;--oc-legend-text:#b0b0b0}.oc-chart-root{width:100%}.oc-table-root,.oc-graph-root,.oc-sankey-root{width:100%;height:100%}.oc-table-root{overflow:auto}.oc-chart{font-family:var(--oc-font-family);width:100%;display:block}.oc-sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.oc-editable-hover{outline-offset:2px;border-radius:2px;outline:1.5px solid rgba(79,70,229,.35)}.oc-chrome{font-family:var(--oc-font-family)}.oc-title{font-size:var(--oc-title-size);font-weight:var(--oc-title-weight);letter-spacing:var(--oc-title-tracking);fill:var(--oc-text)}.oc-subtitle{font-size:var(--oc-subtitle-size);font-weight:var(--oc-subtitle-weight);fill:var(--oc-text-secondary)}.oc-source,.oc-byline,.oc-footer{font-size:var(--oc-source-size);font-weight:var(--oc-source-weight);fill:var(--oc-text-muted)}.oc-chrome-footer{padding-top:16px}.oc-tooltip{pointer-events:none;z-index:1000;background:var(--oc-tooltip-bg);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid var(--oc-tooltip-border);box-shadow:var(--oc-tooltip-shadow);color:var(--oc-tooltip-text);font-family:var(--oc-font-family);border-radius:8px;min-width:140px;max-width:260px;padding:0;font-size:12px;line-height:1.4;animation:.12s ease-out oc-tooltip-in;display:none;position:absolute}.oc-tooltip .oc-tooltip-header{align-items:center;gap:6px;padding:8px 12px 6px;display:flex}.oc-tooltip .oc-tooltip-dot{border-radius:50%;flex-shrink:0;width:8px;height:8px}.oc-tooltip .oc-tooltip-title{letter-spacing:-.01em;color:var(--oc-tooltip-text);white-space:nowrap;text-overflow:ellipsis;font-size:12px;font-weight:600;overflow:hidden}.oc-tooltip .oc-tooltip-body{border-top:1px solid var(--oc-tooltip-border);padding:4px 12px 8px}.oc-tooltip .oc-tooltip-header+.oc-tooltip-body{padding-top:6px}.oc-tooltip .oc-tooltip-body:first-child{border-top:none;padding-top:8px}.oc-tooltip .oc-tooltip-row{justify-content:space-between;align-items:baseline;gap:12px;padding:1px 0;display:flex}.oc-tooltip .oc-tooltip-label{color:var(--oc-text-muted);white-space:nowrap;flex-shrink:0;font-size:11px}.oc-tooltip .oc-tooltip-value{font-variant-numeric:tabular-nums;text-align:right;text-overflow:ellipsis;white-space:nowrap;font-size:11px;font-weight:500;overflow:hidden}.oc-legend{font-family:var(--oc-font-family);font-size:var(--oc-body-size)}.oc-legend-entry{cursor:default}.oc-legend text{fill:var(--oc-legend-text)}.oc-table-wrapper{font-family:var(--oc-font-family);color:var(--oc-text);background:var(--oc-bg)}.oc-table-wrapper>.oc-chrome{margin-bottom:16px}.oc-table-wrapper table{border-collapse:collapse;width:100%}.oc-table-wrapper th{text-align:left;border-bottom:1px solid var(--oc-border);padding:10px 16px}.oc-table-wrapper td{text-align:left;border-bottom:1px solid var(--oc-border);padding:10px 16px}.oc-table-wrapper th{text-transform:uppercase;letter-spacing:.05em;color:var(--oc-text-secondary);white-space:nowrap;font-size:12px;font-weight:600}.oc-table-wrapper thead{z-index:2;background:var(--oc-bg);position:sticky;top:0}.oc-table-wrapper thead th{border-bottom-width:2px}.oc-table-wrapper td{font-variant-numeric:tabular-nums;font-size:14px}.oc-table-wrapper th:focus{outline:2px solid var(--oc-focus);outline-offset:-2px}.oc-table-wrapper tbody:focus{outline:none}.oc-table-title{font-size:var(--oc-title-computed-size,var(--oc-title-size));font-weight:var(--oc-title-computed-weight,var(--oc-title-weight));color:var(--oc-title-computed-color,var(--oc-text));margin-bottom:4px}.oc-table-subtitle{font-size:var(--oc-subtitle-computed-size,var(--oc-subtitle-size));font-weight:var(--oc-subtitle-computed-weight,var(--oc-subtitle-weight));color:var(--oc-subtitle-computed-color,var(--oc-text-secondary));margin-bottom:8px}.oc-table-source{font-size:var(--oc-source-computed-size,var(--oc-source-size));color:var(--oc-source-computed-color,var(--oc-text-muted))}.oc-table-footer-text{font-size:var(--oc-footer-computed-size,var(--oc-source-size));color:var(--oc-footer-computed-color,var(--oc-text-muted))}.oc-table-scroll{overflow-x:auto}.oc-table--sticky th:first-child{z-index:1;background:var(--oc-bg);position:sticky;left:0}.oc-table--sticky td:first-child{z-index:1;background:var(--oc-bg);position:sticky;left:0}.oc-table-sort-btn{cursor:pointer;vertical-align:middle;background:0 0;border:none;flex-direction:column;align-items:center;gap:2px;margin-left:6px;padding:2px;display:inline-flex}.oc-table-sort-btn:before{content:"";border-left:5px solid transparent;border-right:5px solid transparent;width:0;height:0;transition:opacity .15s,border-color .15s;display:block}.oc-table-sort-btn:after{content:"";border-left:5px solid transparent;border-right:5px solid transparent;width:0;height:0;transition:opacity .15s,border-color .15s;display:block}.oc-table-sort-btn:before{border-bottom:4.5px solid var(--oc-text-secondary);opacity:.45}.oc-table-sort-btn:after{border-top:4.5px solid var(--oc-text-secondary);opacity:.45}.oc-table-sort-btn:hover:before{opacity:.75}.oc-table-sort-btn:hover:after{opacity:.75}th[aria-sort=ascending] .oc-table-sort-btn:before{opacity:1;border-bottom-color:var(--oc-text)}th[aria-sort=ascending] .oc-table-sort-btn:after{opacity:.15}th[aria-sort=descending] .oc-table-sort-btn:after{opacity:1;border-top-color:var(--oc-text)}th[aria-sort=descending] .oc-table-sort-btn:before{opacity:.15}.oc-table-search{padding:8px 0}.oc-table-search input{border:1px solid var(--oc-border);background:var(--oc-bg);width:100%;color:var(--oc-text);box-sizing:border-box;border-radius:6px;padding:8px 12px;font-family:inherit;font-size:13px;transition:border-color .15s}.oc-table-search input::-ms-input-placeholder{color:var(--oc-text-muted);font-size:13px}.oc-table-search input::placeholder{color:var(--oc-text-muted);font-size:13px}.oc-table-search input:focus{border-color:var(--oc-focus);outline:none;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.oc-table-pagination{color:var(--oc-text-secondary);justify-content:space-between;align-items:center;padding:12px 0 4px;font-size:13px;display:flex}.oc-table-pagination button{border:1px solid var(--oc-border);background:var(--oc-bg);color:var(--oc-text);cursor:pointer;border-radius:6px;padding:6px 14px;font-family:inherit;font-size:13px;transition:background .15s,border-color .15s}.oc-table-pagination button:disabled{opacity:.35;cursor:not-allowed}.oc-table-pagination button:hover:not(:disabled){background:var(--oc-hover-bg);border-color:var(--oc-axis)}.oc-table-pagination button:focus-visible{outline:2px solid var(--oc-focus);outline-offset:1px}.oc-table-pagination-info{font-variant-numeric:tabular-nums}.oc-table-pagination-btns{gap:8px;display:flex}.oc-table-bar{position:relative}.oc-table-bar-fill{opacity:.15;pointer-events:none;border-radius:2px;position:absolute;top:6px;bottom:6px;left:0}.oc-table-bar-value{z-index:1;position:relative}.oc-table-sparkline{width:100%;display:block;position:relative}.oc-table-sparkline svg{width:100%;display:block;overflow:visible}.oc-table-sparkline-dot{border-radius:50%;width:5px;height:5px;position:absolute}.oc-table-sparkline-labels{justify-content:space-between;font-size:11px;line-height:1;display:flex}.oc-table-image{vertical-align:middle;display:inline-block}.oc-table-image img{object-fit:cover}.oc-table-image-rounded img{border-radius:50%}.oc-table-flag{font-size:1.2em}.oc-table--compact th{padding:4px 8px;font-size:13px}.oc-table--compact td{padding:4px 8px;font-size:13px}.oc-table--compact th{font-size:11px}.oc-table--clickable tbody tr{cursor:pointer}.oc-table--clickable tbody tr:hover{background:var(--oc-hover-bg)}.oc-table-cell-focus{outline:2px solid var(--oc-focus);outline-offset:-2px}.oc-table-empty{text-align:center;color:var(--oc-text-secondary);padding:32px 16px;font-size:14px;font-style:italic}.oc-table-wrapper.oc-animate>.oc-chrome{animation:oc-enter-fade calc(var(--oc-animation-duration) * .6) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-table-wrapper.oc-animate thead{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .4) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-table-wrapper.oc-animate tbody tr{animation:oc-table-enter-row var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0))}.oc-table-wrapper.oc-animate tbody td{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0))}.oc-table-wrapper.oc-animate td.oc-table-heatmap{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .7) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .3)}.oc-table-wrapper.oc-animate td.oc-table-category{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .7) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .3)}.oc-table-wrapper.oc-animate .oc-table-bar-fill{animation:oc-table-enter-bar-fill calc(var(--oc-animation-duration) * .8) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .3)}.oc-table-wrapper.oc-animate .oc-table-sparkline>svg{animation:oc-enter-line calc(var(--oc-animation-duration) * .8) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .4)}.oc-table-wrapper.oc-animate .oc-table-sparkline-dot{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .3) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .8)}.oc-table-wrapper.oc-animate .oc-table-sparkline-labels{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .3) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .8)}.oc-table-wrapper.oc-animate .oc-table-search{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-table-wrapper.oc-animate .oc-table-pagination{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-graph-wrapper{background:var(--oc-bg);font-family:var(--oc-font-family);width:100%;height:100%;position:relative;overflow:hidden}.oc-graph-canvas{cursor:grab;display:block}.oc-graph-canvas--dragging{cursor:grabbing}.oc-graph-chrome{padding:16px 16px 8px}.oc-graph-chrome .oc-title{font-size:var(--oc-title-size);font-weight:var(--oc-title-weight);letter-spacing:var(--oc-title-tracking);color:var(--oc-text);margin:0 0 4px}.oc-graph-chrome .oc-subtitle{font-size:var(--oc-subtitle-size);color:var(--oc-text-secondary);margin:0}.oc-graph-legend{background:var(--oc-bg);border:1px solid var(--oc-border);border-radius:var(--oc-border-radius);color:var(--oc-text-secondary);max-height:200px;padding:8px 12px;font-size:12px;position:absolute;top:8px;right:8px;overflow-y:auto}.oc-graph-legend-item{align-items:center;gap:6px;padding:2px 0;display:flex}.oc-graph-legend-swatch{border-radius:50%;flex-shrink:0;width:10px;height:10px}.oc-graph-search{position:absolute;top:8px;left:8px}.oc-graph-search input{font-family:var(--oc-font-family);font-size:var(--oc-body-size);border:1px solid var(--oc-border);border-radius:var(--oc-border-radius);background:var(--oc-bg);color:var(--oc-text);outline:none;padding:6px 10px}.oc-graph-search input:focus{border-color:var(--oc-focus);box-shadow:0 0 0 2px rgba(59,130,246,.25)}.oc-dark .oc-graph-wrapper,.oc-graph-wrapper.oc-dark{--oc-bg:#0d1117}.oc-dark .oc-graph-legend,.oc-dark.oc-graph-wrapper .oc-graph-legend,.oc-dark .oc-graph-search input{background:rgba(13,17,23,.85);border-color:rgba(255,255,255,.1)}@keyframes oc-enter-bar{0%{clip-path:inset(100% 0 0);opacity:0}75%{opacity:1}to{clip-path:inset(0);opacity:1}}@keyframes oc-enter-bar-h{0%{clip-path:inset(0 100% 0 0);opacity:0}75%{opacity:1}to{clip-path:inset(0);opacity:1}}@keyframes oc-enter-line{0%{clip-path:inset(0 100% 0 0);opacity:0}15%{opacity:1}to{clip-path:inset(0);opacity:1}}@keyframes oc-enter-point{0%{opacity:0;transform:scale(.3)}to{opacity:1;transform:scale(1)}}@keyframes oc-enter-fade-only{0%{opacity:0}to{opacity:1}}@keyframes oc-enter-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes oc-table-enter-row{0%{transform:translateY(6px)}to{transform:translateY(0)}}@keyframes oc-table-enter-bar-fill{0%{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0)}}@keyframes oc-tooltip-in{0%{opacity:0;transform:translateY(2px)}to{opacity:1;transform:translateY(0)}}.oc-animate .oc-mark-rect rect{animation:oc-enter-bar var(--oc-animation-duration) var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-bar rect{animation:oc-enter-bar var(--oc-animation-duration) var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-rect[data-orient=horizontal] rect{animation-name:oc-enter-bar-h}.oc-animate .oc-mark-bar[data-orient=horizontal] rect{animation-name:oc-enter-bar-h}.oc-animate .oc-mark-rect[data-stack-pos] rect{animation-duration:var(--oc-stack-segment-duration,.15s);animation-timing-function:linear;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0) + var(--oc-stack-pos,0) * var(--oc-stack-segment-duration,.15s))}.oc-animate .oc-mark-line{animation:oc-enter-line var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-area{animation:oc-enter-line var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-arc{animation:oc-enter-fade-only var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate circle.oc-mark-point{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .4) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate circle.oc-mark-circle{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .4) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-line~circle.oc-mark-point{animation-delay:calc(var(--oc-animation-duration) * .35 + var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-area~circle.oc-mark-point{animation-delay:calc(var(--oc-animation-duration) * .35 + var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-text text{animation:oc-enter-fade calc(var(--oc-animation-duration) * .6) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-rule line{animation:oc-enter-fade calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-tick line{animation:oc-enter-fade calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-label{animation:oc-enter-fade .3s var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0) + var(--oc-animation-duration) * .7)}.oc-animate .oc-annotation{animation:oc-enter-fade .4s var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-duration) + var(--oc-annotation-delay,.2s))}.oc-animate .oc-sankey-node rect{animation:oc-enter-fade-only var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-sankey-link path{animation:oc-enter-fade-only var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-duration) * .3 + var(--oc-animation-stagger) * var(--oc-mark-index,0))}@media (prefers-reduced-motion:reduce){.oc-table-sort-btn:before,.oc-table-sort-btn:after,.oc-table-search input,.oc-table-pagination button{transition:none}.oc-animate .oc-mark-rect rect,.oc-animate .oc-mark-bar rect,.oc-animate .oc-mark-arc,.oc-animate .oc-mark-line,.oc-animate .oc-mark-area,.oc-animate circle.oc-mark-point,.oc-animate circle.oc-mark-circle,.oc-animate .oc-mark-text text,.oc-animate .oc-mark-rule line,.oc-animate .oc-mark-tick line,.oc-animate .oc-mark-label,.oc-animate .oc-annotation,.oc-animate .oc-sankey-node rect,.oc-animate .oc-sankey-link path,.oc-table-wrapper.oc-animate>.oc-chrome,.oc-table-wrapper.oc-animate thead,.oc-table-wrapper.oc-animate tbody tr,.oc-table-wrapper.oc-animate tbody td,.oc-table-wrapper.oc-animate td.oc-table-heatmap,.oc-table-wrapper.oc-animate td.oc-table-category,.oc-table-wrapper.oc-animate .oc-table-bar-fill,.oc-table-wrapper.oc-animate .oc-table-sparkline>svg,.oc-table-wrapper.oc-animate .oc-table-sparkline-dot,.oc-table-wrapper.oc-animate .oc-table-sparkline-labels,.oc-table-wrapper.oc-animate .oc-table-search,.oc-table-wrapper.oc-animate .oc-table-pagination{animation:none}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendata-ai/openchart-react",
3
- "version": "6.5.2",
3
+ "version": "6.6.0",
4
4
  "description": "React components for openchart: <Chart />, <DataTable />, <VizThemeProvider />",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Riley Hilliard",
@@ -49,9 +49,9 @@
49
49
  "typecheck": "tsc --noEmit"
50
50
  },
51
51
  "dependencies": {
52
- "@opendata-ai/openchart-core": "6.5.2",
53
- "@opendata-ai/openchart-engine": "6.5.2",
54
- "@opendata-ai/openchart-vanilla": "6.5.2"
52
+ "@opendata-ai/openchart-core": "6.6.0",
53
+ "@opendata-ai/openchart-engine": "6.6.0",
54
+ "@opendata-ai/openchart-vanilla": "6.6.0"
55
55
  },
56
56
  "peerDependencies": {
57
57
  "react": ">=18.0.0",
package/src/Sankey.tsx ADDED
@@ -0,0 +1,176 @@
1
+ /**
2
+ * React Sankey component: thin wrapper around the vanilla adapter.
3
+ *
4
+ * Mounts a sankey instance on render, updates when spec changes,
5
+ * and cleans up on unmount. All heavy lifting is done by the vanilla
6
+ * createSankey() function.
7
+ *
8
+ * Supports forwardRef for imperative control via the instance getter.
9
+ */
10
+
11
+ import type { DarkMode, SankeySpec, ThemeConfig } from '@opendata-ai/openchart-core';
12
+ import {
13
+ createSankey,
14
+ type SankeyInstance,
15
+ type SankeyMountOptions,
16
+ } from '@opendata-ai/openchart-vanilla';
17
+ import {
18
+ type CSSProperties,
19
+ forwardRef,
20
+ useCallback,
21
+ useEffect,
22
+ useImperativeHandle,
23
+ useRef,
24
+ } from 'react';
25
+ import { useVizDarkMode, useVizTheme } from './ThemeContext';
26
+
27
+ export interface SankeyProps {
28
+ /** The sankey spec to render. */
29
+ spec: SankeySpec;
30
+ /** Theme overrides. */
31
+ theme?: ThemeConfig;
32
+ /** Dark mode: "auto", "force", or "off". */
33
+ darkMode?: DarkMode;
34
+ /** Callback when a node is clicked. */
35
+ onNodeClick?: (node: Record<string, unknown>) => void;
36
+ /** Callback when a link is clicked. */
37
+ onLinkClick?: (link: Record<string, unknown>) => void;
38
+ /** Callback when a node is hovered (null when hover ends). */
39
+ onNodeHover?: (node: Record<string, unknown> | null) => void;
40
+ /** Callback when a link is hovered (null when hover ends). */
41
+ onLinkHover?: (link: Record<string, unknown> | null) => void;
42
+ /** CSS class name for the wrapper div. */
43
+ className?: string;
44
+ /** Inline styles for the wrapper div. */
45
+ style?: CSSProperties;
46
+ }
47
+
48
+ export interface SankeyHandle {
49
+ /** The underlying sankey instance (null until mounted). */
50
+ readonly instance: SankeyInstance | null;
51
+ }
52
+
53
+ /**
54
+ * React component that renders a sankey diagram from a SankeySpec.
55
+ *
56
+ * Uses the vanilla adapter internally. The spec is compiled and rendered
57
+ * as SVG inside a wrapper div. Spec changes trigger re-renders via the
58
+ * vanilla adapter's update() method.
59
+ */
60
+ export const Sankey = forwardRef<SankeyHandle, SankeyProps>(function Sankey(
61
+ {
62
+ spec,
63
+ theme: themeProp,
64
+ darkMode,
65
+ onNodeClick,
66
+ onLinkClick,
67
+ onNodeHover,
68
+ onLinkHover,
69
+ className,
70
+ style,
71
+ },
72
+ ref,
73
+ ) {
74
+ const contextTheme = useVizTheme();
75
+ const contextDarkMode = useVizDarkMode();
76
+ const theme = themeProp ?? contextTheme;
77
+ const resolvedDarkMode = darkMode ?? contextDarkMode;
78
+ const containerRef = useRef<HTMLDivElement>(null);
79
+ const instanceRef = useRef<SankeyInstance | null>(null);
80
+ const specRef = useRef<string>('');
81
+
82
+ // Store event handlers in refs so they don't trigger recreation.
83
+ const handlersRef = useRef<{
84
+ onNodeClick?: SankeyProps['onNodeClick'];
85
+ onLinkClick?: SankeyProps['onLinkClick'];
86
+ onNodeHover?: SankeyProps['onNodeHover'];
87
+ onLinkHover?: SankeyProps['onLinkHover'];
88
+ }>({});
89
+ handlersRef.current = {
90
+ onNodeClick,
91
+ onLinkClick,
92
+ onNodeHover,
93
+ onLinkHover,
94
+ };
95
+
96
+ // Stable callback wrappers that read from refs
97
+ const stableOnNodeClick = useCallback(
98
+ (node: Record<string, unknown>) => handlersRef.current.onNodeClick?.(node),
99
+ [],
100
+ );
101
+ const stableOnLinkClick = useCallback(
102
+ (link: Record<string, unknown>) => handlersRef.current.onLinkClick?.(link),
103
+ [],
104
+ );
105
+ const stableOnNodeHover = useCallback(
106
+ (node: Record<string, unknown> | null) => handlersRef.current.onNodeHover?.(node),
107
+ [],
108
+ );
109
+ const stableOnLinkHover = useCallback(
110
+ (link: Record<string, unknown> | null) => handlersRef.current.onLinkHover?.(link),
111
+ [],
112
+ );
113
+
114
+ // Expose imperative handle
115
+ useImperativeHandle(
116
+ ref,
117
+ () => ({
118
+ get instance() {
119
+ return instanceRef.current;
120
+ },
121
+ }),
122
+ [],
123
+ );
124
+
125
+ // Mount sankey and recreate when theme/darkMode change.
126
+ // biome-ignore lint/correctness/useExhaustiveDependencies: spec intentionally excluded - spec changes handled via update() in Effect 2
127
+ useEffect(() => {
128
+ const container = containerRef.current;
129
+ if (!container) return;
130
+
131
+ const options: SankeyMountOptions = {
132
+ theme,
133
+ darkMode: resolvedDarkMode,
134
+ onNodeClick: stableOnNodeClick,
135
+ onLinkClick: stableOnLinkClick,
136
+ onNodeHover: stableOnNodeHover,
137
+ onLinkHover: stableOnLinkHover,
138
+ responsive: true,
139
+ };
140
+
141
+ instanceRef.current = createSankey(container, spec, options);
142
+ specRef.current = JSON.stringify(spec);
143
+
144
+ return () => {
145
+ instanceRef.current?.destroy();
146
+ instanceRef.current = null;
147
+ };
148
+ }, [
149
+ theme,
150
+ resolvedDarkMode,
151
+ stableOnNodeClick,
152
+ stableOnLinkClick,
153
+ stableOnNodeHover,
154
+ stableOnLinkHover,
155
+ ]);
156
+
157
+ // Update sankey when spec changes.
158
+ useEffect(() => {
159
+ const instance = instanceRef.current;
160
+ if (!instance) return;
161
+
162
+ const specString = JSON.stringify(spec);
163
+ if (specString === specRef.current) return;
164
+
165
+ specRef.current = specString;
166
+ instance.update(spec);
167
+ }, [spec]);
168
+
169
+ return (
170
+ <div
171
+ ref={containerRef}
172
+ className={className ? `oc-sankey-root ${className}` : 'oc-sankey-root'}
173
+ style={style}
174
+ />
175
+ );
176
+ });
@@ -6,11 +6,12 @@
6
6
  */
7
7
 
8
8
  import type { DarkMode, ThemeConfig, VizSpec } from '@opendata-ai/openchart-core';
9
- import { isGraphSpec, isTableSpec } from '@opendata-ai/openchart-core';
9
+ import { isGraphSpec, isSankeySpec, isTableSpec } from '@opendata-ai/openchart-core';
10
10
  import type { CSSProperties } from 'react';
11
11
  import { Chart } from './Chart';
12
12
  import { DataTable } from './DataTable';
13
13
  import { Graph } from './Graph';
14
+ import { Sankey } from './Sankey';
14
15
 
15
16
  export interface VisualizationProps {
16
17
  /** The visualization spec to render. */
@@ -48,6 +49,11 @@ export function Visualization({ spec, theme, darkMode, className, style }: Visua
48
49
  <Graph spec={spec} theme={theme} darkMode={darkMode} className={className} style={style} />
49
50
  );
50
51
  }
52
+ if (isSankeySpec(spec)) {
53
+ return (
54
+ <Sankey spec={spec} theme={theme} darkMode={darkMode} className={className} style={style} />
55
+ );
56
+ }
51
57
  return (
52
58
  <Chart spec={spec} theme={theme} darkMode={darkMode} className={className} style={style} />
53
59
  );
package/src/index.ts CHANGED
@@ -44,6 +44,7 @@ export type {
44
44
  NormalizedChartSpec,
45
45
  NormalizedChrome,
46
46
  NormalizedGraphSpec,
47
+ NormalizedSankeySpec,
47
48
  NormalizedSpec,
48
49
  NormalizedTableSpec,
49
50
  SimulationConfig,
@@ -56,6 +57,7 @@ export {
56
57
  compile,
57
58
  compileChart,
58
59
  compileGraph,
60
+ compileSankey,
59
61
  compileTable,
60
62
  getChartRenderer,
61
63
  normalizeSpec,
@@ -79,6 +81,8 @@ export type { UseTableReturn } from './hooks/useTable';
79
81
  export { useTable } from './hooks/useTable';
80
82
  export type { UseTableStateOptions, UseTableStateReturn } from './hooks/useTableState';
81
83
  export { useTableState } from './hooks/useTableState';
84
+ export type { SankeyHandle, SankeyProps } from './Sankey';
85
+ export { Sankey } from './Sankey';
82
86
  export type { VizThemeProviderProps } from './ThemeContext';
83
87
  // Theme context
84
88
  export { useVizDarkMode, useVizTheme, VizThemeProvider } from './ThemeContext';