wickchart 0.4.0 → 1.2.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/README.md +274 -48
- package/package.json +29 -12
- package/src/core.js +452 -12
- package/src/feeds.js +1 -1
- package/src/react-core.js +191 -0
- package/src/react.js +21 -0
- package/src/{hab-chart.js → wick-chart.js} +525 -111
- package/src/{hab-feed.js → wick-feed.js} +34 -19
- package/types/core.d.ts +209 -3
- package/types/react-core.d.ts +64 -0
- package/types/react.d.ts +3 -0
- package/types/{hab-chart.d.ts → wick-chart.d.ts} +215 -13
- package/types/{hab-feed.d.ts → wick-feed.d.ts} +8 -4
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
* react-core — React bindings for <wick-chart> (pure logic, no side effects).
|
|
3
|
+
*
|
|
4
|
+
* import { WickChart, useWickChart } from 'wickchart/react';
|
|
5
|
+
*
|
|
6
|
+
* <WickChart type="candles" indicators="sma:20 volume" volshading
|
|
7
|
+
* data={bars} onRange={fn} onAlert={fn} style={{ height: 420 }} />
|
|
8
|
+
*
|
|
9
|
+
* Props map onto the element 1:1:
|
|
10
|
+
* - string/number/boolean props become attributes ("indicators", "type", …)
|
|
11
|
+
* - `data` assigns the bar array (pass a fresh array to trigger an update)
|
|
12
|
+
* - `overlays` assigns server-side zones & levels via setOverlays()
|
|
13
|
+
* - `onRange` / `onAlert` / … subscribe to the matching `wick:range`,
|
|
14
|
+
* `wick:alert`, … events and unsubscribe on unmount; an `events`
|
|
15
|
+
* object ({ range: fn }) works too
|
|
16
|
+
* - className/style/id/… are passed through to React as usual
|
|
17
|
+
*
|
|
18
|
+
* This module never touches the DOM at import time and does NOT define the
|
|
19
|
+
* element — the package entry (src/react.js) imports <wick-chart> for its
|
|
20
|
+
* side effect, which keeps this file importable in tests and on the server.
|
|
21
|
+
* Requires React >= 16.8 (hooks) — see peerDependencies in package.json.
|
|
22
|
+
* ========================================================================== */
|
|
23
|
+
import {
|
|
24
|
+
useCallback,
|
|
25
|
+
useEffect,
|
|
26
|
+
useLayoutEffect,
|
|
27
|
+
useRef,
|
|
28
|
+
useState,
|
|
29
|
+
createElement,
|
|
30
|
+
forwardRef,
|
|
31
|
+
} from 'react';
|
|
32
|
+
|
|
33
|
+
/** useEffect on the server, useLayoutEffect in the browser (avoids the SSR warning). */
|
|
34
|
+
const useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Map an event shorthand to the dispatched event name.
|
|
38
|
+
* "range" / "wick:range" → "wick:range" (the 0.x "hab:" prefix is no longer emitted).
|
|
39
|
+
* @param {string} name
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
export const toWickEventName = (name) =>
|
|
43
|
+
String(name).startsWith('wick:') ? String(name) : 'wick:' + String(name);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* camelCase prop name → kebab-case attribute name ("volShading" → "vol-shading").
|
|
47
|
+
* @param {string} key
|
|
48
|
+
* @returns {string}
|
|
49
|
+
*/
|
|
50
|
+
export const toAttrName = (key) => key.replace(/[A-Z]/g, (c) => '-' + c.toLowerCase());
|
|
51
|
+
|
|
52
|
+
const DOM_PROPS = new Set(['className', 'class', 'style', 'id', 'title', 'role', 'key', 'ref']);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Split React props into chart attrs / event handlers / DOM passthrough /
|
|
56
|
+
* the data array / the overlays array.
|
|
57
|
+
* @param {object} props
|
|
58
|
+
* @returns {{ attrs: object, events: Record<string, Function>, dom: object, data: any, overlays: any }}
|
|
59
|
+
*/
|
|
60
|
+
export function splitChartProps(props) {
|
|
61
|
+
const attrs = {};
|
|
62
|
+
const events = {};
|
|
63
|
+
const dom = {};
|
|
64
|
+
let data;
|
|
65
|
+
let overlays;
|
|
66
|
+
for (const [key, val] of Object.entries(props || {})) {
|
|
67
|
+
if (key === 'data') {
|
|
68
|
+
data = val;
|
|
69
|
+
} else if (key === 'overlays') {
|
|
70
|
+
overlays = val;
|
|
71
|
+
} else if (key === 'events') {
|
|
72
|
+
for (const [name, fn] of Object.entries(val || {})) events[name] = fn;
|
|
73
|
+
} else if (/^on[A-Z]/.test(key)) {
|
|
74
|
+
events[key.slice(2, 3).toLowerCase() + key.slice(3)] = val;
|
|
75
|
+
} else if (
|
|
76
|
+
DOM_PROPS.has(key) ||
|
|
77
|
+
key.startsWith('aria-') ||
|
|
78
|
+
key.startsWith('data-') ||
|
|
79
|
+
typeof val === 'object'
|
|
80
|
+
) {
|
|
81
|
+
dom[key] = val;
|
|
82
|
+
} else {
|
|
83
|
+
attrs[key] = val;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return { attrs, events, dom, data, overlays };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Apply split props to a chart element. Every write is guarded so re-running
|
|
91
|
+
* with identical values is a no-op (attributes compared as strings, `data`
|
|
92
|
+
* and `overlays` compared by identity — passing a fresh array is what
|
|
93
|
+
* triggers a redraw).
|
|
94
|
+
* @param {HTMLElement} el
|
|
95
|
+
* @param {{ attrs?: object, data?: any, overlays?: any }} split
|
|
96
|
+
*/
|
|
97
|
+
export function applyChartProps(el, split) {
|
|
98
|
+
if (!el) return;
|
|
99
|
+
for (const [key, val] of Object.entries(split.attrs || {})) {
|
|
100
|
+
const name = toAttrName(key);
|
|
101
|
+
if (val == null || val === false) {
|
|
102
|
+
if (el.hasAttribute(name)) el.removeAttribute(name);
|
|
103
|
+
} else {
|
|
104
|
+
const str = val === true ? '' : String(val);
|
|
105
|
+
if (el.getAttribute(name) !== str) el.setAttribute(name, str);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// The element exposes `data` as a getter-only accessor — feed it through
|
|
109
|
+
// setData() (guarded by identity so unchanged arrays never re-ingest).
|
|
110
|
+
if (split.data != null && el.data !== split.data) {
|
|
111
|
+
if (typeof el.setData === 'function') el.setData(split.data);
|
|
112
|
+
else el.data = split.data;
|
|
113
|
+
}
|
|
114
|
+
// Overlays follow the same rule through setOverlays(); identity is tracked
|
|
115
|
+
// on the element because the getter returns copies.
|
|
116
|
+
if (split.overlays != null && el.__wickOverlaysRef !== split.overlays) {
|
|
117
|
+
el.__wickOverlaysRef = split.overlays;
|
|
118
|
+
if (typeof el.setOverlays === 'function') el.setOverlays(split.overlays);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Full control hook: renders nothing — attach the returned ref to your own
|
|
124
|
+
* <wick-chart> element and pass the same options you would give the component.
|
|
125
|
+
*
|
|
126
|
+
* const { ref, chart } = useWickChart({ data: bars, indicators: 'sma:20', onRange });
|
|
127
|
+
* return <wick-chart ref={ref} style={{ height: 420 }} />;
|
|
128
|
+
* // chart.getDataWindow() etc. once mounted
|
|
129
|
+
*
|
|
130
|
+
* @param {object} [options] any <wick-chart> attribute, plus `data`, `events`
|
|
131
|
+
* and `onXxx`-style handlers (see splitChartProps).
|
|
132
|
+
* @returns {{ ref: (node: any) => void, chart: any }} `chart` is the element
|
|
133
|
+
* instance (or null before mount) for the imperative API.
|
|
134
|
+
*/
|
|
135
|
+
export function useWickChart(options = {}) {
|
|
136
|
+
const split = splitChartProps(options);
|
|
137
|
+
const splitRef = useRef(split);
|
|
138
|
+
splitRef.current = split;
|
|
139
|
+
|
|
140
|
+
const [chart, setChart] = useState(null);
|
|
141
|
+
const ref = useCallback((node) => { setChart(node); }, []);
|
|
142
|
+
|
|
143
|
+
// Apply attrs/data on every commit — guarded writes make this cheap, and it
|
|
144
|
+
// catches both prop changes and the element mounting after the first render.
|
|
145
|
+
useIsomorphicLayoutEffect(() => {
|
|
146
|
+
if (chart) applyChartProps(chart, splitRef.current);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// Subscribe to wick:* events through stable trampolines so subscriptions
|
|
150
|
+
// only churn when the set of event names changes — not on every render.
|
|
151
|
+
const eventsRef = useRef(split.events);
|
|
152
|
+
eventsRef.current = split.events;
|
|
153
|
+
const names = Object.keys(split.events).join(' ');
|
|
154
|
+
useEffect(() => {
|
|
155
|
+
if (!chart || !names) return undefined;
|
|
156
|
+
const offs = names.split(' ').map((key) => {
|
|
157
|
+
const type = toWickEventName(key);
|
|
158
|
+
const trampoline = (ev) => {
|
|
159
|
+
const fn = eventsRef.current[key];
|
|
160
|
+
if (fn) fn(ev);
|
|
161
|
+
};
|
|
162
|
+
chart.addEventListener(type, trampoline);
|
|
163
|
+
return () => chart.removeEventListener(type, trampoline);
|
|
164
|
+
});
|
|
165
|
+
return () => { for (const off of offs) off(); };
|
|
166
|
+
}, [chart, names]);
|
|
167
|
+
|
|
168
|
+
return { ref, chart };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Drop-in React component for <wick-chart>. Attributes ride through
|
|
173
|
+
* createElement (so they exist at first paint and in SSR output) while the
|
|
174
|
+
* hook keeps them in sync on updates; `data` and event handlers never touch
|
|
175
|
+
* React's prop pipeline. Works the same on React 16.8 → 19.
|
|
176
|
+
*/
|
|
177
|
+
export const WickChart = forwardRef(function WickChart(props, fwdRef) {
|
|
178
|
+
const { ref } = useWickChart(props);
|
|
179
|
+
const { attrs, dom } = splitChartProps(props);
|
|
180
|
+
const setEl = useCallback(
|
|
181
|
+
(node) => {
|
|
182
|
+
ref(node);
|
|
183
|
+
if (typeof fwdRef === 'function') fwdRef(node);
|
|
184
|
+
else if (fwdRef) fwdRef.current = node;
|
|
185
|
+
},
|
|
186
|
+
[ref, fwdRef]
|
|
187
|
+
);
|
|
188
|
+
return createElement('wick-chart', { ...dom, ...attrs, ref: setEl });
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
export default WickChart;
|
package/src/react.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* ==========================================================================
|
|
2
|
+
* wickchart/react — React bindings for <wick-chart> (package entry).
|
|
3
|
+
*
|
|
4
|
+
* import { WickChart, useWickChart } from 'wickchart/react';
|
|
5
|
+
*
|
|
6
|
+
* Importing this module also defines the <wick-chart> custom element (same
|
|
7
|
+
* side-effect contract as wickchart/feed). All binding logic lives in
|
|
8
|
+
* ./react-core.js; SSR-safe on the server (the define call is guarded).
|
|
9
|
+
* ========================================================================== */
|
|
10
|
+
import './wick-chart.js';
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
WickChart,
|
|
14
|
+
useWickChart,
|
|
15
|
+
splitChartProps,
|
|
16
|
+
applyChartProps,
|
|
17
|
+
toWickEventName,
|
|
18
|
+
toAttrName,
|
|
19
|
+
} from './react-core.js';
|
|
20
|
+
|
|
21
|
+
export { default } from './react-core.js';
|