react-klinecharts-ui 1.0.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 CHANGED
@@ -69,6 +69,17 @@ pnpm add react-klinecharts-ui klinecharts
69
69
  yarn add react-klinecharts-ui klinecharts
70
70
  ```
71
71
 
72
+ > **Rendering a chart?** `react-klinecharts-ui` is headless — it does not render
73
+ > the canvas itself. The fastest path is the optional `ChartCanvas` wrapper,
74
+ > which needs `react-klinecharts`:
75
+ >
76
+ > ```bash
77
+ > npm install react-klinecharts-ui klinecharts react-klinecharts
78
+ > ```
79
+ >
80
+ > You can also initialise the chart yourself with `klinecharts.init()` and skip
81
+ > `react-klinecharts` entirely — see [Renderer-agnostic](#renderer-agnostic).
82
+
72
83
  ---
73
84
 
74
85
  ## Concept
@@ -82,6 +93,170 @@ The library follows a **headless** pattern — all UI is written by the consumer
82
93
 
83
94
  All hooks must be called inside `<KlinechartsUIProvider>`.
84
95
 
96
+ ### Renderer-agnostic
97
+
98
+ `react-klinecharts-ui` is **headless** — it owns state (symbol, period, indicators, alerts, replay, …) and drives a klinecharts `Chart` instance, but it does **not** render the canvas. The only bridge between a renderer and the provider is a single dispatch:
99
+
100
+ ```ts
101
+ dispatch({ type: "SET_CHART", chart });
102
+ ```
103
+
104
+ Once the chart instance is registered, every hook (`useIndicators`, `useAlerts`, `useReplay`, …) reads and mutates it via `state.chart.*`. You can initialise that instance **three ways**:
105
+
106
+ **1. `ChartCanvas` (fastest, optional peer `react-klinecharts`)** — the thin wrapper shipped at `react-klinecharts-ui/chart` wires the `<KLineChart>` renderer, builds the data loader, forwards symbol/period/theme from provider state, and dispatches `SET_CHART` for you:
107
+
108
+ ```tsx
109
+ import { KlinechartsUIProvider } from "react-klinecharts-ui";
110
+ import { ChartCanvas } from "react-klinecharts-ui/chart";
111
+
112
+ <KlinechartsUIProvider datafeed={datafeed} defaultSymbol={symbol} defaultTheme="dark">
113
+ <ChartCanvas className="h-[500px]" />
114
+ </KlinechartsUIProvider>;
115
+ ```
116
+
117
+ **2. `<KLineChart>` from `react-klinecharts` directly** — full control over props, at the cost of writing the `onReady` bridge yourself:
118
+
119
+ ```tsx
120
+ import { useMemo } from "react";
121
+ import { KLineChart } from "react-klinecharts";
122
+ import { useKlinechartsUI, createDataLoader } from "react-klinecharts-ui";
123
+
124
+ function ChartView() {
125
+ const { state, dispatch, datafeed } = useKlinechartsUI();
126
+ const dataLoader = useMemo(() => createDataLoader(datafeed, dispatch), [datafeed, dispatch]);
127
+ return (
128
+ <KLineChart
129
+ dataLoader={dataLoader}
130
+ symbol={state.symbol ?? undefined}
131
+ period={state.period}
132
+ styles={state.theme}
133
+ onReady={(chart) => dispatch({ type: "SET_CHART", chart })}
134
+ />
135
+ );
136
+ }
137
+ ```
138
+
139
+ **3. Direct `klinecharts.init()` (no `react-klinecharts` at all)** — for custom lifecycles, SSR/Next.js, or when you want zero extra dependencies:
140
+
141
+ ```tsx
142
+ import { useEffect, useRef } from "react";
143
+ import { init, dispose } from "klinecharts";
144
+ import { useKlinechartsUI } from "react-klinecharts-ui";
145
+
146
+ function ChartView() {
147
+ const { dispatch, datafeed } = useKlinechartsUI();
148
+ const ref = useRef<HTMLDivElement>(null);
149
+ useEffect(() => {
150
+ const chart = init(ref.current!);
151
+ dispatch({ type: "SET_CHART", chart }); // the bridge
152
+ chart!.applyNewData(/* load bars yourself */);
153
+ return () => dispose(ref.current!);
154
+ }, []);
155
+ return <div ref={ref} style={{ height: 500 }} />;
156
+ }
157
+ ```
158
+
159
+ Whatever route you pick, the hooks work the same — they only care about the `Chart` instance in the store.
160
+
161
+ ### Persistence
162
+
163
+ By default, user-facing state (alerts, chart settings, the active indicator
164
+ set) lives only in memory and is **lost on page reload**. Pass a `storage`
165
+ option to the provider to hydrate it on mount and write it back on every
166
+ change. The adapter mirrors the Web Storage API, so `localStorage` works out
167
+ of the box:
168
+
169
+ ```tsx
170
+ <KlinechartsUIProvider datafeed={datafeed} storage={{}}>
171
+ {/* alerts / settings / indicators now survive refresh */}
172
+ </KlinechartsUIProvider>
173
+ ```
174
+
175
+ `storage={{}}` uses the defaults: `localStorage` adapter, the `alerts` /
176
+ `settings` / `indicators` namespaces, and a `"rkui:"` key prefix. You can
177
+ override any of them — e.g. plug in a remote or IndexedDB-backed adapter:
178
+
179
+ ```tsx
180
+ import type { StorageAdapter } from "react-klinecharts-ui";
181
+
182
+ const remoteAdapter: StorageAdapter = {
183
+ getItem: (key) => mySyncCache.get(key) ?? null,
184
+ setItem: (key, value) => {
185
+ mySyncCache.set(key, value);
186
+ flushToServer(key, value); // async, fire-and-forget
187
+ },
188
+ removeItem: (key) => { mySyncCache.delete(key); deleteFromServer(key); },
189
+ };
190
+
191
+ <KlinechartsUIProvider datafeed={datafeed} storage={{ adapter: remoteAdapter }}>
192
+ ```
193
+
194
+ **Scope.** The adapter covers the reducer store today: `alerts`, `settings`
195
+ (`useKlinechartsUISettings`), and `indicators` (the active lists, pane ids,
196
+ axis bindings, and visibility). Per-hook `useState` values — script code
197
+ (`useScriptEditor`), compared symbols (`useCompare`), watchlist, annotations —
198
+ are not yet covered by the adapter and remain in memory. `useLayoutManager`
199
+ (named snapshot presets) is orthogonal: it serializes the whole chart
200
+ (indicators + drawings + meta) on demand to its own keys, independent of the
201
+ live `storage` adapter.
202
+
203
+ The adapter contract is **synchronous** (matching the Web Storage API). For
204
+ async backends, keep a synchronous in-memory cache and flush in the
205
+ background — the provider reads/writes through `getItem`/`setItem` only.
206
+
207
+ ---
208
+
209
+ ### Workspace & multi-chart
210
+
211
+ `KlinechartsUIProvider` owns exactly one chart. To render a **grid** of charts
212
+ that share crosshair / scroll / zoom / symbol / period, wrap several providers
213
+ in a `WorkspaceProvider` and drop a `useChartSync` bridge inside each:
214
+
215
+ ```tsx
216
+ import {
217
+ KlinechartsUIProvider,
218
+ WorkspaceProvider,
219
+ useChartSync,
220
+ useWorkspace,
221
+ } from "react-klinecharts-ui";
222
+
223
+ const cells = [
224
+ { id: "a", symbol: { ticker: "BTCUSDT" }, period: { span: 1, type: "minute", label: "1m" } },
225
+ { id: "b", symbol: { ticker: "ETHUSDT" }, period: { span: 1, type: "minute", label: "1m" } },
226
+ ];
227
+
228
+ // Rendered inside each KlinechartsUIProvider — registers its chart with the
229
+ // workspace and mirrors viewport events to siblings.
230
+ function ChartSyncBridge({ cellId }: { cellId: string }) {
231
+ useChartSync({ cellId });
232
+ return null;
233
+ }
234
+
235
+ <WorkspaceProvider defaultCells={cells}>
236
+ <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr" }}>
237
+ {cells.map((c) => (
238
+ <KlinechartsUIProvider key={c.id} datafeed={datafeed} defaultSymbol={c.symbol} defaultPeriod={c.period}>
239
+ <ChartSyncBridge cellId={c.id} />
240
+ <ChartCanvas />
241
+ </KlinechartsUIProvider>
242
+ ))}
243
+ </div>
244
+ </WorkspaceProvider>
245
+ ```
246
+
247
+ `useChartSync` mirrors crosshair / scroll / zoom between the registered charts
248
+ using only the **public** klinecharts API (`executeAction`, `scrollToTimestamp`,
249
+ `setBarSpace`) — no internal `_chartStore`. A re-entrancy guard prevents
250
+ feedback loops. Per-channel sync can be disabled via the `sync` prop
251
+ (`{ scroll: false }`).
252
+
253
+ > **Scope of this foundation.** Each cell keeps its own alerts, replay, and
254
+ > drawings (per-provider). Hoisting shared alerts/replay/drawings to the
255
+ > workspace level, plus tabbed layouts and server-persisted workspaces, is
256
+ > planned. Read the
257
+ > [multi-chart example](https://nemezzizz.github.io/react-klinecharts-ui/examples/multi-chart/)
258
+ > for a runnable 2×2 grid.
259
+
85
260
  ---
86
261
 
87
262
  ## KlinechartsUIProvider
@@ -575,6 +750,7 @@ const {
575
750
  isLocked,
576
751
  isVisible,
577
752
  autoRetrigger,
753
+ overlays,
578
754
  selectTool,
579
755
  clearActiveTool,
580
756
  setMagnetMode,
@@ -582,24 +758,31 @@ const {
582
758
  toggleVisibility,
583
759
  removeAllDrawings,
584
760
  setAutoRetrigger,
761
+ removeDrawing,
762
+ setDrawingVisible,
763
+ setDrawingLocked,
585
764
  } = useDrawingTools();
586
765
  ```
587
766
 
588
- | Field/Method | Type | Description |
589
- | --------------------- | -------------------------------- | ------------------------------------------------------- |
590
- | `categories` | `DrawingCategoryItem[]` | Tool categories with nested tools |
591
- | `activeTool` | `string \| null` | Name of the last selected tool |
592
- | `magnetMode` | `"normal" \| "weak" \| "strong"` | Snap-to-OHLC mode |
593
- | `isLocked` | `boolean` | Whether all drawings are locked |
594
- | `isVisible` | `boolean` | Whether all drawings are visible |
595
- | `autoRetrigger` | `boolean` | Re-arm the tool after finishing a shape (default `true`) |
596
- | `selectTool(name)` | | Start drawing via `chart.createOverlay` |
597
- | `clearActiveTool()` | — | Deselect tool (local state only) |
598
- | `setMagnetMode(mode)` | — | Change magnet mode for all existing and future drawings |
599
- | `toggleLock()` | — | Toggle lock on all drawings |
600
- | `toggleVisibility()` | — | Show/hide all drawings |
601
- | `removeAllDrawings()` | — | Remove all drawings in the `drawing_tools` group |
602
- | `setAutoRetrigger(enabled)` | — | Enable/disable auto re-arming |
767
+ | Field/Method | Type | Description |
768
+ | ------------------------ | -------------------------------- | ------------------------------------------------------- |
769
+ | `categories` | `DrawingCategoryItem[]` | Tool categories with nested tools |
770
+ | `activeTool` | `string \| null` | Name of the last selected tool |
771
+ | `magnetMode` | `"normal" \| "weak" \| "strong"` | Snap-to-OHLC mode |
772
+ | `isLocked` | `boolean` | Whether all drawings are locked |
773
+ | `isVisible` | `boolean` | Whether all drawings are visible |
774
+ | `autoRetrigger` | `boolean` | Re-arm the tool after finishing a shape (default `true`) |
775
+ | `overlays` | `DrawingOverlayInfo[]` | Reactive list of drawings in the `drawing_tools` group |
776
+ | `selectTool(name)` | — | Start drawing via `chart.createOverlay` |
777
+ | `clearActiveTool()` | — | Deselect tool (local state only) |
778
+ | `setMagnetMode(mode)` | — | Change magnet mode for all existing and future drawings |
779
+ | `toggleLock()` | — | Toggle lock on all drawings |
780
+ | `toggleVisibility()` | — | Show/hide all drawings |
781
+ | `removeAllDrawings()` | — | Remove all drawings in the `drawing_tools` group |
782
+ | `setAutoRetrigger(enabled)` | — | Enable/disable auto re-arming |
783
+ | `removeDrawing(id)` | — | Remove a single drawing by id |
784
+ | `setDrawingVisible(id, visible)` | — | Show/hide a single drawing |
785
+ | `setDrawingLocked(id, locked)` | — | Lock/unlock a single drawing |
603
786
 
604
787
  ```typescript
605
788
  interface DrawingToolItem {
@@ -611,8 +794,39 @@ interface DrawingCategoryItem {
611
794
  key: string; // "singleLine" | "moreLine" | "polygon" | "fibonacci" | "wave"
612
795
  tools: DrawingToolItem[];
613
796
  }
797
+
798
+ interface DrawingOverlayInfo {
799
+ id: string; // Stable id from klinecharts (chart.getOverlays()[].id)
800
+ name: string; // Overlay name, e.g. "segment", "fibonacciLine", "arrow"
801
+ paneId: string; // Pane id where the drawing lives
802
+ locked: boolean; // Current lock state
803
+ visible: boolean; // Current visibility
804
+ }
614
805
  ```
615
806
 
807
+ #### Per-drawing management
808
+
809
+ `overlays` is a **reactive** snapshot of the drawings in the `drawing_tools` group. It updates when a drawing is created (via `selectTool` + finishing a shape on the canvas), removed, or has its `locked`/`visible` properties changed — both through the per-drawing operations above and through the batch operations (`toggleLock`, `toggleVisibility`, `removeAllDrawings`).
810
+
811
+ Because klinecharts v10 has no overlay change events, the hook also runs a 1s polling fallback so changes made **outside** the hook (e.g. the user pressing <kbd>Delete</kbd> via klinecharts, or undo/redo) are still reflected.
812
+
813
+ ```typescript
814
+ // Build an Object Tree panel from `overlays`
815
+ const { overlays, removeDrawing, setDrawingVisible, setDrawingLocked } = useDrawingTools();
816
+ // overlays.map((o) => (
817
+ // <Row
818
+ // key={o.id}
819
+ // label={drawingLabel(o.name)} // "segment" -> "segment" locale key
820
+ // visible={o.visible}
821
+ // locked={o.locked}
822
+ // onToggleVis={() => setDrawingVisible(o.id, !o.visible)}
823
+ // onDel={() => removeDrawing(o.id)}
824
+ // />
825
+ // ));
826
+ ```
827
+
828
+ `drawingLabel(name)` is an exported helper that maps a klinecharts overlay name to its `localeKey` using `DRAWING_CATEGORIES` (falls back to the name itself when the tool is unknown), so consumers don't have to duplicate the category table.
829
+
616
830
  **Categories and tools:**
617
831
 
618
832
  | Category (`key`) | Tools |
@@ -2084,4 +2298,8 @@ export type {
2084
2298
  DepthOverlayExtendData,
2085
2299
  DepthOverlayRow,
2086
2300
  };
2301
+
2302
+ // Chart entry (optional — peer-depends on react-klinecharts).
2303
+ // import from "react-klinecharts-ui/chart"
2304
+ export { ChartCanvas, type ChartCanvasProps };
2087
2305
  ```
package/dist/chart.cjs ADDED
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ var chunkF24D6PWY_cjs = require('./chunk-F24D6PWY.cjs');
4
+ require('./chunk-Q7SFCCGT.cjs');
5
+ var react = require('react');
6
+ var reactKlinecharts = require('react-klinecharts');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+
9
+ function ChartCanvas({
10
+ className,
11
+ children,
12
+ options,
13
+ styles
14
+ }) {
15
+ const { state, dispatch, datafeed } = chunkF24D6PWY_cjs.useKlinechartsUI();
16
+ const dataLoader = react.useMemo(
17
+ () => chunkF24D6PWY_cjs.createDataLoader(datafeed, dispatch),
18
+ [datafeed, dispatch]
19
+ );
20
+ const mainIndicatorsRef = react.useRef(state.mainIndicators);
21
+ const subIndicatorsRef = react.useRef(state.subIndicators);
22
+ react.useEffect(() => {
23
+ mainIndicatorsRef.current = state.mainIndicators;
24
+ subIndicatorsRef.current = state.subIndicators;
25
+ }, [state.mainIndicators, state.subIndicators]);
26
+ const handleReady = react.useCallback(
27
+ (chart) => {
28
+ dispatch({ type: "SET_CHART", chart });
29
+ mainIndicatorsRef.current.forEach((name) => {
30
+ chart.createIndicator(
31
+ { name, id: `main_${name}` },
32
+ { isStack: true, pane: { id: "candle_pane" } }
33
+ );
34
+ });
35
+ const subUpdates = {};
36
+ Object.keys(subIndicatorsRef.current).forEach((name) => {
37
+ const id = `sub_${name}`;
38
+ chart.createIndicator({ name, id });
39
+ const ind = chart.getIndicators({ id })[0];
40
+ if (ind?.paneId) subUpdates[name] = ind.paneId;
41
+ });
42
+ if (Object.keys(subUpdates).length > 0) {
43
+ dispatch({
44
+ type: "SET_SUB_INDICATORS",
45
+ indicators: { ...subIndicatorsRef.current, ...subUpdates }
46
+ });
47
+ }
48
+ },
49
+ [dispatch]
50
+ );
51
+ return /* @__PURE__ */ jsxRuntime.jsx(
52
+ reactKlinecharts.KLineChart,
53
+ {
54
+ className,
55
+ options,
56
+ styles: styles ?? state.theme,
57
+ dataLoader,
58
+ symbol: state.symbol ?? void 0,
59
+ period: state.period,
60
+ locale: state.locale,
61
+ timezone: state.timezone,
62
+ onReady: handleReady,
63
+ children
64
+ }
65
+ );
66
+ }
67
+
68
+ exports.ChartCanvas = ChartCanvas;
69
+ //# sourceMappingURL=chart.cjs.map
70
+ //# sourceMappingURL=chart.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/chart/ChartCanvas.tsx"],"names":["useKlinechartsUI","useMemo","createDataLoader","useRef","useEffect","useCallback","jsx","KLineChart"],"mappings":";;;;;;;;AAsDO,SAAS,WAAA,CAAY;AAAA,EAC1B,SAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAgC;AAC9B,EAAA,MAAM,EAAE,KAAA,EAAO,QAAA,EAAU,QAAA,KAAaA,kCAAA,EAAiB;AAEvD,EAAA,MAAM,UAAA,GAAaC,aAAA;AAAA,IACjB,MAAMC,kCAAA,CAAiB,QAAA,EAAU,QAAQ,CAAA;AAAA,IACzC,CAAC,UAAU,QAAQ;AAAA,GACrB;AAKA,EAAA,MAAM,iBAAA,GAAoBC,YAAA,CAAO,KAAA,CAAM,cAAc,CAAA;AACrD,EAAA,MAAM,gBAAA,GAAmBA,YAAA,CAAO,KAAA,CAAM,aAAa,CAAA;AACnD,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,iBAAA,CAAkB,UAAU,KAAA,CAAM,cAAA;AAClC,IAAA,gBAAA,CAAiB,UAAU,KAAA,CAAM,aAAA;AAAA,EACnC,GAAG,CAAC,KAAA,CAAM,cAAA,EAAgB,KAAA,CAAM,aAAa,CAAC,CAAA;AAE9C,EAAA,MAAM,WAAA,GAAcC,iBAAA;AAAA,IAClB,CAAC,KAAA,KAAiB;AAEhB,MAAA,QAAA,CAAS,EAAE,IAAA,EAAM,WAAA,EAAa,KAAA,EAAO,CAAA;AAIrC,MAAA,iBAAA,CAAkB,OAAA,CAAQ,OAAA,CAAQ,CAAC,IAAA,KAAS;AAC1C,QAAA,KAAA,CAAM,eAAA;AAAA,UACJ,EAAE,IAAA,EAAM,EAAA,EAAI,CAAA,KAAA,EAAQ,IAAI,CAAA,CAAA,EAAG;AAAA,UAC3B,EAAE,OAAA,EAAS,IAAA,EAAM,MAAM,EAAE,EAAA,EAAI,eAAc;AAAE,SAC/C;AAAA,MACF,CAAC,CAAA;AAED,MAAA,MAAM,aAAqC,EAAC;AAC5C,MAAA,MAAA,CAAO,KAAK,gBAAA,CAAiB,OAAO,CAAA,CAAE,OAAA,CAAQ,CAAC,IAAA,KAAS;AACtD,QAAA,MAAM,EAAA,GAAK,OAAO,IAAI,CAAA,CAAA;AACtB,QAAA,KAAA,CAAM,eAAA,CAAgB,EAAE,IAAA,EAAM,EAAA,EAAI,CAAA;AAClC,QAAA,MAAM,MAAM,KAAA,CAAM,aAAA,CAAc,EAAE,EAAA,EAAI,EAAE,CAAC,CAAA;AACzC,QAAA,IAAI,GAAA,EAAK,MAAA,EAAQ,UAAA,CAAW,IAAI,IAAI,GAAA,CAAI,MAAA;AAAA,MAC1C,CAAC,CAAA;AACD,MAAA,IAAI,MAAA,CAAO,IAAA,CAAK,UAAU,CAAA,CAAE,SAAS,CAAA,EAAG;AACtC,QAAA,QAAA,CAAS;AAAA,UACP,IAAA,EAAM,oBAAA;AAAA,UACN,YAAY,EAAE,GAAG,gBAAA,CAAiB,OAAA,EAAS,GAAG,UAAA;AAAW,SAC1D,CAAA;AAAA,MACH;AAAA,IACF,CAAA;AAAA,IACA,CAAC,QAAQ;AAAA,GACX;AAEA,EAAA,uBACEC,cAAA;AAAA,IAACC,2BAAA;AAAA,IAAA;AAAA,MACC,SAAA;AAAA,MACA,OAAA;AAAA,MACA,MAAA,EAAQ,UAAU,KAAA,CAAM,KAAA;AAAA,MACxB,UAAA;AAAA,MACA,MAAA,EAAQ,MAAM,MAAA,IAAU,MAAA;AAAA,MACxB,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,UAAU,KAAA,CAAM,QAAA;AAAA,MAChB,OAAA,EAAS,WAAA;AAAA,MAER;AAAA;AAAA,GACH;AAEJ","file":"chart.cjs","sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n type ReactNode,\n} from \"react\";\nimport { KLineChart } from \"react-klinecharts\";\nimport type { Chart, Options, DeepPartial, Styles } from \"klinecharts\";\nimport {\n useKlinechartsUI,\n} from \"../provider/ChartTerminalContext\";\nimport { createDataLoader } from \"../utils/createDataLoader\";\n\nexport interface ChartCanvasProps {\n /** className applied to the chart container div. */\n className?: string;\n /**\n * Optional children rendered inside `<KLineChart>` (use the `<Widget>`\n * component from `react-klinecharts` to portal React into chart DOM layers).\n */\n children?: ReactNode;\n /**\n * Chart initialization options, applied once on mount. Forwarded to\n * `<KLineChart>` as the `options` prop.\n */\n options?: Options;\n /**\n * Styles override applied on mount and whenever it changes. Forwarded to\n * `<KLineChart>` as the `styles` prop.\n */\n styles?: string | DeepPartial<Styles>;\n}\n\n/**\n * Thin renderer wrapper for `react-klinecharts-ui`.\n *\n * `react-klinecharts-ui` is **headless / renderer-agnostic**: it only needs a\n * klinecharts `Chart` instance to be registered via\n * `dispatch({ type: \"SET_CHART\", chart })`. This component wires the\n * `<KLineChart>` renderer from `react-klinecharts` to that bridge for you —\n * building the data loader, forwarding symbol/period/theme/locale/timezone\n * from provider state, bootstrapping the default indicators, and dispatching\n * `SET_CHART` on ready.\n *\n * It is the fastest path to a working chart. For full control (custom\n * lifecycle, a different renderer, or direct `klinecharts.init()`), render the\n * chart yourself and dispatch `SET_CHART` — see the README \"Renderer-agnostic\"\n * section.\n *\n * This entry point requires `react-klinecharts` as an optional peer\n * dependency. Install it explicitly when you import from\n * `react-klinecharts-ui/chart`.\n */\nexport function ChartCanvas({\n className,\n children,\n options,\n styles,\n}: ChartCanvasProps): ReactNode {\n const { state, dispatch, datafeed } = useKlinechartsUI();\n\n const dataLoader = useMemo(\n () => createDataLoader(datafeed, dispatch),\n [datafeed, dispatch],\n );\n\n // Keep the latest default indicator lists in refs so `handleReady` stays\n // stable (only depends on `dispatch`) yet reads current values when the\n // chart finishes initializing.\n const mainIndicatorsRef = useRef(state.mainIndicators);\n const subIndicatorsRef = useRef(state.subIndicators);\n useEffect(() => {\n mainIndicatorsRef.current = state.mainIndicators;\n subIndicatorsRef.current = state.subIndicators;\n }, [state.mainIndicators, state.subIndicators]);\n\n const handleReady = useCallback(\n (chart: Chart) => {\n // The bridge: register the chart instance so every hook can drive it.\n dispatch({ type: \"SET_CHART\", chart });\n\n // Bootstrap the provider's default indicators onto the fresh chart,\n // mirroring the canonical pattern in examples/ChartView.tsx.\n mainIndicatorsRef.current.forEach((name) => {\n chart.createIndicator(\n { name, id: `main_${name}` },\n { isStack: true, pane: { id: \"candle_pane\" } },\n );\n });\n\n const subUpdates: Record<string, string> = {};\n Object.keys(subIndicatorsRef.current).forEach((name) => {\n const id = `sub_${name}`;\n chart.createIndicator({ name, id });\n const ind = chart.getIndicators({ id })[0];\n if (ind?.paneId) subUpdates[name] = ind.paneId;\n });\n if (Object.keys(subUpdates).length > 0) {\n dispatch({\n type: \"SET_SUB_INDICATORS\",\n indicators: { ...subIndicatorsRef.current, ...subUpdates },\n });\n }\n },\n [dispatch],\n );\n\n return (\n <KLineChart\n className={className}\n options={options}\n styles={styles ?? state.theme}\n dataLoader={dataLoader}\n symbol={state.symbol ?? undefined}\n period={state.period}\n locale={state.locale}\n timezone={state.timezone}\n onReady={handleReady}\n >\n {children}\n </KLineChart>\n );\n}\n"]}
@@ -0,0 +1,45 @@
1
+ import { ReactNode } from 'react';
2
+ import { Options, DeepPartial, Styles } from 'klinecharts';
3
+
4
+ interface ChartCanvasProps {
5
+ /** className applied to the chart container div. */
6
+ className?: string;
7
+ /**
8
+ * Optional children rendered inside `<KLineChart>` (use the `<Widget>`
9
+ * component from `react-klinecharts` to portal React into chart DOM layers).
10
+ */
11
+ children?: ReactNode;
12
+ /**
13
+ * Chart initialization options, applied once on mount. Forwarded to
14
+ * `<KLineChart>` as the `options` prop.
15
+ */
16
+ options?: Options;
17
+ /**
18
+ * Styles override applied on mount and whenever it changes. Forwarded to
19
+ * `<KLineChart>` as the `styles` prop.
20
+ */
21
+ styles?: string | DeepPartial<Styles>;
22
+ }
23
+ /**
24
+ * Thin renderer wrapper for `react-klinecharts-ui`.
25
+ *
26
+ * `react-klinecharts-ui` is **headless / renderer-agnostic**: it only needs a
27
+ * klinecharts `Chart` instance to be registered via
28
+ * `dispatch({ type: "SET_CHART", chart })`. This component wires the
29
+ * `<KLineChart>` renderer from `react-klinecharts` to that bridge for you —
30
+ * building the data loader, forwarding symbol/period/theme/locale/timezone
31
+ * from provider state, bootstrapping the default indicators, and dispatching
32
+ * `SET_CHART` on ready.
33
+ *
34
+ * It is the fastest path to a working chart. For full control (custom
35
+ * lifecycle, a different renderer, or direct `klinecharts.init()`), render the
36
+ * chart yourself and dispatch `SET_CHART` — see the README "Renderer-agnostic"
37
+ * section.
38
+ *
39
+ * This entry point requires `react-klinecharts` as an optional peer
40
+ * dependency. Install it explicitly when you import from
41
+ * `react-klinecharts-ui/chart`.
42
+ */
43
+ declare function ChartCanvas({ className, children, options, styles, }: ChartCanvasProps): ReactNode;
44
+
45
+ export { ChartCanvas, type ChartCanvasProps };
@@ -0,0 +1,45 @@
1
+ import { ReactNode } from 'react';
2
+ import { Options, DeepPartial, Styles } from 'klinecharts';
3
+
4
+ interface ChartCanvasProps {
5
+ /** className applied to the chart container div. */
6
+ className?: string;
7
+ /**
8
+ * Optional children rendered inside `<KLineChart>` (use the `<Widget>`
9
+ * component from `react-klinecharts` to portal React into chart DOM layers).
10
+ */
11
+ children?: ReactNode;
12
+ /**
13
+ * Chart initialization options, applied once on mount. Forwarded to
14
+ * `<KLineChart>` as the `options` prop.
15
+ */
16
+ options?: Options;
17
+ /**
18
+ * Styles override applied on mount and whenever it changes. Forwarded to
19
+ * `<KLineChart>` as the `styles` prop.
20
+ */
21
+ styles?: string | DeepPartial<Styles>;
22
+ }
23
+ /**
24
+ * Thin renderer wrapper for `react-klinecharts-ui`.
25
+ *
26
+ * `react-klinecharts-ui` is **headless / renderer-agnostic**: it only needs a
27
+ * klinecharts `Chart` instance to be registered via
28
+ * `dispatch({ type: "SET_CHART", chart })`. This component wires the
29
+ * `<KLineChart>` renderer from `react-klinecharts` to that bridge for you —
30
+ * building the data loader, forwarding symbol/period/theme/locale/timezone
31
+ * from provider state, bootstrapping the default indicators, and dispatching
32
+ * `SET_CHART` on ready.
33
+ *
34
+ * It is the fastest path to a working chart. For full control (custom
35
+ * lifecycle, a different renderer, or direct `klinecharts.init()`), render the
36
+ * chart yourself and dispatch `SET_CHART` — see the README "Renderer-agnostic"
37
+ * section.
38
+ *
39
+ * This entry point requires `react-klinecharts` as an optional peer
40
+ * dependency. Install it explicitly when you import from
41
+ * `react-klinecharts-ui/chart`.
42
+ */
43
+ declare function ChartCanvas({ className, children, options, styles, }: ChartCanvasProps): ReactNode;
44
+
45
+ export { ChartCanvas, type ChartCanvasProps };
package/dist/chart.js ADDED
@@ -0,0 +1,68 @@
1
+ import { useKlinechartsUI, createDataLoader } from './chunk-O7E57I66.js';
2
+ import './chunk-PZ5AY32C.js';
3
+ import { useMemo, useRef, useEffect, useCallback } from 'react';
4
+ import { KLineChart } from 'react-klinecharts';
5
+ import { jsx } from 'react/jsx-runtime';
6
+
7
+ function ChartCanvas({
8
+ className,
9
+ children,
10
+ options,
11
+ styles
12
+ }) {
13
+ const { state, dispatch, datafeed } = useKlinechartsUI();
14
+ const dataLoader = useMemo(
15
+ () => createDataLoader(datafeed, dispatch),
16
+ [datafeed, dispatch]
17
+ );
18
+ const mainIndicatorsRef = useRef(state.mainIndicators);
19
+ const subIndicatorsRef = useRef(state.subIndicators);
20
+ useEffect(() => {
21
+ mainIndicatorsRef.current = state.mainIndicators;
22
+ subIndicatorsRef.current = state.subIndicators;
23
+ }, [state.mainIndicators, state.subIndicators]);
24
+ const handleReady = useCallback(
25
+ (chart) => {
26
+ dispatch({ type: "SET_CHART", chart });
27
+ mainIndicatorsRef.current.forEach((name) => {
28
+ chart.createIndicator(
29
+ { name, id: `main_${name}` },
30
+ { isStack: true, pane: { id: "candle_pane" } }
31
+ );
32
+ });
33
+ const subUpdates = {};
34
+ Object.keys(subIndicatorsRef.current).forEach((name) => {
35
+ const id = `sub_${name}`;
36
+ chart.createIndicator({ name, id });
37
+ const ind = chart.getIndicators({ id })[0];
38
+ if (ind?.paneId) subUpdates[name] = ind.paneId;
39
+ });
40
+ if (Object.keys(subUpdates).length > 0) {
41
+ dispatch({
42
+ type: "SET_SUB_INDICATORS",
43
+ indicators: { ...subIndicatorsRef.current, ...subUpdates }
44
+ });
45
+ }
46
+ },
47
+ [dispatch]
48
+ );
49
+ return /* @__PURE__ */ jsx(
50
+ KLineChart,
51
+ {
52
+ className,
53
+ options,
54
+ styles: styles ?? state.theme,
55
+ dataLoader,
56
+ symbol: state.symbol ?? void 0,
57
+ period: state.period,
58
+ locale: state.locale,
59
+ timezone: state.timezone,
60
+ onReady: handleReady,
61
+ children
62
+ }
63
+ );
64
+ }
65
+
66
+ export { ChartCanvas };
67
+ //# sourceMappingURL=chart.js.map
68
+ //# sourceMappingURL=chart.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/chart/ChartCanvas.tsx"],"names":[],"mappings":";;;;;;AAsDO,SAAS,WAAA,CAAY;AAAA,EAC1B,SAAA;AAAA,EACA,QAAA;AAAA,EACA,OAAA;AAAA,EACA;AACF,CAAA,EAAgC;AAC9B,EAAA,MAAM,EAAE,KAAA,EAAO,QAAA,EAAU,QAAA,KAAa,gBAAA,EAAiB;AAEvD,EAAA,MAAM,UAAA,GAAa,OAAA;AAAA,IACjB,MAAM,gBAAA,CAAiB,QAAA,EAAU,QAAQ,CAAA;AAAA,IACzC,CAAC,UAAU,QAAQ;AAAA,GACrB;AAKA,EAAA,MAAM,iBAAA,GAAoB,MAAA,CAAO,KAAA,CAAM,cAAc,CAAA;AACrD,EAAA,MAAM,gBAAA,GAAmB,MAAA,CAAO,KAAA,CAAM,aAAa,CAAA;AACnD,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,iBAAA,CAAkB,UAAU,KAAA,CAAM,cAAA;AAClC,IAAA,gBAAA,CAAiB,UAAU,KAAA,CAAM,aAAA;AAAA,EACnC,GAAG,CAAC,KAAA,CAAM,cAAA,EAAgB,KAAA,CAAM,aAAa,CAAC,CAAA;AAE9C,EAAA,MAAM,WAAA,GAAc,WAAA;AAAA,IAClB,CAAC,KAAA,KAAiB;AAEhB,MAAA,QAAA,CAAS,EAAE,IAAA,EAAM,WAAA,EAAa,KAAA,EAAO,CAAA;AAIrC,MAAA,iBAAA,CAAkB,OAAA,CAAQ,OAAA,CAAQ,CAAC,IAAA,KAAS;AAC1C,QAAA,KAAA,CAAM,eAAA;AAAA,UACJ,EAAE,IAAA,EAAM,EAAA,EAAI,CAAA,KAAA,EAAQ,IAAI,CAAA,CAAA,EAAG;AAAA,UAC3B,EAAE,OAAA,EAAS,IAAA,EAAM,MAAM,EAAE,EAAA,EAAI,eAAc;AAAE,SAC/C;AAAA,MACF,CAAC,CAAA;AAED,MAAA,MAAM,aAAqC,EAAC;AAC5C,MAAA,MAAA,CAAO,KAAK,gBAAA,CAAiB,OAAO,CAAA,CAAE,OAAA,CAAQ,CAAC,IAAA,KAAS;AACtD,QAAA,MAAM,EAAA,GAAK,OAAO,IAAI,CAAA,CAAA;AACtB,QAAA,KAAA,CAAM,eAAA,CAAgB,EAAE,IAAA,EAAM,EAAA,EAAI,CAAA;AAClC,QAAA,MAAM,MAAM,KAAA,CAAM,aAAA,CAAc,EAAE,EAAA,EAAI,EAAE,CAAC,CAAA;AACzC,QAAA,IAAI,GAAA,EAAK,MAAA,EAAQ,UAAA,CAAW,IAAI,IAAI,GAAA,CAAI,MAAA;AAAA,MAC1C,CAAC,CAAA;AACD,MAAA,IAAI,MAAA,CAAO,IAAA,CAAK,UAAU,CAAA,CAAE,SAAS,CAAA,EAAG;AACtC,QAAA,QAAA,CAAS;AAAA,UACP,IAAA,EAAM,oBAAA;AAAA,UACN,YAAY,EAAE,GAAG,gBAAA,CAAiB,OAAA,EAAS,GAAG,UAAA;AAAW,SAC1D,CAAA;AAAA,MACH;AAAA,IACF,CAAA;AAAA,IACA,CAAC,QAAQ;AAAA,GACX;AAEA,EAAA,uBACE,GAAA;AAAA,IAAC,UAAA;AAAA,IAAA;AAAA,MACC,SAAA;AAAA,MACA,OAAA;AAAA,MACA,MAAA,EAAQ,UAAU,KAAA,CAAM,KAAA;AAAA,MACxB,UAAA;AAAA,MACA,MAAA,EAAQ,MAAM,MAAA,IAAU,MAAA;AAAA,MACxB,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,QAAQ,KAAA,CAAM,MAAA;AAAA,MACd,UAAU,KAAA,CAAM,QAAA;AAAA,MAChB,OAAA,EAAS,WAAA;AAAA,MAER;AAAA;AAAA,GACH;AAEJ","file":"chart.js","sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n type ReactNode,\n} from \"react\";\nimport { KLineChart } from \"react-klinecharts\";\nimport type { Chart, Options, DeepPartial, Styles } from \"klinecharts\";\nimport {\n useKlinechartsUI,\n} from \"../provider/ChartTerminalContext\";\nimport { createDataLoader } from \"../utils/createDataLoader\";\n\nexport interface ChartCanvasProps {\n /** className applied to the chart container div. */\n className?: string;\n /**\n * Optional children rendered inside `<KLineChart>` (use the `<Widget>`\n * component from `react-klinecharts` to portal React into chart DOM layers).\n */\n children?: ReactNode;\n /**\n * Chart initialization options, applied once on mount. Forwarded to\n * `<KLineChart>` as the `options` prop.\n */\n options?: Options;\n /**\n * Styles override applied on mount and whenever it changes. Forwarded to\n * `<KLineChart>` as the `styles` prop.\n */\n styles?: string | DeepPartial<Styles>;\n}\n\n/**\n * Thin renderer wrapper for `react-klinecharts-ui`.\n *\n * `react-klinecharts-ui` is **headless / renderer-agnostic**: it only needs a\n * klinecharts `Chart` instance to be registered via\n * `dispatch({ type: \"SET_CHART\", chart })`. This component wires the\n * `<KLineChart>` renderer from `react-klinecharts` to that bridge for you —\n * building the data loader, forwarding symbol/period/theme/locale/timezone\n * from provider state, bootstrapping the default indicators, and dispatching\n * `SET_CHART` on ready.\n *\n * It is the fastest path to a working chart. For full control (custom\n * lifecycle, a different renderer, or direct `klinecharts.init()`), render the\n * chart yourself and dispatch `SET_CHART` — see the README \"Renderer-agnostic\"\n * section.\n *\n * This entry point requires `react-klinecharts` as an optional peer\n * dependency. Install it explicitly when you import from\n * `react-klinecharts-ui/chart`.\n */\nexport function ChartCanvas({\n className,\n children,\n options,\n styles,\n}: ChartCanvasProps): ReactNode {\n const { state, dispatch, datafeed } = useKlinechartsUI();\n\n const dataLoader = useMemo(\n () => createDataLoader(datafeed, dispatch),\n [datafeed, dispatch],\n );\n\n // Keep the latest default indicator lists in refs so `handleReady` stays\n // stable (only depends on `dispatch`) yet reads current values when the\n // chart finishes initializing.\n const mainIndicatorsRef = useRef(state.mainIndicators);\n const subIndicatorsRef = useRef(state.subIndicators);\n useEffect(() => {\n mainIndicatorsRef.current = state.mainIndicators;\n subIndicatorsRef.current = state.subIndicators;\n }, [state.mainIndicators, state.subIndicators]);\n\n const handleReady = useCallback(\n (chart: Chart) => {\n // The bridge: register the chart instance so every hook can drive it.\n dispatch({ type: \"SET_CHART\", chart });\n\n // Bootstrap the provider's default indicators onto the fresh chart,\n // mirroring the canonical pattern in examples/ChartView.tsx.\n mainIndicatorsRef.current.forEach((name) => {\n chart.createIndicator(\n { name, id: `main_${name}` },\n { isStack: true, pane: { id: \"candle_pane\" } },\n );\n });\n\n const subUpdates: Record<string, string> = {};\n Object.keys(subIndicatorsRef.current).forEach((name) => {\n const id = `sub_${name}`;\n chart.createIndicator({ name, id });\n const ind = chart.getIndicators({ id })[0];\n if (ind?.paneId) subUpdates[name] = ind.paneId;\n });\n if (Object.keys(subUpdates).length > 0) {\n dispatch({\n type: \"SET_SUB_INDICATORS\",\n indicators: { ...subIndicatorsRef.current, ...subUpdates },\n });\n }\n },\n [dispatch],\n );\n\n return (\n <KLineChart\n className={className}\n options={options}\n styles={styles ?? state.theme}\n dataLoader={dataLoader}\n symbol={state.symbol ?? undefined}\n period={state.period}\n locale={state.locale}\n timezone={state.timezone}\n onReady={handleReady}\n >\n {children}\n </KLineChart>\n );\n}\n"]}
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+
5
+ // src/provider/ChartTerminalContext.ts
6
+ var KlinechartsUIStateContext = react.createContext(null);
7
+ var KlinechartsUIDispatchContext = react.createContext(null);
8
+ function useKlinechartsUIDispatch() {
9
+ const ctx = react.useContext(KlinechartsUIDispatchContext);
10
+ if (!ctx) {
11
+ throw new Error(
12
+ "useKlinechartsUI must be used within a <KlinechartsUIProvider>"
13
+ );
14
+ }
15
+ return ctx;
16
+ }
17
+ function useKlinechartsUI() {
18
+ const state = react.useContext(KlinechartsUIStateContext);
19
+ const dispatch = react.useContext(KlinechartsUIDispatchContext);
20
+ if (!state || !dispatch) {
21
+ throw new Error(
22
+ "useKlinechartsUI must be used within a <KlinechartsUIProvider>"
23
+ );
24
+ }
25
+ return { state, ...dispatch };
26
+ }
27
+
28
+ // src/utils/createDataLoader.ts
29
+ function createDataLoader(datafeed, dispatch) {
30
+ let oldestTimestamp = null;
31
+ let currentGen = 0;
32
+ return {
33
+ getBars: async (params) => {
34
+ try {
35
+ dispatch({ type: "SET_LOADING", isLoading: true });
36
+ if (params.type === "init") {
37
+ oldestTimestamp = null;
38
+ const gen = ++currentGen;
39
+ const data = await datafeed.getHistoryKLineData(
40
+ params.symbol,
41
+ { ...params.period, label: "" },
42
+ 0,
43
+ Date.now()
44
+ );
45
+ if (gen !== currentGen) return;
46
+ if (data.length > 0) {
47
+ oldestTimestamp = data[0].timestamp;
48
+ }
49
+ params.callback(data, {
50
+ forward: data.length > 0,
51
+ backward: false
52
+ });
53
+ } else if (params.type === "forward" && oldestTimestamp !== null) {
54
+ const gen = currentGen;
55
+ const data = await datafeed.getHistoryKLineData(
56
+ params.symbol,
57
+ { ...params.period, label: "" },
58
+ 0,
59
+ oldestTimestamp - 1
60
+ );
61
+ if (gen !== currentGen) return;
62
+ if (data.length > 0) {
63
+ oldestTimestamp = data[0].timestamp;
64
+ }
65
+ params.callback(data, {
66
+ forward: data.length > 0,
67
+ backward: false
68
+ });
69
+ } else if (params.type === "backward") {
70
+ params.callback([], { forward: false, backward: false });
71
+ }
72
+ } catch (error) {
73
+ console.error("Failed to load chart data:", error);
74
+ params.callback([], { forward: false, backward: false });
75
+ } finally {
76
+ dispatch({ type: "SET_LOADING", isLoading: false });
77
+ }
78
+ },
79
+ subscribeBar: (params) => {
80
+ datafeed.subscribe(
81
+ params.symbol,
82
+ { ...params.period, label: "" },
83
+ (klineData) => params.callback(klineData)
84
+ );
85
+ },
86
+ unsubscribeBar: (params) => {
87
+ datafeed.unsubscribe(params.symbol, { ...params.period, label: "" });
88
+ }
89
+ };
90
+ }
91
+
92
+ exports.KlinechartsUIDispatchContext = KlinechartsUIDispatchContext;
93
+ exports.KlinechartsUIStateContext = KlinechartsUIStateContext;
94
+ exports.createDataLoader = createDataLoader;
95
+ exports.useKlinechartsUI = useKlinechartsUI;
96
+ exports.useKlinechartsUIDispatch = useKlinechartsUIDispatch;
97
+ //# sourceMappingURL=chunk-F24D6PWY.cjs.map
98
+ //# sourceMappingURL=chunk-F24D6PWY.cjs.map