canvasxpress-react 65.6.0 → 65.8.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.
Files changed (4) hide show
  1. package/README.md +23 -0
  2. package/index.d.ts +16 -6
  3. package/index.js +66 -49
  4. package/package.json +5 -2
package/README.md CHANGED
@@ -62,6 +62,29 @@ var reactapp = document.createElement("div");
62
62
  document.body.appendChild(reactapp);
63
63
  ReactDOM.render(<Bar />, reactapp)
64
64
  ```
65
+ Notes
66
+ -----------------
67
+
68
+ - Requires **React 16.8+** (the component is implemented with hooks).
69
+ - `target` is **optional** — the component renders through an internal ref, so you
70
+ no longer need to supply a globally unique DOM id. If you do pass `target`, it is
71
+ applied as the `<canvas>` id.
72
+ - The live CanvasXpress instance is available two ways: the `onRef` callback (fired
73
+ with the instance on mount and `undefined` on unmount), or a forwarded `ref`
74
+ (`ref.current` is the instance; `ref.current.graph` also resolves for backward
75
+ compatibility):
76
+
77
+ ```jsx
78
+ import React, { useRef } from 'react';
79
+ import CanvasXpressReact from 'canvasxpress-react';
80
+
81
+ function Chart(props) {
82
+ const cx = useRef(null);
83
+ // cx.current is the CanvasXpress instance after mount
84
+ return <CanvasXpressReact ref={cx} data={props.data} config={props.config} />;
85
+ }
86
+ ```
87
+
65
88
  Alternative Used
66
89
  -----------------
67
90
 
package/index.d.ts CHANGED
@@ -8,8 +8,9 @@ import * as CanvasXpress from 'canvasxpress';
8
8
  export = CanvasXpressReact;
9
9
 
10
10
  interface CanvasXpressReactProps {
11
- /** DOM id assigned to the rendered <canvas>. */
12
- target: string;
11
+ /** DOM id applied to the rendered <canvas> (optional; the component uses an
12
+ * internal ref, so a unique id is no longer required). */
13
+ target?: string;
13
14
  /** CanvasXpress data object. */
14
15
  data?: CanvasXpress.CXData;
15
16
  /** CanvasXpress configuration. */
@@ -28,7 +29,16 @@ interface CanvasXpressReactProps {
28
29
  onRef?: (instance: CanvasXpress.CanvasXpressInstance | undefined) => void;
29
30
  }
30
31
 
31
- declare class CanvasXpressReact extends React.Component<CanvasXpressReactProps> {
32
- /** The live CanvasXpress instance, available after mount. */
33
- graph?: CanvasXpress.CanvasXpressInstance;
34
- }
32
+ /**
33
+ * The value resolved by a forwarded `ref`: the live CanvasXpress instance,
34
+ * carrying a `graph` self-alias for backward compatibility with code that read
35
+ * `ref.current.graph` off the previous class component.
36
+ */
37
+ type CanvasXpressReactHandle = CanvasXpress.CanvasXpressInstance & {
38
+ /** Self-reference to the CanvasXpress instance (legacy accessor). */
39
+ graph: CanvasXpress.CanvasXpressInstance;
40
+ };
41
+
42
+ declare const CanvasXpressReact: React.ForwardRefExoticComponent<
43
+ CanvasXpressReactProps & React.RefAttributes<CanvasXpressReactHandle>
44
+ >;
package/index.js CHANGED
@@ -2,71 +2,88 @@ var React = require('react');
2
2
  var CanvasXpress = require('canvasxpress');
3
3
  require('canvasxpress/src/canvasXpress.css');
4
4
 
5
- class CanvasXpressReact extends React.Component {
5
+ //Hooks-based wrapper (requires React >= 16.8). The previous release shipped a
6
+ //class component; the public surface is unchanged: the same props are accepted,
7
+ //onRef(instance) still fires on mount/unmount, and a forwarded ref now resolves
8
+ //to the live CanvasXpress instance (with a .graph self-alias, so the old
9
+ //ref.current.graph access keeps working).
10
+ var CanvasXpressReact = React.forwardRef(function CanvasXpressReact(props, ref) {
6
11
 
7
- constructor(props) {
8
- super(props);
9
- this.target = props.target ? props.target : false;
10
- this.data = props.data ? props.data : false;
11
- this.config = props.config ? props.config : false;
12
- this.events = props.events ? props.events : false;
13
- this.width = props.width ? props.width : 500;
14
- this.height = props.height ? props.height : 500;
15
- this.responsive = props.responsive ? props.responsive : false;
16
- this.aspectRatio = props.aspectratio ? props.aspectratio : "1:1";
17
- }
12
+ //A ref to the <canvas> node replaces the old required-unique `target` id, so
13
+ //two components can no longer collide on a shared DOM id. `target`, if passed,
14
+ //is still applied as the canvas id for backward compatibility.
15
+ var canvasRef = React.useRef(null);
16
+ var graphRef = React.useRef(null);
18
17
 
19
- componentDidMount() {
20
- //Create graph and render
21
- this.graph = new CanvasXpress.init(this.target, this.data, this.config, this.events);
22
- if (this.props.onRef) {
23
- this.props.onRef(this.graph);
18
+ //Expose the live instance through the forwarded ref, and mirror the legacy
19
+ //ref.current.graph accessor onto the instance itself (the engine never reads
20
+ //`.graph`, so this alias is inert).
21
+ React.useImperativeHandle(ref, function () {
22
+ var instance = graphRef.current;
23
+ if (instance && !instance.graph) {
24
+ instance.graph = instance;
24
25
  }
25
- }
26
+ return instance;
27
+ });
26
28
 
27
- shouldComponentUpdate(nextProps) {
28
- //Re-render only when the incoming props differ from the CURRENT props.
29
- //Comparing against the constructor snapshots (this.data/this.config) drifts
30
- //after the first update, because those are never refreshed.
31
- return nextProps.data !== this.props.data
32
- || nextProps.config !== this.props.config
33
- || nextProps.events !== this.props.events;
34
- }
29
+ //Mount / unmount. Empty dependency list: build the graph once, tear it down on
30
+ //unmount. Data/config/event updates are handled by the effect below.
31
+ React.useEffect(function () {
32
+ graphRef.current = new CanvasXpress.init(
33
+ canvasRef.current,
34
+ props.data ? props.data : false,
35
+ props.config ? props.config : false,
36
+ props.events ? props.events : false
37
+ );
38
+ if (props.onRef) {
39
+ props.onRef(graphRef.current);
40
+ }
41
+ return function () {
42
+ if (graphRef.current) {
43
+ graphRef.current.destroy();
44
+ graphRef.current = null;
45
+ }
46
+ if (props.onRef) {
47
+ props.onRef(undefined);
48
+ }
49
+ };
50
+ // eslint-disable-next-line react-hooks/exhaustive-deps
51
+ }, []);
35
52
 
36
- componentDidUpdate() {
37
- //Update Graph Options & Data. Bail out if the graph never mounted.
38
- if (!this.graph) {
53
+ //Re-apply data, config, and events whenever any of them change. The class
54
+ //component only refreshed data/config; events are now re-bound too (the engine
55
+ //reads handlers off instance.events).
56
+ React.useEffect(function () {
57
+ var graph = graphRef.current;
58
+ if (!graph) {
39
59
  return;
40
60
  }
61
+ graph.events = props.events ? props.events : false;
41
62
  //resetConfig()/updateConfig() land in canvasxpress >= 65.4. Guard so an
42
63
  //older library falls back to updateData's config-merge path instead of
43
64
  //throwing on a missing method.
44
- if (typeof this.graph.resetConfig === 'function' && typeof this.graph.updateConfig === 'function') {
45
- this.graph.resetConfig();
65
+ if (typeof graph.resetConfig === 'function' && typeof graph.updateConfig === 'function') {
66
+ graph.resetConfig();
46
67
  //Second arg (n) = "do not draw": defer the redraw so only updateData below
47
68
  //paints, avoiding a double render.
48
- this.graph.updateConfig(this.props.config, true);
49
- this.graph.updateData(this.props.data);
69
+ graph.updateConfig(props.config, true);
70
+ graph.updateData(props.data);
50
71
  } else {
51
72
  //Fallback: apply config alongside the data in a single redraw.
52
- this.graph.updateData(this.props.data, true, false, this.props.config);
53
- }
54
- }
55
-
56
- componentWillUnmount() {
57
- //Destroy graph and remove reference
58
- if (this.graph) {
59
- this.graph.destroy();
60
- }
61
- if (this.props.onRef) {
62
- this.props.onRef(undefined);
73
+ graph.updateData(props.data, true, false, props.config);
63
74
  }
64
- }
75
+ // eslint-disable-next-line react-hooks/exhaustive-deps
76
+ }, [props.data, props.config, props.events]);
65
77
 
66
- render() {
67
- return React.createElement('canvas', { id: this.target, width: this.width, height: this.height, 'data-responsive': this.responsive, 'data-aspectratio': this.aspectRatio});
68
- }
78
+ return React.createElement('canvas', {
79
+ ref: canvasRef,
80
+ id: props.target ? props.target : undefined,
81
+ width: props.width ? props.width : 500,
82
+ height: props.height ? props.height : 500,
83
+ 'data-responsive': props.responsive ? props.responsive : false,
84
+ 'data-aspectratio': props.aspectratio ? props.aspectratio : "1:1"
85
+ });
69
86
 
70
- }
87
+ });
71
88
 
72
89
  module.exports = CanvasXpressReact;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "main": "index.js",
4
4
  "types": "index.d.ts",
5
5
  "title": "CanvasXpress React",
6
- "version": "65.6.0",
6
+ "version": "65.8.0",
7
7
  "license": {
8
8
  "type": "Dual licensing",
9
9
  "url": "https://www.canvasxpress.org/docs/license.html"
@@ -29,6 +29,9 @@
29
29
  },
30
30
  "homepage": "https://github.com/neuhausi/canvasxpress#readme",
31
31
  "dependencies": {
32
- "canvasxpress": "^65.6.0"
32
+ "canvasxpress": "^65.8.0"
33
+ },
34
+ "peerDependencies": {
35
+ "react": ">=16.8.0"
33
36
  }
34
37
  }