@graphein/react 0.3.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Sachin Patney
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @graphein/react
2
+
3
+ > The React wrapper for [Graphein](https://github.com/spatney/graphein) — a beautiful,
4
+ > high-performance, **agent-first** data visualization library.
5
+
6
+ Install alongside `react` (18+, a peer dependency):
7
+
8
+ ```bash
9
+ npm install @graphein/react react
10
+ ```
11
+
12
+ ```tsx
13
+ import { Chart } from '@graphein/react';
14
+ import type { ChartSpec } from 'graphein';
15
+
16
+ const spec: ChartSpec = {
17
+ type: 'bar',
18
+ data: [
19
+ { quarter: 'Q1', revenue: 240 },
20
+ { quarter: 'Q2', revenue: 310 },
21
+ { quarter: 'Q3', revenue: 280 },
22
+ { quarter: 'Q4', revenue: 360 },
23
+ ],
24
+ encoding: { x: { field: 'quarter' }, y: { field: 'revenue' } },
25
+ };
26
+
27
+ export function Dashboard() {
28
+ return (
29
+ <div style={{ height: 360 }}>
30
+ <Chart spec={spec} />
31
+ </div>
32
+ );
33
+ }
34
+ ```
35
+
36
+ `<Chart spec={…} />` renders into a fill-by-default container; pass a new `spec`
37
+ to update in place, and it tears down on unmount. For headless control over your
38
+ own element, use the `useChart(spec)` hook (returns a ref to attach).
39
+
40
+ ## Documentation
41
+
42
+ - [README](https://github.com/spatney/graphein#readme)
43
+ - [Agent Guide](https://github.com/spatney/graphein/blob/main/docs/agent-guide.md)
44
+ - [Spec Reference](https://github.com/spatney/graphein/blob/main/docs/spec-reference.md)
45
+
46
+ MIT © Sachin Patney
@@ -0,0 +1,133 @@
1
+ import { RefObject, CSSProperties, ReactElement } from 'react';
2
+ import { ChartInstance, SelectionStore, SelectionChangeListener, ChartSpec, DashboardInstance, DashboardSpec, SelectionValue } from 'graphein';
3
+ export { ChartInstance, ChartSpec, DashboardInstance, DashboardSpec, SelectionChangeListener, SelectionStore, SelectionValue, createSelectionStore } from 'graphein';
4
+
5
+ interface UseChartOptions {
6
+ /** Called with the live instance after each mount and update. */
7
+ onReady?: (instance: ChartInstance) => void;
8
+ /**
9
+ * A shared selection bus. Pass the same store to several charts (or a
10
+ * dashboard) to link them — cross-highlight / cross-filter. Should be stable
11
+ * across renders (e.g. `useMemo(() => createSelectionStore(), [])`); changing
12
+ * its identity remounts the chart.
13
+ */
14
+ store?: SelectionStore;
15
+ /** Called whenever any selection this chart is bound to changes. */
16
+ onSelectionChange?: SelectionChangeListener;
17
+ }
18
+ /**
19
+ * Mount an Graphein chart into a DOM node and keep it in sync with `spec`.
20
+ *
21
+ * Returns a ref to attach to the container element. The chart is created on
22
+ * mount, re-rendered via `instance.update()` whenever `spec` changes identity,
23
+ * and torn down on unmount. The effect lifecycle is StrictMode-safe (double
24
+ * mount → destroy → mount produces no leaked instances or duplicate renders).
25
+ *
26
+ * ```tsx
27
+ * const ref = useChart(spec);
28
+ * return <div ref={ref} style={{ width: '100%', height: 360 }} />;
29
+ * ```
30
+ */
31
+ declare function useChart<T extends HTMLElement = HTMLDivElement>(spec: ChartSpec, options?: UseChartOptions): RefObject<T | null>;
32
+
33
+ interface ChartProps extends UseChartOptions {
34
+ /** The Graphein chart spec to render. */
35
+ spec: ChartSpec;
36
+ className?: string;
37
+ style?: CSSProperties;
38
+ }
39
+ /**
40
+ * Declarative React wrapper around the Graphein core runtime.
41
+ *
42
+ * Renders a container `<div>` that fills its parent (override via `style`) and
43
+ * draws `spec` into it. Pass a new `spec` to update; unmounting tears the chart
44
+ * down. Give the parent an explicit size for best results.
45
+ *
46
+ * ```tsx
47
+ * <div style={{ height: 360 }}>
48
+ * <Chart spec={{ type: 'line', data, encoding: { x, y } }} />
49
+ * </div>
50
+ * ```
51
+ */
52
+ declare function Chart({ spec, className, style, ...options }: ChartProps): ReactElement;
53
+
54
+ interface UseDashboardOptions {
55
+ /** Called with the live instance after each mount and update. */
56
+ onReady?: (instance: DashboardInstance) => void;
57
+ /**
58
+ * A shared selection bus. Omit to let the dashboard own one internally; pass
59
+ * one (stable across renders) to drive it from React via {@link useSelection}
60
+ * or to link it with standalone charts. Changing its identity remounts.
61
+ */
62
+ store?: SelectionStore;
63
+ /** Called whenever any selection in the dashboard changes. */
64
+ onSelectionChange?: SelectionChangeListener;
65
+ }
66
+ /**
67
+ * Mount an Graphein dashboard into a DOM node and keep it in sync with `spec`.
68
+ *
69
+ * Mirrors {@link useChart}: created on mount, updated in place when `spec`
70
+ * changes identity, torn down on unmount, StrictMode-safe.
71
+ *
72
+ * ```tsx
73
+ * const ref = useDashboard(dashboardSpec);
74
+ * return <div ref={ref} style={{ width: '100%' }} />;
75
+ * ```
76
+ */
77
+ declare function useDashboard<T extends HTMLElement = HTMLDivElement>(spec: DashboardSpec, options?: UseDashboardOptions): RefObject<T | null>;
78
+
79
+ interface DashboardProps extends UseDashboardOptions {
80
+ /** The Graphein dashboard spec to render. */
81
+ spec: DashboardSpec;
82
+ className?: string;
83
+ style?: CSSProperties;
84
+ }
85
+ /**
86
+ * Declarative React wrapper around the Graphein dashboard runtime.
87
+ *
88
+ * Renders a container `<div>` (full width by default; height comes from the
89
+ * dashboard's grid rows) and draws `spec` into it. Pass a new `spec` to update;
90
+ * unmounting tears it down. Auto-wires cross-interaction unless `spec` opts out.
91
+ *
92
+ * ```tsx
93
+ * <Dashboard spec={{ type: 'dashboard', data, views, interactions: 'auto' }} />
94
+ * ```
95
+ */
96
+ declare function Dashboard({ spec, className, style, ...options }: DashboardProps): ReactElement;
97
+
98
+ /** Anything that exposes a selection bus: a store, or a chart/dashboard instance. */
99
+ type SelectionSource = SelectionStore | {
100
+ store: SelectionStore;
101
+ } | null | undefined;
102
+ /**
103
+ * Read and write a named selection as React state.
104
+ *
105
+ * Pass a shared {@link SelectionStore} (or a chart/dashboard instance, which
106
+ * exposes one as `.store`) plus the param name. Returns the current value and a
107
+ * setter that publishes to the bus — driving cross-highlight / cross-filter
108
+ * across every visual bound to that store.
109
+ *
110
+ * ```tsx
111
+ * const store = useMemo(() => createSelectionStore(), []);
112
+ * const [region, setRegion] = useSelection(store, 'region');
113
+ * // …
114
+ * <Dashboard spec={spec} store={store} />
115
+ * <button onClick={() => setRegion({ kind: 'set', field: 'region', values: ['West'] })}>
116
+ * West
117
+ * </button>
118
+ * ```
119
+ */
120
+ declare function useSelection(source: SelectionSource, name: string): [SelectionValue | null, (value: SelectionValue | null) => void];
121
+
122
+ /**
123
+ * @graphein/react — a thin React wrapper around the `graphein` runtime.
124
+ *
125
+ * `<Chart spec={…} />` for a single chart or `<Dashboard spec={…} />` for a
126
+ * cross-interacting page; the `useChart` / `useDashboard` hooks give headless
127
+ * control over your own container, and `useSelection` reads/writes a selection
128
+ * as React state. Core spec/instance types are re-exported for convenience.
129
+ */
130
+
131
+ declare const VERSION = "0.0.0";
132
+
133
+ export { Chart, type ChartProps, Dashboard, type DashboardProps, type SelectionSource, type UseChartOptions, type UseDashboardOptions, VERSION, useChart, useDashboard, useSelection };
package/dist/index.js ADDED
@@ -0,0 +1,172 @@
1
+ // src/Chart.ts
2
+ import { createElement } from "react";
3
+
4
+ // src/useChart.ts
5
+ import { useEffect, useRef } from "react";
6
+ import {
7
+ render
8
+ } from "graphein";
9
+ function useChart(spec, options = {}) {
10
+ const ref = useRef(null);
11
+ const instanceRef = useRef(null);
12
+ const specRef = useRef(spec);
13
+ specRef.current = spec;
14
+ const onReadyRef = useRef(options.onReady);
15
+ onReadyRef.current = options.onReady;
16
+ const onSelectionChangeRef = useRef(options.onSelectionChange);
17
+ onSelectionChangeRef.current = options.onSelectionChange;
18
+ const storeRef = useRef(options.store);
19
+ storeRef.current = options.store;
20
+ const skipNextUpdate = useRef(true);
21
+ useEffect(() => {
22
+ const el = ref.current;
23
+ if (!el) return;
24
+ const instance = render(
25
+ el,
26
+ specRef.current,
27
+ storeRef.current ? { store: storeRef.current } : void 0
28
+ );
29
+ instanceRef.current = instance;
30
+ const offSelection = instance.on(
31
+ "selectionchange",
32
+ (name, value) => onSelectionChangeRef.current?.(name, value)
33
+ );
34
+ skipNextUpdate.current = true;
35
+ onReadyRef.current?.(instance);
36
+ return () => {
37
+ offSelection();
38
+ instance.destroy();
39
+ instanceRef.current = null;
40
+ };
41
+ }, [options.store]);
42
+ useEffect(() => {
43
+ if (skipNextUpdate.current) {
44
+ skipNextUpdate.current = false;
45
+ return;
46
+ }
47
+ const instance = instanceRef.current;
48
+ if (!instance) return;
49
+ instance.update(spec);
50
+ onReadyRef.current?.(instance);
51
+ }, [spec]);
52
+ return ref;
53
+ }
54
+
55
+ // src/Chart.ts
56
+ var FILL = { width: "100%", height: "100%" };
57
+ function Chart({ spec, className, style, ...options }) {
58
+ const ref = useChart(spec, options);
59
+ return createElement("div", {
60
+ ref,
61
+ className,
62
+ style: style ? { ...FILL, ...style } : FILL
63
+ });
64
+ }
65
+
66
+ // src/Dashboard.ts
67
+ import { createElement as createElement2 } from "react";
68
+
69
+ // src/useDashboard.ts
70
+ import { useEffect as useEffect2, useRef as useRef2 } from "react";
71
+ import {
72
+ renderDashboard
73
+ } from "graphein";
74
+ function useDashboard(spec, options = {}) {
75
+ const ref = useRef2(null);
76
+ const instanceRef = useRef2(null);
77
+ const specRef = useRef2(spec);
78
+ specRef.current = spec;
79
+ const onReadyRef = useRef2(options.onReady);
80
+ onReadyRef.current = options.onReady;
81
+ const onSelectionChangeRef = useRef2(options.onSelectionChange);
82
+ onSelectionChangeRef.current = options.onSelectionChange;
83
+ const storeRef = useRef2(options.store);
84
+ storeRef.current = options.store;
85
+ const skipNextUpdate = useRef2(true);
86
+ useEffect2(() => {
87
+ const el = ref.current;
88
+ if (!el) return;
89
+ const instance = renderDashboard(
90
+ el,
91
+ specRef.current,
92
+ storeRef.current ? { store: storeRef.current } : void 0
93
+ );
94
+ instanceRef.current = instance;
95
+ const offSelection = instance.on(
96
+ "selectionchange",
97
+ (name, value) => onSelectionChangeRef.current?.(name, value)
98
+ );
99
+ skipNextUpdate.current = true;
100
+ onReadyRef.current?.(instance);
101
+ return () => {
102
+ offSelection();
103
+ instance.destroy();
104
+ instanceRef.current = null;
105
+ };
106
+ }, [options.store]);
107
+ useEffect2(() => {
108
+ if (skipNextUpdate.current) {
109
+ skipNextUpdate.current = false;
110
+ return;
111
+ }
112
+ const instance = instanceRef.current;
113
+ if (!instance) return;
114
+ instance.update(spec);
115
+ onReadyRef.current?.(instance);
116
+ }, [spec]);
117
+ return ref;
118
+ }
119
+
120
+ // src/Dashboard.ts
121
+ var FILL2 = { width: "100%" };
122
+ function Dashboard({ spec, className, style, ...options }) {
123
+ const ref = useDashboard(spec, options);
124
+ return createElement2("div", {
125
+ ref,
126
+ className,
127
+ style: style ? { ...FILL2, ...style } : FILL2
128
+ });
129
+ }
130
+
131
+ // src/useSelection.ts
132
+ import { useCallback, useEffect as useEffect3, useState } from "react";
133
+ function resolveStore(source) {
134
+ if (!source) return null;
135
+ return "store" in source ? source.store : source;
136
+ }
137
+ function useSelection(source, name) {
138
+ const store = resolveStore(source);
139
+ const [value, setValue] = useState(() => store?.get(name) ?? null);
140
+ useEffect3(() => {
141
+ if (!store) {
142
+ setValue(null);
143
+ return;
144
+ }
145
+ setValue(store.get(name) ?? null);
146
+ const unsubscribe = store.subscribe((changed, next) => {
147
+ if (changed === name) setValue(next);
148
+ });
149
+ return unsubscribe;
150
+ }, [store, name]);
151
+ const set = useCallback(
152
+ (next) => {
153
+ store?.set(name, next ?? null);
154
+ },
155
+ [store, name]
156
+ );
157
+ return [value, set];
158
+ }
159
+
160
+ // src/index.ts
161
+ import { createSelectionStore } from "graphein";
162
+ var VERSION = "0.0.0";
163
+ export {
164
+ Chart,
165
+ Dashboard,
166
+ VERSION,
167
+ createSelectionStore,
168
+ useChart,
169
+ useDashboard,
170
+ useSelection
171
+ };
172
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/Chart.ts","../src/useChart.ts","../src/Dashboard.ts","../src/useDashboard.ts","../src/useSelection.ts","../src/index.ts"],"sourcesContent":["import { createElement, type CSSProperties, type ReactElement } from 'react';\r\nimport type { ChartSpec } from 'graphein';\r\nimport { useChart, type UseChartOptions } from './useChart';\r\n\r\nexport interface ChartProps extends UseChartOptions {\r\n /** The Graphein chart spec to render. */\r\n spec: ChartSpec;\r\n className?: string;\r\n style?: CSSProperties;\r\n}\r\n\r\nconst FILL: CSSProperties = { width: '100%', height: '100%' };\r\n\r\n/**\r\n * Declarative React wrapper around the Graphein core runtime.\r\n *\r\n * Renders a container `<div>` that fills its parent (override via `style`) and\r\n * draws `spec` into it. Pass a new `spec` to update; unmounting tears the chart\r\n * down. Give the parent an explicit size for best results.\r\n *\r\n * ```tsx\r\n * <div style={{ height: 360 }}>\r\n * <Chart spec={{ type: 'line', data, encoding: { x, y } }} />\r\n * </div>\r\n * ```\r\n */\r\nexport function Chart({ spec, className, style, ...options }: ChartProps): ReactElement {\r\n const ref = useChart<HTMLDivElement>(spec, options);\r\n return createElement('div', {\r\n ref,\r\n className,\r\n style: style ? { ...FILL, ...style } : FILL,\r\n });\r\n}\r\n","import { useEffect, useRef, type RefObject } from 'react';\r\nimport {\r\n render,\r\n type ChartInstance,\r\n type ChartSpec,\r\n type SelectionChangeListener,\r\n type SelectionStore,\r\n} from 'graphein';\r\n\r\nexport interface UseChartOptions {\r\n /** Called with the live instance after each mount and update. */\r\n onReady?: (instance: ChartInstance) => void;\r\n /**\r\n * A shared selection bus. Pass the same store to several charts (or a\r\n * dashboard) to link them — cross-highlight / cross-filter. Should be stable\r\n * across renders (e.g. `useMemo(() => createSelectionStore(), [])`); changing\r\n * its identity remounts the chart.\r\n */\r\n store?: SelectionStore;\r\n /** Called whenever any selection this chart is bound to changes. */\r\n onSelectionChange?: SelectionChangeListener;\r\n}\r\n\r\n/**\r\n * Mount an Graphein chart into a DOM node and keep it in sync with `spec`.\r\n *\r\n * Returns a ref to attach to the container element. The chart is created on\r\n * mount, re-rendered via `instance.update()` whenever `spec` changes identity,\r\n * and torn down on unmount. The effect lifecycle is StrictMode-safe (double\r\n * mount → destroy → mount produces no leaked instances or duplicate renders).\r\n *\r\n * ```tsx\r\n * const ref = useChart(spec);\r\n * return <div ref={ref} style={{ width: '100%', height: 360 }} />;\r\n * ```\r\n */\r\nexport function useChart<T extends HTMLElement = HTMLDivElement>(\r\n spec: ChartSpec,\r\n options: UseChartOptions = {},\r\n): RefObject<T | null> {\r\n const ref = useRef<T | null>(null);\r\n const instanceRef = useRef<ChartInstance | null>(null);\r\n // Hold the latest spec / callback in refs so the mount effect (which runs\r\n // once) always reads current values without re-subscribing.\r\n const specRef = useRef(spec);\r\n specRef.current = spec;\r\n const onReadyRef = useRef(options.onReady);\r\n onReadyRef.current = options.onReady;\r\n const onSelectionChangeRef = useRef(options.onSelectionChange);\r\n onSelectionChangeRef.current = options.onSelectionChange;\r\n const storeRef = useRef(options.store);\r\n storeRef.current = options.store;\r\n const skipNextUpdate = useRef(true);\r\n\r\n useEffect(() => {\r\n const el = ref.current;\r\n if (!el) return;\r\n const instance = render(\r\n el,\r\n specRef.current,\r\n storeRef.current ? { store: storeRef.current } : undefined,\r\n );\r\n instanceRef.current = instance;\r\n const offSelection = instance.on('selectionchange', (name, value) =>\r\n onSelectionChangeRef.current?.(name, value),\r\n );\r\n // The update effect runs once right after this on mount; skip that pass so\r\n // we don't redundantly re-render the freshly created chart.\r\n skipNextUpdate.current = true;\r\n onReadyRef.current?.(instance);\r\n return () => {\r\n offSelection();\r\n instance.destroy();\r\n instanceRef.current = null;\r\n };\r\n }, [options.store]);\r\n\r\n useEffect(() => {\r\n if (skipNextUpdate.current) {\r\n skipNextUpdate.current = false;\r\n return;\r\n }\r\n const instance = instanceRef.current;\r\n if (!instance) return;\r\n instance.update(spec);\r\n onReadyRef.current?.(instance);\r\n }, [spec]);\r\n\r\n return ref;\r\n}\r\n","import { createElement, type CSSProperties, type ReactElement } from 'react';\r\nimport type { DashboardSpec } from 'graphein';\r\nimport { useDashboard, type UseDashboardOptions } from './useDashboard';\r\n\r\nexport interface DashboardProps extends UseDashboardOptions {\r\n /** The Graphein dashboard spec to render. */\r\n spec: DashboardSpec;\r\n className?: string;\r\n style?: CSSProperties;\r\n}\r\n\r\nconst FILL: CSSProperties = { width: '100%' };\r\n\r\n/**\r\n * Declarative React wrapper around the Graphein dashboard runtime.\r\n *\r\n * Renders a container `<div>` (full width by default; height comes from the\r\n * dashboard's grid rows) and draws `spec` into it. Pass a new `spec` to update;\r\n * unmounting tears it down. Auto-wires cross-interaction unless `spec` opts out.\r\n *\r\n * ```tsx\r\n * <Dashboard spec={{ type: 'dashboard', data, views, interactions: 'auto' }} />\r\n * ```\r\n */\r\nexport function Dashboard({ spec, className, style, ...options }: DashboardProps): ReactElement {\r\n const ref = useDashboard<HTMLDivElement>(spec, options);\r\n return createElement('div', {\r\n ref,\r\n className,\r\n style: style ? { ...FILL, ...style } : FILL,\r\n });\r\n}\r\n","import { useEffect, useRef, type RefObject } from 'react';\r\nimport {\r\n renderDashboard,\r\n type DashboardInstance,\r\n type DashboardSpec,\r\n type SelectionChangeListener,\r\n type SelectionStore,\r\n} from 'graphein';\r\n\r\nexport interface UseDashboardOptions {\r\n /** Called with the live instance after each mount and update. */\r\n onReady?: (instance: DashboardInstance) => void;\r\n /**\r\n * A shared selection bus. Omit to let the dashboard own one internally; pass\r\n * one (stable across renders) to drive it from React via {@link useSelection}\r\n * or to link it with standalone charts. Changing its identity remounts.\r\n */\r\n store?: SelectionStore;\r\n /** Called whenever any selection in the dashboard changes. */\r\n onSelectionChange?: SelectionChangeListener;\r\n}\r\n\r\n/**\r\n * Mount an Graphein dashboard into a DOM node and keep it in sync with `spec`.\r\n *\r\n * Mirrors {@link useChart}: created on mount, updated in place when `spec`\r\n * changes identity, torn down on unmount, StrictMode-safe.\r\n *\r\n * ```tsx\r\n * const ref = useDashboard(dashboardSpec);\r\n * return <div ref={ref} style={{ width: '100%' }} />;\r\n * ```\r\n */\r\nexport function useDashboard<T extends HTMLElement = HTMLDivElement>(\r\n spec: DashboardSpec,\r\n options: UseDashboardOptions = {},\r\n): RefObject<T | null> {\r\n const ref = useRef<T | null>(null);\r\n const instanceRef = useRef<DashboardInstance | null>(null);\r\n const specRef = useRef(spec);\r\n specRef.current = spec;\r\n const onReadyRef = useRef(options.onReady);\r\n onReadyRef.current = options.onReady;\r\n const onSelectionChangeRef = useRef(options.onSelectionChange);\r\n onSelectionChangeRef.current = options.onSelectionChange;\r\n const storeRef = useRef(options.store);\r\n storeRef.current = options.store;\r\n const skipNextUpdate = useRef(true);\r\n\r\n useEffect(() => {\r\n const el = ref.current;\r\n if (!el) return;\r\n const instance = renderDashboard(\r\n el,\r\n specRef.current,\r\n storeRef.current ? { store: storeRef.current } : undefined,\r\n );\r\n instanceRef.current = instance;\r\n const offSelection = instance.on('selectionchange', (name, value) =>\r\n onSelectionChangeRef.current?.(name, value),\r\n );\r\n skipNextUpdate.current = true;\r\n onReadyRef.current?.(instance);\r\n return () => {\r\n offSelection();\r\n instance.destroy();\r\n instanceRef.current = null;\r\n };\r\n }, [options.store]);\r\n\r\n useEffect(() => {\r\n if (skipNextUpdate.current) {\r\n skipNextUpdate.current = false;\r\n return;\r\n }\r\n const instance = instanceRef.current;\r\n if (!instance) return;\r\n instance.update(spec);\r\n onReadyRef.current?.(instance);\r\n }, [spec]);\r\n\r\n return ref;\r\n}\r\n","import { useCallback, useEffect, useState } from 'react';\r\nimport type { SelectionStore, SelectionValue } from 'graphein';\r\n\r\n/** Anything that exposes a selection bus: a store, or a chart/dashboard instance. */\r\nexport type SelectionSource =\r\n | SelectionStore\r\n | { store: SelectionStore }\r\n | null\r\n | undefined;\r\n\r\nfunction resolveStore(source: SelectionSource): SelectionStore | null {\r\n if (!source) return null;\r\n return 'store' in source ? source.store : source;\r\n}\r\n\r\n/**\r\n * Read and write a named selection as React state.\r\n *\r\n * Pass a shared {@link SelectionStore} (or a chart/dashboard instance, which\r\n * exposes one as `.store`) plus the param name. Returns the current value and a\r\n * setter that publishes to the bus — driving cross-highlight / cross-filter\r\n * across every visual bound to that store.\r\n *\r\n * ```tsx\r\n * const store = useMemo(() => createSelectionStore(), []);\r\n * const [region, setRegion] = useSelection(store, 'region');\r\n * // …\r\n * <Dashboard spec={spec} store={store} />\r\n * <button onClick={() => setRegion({ kind: 'set', field: 'region', values: ['West'] })}>\r\n * West\r\n * </button>\r\n * ```\r\n */\r\nexport function useSelection(\r\n source: SelectionSource,\r\n name: string,\r\n): [SelectionValue | null, (value: SelectionValue | null) => void] {\r\n const store = resolveStore(source);\r\n const [value, setValue] = useState<SelectionValue | null>(() => store?.get(name) ?? null);\r\n\r\n useEffect(() => {\r\n if (!store) {\r\n setValue(null);\r\n return;\r\n }\r\n setValue(store.get(name) ?? null);\r\n const unsubscribe = store.subscribe((changed, next) => {\r\n if (changed === name) setValue(next);\r\n });\r\n return unsubscribe;\r\n }, [store, name]);\r\n\r\n const set = useCallback(\r\n (next: SelectionValue | null) => {\r\n store?.set(name, next ?? null);\r\n },\r\n [store, name],\r\n );\r\n\r\n return [value, set];\r\n}\r\n","/**\r\n * @graphein/react — a thin React wrapper around the `graphein` runtime.\r\n *\r\n * `<Chart spec={…} />` for a single chart or `<Dashboard spec={…} />` for a\r\n * cross-interacting page; the `useChart` / `useDashboard` hooks give headless\r\n * control over your own container, and `useSelection` reads/writes a selection\r\n * as React state. Core spec/instance types are re-exported for convenience.\r\n */\r\nexport { Chart, type ChartProps } from './Chart';\r\nexport { useChart, type UseChartOptions } from './useChart';\r\nexport { Dashboard, type DashboardProps } from './Dashboard';\r\nexport { useDashboard, type UseDashboardOptions } from './useDashboard';\r\nexport { useSelection, type SelectionSource } from './useSelection';\r\nexport { createSelectionStore } from 'graphein';\r\nexport type {\r\n ChartSpec,\r\n ChartInstance,\r\n DashboardSpec,\r\n DashboardInstance,\r\n SelectionStore,\r\n SelectionValue,\r\n SelectionChangeListener,\r\n} from 'graphein';\r\n\r\nexport const VERSION = '0.0.0';\r\n"],"mappings":";AAAA,SAAS,qBAA4D;;;ACArE,SAAS,WAAW,cAA8B;AAClD;AAAA,EACE;AAAA,OAKK;AA6BA,SAAS,SACd,MACA,UAA2B,CAAC,GACP;AACrB,QAAM,MAAM,OAAiB,IAAI;AACjC,QAAM,cAAc,OAA6B,IAAI;AAGrD,QAAM,UAAU,OAAO,IAAI;AAC3B,UAAQ,UAAU;AAClB,QAAM,aAAa,OAAO,QAAQ,OAAO;AACzC,aAAW,UAAU,QAAQ;AAC7B,QAAM,uBAAuB,OAAO,QAAQ,iBAAiB;AAC7D,uBAAqB,UAAU,QAAQ;AACvC,QAAM,WAAW,OAAO,QAAQ,KAAK;AACrC,WAAS,UAAU,QAAQ;AAC3B,QAAM,iBAAiB,OAAO,IAAI;AAElC,YAAU,MAAM;AACd,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AACT,UAAM,WAAW;AAAA,MACf;AAAA,MACA,QAAQ;AAAA,MACR,SAAS,UAAU,EAAE,OAAO,SAAS,QAAQ,IAAI;AAAA,IACnD;AACA,gBAAY,UAAU;AACtB,UAAM,eAAe,SAAS;AAAA,MAAG;AAAA,MAAmB,CAAC,MAAM,UACzD,qBAAqB,UAAU,MAAM,KAAK;AAAA,IAC5C;AAGA,mBAAe,UAAU;AACzB,eAAW,UAAU,QAAQ;AAC7B,WAAO,MAAM;AACX,mBAAa;AACb,eAAS,QAAQ;AACjB,kBAAY,UAAU;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,QAAQ,KAAK,CAAC;AAElB,YAAU,MAAM;AACd,QAAI,eAAe,SAAS;AAC1B,qBAAe,UAAU;AACzB;AAAA,IACF;AACA,UAAM,WAAW,YAAY;AAC7B,QAAI,CAAC,SAAU;AACf,aAAS,OAAO,IAAI;AACpB,eAAW,UAAU,QAAQ;AAAA,EAC/B,GAAG,CAAC,IAAI,CAAC;AAET,SAAO;AACT;;;AD9EA,IAAM,OAAsB,EAAE,OAAO,QAAQ,QAAQ,OAAO;AAerD,SAAS,MAAM,EAAE,MAAM,WAAW,OAAO,GAAG,QAAQ,GAA6B;AACtF,QAAM,MAAM,SAAyB,MAAM,OAAO;AAClD,SAAO,cAAc,OAAO;AAAA,IAC1B;AAAA,IACA;AAAA,IACA,OAAO,QAAQ,EAAE,GAAG,MAAM,GAAG,MAAM,IAAI;AAAA,EACzC,CAAC;AACH;;;AEjCA,SAAS,iBAAAA,sBAA4D;;;ACArE,SAAS,aAAAC,YAAW,UAAAC,eAA8B;AAClD;AAAA,EACE;AAAA,OAKK;AA0BA,SAAS,aACd,MACA,UAA+B,CAAC,GACX;AACrB,QAAM,MAAMA,QAAiB,IAAI;AACjC,QAAM,cAAcA,QAAiC,IAAI;AACzD,QAAM,UAAUA,QAAO,IAAI;AAC3B,UAAQ,UAAU;AAClB,QAAM,aAAaA,QAAO,QAAQ,OAAO;AACzC,aAAW,UAAU,QAAQ;AAC7B,QAAM,uBAAuBA,QAAO,QAAQ,iBAAiB;AAC7D,uBAAqB,UAAU,QAAQ;AACvC,QAAM,WAAWA,QAAO,QAAQ,KAAK;AACrC,WAAS,UAAU,QAAQ;AAC3B,QAAM,iBAAiBA,QAAO,IAAI;AAElC,EAAAD,WAAU,MAAM;AACd,UAAM,KAAK,IAAI;AACf,QAAI,CAAC,GAAI;AACT,UAAM,WAAW;AAAA,MACf;AAAA,MACA,QAAQ;AAAA,MACR,SAAS,UAAU,EAAE,OAAO,SAAS,QAAQ,IAAI;AAAA,IACnD;AACA,gBAAY,UAAU;AACtB,UAAM,eAAe,SAAS;AAAA,MAAG;AAAA,MAAmB,CAAC,MAAM,UACzD,qBAAqB,UAAU,MAAM,KAAK;AAAA,IAC5C;AACA,mBAAe,UAAU;AACzB,eAAW,UAAU,QAAQ;AAC7B,WAAO,MAAM;AACX,mBAAa;AACb,eAAS,QAAQ;AACjB,kBAAY,UAAU;AAAA,IACxB;AAAA,EACF,GAAG,CAAC,QAAQ,KAAK,CAAC;AAElB,EAAAA,WAAU,MAAM;AACd,QAAI,eAAe,SAAS;AAC1B,qBAAe,UAAU;AACzB;AAAA,IACF;AACA,UAAM,WAAW,YAAY;AAC7B,QAAI,CAAC,SAAU;AACf,aAAS,OAAO,IAAI;AACpB,eAAW,UAAU,QAAQ;AAAA,EAC/B,GAAG,CAAC,IAAI,CAAC;AAET,SAAO;AACT;;;ADvEA,IAAME,QAAsB,EAAE,OAAO,OAAO;AAarC,SAAS,UAAU,EAAE,MAAM,WAAW,OAAO,GAAG,QAAQ,GAAiC;AAC9F,QAAM,MAAM,aAA6B,MAAM,OAAO;AACtD,SAAOC,eAAc,OAAO;AAAA,IAC1B;AAAA,IACA;AAAA,IACA,OAAO,QAAQ,EAAE,GAAGD,OAAM,GAAG,MAAM,IAAIA;AAAA,EACzC,CAAC;AACH;;;AE/BA,SAAS,aAAa,aAAAE,YAAW,gBAAgB;AAUjD,SAAS,aAAa,QAAgD;AACpE,MAAI,CAAC,OAAQ,QAAO;AACpB,SAAO,WAAW,SAAS,OAAO,QAAQ;AAC5C;AAoBO,SAAS,aACd,QACA,MACiE;AACjE,QAAM,QAAQ,aAAa,MAAM;AACjC,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAgC,MAAM,OAAO,IAAI,IAAI,KAAK,IAAI;AAExF,EAAAA,WAAU,MAAM;AACd,QAAI,CAAC,OAAO;AACV,eAAS,IAAI;AACb;AAAA,IACF;AACA,aAAS,MAAM,IAAI,IAAI,KAAK,IAAI;AAChC,UAAM,cAAc,MAAM,UAAU,CAAC,SAAS,SAAS;AACrD,UAAI,YAAY,KAAM,UAAS,IAAI;AAAA,IACrC,CAAC;AACD,WAAO;AAAA,EACT,GAAG,CAAC,OAAO,IAAI,CAAC;AAEhB,QAAM,MAAM;AAAA,IACV,CAAC,SAAgC;AAC/B,aAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC/B;AAAA,IACA,CAAC,OAAO,IAAI;AAAA,EACd;AAEA,SAAO,CAAC,OAAO,GAAG;AACpB;;;AC/CA,SAAS,4BAA4B;AAW9B,IAAM,UAAU;","names":["createElement","useEffect","useRef","FILL","createElement","useEffect"]}
package/package.json ADDED
@@ -0,0 +1,68 @@
1
+ {
2
+ "name": "@graphein/react",
3
+ "version": "0.3.0",
4
+ "type": "module",
5
+ "description": "Graphein React wrapper — declarative <Chart spec={...} /> for the Graphein visualization engine.",
6
+ "license": "MIT",
7
+ "author": "Sachin Patney <sachin.patney@gmail.com>",
8
+ "homepage": "https://github.com/spatney/graphein/tree/main/packages/react#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/spatney/graphein.git",
12
+ "directory": "packages/react"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/spatney/graphein/issues"
16
+ },
17
+ "keywords": [
18
+ "react",
19
+ "visualization",
20
+ "charts",
21
+ "dataviz",
22
+ "graphein",
23
+ "agent",
24
+ "declarative"
25
+ ],
26
+ "sideEffects": false,
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js"
34
+ },
35
+ "./package.json": "./package.json"
36
+ },
37
+ "files": [
38
+ "dist",
39
+ "README.md",
40
+ "LICENSE"
41
+ ],
42
+ "engines": {
43
+ "node": ">=18"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "scripts": {
49
+ "build": "tsup",
50
+ "dev": "tsup --watch",
51
+ "test": "vitest run",
52
+ "typecheck": "tsc --noEmit",
53
+ "prepack": "tsup"
54
+ },
55
+ "peerDependencies": {
56
+ "react": ">=18"
57
+ },
58
+ "dependencies": {
59
+ "graphein": "^0.3.0"
60
+ },
61
+ "devDependencies": {
62
+ "@types/react": "^19.2.17",
63
+ "@types/react-dom": "^19.2.3",
64
+ "jsdom": "^25.0.1",
65
+ "react": "^19.2.7",
66
+ "react-dom": "^19.2.7"
67
+ }
68
+ }