@tradecanvas/react 1.3.28 → 1.3.30
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 +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -5
- package/dist/index.js +67 -67
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,7 +28,7 @@ Streams live Binance data by default. Pass `data={bars}` for static OHLC, or `ad
|
|
|
28
28
|
|
|
29
29
|
## Props
|
|
30
30
|
|
|
31
|
-
`symbol`, `timeframe`, `theme` (`'dark'` | `'light'` | `Theme`), `chartType`, `indicators` (ids, or `{ id, params, position }`), `data` (bars of your own; no stream), `adapter`, `stream` (`false`: no stream of its own), `historyLimit`, `features` (changes apply after mount too), `autoScale`, `signalMarkers`, `signalMarkerStyle`, `tradeZones`, `tradeZoneStyle`, `overrides` (style overrides by key, all of your app's), `watermarkText`, `onReady(chart)`, `onCrosshairMove`, `className`, `style`.
|
|
31
|
+
`symbol`, `timeframe`, `theme` (`'dark'` | `'light'` | `Theme`), `chartType`, `indicators` (ids, or `{ id, params, position }`), `data` (bars of your own; no stream; a new array that goes on from the last, `[...bars, next]`, goes in as live ticks), `adapter`, `stream` (`false`: no stream of its own), `historyLimit`, `features` (changes apply after mount too), `autoScale`, `signalMarkers`, `signalMarkerStyle`, `tradeZones`, `tradeZoneStyle`, `overrides` (style overrides by key, all of your app's), `watermarkText`, `onReady(chart)`, `onCrosshairMove`, `className`, `style`.
|
|
32
32
|
|
|
33
33
|
## Imperative access
|
|
34
34
|
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`react`),t=require(`@tradecanvas/chart`),n=require(`react/jsx-runtime`);function r(e){return!e||e===`dark`?t.DARK_THEME:e===`light`?t.LIGHT_THEME:e}var i=(0,e.forwardRef)(function(i,a){let{symbol:o=`BTCUSDT`,timeframe:s=`5m`,theme:c=`dark`,chartType:l=`candlestick`,indicators:u=[],data:d,adapter:f,stream:p=!0,historyLimit:m=500,features:h,autoScale:g=!0,signalMarkers:_,signalMarkerStyle:v,tradeZones:y,tradeZoneStyle:b,overrides:x,watermarkText:S,options:C,onReady:w,onCrosshairMove:T,className:E,style:D}=i,O=(0,e.useRef)(null),k=(0,e.useRef)(null),A=(0,e.useRef)(new Map),j=(0,e.useRef)(null),M=(0,e.useRef)(JSON.stringify(h??null));(0,e.useImperativeHandle)(a,()=>({getChart:()=>k.current,screenshot:e=>k.current?.screenshot(e),screenshotDataURL:()=>k.current?.screenshotDataURL()??null})),(0,e.useEffect)(()=>{let e=O.current;if(!e)return;let n={rightMargin:5,crosshair:{mode:`magnet`},...C,chartType:l,theme:r(c),autoScale:g,features:{drawings:!0,drawingMagnet:!0,drawingUndoRedo:!0,indicators:!0,trading:!0,volume:!0,legend:!0,crosshair:!0,keyboard:!0,screenshot:!0,alerts:!0,barCountdown:!0,logScale:!0,watermark:!0,...C?.features,...h}};S&&(n.watermark={text:S,fontSize:48,color:`rgba(255,255,255,0.03)`});let i=new t.Chart(e,n);if(k.current=i,A.current=new Map,d)i
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`react`),t=require(`@tradecanvas/chart`),n=require(`react/jsx-runtime`);function r(e){return!e||e===`dark`?t.DARK_THEME:e===`light`?t.LIGHT_THEME:e}var i=(0,e.forwardRef)(function(i,a){let{symbol:o=`BTCUSDT`,timeframe:s=`5m`,theme:c=`dark`,chartType:l=`candlestick`,indicators:u=[],data:d,adapter:f,stream:p=!0,historyLimit:m=500,features:h,autoScale:g=!0,signalMarkers:_,signalMarkerStyle:v,tradeZones:y,tradeZoneStyle:b,overrides:x,watermarkText:S,options:C,onReady:w,onCrosshairMove:T,className:E,style:D}=i,O=(0,e.useRef)(null),k=(0,e.useRef)(null),A=(0,e.useRef)(new Map),j=(0,e.useRef)(null),M=(0,e.useRef)(null),N=(0,e.useRef)(JSON.stringify(h??null));(0,e.useImperativeHandle)(a,()=>({getChart:()=>k.current,screenshot:e=>k.current?.screenshot(e),screenshotDataURL:()=>k.current?.screenshotDataURL()??null})),(0,e.useEffect)(()=>{let e=O.current;if(!e)return;let n={rightMargin:5,crosshair:{mode:`magnet`},...C,chartType:l,theme:r(c),autoScale:g,features:{drawings:!0,drawingMagnet:!0,drawingUndoRedo:!0,indicators:!0,trading:!0,volume:!0,legend:!0,crosshair:!0,keyboard:!0,screenshot:!0,alerts:!0,barCountdown:!0,logScale:!0,watermark:!0,...C?.features,...h}};S&&(n.watermark={text:S,fontSize:48,color:`rgba(255,255,255,0.03)`});let i=new t.Chart(e,n);if(k.current=i,A.current=new Map,j.current=d?(0,t.feedBars)(i,d,null):null,!d&&p){let e=f??new t.BinanceAdapter;i.connect({adapter:e,symbol:o,timeframe:s,historyLimit:m}),M.current=`${o}|${s}`}return w?.(i),()=>{i.disconnectStream(),i.destroy(),k.current=null,A.current.clear()}},[]),(0,e.useEffect)(()=>{let e=k.current;if(!e)return;if(!p){M.current!==null&&e.disconnectStream(),M.current=null;return}if(d)return;let n=`${o}|${s}`;if(M.current===n)return;M.current=n,e.disconnectStream();let r=f??new t.BinanceAdapter;e.connect({adapter:r,symbol:o,timeframe:s,historyLimit:m}),S!==void 0&&e.setWatermark(S)},[o,s,f,m,d,p,S]),(0,e.useEffect)(()=>{let e=JSON.stringify(h??null);e!==N.current&&(N.current=e,h&&k.current?.setFeatures(h))},[h]),(0,e.useEffect)(()=>{k.current?.setTheme(r(c))},[c]),(0,e.useEffect)(()=>{k.current?.setChartType(l)},[l]),(0,e.useEffect)(()=>{let e=k.current;e&&(j.current=d?(0,t.feedBars)(e,d,j.current):null)},[d]),(0,e.useEffect)(()=>{let e=k.current;e&&(0,t.syncIndicators)(e,u,A.current)},[u]),(0,e.useEffect)(()=>{_&&k.current?.setSignalMarkers(_)},[_]),(0,e.useEffect)(()=>{v&&k.current?.setSignalMarkerStyle(v)},[v]),(0,e.useEffect)(()=>{y&&k.current?.setTradeZones(y)},[y]),(0,e.useEffect)(()=>{b&&k.current?.setTradeZoneStyle(b)},[b]);let P=(0,e.useRef)(!1);return(0,e.useEffect)(()=>{x===void 0&&!P.current||(P.current=x!==void 0,k.current?.setOverrides(x??{}))},[x]),(0,e.useEffect)(()=>{let e=k.current;if(!e||!T)return;let t=T;return e.on(`crosshairMove`,t),()=>e.off(`crosshairMove`,t)},[T]),(0,n.jsx)(`div`,{ref:O,className:E,style:{width:`100%`,height:`100%`,...D}})});exports.TradeCanvas=i;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/TradeCanvas.tsx"],"sourcesContent":["import { useRef, useEffect, useImperativeHandle, forwardRef, type CSSProperties } from 'react';\nimport {\n Chart,\n BinanceAdapter,\n DARK_THEME,\n LIGHT_THEME,\n syncIndicators,\n} from '@tradecanvas/chart';\nimport type {\n ChartType,\n TimeFrame,\n ChartOptions,\n FeaturesConfig,\n Theme,\n OHLCBar,\n SignalMarker,\n TradeZone,\n SignalMarkerStyle,\n TradeZoneStyle,\n DataAdapter,\n StreamConfig,\n ChartStyleOverrides,\n IndicatorSpec,\n} from '@tradecanvas/chart';\n\nexport interface TradeCanvasRef {\n getChart(): Chart | null;\n screenshot(filename?: string): void;\n screenshotDataURL(): string | null;\n}\n\n/**\n * The chart is made once, on mount. Later changes to `symbol`, `timeframe`,\n * `theme`, `chartType`, `indicators`, `data`, `stream`, `features`,\n * `overrides`, the markers and zones and `onCrosshairMove` go to it\n * (`setTheme`, `setChartType`, `setData`…). `options` and `autoScale` are\n * read at mount; `adapter`, `historyLimit` and `watermarkText` when the\n * stream connects (at mount, or for a new symbol or interval). Unmounting\n * closes the chart's stream and destroys it.\n */\nexport interface TradeCanvasProps {\n /** The stream's symbol (`'BTCUSDT'`); a new one reconnects the stream, while there is no `data`. */\n symbol?: string;\n /** The stream's interval (`'5m'`); a new one reconnects it, while there is no `data`. */\n timeframe?: TimeFrame;\n theme?: 'dark' | 'light' | Theme;\n chartType?: ChartType;\n /**\n * Indicators by id (`'rsi'`), or with their inputs (`{ id: 'ema', params: { period: 50 } }`).\n * A new list is matched to what's on the chart: added, removed, and one\n * whose inputs changed put back with them (its style edits go).\n */\n indicators?: IndicatorSpec[];\n /**\n * Static bars: the chart shows these, and opens no stream of its own when\n * it has them at mount. A new array replaces the series (`setData`): the\n * view goes back to the newest bar and every indicator is computed again.\n * For a live feed, update the chart itself from the ref\n * (`getChart()?.updateLastBar(bar)`, `appendBar`), or give it a stream.\n */\n data?: OHLCBar[];\n /** The stream's feed (Binance by default), taken when the stream connects. */\n adapter?: DataAdapter;\n /** `false`: no stream until you say so (no Binance by default); the chart waits for `data`. Default `true`. */\n stream?: boolean;\n historyLimit?: number;\n /** What users can do on the chart; changes after mount are applied too (the keys given). */\n features?: FeaturesConfig;\n autoScale?: boolean;\n signalMarkers?: SignalMarker[];\n signalMarkerStyle?: SignalMarkerStyle;\n tradeZones?: TradeZone[];\n tradeZoneStyle?: TradeZoneStyle;\n /** Style overrides, by key: all of your app's (`chart.setOverrides`). */\n overrides?: ChartStyleOverrides;\n watermarkText?: string;\n /**\n * Chart options the props don't cover (`maxBarSpacing`, `rightMargin`,\n * `renderer`…), merged over the component's defaults at mount; later\n * changes are not applied. `chartType`, `theme` and `autoScale` always come\n * from their props.\n */\n options?: Partial<ChartOptions>;\n /**\n * Called with the chart when it is made, on mount: once, or twice in\n * React's StrictMode in development, which mounts twice (the first chart\n * is destroyed). The ref's `getChart()` gives it too.\n */\n onReady?: (chart: Chart) => void;\n onCrosshairMove?: (payload: unknown) => void;\n className?: string;\n style?: CSSProperties;\n}\n\nfunction resolveTheme(theme: 'dark' | 'light' | Theme | undefined): Theme {\n if (!theme || theme === 'dark') return DARK_THEME;\n if (theme === 'light') return LIGHT_THEME;\n return theme;\n}\n\nexport const TradeCanvas = forwardRef<TradeCanvasRef, TradeCanvasProps>(\n function TradeCanvas(props, ref) {\n const {\n symbol = 'BTCUSDT',\n timeframe = '5m' as TimeFrame,\n theme = 'dark',\n chartType = 'candlestick' as ChartType,\n indicators = [],\n data,\n adapter,\n stream = true,\n historyLimit = 500,\n features,\n autoScale = true,\n signalMarkers,\n signalMarkerStyle,\n tradeZones,\n tradeZoneStyle,\n overrides,\n watermarkText,\n options,\n onReady,\n onCrosshairMove,\n className,\n style,\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const chartRef = useRef<Chart | null>(null);\n const indicatorIdsRef = useRef<Map<string, string>>(new Map());\n /** The stream open now (`symbol|timeframe`), or null. */\n const streamKeyRef = useRef<string | null>(null);\n const featuresRef = useRef(JSON.stringify(features ?? null));\n\n useImperativeHandle(ref, () => ({\n getChart: () => chartRef.current,\n screenshot: (filename?: string) => chartRef.current?.screenshot(filename),\n screenshotDataURL: () => chartRef.current?.screenshotDataURL() ?? null,\n }));\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const opts: ChartOptions = {\n rightMargin: 5,\n crosshair: { mode: 'magnet' },\n ...options,\n chartType,\n theme: resolveTheme(theme),\n autoScale,\n features: {\n drawings: true,\n drawingMagnet: true,\n drawingUndoRedo: true,\n indicators: true,\n trading: true,\n volume: true,\n legend: true,\n crosshair: true,\n keyboard: true,\n screenshot: true,\n alerts: true,\n barCountdown: true,\n logScale: true,\n watermark: true,\n ...options?.features,\n ...features,\n },\n };\n\n if (watermarkText) {\n opts.watermark = { text: watermarkText, fontSize: 48, color: 'rgba(255,255,255,0.03)' };\n }\n\n const chart = new Chart(container, opts);\n chartRef.current = chart;\n indicatorIdsRef.current = new Map();\n\n if (data) {\n chart.setData(data);\n } else if (stream) {\n const dataAdapter = adapter ?? new BinanceAdapter();\n chart.connect({ adapter: dataAdapter, symbol, timeframe, historyLimit });\n streamKeyRef.current = `${symbol}|${timeframe}`;\n }\n\n onReady?.(chart);\n\n return () => {\n chart.disconnectStream();\n chart.destroy();\n chartRef.current = null;\n indicatorIdsRef.current.clear();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // A stream of its own while it has no data; `stream={false}` closes it. Data given later leaves it be.\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart) return;\n if (!stream) {\n if (streamKeyRef.current !== null) chart.disconnectStream();\n streamKeyRef.current = null;\n return;\n }\n if (data) return;\n const key = `${symbol}|${timeframe}`;\n if (streamKeyRef.current === key) return;\n streamKeyRef.current = key;\n\n chart.disconnectStream();\n const dataAdapter = adapter ?? new BinanceAdapter();\n chart.connect({ adapter: dataAdapter, symbol, timeframe, historyLimit });\n if (watermarkText !== undefined) {\n chart.setWatermark(watermarkText);\n }\n }, [symbol, timeframe, adapter, historyLimit, data, stream, watermarkText]);\n\n useEffect(() => {\n const next = JSON.stringify(features ?? null);\n if (next === featuresRef.current) return;\n featuresRef.current = next;\n if (features) chartRef.current?.setFeatures(features);\n }, [features]);\n\n useEffect(() => {\n chartRef.current?.setTheme(resolveTheme(theme));\n }, [theme]);\n\n useEffect(() => {\n chartRef.current?.setChartType(chartType);\n }, [chartType]);\n\n useEffect(() => {\n if (data) chartRef.current?.setData(data);\n }, [data]);\n\n useEffect(() => {\n const chart = chartRef.current;\n if (chart) syncIndicators(chart, indicators, indicatorIdsRef.current);\n }, [indicators]);\n\n useEffect(() => {\n if (signalMarkers) chartRef.current?.setSignalMarkers(signalMarkers);\n }, [signalMarkers]);\n\n useEffect(() => {\n if (signalMarkerStyle) chartRef.current?.setSignalMarkerStyle(signalMarkerStyle);\n }, [signalMarkerStyle]);\n\n useEffect(() => {\n if (tradeZones) chartRef.current?.setTradeZones(tradeZones);\n }, [tradeZones]);\n\n useEffect(() => {\n if (tradeZoneStyle) chartRef.current?.setTradeZoneStyle(tradeZoneStyle);\n }, [tradeZoneStyle]);\n\n // All of the host's overrides: keys left out of the prop are taken away.\n // Without the prop, overrides set through the chart itself are left alone.\n const overridesGiven = useRef(false);\n useEffect(() => {\n if (overrides === undefined && !overridesGiven.current) return;\n overridesGiven.current = overrides !== undefined;\n chartRef.current?.setOverrides(overrides ?? {});\n }, [overrides]);\n\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart || !onCrosshairMove) return;\n const handler = onCrosshairMove as (e: unknown) => void;\n chart.on('crosshairMove', handler);\n // A new handler (an inline one is new each render) replaces this one.\n return () => chart.off('crosshairMove', handler);\n }, [onCrosshairMove]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ width: '100%', height: '100%', ...style }}\n />\n );\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/TradeCanvas.tsx"],"sourcesContent":["import { useRef, useEffect, useImperativeHandle, forwardRef, type CSSProperties } from 'react';\nimport {\n Chart,\n BinanceAdapter,\n DARK_THEME,\n LIGHT_THEME,\n syncIndicators,\n feedBars,\n} from '@tradecanvas/chart';\nimport type {\n ChartType,\n TimeFrame,\n ChartOptions,\n FeaturesConfig,\n Theme,\n OHLCBar,\n SignalMarker,\n TradeZone,\n SignalMarkerStyle,\n TradeZoneStyle,\n DataAdapter,\n StreamConfig,\n ChartStyleOverrides,\n IndicatorSpec,\n FedBars,\n} from '@tradecanvas/chart';\n\nexport interface TradeCanvasRef {\n getChart(): Chart | null;\n screenshot(filename?: string): void;\n screenshotDataURL(): string | null;\n}\n\n/**\n * The chart is made once, on mount. Later changes to `symbol`, `timeframe`,\n * `theme`, `chartType`, `indicators`, `data`, `stream`, `features`,\n * `overrides`, the markers and zones and `onCrosshairMove` go to it\n * (`setTheme`, `setChartType`, `setData`…). `options` and `autoScale` are\n * read at mount; `adapter`, `historyLimit` and `watermarkText` when the\n * stream connects (at mount, or for a new symbol or interval). Unmounting\n * closes the chart's stream and destroys it.\n */\nexport interface TradeCanvasProps {\n /** The stream's symbol (`'BTCUSDT'`); a new one reconnects the stream, while there is no `data`. */\n symbol?: string;\n /** The stream's interval (`'5m'`); a new one reconnects it, while there is no `data`. */\n timeframe?: TimeFrame;\n theme?: 'dark' | 'light' | Theme;\n chartType?: ChartType;\n /**\n * Indicators by id (`'rsi'`), or with their inputs (`{ id: 'ema', params: { period: 50 } }`).\n * A new list is matched to what's on the chart: added, removed, and one\n * whose inputs changed put back with them (its style edits go).\n */\n indicators?: IndicatorSpec[];\n /**\n * The bars: the chart shows these, and opens no stream of its own when it\n * has them at mount. A new array that goes on from the last one, its last\n * bar changed or bars added after it (`[...bars.slice(0, -1), forming]`,\n * `[...bars, next]`), goes in as a live feed's ticks do (`updateLastBar`,\n * `appendBar`, `appendBars`): the view isn't reset, only the end is\n * computed again, and alerts follow the price. Any other new array is a new\n * series (`setData`): the view goes back to the newest bar and every\n * indicator is computed again. So is one after a stream, or code through\n * the ref, gave the chart bars of its own. An older bar changed needs a new\n * bar object in its place.\n */\n data?: OHLCBar[];\n /** The stream's feed (Binance by default), taken when the stream connects. */\n adapter?: DataAdapter;\n /** `false`: no stream until you say so (no Binance by default); the chart waits for `data`. Default `true`. */\n stream?: boolean;\n historyLimit?: number;\n /** What users can do on the chart; changes after mount are applied too (the keys given). */\n features?: FeaturesConfig;\n autoScale?: boolean;\n signalMarkers?: SignalMarker[];\n signalMarkerStyle?: SignalMarkerStyle;\n tradeZones?: TradeZone[];\n tradeZoneStyle?: TradeZoneStyle;\n /** Style overrides, by key: all of your app's (`chart.setOverrides`). */\n overrides?: ChartStyleOverrides;\n watermarkText?: string;\n /**\n * Chart options the props don't cover (`maxBarSpacing`, `rightMargin`,\n * `renderer`…), merged over the component's defaults at mount; later\n * changes are not applied. `chartType`, `theme` and `autoScale` always come\n * from their props.\n */\n options?: Partial<ChartOptions>;\n /**\n * Called with the chart when it is made, on mount: once, or twice in\n * React's StrictMode in development, which mounts twice (the first chart\n * is destroyed). The ref's `getChart()` gives it too.\n */\n onReady?: (chart: Chart) => void;\n onCrosshairMove?: (payload: unknown) => void;\n className?: string;\n style?: CSSProperties;\n}\n\nfunction resolveTheme(theme: 'dark' | 'light' | Theme | undefined): Theme {\n if (!theme || theme === 'dark') return DARK_THEME;\n if (theme === 'light') return LIGHT_THEME;\n return theme;\n}\n\nexport const TradeCanvas = forwardRef<TradeCanvasRef, TradeCanvasProps>(\n function TradeCanvas(props, ref) {\n const {\n symbol = 'BTCUSDT',\n timeframe = '5m' as TimeFrame,\n theme = 'dark',\n chartType = 'candlestick' as ChartType,\n indicators = [],\n data,\n adapter,\n stream = true,\n historyLimit = 500,\n features,\n autoScale = true,\n signalMarkers,\n signalMarkerStyle,\n tradeZones,\n tradeZoneStyle,\n overrides,\n watermarkText,\n options,\n onReady,\n onCrosshairMove,\n className,\n style,\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const chartRef = useRef<Chart | null>(null);\n const indicatorIdsRef = useRef<Map<string, string>>(new Map());\n /** The bars last given to the chart (`feedBars`). */\n const fedRef = useRef<FedBars | null>(null);\n /** The stream open now (`symbol|timeframe`), or null. */\n const streamKeyRef = useRef<string | null>(null);\n const featuresRef = useRef(JSON.stringify(features ?? null));\n\n useImperativeHandle(ref, () => ({\n getChart: () => chartRef.current,\n screenshot: (filename?: string) => chartRef.current?.screenshot(filename),\n screenshotDataURL: () => chartRef.current?.screenshotDataURL() ?? null,\n }));\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const opts: ChartOptions = {\n rightMargin: 5,\n crosshair: { mode: 'magnet' },\n ...options,\n chartType,\n theme: resolveTheme(theme),\n autoScale,\n features: {\n drawings: true,\n drawingMagnet: true,\n drawingUndoRedo: true,\n indicators: true,\n trading: true,\n volume: true,\n legend: true,\n crosshair: true,\n keyboard: true,\n screenshot: true,\n alerts: true,\n barCountdown: true,\n logScale: true,\n watermark: true,\n ...options?.features,\n ...features,\n },\n };\n\n if (watermarkText) {\n opts.watermark = { text: watermarkText, fontSize: 48, color: 'rgba(255,255,255,0.03)' };\n }\n\n const chart = new Chart(container, opts);\n chartRef.current = chart;\n indicatorIdsRef.current = new Map();\n\n fedRef.current = data ? feedBars(chart, data, null) : null;\n if (!data && stream) {\n const dataAdapter = adapter ?? new BinanceAdapter();\n chart.connect({ adapter: dataAdapter, symbol, timeframe, historyLimit });\n streamKeyRef.current = `${symbol}|${timeframe}`;\n }\n\n onReady?.(chart);\n\n return () => {\n chart.disconnectStream();\n chart.destroy();\n chartRef.current = null;\n indicatorIdsRef.current.clear();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // A stream of its own while it has no data; `stream={false}` closes it. Data given later leaves it be.\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart) return;\n if (!stream) {\n if (streamKeyRef.current !== null) chart.disconnectStream();\n streamKeyRef.current = null;\n return;\n }\n if (data) return;\n const key = `${symbol}|${timeframe}`;\n if (streamKeyRef.current === key) return;\n streamKeyRef.current = key;\n\n chart.disconnectStream();\n const dataAdapter = adapter ?? new BinanceAdapter();\n chart.connect({ adapter: dataAdapter, symbol, timeframe, historyLimit });\n if (watermarkText !== undefined) {\n chart.setWatermark(watermarkText);\n }\n }, [symbol, timeframe, adapter, historyLimit, data, stream, watermarkText]);\n\n useEffect(() => {\n const next = JSON.stringify(features ?? null);\n if (next === featuresRef.current) return;\n featuresRef.current = next;\n if (features) chartRef.current?.setFeatures(features);\n }, [features]);\n\n useEffect(() => {\n chartRef.current?.setTheme(resolveTheme(theme));\n }, [theme]);\n\n useEffect(() => {\n chartRef.current?.setChartType(chartType);\n }, [chartType]);\n\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart) return;\n // Without data, the next bars are a new series: a stream may have come in between.\n fedRef.current = data ? feedBars(chart, data, fedRef.current) : null;\n }, [data]);\n\n useEffect(() => {\n const chart = chartRef.current;\n if (chart) syncIndicators(chart, indicators, indicatorIdsRef.current);\n }, [indicators]);\n\n useEffect(() => {\n if (signalMarkers) chartRef.current?.setSignalMarkers(signalMarkers);\n }, [signalMarkers]);\n\n useEffect(() => {\n if (signalMarkerStyle) chartRef.current?.setSignalMarkerStyle(signalMarkerStyle);\n }, [signalMarkerStyle]);\n\n useEffect(() => {\n if (tradeZones) chartRef.current?.setTradeZones(tradeZones);\n }, [tradeZones]);\n\n useEffect(() => {\n if (tradeZoneStyle) chartRef.current?.setTradeZoneStyle(tradeZoneStyle);\n }, [tradeZoneStyle]);\n\n // All of the host's overrides: keys left out of the prop are taken away.\n // Without the prop, overrides set through the chart itself are left alone.\n const overridesGiven = useRef(false);\n useEffect(() => {\n if (overrides === undefined && !overridesGiven.current) return;\n overridesGiven.current = overrides !== undefined;\n chartRef.current?.setOverrides(overrides ?? {});\n }, [overrides]);\n\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart || !onCrosshairMove) return;\n const handler = onCrosshairMove as (e: unknown) => void;\n chart.on('crosshairMove', handler);\n // A new handler (an inline one is new each render) replaces this one.\n return () => chart.off('crosshairMove', handler);\n }, [onCrosshairMove]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ width: '100%', height: '100%', ...style }}\n />\n );\n },\n);\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -41,11 +41,16 @@ export declare interface TradeCanvasProps {
|
|
|
41
41
|
*/
|
|
42
42
|
indicators?: IndicatorSpec[];
|
|
43
43
|
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
44
|
+
* The bars: the chart shows these, and opens no stream of its own when it
|
|
45
|
+
* has them at mount. A new array that goes on from the last one, its last
|
|
46
|
+
* bar changed or bars added after it (`[...bars.slice(0, -1), forming]`,
|
|
47
|
+
* `[...bars, next]`), goes in as a live feed's ticks do (`updateLastBar`,
|
|
48
|
+
* `appendBar`, `appendBars`): the view isn't reset, only the end is
|
|
49
|
+
* computed again, and alerts follow the price. Any other new array is a new
|
|
50
|
+
* series (`setData`): the view goes back to the newest bar and every
|
|
51
|
+
* indicator is computed again. So is one after a stream, or code through
|
|
52
|
+
* the ref, gave the chart bars of its own. An older bar changed needs a new
|
|
53
|
+
* bar object in its place.
|
|
49
54
|
*/
|
|
50
55
|
data?: OHLCBar[];
|
|
51
56
|
/** The stream's feed (Binance by default), taken when the stream connects. */
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import { forwardRef as e, useEffect as t, useImperativeHandle as n, useRef as r } from "react";
|
|
2
|
-
import { BinanceAdapter as i, Chart as a, DARK_THEME as o, LIGHT_THEME as s,
|
|
3
|
-
import { jsx as
|
|
2
|
+
import { BinanceAdapter as i, Chart as a, DARK_THEME as o, LIGHT_THEME as s, feedBars as c, syncIndicators as l } from "@tradecanvas/chart";
|
|
3
|
+
import { jsx as u } from "react/jsx-runtime";
|
|
4
4
|
//#region src/TradeCanvas.tsx
|
|
5
|
-
function
|
|
5
|
+
function d(e) {
|
|
6
6
|
return !e || e === "dark" ? o : e === "light" ? s : e;
|
|
7
7
|
}
|
|
8
|
-
var
|
|
9
|
-
let { symbol: s = "BTCUSDT", timeframe:
|
|
8
|
+
var f = e(function(e, o) {
|
|
9
|
+
let { symbol: s = "BTCUSDT", timeframe: f = "5m", theme: p = "dark", chartType: m = "candlestick", indicators: h = [], data: g, adapter: _, stream: v = !0, historyLimit: y = 500, features: b, autoScale: x = !0, signalMarkers: S, signalMarkerStyle: C, tradeZones: w, tradeZoneStyle: T, overrides: E, watermarkText: D, options: O, onReady: k, onCrosshairMove: A, className: j, style: M } = e, N = r(null), P = r(null), F = r(/* @__PURE__ */ new Map()), I = r(null), L = r(null), R = r(JSON.stringify(b ?? null));
|
|
10
10
|
n(o, () => ({
|
|
11
|
-
getChart: () =>
|
|
12
|
-
screenshot: (e) =>
|
|
13
|
-
screenshotDataURL: () =>
|
|
11
|
+
getChart: () => P.current,
|
|
12
|
+
screenshot: (e) => P.current?.screenshot(e),
|
|
13
|
+
screenshotDataURL: () => P.current?.screenshotDataURL() ?? null
|
|
14
14
|
})), t(() => {
|
|
15
|
-
let e =
|
|
15
|
+
let e = N.current;
|
|
16
16
|
if (!e) return;
|
|
17
17
|
let t = {
|
|
18
18
|
rightMargin: 5,
|
|
19
19
|
crosshair: { mode: "magnet" },
|
|
20
|
-
...
|
|
21
|
-
chartType:
|
|
22
|
-
theme:
|
|
23
|
-
autoScale:
|
|
20
|
+
...O,
|
|
21
|
+
chartType: m,
|
|
22
|
+
theme: d(p),
|
|
23
|
+
autoScale: x,
|
|
24
24
|
features: {
|
|
25
25
|
drawings: !0,
|
|
26
26
|
drawingMagnet: !0,
|
|
@@ -36,95 +36,95 @@ var d = e(function(e, o) {
|
|
|
36
36
|
barCountdown: !0,
|
|
37
37
|
logScale: !0,
|
|
38
38
|
watermark: !0,
|
|
39
|
-
...
|
|
40
|
-
...
|
|
39
|
+
...O?.features,
|
|
40
|
+
...b
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
|
-
|
|
44
|
-
text:
|
|
43
|
+
D && (t.watermark = {
|
|
44
|
+
text: D,
|
|
45
45
|
fontSize: 48,
|
|
46
46
|
color: "rgba(255,255,255,0.03)"
|
|
47
47
|
});
|
|
48
48
|
let n = new a(e, t);
|
|
49
|
-
if (
|
|
50
|
-
|
|
51
|
-
let e = g ?? new i();
|
|
49
|
+
if (P.current = n, F.current = /* @__PURE__ */ new Map(), I.current = g ? c(n, g, null) : null, !g && v) {
|
|
50
|
+
let e = _ ?? new i();
|
|
52
51
|
n.connect({
|
|
53
52
|
adapter: e,
|
|
54
53
|
symbol: s,
|
|
55
|
-
timeframe:
|
|
56
|
-
historyLimit:
|
|
57
|
-
}),
|
|
54
|
+
timeframe: f,
|
|
55
|
+
historyLimit: y
|
|
56
|
+
}), L.current = `${s}|${f}`;
|
|
58
57
|
}
|
|
59
|
-
return
|
|
60
|
-
n.disconnectStream(), n.destroy(),
|
|
58
|
+
return k?.(n), () => {
|
|
59
|
+
n.disconnectStream(), n.destroy(), P.current = null, F.current.clear();
|
|
61
60
|
};
|
|
62
61
|
}, []), t(() => {
|
|
63
|
-
let e =
|
|
62
|
+
let e = P.current;
|
|
64
63
|
if (!e) return;
|
|
65
|
-
if (!
|
|
66
|
-
|
|
64
|
+
if (!v) {
|
|
65
|
+
L.current !== null && e.disconnectStream(), L.current = null;
|
|
67
66
|
return;
|
|
68
67
|
}
|
|
69
|
-
if (
|
|
70
|
-
let t = `${s}|${
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
let n =
|
|
68
|
+
if (g) return;
|
|
69
|
+
let t = `${s}|${f}`;
|
|
70
|
+
if (L.current === t) return;
|
|
71
|
+
L.current = t, e.disconnectStream();
|
|
72
|
+
let n = _ ?? new i();
|
|
74
73
|
e.connect({
|
|
75
74
|
adapter: n,
|
|
76
75
|
symbol: s,
|
|
77
|
-
timeframe:
|
|
78
|
-
historyLimit:
|
|
79
|
-
}),
|
|
76
|
+
timeframe: f,
|
|
77
|
+
historyLimit: y
|
|
78
|
+
}), D !== void 0 && e.setWatermark(D);
|
|
80
79
|
}, [
|
|
81
80
|
s,
|
|
82
|
-
|
|
81
|
+
f,
|
|
82
|
+
_,
|
|
83
|
+
y,
|
|
83
84
|
g,
|
|
84
85
|
v,
|
|
85
|
-
|
|
86
|
-
_,
|
|
87
|
-
E
|
|
86
|
+
D
|
|
88
87
|
]), t(() => {
|
|
89
|
-
let e = JSON.stringify(
|
|
90
|
-
e !==
|
|
91
|
-
}, [
|
|
92
|
-
|
|
93
|
-
}, [f]), t(() => {
|
|
94
|
-
N.current?.setChartType(p);
|
|
88
|
+
let e = JSON.stringify(b ?? null);
|
|
89
|
+
e !== R.current && (R.current = e, b && P.current?.setFeatures(b));
|
|
90
|
+
}, [b]), t(() => {
|
|
91
|
+
P.current?.setTheme(d(p));
|
|
95
92
|
}, [p]), t(() => {
|
|
96
|
-
|
|
97
|
-
}, [h]), t(() => {
|
|
98
|
-
let e = N.current;
|
|
99
|
-
e && c(e, m, P.current);
|
|
93
|
+
P.current?.setChartType(m);
|
|
100
94
|
}, [m]), t(() => {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
let e = P.current;
|
|
96
|
+
e && (I.current = g ? c(e, g, I.current) : null);
|
|
97
|
+
}, [g]), t(() => {
|
|
98
|
+
let e = P.current;
|
|
99
|
+
e && l(e, h, F.current);
|
|
100
|
+
}, [h]), t(() => {
|
|
101
|
+
S && P.current?.setSignalMarkers(S);
|
|
104
102
|
}, [S]), t(() => {
|
|
105
|
-
C &&
|
|
103
|
+
C && P.current?.setSignalMarkerStyle(C);
|
|
106
104
|
}, [C]), t(() => {
|
|
107
|
-
w &&
|
|
108
|
-
}, [w])
|
|
109
|
-
|
|
105
|
+
w && P.current?.setTradeZones(w);
|
|
106
|
+
}, [w]), t(() => {
|
|
107
|
+
T && P.current?.setTradeZoneStyle(T);
|
|
108
|
+
}, [T]);
|
|
109
|
+
let z = r(!1);
|
|
110
110
|
return t(() => {
|
|
111
|
-
|
|
112
|
-
}, [
|
|
113
|
-
let e =
|
|
114
|
-
if (!e || !
|
|
115
|
-
let t =
|
|
111
|
+
E === void 0 && !z.current || (z.current = E !== void 0, P.current?.setOverrides(E ?? {}));
|
|
112
|
+
}, [E]), t(() => {
|
|
113
|
+
let e = P.current;
|
|
114
|
+
if (!e || !A) return;
|
|
115
|
+
let t = A;
|
|
116
116
|
return e.on("crosshairMove", t), () => e.off("crosshairMove", t);
|
|
117
|
-
}, [
|
|
118
|
-
ref:
|
|
119
|
-
className:
|
|
117
|
+
}, [A]), /* @__PURE__ */ u("div", {
|
|
118
|
+
ref: N,
|
|
119
|
+
className: j,
|
|
120
120
|
style: {
|
|
121
121
|
width: "100%",
|
|
122
122
|
height: "100%",
|
|
123
|
-
...
|
|
123
|
+
...M
|
|
124
124
|
}
|
|
125
125
|
});
|
|
126
126
|
});
|
|
127
127
|
//#endregion
|
|
128
|
-
export {
|
|
128
|
+
export { f as TradeCanvas };
|
|
129
129
|
|
|
130
130
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/TradeCanvas.tsx"],"sourcesContent":["import { useRef, useEffect, useImperativeHandle, forwardRef, type CSSProperties } from 'react';\nimport {\n Chart,\n BinanceAdapter,\n DARK_THEME,\n LIGHT_THEME,\n syncIndicators,\n} from '@tradecanvas/chart';\nimport type {\n ChartType,\n TimeFrame,\n ChartOptions,\n FeaturesConfig,\n Theme,\n OHLCBar,\n SignalMarker,\n TradeZone,\n SignalMarkerStyle,\n TradeZoneStyle,\n DataAdapter,\n StreamConfig,\n ChartStyleOverrides,\n IndicatorSpec,\n} from '@tradecanvas/chart';\n\nexport interface TradeCanvasRef {\n getChart(): Chart | null;\n screenshot(filename?: string): void;\n screenshotDataURL(): string | null;\n}\n\n/**\n * The chart is made once, on mount. Later changes to `symbol`, `timeframe`,\n * `theme`, `chartType`, `indicators`, `data`, `stream`, `features`,\n * `overrides`, the markers and zones and `onCrosshairMove` go to it\n * (`setTheme`, `setChartType`, `setData`…). `options` and `autoScale` are\n * read at mount; `adapter`, `historyLimit` and `watermarkText` when the\n * stream connects (at mount, or for a new symbol or interval). Unmounting\n * closes the chart's stream and destroys it.\n */\nexport interface TradeCanvasProps {\n /** The stream's symbol (`'BTCUSDT'`); a new one reconnects the stream, while there is no `data`. */\n symbol?: string;\n /** The stream's interval (`'5m'`); a new one reconnects it, while there is no `data`. */\n timeframe?: TimeFrame;\n theme?: 'dark' | 'light' | Theme;\n chartType?: ChartType;\n /**\n * Indicators by id (`'rsi'`), or with their inputs (`{ id: 'ema', params: { period: 50 } }`).\n * A new list is matched to what's on the chart: added, removed, and one\n * whose inputs changed put back with them (its style edits go).\n */\n indicators?: IndicatorSpec[];\n /**\n * Static bars: the chart shows these, and opens no stream of its own when\n * it has them at mount. A new array replaces the series (`setData`): the\n * view goes back to the newest bar and every indicator is computed again.\n * For a live feed, update the chart itself from the ref\n * (`getChart()?.updateLastBar(bar)`, `appendBar`), or give it a stream.\n */\n data?: OHLCBar[];\n /** The stream's feed (Binance by default), taken when the stream connects. */\n adapter?: DataAdapter;\n /** `false`: no stream until you say so (no Binance by default); the chart waits for `data`. Default `true`. */\n stream?: boolean;\n historyLimit?: number;\n /** What users can do on the chart; changes after mount are applied too (the keys given). */\n features?: FeaturesConfig;\n autoScale?: boolean;\n signalMarkers?: SignalMarker[];\n signalMarkerStyle?: SignalMarkerStyle;\n tradeZones?: TradeZone[];\n tradeZoneStyle?: TradeZoneStyle;\n /** Style overrides, by key: all of your app's (`chart.setOverrides`). */\n overrides?: ChartStyleOverrides;\n watermarkText?: string;\n /**\n * Chart options the props don't cover (`maxBarSpacing`, `rightMargin`,\n * `renderer`…), merged over the component's defaults at mount; later\n * changes are not applied. `chartType`, `theme` and `autoScale` always come\n * from their props.\n */\n options?: Partial<ChartOptions>;\n /**\n * Called with the chart when it is made, on mount: once, or twice in\n * React's StrictMode in development, which mounts twice (the first chart\n * is destroyed). The ref's `getChart()` gives it too.\n */\n onReady?: (chart: Chart) => void;\n onCrosshairMove?: (payload: unknown) => void;\n className?: string;\n style?: CSSProperties;\n}\n\nfunction resolveTheme(theme: 'dark' | 'light' | Theme | undefined): Theme {\n if (!theme || theme === 'dark') return DARK_THEME;\n if (theme === 'light') return LIGHT_THEME;\n return theme;\n}\n\nexport const TradeCanvas = forwardRef<TradeCanvasRef, TradeCanvasProps>(\n function TradeCanvas(props, ref) {\n const {\n symbol = 'BTCUSDT',\n timeframe = '5m' as TimeFrame,\n theme = 'dark',\n chartType = 'candlestick' as ChartType,\n indicators = [],\n data,\n adapter,\n stream = true,\n historyLimit = 500,\n features,\n autoScale = true,\n signalMarkers,\n signalMarkerStyle,\n tradeZones,\n tradeZoneStyle,\n overrides,\n watermarkText,\n options,\n onReady,\n onCrosshairMove,\n className,\n style,\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const chartRef = useRef<Chart | null>(null);\n const indicatorIdsRef = useRef<Map<string, string>>(new Map());\n /** The stream open now (`symbol|timeframe`), or null. */\n const streamKeyRef = useRef<string | null>(null);\n const featuresRef = useRef(JSON.stringify(features ?? null));\n\n useImperativeHandle(ref, () => ({\n getChart: () => chartRef.current,\n screenshot: (filename?: string) => chartRef.current?.screenshot(filename),\n screenshotDataURL: () => chartRef.current?.screenshotDataURL() ?? null,\n }));\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const opts: ChartOptions = {\n rightMargin: 5,\n crosshair: { mode: 'magnet' },\n ...options,\n chartType,\n theme: resolveTheme(theme),\n autoScale,\n features: {\n drawings: true,\n drawingMagnet: true,\n drawingUndoRedo: true,\n indicators: true,\n trading: true,\n volume: true,\n legend: true,\n crosshair: true,\n keyboard: true,\n screenshot: true,\n alerts: true,\n barCountdown: true,\n logScale: true,\n watermark: true,\n ...options?.features,\n ...features,\n },\n };\n\n if (watermarkText) {\n opts.watermark = { text: watermarkText, fontSize: 48, color: 'rgba(255,255,255,0.03)' };\n }\n\n const chart = new Chart(container, opts);\n chartRef.current = chart;\n indicatorIdsRef.current = new Map();\n\n if (data) {\n chart.setData(data);\n } else if (stream) {\n const dataAdapter = adapter ?? new BinanceAdapter();\n chart.connect({ adapter: dataAdapter, symbol, timeframe, historyLimit });\n streamKeyRef.current = `${symbol}|${timeframe}`;\n }\n\n onReady?.(chart);\n\n return () => {\n chart.disconnectStream();\n chart.destroy();\n chartRef.current = null;\n indicatorIdsRef.current.clear();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // A stream of its own while it has no data; `stream={false}` closes it. Data given later leaves it be.\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart) return;\n if (!stream) {\n if (streamKeyRef.current !== null) chart.disconnectStream();\n streamKeyRef.current = null;\n return;\n }\n if (data) return;\n const key = `${symbol}|${timeframe}`;\n if (streamKeyRef.current === key) return;\n streamKeyRef.current = key;\n\n chart.disconnectStream();\n const dataAdapter = adapter ?? new BinanceAdapter();\n chart.connect({ adapter: dataAdapter, symbol, timeframe, historyLimit });\n if (watermarkText !== undefined) {\n chart.setWatermark(watermarkText);\n }\n }, [symbol, timeframe, adapter, historyLimit, data, stream, watermarkText]);\n\n useEffect(() => {\n const next = JSON.stringify(features ?? null);\n if (next === featuresRef.current) return;\n featuresRef.current = next;\n if (features) chartRef.current?.setFeatures(features);\n }, [features]);\n\n useEffect(() => {\n chartRef.current?.setTheme(resolveTheme(theme));\n }, [theme]);\n\n useEffect(() => {\n chartRef.current?.setChartType(chartType);\n }, [chartType]);\n\n useEffect(() => {\n if (data) chartRef.current?.setData(data);\n }, [data]);\n\n useEffect(() => {\n const chart = chartRef.current;\n if (chart) syncIndicators(chart, indicators, indicatorIdsRef.current);\n }, [indicators]);\n\n useEffect(() => {\n if (signalMarkers) chartRef.current?.setSignalMarkers(signalMarkers);\n }, [signalMarkers]);\n\n useEffect(() => {\n if (signalMarkerStyle) chartRef.current?.setSignalMarkerStyle(signalMarkerStyle);\n }, [signalMarkerStyle]);\n\n useEffect(() => {\n if (tradeZones) chartRef.current?.setTradeZones(tradeZones);\n }, [tradeZones]);\n\n useEffect(() => {\n if (tradeZoneStyle) chartRef.current?.setTradeZoneStyle(tradeZoneStyle);\n }, [tradeZoneStyle]);\n\n // All of the host's overrides: keys left out of the prop are taken away.\n // Without the prop, overrides set through the chart itself are left alone.\n const overridesGiven = useRef(false);\n useEffect(() => {\n if (overrides === undefined && !overridesGiven.current) return;\n overridesGiven.current = overrides !== undefined;\n chartRef.current?.setOverrides(overrides ?? {});\n }, [overrides]);\n\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart || !onCrosshairMove) return;\n const handler = onCrosshairMove as (e: unknown) => void;\n chart.on('crosshairMove', handler);\n // A new handler (an inline one is new each render) replaces this one.\n return () => chart.off('crosshairMove', handler);\n }, [onCrosshairMove]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ width: '100%', height: '100%', ...style }}\n />\n );\n },\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/TradeCanvas.tsx"],"sourcesContent":["import { useRef, useEffect, useImperativeHandle, forwardRef, type CSSProperties } from 'react';\nimport {\n Chart,\n BinanceAdapter,\n DARK_THEME,\n LIGHT_THEME,\n syncIndicators,\n feedBars,\n} from '@tradecanvas/chart';\nimport type {\n ChartType,\n TimeFrame,\n ChartOptions,\n FeaturesConfig,\n Theme,\n OHLCBar,\n SignalMarker,\n TradeZone,\n SignalMarkerStyle,\n TradeZoneStyle,\n DataAdapter,\n StreamConfig,\n ChartStyleOverrides,\n IndicatorSpec,\n FedBars,\n} from '@tradecanvas/chart';\n\nexport interface TradeCanvasRef {\n getChart(): Chart | null;\n screenshot(filename?: string): void;\n screenshotDataURL(): string | null;\n}\n\n/**\n * The chart is made once, on mount. Later changes to `symbol`, `timeframe`,\n * `theme`, `chartType`, `indicators`, `data`, `stream`, `features`,\n * `overrides`, the markers and zones and `onCrosshairMove` go to it\n * (`setTheme`, `setChartType`, `setData`…). `options` and `autoScale` are\n * read at mount; `adapter`, `historyLimit` and `watermarkText` when the\n * stream connects (at mount, or for a new symbol or interval). Unmounting\n * closes the chart's stream and destroys it.\n */\nexport interface TradeCanvasProps {\n /** The stream's symbol (`'BTCUSDT'`); a new one reconnects the stream, while there is no `data`. */\n symbol?: string;\n /** The stream's interval (`'5m'`); a new one reconnects it, while there is no `data`. */\n timeframe?: TimeFrame;\n theme?: 'dark' | 'light' | Theme;\n chartType?: ChartType;\n /**\n * Indicators by id (`'rsi'`), or with their inputs (`{ id: 'ema', params: { period: 50 } }`).\n * A new list is matched to what's on the chart: added, removed, and one\n * whose inputs changed put back with them (its style edits go).\n */\n indicators?: IndicatorSpec[];\n /**\n * The bars: the chart shows these, and opens no stream of its own when it\n * has them at mount. A new array that goes on from the last one, its last\n * bar changed or bars added after it (`[...bars.slice(0, -1), forming]`,\n * `[...bars, next]`), goes in as a live feed's ticks do (`updateLastBar`,\n * `appendBar`, `appendBars`): the view isn't reset, only the end is\n * computed again, and alerts follow the price. Any other new array is a new\n * series (`setData`): the view goes back to the newest bar and every\n * indicator is computed again. So is one after a stream, or code through\n * the ref, gave the chart bars of its own. An older bar changed needs a new\n * bar object in its place.\n */\n data?: OHLCBar[];\n /** The stream's feed (Binance by default), taken when the stream connects. */\n adapter?: DataAdapter;\n /** `false`: no stream until you say so (no Binance by default); the chart waits for `data`. Default `true`. */\n stream?: boolean;\n historyLimit?: number;\n /** What users can do on the chart; changes after mount are applied too (the keys given). */\n features?: FeaturesConfig;\n autoScale?: boolean;\n signalMarkers?: SignalMarker[];\n signalMarkerStyle?: SignalMarkerStyle;\n tradeZones?: TradeZone[];\n tradeZoneStyle?: TradeZoneStyle;\n /** Style overrides, by key: all of your app's (`chart.setOverrides`). */\n overrides?: ChartStyleOverrides;\n watermarkText?: string;\n /**\n * Chart options the props don't cover (`maxBarSpacing`, `rightMargin`,\n * `renderer`…), merged over the component's defaults at mount; later\n * changes are not applied. `chartType`, `theme` and `autoScale` always come\n * from their props.\n */\n options?: Partial<ChartOptions>;\n /**\n * Called with the chart when it is made, on mount: once, or twice in\n * React's StrictMode in development, which mounts twice (the first chart\n * is destroyed). The ref's `getChart()` gives it too.\n */\n onReady?: (chart: Chart) => void;\n onCrosshairMove?: (payload: unknown) => void;\n className?: string;\n style?: CSSProperties;\n}\n\nfunction resolveTheme(theme: 'dark' | 'light' | Theme | undefined): Theme {\n if (!theme || theme === 'dark') return DARK_THEME;\n if (theme === 'light') return LIGHT_THEME;\n return theme;\n}\n\nexport const TradeCanvas = forwardRef<TradeCanvasRef, TradeCanvasProps>(\n function TradeCanvas(props, ref) {\n const {\n symbol = 'BTCUSDT',\n timeframe = '5m' as TimeFrame,\n theme = 'dark',\n chartType = 'candlestick' as ChartType,\n indicators = [],\n data,\n adapter,\n stream = true,\n historyLimit = 500,\n features,\n autoScale = true,\n signalMarkers,\n signalMarkerStyle,\n tradeZones,\n tradeZoneStyle,\n overrides,\n watermarkText,\n options,\n onReady,\n onCrosshairMove,\n className,\n style,\n } = props;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const chartRef = useRef<Chart | null>(null);\n const indicatorIdsRef = useRef<Map<string, string>>(new Map());\n /** The bars last given to the chart (`feedBars`). */\n const fedRef = useRef<FedBars | null>(null);\n /** The stream open now (`symbol|timeframe`), or null. */\n const streamKeyRef = useRef<string | null>(null);\n const featuresRef = useRef(JSON.stringify(features ?? null));\n\n useImperativeHandle(ref, () => ({\n getChart: () => chartRef.current,\n screenshot: (filename?: string) => chartRef.current?.screenshot(filename),\n screenshotDataURL: () => chartRef.current?.screenshotDataURL() ?? null,\n }));\n\n useEffect(() => {\n const container = containerRef.current;\n if (!container) return;\n\n const opts: ChartOptions = {\n rightMargin: 5,\n crosshair: { mode: 'magnet' },\n ...options,\n chartType,\n theme: resolveTheme(theme),\n autoScale,\n features: {\n drawings: true,\n drawingMagnet: true,\n drawingUndoRedo: true,\n indicators: true,\n trading: true,\n volume: true,\n legend: true,\n crosshair: true,\n keyboard: true,\n screenshot: true,\n alerts: true,\n barCountdown: true,\n logScale: true,\n watermark: true,\n ...options?.features,\n ...features,\n },\n };\n\n if (watermarkText) {\n opts.watermark = { text: watermarkText, fontSize: 48, color: 'rgba(255,255,255,0.03)' };\n }\n\n const chart = new Chart(container, opts);\n chartRef.current = chart;\n indicatorIdsRef.current = new Map();\n\n fedRef.current = data ? feedBars(chart, data, null) : null;\n if (!data && stream) {\n const dataAdapter = adapter ?? new BinanceAdapter();\n chart.connect({ adapter: dataAdapter, symbol, timeframe, historyLimit });\n streamKeyRef.current = `${symbol}|${timeframe}`;\n }\n\n onReady?.(chart);\n\n return () => {\n chart.disconnectStream();\n chart.destroy();\n chartRef.current = null;\n indicatorIdsRef.current.clear();\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // A stream of its own while it has no data; `stream={false}` closes it. Data given later leaves it be.\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart) return;\n if (!stream) {\n if (streamKeyRef.current !== null) chart.disconnectStream();\n streamKeyRef.current = null;\n return;\n }\n if (data) return;\n const key = `${symbol}|${timeframe}`;\n if (streamKeyRef.current === key) return;\n streamKeyRef.current = key;\n\n chart.disconnectStream();\n const dataAdapter = adapter ?? new BinanceAdapter();\n chart.connect({ adapter: dataAdapter, symbol, timeframe, historyLimit });\n if (watermarkText !== undefined) {\n chart.setWatermark(watermarkText);\n }\n }, [symbol, timeframe, adapter, historyLimit, data, stream, watermarkText]);\n\n useEffect(() => {\n const next = JSON.stringify(features ?? null);\n if (next === featuresRef.current) return;\n featuresRef.current = next;\n if (features) chartRef.current?.setFeatures(features);\n }, [features]);\n\n useEffect(() => {\n chartRef.current?.setTheme(resolveTheme(theme));\n }, [theme]);\n\n useEffect(() => {\n chartRef.current?.setChartType(chartType);\n }, [chartType]);\n\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart) return;\n // Without data, the next bars are a new series: a stream may have come in between.\n fedRef.current = data ? feedBars(chart, data, fedRef.current) : null;\n }, [data]);\n\n useEffect(() => {\n const chart = chartRef.current;\n if (chart) syncIndicators(chart, indicators, indicatorIdsRef.current);\n }, [indicators]);\n\n useEffect(() => {\n if (signalMarkers) chartRef.current?.setSignalMarkers(signalMarkers);\n }, [signalMarkers]);\n\n useEffect(() => {\n if (signalMarkerStyle) chartRef.current?.setSignalMarkerStyle(signalMarkerStyle);\n }, [signalMarkerStyle]);\n\n useEffect(() => {\n if (tradeZones) chartRef.current?.setTradeZones(tradeZones);\n }, [tradeZones]);\n\n useEffect(() => {\n if (tradeZoneStyle) chartRef.current?.setTradeZoneStyle(tradeZoneStyle);\n }, [tradeZoneStyle]);\n\n // All of the host's overrides: keys left out of the prop are taken away.\n // Without the prop, overrides set through the chart itself are left alone.\n const overridesGiven = useRef(false);\n useEffect(() => {\n if (overrides === undefined && !overridesGiven.current) return;\n overridesGiven.current = overrides !== undefined;\n chartRef.current?.setOverrides(overrides ?? {});\n }, [overrides]);\n\n useEffect(() => {\n const chart = chartRef.current;\n if (!chart || !onCrosshairMove) return;\n const handler = onCrosshairMove as (e: unknown) => void;\n chart.on('crosshairMove', handler);\n // A new handler (an inline one is new each render) replaces this one.\n return () => chart.off('crosshairMove', handler);\n }, [onCrosshairMove]);\n\n return (\n <div\n ref={containerRef}\n className={className}\n style={{ width: '100%', height: '100%', ...style }}\n />\n );\n },\n);\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tradecanvas/react",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.30",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "React component for @tradecanvas/chart — reactive props, ref forwarding, zero boilerplate.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"README.md"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@tradecanvas/chart": "1.
|
|
40
|
+
"@tradecanvas/chart": "1.48.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"react": ">=18.0.0",
|