react-klinecharts-ui 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,31 +1,12 @@
1
- import { registerExtensions, TA_default, ensureAlertLineRegistered } from './chunk-JD4NBJ5F.js';
2
- export { TA_default as TA, abcd_default as abcd, alertLine_default as alertLine, anyWaves_default as anyWaves, arrow_default as arrow, bollTv_default as bollTv, brush_default as brush, cci_default as cci, circle_default as circle, depthOverlay_default as depthOverlay, eightWaves_default as eightWaves, elliottWave_default as elliottWave, fibRetracement_default as fibRetracement, fibonacciCircle_default as fibonacciCircle, fibonacciExtension_default as fibonacciExtension, fibonacciSegment_default as fibonacciSegment, fibonacciSpeedResistanceFan_default as fibonacciSpeedResistanceFan, fibonacciSpiral_default as fibonacciSpiral, fiveWaves_default as fiveWaves, gannBox_default as gannBox, gannFan_default as gannFan, hma_default as hma, ichimoku_default as ichimoku, indicators, longPosition_default as longPosition, maRibbon_default as maRibbon, macdTv_default as macdTv, measure_default as measure, orderLine_default as orderLine, overlays, parallelChannel_default as parallelChannel, parallelogram_default as parallelogram, pivotPoints_default as pivotPoints, ray_default as ray, rect_default as rect, registerExtensions, rsiTv_default as rsiTv, shortPosition_default as shortPosition, stochastic_default as stochastic, superTrend_default as superTrend, threeWaves_default as threeWaves, triangle_default as triangle, vwap_default as vwap, xabcd_default as xabcd } from './chunk-JD4NBJ5F.js';
3
- import { createContext, useContext, useReducer, useRef, useEffect, useCallback, useMemo, useState, useId } from 'react';
1
+ import { registerExtensions, TA_default, ensureAlertLineRegistered } from './chunk-RIT2LJQT.js';
2
+ export { TA_default as TA, abcd_default as abcd, alertLine_default as alertLine, anyWaves_default as anyWaves, arrow_default as arrow, bollTv_default as bollTv, brush_default as brush, cci_default as cci, circle_default as circle, depthOverlay_default as depthOverlay, eightWaves_default as eightWaves, elliottWave_default as elliottWave, fibRetracement_default as fibRetracement, fibonacciCircle_default as fibonacciCircle, fibonacciExtension_default as fibonacciExtension, fibonacciSegment_default as fibonacciSegment, fibonacciSpeedResistanceFan_default as fibonacciSpeedResistanceFan, fibonacciSpiral_default as fibonacciSpiral, fiveWaves_default as fiveWaves, gannBox_default as gannBox, gannFan_default as gannFan, hma_default as hma, ichimoku_default as ichimoku, indicators, longPosition_default as longPosition, maRibbon_default as maRibbon, macdTv_default as macdTv, measure_default as measure, orderLine_default as orderLine, overlays, parallelChannel_default as parallelChannel, parallelogram_default as parallelogram, pivotPoints_default as pivotPoints, ray_default as ray, rect_default as rect, registerExtensions, rsiTv_default as rsiTv, shortPosition_default as shortPosition, stochastic_default as stochastic, superTrend_default as superTrend, threeWaves_default as threeWaves, triangle_default as triangle, vwap_default as vwap, xabcd_default as xabcd } from './chunk-RIT2LJQT.js';
3
+ import { KlinechartsUIStateContext, KlinechartsUIDispatchContext, useKlinechartsUI, useKlinechartsUIDispatch } from './chunk-O7E57I66.js';
4
+ export { KlinechartsUIDispatchContext, KlinechartsUIStateContext, createDataLoader, useKlinechartsUI } from './chunk-O7E57I66.js';
5
+ import './chunk-PZ5AY32C.js';
6
+ import { createContext, useMemo, useReducer, useRef, useEffect, useCallback, useState, useId, useContext } from 'react';
4
7
  import { registerOverlay, registerHotkey, getHotkey, getSupportedHotkeys, registerIndicator } from 'klinecharts';
5
8
  import { jsx } from 'react/jsx-runtime';
6
9
 
7
- var KlinechartsUIStateContext = createContext(null);
8
- var KlinechartsUIDispatchContext = createContext(null);
9
- function useKlinechartsUIDispatch() {
10
- const ctx = useContext(KlinechartsUIDispatchContext);
11
- if (!ctx) {
12
- throw new Error(
13
- "useKlinechartsUI must be used within a <KlinechartsUIProvider>"
14
- );
15
- }
16
- return ctx;
17
- }
18
- function useKlinechartsUI() {
19
- const state = useContext(KlinechartsUIStateContext);
20
- const dispatch = useContext(KlinechartsUIDispatchContext);
21
- if (!state || !dispatch) {
22
- throw new Error(
23
- "useKlinechartsUI must be used within a <KlinechartsUIProvider>"
24
- );
25
- }
26
- return { state, ...dispatch };
27
- }
28
-
29
10
  // src/data/periods.ts
30
11
  var DEFAULT_PERIODS = [
31
12
  { span: 1, type: "minute", label: "1m" },
@@ -39,6 +20,50 @@ var DEFAULT_PERIODS = [
39
20
  { span: 1, type: "month", label: "M" },
40
21
  { span: 1, type: "year", label: "Y" }
41
22
  ];
23
+
24
+ // src/storage/types.ts
25
+ var DEFAULT_STORAGE_NAMESPACES = [
26
+ "alerts",
27
+ "settings",
28
+ "indicators"
29
+ ];
30
+ var DEFAULT_STORAGE_KEY_PREFIX = "rkui:";
31
+
32
+ // src/storage/createDefaultStorage.ts
33
+ var NOOP_ADAPTER = {
34
+ getItem: () => null,
35
+ setItem: () => {
36
+ },
37
+ removeItem: () => {
38
+ }
39
+ };
40
+ function createDefaultStorage() {
41
+ if (typeof localStorage === "undefined") return NOOP_ADAPTER;
42
+ try {
43
+ const probe = localStorage.getItem("__rkui_probe__");
44
+ return probe === null ? localStorage : localStorage;
45
+ } catch {
46
+ return NOOP_ADAPTER;
47
+ }
48
+ }
49
+ function resolveStorage(options) {
50
+ const adapter = options?.adapter ?? createDefaultStorage();
51
+ const keyPrefix = options?.keyPrefix ?? DEFAULT_STORAGE_KEY_PREFIX;
52
+ const namespaces = new Set(
53
+ options?.namespaces ?? DEFAULT_STORAGE_NAMESPACES
54
+ );
55
+ return {
56
+ adapter,
57
+ namespaces,
58
+ keyPrefix,
59
+ key(namespace) {
60
+ return `${keyPrefix}${namespace}`;
61
+ },
62
+ persists(namespace) {
63
+ return namespaces.has(namespace);
64
+ }
65
+ };
66
+ }
42
67
  function reducer(state, action) {
43
68
  switch (action.type) {
44
69
  case "SET_CHART":
@@ -102,13 +127,16 @@ function KlinechartsUIProvider({
102
127
  defaultPeriod,
103
128
  defaultTheme = "light",
104
129
  defaultTimezone = "Asia/Shanghai",
105
- defaultMainIndicators = ["MA"],
106
- defaultSubIndicators = ["VOL"],
130
+ // NOTE: no destructuring default here — the init function must distinguish
131
+ // "consumer passed default*" (wins) from "use stored value" (hydrate).
132
+ defaultMainIndicators,
133
+ defaultSubIndicators,
107
134
  defaultLocale = "en-US",
108
135
  periods,
109
136
  styles,
110
137
  registerExtensions: shouldRegister = true,
111
138
  overlays: extraOverlays,
139
+ storage: storageOptions,
112
140
  children,
113
141
  onStateChange,
114
142
  onSymbolChange,
@@ -119,6 +147,11 @@ function KlinechartsUIProvider({
119
147
  onSubIndicatorsChange,
120
148
  onSettingsChange
121
149
  }) {
150
+ const resolvedStorage = useMemo(
151
+ () => storageOptions ? resolveStorage(storageOptions) : null,
152
+ // eslint-disable-next-line react-hooks/exhaustive-deps
153
+ []
154
+ );
122
155
  const [state, dispatch] = useReducer(
123
156
  reducer,
124
157
  {
@@ -131,41 +164,61 @@ function KlinechartsUIProvider({
131
164
  defaultSubIndicators,
132
165
  defaultLocale,
133
166
  periods,
134
- styles
135
- },
136
- (opts) => ({
137
- chart: null,
138
- datafeed: opts.datafeed,
139
- symbol: opts.defaultSymbol ?? null,
140
- period: opts.defaultPeriod ?? (opts.periods ?? DEFAULT_PERIODS)[0],
141
- theme: opts.defaultTheme ?? "light",
142
- timezone: opts.defaultTimezone ?? "Asia/Shanghai",
143
- isLoading: false,
144
- locale: opts.defaultLocale ?? "en-US",
145
- periods: opts.periods ?? DEFAULT_PERIODS,
146
- mainIndicators: opts.defaultMainIndicators ?? ["MA"],
147
- subIndicators: (opts.defaultSubIndicators ?? ["VOL"]).reduce(
148
- (acc, name) => ({ ...acc, [name]: "" }),
149
- {}
150
- ),
151
- indicatorAxes: {},
152
- indicatorVisibility: {},
153
- alerts: [],
154
- measure: { isActive: false, fromPoint: null, result: null },
155
- replay: {
156
- isReplaying: false,
157
- isPaused: false,
158
- speed: 1,
159
- barIndex: 0,
160
- totalBars: 0
161
- },
162
- styles: opts.styles,
163
- screenshotUrl: null
164
- })
167
+ styles,
168
+ storage: resolvedStorage
169
+ },
170
+ (opts) => {
171
+ const s = opts.storage;
172
+ const read = (ns, fallback) => {
173
+ if (!s || !s.persists(ns)) return fallback;
174
+ try {
175
+ const raw = s.adapter.getItem(s.key(ns));
176
+ return raw ? JSON.parse(raw) : fallback;
177
+ } catch {
178
+ return fallback;
179
+ }
180
+ };
181
+ const storedIndicators = read("indicators", {});
182
+ return {
183
+ chart: null,
184
+ datafeed: opts.datafeed,
185
+ symbol: opts.defaultSymbol ?? null,
186
+ period: opts.defaultPeriod ?? (opts.periods ?? DEFAULT_PERIODS)[0],
187
+ theme: opts.defaultTheme ?? "light",
188
+ timezone: opts.defaultTimezone ?? "Asia/Shanghai",
189
+ isLoading: false,
190
+ locale: opts.defaultLocale ?? "en-US",
191
+ periods: opts.periods ?? DEFAULT_PERIODS,
192
+ // Persistence semantics: STORED values win over `default*` props —
193
+ // the user's saved configuration should survive reload even when the
194
+ // app passes defaults. Only when storage is empty/unconfigured do the
195
+ // `default*` props apply, falling back to the built-ins last.
196
+ // (Settings live in useKlinechartsUISettings useState and hydrate
197
+ // separately via the dispatch-context `storage`.)
198
+ mainIndicators: storedIndicators.main ?? opts.defaultMainIndicators ?? ["MA"],
199
+ subIndicators: storedIndicators.sub ?? (opts.defaultSubIndicators ? opts.defaultSubIndicators.reduce(
200
+ (acc, name) => ({ ...acc, [name]: "" }),
201
+ {}
202
+ ) : { VOL: "" }),
203
+ indicatorAxes: storedIndicators.axes ?? {},
204
+ indicatorVisibility: storedIndicators.visibility ?? {},
205
+ alerts: read("alerts", []),
206
+ measure: { isActive: false, fromPoint: null, result: null },
207
+ replay: {
208
+ isReplaying: false,
209
+ isPaused: false,
210
+ speed: 1,
211
+ barIndex: 0,
212
+ totalBars: 0
213
+ },
214
+ styles: opts.styles,
215
+ screenshotUrl: null
216
+ };
217
+ }
165
218
  );
166
219
  const fullscreenContainerRef = useRef(null);
167
220
  const undoRedoListenerRef = useRef(null);
168
- const alertTriggeredListenerRef = useRef(null);
221
+ const alertTriggeredListenersRef = useRef(/* @__PURE__ */ new Set());
169
222
  const replayIntervalRef = useRef(null);
170
223
  const replaySavedDataRef = useRef([]);
171
224
  const replayIndexRef = useRef(0);
@@ -243,23 +296,53 @@ function KlinechartsUIProvider({
243
296
  const chart = state.chart;
244
297
  if (!chart || !hasAlerts) return;
245
298
  alertPrevCloseRef.current = null;
299
+ const prevValueByAlert = /* @__PURE__ */ new Map();
246
300
  const interval = setInterval(() => {
247
301
  const dataList = chart.getDataList();
248
302
  if (!dataList || dataList.length === 0) return;
249
- const currentClose = dataList[dataList.length - 1].close;
250
- const prevClose = alertPrevCloseRef.current;
251
- alertPrevCloseRef.current = currentClose;
252
- if (prevClose === null) return;
303
+ const lastIdx = dataList.length - 1;
304
+ const indicatorCache = /* @__PURE__ */ new Map();
305
+ const readIndicatorValue = (indicatorId, figureKey) => {
306
+ const cacheKey = `${indicatorId}:${figureKey}`;
307
+ if (indicatorCache.has(cacheKey)) return indicatorCache.get(cacheKey);
308
+ let value = null;
309
+ try {
310
+ const indicators2 = chart.getIndicators({ id: indicatorId });
311
+ const ind = indicators2?.[0];
312
+ const result = ind?.result;
313
+ const last = Array.isArray(result) ? result[lastIdx] : null;
314
+ if (last && typeof last === "object") {
315
+ const v = last[figureKey];
316
+ value = typeof v === "number" ? v : null;
317
+ }
318
+ } catch {
319
+ value = null;
320
+ }
321
+ indicatorCache.set(cacheKey, value);
322
+ return value;
323
+ };
253
324
  const alerts = stateRef.current.alerts;
254
325
  const triggeredIds = [];
255
326
  for (const alert of alerts) {
256
327
  if (alert.triggered) continue;
257
- const crossedUp = prevClose < alert.price && currentClose >= alert.price;
258
- const crossedDown = prevClose > alert.price && currentClose <= alert.price;
328
+ const target = alert.target ?? { type: "price" };
329
+ let currentValue;
330
+ if (target.type === "indicator") {
331
+ currentValue = readIndicatorValue(target.indicatorId, target.figureKey);
332
+ } else {
333
+ currentValue = dataList[lastIdx].close;
334
+ }
335
+ if (currentValue === null || !Number.isFinite(currentValue)) continue;
336
+ const prevValue = prevValueByAlert.get(alert.id) ?? null;
337
+ prevValueByAlert.set(alert.id, currentValue);
338
+ if (prevValue === null) continue;
339
+ const crossedUp = prevValue < alert.price && currentValue >= alert.price;
340
+ const crossedDown = prevValue > alert.price && currentValue <= alert.price;
259
341
  const shouldTrigger = alert.condition === "crossing_up" ? crossedUp : alert.condition === "crossing_down" ? crossedDown : crossedUp || crossedDown;
260
342
  if (shouldTrigger) {
261
343
  triggeredIds.push(alert.id);
262
- alertTriggeredListenerRef.current?.({ ...alert, triggered: true });
344
+ const fired = { ...alert, triggered: true };
345
+ alertTriggeredListenersRef.current.forEach((cb) => cb(fired));
263
346
  }
264
347
  }
265
348
  if (triggeredIds.length > 0) {
@@ -274,6 +357,36 @@ function KlinechartsUIProvider({
274
357
  useEffect(() => {
275
358
  alertPrevCloseRef.current = null;
276
359
  }, [state.symbol, state.period]);
360
+ const writeNs = useCallback(
361
+ (ns, value) => {
362
+ if (!resolvedStorage || !resolvedStorage.persists(ns)) return;
363
+ try {
364
+ resolvedStorage.adapter.setItem(
365
+ resolvedStorage.key(ns),
366
+ JSON.stringify(value)
367
+ );
368
+ } catch {
369
+ }
370
+ },
371
+ [resolvedStorage]
372
+ );
373
+ useEffect(() => {
374
+ writeNs("alerts", state.alerts);
375
+ }, [state.alerts, writeNs]);
376
+ useEffect(() => {
377
+ writeNs("indicators", {
378
+ main: state.mainIndicators,
379
+ sub: state.subIndicators,
380
+ axes: state.indicatorAxes,
381
+ visibility: state.indicatorVisibility
382
+ });
383
+ }, [
384
+ state.mainIndicators,
385
+ state.subIndicators,
386
+ state.indicatorAxes,
387
+ state.indicatorVisibility,
388
+ writeNs
389
+ ]);
277
390
  useEffect(() => {
278
391
  return () => {
279
392
  if (replayIntervalRef.current !== null) {
@@ -289,13 +402,14 @@ function KlinechartsUIProvider({
289
402
  onSettingsChange: onSettingsChangeRef.current,
290
403
  fullscreenContainerRef,
291
404
  undoRedoListenerRef,
292
- alertTriggeredListenerRef,
405
+ alertTriggeredListenersRef,
293
406
  replayIntervalRef,
294
407
  replaySavedDataRef,
295
- replayIndexRef
408
+ replayIndexRef,
409
+ storage: resolvedStorage
296
410
  }),
297
411
  // eslint-disable-next-line react-hooks/exhaustive-deps
298
- [enhancedDispatch]
412
+ [enhancedDispatch, resolvedStorage]
299
413
  );
300
414
  return /* @__PURE__ */ jsx(KlinechartsUIStateContext.Provider, { value: state, children: /* @__PURE__ */ jsx(KlinechartsUIDispatchContext.Provider, { value: dispatchValue, children }) });
301
415
  }
@@ -1437,7 +1551,17 @@ var defaultSettings = {
1437
1551
  };
1438
1552
  function useKlinechartsUISettings() {
1439
1553
  const { state, onSettingsChange } = useKlinechartsUI();
1440
- const [settings, setSettings] = useState(defaultSettings);
1554
+ const { storage } = useKlinechartsUIDispatch();
1555
+ const [settings, setSettings] = useState(() => {
1556
+ if (storage && storage.persists("settings")) {
1557
+ try {
1558
+ const raw = storage.adapter.getItem(storage.key("settings"));
1559
+ if (raw) return { ...defaultSettings, ...JSON.parse(raw) };
1560
+ } catch {
1561
+ }
1562
+ }
1563
+ return defaultSettings;
1564
+ });
1441
1565
  const isInitialMount = useRef(true);
1442
1566
  useEffect(() => {
1443
1567
  if (isInitialMount.current) {
@@ -1446,6 +1570,14 @@ function useKlinechartsUISettings() {
1446
1570
  }
1447
1571
  onSettingsChange?.({ ...settings });
1448
1572
  }, [settings, onSettingsChange]);
1573
+ useEffect(() => {
1574
+ if (!storage || !storage.persists("settings")) return;
1575
+ if (isInitialMount.current) return;
1576
+ try {
1577
+ storage.adapter.setItem(storage.key("settings"), JSON.stringify(settings));
1578
+ } catch {
1579
+ }
1580
+ }, [settings, storage]);
1449
1581
  const candleTypes = useMemo(
1450
1582
  () => CANDLE_TYPES.map((ct) => ({
1451
1583
  key: ct.key,
@@ -2788,10 +2920,10 @@ function useCrosshair() {
2788
2920
  var alertCounter = 0;
2789
2921
  function useAlerts() {
2790
2922
  const { state, dispatch } = useKlinechartsUI();
2791
- const { alertTriggeredListenerRef } = useKlinechartsUIDispatch();
2923
+ const { alertTriggeredListenersRef } = useKlinechartsUIDispatch();
2792
2924
  const alerts = state.alerts;
2793
2925
  const addAlert = useCallback(
2794
- (price, condition, message, extendData) => {
2926
+ (price, condition, message, extendData, target) => {
2795
2927
  const id = `alert_${++alertCounter}`;
2796
2928
  const precision = state.chart?.getSymbol()?.pricePrecision ?? 2;
2797
2929
  const resolvedExtendData = {
@@ -2804,7 +2936,10 @@ function useAlerts() {
2804
2936
  condition,
2805
2937
  message,
2806
2938
  triggered: false,
2807
- extendData: resolvedExtendData
2939
+ extendData: resolvedExtendData,
2940
+ // Persist the target only when it's an indicator alert; omit for the
2941
+ // default price target so existing serialized alerts stay compatible.
2942
+ ...target && target.type === "indicator" ? { target } : {}
2808
2943
  };
2809
2944
  dispatch({ type: "ADD_ALERT", alert });
2810
2945
  if (state.chart) {
@@ -2835,9 +2970,13 @@ function useAlerts() {
2835
2970
  }, [state.chart, dispatch]);
2836
2971
  const onAlertTriggered = useCallback(
2837
2972
  (callback) => {
2838
- alertTriggeredListenerRef.current = callback;
2973
+ const set = alertTriggeredListenersRef.current;
2974
+ set.add(callback);
2975
+ return () => {
2976
+ set.delete(callback);
2977
+ };
2839
2978
  },
2840
- [alertTriggeredListenerRef]
2979
+ [alertTriggeredListenersRef]
2841
2980
  );
2842
2981
  return {
2843
2982
  alerts,
@@ -3441,71 +3580,161 @@ function useAnnotations() {
3441
3580
  clearAnnotations
3442
3581
  };
3443
3582
  }
3583
+ var WorkspaceContext = createContext(null);
3584
+ function useWorkspace() {
3585
+ const ctx = useContext(WorkspaceContext);
3586
+ if (!ctx) {
3587
+ throw new Error("useWorkspace must be used within a <WorkspaceProvider>");
3588
+ }
3589
+ return ctx;
3590
+ }
3444
3591
 
3445
- // src/utils/createDataLoader.ts
3446
- function createDataLoader(datafeed, dispatch) {
3447
- let oldestTimestamp = null;
3448
- let currentGen = 0;
3449
- return {
3450
- getBars: async (params) => {
3592
+ // src/workspace/types.ts
3593
+ var DEFAULT_SYNC_CONFIG = {
3594
+ crosshair: true,
3595
+ scroll: true,
3596
+ zoom: true,
3597
+ symbol: true,
3598
+ period: true
3599
+ };
3600
+ function reducer2(state, action) {
3601
+ switch (action.type) {
3602
+ case "SET_CELLS":
3603
+ return { ...state, cells: action.cells };
3604
+ case "ADD_CELL":
3605
+ return { ...state, cells: [...state.cells, action.cell] };
3606
+ case "REMOVE_CELL": {
3607
+ const cells = state.cells.filter((c) => c.id !== action.id);
3608
+ return {
3609
+ cells,
3610
+ activeCellId: state.activeCellId === action.id ? cells[0]?.id ?? null : state.activeCellId
3611
+ };
3612
+ }
3613
+ case "SET_CELL_SYMBOL":
3614
+ return {
3615
+ ...state,
3616
+ cells: state.cells.map(
3617
+ (c) => c.id === action.id ? { ...c, symbol: action.symbol } : c
3618
+ )
3619
+ };
3620
+ case "SET_CELL_PERIOD":
3621
+ return {
3622
+ ...state,
3623
+ cells: state.cells.map(
3624
+ (c) => c.id === action.id ? { ...c, period: action.period } : c
3625
+ )
3626
+ };
3627
+ case "SET_ACTIVE_CELL":
3628
+ return { ...state, activeCellId: action.id };
3629
+ default:
3630
+ return state;
3631
+ }
3632
+ }
3633
+ function WorkspaceProvider({
3634
+ defaultCells,
3635
+ sync,
3636
+ children
3637
+ }) {
3638
+ const [state, dispatch] = useReducer(reducer2, defaultCells, (cells) => ({
3639
+ cells,
3640
+ activeCellId: cells[0]?.id ?? null
3641
+ }));
3642
+ const chartsRef = useRef(/* @__PURE__ */ new Map());
3643
+ const broadcastingRef = useRef(false);
3644
+ const resolvedSync = useMemo(
3645
+ () => ({ ...DEFAULT_SYNC_CONFIG, ...sync }),
3646
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3647
+ []
3648
+ );
3649
+ const value = useMemo(
3650
+ () => ({
3651
+ state,
3652
+ dispatch,
3653
+ chartsRef,
3654
+ broadcastingRef,
3655
+ sync: resolvedSync
3656
+ }),
3657
+ [state, resolvedSync]
3658
+ );
3659
+ return /* @__PURE__ */ jsx(WorkspaceContext.Provider, { value, children });
3660
+ }
3661
+ function useChartSync({ cellId }) {
3662
+ const { chartsRef, broadcastingRef, sync, dispatch: workspaceDispatch } = useWorkspace();
3663
+ const { state } = useKlinechartsUI();
3664
+ const chart = state.chart;
3665
+ useEffect(() => {
3666
+ if (!chart) return;
3667
+ chartsRef.current.set(cellId, chart);
3668
+ return () => {
3669
+ chartsRef.current.delete(cellId);
3670
+ };
3671
+ }, [chart, cellId, chartsRef]);
3672
+ useEffect(() => {
3673
+ if (!chart) return;
3674
+ const broadcast = (fn) => {
3675
+ if (broadcastingRef.current) return;
3676
+ broadcastingRef.current = true;
3451
3677
  try {
3452
- dispatch({ type: "SET_LOADING", isLoading: true });
3453
- if (params.type === "init") {
3454
- oldestTimestamp = null;
3455
- const gen = ++currentGen;
3456
- const data = await datafeed.getHistoryKLineData(
3457
- params.symbol,
3458
- { ...params.period, label: "" },
3459
- 0,
3460
- Date.now()
3461
- );
3462
- if (gen !== currentGen) return;
3463
- if (data.length > 0) {
3464
- oldestTimestamp = data[0].timestamp;
3465
- }
3466
- params.callback(data, {
3467
- forward: data.length > 0,
3468
- backward: false
3469
- });
3470
- } else if (params.type === "forward" && oldestTimestamp !== null) {
3471
- const gen = currentGen;
3472
- const data = await datafeed.getHistoryKLineData(
3473
- params.symbol,
3474
- { ...params.period, label: "" },
3475
- 0,
3476
- oldestTimestamp - 1
3477
- );
3478
- if (gen !== currentGen) return;
3479
- if (data.length > 0) {
3480
- oldestTimestamp = data[0].timestamp;
3678
+ chartsRef.current.forEach((target, id) => {
3679
+ if (id !== cellId) {
3680
+ try {
3681
+ fn(target);
3682
+ } catch {
3683
+ }
3481
3684
  }
3482
- params.callback(data, {
3483
- forward: data.length > 0,
3484
- backward: false
3485
- });
3486
- } else if (params.type === "backward") {
3487
- params.callback([], { forward: false, backward: false });
3488
- }
3489
- } catch (error) {
3490
- console.error("Failed to load chart data:", error);
3491
- params.callback([], { forward: false, backward: false });
3685
+ });
3492
3686
  } finally {
3493
- dispatch({ type: "SET_LOADING", isLoading: false });
3687
+ broadcastingRef.current = false;
3494
3688
  }
3495
- },
3496
- subscribeBar: (params) => {
3497
- datafeed.subscribe(
3498
- params.symbol,
3499
- { ...params.period, label: "" },
3500
- (klineData) => params.callback(klineData)
3689
+ };
3690
+ const onCrosshairChange = (data) => {
3691
+ if (!sync.crosshair) return;
3692
+ const crosshair = data;
3693
+ if (!crosshair) return;
3694
+ broadcast((target) => target.executeAction("onCrosshairChange", crosshair));
3695
+ };
3696
+ const onScroll = () => {
3697
+ if (!sync.scroll) return;
3698
+ let range = null;
3699
+ try {
3700
+ range = chart.getVisibleRange();
3701
+ } catch {
3702
+ return;
3703
+ }
3704
+ if (!range || range.realTo == null) return;
3705
+ broadcast(
3706
+ (target) => target.scrollToTimestamp(range.realTo, 0)
3501
3707
  );
3502
- },
3503
- unsubscribeBar: (params) => {
3504
- datafeed.unsubscribe(params.symbol, { ...params.period, label: "" });
3708
+ };
3709
+ const onZoom = () => {
3710
+ if (!sync.zoom) return;
3711
+ let bar = 0;
3712
+ try {
3713
+ bar = chart.getBarSpace().bar;
3714
+ } catch {
3715
+ return;
3716
+ }
3717
+ broadcast((target) => target.setBarSpace(bar));
3718
+ };
3719
+ chart.subscribeAction("onCrosshairChange", onCrosshairChange);
3720
+ chart.subscribeAction("onScroll", onScroll);
3721
+ chart.subscribeAction("onZoom", onZoom);
3722
+ return () => {
3723
+ chart.unsubscribeAction("onCrosshairChange", onCrosshairChange);
3724
+ chart.unsubscribeAction("onScroll", onScroll);
3725
+ chart.unsubscribeAction("onZoom", onZoom);
3726
+ };
3727
+ }, [chart, cellId, chartsRef, broadcastingRef, sync.crosshair, sync.scroll, sync.zoom]);
3728
+ useEffect(() => {
3729
+ if (state.symbol) {
3730
+ workspaceDispatch({ type: "SET_CELL_SYMBOL", id: cellId, symbol: state.symbol });
3505
3731
  }
3506
- };
3732
+ }, [state.symbol]);
3733
+ useEffect(() => {
3734
+ workspaceDispatch({ type: "SET_CELL_PERIOD", id: cellId, period: state.period });
3735
+ }, [state.period]);
3507
3736
  }
3508
3737
 
3509
- export { CANDLE_TYPES, COMPARE_RULES, DEFAULT_PERIODS, DRAWING_CATEGORIES, INDICATOR_PARAMS, KlinechartsUIDispatchContext, KlinechartsUIProvider, KlinechartsUIStateContext, MAIN_INDICATORS, PRICE_AXIS_TYPES, SUB_INDICATORS, TIMEZONES, TOOLTIP_SHOW_RULES, YAXIS_POSITIONS, createDataLoader, useAlerts, useAnnotations, useChartAxes, useCompare, useCrosshair, useDataExport, useDrawingTools, useFullscreen, useHotkeys, useIndicators, useKlinechartsUI, useKlinechartsUILoading, useKlinechartsUISettings, useKlinechartsUITheme, useLayoutManager, useMeasure, useOrderLines, usePeriods, useReplay, useScreenshot, useScriptEditor, useSymbolSearch, useTimezone, useUndoRedo, useWatchlist };
3738
+ export { CANDLE_TYPES, COMPARE_RULES, DEFAULT_PERIODS, DEFAULT_STORAGE_KEY_PREFIX, DEFAULT_STORAGE_NAMESPACES, DEFAULT_SYNC_CONFIG, DRAWING_CATEGORIES, INDICATOR_PARAMS, KlinechartsUIProvider, MAIN_INDICATORS, PRICE_AXIS_TYPES, SUB_INDICATORS, TIMEZONES, TOOLTIP_SHOW_RULES, WorkspaceProvider, YAXIS_POSITIONS, createDefaultStorage, resolveStorage, useAlerts, useAnnotations, useChartAxes, useChartSync, useCompare, useCrosshair, useDataExport, useDrawingTools, useFullscreen, useHotkeys, useIndicators, useKlinechartsUILoading, useKlinechartsUISettings, useKlinechartsUITheme, useLayoutManager, useMeasure, useOrderLines, usePeriods, useReplay, useScreenshot, useScriptEditor, useSymbolSearch, useTimezone, useUndoRedo, useWatchlist, useWorkspace };
3510
3739
  //# sourceMappingURL=index.js.map
3511
3740
  //# sourceMappingURL=index.js.map