@graphein/react 0.14.0 → 0.16.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
@@ -14,6 +14,12 @@ interface UseChartOptions {
14
14
  store?: SelectionStore;
15
15
  /** Called whenever any selection this chart is bound to changes. */
16
16
  onSelectionChange?: SelectionChangeListener;
17
+ /**
18
+ * Publish the chart's config to a `data-graphein-spec` DOM attribute and
19
+ * re-render when an external tool edits it. Defaults to `true`; set `false` to
20
+ * opt out. Read once at mount.
21
+ */
22
+ inspect?: boolean;
17
23
  }
18
24
  /**
19
25
  * Mount an Graphein chart into a DOM node and keep it in sync with `spec`.
@@ -62,6 +68,12 @@ interface UseDashboardOptions {
62
68
  store?: SelectionStore;
63
69
  /** Called whenever any selection in the dashboard changes. */
64
70
  onSelectionChange?: SelectionChangeListener;
71
+ /**
72
+ * Publish each view's config (and the dashboard's) to `data-graphein-spec` DOM
73
+ * attributes and re-render on external edits. Defaults to `true`; set `false` to
74
+ * opt out for the whole dashboard. Read once at mount.
75
+ */
76
+ inspect?: boolean;
65
77
  }
66
78
  /**
67
79
  * Mount an Graphein dashboard into a DOM node and keep it in sync with `spec`.
package/dist/index.js CHANGED
@@ -17,15 +17,16 @@ function useChart(spec, options = {}) {
17
17
  onSelectionChangeRef.current = options.onSelectionChange;
18
18
  const storeRef = useRef(options.store);
19
19
  storeRef.current = options.store;
20
+ const inspectRef = useRef(options.inspect);
21
+ inspectRef.current = options.inspect;
20
22
  const skipNextUpdate = useRef(true);
21
23
  useEffect(() => {
22
24
  const el = ref.current;
23
25
  if (!el) return;
24
- const instance = render(
25
- el,
26
- specRef.current,
27
- storeRef.current ? { store: storeRef.current } : void 0
28
- );
26
+ const instance = render(el, specRef.current, {
27
+ store: storeRef.current,
28
+ inspect: inspectRef.current
29
+ });
29
30
  instanceRef.current = instance;
30
31
  const offSelection = instance.on(
31
32
  "selectionchange",
@@ -82,15 +83,16 @@ function useDashboard(spec, options = {}) {
82
83
  onSelectionChangeRef.current = options.onSelectionChange;
83
84
  const storeRef = useRef2(options.store);
84
85
  storeRef.current = options.store;
86
+ const inspectRef = useRef2(options.inspect);
87
+ inspectRef.current = options.inspect;
85
88
  const skipNextUpdate = useRef2(true);
86
89
  useEffect2(() => {
87
90
  const el = ref.current;
88
91
  if (!el) return;
89
- const instance = renderDashboard(
90
- el,
91
- specRef.current,
92
- storeRef.current ? { store: storeRef.current } : void 0
93
- );
92
+ const instance = renderDashboard(el, specRef.current, {
93
+ store: storeRef.current,
94
+ inspect: inspectRef.current
95
+ });
94
96
  instanceRef.current = instance;
95
97
  const offSelection = instance.on(
96
98
  "selectionchange",
package/dist/index.js.map CHANGED
@@ -1 +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';\nimport type { ChartSpec } from 'graphein';\nimport { useChart, type UseChartOptions } from './useChart';\n\nexport interface ChartProps extends UseChartOptions {\n /** The Graphein chart spec to render. */\n spec: ChartSpec;\n className?: string;\n style?: CSSProperties;\n}\n\nconst FILL: CSSProperties = { width: '100%', height: '100%' };\n\n/**\n * Declarative React wrapper around the Graphein core runtime.\n *\n * Renders a container `<div>` that fills its parent (override via `style`) and\n * draws `spec` into it. Pass a new `spec` to update; unmounting tears the chart\n * down. Give the parent an explicit size for best results.\n *\n * ```tsx\n * <div style={{ height: 360 }}>\n * <Chart spec={{ type: 'line', data, encoding: { x, y } }} />\n * </div>\n * ```\n */\nexport function Chart({ spec, className, style, ...options }: ChartProps): ReactElement {\n const ref = useChart<HTMLDivElement>(spec, options);\n return createElement('div', {\n ref,\n className,\n style: style ? { ...FILL, ...style } : FILL,\n });\n}\n","import { useEffect, useRef, type RefObject } from 'react';\nimport {\n render,\n type ChartInstance,\n type ChartSpec,\n type SelectionChangeListener,\n type SelectionStore,\n} from 'graphein';\n\nexport interface UseChartOptions {\n /** Called with the live instance after each mount and update. */\n onReady?: (instance: ChartInstance) => void;\n /**\n * A shared selection bus. Pass the same store to several charts (or a\n * dashboard) to link them — cross-highlight / cross-filter. Should be stable\n * across renders (e.g. `useMemo(() => createSelectionStore(), [])`); changing\n * its identity remounts the chart.\n */\n store?: SelectionStore;\n /** Called whenever any selection this chart is bound to changes. */\n onSelectionChange?: SelectionChangeListener;\n}\n\n/**\n * Mount an Graphein chart into a DOM node and keep it in sync with `spec`.\n *\n * Returns a ref to attach to the container element. The chart is created on\n * mount, re-rendered via `instance.update()` whenever `spec` changes identity,\n * and torn down on unmount. The effect lifecycle is StrictMode-safe (double\n * mount → destroy → mount produces no leaked instances or duplicate renders).\n *\n * ```tsx\n * const ref = useChart(spec);\n * return <div ref={ref} style={{ width: '100%', height: 360 }} />;\n * ```\n */\nexport function useChart<T extends HTMLElement = HTMLDivElement>(\n spec: ChartSpec,\n options: UseChartOptions = {},\n): RefObject<T | null> {\n const ref = useRef<T | null>(null);\n const instanceRef = useRef<ChartInstance | null>(null);\n // Hold the latest spec / callback in refs so the mount effect (which runs\n // once) always reads current values without re-subscribing.\n const specRef = useRef(spec);\n specRef.current = spec;\n const onReadyRef = useRef(options.onReady);\n onReadyRef.current = options.onReady;\n const onSelectionChangeRef = useRef(options.onSelectionChange);\n onSelectionChangeRef.current = options.onSelectionChange;\n const storeRef = useRef(options.store);\n storeRef.current = options.store;\n const skipNextUpdate = useRef(true);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const instance = render(\n el,\n specRef.current,\n storeRef.current ? { store: storeRef.current } : undefined,\n );\n instanceRef.current = instance;\n const offSelection = instance.on('selectionchange', (name, value) =>\n onSelectionChangeRef.current?.(name, value),\n );\n // The update effect runs once right after this on mount; skip that pass so\n // we don't redundantly re-render the freshly created chart.\n skipNextUpdate.current = true;\n onReadyRef.current?.(instance);\n return () => {\n offSelection();\n instance.destroy();\n instanceRef.current = null;\n };\n }, [options.store]);\n\n useEffect(() => {\n if (skipNextUpdate.current) {\n skipNextUpdate.current = false;\n return;\n }\n const instance = instanceRef.current;\n if (!instance) return;\n instance.update(spec);\n onReadyRef.current?.(instance);\n }, [spec]);\n\n return ref;\n}\n","import { createElement, type CSSProperties, type ReactElement } from 'react';\nimport type { DashboardSpec } from 'graphein';\nimport { useDashboard, type UseDashboardOptions } from './useDashboard';\n\nexport interface DashboardProps extends UseDashboardOptions {\n /** The Graphein dashboard spec to render. */\n spec: DashboardSpec;\n className?: string;\n style?: CSSProperties;\n}\n\nconst FILL: CSSProperties = { width: '100%' };\n\n/**\n * Declarative React wrapper around the Graphein dashboard runtime.\n *\n * Renders a container `<div>` (full width by default; height comes from the\n * dashboard's grid rows) and draws `spec` into it. Pass a new `spec` to update;\n * unmounting tears it down. Auto-wires cross-interaction unless `spec` opts out.\n *\n * ```tsx\n * <Dashboard spec={{ type: 'dashboard', data, views, interactions: 'auto' }} />\n * ```\n */\nexport function Dashboard({ spec, className, style, ...options }: DashboardProps): ReactElement {\n const ref = useDashboard<HTMLDivElement>(spec, options);\n return createElement('div', {\n ref,\n className,\n style: style ? { ...FILL, ...style } : FILL,\n });\n}\n","import { useEffect, useRef, type RefObject } from 'react';\nimport {\n renderDashboard,\n type DashboardInstance,\n type DashboardSpec,\n type SelectionChangeListener,\n type SelectionStore,\n} from 'graphein';\n\nexport interface UseDashboardOptions {\n /** Called with the live instance after each mount and update. */\n onReady?: (instance: DashboardInstance) => void;\n /**\n * A shared selection bus. Omit to let the dashboard own one internally; pass\n * one (stable across renders) to drive it from React via {@link useSelection}\n * or to link it with standalone charts. Changing its identity remounts.\n */\n store?: SelectionStore;\n /** Called whenever any selection in the dashboard changes. */\n onSelectionChange?: SelectionChangeListener;\n}\n\n/**\n * Mount an Graphein dashboard into a DOM node and keep it in sync with `spec`.\n *\n * Mirrors {@link useChart}: created on mount, updated in place when `spec`\n * changes identity, torn down on unmount, StrictMode-safe.\n *\n * ```tsx\n * const ref = useDashboard(dashboardSpec);\n * return <div ref={ref} style={{ width: '100%' }} />;\n * ```\n */\nexport function useDashboard<T extends HTMLElement = HTMLDivElement>(\n spec: DashboardSpec,\n options: UseDashboardOptions = {},\n): RefObject<T | null> {\n const ref = useRef<T | null>(null);\n const instanceRef = useRef<DashboardInstance | null>(null);\n const specRef = useRef(spec);\n specRef.current = spec;\n const onReadyRef = useRef(options.onReady);\n onReadyRef.current = options.onReady;\n const onSelectionChangeRef = useRef(options.onSelectionChange);\n onSelectionChangeRef.current = options.onSelectionChange;\n const storeRef = useRef(options.store);\n storeRef.current = options.store;\n const skipNextUpdate = useRef(true);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const instance = renderDashboard(\n el,\n specRef.current,\n storeRef.current ? { store: storeRef.current } : undefined,\n );\n instanceRef.current = instance;\n const offSelection = instance.on('selectionchange', (name, value) =>\n onSelectionChangeRef.current?.(name, value),\n );\n skipNextUpdate.current = true;\n onReadyRef.current?.(instance);\n return () => {\n offSelection();\n instance.destroy();\n instanceRef.current = null;\n };\n }, [options.store]);\n\n useEffect(() => {\n if (skipNextUpdate.current) {\n skipNextUpdate.current = false;\n return;\n }\n const instance = instanceRef.current;\n if (!instance) return;\n instance.update(spec);\n onReadyRef.current?.(instance);\n }, [spec]);\n\n return ref;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { SelectionStore, SelectionValue } from 'graphein';\n\n/** Anything that exposes a selection bus: a store, or a chart/dashboard instance. */\nexport type SelectionSource =\n | SelectionStore\n | { store: SelectionStore }\n | null\n | undefined;\n\nfunction resolveStore(source: SelectionSource): SelectionStore | null {\n if (!source) return null;\n return 'store' in source ? source.store : source;\n}\n\n/**\n * Read and write a named selection as React state.\n *\n * Pass a shared {@link SelectionStore} (or a chart/dashboard instance, which\n * exposes one as `.store`) plus the param name. Returns the current value and a\n * setter that publishes to the bus — driving cross-highlight / cross-filter\n * across every visual bound to that store.\n *\n * ```tsx\n * const store = useMemo(() => createSelectionStore(), []);\n * const [region, setRegion] = useSelection(store, 'region');\n * // …\n * <Dashboard spec={spec} store={store} />\n * <button onClick={() => setRegion({ kind: 'set', field: 'region', values: ['West'] })}>\n * West\n * </button>\n * ```\n */\nexport function useSelection(\n source: SelectionSource,\n name: string,\n): [SelectionValue | null, (value: SelectionValue | null) => void] {\n const store = resolveStore(source);\n const [value, setValue] = useState<SelectionValue | null>(() => store?.get(name) ?? null);\n\n useEffect(() => {\n if (!store) {\n setValue(null);\n return;\n }\n setValue(store.get(name) ?? null);\n const unsubscribe = store.subscribe((changed, next) => {\n if (changed === name) setValue(next);\n });\n return unsubscribe;\n }, [store, name]);\n\n const set = useCallback(\n (next: SelectionValue | null) => {\n store?.set(name, next ?? null);\n },\n [store, name],\n );\n\n return [value, set];\n}\n","/**\n * @graphein/react — a thin React wrapper around the `graphein` runtime.\n *\n * `<Chart spec={…} />` for a single chart or `<Dashboard spec={…} />` for a\n * cross-interacting page; the `useChart` / `useDashboard` hooks give headless\n * control over your own container, and `useSelection` reads/writes a selection\n * as React state. Core spec/instance types are re-exported for convenience.\n */\nexport { Chart, type ChartProps } from './Chart';\nexport { useChart, type UseChartOptions } from './useChart';\nexport { Dashboard, type DashboardProps } from './Dashboard';\nexport { useDashboard, type UseDashboardOptions } from './useDashboard';\nexport { useSelection, type SelectionSource } from './useSelection';\nexport { createSelectionStore } from 'graphein';\nexport type {\n ChartSpec,\n ChartInstance,\n DashboardSpec,\n DashboardInstance,\n SelectionStore,\n SelectionValue,\n SelectionChangeListener,\n} from 'graphein';\n\nexport const VERSION = '0.0.0';\n"],"mappings":"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","names":["createElement","useEffect","useRef","FILL","createElement","useEffect"]}
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';\nimport type { ChartSpec } from 'graphein';\nimport { useChart, type UseChartOptions } from './useChart';\n\nexport interface ChartProps extends UseChartOptions {\n /** The Graphein chart spec to render. */\n spec: ChartSpec;\n className?: string;\n style?: CSSProperties;\n}\n\nconst FILL: CSSProperties = { width: '100%', height: '100%' };\n\n/**\n * Declarative React wrapper around the Graphein core runtime.\n *\n * Renders a container `<div>` that fills its parent (override via `style`) and\n * draws `spec` into it. Pass a new `spec` to update; unmounting tears the chart\n * down. Give the parent an explicit size for best results.\n *\n * ```tsx\n * <div style={{ height: 360 }}>\n * <Chart spec={{ type: 'line', data, encoding: { x, y } }} />\n * </div>\n * ```\n */\nexport function Chart({ spec, className, style, ...options }: ChartProps): ReactElement {\n const ref = useChart<HTMLDivElement>(spec, options);\n return createElement('div', {\n ref,\n className,\n style: style ? { ...FILL, ...style } : FILL,\n });\n}\n","import { useEffect, useRef, type RefObject } from 'react';\nimport {\n render,\n type ChartInstance,\n type ChartSpec,\n type SelectionChangeListener,\n type SelectionStore,\n} from 'graphein';\n\nexport interface UseChartOptions {\n /** Called with the live instance after each mount and update. */\n onReady?: (instance: ChartInstance) => void;\n /**\n * A shared selection bus. Pass the same store to several charts (or a\n * dashboard) to link them — cross-highlight / cross-filter. Should be stable\n * across renders (e.g. `useMemo(() => createSelectionStore(), [])`); changing\n * its identity remounts the chart.\n */\n store?: SelectionStore;\n /** Called whenever any selection this chart is bound to changes. */\n onSelectionChange?: SelectionChangeListener;\n /**\n * Publish the chart's config to a `data-graphein-spec` DOM attribute and\n * re-render when an external tool edits it. Defaults to `true`; set `false` to\n * opt out. Read once at mount.\n */\n inspect?: boolean;\n}\n\n/**\n * Mount an Graphein chart into a DOM node and keep it in sync with `spec`.\n *\n * Returns a ref to attach to the container element. The chart is created on\n * mount, re-rendered via `instance.update()` whenever `spec` changes identity,\n * and torn down on unmount. The effect lifecycle is StrictMode-safe (double\n * mount → destroy → mount produces no leaked instances or duplicate renders).\n *\n * ```tsx\n * const ref = useChart(spec);\n * return <div ref={ref} style={{ width: '100%', height: 360 }} />;\n * ```\n */\nexport function useChart<T extends HTMLElement = HTMLDivElement>(\n spec: ChartSpec,\n options: UseChartOptions = {},\n): RefObject<T | null> {\n const ref = useRef<T | null>(null);\n const instanceRef = useRef<ChartInstance | null>(null);\n // Hold the latest spec / callback in refs so the mount effect (which runs\n // once) always reads current values without re-subscribing.\n const specRef = useRef(spec);\n specRef.current = spec;\n const onReadyRef = useRef(options.onReady);\n onReadyRef.current = options.onReady;\n const onSelectionChangeRef = useRef(options.onSelectionChange);\n onSelectionChangeRef.current = options.onSelectionChange;\n const storeRef = useRef(options.store);\n storeRef.current = options.store;\n const inspectRef = useRef(options.inspect);\n inspectRef.current = options.inspect;\n const skipNextUpdate = useRef(true);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const instance = render(el, specRef.current, {\n store: storeRef.current,\n inspect: inspectRef.current,\n });\n instanceRef.current = instance;\n const offSelection = instance.on('selectionchange', (name, value) =>\n onSelectionChangeRef.current?.(name, value),\n );\n // The update effect runs once right after this on mount; skip that pass so\n // we don't redundantly re-render the freshly created chart.\n skipNextUpdate.current = true;\n onReadyRef.current?.(instance);\n return () => {\n offSelection();\n instance.destroy();\n instanceRef.current = null;\n };\n }, [options.store]);\n\n useEffect(() => {\n if (skipNextUpdate.current) {\n skipNextUpdate.current = false;\n return;\n }\n const instance = instanceRef.current;\n if (!instance) return;\n instance.update(spec);\n onReadyRef.current?.(instance);\n }, [spec]);\n\n return ref;\n}\n","import { createElement, type CSSProperties, type ReactElement } from 'react';\nimport type { DashboardSpec } from 'graphein';\nimport { useDashboard, type UseDashboardOptions } from './useDashboard';\n\nexport interface DashboardProps extends UseDashboardOptions {\n /** The Graphein dashboard spec to render. */\n spec: DashboardSpec;\n className?: string;\n style?: CSSProperties;\n}\n\nconst FILL: CSSProperties = { width: '100%' };\n\n/**\n * Declarative React wrapper around the Graphein dashboard runtime.\n *\n * Renders a container `<div>` (full width by default; height comes from the\n * dashboard's grid rows) and draws `spec` into it. Pass a new `spec` to update;\n * unmounting tears it down. Auto-wires cross-interaction unless `spec` opts out.\n *\n * ```tsx\n * <Dashboard spec={{ type: 'dashboard', data, views, interactions: 'auto' }} />\n * ```\n */\nexport function Dashboard({ spec, className, style, ...options }: DashboardProps): ReactElement {\n const ref = useDashboard<HTMLDivElement>(spec, options);\n return createElement('div', {\n ref,\n className,\n style: style ? { ...FILL, ...style } : FILL,\n });\n}\n","import { useEffect, useRef, type RefObject } from 'react';\nimport {\n renderDashboard,\n type DashboardInstance,\n type DashboardSpec,\n type SelectionChangeListener,\n type SelectionStore,\n} from 'graphein';\n\nexport interface UseDashboardOptions {\n /** Called with the live instance after each mount and update. */\n onReady?: (instance: DashboardInstance) => void;\n /**\n * A shared selection bus. Omit to let the dashboard own one internally; pass\n * one (stable across renders) to drive it from React via {@link useSelection}\n * or to link it with standalone charts. Changing its identity remounts.\n */\n store?: SelectionStore;\n /** Called whenever any selection in the dashboard changes. */\n onSelectionChange?: SelectionChangeListener;\n /**\n * Publish each view's config (and the dashboard's) to `data-graphein-spec` DOM\n * attributes and re-render on external edits. Defaults to `true`; set `false` to\n * opt out for the whole dashboard. Read once at mount.\n */\n inspect?: boolean;\n}\n\n/**\n * Mount an Graphein dashboard into a DOM node and keep it in sync with `spec`.\n *\n * Mirrors {@link useChart}: created on mount, updated in place when `spec`\n * changes identity, torn down on unmount, StrictMode-safe.\n *\n * ```tsx\n * const ref = useDashboard(dashboardSpec);\n * return <div ref={ref} style={{ width: '100%' }} />;\n * ```\n */\nexport function useDashboard<T extends HTMLElement = HTMLDivElement>(\n spec: DashboardSpec,\n options: UseDashboardOptions = {},\n): RefObject<T | null> {\n const ref = useRef<T | null>(null);\n const instanceRef = useRef<DashboardInstance | null>(null);\n const specRef = useRef(spec);\n specRef.current = spec;\n const onReadyRef = useRef(options.onReady);\n onReadyRef.current = options.onReady;\n const onSelectionChangeRef = useRef(options.onSelectionChange);\n onSelectionChangeRef.current = options.onSelectionChange;\n const storeRef = useRef(options.store);\n storeRef.current = options.store;\n const inspectRef = useRef(options.inspect);\n inspectRef.current = options.inspect;\n const skipNextUpdate = useRef(true);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n const instance = renderDashboard(el, specRef.current, {\n store: storeRef.current,\n inspect: inspectRef.current,\n });\n instanceRef.current = instance;\n const offSelection = instance.on('selectionchange', (name, value) =>\n onSelectionChangeRef.current?.(name, value),\n );\n skipNextUpdate.current = true;\n onReadyRef.current?.(instance);\n return () => {\n offSelection();\n instance.destroy();\n instanceRef.current = null;\n };\n }, [options.store]);\n\n useEffect(() => {\n if (skipNextUpdate.current) {\n skipNextUpdate.current = false;\n return;\n }\n const instance = instanceRef.current;\n if (!instance) return;\n instance.update(spec);\n onReadyRef.current?.(instance);\n }, [spec]);\n\n return ref;\n}\n","import { useCallback, useEffect, useState } from 'react';\nimport type { SelectionStore, SelectionValue } from 'graphein';\n\n/** Anything that exposes a selection bus: a store, or a chart/dashboard instance. */\nexport type SelectionSource =\n | SelectionStore\n | { store: SelectionStore }\n | null\n | undefined;\n\nfunction resolveStore(source: SelectionSource): SelectionStore | null {\n if (!source) return null;\n return 'store' in source ? source.store : source;\n}\n\n/**\n * Read and write a named selection as React state.\n *\n * Pass a shared {@link SelectionStore} (or a chart/dashboard instance, which\n * exposes one as `.store`) plus the param name. Returns the current value and a\n * setter that publishes to the bus — driving cross-highlight / cross-filter\n * across every visual bound to that store.\n *\n * ```tsx\n * const store = useMemo(() => createSelectionStore(), []);\n * const [region, setRegion] = useSelection(store, 'region');\n * // …\n * <Dashboard spec={spec} store={store} />\n * <button onClick={() => setRegion({ kind: 'set', field: 'region', values: ['West'] })}>\n * West\n * </button>\n * ```\n */\nexport function useSelection(\n source: SelectionSource,\n name: string,\n): [SelectionValue | null, (value: SelectionValue | null) => void] {\n const store = resolveStore(source);\n const [value, setValue] = useState<SelectionValue | null>(() => store?.get(name) ?? null);\n\n useEffect(() => {\n if (!store) {\n setValue(null);\n return;\n }\n setValue(store.get(name) ?? null);\n const unsubscribe = store.subscribe((changed, next) => {\n if (changed === name) setValue(next);\n });\n return unsubscribe;\n }, [store, name]);\n\n const set = useCallback(\n (next: SelectionValue | null) => {\n store?.set(name, next ?? null);\n },\n [store, name],\n );\n\n return [value, set];\n}\n","/**\n * @graphein/react — a thin React wrapper around the `graphein` runtime.\n *\n * `<Chart spec={…} />` for a single chart or `<Dashboard spec={…} />` for a\n * cross-interacting page; the `useChart` / `useDashboard` hooks give headless\n * control over your own container, and `useSelection` reads/writes a selection\n * as React state. Core spec/instance types are re-exported for convenience.\n */\nexport { Chart, type ChartProps } from './Chart';\nexport { useChart, type UseChartOptions } from './useChart';\nexport { Dashboard, type DashboardProps } from './Dashboard';\nexport { useDashboard, type UseDashboardOptions } from './useDashboard';\nexport { useSelection, type SelectionSource } from './useSelection';\nexport { createSelectionStore } from 'graphein';\nexport type {\n ChartSpec,\n ChartInstance,\n DashboardSpec,\n DashboardInstance,\n SelectionStore,\n SelectionValue,\n SelectionChangeListener,\n} from 'graphein';\n\nexport const VERSION = '0.0.0';\n"],"mappings":"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","names":["createElement","useEffect","useRef","FILL","createElement","useEffect"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphein/react",
3
- "version": "0.14.0",
3
+ "version": "0.16.0",
4
4
  "type": "module",
5
5
  "description": "Graphein React wrapper — declarative <Chart spec={...} /> for the Graphein visualization engine.",
6
6
  "license": "MIT",
@@ -56,7 +56,7 @@
56
56
  "react": ">=18"
57
57
  },
58
58
  "dependencies": {
59
- "graphein": "^0.14.0"
59
+ "graphein": "^0.16.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/react": "^19.2.17",