react-klinecharts-ui 0.4.0 → 0.6.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,7 +1,7 @@
1
- import { registerExtensions, TA_default } from './chunk-U325AHHX.js';
2
- export { TA_default as TA, abcd_default as abcd, 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-U325AHHX.js';
1
+ import { registerExtensions, TA_default, ensureAlertLineRegistered } from './chunk-MVD3ILJT.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-MVD3ILJT.js';
3
3
  import { createContext, useContext, useReducer, useRef, useCallback, useEffect, useMemo, useState } from 'react';
4
- import { registerOverlay, registerIndicator } from 'react-klinecharts';
4
+ import { registerOverlay, registerHotkey, getHotkey, getSupportedHotkeys, registerIndicator } from 'react-klinecharts';
5
5
  import { jsx } from 'react/jsx-runtime';
6
6
 
7
7
  var KlinechartsUIStateContext = createContext(null);
@@ -59,6 +59,14 @@ function reducer(state, action) {
59
59
  return { ...state, subIndicators: action.indicators };
60
60
  case "SET_INDICATOR_AXES":
61
61
  return { ...state, indicatorAxes: action.axes };
62
+ case "SET_INDICATOR_VISIBILITY":
63
+ return { ...state, indicatorVisibility: action.visibility };
64
+ case "SET_ALERTS":
65
+ return { ...state, alerts: action.alerts };
66
+ case "SET_MEASURE":
67
+ return { ...state, measure: { ...state.measure, ...action.measure } };
68
+ case "SET_REPLAY":
69
+ return { ...state, replay: { ...state.replay, ...action.replay } };
62
70
  case "SET_STYLES":
63
71
  return { ...state, styles: action.styles };
64
72
  case "SET_LOCALE":
@@ -122,12 +130,27 @@ function KlinechartsUIProvider({
122
130
  {}
123
131
  ),
124
132
  indicatorAxes: {},
133
+ indicatorVisibility: {},
134
+ alerts: [],
135
+ measure: { isActive: false, fromPoint: null, result: null },
136
+ replay: {
137
+ isReplaying: false,
138
+ isPaused: false,
139
+ speed: 1,
140
+ barIndex: 0,
141
+ totalBars: 0
142
+ },
125
143
  styles: opts.styles,
126
144
  screenshotUrl: null
127
145
  })
128
146
  );
129
147
  const fullscreenContainerRef = useRef(null);
130
148
  const undoRedoListenerRef = useRef(null);
149
+ const alertTriggeredListenerRef = useRef(null);
150
+ const replayIntervalRef = useRef(null);
151
+ const replaySavedDataRef = useRef([]);
152
+ const replayIndexRef = useRef(0);
153
+ const alertPrevCloseRef = useRef(null);
131
154
  const stateRef = useRef(state);
132
155
  stateRef.current = state;
133
156
  const callbacksRef = useRef({
@@ -188,13 +211,58 @@ function KlinechartsUIProvider({
188
211
  state.chart.setStyles(state.styles);
189
212
  }
190
213
  }, [state.chart, state.styles]);
214
+ const hasAlerts = state.alerts.length > 0;
215
+ useEffect(() => {
216
+ const chart = state.chart;
217
+ if (!chart || !hasAlerts) return;
218
+ alertPrevCloseRef.current = null;
219
+ const interval = setInterval(() => {
220
+ const dataList = chart.getDataList();
221
+ if (!dataList || dataList.length === 0) return;
222
+ const currentClose = dataList[dataList.length - 1].close;
223
+ const prevClose = alertPrevCloseRef.current;
224
+ alertPrevCloseRef.current = currentClose;
225
+ if (prevClose === null) return;
226
+ const alerts = stateRef.current.alerts;
227
+ let changed = false;
228
+ const next = alerts.map((alert) => {
229
+ if (alert.triggered) return alert;
230
+ const crossedUp = prevClose < alert.price && currentClose >= alert.price;
231
+ const crossedDown = prevClose > alert.price && currentClose <= alert.price;
232
+ const shouldTrigger = alert.condition === "crossing_up" ? crossedUp : alert.condition === "crossing_down" ? crossedDown : crossedUp || crossedDown;
233
+ if (shouldTrigger) {
234
+ changed = true;
235
+ const triggered = { ...alert, triggered: true };
236
+ alertTriggeredListenerRef.current?.(triggered);
237
+ return triggered;
238
+ }
239
+ return alert;
240
+ });
241
+ if (changed) {
242
+ enhancedDispatch({ type: "SET_ALERTS", alerts: next });
243
+ }
244
+ }, 1e3);
245
+ return () => clearInterval(interval);
246
+ }, [state.chart, hasAlerts, enhancedDispatch]);
247
+ useEffect(() => {
248
+ return () => {
249
+ if (replayIntervalRef.current !== null) {
250
+ clearInterval(replayIntervalRef.current);
251
+ replayIntervalRef.current = null;
252
+ }
253
+ };
254
+ }, []);
191
255
  const dispatchValue = useMemo(
192
256
  () => ({
193
257
  dispatch: enhancedDispatch,
194
258
  datafeed,
195
259
  onSettingsChange,
196
260
  fullscreenContainerRef,
197
- undoRedoListenerRef
261
+ undoRedoListenerRef,
262
+ alertTriggeredListenerRef,
263
+ replayIntervalRef,
264
+ replaySavedDataRef,
265
+ replayIndexRef
198
266
  }),
199
267
  [enhancedDispatch, datafeed, onSettingsChange]
200
268
  );
@@ -656,18 +724,26 @@ function useIndicators() {
656
724
  const activeNames = state.mainIndicators;
657
725
  const inactive = MAIN_INDICATORS.filter((n) => !activeNames.includes(n));
658
726
  return [
659
- ...activeNames.map((name) => ({ name, isActive: true })),
660
- ...inactive.map((name) => ({ name, isActive: false }))
727
+ ...activeNames.map((name) => ({
728
+ name,
729
+ isActive: true,
730
+ visible: state.indicatorVisibility[`main_${name}`] ?? true
731
+ })),
732
+ ...inactive.map((name) => ({ name, isActive: false, visible: true }))
661
733
  ];
662
- }, [state.mainIndicators]);
734
+ }, [state.mainIndicators, state.indicatorVisibility]);
663
735
  const subIndicators = useMemo(() => {
664
736
  const activeNames = Object.keys(state.subIndicators);
665
737
  const inactive = SUB_INDICATORS.filter((n) => !activeNames.includes(n));
666
738
  return [
667
- ...activeNames.map((name) => ({ name, isActive: true })),
668
- ...inactive.map((name) => ({ name, isActive: false }))
739
+ ...activeNames.map((name) => ({
740
+ name,
741
+ isActive: true,
742
+ visible: state.indicatorVisibility[`sub_${name}`] ?? true
743
+ })),
744
+ ...inactive.map((name) => ({ name, isActive: false, visible: true }))
669
745
  ];
670
- }, [state.subIndicators]);
746
+ }, [state.subIndicators, state.indicatorVisibility]);
671
747
  const addMainIndicator = useCallback(
672
748
  (name, options) => {
673
749
  if (state.mainIndicators.includes(name)) return;
@@ -709,12 +785,24 @@ function useIndicators() {
709
785
  delete nextAxes[id];
710
786
  dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
711
787
  }
788
+ if (id in state.indicatorVisibility) {
789
+ const nextVisibility = { ...state.indicatorVisibility };
790
+ delete nextVisibility[id];
791
+ dispatch({ type: "SET_INDICATOR_VISIBILITY", visibility: nextVisibility });
792
+ }
712
793
  undoRedoListenerRef.current?.({
713
794
  type: "indicator_toggled",
714
795
  data: { name, wasActive: true, isMain: true, paneId: "candle_pane", yAxisId }
715
796
  });
716
797
  },
717
- [state.chart, state.mainIndicators, state.indicatorAxes, dispatch, undoRedoListenerRef]
798
+ [
799
+ state.chart,
800
+ state.mainIndicators,
801
+ state.indicatorAxes,
802
+ state.indicatorVisibility,
803
+ dispatch,
804
+ undoRedoListenerRef
805
+ ]
718
806
  );
719
807
  const addSubIndicator = useCallback(
720
808
  (name, options) => {
@@ -752,12 +840,24 @@ function useIndicators() {
752
840
  delete nextAxes[id];
753
841
  dispatch({ type: "SET_INDICATOR_AXES", axes: nextAxes });
754
842
  }
843
+ if (id in state.indicatorVisibility) {
844
+ const nextVisibility = { ...state.indicatorVisibility };
845
+ delete nextVisibility[id];
846
+ dispatch({ type: "SET_INDICATOR_VISIBILITY", visibility: nextVisibility });
847
+ }
755
848
  undoRedoListenerRef.current?.({
756
849
  type: "indicator_toggled",
757
850
  data: { name, wasActive: true, isMain: false, paneId, yAxisId }
758
851
  });
759
852
  },
760
- [state.chart, state.subIndicators, state.indicatorAxes, dispatch, undoRedoListenerRef]
853
+ [
854
+ state.chart,
855
+ state.subIndicators,
856
+ state.indicatorAxes,
857
+ state.indicatorVisibility,
858
+ dispatch,
859
+ undoRedoListenerRef
860
+ ]
761
861
  );
762
862
  const toggleMainIndicator = useCallback(
763
863
  (name) => {
@@ -783,8 +883,16 @@ function useIndicators() {
783
883
  (name, isMain, visible) => {
784
884
  const id = isMain ? `main_${name}` : `sub_${name}`;
785
885
  state.chart?.overrideIndicator({ name, id, visible });
886
+ const nextVisibility = { ...state.indicatorVisibility };
887
+ if (visible) delete nextVisibility[id];
888
+ else nextVisibility[id] = false;
889
+ dispatch({ type: "SET_INDICATOR_VISIBILITY", visibility: nextVisibility });
786
890
  },
787
- [state.chart]
891
+ [state.chart, state.indicatorVisibility, dispatch]
892
+ );
893
+ const isIndicatorVisible = useCallback(
894
+ (name, isMain) => state.indicatorVisibility[isMain ? `main_${name}` : `sub_${name}`] ?? true,
895
+ [state.indicatorVisibility]
788
896
  );
789
897
  const updateIndicatorParams = useCallback(
790
898
  // `paneId` is kept in the signature for API stability; klinecharts v10
@@ -867,9 +975,14 @@ function useIndicators() {
867
975
  id: paneId,
868
976
  height: COLLAPSED_HEIGHT
869
977
  });
870
- state.chart.overrideIndicator({ name, id: `sub_${name}`, visible: false });
978
+ const id = `sub_${name}`;
979
+ state.chart.overrideIndicator({ name, id, visible: false });
980
+ dispatch({
981
+ type: "SET_INDICATOR_VISIBILITY",
982
+ visibility: { ...state.indicatorVisibility, [id]: false }
983
+ });
871
984
  },
872
- [state.chart, state.subIndicators]
985
+ [state.chart, state.subIndicators, state.indicatorVisibility, dispatch]
873
986
  );
874
987
  const expandSubIndicator = useCallback(
875
988
  (name) => {
@@ -881,9 +994,13 @@ function useIndicators() {
881
994
  id: paneId,
882
995
  height: savedHeight
883
996
  });
884
- state.chart.overrideIndicator({ name, id: `sub_${name}`, visible: true });
997
+ const id = `sub_${name}`;
998
+ state.chart.overrideIndicator({ name, id, visible: true });
999
+ const nextVisibility = { ...state.indicatorVisibility };
1000
+ delete nextVisibility[id];
1001
+ dispatch({ type: "SET_INDICATOR_VISIBILITY", visibility: nextVisibility });
885
1002
  },
886
- [state.chart, state.subIndicators]
1003
+ [state.chart, state.subIndicators, state.indicatorVisibility, dispatch]
887
1004
  );
888
1005
  const isSubIndicatorCollapsed = useCallback(
889
1006
  (name) => {
@@ -992,6 +1109,7 @@ function useIndicators() {
992
1109
  moveToMain,
993
1110
  moveToSub,
994
1111
  setIndicatorVisible,
1112
+ isIndicatorVisible,
995
1113
  updateIndicatorParams,
996
1114
  getIndicatorParams,
997
1115
  isMainIndicatorActive,
@@ -1002,6 +1120,7 @@ function useIndicators() {
1002
1120
  reorderSubIndicator,
1003
1121
  indicatorAxes: state.indicatorAxes,
1004
1122
  getIndicatorAxis,
1123
+ indicatorVisibility: state.indicatorVisibility,
1005
1124
  bindIndicatorToNewAxis
1006
1125
  };
1007
1126
  }
@@ -1558,6 +1677,53 @@ function useScreenshot() {
1558
1677
  clear
1559
1678
  };
1560
1679
  }
1680
+ function useHotkeys() {
1681
+ const { state } = useKlinechartsUI();
1682
+ const registerHotkey$1 = useCallback((template) => {
1683
+ registerHotkey(template);
1684
+ }, []);
1685
+ const getHotkey$1 = useCallback(
1686
+ (name) => getHotkey(name) ?? null,
1687
+ []
1688
+ );
1689
+ const setHotkeysEnabled = useCallback(
1690
+ (enabled, exclude) => {
1691
+ state.chart?.setHotkey(
1692
+ exclude ? { enabled, exclude } : { enabled }
1693
+ );
1694
+ },
1695
+ [state.chart]
1696
+ );
1697
+ const getHotkeysConfig = useCallback(
1698
+ () => state.chart?.getHotkey() ?? null,
1699
+ [state.chart]
1700
+ );
1701
+ return {
1702
+ registerHotkey: registerHotkey$1,
1703
+ getHotkey: getHotkey$1,
1704
+ supportedHotkeys: getSupportedHotkeys(),
1705
+ setHotkeysEnabled,
1706
+ getHotkeysConfig
1707
+ };
1708
+ }
1709
+ function useChartAxes() {
1710
+ const { state } = useKlinechartsUI();
1711
+ const overrideXAxis = useCallback(
1712
+ (override) => {
1713
+ if (!state.chart) return;
1714
+ state.chart.overrideXAxis(override);
1715
+ },
1716
+ [state.chart]
1717
+ );
1718
+ const overrideYAxis = useCallback(
1719
+ (override) => {
1720
+ if (!state.chart) return;
1721
+ state.chart.overrideYAxis(override);
1722
+ },
1723
+ [state.chart]
1724
+ );
1725
+ return { overrideXAxis, overrideYAxis };
1726
+ }
1561
1727
  function useFullscreen() {
1562
1728
  const { fullscreenContainerRef } = useKlinechartsUIDispatch();
1563
1729
  const [isFullscreen, setIsFullscreen] = useState(false);
@@ -2122,6 +2288,7 @@ function useLayoutManager() {
2122
2288
  const newMainIndicators = [];
2123
2289
  const newSubIndicators = {};
2124
2290
  const restoredAxes = {};
2291
+ const restoredVisibility = {};
2125
2292
  if (chartState.indicators) {
2126
2293
  for (const ind of chartState.indicators) {
2127
2294
  const isMain = ind.paneId === "candle_pane";
@@ -2149,6 +2316,9 @@ function useLayoutManager() {
2149
2316
  if (ind.yAxisId) {
2150
2317
  restoredAxes[id2] = ind.yAxisId;
2151
2318
  }
2319
+ if (ind.visible === false) {
2320
+ restoredVisibility[id2] = false;
2321
+ }
2152
2322
  if (isMain) {
2153
2323
  newMainIndicators.push(ind.name);
2154
2324
  } else {
@@ -2168,6 +2338,10 @@ function useLayoutManager() {
2168
2338
  type: "SET_INDICATOR_AXES",
2169
2339
  axes: restoredAxes
2170
2340
  });
2341
+ dispatch({
2342
+ type: "SET_INDICATOR_VISIBILITY",
2343
+ visibility: restoredVisibility
2344
+ });
2171
2345
  if (chartState.drawings) {
2172
2346
  for (const drawing of chartState.drawings) {
2173
2347
  chart.createOverlay({
@@ -2534,100 +2708,63 @@ function useCrosshair() {
2534
2708
  }
2535
2709
  var alertCounter = 0;
2536
2710
  function useAlerts() {
2537
- const { state } = useKlinechartsUI();
2538
- const [alerts, setAlerts] = useState([]);
2539
- const callbackRef = useRef(null);
2540
- const prevCloseRef = useRef(null);
2711
+ const { state, dispatch } = useKlinechartsUI();
2712
+ const { alertTriggeredListenerRef } = useKlinechartsUIDispatch();
2713
+ const alerts = state.alerts;
2541
2714
  const addAlert = useCallback(
2542
- (price, condition, message) => {
2715
+ (price, condition, message, extendData) => {
2543
2716
  const id = `alert_${++alertCounter}`;
2717
+ const precision = state.chart?.getSymbol()?.pricePrecision ?? 2;
2718
+ const resolvedExtendData = {
2719
+ ...extendData,
2720
+ text: extendData?.text ?? message ?? price.toFixed(precision)
2721
+ };
2544
2722
  const alert = {
2545
2723
  id,
2546
2724
  price,
2547
2725
  condition,
2548
2726
  message,
2549
- triggered: false
2727
+ triggered: false,
2728
+ extendData: resolvedExtendData
2550
2729
  };
2551
- setAlerts((prev) => [...prev, alert]);
2730
+ dispatch({ type: "SET_ALERTS", alerts: [...state.alerts, alert] });
2552
2731
  if (state.chart) {
2732
+ ensureAlertLineRegistered();
2553
2733
  state.chart.createOverlay({
2554
- name: "horizontalStraightLine",
2734
+ name: "alertLine",
2555
2735
  id,
2556
2736
  groupId: "price_alerts",
2557
2737
  points: [{ value: price }],
2558
- styles: {
2559
- line: {
2560
- style: "dashed",
2561
- color: "#ff9800",
2562
- size: 1
2563
- }
2564
- },
2738
+ extendData: resolvedExtendData,
2565
2739
  lock: true
2566
2740
  });
2567
2741
  }
2568
2742
  return id;
2569
2743
  },
2570
- [state.chart]
2744
+ [state.chart, state.alerts, dispatch]
2571
2745
  );
2572
2746
  const removeAlert = useCallback(
2573
2747
  (id) => {
2574
- setAlerts((prev) => prev.filter((a) => a.id !== id));
2748
+ dispatch({
2749
+ type: "SET_ALERTS",
2750
+ alerts: state.alerts.filter((a) => a.id !== id)
2751
+ });
2575
2752
  state.chart?.removeOverlay({ id });
2576
2753
  },
2577
- [state.chart]
2754
+ [state.chart, state.alerts, dispatch]
2578
2755
  );
2579
2756
  const clearAlerts = useCallback(() => {
2580
- setAlerts((prev) => {
2581
- for (const alert of prev) {
2582
- state.chart?.removeOverlay({ id: alert.id });
2583
- }
2584
- return [];
2585
- });
2586
- }, [state.chart]);
2757
+ for (const alert of state.alerts) {
2758
+ state.chart?.removeOverlay({ id: alert.id });
2759
+ }
2760
+ dispatch({ type: "SET_ALERTS", alerts: [] });
2761
+ }, [state.chart, state.alerts, dispatch]);
2587
2762
  const onAlertTriggered = useCallback(
2588
2763
  (callback) => {
2589
- callbackRef.current = callback;
2764
+ alertTriggeredListenerRef.current = callback;
2590
2765
  },
2591
- []
2766
+ [alertTriggeredListenerRef]
2592
2767
  );
2593
- useEffect(() => {
2594
- if (!state.chart) return;
2595
- const interval = setInterval(() => {
2596
- const dataList = state.chart?.getDataList();
2597
- if (!dataList || dataList.length === 0) return;
2598
- const lastBar = dataList[dataList.length - 1];
2599
- const currentClose = lastBar.close;
2600
- const prevClose = prevCloseRef.current;
2601
- if (prevClose === null) {
2602
- prevCloseRef.current = currentClose;
2603
- return;
2604
- }
2605
- prevCloseRef.current = currentClose;
2606
- setAlerts((prev) => {
2607
- let changed = false;
2608
- const next = prev.map((alert) => {
2609
- if (alert.triggered) return alert;
2610
- let shouldTrigger = false;
2611
- if (alert.condition === "crossing_up") {
2612
- shouldTrigger = prevClose < alert.price && currentClose >= alert.price;
2613
- } else if (alert.condition === "crossing_down") {
2614
- shouldTrigger = prevClose > alert.price && currentClose <= alert.price;
2615
- } else if (alert.condition === "crossing") {
2616
- shouldTrigger = prevClose < alert.price && currentClose >= alert.price || prevClose > alert.price && currentClose <= alert.price;
2617
- }
2618
- if (shouldTrigger) {
2619
- changed = true;
2620
- const triggered = { ...alert, triggered: true };
2621
- callbackRef.current?.(triggered);
2622
- return triggered;
2623
- }
2624
- return alert;
2625
- });
2626
- return changed ? next : prev;
2627
- });
2628
- }, 1e3);
2629
- return () => clearInterval(interval);
2630
- }, [state.chart]);
2631
2768
  return {
2632
2769
  alerts,
2633
2770
  addAlert,
@@ -2773,128 +2910,124 @@ function useWatchlist() {
2773
2910
  };
2774
2911
  }
2775
2912
  function useReplay() {
2776
- const { state } = useKlinechartsUI();
2777
- const [isReplaying, setIsReplaying] = useState(false);
2778
- const [isPaused, setIsPaused] = useState(false);
2779
- const [speed, setSpeedState] = useState(1);
2780
- const [barIndex, setBarIndex] = useState(0);
2781
- const [totalBars, setTotalBars] = useState(0);
2782
- const intervalRef = useRef(null);
2783
- const savedDataRef = useRef([]);
2784
- const currentIndexRef = useRef(0);
2913
+ const { state, dispatch } = useKlinechartsUI();
2914
+ const { replayIntervalRef, replaySavedDataRef, replayIndexRef } = useKlinechartsUIDispatch();
2915
+ const { isReplaying, isPaused, speed, barIndex, totalBars } = state.replay;
2785
2916
  const clearInterval_ = useCallback(() => {
2786
- if (intervalRef.current !== null) {
2787
- clearInterval(intervalRef.current);
2788
- intervalRef.current = null;
2917
+ if (replayIntervalRef.current !== null) {
2918
+ clearInterval(replayIntervalRef.current);
2919
+ replayIntervalRef.current = null;
2789
2920
  }
2790
- }, []);
2921
+ }, [replayIntervalRef]);
2791
2922
  const addNextBar = useCallback(() => {
2792
2923
  if (!state.chart) return;
2793
- const data = savedDataRef.current;
2794
- const idx = currentIndexRef.current;
2924
+ const data = replaySavedDataRef.current;
2925
+ const idx = replayIndexRef.current;
2795
2926
  if (idx >= data.length) {
2796
2927
  clearInterval_();
2797
- setIsPaused(true);
2928
+ dispatch({ type: "SET_REPLAY", replay: { isPaused: true } });
2798
2929
  return;
2799
2930
  }
2800
2931
  const bar = data[idx];
2801
2932
  state.chart.updateData?.(bar);
2802
- currentIndexRef.current = idx + 1;
2803
- setBarIndex(idx + 1);
2804
- }, [state.chart, clearInterval_]);
2933
+ replayIndexRef.current = idx + 1;
2934
+ dispatch({ type: "SET_REPLAY", replay: { barIndex: idx + 1 } });
2935
+ }, [state.chart, clearInterval_, replaySavedDataRef, replayIndexRef, dispatch]);
2805
2936
  const startInterval = useCallback(
2806
2937
  (currentSpeed) => {
2807
2938
  clearInterval_();
2808
- intervalRef.current = setInterval(addNextBar, 1e3 / currentSpeed);
2939
+ replayIntervalRef.current = setInterval(addNextBar, 1e3 / currentSpeed);
2809
2940
  },
2810
- [addNextBar, clearInterval_]
2941
+ [addNextBar, clearInterval_, replayIntervalRef]
2811
2942
  );
2812
2943
  const startReplay = useCallback(() => {
2813
2944
  if (!state.chart) return;
2814
2945
  const dataList = state.chart.getDataList();
2815
2946
  if (!dataList || dataList.length === 0) return;
2816
- savedDataRef.current = [...dataList];
2817
- currentIndexRef.current = 0;
2818
- setTotalBars(dataList.length);
2819
- setBarIndex(0);
2820
- setIsReplaying(true);
2821
- setIsPaused(false);
2947
+ replaySavedDataRef.current = [...dataList];
2948
+ replayIndexRef.current = 0;
2949
+ dispatch({
2950
+ type: "SET_REPLAY",
2951
+ replay: {
2952
+ totalBars: dataList.length,
2953
+ barIndex: 0,
2954
+ isReplaying: true,
2955
+ isPaused: false
2956
+ }
2957
+ });
2822
2958
  state.chart.clearData?.();
2823
2959
  startInterval(speed);
2824
- }, [state.chart, speed, startInterval]);
2960
+ }, [state.chart, speed, startInterval, replaySavedDataRef, replayIndexRef, dispatch]);
2825
2961
  const stopReplay = useCallback(() => {
2826
2962
  if (!state.chart) return;
2827
2963
  clearInterval_();
2828
- const data = savedDataRef.current;
2964
+ const data = replaySavedDataRef.current;
2829
2965
  if (data.length > 0) {
2830
2966
  state.chart.clearData?.();
2831
2967
  for (const bar of data) {
2832
2968
  state.chart.updateData?.(bar);
2833
2969
  }
2834
2970
  }
2835
- savedDataRef.current = [];
2836
- currentIndexRef.current = 0;
2837
- setIsReplaying(false);
2838
- setIsPaused(false);
2839
- setBarIndex(0);
2840
- setTotalBars(0);
2841
- }, [state.chart, clearInterval_]);
2971
+ replaySavedDataRef.current = [];
2972
+ replayIndexRef.current = 0;
2973
+ dispatch({
2974
+ type: "SET_REPLAY",
2975
+ replay: { isReplaying: false, isPaused: false, barIndex: 0, totalBars: 0 }
2976
+ });
2977
+ }, [state.chart, clearInterval_, replaySavedDataRef, replayIndexRef, dispatch]);
2842
2978
  const togglePause = useCallback(() => {
2843
2979
  if (!isReplaying) return;
2844
2980
  if (isPaused) {
2845
2981
  startInterval(speed);
2846
- setIsPaused(false);
2982
+ dispatch({ type: "SET_REPLAY", replay: { isPaused: false } });
2847
2983
  } else {
2848
2984
  clearInterval_();
2849
- setIsPaused(true);
2985
+ dispatch({ type: "SET_REPLAY", replay: { isPaused: true } });
2850
2986
  }
2851
- }, [isReplaying, isPaused, speed, startInterval, clearInterval_]);
2987
+ }, [isReplaying, isPaused, speed, startInterval, clearInterval_, dispatch]);
2852
2988
  const stepForward = useCallback(() => {
2853
2989
  if (!isReplaying || !isPaused) return;
2854
2990
  addNextBar();
2855
2991
  }, [isReplaying, isPaused, addNextBar]);
2856
2992
  const stepBackward = useCallback(() => {
2857
2993
  if (!isReplaying || !isPaused || !state.chart) return;
2858
- const idx = currentIndexRef.current;
2994
+ const idx = replayIndexRef.current;
2859
2995
  if (idx <= 1) return;
2860
- const data = savedDataRef.current;
2996
+ const data = replaySavedDataRef.current;
2861
2997
  state.chart.clearData?.();
2862
2998
  for (let i = 0; i < idx - 1; i++) {
2863
2999
  state.chart.updateData?.(data[i]);
2864
3000
  }
2865
- currentIndexRef.current = idx - 1;
2866
- setBarIndex(idx - 1);
2867
- }, [isReplaying, isPaused, state.chart]);
3001
+ replayIndexRef.current = idx - 1;
3002
+ dispatch({ type: "SET_REPLAY", replay: { barIndex: idx - 1 } });
3003
+ }, [isReplaying, isPaused, state.chart, replaySavedDataRef, replayIndexRef, dispatch]);
2868
3004
  const seekTo = useCallback(
2869
3005
  (targetIndex) => {
2870
3006
  if (!isReplaying || !state.chart) return;
2871
- const data = savedDataRef.current;
3007
+ const data = replaySavedDataRef.current;
2872
3008
  const clamped = Math.max(0, Math.min(targetIndex, data.length));
2873
3009
  clearInterval_();
2874
- setIsPaused(true);
2875
3010
  state.chart.clearData?.();
2876
3011
  for (let i = 0; i < clamped; i++) {
2877
3012
  state.chart.updateData?.(data[i]);
2878
3013
  }
2879
- currentIndexRef.current = clamped;
2880
- setBarIndex(clamped);
3014
+ replayIndexRef.current = clamped;
3015
+ dispatch({
3016
+ type: "SET_REPLAY",
3017
+ replay: { isPaused: true, barIndex: clamped }
3018
+ });
2881
3019
  },
2882
- [isReplaying, state.chart, clearInterval_]
3020
+ [isReplaying, state.chart, clearInterval_, replaySavedDataRef, replayIndexRef, dispatch]
2883
3021
  );
2884
3022
  const setSpeed = useCallback(
2885
3023
  (newSpeed) => {
2886
- setSpeedState(newSpeed);
3024
+ dispatch({ type: "SET_REPLAY", replay: { speed: newSpeed } });
2887
3025
  if (isReplaying && !isPaused) {
2888
3026
  startInterval(newSpeed);
2889
3027
  }
2890
3028
  },
2891
- [isReplaying, isPaused, startInterval]
3029
+ [isReplaying, isPaused, startInterval, dispatch]
2892
3030
  );
2893
- useEffect(() => {
2894
- return () => {
2895
- clearInterval_();
2896
- };
2897
- }, [clearInterval_]);
2898
3031
  return {
2899
3032
  isReplaying,
2900
3033
  isPaused,
@@ -3051,31 +3184,25 @@ function useCompare() {
3051
3184
  return { symbols, addSymbol, removeSymbol, toggleSymbol, clearAll };
3052
3185
  }
3053
3186
  var MEASURE_OVERLAY_ID = "__measure_line__";
3187
+ function computeResult(from, to) {
3188
+ const priceDiff = to.price - from.price;
3189
+ const pricePercent = from.price !== 0 ? priceDiff / from.price * 100 : 0;
3190
+ const bars = Math.abs(to.barIndex - from.barIndex);
3191
+ const timeDiff = Math.abs(to.timestamp - from.timestamp);
3192
+ return { from, to, priceDiff, pricePercent, bars, timeDiff };
3193
+ }
3054
3194
  function useMeasure() {
3055
- const { state } = useKlinechartsUI();
3056
- const [isActive, setIsActive] = useState(false);
3057
- const [fromPoint, setFromPoint] = useState(null);
3058
- const [result, setResult] = useState(null);
3059
- const clickCountRef = useRef(0);
3060
- const computeResult = useCallback(
3061
- (from, to) => {
3062
- const priceDiff = to.price - from.price;
3063
- const pricePercent = from.price !== 0 ? priceDiff / from.price * 100 : 0;
3064
- const bars = Math.abs(to.barIndex - from.barIndex);
3065
- const timeDiff = Math.abs(to.timestamp - from.timestamp);
3066
- return { from, to, priceDiff, pricePercent, bars, timeDiff };
3067
- },
3068
- []
3069
- );
3195
+ const { state, dispatch } = useKlinechartsUI();
3196
+ const { isActive, fromPoint, result } = state.measure;
3070
3197
  const cleanup = useCallback(() => {
3071
3198
  state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
3072
3199
  }, [state.chart]);
3073
3200
  const startMeasure = useCallback(() => {
3074
3201
  cleanup();
3075
- setIsActive(true);
3076
- setFromPoint(null);
3077
- setResult(null);
3078
- clickCountRef.current = 0;
3202
+ dispatch({
3203
+ type: "SET_MEASURE",
3204
+ measure: { isActive: true, fromPoint: null, result: null }
3205
+ });
3079
3206
  if (!state.chart) return;
3080
3207
  state.chart.createOverlay({
3081
3208
  name: "segment",
@@ -3111,23 +3238,25 @@ function useMeasure() {
3111
3238
  };
3112
3239
  const from = makePoint(points[0]);
3113
3240
  const to = makePoint(points[1]);
3114
- setFromPoint(from);
3115
- setResult(computeResult(from, to));
3116
- setIsActive(false);
3241
+ dispatch({
3242
+ type: "SET_MEASURE",
3243
+ measure: {
3244
+ isActive: false,
3245
+ fromPoint: from,
3246
+ result: computeResult(from, to)
3247
+ }
3248
+ });
3117
3249
  }
3118
3250
  });
3119
- }, [state.chart, cleanup, computeResult]);
3251
+ }, [state.chart, cleanup, dispatch]);
3120
3252
  const cancelMeasure = useCallback(() => {
3121
3253
  cleanup();
3122
- setIsActive(false);
3123
- setFromPoint(null);
3124
- clickCountRef.current = 0;
3125
- }, [cleanup]);
3254
+ dispatch({ type: "SET_MEASURE", measure: { isActive: false, fromPoint: null } });
3255
+ }, [cleanup, dispatch]);
3126
3256
  const clearResult = useCallback(() => {
3127
3257
  cleanup();
3128
- setResult(null);
3129
- setFromPoint(null);
3130
- }, [cleanup]);
3258
+ dispatch({ type: "SET_MEASURE", measure: { result: null, fromPoint: null } });
3259
+ }, [cleanup, dispatch]);
3131
3260
  useEffect(() => {
3132
3261
  return () => {
3133
3262
  state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
@@ -3291,6 +3420,6 @@ function createDataLoader(datafeed, dispatch) {
3291
3420
  };
3292
3421
  }
3293
3422
 
3294
- 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, useCompare, useCrosshair, useDataExport, useDrawingTools, useFullscreen, useIndicators, useKlinechartsUI, useKlinechartsUILoading, useKlinechartsUISettings, useKlinechartsUITheme, useLayoutManager, useMeasure, useOrderLines, usePeriods, useReplay, useScreenshot, useScriptEditor, useSymbolSearch, useTimezone, useUndoRedo, useWatchlist };
3423
+ 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 };
3295
3424
  //# sourceMappingURL=index.js.map
3296
3425
  //# sourceMappingURL=index.js.map