@opendata-ai/openchart-vue 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,95 @@
1
+ /**
2
+ * useChart: composable for manual chart lifecycle control.
3
+ *
4
+ * Provides a template ref to attach to a container div. The chart
5
+ * mounts automatically and updates when the spec changes.
6
+ */
7
+
8
+ import type {
9
+ ChartLayout,
10
+ ChartSpec,
11
+ DarkMode,
12
+ GraphSpec,
13
+ ThemeConfig,
14
+ } from '@opendata-ai/openchart-core';
15
+ import { type ChartInstance, createChart, type MountOptions } from '@opendata-ai/openchart-vanilla';
16
+ import { onMounted, onUnmounted, type Ref, ref, type ShallowRef, shallowRef, watch } from 'vue';
17
+
18
+ export interface UseChartOptions {
19
+ /** Theme overrides. */
20
+ theme?: ThemeConfig;
21
+ /** Dark mode setting. */
22
+ darkMode?: DarkMode;
23
+ /** Data point click handler. */
24
+ onDataPointClick?: MountOptions['onDataPointClick'];
25
+ /** Enable responsive resizing. Defaults to true. */
26
+ responsive?: boolean;
27
+ }
28
+
29
+ export interface UseChartReturn {
30
+ /** Template ref to attach to the container div. */
31
+ containerRef: Ref<HTMLDivElement | null>;
32
+ /** The chart instance (null until mounted). */
33
+ chart: ShallowRef<ChartInstance | null>;
34
+ /** The current compiled layout (null until mounted). */
35
+ layout: ShallowRef<ChartLayout | null>;
36
+ }
37
+
38
+ /**
39
+ * Composable for manual chart lifecycle control.
40
+ *
41
+ * Attach the returned containerRef to a container div via `ref="containerRef"`.
42
+ * The chart mounts automatically and updates when the spec changes.
43
+ */
44
+ export function useChart(
45
+ spec: Ref<ChartSpec | GraphSpec>,
46
+ options?: UseChartOptions,
47
+ ): UseChartReturn {
48
+ const containerRef = ref<HTMLDivElement | null>(null);
49
+ const chart = shallowRef<ChartInstance | null>(null);
50
+ const layout = shallowRef<ChartLayout | null>(null);
51
+
52
+ function mount() {
53
+ const container = containerRef.value;
54
+ if (!container) return;
55
+
56
+ const mountOpts: MountOptions = {
57
+ theme: options?.theme,
58
+ darkMode: options?.darkMode,
59
+ onDataPointClick: options?.onDataPointClick,
60
+ responsive: options?.responsive,
61
+ };
62
+
63
+ const instance = createChart(container, spec.value, mountOpts);
64
+ chart.value = instance;
65
+ layout.value = instance.layout;
66
+ }
67
+
68
+ function destroy() {
69
+ chart.value?.destroy();
70
+ chart.value = null;
71
+ layout.value = null;
72
+ }
73
+
74
+ onMounted(() => {
75
+ mount();
76
+ });
77
+
78
+ onUnmounted(() => {
79
+ destroy();
80
+ });
81
+
82
+ // Update on spec change
83
+ watch(spec, (newSpec) => {
84
+ const instance = chart.value;
85
+ if (!instance) return;
86
+ instance.update(newSpec);
87
+ layout.value = instance.layout;
88
+ });
89
+
90
+ return {
91
+ containerRef,
92
+ chart,
93
+ layout,
94
+ };
95
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * useDarkMode: composable that resolves a DarkMode preference to a boolean.
3
+ *
4
+ * - "force" -> true
5
+ * - "off" -> false
6
+ * - "auto" -> matches system preference (reactive to changes)
7
+ */
8
+
9
+ import type { DarkMode } from '@opendata-ai/openchart-core';
10
+ import { onUnmounted, type Ref, ref, watch } from 'vue';
11
+
12
+ /**
13
+ * Resolve a DarkMode preference to a reactive boolean.
14
+ *
15
+ * For "auto" mode, watches the system `prefers-color-scheme` media query
16
+ * and updates reactively when the user changes their OS theme.
17
+ */
18
+ export function useDarkMode(mode: Ref<DarkMode | undefined>): Ref<boolean> {
19
+ const isDark = ref(resolveInitial(mode.value));
20
+ let cleanup: (() => void) | null = null;
21
+
22
+ function setup(currentMode: DarkMode | undefined) {
23
+ // Clean up previous listener
24
+ cleanup?.();
25
+ cleanup = null;
26
+
27
+ if (currentMode !== 'auto') {
28
+ isDark.value = currentMode === 'force';
29
+ return;
30
+ }
31
+
32
+ if (typeof window === 'undefined' || !window.matchMedia) {
33
+ isDark.value = false;
34
+ return;
35
+ }
36
+
37
+ const mq = window.matchMedia('(prefers-color-scheme: dark)');
38
+ isDark.value = mq.matches;
39
+
40
+ const handler = (e: MediaQueryListEvent) => {
41
+ isDark.value = e.matches;
42
+ };
43
+ mq.addEventListener('change', handler);
44
+ cleanup = () => mq.removeEventListener('change', handler);
45
+ }
46
+
47
+ // Run setup for initial value
48
+ setup(mode.value);
49
+
50
+ // React to mode changes
51
+ watch(mode, (newMode) => {
52
+ setup(newMode);
53
+ });
54
+
55
+ onUnmounted(() => {
56
+ cleanup?.();
57
+ cleanup = null;
58
+ });
59
+
60
+ return isDark;
61
+ }
62
+
63
+ function resolveInitial(mode?: DarkMode): boolean {
64
+ if (mode === 'force') return true;
65
+ if (mode === 'off' || mode === undefined) return false;
66
+ // "auto"
67
+ if (typeof window !== 'undefined' && window.matchMedia) {
68
+ return window.matchMedia('(prefers-color-scheme: dark)').matches;
69
+ }
70
+ return false;
71
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * useGraph: composable for imperative graph control.
3
+ *
4
+ * Provides a template ref to pass to <Graph /> and exposes graph methods
5
+ * (search, zoom, select) for programmatic control of the graph instance.
6
+ */
7
+
8
+ import type { GraphInstance } from '@opendata-ai/openchart-vanilla';
9
+ import { type Ref, ref } from 'vue';
10
+
11
+ /** Handle exposed by Graph component via expose(). */
12
+ export interface GraphHandle {
13
+ search: (query: string) => void;
14
+ clearSearch: () => void;
15
+ zoomToFit: () => void;
16
+ zoomToNode: (nodeId: string) => void;
17
+ selectNode: (nodeId: string) => void;
18
+ getSelectedNodes: () => string[];
19
+ /** The underlying GraphInstance from the vanilla adapter. */
20
+ instance: GraphInstance | null;
21
+ }
22
+
23
+ export interface UseGraphReturn {
24
+ /** Template ref to pass to <Graph ref={graphRef} />. */
25
+ graphRef: Ref<GraphHandle | null>;
26
+ /** Search for nodes matching a query string. */
27
+ search: (query: string) => void;
28
+ /** Clear the current search. */
29
+ clearSearch: () => void;
30
+ /** Zoom to fit all nodes in view. */
31
+ zoomToFit: () => void;
32
+ /** Zoom and center on a specific node. */
33
+ zoomToNode: (nodeId: string) => void;
34
+ /** Select a node by id. */
35
+ selectNode: (nodeId: string) => void;
36
+ /** Get the currently selected node ids. */
37
+ getSelectedNodes: () => string[];
38
+ }
39
+
40
+ /**
41
+ * Composable for imperative graph control.
42
+ *
43
+ * Usage:
44
+ * ```vue
45
+ * <script setup>
46
+ * const { graphRef, search, zoomToFit } = useGraph();
47
+ * </script>
48
+ * <template>
49
+ * <Graph ref="graphRef" :spec="spec" />
50
+ * </template>
51
+ * ```
52
+ */
53
+ export function useGraph(): UseGraphReturn {
54
+ const graphRef = ref<GraphHandle | null>(null);
55
+
56
+ function search(query: string) {
57
+ graphRef.value?.search(query);
58
+ }
59
+
60
+ function clearSearch() {
61
+ graphRef.value?.clearSearch();
62
+ }
63
+
64
+ function zoomToFit() {
65
+ graphRef.value?.zoomToFit();
66
+ }
67
+
68
+ function zoomToNode(nodeId: string) {
69
+ graphRef.value?.zoomToNode(nodeId);
70
+ }
71
+
72
+ function selectNode(nodeId: string) {
73
+ graphRef.value?.selectNode(nodeId);
74
+ }
75
+
76
+ function getSelectedNodes(): string[] {
77
+ return graphRef.value?.getSelectedNodes() ?? [];
78
+ }
79
+
80
+ return {
81
+ graphRef,
82
+ search,
83
+ clearSearch,
84
+ zoomToFit,
85
+ zoomToNode,
86
+ selectNode,
87
+ getSelectedNodes,
88
+ };
89
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * useTable: composable for manual table lifecycle control.
3
+ *
4
+ * Attaches to a container ref, mounts a vanilla table instance,
5
+ * and exposes the instance and current state.
6
+ */
7
+
8
+ import type { TableSpec } from '@opendata-ai/openchart-core';
9
+ import {
10
+ createTable,
11
+ type TableInstance,
12
+ type TableMountOptions,
13
+ type TableState,
14
+ } from '@opendata-ai/openchart-vanilla';
15
+ import { onMounted, onUnmounted, type Ref, ref, type ShallowRef, shallowRef, watch } from 'vue';
16
+
17
+ export interface UseTableReturn {
18
+ /** Template ref to attach to the container div. */
19
+ containerRef: Ref<HTMLDivElement | null>;
20
+ /** The table instance (null until mounted). */
21
+ table: ShallowRef<TableInstance | null>;
22
+ /** The current table state (sort, search, page). */
23
+ state: Ref<TableState>;
24
+ }
25
+
26
+ /**
27
+ * Composable for manual table lifecycle control.
28
+ *
29
+ * Attach the returned containerRef to a container div via `ref="containerRef"`.
30
+ * The table mounts automatically and updates when the spec changes.
31
+ */
32
+ export function useTable(spec: Ref<TableSpec>, options?: TableMountOptions): UseTableReturn {
33
+ const containerRef = ref<HTMLDivElement | null>(null);
34
+ const table = shallowRef<TableInstance | null>(null);
35
+ const state = ref<TableState>({
36
+ sort: null,
37
+ search: '',
38
+ page: 0,
39
+ });
40
+
41
+ const originalOnStateChange = options?.onStateChange;
42
+
43
+ function handleStateChange(newState: TableState) {
44
+ state.value = newState;
45
+ originalOnStateChange?.(newState);
46
+ }
47
+
48
+ function mount() {
49
+ const container = containerRef.value;
50
+ if (!container) return;
51
+
52
+ const mountOpts: TableMountOptions = {
53
+ ...options,
54
+ onStateChange: handleStateChange,
55
+ };
56
+
57
+ const instance = createTable(container, spec.value, mountOpts);
58
+ table.value = instance;
59
+ state.value = instance.getState();
60
+ }
61
+
62
+ function destroy() {
63
+ table.value?.destroy();
64
+ table.value = null;
65
+ }
66
+
67
+ onMounted(() => {
68
+ mount();
69
+ });
70
+
71
+ onUnmounted(() => {
72
+ destroy();
73
+ });
74
+
75
+ // Update on spec change
76
+ watch(spec, (newSpec) => {
77
+ const instance = table.value;
78
+ if (!instance) return;
79
+ instance.update(newSpec);
80
+ state.value = instance.getState();
81
+ });
82
+
83
+ return {
84
+ containerRef,
85
+ table,
86
+ state,
87
+ };
88
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * useTableState: managed state composable 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 { type Ref, ref } from 'vue';
10
+
11
+ export interface UseTableStateReturn {
12
+ sort: Ref<SortState | null>;
13
+ setSort: (sort: SortState | null) => void;
14
+ search: Ref<string>;
15
+ setSearch: (query: string) => void;
16
+ page: Ref<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
+ * Composable for managing table state (sort, search, page).
29
+ *
30
+ * Use with the DataTable component's controlled props:
31
+ * ```vue
32
+ * <script setup>
33
+ * const { sort, search, page, setSort, setSearch, setPage } = useTableState();
34
+ * </script>
35
+ * <template>
36
+ * <DataTable
37
+ * :spec="spec"
38
+ * :sort="sort"
39
+ * :search="search"
40
+ * :page="page"
41
+ * @update:sort="setSort"
42
+ * @update:search="setSearch"
43
+ * @update:page="setPage"
44
+ * />
45
+ * </template>
46
+ * ```
47
+ */
48
+ export function useTableState(initialState?: UseTableStateOptions): UseTableStateReturn {
49
+ const sort = ref<SortState | null>(initialState?.sort ?? null);
50
+ const search = ref(initialState?.search ?? '');
51
+ const page = ref(initialState?.page ?? 0);
52
+
53
+ function setSort(newSort: SortState | null) {
54
+ sort.value = newSort;
55
+ }
56
+
57
+ function setSearch(query: string) {
58
+ search.value = query;
59
+ }
60
+
61
+ function setPage(newPage: number) {
62
+ page.value = newPage;
63
+ }
64
+
65
+ function resetState() {
66
+ sort.value = initialState?.sort ?? null;
67
+ search.value = initialState?.search ?? '';
68
+ page.value = initialState?.page ?? 0;
69
+ }
70
+
71
+ return {
72
+ sort,
73
+ setSort,
74
+ search,
75
+ setSearch,
76
+ page,
77
+ setPage,
78
+ resetState,
79
+ };
80
+ }
package/src/context.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Theme injection context for Vue.
3
+ *
4
+ * Provides typed injection keys and composables for accessing theme
5
+ * and dark mode from any descendant component within a VizThemeProvider.
6
+ */
7
+
8
+ import type { DarkMode, ThemeConfig } from '@opendata-ai/openchart-core';
9
+ import { type ComputedRef, computed, type InjectionKey, inject, type Ref } from 'vue';
10
+
11
+ /** Injection key for the theme config ref. */
12
+ export const VizThemeKey: InjectionKey<Ref<ThemeConfig | undefined>> = Symbol('VizTheme');
13
+
14
+ /** Injection key for the dark mode ref. */
15
+ export const VizDarkModeKey: InjectionKey<Ref<DarkMode | undefined>> = Symbol('VizDarkMode');
16
+
17
+ /**
18
+ * Read the current theme from the nearest VizThemeProvider.
19
+ * Returns a computed ref that stays reactive to provider changes.
20
+ */
21
+ export function useVizTheme(): ComputedRef<ThemeConfig | undefined> {
22
+ const theme = inject(VizThemeKey, undefined);
23
+ return computed(() => theme?.value);
24
+ }
25
+
26
+ /**
27
+ * Read the current dark mode preference from the nearest VizThemeProvider.
28
+ * Returns a computed ref that stays reactive to provider changes.
29
+ */
30
+ export function useVizDarkMode(): ComputedRef<DarkMode | undefined> {
31
+ const darkMode = inject(VizDarkModeKey, undefined);
32
+ return computed(() => darkMode?.value);
33
+ }
package/src/index.ts ADDED
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @opendata-ai/openchart-vue
3
+ *
4
+ * Vue 3 adapter for openchart. Provides <Chart />, <DataTable />, <Graph />,
5
+ * and <VizThemeProvider /> components that wrap the vanilla adapter with
6
+ * Vue lifecycle management.
7
+ */
8
+
9
+ // Re-export core types for convenience
10
+ export type {
11
+ ChartLayout,
12
+ ChartSpec,
13
+ CompileOptions,
14
+ TableLayout,
15
+ TableSpec,
16
+ VizSpec,
17
+ } from '@opendata-ai/openchart-engine';
18
+
19
+ // Components
20
+ export type { ChartProps } from './Chart';
21
+ export { Chart } from './Chart';
22
+ // Composables
23
+ export type { UseChartOptions, UseChartReturn } from './composables/useChart';
24
+ export { useChart } from './composables/useChart';
25
+ export { useDarkMode } from './composables/useDarkMode';
26
+ export type { GraphHandle, UseGraphReturn } from './composables/useGraph';
27
+ export { useGraph } from './composables/useGraph';
28
+ export type { UseTableReturn } from './composables/useTable';
29
+ export { useTable } from './composables/useTable';
30
+ export type { UseTableStateOptions, UseTableStateReturn } from './composables/useTableState';
31
+ export { useTableState } from './composables/useTableState';
32
+ // Context (injection keys and composables)
33
+ export { useVizDarkMode, useVizTheme, VizDarkModeKey, VizThemeKey } from './context';
34
+ export type { DataTableProps } from './DataTable';
35
+ export { DataTable } from './DataTable';
36
+ export type { GraphProps } from './Graph';
37
+ export { Graph } from './Graph';
38
+ export type { VizThemeProviderProps } from './ThemeProvider';
39
+ export { VizThemeProvider } from './ThemeProvider';
40
+ export type { VisualizationProps } from './Visualization';
41
+ export { Visualization } from './Visualization';