@opendata-ai/openchart-react 2.0.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.
@@ -0,0 +1,76 @@
1
+ /**
2
+ * useTableState: managed state hook for controlled table usage.
3
+ *
4
+ * Provides individual sort/search/page state with setters and a
5
+ * resetState function to return to initial values.
6
+ */
7
+
8
+ import type { SortState } from '@opendata-ai/openchart-core';
9
+ import { useCallback, useState } from 'react';
10
+
11
+ export interface UseTableStateReturn {
12
+ sort: SortState | null;
13
+ setSort: (sort: SortState | null) => void;
14
+ search: string;
15
+ setSearch: (query: string) => void;
16
+ page: number;
17
+ setPage: (page: number) => void;
18
+ resetState: () => void;
19
+ }
20
+
21
+ export interface UseTableStateOptions {
22
+ sort?: SortState | null;
23
+ search?: string;
24
+ page?: number;
25
+ }
26
+
27
+ /**
28
+ * Hook for managing table state (sort, search, page).
29
+ *
30
+ * Use with the DataTable component's controlled props:
31
+ * ```tsx
32
+ * const { sort, search, page, setSort, setSearch, setPage } = useTableState();
33
+ * <DataTable
34
+ * spec={spec}
35
+ * sort={sort}
36
+ * search={search}
37
+ * page={page}
38
+ * onSortChange={setSort}
39
+ * onSearchChange={setSearch}
40
+ * onPageChange={setPage}
41
+ * />
42
+ * ```
43
+ */
44
+ export function useTableState(initialState?: UseTableStateOptions): UseTableStateReturn {
45
+ const [sort, setSortInternal] = useState<SortState | null>(initialState?.sort ?? null);
46
+ const [search, setSearchInternal] = useState(initialState?.search ?? '');
47
+ const [page, setPageInternal] = useState(initialState?.page ?? 0);
48
+
49
+ const setSort = useCallback((newSort: SortState | null) => {
50
+ setSortInternal(newSort);
51
+ }, []);
52
+
53
+ const setSearch = useCallback((query: string) => {
54
+ setSearchInternal(query);
55
+ }, []);
56
+
57
+ const setPage = useCallback((newPage: number) => {
58
+ setPageInternal(newPage);
59
+ }, []);
60
+
61
+ const resetState = useCallback(() => {
62
+ setSortInternal(initialState?.sort ?? null);
63
+ setSearchInternal(initialState?.search ?? '');
64
+ setPageInternal(initialState?.page ?? 0);
65
+ }, [initialState?.sort, initialState?.search, initialState?.page]);
66
+
67
+ return {
68
+ sort,
69
+ setSort,
70
+ search,
71
+ setSearch,
72
+ page,
73
+ setPage,
74
+ resetState,
75
+ };
76
+ }
package/src/hooks.ts ADDED
@@ -0,0 +1,145 @@
1
+ /**
2
+ * React hooks for chart lifecycle and dark mode resolution.
3
+ *
4
+ * useChart: manual control over a chart instance (for advanced usage).
5
+ * useDarkMode: resolves the DarkMode preference to a boolean.
6
+ */
7
+
8
+ import type { ChartLayout, ChartSpec, DarkMode, GraphSpec } from '@opendata-ai/openchart-core';
9
+ import { type ChartInstance, createChart, type MountOptions } from '@opendata-ai/openchart-vanilla';
10
+ import { useEffect, useRef, useState } from 'react';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // useChart
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export interface UseChartOptions {
17
+ /** Theme overrides. */
18
+ theme?: MountOptions['theme'];
19
+ /** Dark mode setting. */
20
+ darkMode?: MountOptions['darkMode'];
21
+ /** Data point click handler. */
22
+ onDataPointClick?: MountOptions['onDataPointClick'];
23
+ /** Enable responsive resizing. Defaults to true. */
24
+ responsive?: boolean;
25
+ }
26
+
27
+ export interface UseChartReturn {
28
+ /** Ref to attach to the container div. */
29
+ ref: React.RefObject<HTMLDivElement | null>;
30
+ /** The chart instance (null until mounted). */
31
+ chart: ChartInstance | null;
32
+ /** The current compiled layout (null until mounted). */
33
+ layout: ChartLayout | null;
34
+ }
35
+
36
+ /**
37
+ * Hook for manual chart lifecycle control.
38
+ *
39
+ * Attach the returned ref to a container div. The chart mounts
40
+ * automatically and updates when the spec changes.
41
+ *
42
+ * @param spec - The visualization spec.
43
+ * @param options - Mount options.
44
+ * @returns { ref, chart, layout }
45
+ */
46
+ export function useChart(spec: ChartSpec | GraphSpec, options?: UseChartOptions): UseChartReturn {
47
+ const ref = useRef<HTMLDivElement | null>(null);
48
+ const chartRef = useRef<ChartInstance | null>(null);
49
+ const [layout, setLayout] = useState<ChartLayout | null>(null);
50
+ const specRef = useRef<string>('');
51
+
52
+ // Mount / unmount
53
+ // biome-ignore lint/correctness/useExhaustiveDependencies: spec intentionally excluded - spec changes handled via update() in the update effect
54
+ useEffect(() => {
55
+ const container = ref.current;
56
+ if (!container) return;
57
+
58
+ const mountOpts: MountOptions = {
59
+ theme: options?.theme,
60
+ darkMode: options?.darkMode,
61
+ onDataPointClick: options?.onDataPointClick,
62
+ responsive: options?.responsive,
63
+ };
64
+
65
+ const chart = createChart(container, spec, mountOpts);
66
+ chartRef.current = chart;
67
+ setLayout(chart.layout);
68
+ specRef.current = JSON.stringify(spec);
69
+
70
+ return () => {
71
+ chart.destroy();
72
+ chartRef.current = null;
73
+ setLayout(null);
74
+ };
75
+ // eslint-disable-next-line react-hooks/exhaustive-deps
76
+ }, [options?.theme, options?.darkMode, options?.onDataPointClick, options?.responsive]);
77
+
78
+ // Update on spec change
79
+ useEffect(() => {
80
+ const chart = chartRef.current;
81
+ if (!chart) return;
82
+
83
+ const specString = JSON.stringify(spec);
84
+ if (specString !== specRef.current) {
85
+ specRef.current = specString;
86
+ chart.update(spec);
87
+ setLayout(chart.layout);
88
+ }
89
+ }, [spec]);
90
+
91
+ return {
92
+ ref,
93
+ chart: chartRef.current,
94
+ layout,
95
+ };
96
+ }
97
+
98
+ // ---------------------------------------------------------------------------
99
+ // useDarkMode
100
+ // ---------------------------------------------------------------------------
101
+
102
+ /**
103
+ * Resolve a DarkMode preference to a boolean.
104
+ *
105
+ * - "force" -> true
106
+ * - "off" -> false
107
+ * - "auto" -> matches system preference (reactive to changes)
108
+ *
109
+ * @param mode - The dark mode preference.
110
+ * @returns Whether dark mode is active.
111
+ */
112
+ export function useDarkMode(mode?: DarkMode): boolean {
113
+ const [isDark, setIsDark] = useState(() => resolveInitial(mode));
114
+
115
+ useEffect(() => {
116
+ if (mode !== 'auto') {
117
+ setIsDark(mode === 'force');
118
+ return;
119
+ }
120
+
121
+ if (typeof window === 'undefined' || !window.matchMedia) {
122
+ setIsDark(false);
123
+ return;
124
+ }
125
+
126
+ const mq = window.matchMedia('(prefers-color-scheme: dark)');
127
+ setIsDark(mq.matches);
128
+
129
+ const handler = (e: MediaQueryListEvent) => setIsDark(e.matches);
130
+ mq.addEventListener('change', handler);
131
+ return () => mq.removeEventListener('change', handler);
132
+ }, [mode]);
133
+
134
+ return isDark;
135
+ }
136
+
137
+ function resolveInitial(mode?: DarkMode): boolean {
138
+ if (mode === 'force') return true;
139
+ if (mode === 'off' || mode === undefined) return false;
140
+ // "auto"
141
+ if (typeof window !== 'undefined' && window.matchMedia) {
142
+ return window.matchMedia('(prefers-color-scheme: dark)').matches;
143
+ }
144
+ return false;
145
+ }
package/src/index.ts ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @opendata-ai/openchart-react
3
+ *
4
+ * React adapter for openchart. Provides <Chart /> and <DataTable />
5
+ * components that wrap the vanilla adapter with React lifecycle management.
6
+ */
7
+
8
+ // Re-export core types for convenience
9
+ export type {
10
+ ChartLayout,
11
+ ChartSpec,
12
+ CompileOptions,
13
+ TableLayout,
14
+ TableSpec,
15
+ VizSpec,
16
+ } from '@opendata-ai/openchart-engine';
17
+ export type { ChartProps } from './Chart';
18
+ // Components
19
+ export { Chart } from './Chart';
20
+ export type { DataTableProps } from './DataTable';
21
+ export { DataTable } from './DataTable';
22
+ export type { GraphProps } from './Graph';
23
+ export { Graph } from './Graph';
24
+ export type { UseChartOptions, UseChartReturn } from './hooks';
25
+ // Hooks
26
+ export { useChart, useDarkMode } from './hooks';
27
+ export type { GraphHandle, UseGraphReturn } from './hooks/useGraph';
28
+ export { useGraph } from './hooks/useGraph';
29
+ export type { UseTableReturn } from './hooks/useTable';
30
+ export { useTable } from './hooks/useTable';
31
+ export type { UseTableStateOptions, UseTableStateReturn } from './hooks/useTableState';
32
+ export { useTableState } from './hooks/useTableState';
33
+ export type { VizThemeProviderProps } from './ThemeContext';
34
+ // Theme context
35
+ export { useVizDarkMode, useVizTheme, VizThemeProvider } from './ThemeContext';
36
+ export type { VisualizationProps } from './Visualization';
37
+ export { Visualization } from './Visualization';