react-klinecharts-ui 0.1.0 → 0.3.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.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkSLTXNLKN_cjs = require('./chunk-SLTXNLKN.cjs');
3
+ var chunkPIFLJKYF_cjs = require('./chunk-PIFLJKYF.cjs');
4
4
  var react = require('react');
5
5
  var reactKlinecharts = require('react-klinecharts');
6
6
  var jsxRuntime = require('react/jsx-runtime');
@@ -125,6 +125,7 @@ function KlinechartsUIProvider({
125
125
  })
126
126
  );
127
127
  const fullscreenContainerRef = react.useRef(null);
128
+ const undoRedoListenerRef = react.useRef(null);
128
129
  const stateRef = react.useRef(state);
129
130
  stateRef.current = state;
130
131
  const callbacksRef = react.useRef({
@@ -176,7 +177,7 @@ function KlinechartsUIProvider({
176
177
  const extraOverlaysRef = react.useRef(extraOverlays);
177
178
  react.useEffect(() => {
178
179
  if (shouldRegister) {
179
- chunkSLTXNLKN_cjs.registerExtensions();
180
+ chunkPIFLJKYF_cjs.registerExtensions();
180
181
  }
181
182
  extraOverlaysRef.current?.forEach((overlay) => reactKlinecharts.registerOverlay(overlay));
182
183
  }, [shouldRegister]);
@@ -190,7 +191,8 @@ function KlinechartsUIProvider({
190
191
  dispatch: enhancedDispatch,
191
192
  datafeed,
192
193
  onSettingsChange,
193
- fullscreenContainerRef
194
+ fullscreenContainerRef,
195
+ undoRedoListenerRef
194
196
  }),
195
197
  [enhancedDispatch, datafeed, onSettingsChange]
196
198
  );
@@ -358,7 +360,14 @@ var MAIN_INDICATORS = [
358
360
  "SMA",
359
361
  "BOLL",
360
362
  "SAR",
361
- "BBI"
363
+ "BBI",
364
+ "BOLL_TV",
365
+ "Ichimoku",
366
+ "SuperTrend",
367
+ "HMA",
368
+ "VWAP",
369
+ "PivotPoints",
370
+ "MA_Ribbon"
362
371
  ];
363
372
  var SUB_INDICATORS = [
364
373
  "MA",
@@ -386,7 +395,11 @@ var SUB_INDICATORS = [
386
395
  "ROC",
387
396
  "PVT",
388
397
  "BBI",
389
- "AO"
398
+ "AO",
399
+ "MACD_TV",
400
+ "RSI_TV",
401
+ "CCI_TV",
402
+ "Stochastic"
390
403
  ];
391
404
  var INDICATOR_PARAMS = {
392
405
  SMA: {
@@ -545,12 +558,98 @@ var INDICATOR_PARAMS = {
545
558
  { label: "params_1", defaultValue: 5 },
546
559
  { label: "params_2", defaultValue: 34 }
547
560
  ]
561
+ },
562
+ BOLL_TV: {
563
+ name: "BOLL_TV",
564
+ localeKey: "boll_tv",
565
+ params: [
566
+ { label: "period", defaultValue: 20 },
567
+ { label: "multiplier", defaultValue: 2 }
568
+ ]
569
+ },
570
+ Ichimoku: {
571
+ name: "Ichimoku",
572
+ localeKey: "ichimoku",
573
+ params: [
574
+ { label: "tenkan", defaultValue: 9 },
575
+ { label: "kijun", defaultValue: 26 },
576
+ { label: "senkou_b", defaultValue: 52 },
577
+ { label: "offset", defaultValue: 26 }
578
+ ]
579
+ },
580
+ SuperTrend: {
581
+ name: "SuperTrend",
582
+ localeKey: "super_trend",
583
+ params: [
584
+ { label: "period", defaultValue: 10 },
585
+ { label: "factor", defaultValue: 3 }
586
+ ]
587
+ },
588
+ HMA: {
589
+ name: "HMA",
590
+ localeKey: "hma",
591
+ params: [{ label: "period", defaultValue: 9 }]
592
+ },
593
+ VWAP: {
594
+ name: "VWAP",
595
+ localeKey: "vwap",
596
+ params: []
597
+ },
598
+ PivotPoints: {
599
+ name: "PivotPoints",
600
+ localeKey: "pivot_points",
601
+ params: []
602
+ },
603
+ MA_Ribbon: {
604
+ name: "MA_Ribbon",
605
+ localeKey: "ma_ribbon",
606
+ params: [
607
+ { label: "period_1", defaultValue: 10 },
608
+ { label: "period_2", defaultValue: 20 },
609
+ { label: "period_3", defaultValue: 30 },
610
+ { label: "period_4", defaultValue: 40 }
611
+ ]
612
+ },
613
+ MACD_TV: {
614
+ name: "MACD_TV",
615
+ localeKey: "macd_tv",
616
+ params: [
617
+ { label: "fast", defaultValue: 12 },
618
+ { label: "slow", defaultValue: 26 },
619
+ { label: "signal", defaultValue: 9 }
620
+ ]
621
+ },
622
+ RSI_TV: {
623
+ name: "RSI_TV",
624
+ localeKey: "rsi_tv",
625
+ params: [
626
+ { label: "rsi_period", defaultValue: 14 },
627
+ { label: "ma_period", defaultValue: 14 }
628
+ ]
629
+ },
630
+ CCI_TV: {
631
+ name: "CCI_TV",
632
+ localeKey: "cci_tv",
633
+ params: [{ label: "period", defaultValue: 20 }]
634
+ },
635
+ Stochastic: {
636
+ name: "Stochastic",
637
+ localeKey: "stochastic",
638
+ params: [
639
+ { label: "period", defaultValue: 14 },
640
+ { label: "smooth_k", defaultValue: 3 },
641
+ { label: "smooth_d", defaultValue: 3 }
642
+ ]
548
643
  }
549
644
  };
550
645
 
551
646
  // src/hooks/useIndicators.ts
647
+ var COLLAPSED_HEIGHT = 30;
552
648
  function useIndicators() {
553
649
  const { state, dispatch } = useKlinechartsUI();
650
+ const { undoRedoListenerRef } = useKlinechartsUIDispatch();
651
+ const paneHeightsRef = react.useRef({});
652
+ const collapsedPanesRef = react.useRef(/* @__PURE__ */ new Set());
554
653
  const mainIndicators = react.useMemo(() => {
555
654
  const activeNames = state.mainIndicators;
556
655
  const inactive = MAIN_INDICATORS.filter((n) => !activeNames.includes(n));
@@ -577,16 +676,24 @@ function useIndicators() {
577
676
  );
578
677
  const newIndicators = [...state.mainIndicators, name];
579
678
  dispatch({ type: "SET_MAIN_INDICATORS", indicators: newIndicators });
679
+ undoRedoListenerRef.current?.({
680
+ type: "indicator_toggled",
681
+ data: { name, wasActive: false, isMain: true, paneId: "candle_pane" }
682
+ });
580
683
  },
581
- [state.chart, state.mainIndicators, dispatch]
684
+ [state.chart, state.mainIndicators, dispatch, undoRedoListenerRef]
582
685
  );
583
686
  const removeMainIndicator = react.useCallback(
584
687
  (name) => {
585
688
  state.chart?.removeIndicator({ id: `main_${name}` });
586
689
  const newIndicators = state.mainIndicators.filter((n) => n !== name);
587
690
  dispatch({ type: "SET_MAIN_INDICATORS", indicators: newIndicators });
691
+ undoRedoListenerRef.current?.({
692
+ type: "indicator_toggled",
693
+ data: { name, wasActive: true, isMain: true, paneId: "candle_pane" }
694
+ });
588
695
  },
589
- [state.chart, state.mainIndicators, dispatch]
696
+ [state.chart, state.mainIndicators, dispatch, undoRedoListenerRef]
590
697
  );
591
698
  const addSubIndicator = react.useCallback(
592
699
  (name) => {
@@ -596,17 +703,26 @@ function useIndicators() {
596
703
  const paneId = state.chart?.getIndicators({ id })?.[0]?.paneId ?? "";
597
704
  const newIndicators = { ...state.subIndicators, [name]: paneId };
598
705
  dispatch({ type: "SET_SUB_INDICATORS", indicators: newIndicators });
706
+ undoRedoListenerRef.current?.({
707
+ type: "indicator_toggled",
708
+ data: { name, wasActive: false, isMain: false, paneId }
709
+ });
599
710
  },
600
- [state.chart, state.subIndicators, dispatch]
711
+ [state.chart, state.subIndicators, dispatch, undoRedoListenerRef]
601
712
  );
602
713
  const removeSubIndicator = react.useCallback(
603
714
  (name) => {
715
+ const paneId = state.subIndicators[name] ?? "";
604
716
  state.chart?.removeIndicator({ id: `sub_${name}` });
605
717
  const newIndicators = { ...state.subIndicators };
606
718
  delete newIndicators[name];
607
719
  dispatch({ type: "SET_SUB_INDICATORS", indicators: newIndicators });
720
+ undoRedoListenerRef.current?.({
721
+ type: "indicator_toggled",
722
+ data: { name, wasActive: true, isMain: false, paneId }
723
+ });
608
724
  },
609
- [state.chart, state.subIndicators, dispatch]
725
+ [state.chart, state.subIndicators, dispatch, undoRedoListenerRef]
610
726
  );
611
727
  const toggleMainIndicator = react.useCallback(
612
728
  (name) => {
@@ -652,9 +768,15 @@ function useIndicators() {
652
768
  const moveToMain = react.useCallback(
653
769
  (name) => {
654
770
  if (!state.chart) return;
771
+ const prevCalcParams = state.chart.getIndicators({ id: `sub_${name}` })?.[0]?.calcParams;
655
772
  state.chart.removeIndicator({ id: `sub_${name}` });
656
773
  state.chart.createIndicator(
657
- { name, id: `main_${name}`, paneId: "candle_pane" },
774
+ {
775
+ name,
776
+ id: `main_${name}`,
777
+ paneId: "candle_pane",
778
+ ...prevCalcParams ? { calcParams: prevCalcParams } : {}
779
+ },
658
780
  true,
659
781
  { id: "candle_pane" }
660
782
  );
@@ -671,9 +793,14 @@ function useIndicators() {
671
793
  const moveToSub = react.useCallback(
672
794
  (name) => {
673
795
  if (!state.chart) return;
796
+ const prevCalcParams = state.chart.getIndicators({ id: `main_${name}` })?.[0]?.calcParams;
674
797
  state.chart.removeIndicator({ id: `main_${name}` });
675
798
  const subId = `sub_${name}`;
676
- state.chart.createIndicator({ name, id: subId });
799
+ state.chart.createIndicator({
800
+ name,
801
+ id: subId,
802
+ ...prevCalcParams ? { calcParams: prevCalcParams } : {}
803
+ });
677
804
  const paneId = state.chart.getIndicators({ id: subId })?.[0]?.paneId ?? "";
678
805
  const newMain = state.mainIndicators.filter((n) => n !== name);
679
806
  dispatch({ type: "SET_MAIN_INDICATORS", indicators: newMain });
@@ -692,6 +819,90 @@ function useIndicators() {
692
819
  (name) => name in state.subIndicators,
693
820
  [state.subIndicators]
694
821
  );
822
+ const collapseSubIndicator = react.useCallback(
823
+ (name) => {
824
+ const paneId = state.subIndicators[name];
825
+ if (!state.chart || !paneId) return;
826
+ const currentSize = state.chart.getSize?.(paneId);
827
+ if (currentSize?.height && currentSize.height > COLLAPSED_HEIGHT) {
828
+ paneHeightsRef.current[paneId] = currentSize.height;
829
+ }
830
+ collapsedPanesRef.current.add(paneId);
831
+ state.chart.setPaneOptions?.({
832
+ id: paneId,
833
+ height: COLLAPSED_HEIGHT
834
+ });
835
+ state.chart.overrideIndicator({ name, id: `sub_${name}`, visible: false });
836
+ },
837
+ [state.chart, state.subIndicators]
838
+ );
839
+ const expandSubIndicator = react.useCallback(
840
+ (name) => {
841
+ const paneId = state.subIndicators[name];
842
+ if (!state.chart || !paneId) return;
843
+ const savedHeight = paneHeightsRef.current[paneId] ?? 100;
844
+ collapsedPanesRef.current.delete(paneId);
845
+ state.chart.setPaneOptions?.({
846
+ id: paneId,
847
+ height: savedHeight
848
+ });
849
+ state.chart.overrideIndicator({ name, id: `sub_${name}`, visible: true });
850
+ },
851
+ [state.chart, state.subIndicators]
852
+ );
853
+ const isSubIndicatorCollapsed = react.useCallback(
854
+ (name) => {
855
+ const paneId = state.subIndicators[name];
856
+ return paneId ? collapsedPanesRef.current.has(paneId) : false;
857
+ },
858
+ [state.subIndicators]
859
+ );
860
+ const reorderSubIndicator = react.useCallback(
861
+ (name, direction) => {
862
+ if (!state.chart) return;
863
+ const subNames = Object.keys(state.subIndicators);
864
+ const idx = subNames.indexOf(name);
865
+ if (idx === -1) return;
866
+ const swapIdx = direction === "up" ? idx - 1 : idx + 1;
867
+ if (swapIdx < 0 || swapIdx >= subNames.length) return;
868
+ const subStates = subNames.map((n) => {
869
+ const id = `sub_${n}`;
870
+ const indicator = state.chart.getIndicators({ id })?.[0];
871
+ return {
872
+ name: n,
873
+ calcParams: indicator?.calcParams,
874
+ visible: indicator?.visible ?? true,
875
+ styles: indicator?.styles,
876
+ paneId: state.subIndicators[n]
877
+ };
878
+ });
879
+ [subStates[idx], subStates[swapIdx]] = [subStates[swapIdx], subStates[idx]];
880
+ for (const n of subNames) {
881
+ state.chart.removeIndicator({ id: `sub_${n}` });
882
+ }
883
+ const newSubIndicators = {};
884
+ for (const sub of subStates) {
885
+ const id = `sub_${sub.name}`;
886
+ state.chart.createIndicator({
887
+ name: sub.name,
888
+ id,
889
+ ...sub.calcParams ? { calcParams: sub.calcParams } : {},
890
+ visible: sub.visible
891
+ });
892
+ const paneId = state.chart.getIndicators({ id })?.[0]?.paneId ?? "";
893
+ newSubIndicators[sub.name] = paneId;
894
+ if (sub.styles) {
895
+ state.chart.overrideIndicator({
896
+ name: sub.name,
897
+ paneId,
898
+ styles: sub.styles
899
+ });
900
+ }
901
+ }
902
+ dispatch({ type: "SET_SUB_INDICATORS", indicators: newSubIndicators });
903
+ },
904
+ [state.chart, state.subIndicators, dispatch]
905
+ );
695
906
  return {
696
907
  mainIndicators,
697
908
  subIndicators,
@@ -711,7 +922,11 @@ function useIndicators() {
711
922
  updateIndicatorParams,
712
923
  getIndicatorParams,
713
924
  isMainIndicatorActive,
714
- isSubIndicatorActive
925
+ isSubIndicatorActive,
926
+ collapseSubIndicator,
927
+ expandSubIndicator,
928
+ isSubIndicatorCollapsed,
929
+ reorderSubIndicator
715
930
  };
716
931
  }
717
932
 
@@ -737,7 +952,9 @@ var DRAWING_CATEGORIES = [
737
952
  key: "moreLine",
738
953
  tools: [
739
954
  { name: "priceChannelLine", localeKey: "price_channel_line" },
740
- { name: "parallelStraightLine", localeKey: "parallel_straight_line" }
955
+ { name: "parallelStraightLine", localeKey: "parallel_straight_line" },
956
+ { name: "ray", localeKey: "ray" },
957
+ { name: "parallelChannel", localeKey: "parallel_channel" }
741
958
  ]
742
959
  },
743
960
  {
@@ -761,7 +978,9 @@ var DRAWING_CATEGORIES = [
761
978
  localeKey: "fibonacci_speed_resistance_fan"
762
979
  },
763
980
  { name: "fibonacciExtension", localeKey: "fibonacci_extension" },
764
- { name: "gannBox", localeKey: "gann_box" }
981
+ { name: "fibRetracement", localeKey: "fib_retracement" },
982
+ { name: "gannBox", localeKey: "gann_box" },
983
+ { name: "gannFan", localeKey: "gann_fan" }
765
984
  ]
766
985
  },
767
986
  {
@@ -772,7 +991,27 @@ var DRAWING_CATEGORIES = [
772
991
  { name: "threeWaves", localeKey: "three_waves" },
773
992
  { name: "fiveWaves", localeKey: "five_waves" },
774
993
  { name: "eightWaves", localeKey: "eight_waves" },
775
- { name: "anyWaves", localeKey: "any_waves" }
994
+ { name: "anyWaves", localeKey: "any_waves" },
995
+ { name: "elliottWave", localeKey: "elliott_wave" }
996
+ ]
997
+ },
998
+ {
999
+ key: "measure",
1000
+ tools: [
1001
+ { name: "measure", localeKey: "measure" }
1002
+ ]
1003
+ },
1004
+ {
1005
+ key: "position",
1006
+ tools: [
1007
+ { name: "longPosition", localeKey: "long_position" },
1008
+ { name: "shortPosition", localeKey: "short_position" }
1009
+ ]
1010
+ },
1011
+ {
1012
+ key: "annotation",
1013
+ tools: [
1014
+ { name: "brush", localeKey: "brush" }
776
1015
  ]
777
1016
  }
778
1017
  ];
@@ -781,10 +1020,22 @@ var DRAWING_CATEGORIES = [
781
1020
  var DRAWING_GROUP_ID = "drawing_tools";
782
1021
  function useDrawingTools() {
783
1022
  const { state } = useKlinechartsUI();
1023
+ const { undoRedoListenerRef } = useKlinechartsUIDispatch();
784
1024
  const [activeTool, setActiveTool] = react.useState(null);
785
1025
  const [magnetMode, setMagnetModeState] = react.useState("normal");
786
1026
  const [isLocked, setIsLocked] = react.useState(false);
787
1027
  const [isVisible, setIsVisible] = react.useState(true);
1028
+ const [autoRetrigger, setAutoRetrigger] = react.useState(true);
1029
+ const activeToolRef = react.useRef(activeTool);
1030
+ activeToolRef.current = activeTool;
1031
+ const autoRetriggerRef = react.useRef(autoRetrigger);
1032
+ autoRetriggerRef.current = autoRetrigger;
1033
+ const isLockedRef = react.useRef(isLocked);
1034
+ isLockedRef.current = isLocked;
1035
+ const isVisibleRef = react.useRef(isVisible);
1036
+ isVisibleRef.current = isVisible;
1037
+ const magnetModeRef = react.useRef(magnetMode);
1038
+ magnetModeRef.current = magnetMode;
788
1039
  const categories = react.useMemo(
789
1040
  () => DRAWING_CATEGORIES.map((cat) => ({
790
1041
  key: cat.key,
@@ -795,18 +1046,45 @@ function useDrawingTools() {
795
1046
  })),
796
1047
  []
797
1048
  );
798
- const selectTool = react.useCallback(
1049
+ const createOverlayForTool = react.useCallback(
799
1050
  (name) => {
800
- setActiveTool(name);
1051
+ const mode = magnetModeRef.current === "strong" ? "strong_magnet" : magnetModeRef.current === "weak" ? "weak_magnet" : "normal";
801
1052
  state.chart?.createOverlay({
802
1053
  name,
803
1054
  groupId: DRAWING_GROUP_ID,
804
- lock: isLocked,
805
- visible: isVisible,
806
- mode: magnetMode === "strong" ? "strong_magnet" : magnetMode === "weak" ? "weak_magnet" : "normal"
1055
+ lock: isLockedRef.current,
1056
+ visible: isVisibleRef.current,
1057
+ mode,
1058
+ onDrawEnd: (event) => {
1059
+ const o = event.overlay;
1060
+ undoRedoListenerRef.current?.({
1061
+ type: "overlay_added",
1062
+ data: {
1063
+ id: o.id,
1064
+ overlayData: {
1065
+ name: o.name,
1066
+ points: o.points,
1067
+ styles: o.styles,
1068
+ extendData: o.extendData
1069
+ }
1070
+ }
1071
+ });
1072
+ if (autoRetriggerRef.current && activeToolRef.current === name) {
1073
+ requestAnimationFrame(() => {
1074
+ createOverlayForTool(name);
1075
+ });
1076
+ }
1077
+ }
807
1078
  });
808
1079
  },
809
- [state.chart, isLocked, isVisible, magnetMode]
1080
+ [state.chart, undoRedoListenerRef]
1081
+ );
1082
+ const selectTool = react.useCallback(
1083
+ (name) => {
1084
+ setActiveTool(name);
1085
+ createOverlayForTool(name);
1086
+ },
1087
+ [createOverlayForTool]
810
1088
  );
811
1089
  const clearActiveTool = react.useCallback(() => {
812
1090
  setActiveTool(null);
@@ -854,21 +1132,37 @@ function useDrawingTools() {
854
1132
  }
855
1133
  }, [state.chart, isVisible]);
856
1134
  const removeAllDrawings = react.useCallback(() => {
1135
+ const overlays2 = state.chart?.getOverlays({ groupId: DRAWING_GROUP_ID });
1136
+ if (overlays2 && overlays2.length > 0) {
1137
+ const snapshot = overlays2.map((o) => ({
1138
+ name: o.name,
1139
+ id: o.id,
1140
+ points: o.points,
1141
+ styles: o.styles,
1142
+ extendData: o.extendData
1143
+ }));
1144
+ undoRedoListenerRef.current?.({
1145
+ type: "overlays_removed",
1146
+ data: { overlays: snapshot }
1147
+ });
1148
+ }
857
1149
  state.chart?.removeOverlay({ groupId: DRAWING_GROUP_ID });
858
1150
  setActiveTool(null);
859
- }, [state.chart]);
1151
+ }, [state.chart, undoRedoListenerRef]);
860
1152
  return {
861
1153
  categories,
862
1154
  activeTool,
863
1155
  magnetMode,
864
1156
  isLocked,
865
1157
  isVisible,
1158
+ autoRetrigger,
866
1159
  selectTool,
867
1160
  clearActiveTool,
868
1161
  setMagnetMode,
869
1162
  toggleLock,
870
1163
  toggleVisibility,
871
- removeAllDrawings
1164
+ removeAllDrawings,
1165
+ setAutoRetrigger
872
1166
  };
873
1167
  }
874
1168
 
@@ -1260,12 +1554,12 @@ function useOrderLines() {
1260
1554
  if (onPriceChange) {
1261
1555
  callbacksRef.current.set(id, onPriceChange);
1262
1556
  }
1263
- const dataCount = state.chart.getDataList().length;
1264
- const anchorIndex = dataCount > 0 ? dataCount - 1 : 0;
1557
+ const dataList = state.chart.getDataList();
1558
+ const anchorTimestamp = dataList.length > 0 ? dataList[dataList.length - 1].timestamp : Date.now();
1265
1559
  state.chart.createOverlay({
1266
1560
  name: "orderLine",
1267
1561
  id,
1268
- points: [{ dataIndex: anchorIndex, value: price }],
1562
+ points: [{ timestamp: anchorTimestamp, value: price }],
1269
1563
  extendData,
1270
1564
  lock: !draggable,
1271
1565
  mode: "normal",
@@ -1289,11 +1583,11 @@ function useOrderLines() {
1289
1583
  if (onPriceChange !== void 0) {
1290
1584
  callbacksRef.current.set(id, onPriceChange);
1291
1585
  }
1292
- const dataCount = state.chart.getDataList().length;
1293
- const anchorIndex = dataCount > 0 ? dataCount - 1 : 0;
1586
+ const dataList = state.chart.getDataList();
1587
+ const anchorTimestamp = dataList.length > 0 ? dataList[dataList.length - 1].timestamp : Date.now();
1294
1588
  state.chart.overrideOverlay({
1295
1589
  id,
1296
- ...price != null ? { points: [{ dataIndex: anchorIndex, value: price }] } : {},
1590
+ ...price != null ? { points: [{ timestamp: anchorTimestamp, value: price }] } : {},
1297
1591
  ...draggable != null ? { lock: !draggable } : {},
1298
1592
  extendData: {
1299
1593
  ...existing.extendData,
@@ -1321,6 +1615,1496 @@ function useOrderLines() {
1321
1615
  removeAllOrderLines
1322
1616
  };
1323
1617
  }
1618
+ var DRAWING_GROUP_ID2 = "drawing_tools";
1619
+ function useUndoRedo() {
1620
+ const { state, dispatch } = useKlinechartsUI();
1621
+ const { undoRedoListenerRef } = useKlinechartsUIDispatch();
1622
+ const [undoStack, setUndoStack] = react.useState([]);
1623
+ const [redoStack, setRedoStack] = react.useState([]);
1624
+ const isProcessingRef = react.useRef(false);
1625
+ const canUndo = undoStack.length > 0;
1626
+ const canRedo = redoStack.length > 0;
1627
+ const pushAction = react.useCallback((action) => {
1628
+ if (isProcessingRef.current) return;
1629
+ setUndoStack((prev) => [...prev, action]);
1630
+ setRedoStack([]);
1631
+ }, []);
1632
+ react.useEffect(() => {
1633
+ undoRedoListenerRef.current = pushAction;
1634
+ return () => {
1635
+ undoRedoListenerRef.current = null;
1636
+ };
1637
+ }, [pushAction, undoRedoListenerRef]);
1638
+ const clear = react.useCallback(() => {
1639
+ setUndoStack([]);
1640
+ setRedoStack([]);
1641
+ }, []);
1642
+ const undo = react.useCallback(() => {
1643
+ if (undoStack.length === 0 || !state.chart || isProcessingRef.current)
1644
+ return;
1645
+ isProcessingRef.current = true;
1646
+ const action = undoStack[undoStack.length - 1];
1647
+ setUndoStack((prev) => prev.slice(0, -1));
1648
+ try {
1649
+ switch (action.type) {
1650
+ case "overlay_added": {
1651
+ const { id, overlayData } = action.data;
1652
+ state.chart.removeOverlay({ id });
1653
+ setRedoStack((prev) => [
1654
+ ...prev,
1655
+ {
1656
+ type: "overlay_added",
1657
+ data: { id, overlayData }
1658
+ }
1659
+ ]);
1660
+ break;
1661
+ }
1662
+ case "overlays_removed": {
1663
+ const { overlays: overlays2 } = action.data;
1664
+ const restoredIds = [];
1665
+ for (const overlay of overlays2) {
1666
+ const newId = state.chart.createOverlay({
1667
+ ...overlay,
1668
+ groupId: DRAWING_GROUP_ID2
1669
+ });
1670
+ if (typeof newId === "string") {
1671
+ restoredIds.push(newId);
1672
+ }
1673
+ }
1674
+ setRedoStack((prev) => [
1675
+ ...prev,
1676
+ {
1677
+ type: "overlays_removed",
1678
+ data: {
1679
+ overlays: overlays2.map((o, i) => ({
1680
+ ...o,
1681
+ id: restoredIds[i]
1682
+ }))
1683
+ }
1684
+ }
1685
+ ]);
1686
+ break;
1687
+ }
1688
+ case "indicator_toggled": {
1689
+ const { name, wasActive, isMain, paneId } = action.data;
1690
+ if (wasActive) {
1691
+ if (isMain) {
1692
+ state.chart.createIndicator(
1693
+ { name, id: `main_${name}`, paneId: "candle_pane" },
1694
+ true,
1695
+ { id: "candle_pane" }
1696
+ );
1697
+ dispatch({
1698
+ type: "SET_MAIN_INDICATORS",
1699
+ indicators: [...state.mainIndicators, name]
1700
+ });
1701
+ } else {
1702
+ state.chart.createIndicator({ name, id: `sub_${name}` });
1703
+ const newPaneId = state.chart.getIndicators({ id: `sub_${name}` })?.[0]?.paneId ?? "";
1704
+ dispatch({
1705
+ type: "SET_SUB_INDICATORS",
1706
+ indicators: {
1707
+ ...state.subIndicators,
1708
+ [name]: newPaneId
1709
+ }
1710
+ });
1711
+ }
1712
+ } else {
1713
+ if (isMain) {
1714
+ state.chart.removeIndicator({ id: `main_${name}` });
1715
+ dispatch({
1716
+ type: "SET_MAIN_INDICATORS",
1717
+ indicators: state.mainIndicators.filter(
1718
+ (n) => n !== name
1719
+ )
1720
+ });
1721
+ } else {
1722
+ state.chart.removeIndicator({ id: `sub_${name}` });
1723
+ const newSub = { ...state.subIndicators };
1724
+ delete newSub[name];
1725
+ dispatch({
1726
+ type: "SET_SUB_INDICATORS",
1727
+ indicators: newSub
1728
+ });
1729
+ }
1730
+ }
1731
+ setRedoStack((prev) => [
1732
+ ...prev,
1733
+ {
1734
+ type: "indicator_toggled",
1735
+ data: {
1736
+ name,
1737
+ wasActive: !wasActive,
1738
+ isMain,
1739
+ paneId
1740
+ }
1741
+ }
1742
+ ]);
1743
+ break;
1744
+ }
1745
+ }
1746
+ } finally {
1747
+ isProcessingRef.current = false;
1748
+ }
1749
+ }, [undoStack, state.chart, state.mainIndicators, state.subIndicators, dispatch]);
1750
+ const redo = react.useCallback(() => {
1751
+ if (redoStack.length === 0 || !state.chart || isProcessingRef.current)
1752
+ return;
1753
+ isProcessingRef.current = true;
1754
+ const action = redoStack[redoStack.length - 1];
1755
+ setRedoStack((prev) => prev.slice(0, -1));
1756
+ try {
1757
+ switch (action.type) {
1758
+ case "overlay_added": {
1759
+ const { overlayData } = action.data;
1760
+ const newId = state.chart.createOverlay({
1761
+ ...overlayData,
1762
+ groupId: DRAWING_GROUP_ID2
1763
+ });
1764
+ setUndoStack((prev) => [
1765
+ ...prev,
1766
+ {
1767
+ type: "overlay_added",
1768
+ data: {
1769
+ id: typeof newId === "string" ? newId : action.data.id,
1770
+ overlayData
1771
+ }
1772
+ }
1773
+ ]);
1774
+ break;
1775
+ }
1776
+ case "overlays_removed": {
1777
+ const { overlays: overlays2 } = action.data;
1778
+ const snapshotOverlays = overlays2.map((o) => {
1779
+ const allOverlays = state.chart?.getOverlays({ id: o.id });
1780
+ const actual = allOverlays?.[0];
1781
+ return actual ? {
1782
+ name: actual.name,
1783
+ points: actual.points,
1784
+ styles: actual.styles,
1785
+ extendData: actual.extendData
1786
+ } : o;
1787
+ });
1788
+ for (const overlay of overlays2) {
1789
+ state.chart.removeOverlay({ id: overlay.id });
1790
+ }
1791
+ setUndoStack((prev) => [
1792
+ ...prev,
1793
+ {
1794
+ type: "overlays_removed",
1795
+ data: { overlays: snapshotOverlays }
1796
+ }
1797
+ ]);
1798
+ break;
1799
+ }
1800
+ case "indicator_toggled": {
1801
+ const { name, wasActive, isMain } = action.data;
1802
+ if (wasActive) {
1803
+ if (isMain) {
1804
+ state.chart.createIndicator(
1805
+ { name, id: `main_${name}`, paneId: "candle_pane" },
1806
+ true,
1807
+ { id: "candle_pane" }
1808
+ );
1809
+ dispatch({
1810
+ type: "SET_MAIN_INDICATORS",
1811
+ indicators: [...state.mainIndicators, name]
1812
+ });
1813
+ } else {
1814
+ state.chart.createIndicator({ name, id: `sub_${name}` });
1815
+ const newPaneId = state.chart.getIndicators({ id: `sub_${name}` })?.[0]?.paneId ?? "";
1816
+ dispatch({
1817
+ type: "SET_SUB_INDICATORS",
1818
+ indicators: {
1819
+ ...state.subIndicators,
1820
+ [name]: newPaneId
1821
+ }
1822
+ });
1823
+ }
1824
+ } else {
1825
+ if (isMain) {
1826
+ state.chart.removeIndicator({ id: `main_${name}` });
1827
+ dispatch({
1828
+ type: "SET_MAIN_INDICATORS",
1829
+ indicators: state.mainIndicators.filter(
1830
+ (n) => n !== name
1831
+ )
1832
+ });
1833
+ } else {
1834
+ state.chart.removeIndicator({ id: `sub_${name}` });
1835
+ const newSub = { ...state.subIndicators };
1836
+ delete newSub[name];
1837
+ dispatch({
1838
+ type: "SET_SUB_INDICATORS",
1839
+ indicators: newSub
1840
+ });
1841
+ }
1842
+ }
1843
+ setUndoStack((prev) => [
1844
+ ...prev,
1845
+ {
1846
+ type: "indicator_toggled",
1847
+ data: {
1848
+ name,
1849
+ wasActive: !wasActive,
1850
+ isMain
1851
+ }
1852
+ }
1853
+ ]);
1854
+ break;
1855
+ }
1856
+ }
1857
+ } finally {
1858
+ isProcessingRef.current = false;
1859
+ }
1860
+ }, [redoStack, state.chart, state.mainIndicators, state.subIndicators, dispatch]);
1861
+ react.useEffect(() => {
1862
+ const handleKeyDown = (e) => {
1863
+ const isCtrlOrMeta = e.ctrlKey || e.metaKey;
1864
+ if (!isCtrlOrMeta) return;
1865
+ if (e.key === "z" && !e.shiftKey) {
1866
+ e.preventDefault();
1867
+ undo();
1868
+ } else if (e.key === "y" || e.key === "z" && e.shiftKey) {
1869
+ e.preventDefault();
1870
+ redo();
1871
+ }
1872
+ };
1873
+ window.addEventListener("keydown", handleKeyDown);
1874
+ return () => window.removeEventListener("keydown", handleKeyDown);
1875
+ }, [undo, redo]);
1876
+ return {
1877
+ canUndo,
1878
+ canRedo,
1879
+ undo,
1880
+ redo,
1881
+ pushAction,
1882
+ clear
1883
+ };
1884
+ }
1885
+ var STORAGE_KEY_PREFIX = "klinecharts_layout:";
1886
+ var INDEX_KEY = "klinecharts_layout_index";
1887
+ var STATE_VERSION = "1.0";
1888
+ function generateId() {
1889
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
1890
+ /[xy]/g,
1891
+ (c) => {
1892
+ const r = Math.random() * 16 | 0;
1893
+ return (c === "x" ? r : r & 3 | 8).toString(16);
1894
+ }
1895
+ );
1896
+ }
1897
+ function getLayoutIds() {
1898
+ try {
1899
+ const raw = localStorage.getItem(INDEX_KEY);
1900
+ return raw ? JSON.parse(raw) : [];
1901
+ } catch {
1902
+ return [];
1903
+ }
1904
+ }
1905
+ function getEntry(id) {
1906
+ try {
1907
+ const raw = localStorage.getItem(STORAGE_KEY_PREFIX + id);
1908
+ return raw ? JSON.parse(raw) : null;
1909
+ } catch {
1910
+ return null;
1911
+ }
1912
+ }
1913
+ function useLayoutManager() {
1914
+ const { state, dispatch } = useKlinechartsUI();
1915
+ const [layouts, setLayouts] = react.useState([]);
1916
+ const [autoSaveEnabled, setAutoSaveEnabled] = react.useState(false);
1917
+ const autoSaveTimerRef = react.useRef(null);
1918
+ const autoSaveIdRef = react.useRef(null);
1919
+ const refreshLayouts = react.useCallback(() => {
1920
+ const entries = getLayoutIds().map((id) => getEntry(id)).filter((e) => e !== null);
1921
+ setLayouts(entries);
1922
+ }, []);
1923
+ react.useEffect(() => {
1924
+ refreshLayouts();
1925
+ }, [refreshLayouts]);
1926
+ const serializeState = react.useCallback(() => {
1927
+ const chart = state.chart;
1928
+ if (!chart) return null;
1929
+ const indicators2 = [];
1930
+ const indicatorMap = chart.getIndicatorByPaneId?.();
1931
+ if (indicatorMap) {
1932
+ indicatorMap.forEach((paneIndicators, paneId) => {
1933
+ paneIndicators.forEach((indicator) => {
1934
+ indicators2.push({
1935
+ paneId,
1936
+ name: indicator.name,
1937
+ calcParams: indicator.calcParams,
1938
+ visible: indicator.visible,
1939
+ ...indicator.styles ? { styles: indicator.styles } : {}
1940
+ });
1941
+ });
1942
+ });
1943
+ }
1944
+ const drawings = [];
1945
+ const allOverlays = chart.getOverlays({});
1946
+ if (allOverlays) {
1947
+ for (const overlay of allOverlays) {
1948
+ drawings.push({
1949
+ name: overlay.name,
1950
+ points: overlay.points,
1951
+ styles: overlay.styles,
1952
+ extendData: overlay.extendData
1953
+ });
1954
+ }
1955
+ }
1956
+ return {
1957
+ version: STATE_VERSION,
1958
+ meta: {
1959
+ symbol: state.symbol?.ticker ?? "",
1960
+ period: state.period?.label ?? "",
1961
+ timestamp: Date.now(),
1962
+ lastModified: Date.now()
1963
+ },
1964
+ indicators: indicators2,
1965
+ drawings
1966
+ };
1967
+ }, [state.chart, state.symbol, state.period]);
1968
+ const saveLayout = react.useCallback(
1969
+ (name) => {
1970
+ const chartState = serializeState();
1971
+ if (!chartState) return null;
1972
+ const id = generateId();
1973
+ const now = Date.now();
1974
+ const entry = {
1975
+ id,
1976
+ name: name.trim() || `Layout ${new Date(now).toLocaleString()}`,
1977
+ symbol: chartState.meta.symbol,
1978
+ period: chartState.meta.period,
1979
+ timestamp: now,
1980
+ lastModified: now,
1981
+ state: chartState
1982
+ };
1983
+ localStorage.setItem(
1984
+ STORAGE_KEY_PREFIX + id,
1985
+ JSON.stringify(entry)
1986
+ );
1987
+ const ids = getLayoutIds();
1988
+ if (!ids.includes(id)) {
1989
+ ids.push(id);
1990
+ localStorage.setItem(INDEX_KEY, JSON.stringify(ids));
1991
+ }
1992
+ refreshLayouts();
1993
+ return id;
1994
+ },
1995
+ [serializeState, refreshLayouts]
1996
+ );
1997
+ const loadLayout = react.useCallback(
1998
+ (id) => {
1999
+ const entry = getEntry(id);
2000
+ if (!entry || !state.chart) return false;
2001
+ const chartState = entry.state;
2002
+ if (chartState.version !== STATE_VERSION) return false;
2003
+ const chart = state.chart;
2004
+ chart.removeOverlay();
2005
+ const indicatorMap = chart.getIndicatorByPaneId?.();
2006
+ if (indicatorMap) {
2007
+ indicatorMap.forEach((_paneIndicators, paneId) => {
2008
+ _paneIndicators.forEach((_, indicatorName) => {
2009
+ chart.removeIndicator({ paneId, name: indicatorName });
2010
+ });
2011
+ });
2012
+ }
2013
+ const newMainIndicators = [];
2014
+ const newSubIndicators = {};
2015
+ if (chartState.indicators) {
2016
+ for (const ind of chartState.indicators) {
2017
+ chart.createIndicator(
2018
+ {
2019
+ name: ind.name,
2020
+ calcParams: ind.calcParams,
2021
+ visible: ind.visible
2022
+ },
2023
+ ind.paneId !== "candle_pane",
2024
+ { id: ind.paneId }
2025
+ );
2026
+ if (ind.styles) {
2027
+ chart.overrideIndicator({
2028
+ name: ind.name,
2029
+ paneId: ind.paneId,
2030
+ styles: ind.styles
2031
+ });
2032
+ }
2033
+ if (ind.paneId === "candle_pane") {
2034
+ newMainIndicators.push(ind.name);
2035
+ } else {
2036
+ newSubIndicators[ind.name] = ind.paneId;
2037
+ }
2038
+ }
2039
+ }
2040
+ dispatch({
2041
+ type: "SET_MAIN_INDICATORS",
2042
+ indicators: newMainIndicators
2043
+ });
2044
+ dispatch({
2045
+ type: "SET_SUB_INDICATORS",
2046
+ indicators: newSubIndicators
2047
+ });
2048
+ if (chartState.drawings) {
2049
+ for (const drawing of chartState.drawings) {
2050
+ chart.createOverlay({
2051
+ name: drawing.name,
2052
+ points: drawing.points,
2053
+ styles: drawing.styles,
2054
+ extendData: drawing.extendData
2055
+ });
2056
+ }
2057
+ }
2058
+ return true;
2059
+ },
2060
+ [state.chart, dispatch]
2061
+ );
2062
+ const deleteLayout = react.useCallback(
2063
+ (id) => {
2064
+ localStorage.removeItem(STORAGE_KEY_PREFIX + id);
2065
+ const ids = getLayoutIds().filter((i) => i !== id);
2066
+ localStorage.setItem(INDEX_KEY, JSON.stringify(ids));
2067
+ refreshLayouts();
2068
+ },
2069
+ [refreshLayouts]
2070
+ );
2071
+ const renameLayout = react.useCallback(
2072
+ (id, name) => {
2073
+ const entry = getEntry(id);
2074
+ if (!entry) return false;
2075
+ const updated = {
2076
+ ...entry,
2077
+ name: name.trim(),
2078
+ lastModified: Date.now()
2079
+ };
2080
+ localStorage.setItem(
2081
+ STORAGE_KEY_PREFIX + id,
2082
+ JSON.stringify(updated)
2083
+ );
2084
+ refreshLayouts();
2085
+ return true;
2086
+ },
2087
+ [refreshLayouts]
2088
+ );
2089
+ react.useEffect(() => {
2090
+ if (!autoSaveEnabled || !state.chart) return;
2091
+ if (autoSaveTimerRef.current) {
2092
+ clearTimeout(autoSaveTimerRef.current);
2093
+ }
2094
+ autoSaveTimerRef.current = setTimeout(() => {
2095
+ const chartState = serializeState();
2096
+ if (!chartState) return;
2097
+ if (autoSaveIdRef.current) {
2098
+ const entry = getEntry(autoSaveIdRef.current);
2099
+ if (entry) {
2100
+ const updated = {
2101
+ ...entry,
2102
+ lastModified: Date.now(),
2103
+ state: chartState
2104
+ };
2105
+ localStorage.setItem(
2106
+ STORAGE_KEY_PREFIX + autoSaveIdRef.current,
2107
+ JSON.stringify(updated)
2108
+ );
2109
+ refreshLayouts();
2110
+ }
2111
+ } else {
2112
+ const id = generateId();
2113
+ const now = Date.now();
2114
+ const entry = {
2115
+ id,
2116
+ name: "Auto-save",
2117
+ symbol: chartState.meta.symbol,
2118
+ period: chartState.meta.period,
2119
+ timestamp: now,
2120
+ lastModified: now,
2121
+ state: chartState
2122
+ };
2123
+ localStorage.setItem(
2124
+ STORAGE_KEY_PREFIX + id,
2125
+ JSON.stringify(entry)
2126
+ );
2127
+ const ids = getLayoutIds();
2128
+ ids.push(id);
2129
+ localStorage.setItem(INDEX_KEY, JSON.stringify(ids));
2130
+ autoSaveIdRef.current = id;
2131
+ refreshLayouts();
2132
+ }
2133
+ }, 5e3);
2134
+ return () => {
2135
+ if (autoSaveTimerRef.current) {
2136
+ clearTimeout(autoSaveTimerRef.current);
2137
+ }
2138
+ };
2139
+ }, [
2140
+ autoSaveEnabled,
2141
+ state.chart,
2142
+ state.mainIndicators,
2143
+ state.subIndicators,
2144
+ serializeState,
2145
+ refreshLayouts
2146
+ ]);
2147
+ return {
2148
+ layouts,
2149
+ saveLayout,
2150
+ loadLayout,
2151
+ deleteLayout,
2152
+ renameLayout,
2153
+ refreshLayouts,
2154
+ autoSaveEnabled,
2155
+ setAutoSaveEnabled
2156
+ };
2157
+ }
2158
+ var DEFAULT_SCRIPT = `// Custom Indicator \u2014 Pine Script Style
2159
+ // Available: TA, dataList, params
2160
+
2161
+ const period = params[0] ?? 14
2162
+ const closes = dataList.map(d => d.close)
2163
+ const highs = dataList.map(d => d.high)
2164
+ const lows = dataList.map(d => d.low)
2165
+
2166
+ // Example: RSI + Bollinger Bands
2167
+ const rsi = TA.rsi(closes, period)
2168
+ const boll = TA.bollinger(closes, period, 2)
2169
+
2170
+ // Return one object per candle \u2014 each key = one line on chart
2171
+ return rsi.map((v, i) => ({
2172
+ rsi: v,
2173
+ upper: boll.upper[i],
2174
+ mid: boll.mid[i],
2175
+ lower: boll.lower[i],
2176
+ }))`;
2177
+ var SERIES_COLORS = [
2178
+ "#2962FF",
2179
+ "#FF6D00",
2180
+ "#00BCD4",
2181
+ "#E91E63",
2182
+ "#76FF03",
2183
+ "#FFD600"
2184
+ ];
2185
+ var SHADOW_KEYS = [
2186
+ "fetch",
2187
+ "XMLHttpRequest",
2188
+ "WebSocket",
2189
+ "Worker",
2190
+ "SharedWorker",
2191
+ "importScripts",
2192
+ "self",
2193
+ "caches",
2194
+ "indexedDB"
2195
+ ];
2196
+ var scriptCounter = 0;
2197
+ function useScriptEditor() {
2198
+ const { state } = useKlinechartsUI();
2199
+ const [code, setCode] = react.useState(DEFAULT_SCRIPT);
2200
+ const [scriptName, setScriptName] = react.useState("");
2201
+ const [params, setParams] = react.useState("14");
2202
+ const [placement, setPlacement] = react.useState("sub");
2203
+ const [error, setError] = react.useState("");
2204
+ const [status, setStatus] = react.useState("");
2205
+ const [isRunning, setIsRunning] = react.useState(false);
2206
+ const activeIdRef = react.useRef(null);
2207
+ const activeNameRef = react.useRef(null);
2208
+ const hasActiveScript = activeNameRef.current !== null;
2209
+ const runScript = react.useCallback(() => {
2210
+ const chart = state.chart;
2211
+ if (!chart) {
2212
+ setError("Chart not initialized");
2213
+ return;
2214
+ }
2215
+ setError("");
2216
+ setStatus("Running...");
2217
+ setIsRunning(true);
2218
+ try {
2219
+ const parsedParams = params.split(",").map((s) => parseFloat(s.trim())).filter((n) => !isNaN(n));
2220
+ const dataList = chart.getDataList?.() ?? [];
2221
+ const allArgs = [
2222
+ ...SHADOW_KEYS,
2223
+ "TA",
2224
+ "dataList",
2225
+ "params",
2226
+ `"use strict";
2227
+ ${code}`
2228
+ ];
2229
+ const fn = new Function(...allArgs);
2230
+ const shadowValues = SHADOW_KEYS.map(() => void 0);
2231
+ const result = fn(...shadowValues, chunkPIFLJKYF_cjs.TA_default, dataList, parsedParams);
2232
+ if (!Array.isArray(result)) {
2233
+ throw new Error("Script must return an Array.");
2234
+ }
2235
+ const sample = result.find((v) => v !== null && v !== void 0);
2236
+ const seriesKeys = sample ? Object.keys(sample) : ["value"];
2237
+ if (seriesKeys.length === 0) {
2238
+ throw new Error("Returned objects have no keys.");
2239
+ }
2240
+ scriptCounter++;
2241
+ const indicatorName = `_custom_script_${scriptCounter}`;
2242
+ const figures = seriesKeys.map((key, i) => ({
2243
+ key,
2244
+ title: `${key}: `,
2245
+ type: "line",
2246
+ styles: () => ({ color: SERIES_COLORS[i % SERIES_COLORS.length] })
2247
+ }));
2248
+ reactKlinecharts.registerIndicator({
2249
+ name: indicatorName,
2250
+ shortName: (scriptName.trim() || `Script #${scriptCounter}`) + (placement === "main" ? " (Main)" : " (Sub)"),
2251
+ calcParams: parsedParams,
2252
+ figures,
2253
+ calc: () => result,
2254
+ extendData: {
2255
+ isCustomScript: true,
2256
+ code,
2257
+ placement
2258
+ }
2259
+ });
2260
+ if (activeNameRef.current) {
2261
+ try {
2262
+ chart.removeIndicator({
2263
+ id: activeIdRef.current === "candle_pane" ? "candle_pane" : activeIdRef.current,
2264
+ name: activeNameRef.current
2265
+ });
2266
+ } catch {
2267
+ }
2268
+ }
2269
+ let paneId = null;
2270
+ if (placement === "main") {
2271
+ paneId = chart.createIndicator(
2272
+ { name: indicatorName },
2273
+ true,
2274
+ { id: "candle_pane" }
2275
+ );
2276
+ } else {
2277
+ paneId = chart.createIndicator({ name: indicatorName });
2278
+ }
2279
+ activeIdRef.current = typeof paneId === "string" ? paneId : null;
2280
+ activeNameRef.current = indicatorName;
2281
+ const title = scriptName.trim() || `Script #${scriptCounter}`;
2282
+ setStatus(
2283
+ `${title} applied to ${placement === "main" ? "Main Chart" : "Sub Pane"} \u2014 ${seriesKeys.length} series: ${seriesKeys.join(", ")}`
2284
+ );
2285
+ } catch (e) {
2286
+ setError(e?.message ?? String(e));
2287
+ } finally {
2288
+ setIsRunning(false);
2289
+ }
2290
+ }, [state.chart, code, scriptName, params, placement]);
2291
+ const removeScript = react.useCallback(() => {
2292
+ const chart = state.chart;
2293
+ if (chart && activeNameRef.current && activeIdRef.current) {
2294
+ try {
2295
+ chart.removeIndicator({
2296
+ id: activeIdRef.current,
2297
+ name: activeNameRef.current
2298
+ });
2299
+ } catch {
2300
+ }
2301
+ activeIdRef.current = null;
2302
+ activeNameRef.current = null;
2303
+ setStatus("Indicator removed.");
2304
+ }
2305
+ }, [state.chart]);
2306
+ const resetCode = react.useCallback(() => {
2307
+ setCode(DEFAULT_SCRIPT);
2308
+ setError("");
2309
+ setStatus("");
2310
+ }, []);
2311
+ const exportScript = react.useCallback(() => {
2312
+ const blob = new Blob([code], {
2313
+ type: "text/javascript;charset=utf-8"
2314
+ });
2315
+ const url = URL.createObjectURL(blob);
2316
+ const a = document.createElement("a");
2317
+ a.href = url;
2318
+ a.download = `${scriptName.trim() || "custom_indicator"}.js`;
2319
+ document.body.appendChild(a);
2320
+ a.click();
2321
+ document.body.removeChild(a);
2322
+ URL.revokeObjectURL(url);
2323
+ setStatus("Script exported.");
2324
+ }, [code, scriptName]);
2325
+ const importScript = react.useCallback(
2326
+ (file) => {
2327
+ const reader = new FileReader();
2328
+ reader.onload = (e) => {
2329
+ const content = e.target?.result;
2330
+ if (content) {
2331
+ setCode(content);
2332
+ if (!scriptName) {
2333
+ setScriptName(file.name.replace(/\.(js|ts|txt)$/i, ""));
2334
+ }
2335
+ setError("");
2336
+ setStatus(`Loaded ${file.name}`);
2337
+ }
2338
+ };
2339
+ reader.readAsText(file);
2340
+ },
2341
+ [scriptName]
2342
+ );
2343
+ return {
2344
+ code,
2345
+ setCode,
2346
+ scriptName,
2347
+ setScriptName,
2348
+ params,
2349
+ setParams,
2350
+ placement,
2351
+ setPlacement,
2352
+ error,
2353
+ status,
2354
+ isRunning,
2355
+ hasActiveScript,
2356
+ runScript,
2357
+ removeScript,
2358
+ resetCode,
2359
+ exportScript,
2360
+ importScript,
2361
+ defaultScript: DEFAULT_SCRIPT
2362
+ };
2363
+ }
2364
+ function useCrosshair() {
2365
+ const { state } = useKlinechartsUI();
2366
+ const [barData, setBarData] = react.useState(null);
2367
+ const rafRef = react.useRef(0);
2368
+ const handler = react.useCallback(
2369
+ (event) => {
2370
+ if (rafRef.current) {
2371
+ cancelAnimationFrame(rafRef.current);
2372
+ }
2373
+ rafRef.current = requestAnimationFrame(() => {
2374
+ rafRef.current = 0;
2375
+ const klineData = event?.kLineData;
2376
+ if (!klineData) {
2377
+ setBarData(null);
2378
+ return;
2379
+ }
2380
+ const pricePrecision = state.symbol?.pricePrecision ?? 2;
2381
+ const open = klineData.open ?? 0;
2382
+ const close = klineData.close ?? 0;
2383
+ const change = parseFloat((close - open).toFixed(pricePrecision));
2384
+ const changePercent = open !== 0 ? parseFloat((change / open * 100).toFixed(2)) : 0;
2385
+ setBarData({
2386
+ open,
2387
+ high: klineData.high ?? 0,
2388
+ low: klineData.low ?? 0,
2389
+ close,
2390
+ volume: klineData.volume ?? 0,
2391
+ timestamp: klineData.timestamp ?? 0,
2392
+ change,
2393
+ changePercent
2394
+ });
2395
+ });
2396
+ },
2397
+ [state.symbol?.pricePrecision]
2398
+ );
2399
+ react.useEffect(() => {
2400
+ const chart = state.chart;
2401
+ if (!chart) return;
2402
+ chart.subscribeAction?.("onCrosshairChange", handler);
2403
+ return () => {
2404
+ if (rafRef.current) {
2405
+ cancelAnimationFrame(rafRef.current);
2406
+ rafRef.current = 0;
2407
+ }
2408
+ chart.unsubscribeAction?.("onCrosshairChange", handler);
2409
+ };
2410
+ }, [state.chart, handler]);
2411
+ return { barData };
2412
+ }
2413
+ var alertCounter = 0;
2414
+ function useAlerts() {
2415
+ const { state } = useKlinechartsUI();
2416
+ const [alerts, setAlerts] = react.useState([]);
2417
+ const callbackRef = react.useRef(null);
2418
+ const prevCloseRef = react.useRef(null);
2419
+ const addAlert = react.useCallback(
2420
+ (price, condition, message) => {
2421
+ const id = `alert_${++alertCounter}`;
2422
+ const alert = {
2423
+ id,
2424
+ price,
2425
+ condition,
2426
+ message,
2427
+ triggered: false
2428
+ };
2429
+ setAlerts((prev) => [...prev, alert]);
2430
+ if (state.chart) {
2431
+ state.chart.createOverlay({
2432
+ name: "horizontalStraightLine",
2433
+ id,
2434
+ groupId: "price_alerts",
2435
+ points: [{ value: price }],
2436
+ styles: {
2437
+ line: {
2438
+ style: "dashed",
2439
+ color: "#ff9800",
2440
+ size: 1
2441
+ }
2442
+ },
2443
+ lock: true
2444
+ });
2445
+ }
2446
+ return id;
2447
+ },
2448
+ [state.chart]
2449
+ );
2450
+ const removeAlert = react.useCallback(
2451
+ (id) => {
2452
+ setAlerts((prev) => prev.filter((a) => a.id !== id));
2453
+ state.chart?.removeOverlay({ id });
2454
+ },
2455
+ [state.chart]
2456
+ );
2457
+ const clearAlerts = react.useCallback(() => {
2458
+ setAlerts((prev) => {
2459
+ for (const alert of prev) {
2460
+ state.chart?.removeOverlay({ id: alert.id });
2461
+ }
2462
+ return [];
2463
+ });
2464
+ }, [state.chart]);
2465
+ const onAlertTriggered = react.useCallback(
2466
+ (callback) => {
2467
+ callbackRef.current = callback;
2468
+ },
2469
+ []
2470
+ );
2471
+ react.useEffect(() => {
2472
+ if (!state.chart) return;
2473
+ const interval = setInterval(() => {
2474
+ const dataList = state.chart?.getDataList();
2475
+ if (!dataList || dataList.length === 0) return;
2476
+ const lastBar = dataList[dataList.length - 1];
2477
+ const currentClose = lastBar.close;
2478
+ const prevClose = prevCloseRef.current;
2479
+ if (prevClose === null) {
2480
+ prevCloseRef.current = currentClose;
2481
+ return;
2482
+ }
2483
+ prevCloseRef.current = currentClose;
2484
+ setAlerts((prev) => {
2485
+ let changed = false;
2486
+ const next = prev.map((alert) => {
2487
+ if (alert.triggered) return alert;
2488
+ let shouldTrigger = false;
2489
+ if (alert.condition === "crossing_up") {
2490
+ shouldTrigger = prevClose < alert.price && currentClose >= alert.price;
2491
+ } else if (alert.condition === "crossing_down") {
2492
+ shouldTrigger = prevClose > alert.price && currentClose <= alert.price;
2493
+ } else if (alert.condition === "crossing") {
2494
+ shouldTrigger = prevClose < alert.price && currentClose >= alert.price || prevClose > alert.price && currentClose <= alert.price;
2495
+ }
2496
+ if (shouldTrigger) {
2497
+ changed = true;
2498
+ const triggered = { ...alert, triggered: true };
2499
+ callbackRef.current?.(triggered);
2500
+ return triggered;
2501
+ }
2502
+ return alert;
2503
+ });
2504
+ return changed ? next : prev;
2505
+ });
2506
+ }, 1e3);
2507
+ return () => clearInterval(interval);
2508
+ }, [state.chart]);
2509
+ return {
2510
+ alerts,
2511
+ addAlert,
2512
+ removeAlert,
2513
+ clearAlerts,
2514
+ onAlertTriggered
2515
+ };
2516
+ }
2517
+ function useDataExport() {
2518
+ const { state } = useKlinechartsUI();
2519
+ const exportAll = react.useCallback(
2520
+ (format) => {
2521
+ if (!state.chart) return;
2522
+ const dataList = state.chart.getDataList();
2523
+ if (!dataList || dataList.length === 0) return;
2524
+ downloadData(dataList, format, state.symbol?.ticker);
2525
+ },
2526
+ [state.chart, state.symbol?.ticker]
2527
+ );
2528
+ const exportVisible = react.useCallback(
2529
+ (format) => {
2530
+ if (!state.chart) return;
2531
+ const dataList = state.chart.getDataList();
2532
+ if (!dataList || dataList.length === 0) return;
2533
+ const visibleRange = state.chart.getVisibleRange?.();
2534
+ if (!visibleRange) {
2535
+ downloadData(dataList, format, state.symbol?.ticker);
2536
+ return;
2537
+ }
2538
+ const { from, to } = visibleRange;
2539
+ const visibleData = dataList.slice(from, to);
2540
+ downloadData(visibleData, format, state.symbol?.ticker);
2541
+ },
2542
+ [state.chart, state.symbol?.ticker]
2543
+ );
2544
+ return { exportAll, exportVisible };
2545
+ }
2546
+ function formatRow(bar) {
2547
+ return {
2548
+ date: new Date(bar.timestamp).toISOString(),
2549
+ open: bar.open,
2550
+ high: bar.high,
2551
+ low: bar.low,
2552
+ close: bar.close,
2553
+ volume: bar.volume ?? 0
2554
+ };
2555
+ }
2556
+ function buildCsv(dataList) {
2557
+ const header = "Date,Open,High,Low,Close,Volume\n";
2558
+ const rows = dataList.map((bar) => {
2559
+ const r = formatRow(bar);
2560
+ return `${r.date},${r.open},${r.high},${r.low},${r.close},${r.volume}`;
2561
+ });
2562
+ return header + rows.join("\n");
2563
+ }
2564
+ function buildJson(dataList) {
2565
+ return JSON.stringify(dataList.map(formatRow), null, 2);
2566
+ }
2567
+ function downloadData(dataList, format, ticker) {
2568
+ const content = format === "csv" ? buildCsv(dataList) : buildJson(dataList);
2569
+ const mimeType = format === "csv" ? "text/csv" : "application/json";
2570
+ const blob = new Blob([content], { type: mimeType });
2571
+ const url = URL.createObjectURL(blob);
2572
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2573
+ const prefix = ticker ?? "chart";
2574
+ const filename = `${prefix}_${date}.${format}`;
2575
+ const link = document.createElement("a");
2576
+ link.href = url;
2577
+ link.download = filename;
2578
+ document.body.appendChild(link);
2579
+ link.click();
2580
+ document.body.removeChild(link);
2581
+ URL.revokeObjectURL(url);
2582
+ }
2583
+ function useWatchlist() {
2584
+ const { state } = useKlinechartsUI();
2585
+ const { dispatch, datafeed } = useKlinechartsUIDispatch();
2586
+ const [items, setItems] = react.useState([]);
2587
+ const subscriptionsRef = react.useRef(/* @__PURE__ */ new Map());
2588
+ const activeSymbol = state.symbol?.ticker ?? null;
2589
+ const addSymbol = react.useCallback(
2590
+ (ticker) => {
2591
+ setItems((prev) => {
2592
+ if (prev.some((item) => item.ticker === ticker)) return prev;
2593
+ return [
2594
+ ...prev,
2595
+ { ticker, lastPrice: null, change: null, changePercent: null }
2596
+ ];
2597
+ });
2598
+ if (subscriptionsRef.current.has(ticker)) return;
2599
+ const symbolInfo = { ticker };
2600
+ const period = state.period;
2601
+ subscriptionsRef.current.set(ticker, { symbolInfo, period });
2602
+ datafeed.subscribe(symbolInfo, period, (bar) => {
2603
+ setItems(
2604
+ (prev) => prev.map((item) => {
2605
+ if (item.ticker !== ticker) return item;
2606
+ const change = bar.close - bar.open;
2607
+ const changePercent = bar.open !== 0 ? change / bar.open * 100 : null;
2608
+ return {
2609
+ ...item,
2610
+ lastPrice: bar.close,
2611
+ change,
2612
+ changePercent
2613
+ };
2614
+ })
2615
+ );
2616
+ });
2617
+ },
2618
+ [datafeed, state.period]
2619
+ );
2620
+ const removeSymbol = react.useCallback(
2621
+ (ticker) => {
2622
+ const sub = subscriptionsRef.current.get(ticker);
2623
+ if (sub) {
2624
+ datafeed.unsubscribe(sub.symbolInfo, sub.period);
2625
+ subscriptionsRef.current.delete(ticker);
2626
+ }
2627
+ setItems((prev) => prev.filter((item) => item.ticker !== ticker));
2628
+ },
2629
+ [datafeed]
2630
+ );
2631
+ const switchSymbol = react.useCallback(
2632
+ (ticker) => {
2633
+ dispatch({ type: "SET_SYMBOL", symbol: { ticker } });
2634
+ },
2635
+ [dispatch]
2636
+ );
2637
+ react.useEffect(() => {
2638
+ return () => {
2639
+ subscriptionsRef.current.forEach((sub) => {
2640
+ datafeed.unsubscribe(sub.symbolInfo, sub.period);
2641
+ });
2642
+ subscriptionsRef.current.clear();
2643
+ };
2644
+ }, [datafeed]);
2645
+ return {
2646
+ items,
2647
+ addSymbol,
2648
+ removeSymbol,
2649
+ switchSymbol,
2650
+ activeSymbol
2651
+ };
2652
+ }
2653
+ function useReplay() {
2654
+ const { state } = useKlinechartsUI();
2655
+ const [isReplaying, setIsReplaying] = react.useState(false);
2656
+ const [isPaused, setIsPaused] = react.useState(false);
2657
+ const [speed, setSpeedState] = react.useState(1);
2658
+ const [barIndex, setBarIndex] = react.useState(0);
2659
+ const [totalBars, setTotalBars] = react.useState(0);
2660
+ const intervalRef = react.useRef(null);
2661
+ const savedDataRef = react.useRef([]);
2662
+ const currentIndexRef = react.useRef(0);
2663
+ const clearInterval_ = react.useCallback(() => {
2664
+ if (intervalRef.current !== null) {
2665
+ clearInterval(intervalRef.current);
2666
+ intervalRef.current = null;
2667
+ }
2668
+ }, []);
2669
+ const addNextBar = react.useCallback(() => {
2670
+ if (!state.chart) return;
2671
+ const data = savedDataRef.current;
2672
+ const idx = currentIndexRef.current;
2673
+ if (idx >= data.length) {
2674
+ clearInterval_();
2675
+ setIsPaused(true);
2676
+ return;
2677
+ }
2678
+ const bar = data[idx];
2679
+ state.chart.updateData?.(bar);
2680
+ currentIndexRef.current = idx + 1;
2681
+ setBarIndex(idx + 1);
2682
+ }, [state.chart, clearInterval_]);
2683
+ const startInterval = react.useCallback(
2684
+ (currentSpeed) => {
2685
+ clearInterval_();
2686
+ intervalRef.current = setInterval(addNextBar, 1e3 / currentSpeed);
2687
+ },
2688
+ [addNextBar, clearInterval_]
2689
+ );
2690
+ const startReplay = react.useCallback(() => {
2691
+ if (!state.chart) return;
2692
+ const dataList = state.chart.getDataList();
2693
+ if (!dataList || dataList.length === 0) return;
2694
+ savedDataRef.current = [...dataList];
2695
+ currentIndexRef.current = 0;
2696
+ setTotalBars(dataList.length);
2697
+ setBarIndex(0);
2698
+ setIsReplaying(true);
2699
+ setIsPaused(false);
2700
+ state.chart.clearData?.();
2701
+ startInterval(speed);
2702
+ }, [state.chart, speed, startInterval]);
2703
+ const stopReplay = react.useCallback(() => {
2704
+ if (!state.chart) return;
2705
+ clearInterval_();
2706
+ const data = savedDataRef.current;
2707
+ if (data.length > 0) {
2708
+ state.chart.clearData?.();
2709
+ for (const bar of data) {
2710
+ state.chart.updateData?.(bar);
2711
+ }
2712
+ }
2713
+ savedDataRef.current = [];
2714
+ currentIndexRef.current = 0;
2715
+ setIsReplaying(false);
2716
+ setIsPaused(false);
2717
+ setBarIndex(0);
2718
+ setTotalBars(0);
2719
+ }, [state.chart, clearInterval_]);
2720
+ const togglePause = react.useCallback(() => {
2721
+ if (!isReplaying) return;
2722
+ if (isPaused) {
2723
+ startInterval(speed);
2724
+ setIsPaused(false);
2725
+ } else {
2726
+ clearInterval_();
2727
+ setIsPaused(true);
2728
+ }
2729
+ }, [isReplaying, isPaused, speed, startInterval, clearInterval_]);
2730
+ const stepForward = react.useCallback(() => {
2731
+ if (!isReplaying || !isPaused) return;
2732
+ addNextBar();
2733
+ }, [isReplaying, isPaused, addNextBar]);
2734
+ const stepBackward = react.useCallback(() => {
2735
+ if (!isReplaying || !isPaused || !state.chart) return;
2736
+ const idx = currentIndexRef.current;
2737
+ if (idx <= 1) return;
2738
+ const data = savedDataRef.current;
2739
+ state.chart.clearData?.();
2740
+ for (let i = 0; i < idx - 1; i++) {
2741
+ state.chart.updateData?.(data[i]);
2742
+ }
2743
+ currentIndexRef.current = idx - 1;
2744
+ setBarIndex(idx - 1);
2745
+ }, [isReplaying, isPaused, state.chart]);
2746
+ const seekTo = react.useCallback(
2747
+ (targetIndex) => {
2748
+ if (!isReplaying || !state.chart) return;
2749
+ const data = savedDataRef.current;
2750
+ const clamped = Math.max(0, Math.min(targetIndex, data.length));
2751
+ clearInterval_();
2752
+ setIsPaused(true);
2753
+ state.chart.clearData?.();
2754
+ for (let i = 0; i < clamped; i++) {
2755
+ state.chart.updateData?.(data[i]);
2756
+ }
2757
+ currentIndexRef.current = clamped;
2758
+ setBarIndex(clamped);
2759
+ },
2760
+ [isReplaying, state.chart, clearInterval_]
2761
+ );
2762
+ const setSpeed = react.useCallback(
2763
+ (newSpeed) => {
2764
+ setSpeedState(newSpeed);
2765
+ if (isReplaying && !isPaused) {
2766
+ startInterval(newSpeed);
2767
+ }
2768
+ },
2769
+ [isReplaying, isPaused, startInterval]
2770
+ );
2771
+ react.useEffect(() => {
2772
+ return () => {
2773
+ clearInterval_();
2774
+ };
2775
+ }, [clearInterval_]);
2776
+ return {
2777
+ isReplaying,
2778
+ isPaused,
2779
+ speed,
2780
+ barIndex,
2781
+ totalBars,
2782
+ startReplay,
2783
+ stopReplay,
2784
+ togglePause,
2785
+ stepForward,
2786
+ stepBackward,
2787
+ seekTo,
2788
+ setSpeed
2789
+ };
2790
+ }
2791
+ var DEFAULT_COLORS = [
2792
+ "#2196f3",
2793
+ "#ff9800",
2794
+ "#4caf50",
2795
+ "#e91e63",
2796
+ "#9c27b0",
2797
+ "#00bcd4",
2798
+ "#ff5722",
2799
+ "#8bc34a"
2800
+ ];
2801
+ var colorIndex = 0;
2802
+ var compareCounter = 0;
2803
+ function useCompare() {
2804
+ const { state, datafeed } = useKlinechartsUI();
2805
+ const [symbols, setSymbols] = react.useState([]);
2806
+ const indicatorsRef = react.useRef(/* @__PURE__ */ new Map());
2807
+ const addSymbol = react.useCallback(
2808
+ async (ticker, color) => {
2809
+ if (!state.chart || !datafeed) return;
2810
+ if (indicatorsRef.current.has(ticker)) return;
2811
+ const assignedColor = color ?? DEFAULT_COLORS[colorIndex++ % DEFAULT_COLORS.length];
2812
+ const mainDataList = state.chart.getDataList();
2813
+ if (!mainDataList || mainDataList.length === 0) return;
2814
+ const from = mainDataList[0].timestamp;
2815
+ const to = mainDataList[mainDataList.length - 1].timestamp;
2816
+ const compareData = await datafeed.getHistoryKLineData(
2817
+ { ticker },
2818
+ { ...state.period, label: "" },
2819
+ from,
2820
+ to
2821
+ );
2822
+ if (!compareData || compareData.length === 0) return;
2823
+ const compareMap = /* @__PURE__ */ new Map();
2824
+ for (const bar of compareData) {
2825
+ compareMap.set(bar.timestamp, bar.close);
2826
+ }
2827
+ let basePrice = null;
2828
+ for (const bar of mainDataList) {
2829
+ const close = compareMap.get(bar.timestamp);
2830
+ if (close != null) {
2831
+ basePrice = close;
2832
+ break;
2833
+ }
2834
+ }
2835
+ if (basePrice === null || basePrice === 0) return;
2836
+ const bp = basePrice;
2837
+ const normalizedData = mainDataList.map((mainBar) => {
2838
+ const close = compareMap.get(mainBar.timestamp);
2839
+ return {
2840
+ pct: close != null ? (close - bp) / bp * 100 : NaN
2841
+ };
2842
+ });
2843
+ const indicatorName = `__cmp_${++compareCounter}_${ticker}`;
2844
+ reactKlinecharts.registerIndicator({
2845
+ name: indicatorName,
2846
+ shortName: ticker,
2847
+ figures: [
2848
+ {
2849
+ key: "pct",
2850
+ title: `${ticker} %: `,
2851
+ type: "line",
2852
+ styles: () => ({ color: assignedColor })
2853
+ }
2854
+ ],
2855
+ calc: () => normalizedData
2856
+ });
2857
+ const paneId = state.chart.createIndicator(
2858
+ { name: indicatorName },
2859
+ true,
2860
+ { id: "candle_pane" }
2861
+ );
2862
+ indicatorsRef.current.set(ticker, {
2863
+ name: indicatorName,
2864
+ paneId: typeof paneId === "string" ? paneId : "candle_pane"
2865
+ });
2866
+ setSymbols((prev) => {
2867
+ if (prev.some((s) => s.ticker === ticker)) return prev;
2868
+ return [
2869
+ ...prev,
2870
+ { ticker, basePrice: bp, color: assignedColor, visible: true }
2871
+ ];
2872
+ });
2873
+ },
2874
+ [state.chart, state.period, datafeed]
2875
+ );
2876
+ const removeSymbol = react.useCallback(
2877
+ (ticker) => {
2878
+ const info = indicatorsRef.current.get(ticker);
2879
+ if (info && state.chart) {
2880
+ try {
2881
+ state.chart.removeIndicator({ name: info.name });
2882
+ } catch {
2883
+ }
2884
+ indicatorsRef.current.delete(ticker);
2885
+ }
2886
+ setSymbols((prev) => prev.filter((s) => s.ticker !== ticker));
2887
+ },
2888
+ [state.chart]
2889
+ );
2890
+ const toggleSymbol = react.useCallback(
2891
+ (ticker) => {
2892
+ const info = indicatorsRef.current.get(ticker);
2893
+ if (!info || !state.chart) return;
2894
+ setSymbols((prev) => {
2895
+ const sym = prev.find((s) => s.ticker === ticker);
2896
+ if (!sym) return prev;
2897
+ const newVisible = !sym.visible;
2898
+ state.chart?.overrideIndicator({
2899
+ name: info.name,
2900
+ visible: newVisible
2901
+ });
2902
+ return prev.map(
2903
+ (s) => s.ticker === ticker ? { ...s, visible: newVisible } : s
2904
+ );
2905
+ });
2906
+ },
2907
+ [state.chart]
2908
+ );
2909
+ const clearAll = react.useCallback(() => {
2910
+ indicatorsRef.current.forEach((info) => {
2911
+ try {
2912
+ state.chart?.removeIndicator({ name: info.name });
2913
+ } catch {
2914
+ }
2915
+ });
2916
+ indicatorsRef.current.clear();
2917
+ setSymbols([]);
2918
+ }, [state.chart]);
2919
+ react.useEffect(() => {
2920
+ return () => {
2921
+ indicatorsRef.current.forEach((info) => {
2922
+ try {
2923
+ state.chart?.removeIndicator({ name: info.name });
2924
+ } catch {
2925
+ }
2926
+ });
2927
+ indicatorsRef.current.clear();
2928
+ };
2929
+ }, [state.chart]);
2930
+ return { symbols, addSymbol, removeSymbol, toggleSymbol, clearAll };
2931
+ }
2932
+ var MEASURE_OVERLAY_ID = "__measure_line__";
2933
+ function useMeasure() {
2934
+ const { state } = useKlinechartsUI();
2935
+ const [isActive, setIsActive] = react.useState(false);
2936
+ const [fromPoint, setFromPoint] = react.useState(null);
2937
+ const [result, setResult] = react.useState(null);
2938
+ const clickCountRef = react.useRef(0);
2939
+ const computeResult = react.useCallback(
2940
+ (from, to) => {
2941
+ const priceDiff = to.price - from.price;
2942
+ const pricePercent = from.price !== 0 ? priceDiff / from.price * 100 : 0;
2943
+ const bars = Math.abs(to.barIndex - from.barIndex);
2944
+ const timeDiff = Math.abs(to.timestamp - from.timestamp);
2945
+ return { from, to, priceDiff, pricePercent, bars, timeDiff };
2946
+ },
2947
+ []
2948
+ );
2949
+ const cleanup = react.useCallback(() => {
2950
+ state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
2951
+ }, [state.chart]);
2952
+ const startMeasure = react.useCallback(() => {
2953
+ cleanup();
2954
+ setIsActive(true);
2955
+ setFromPoint(null);
2956
+ setResult(null);
2957
+ clickCountRef.current = 0;
2958
+ if (!state.chart) return;
2959
+ state.chart.createOverlay({
2960
+ name: "segment",
2961
+ id: MEASURE_OVERLAY_ID,
2962
+ groupId: "measure",
2963
+ styles: {
2964
+ line: {
2965
+ style: "dashed",
2966
+ color: "#FFD600",
2967
+ size: 1
2968
+ }
2969
+ },
2970
+ lock: true,
2971
+ onDrawEnd: (event) => {
2972
+ const overlay = event?.overlay;
2973
+ const points = overlay?.points;
2974
+ if (!points || points.length < 2) return;
2975
+ const chart = state.chart;
2976
+ if (!chart) return;
2977
+ const dataList = chart.getDataList();
2978
+ if (!dataList || dataList.length === 0) return;
2979
+ const makePoint = (p) => {
2980
+ const idx = Math.max(
2981
+ 0,
2982
+ Math.min(p.dataIndex ?? 0, dataList.length - 1)
2983
+ );
2984
+ const bar = dataList[idx];
2985
+ return {
2986
+ price: p.value ?? bar?.close ?? 0,
2987
+ timestamp: bar?.timestamp ?? 0,
2988
+ barIndex: idx
2989
+ };
2990
+ };
2991
+ const from = makePoint(points[0]);
2992
+ const to = makePoint(points[1]);
2993
+ setFromPoint(from);
2994
+ setResult(computeResult(from, to));
2995
+ setIsActive(false);
2996
+ }
2997
+ });
2998
+ }, [state.chart, cleanup, computeResult]);
2999
+ const cancelMeasure = react.useCallback(() => {
3000
+ cleanup();
3001
+ setIsActive(false);
3002
+ setFromPoint(null);
3003
+ clickCountRef.current = 0;
3004
+ }, [cleanup]);
3005
+ const clearResult = react.useCallback(() => {
3006
+ cleanup();
3007
+ setResult(null);
3008
+ setFromPoint(null);
3009
+ }, [cleanup]);
3010
+ react.useEffect(() => {
3011
+ return () => {
3012
+ state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
3013
+ };
3014
+ }, [state.chart]);
3015
+ return {
3016
+ isActive,
3017
+ startMeasure,
3018
+ cancelMeasure,
3019
+ result,
3020
+ clearResult,
3021
+ fromPoint
3022
+ };
3023
+ }
3024
+ var annotationCounter = 0;
3025
+ function useAnnotations() {
3026
+ const { state } = useKlinechartsUI();
3027
+ const [annotations, setAnnotations] = react.useState([]);
3028
+ const addAnnotation = react.useCallback(
3029
+ (text, price, timestamp, color) => {
3030
+ const id = `annotation_${++annotationCounter}`;
3031
+ const annotation = {
3032
+ id,
3033
+ text,
3034
+ price,
3035
+ timestamp,
3036
+ color
3037
+ };
3038
+ setAnnotations((prev) => [...prev, annotation]);
3039
+ if (state.chart) {
3040
+ state.chart.createOverlay({
3041
+ name: "simpleAnnotation",
3042
+ id,
3043
+ groupId: "annotations",
3044
+ points: [{ timestamp, value: price }],
3045
+ extendData: text,
3046
+ styles: color ? {
3047
+ text: {
3048
+ color
3049
+ }
3050
+ } : void 0,
3051
+ lock: true
3052
+ });
3053
+ }
3054
+ return id;
3055
+ },
3056
+ [state.chart]
3057
+ );
3058
+ const removeAnnotation = react.useCallback(
3059
+ (id) => {
3060
+ setAnnotations((prev) => prev.filter((a) => a.id !== id));
3061
+ state.chart?.removeOverlay({ id });
3062
+ },
3063
+ [state.chart]
3064
+ );
3065
+ const updateAnnotation = react.useCallback(
3066
+ (id, updates) => {
3067
+ setAnnotations(
3068
+ (prev) => prev.map((a) => a.id === id ? { ...a, ...updates } : a)
3069
+ );
3070
+ if (state.chart) {
3071
+ const overrideData = { id };
3072
+ if (updates.text !== void 0) {
3073
+ overrideData.extendData = updates.text;
3074
+ }
3075
+ if (updates.color !== void 0) {
3076
+ overrideData.styles = {
3077
+ text: {
3078
+ color: updates.color
3079
+ }
3080
+ };
3081
+ }
3082
+ state.chart.overrideOverlay(overrideData);
3083
+ }
3084
+ },
3085
+ [state.chart]
3086
+ );
3087
+ const clearAnnotations = react.useCallback(() => {
3088
+ setAnnotations((prev) => {
3089
+ for (const annotation of prev) {
3090
+ state.chart?.removeOverlay({ id: annotation.id });
3091
+ }
3092
+ return [];
3093
+ });
3094
+ }, [state.chart]);
3095
+ react.useEffect(() => {
3096
+ return () => {
3097
+ state.chart?.removeOverlay({ groupId: "annotations" });
3098
+ };
3099
+ }, [state.chart]);
3100
+ return {
3101
+ annotations,
3102
+ addAnnotation,
3103
+ removeAnnotation,
3104
+ updateAnnotation,
3105
+ clearAnnotations
3106
+ };
3107
+ }
1324
3108
 
1325
3109
  // src/utils/createDataLoader.ts
1326
3110
  function createDataLoader(datafeed, dispatch) {
@@ -1386,85 +3170,177 @@ function createDataLoader(datafeed, dispatch) {
1386
3170
  };
1387
3171
  }
1388
3172
 
3173
+ Object.defineProperty(exports, "TA", {
3174
+ enumerable: true,
3175
+ get: function () { return chunkPIFLJKYF_cjs.TA_default; }
3176
+ });
1389
3177
  Object.defineProperty(exports, "abcd", {
1390
3178
  enumerable: true,
1391
- get: function () { return chunkSLTXNLKN_cjs.abcd_default; }
3179
+ get: function () { return chunkPIFLJKYF_cjs.abcd_default; }
1392
3180
  });
1393
3181
  Object.defineProperty(exports, "anyWaves", {
1394
3182
  enumerable: true,
1395
- get: function () { return chunkSLTXNLKN_cjs.anyWaves_default; }
3183
+ get: function () { return chunkPIFLJKYF_cjs.anyWaves_default; }
1396
3184
  });
1397
3185
  Object.defineProperty(exports, "arrow", {
1398
3186
  enumerable: true,
1399
- get: function () { return chunkSLTXNLKN_cjs.arrow_default; }
3187
+ get: function () { return chunkPIFLJKYF_cjs.arrow_default; }
3188
+ });
3189
+ Object.defineProperty(exports, "bollTv", {
3190
+ enumerable: true,
3191
+ get: function () { return chunkPIFLJKYF_cjs.bollTv_default; }
3192
+ });
3193
+ Object.defineProperty(exports, "brush", {
3194
+ enumerable: true,
3195
+ get: function () { return chunkPIFLJKYF_cjs.brush_default; }
3196
+ });
3197
+ Object.defineProperty(exports, "cci", {
3198
+ enumerable: true,
3199
+ get: function () { return chunkPIFLJKYF_cjs.cci_default; }
1400
3200
  });
1401
3201
  Object.defineProperty(exports, "circle", {
1402
3202
  enumerable: true,
1403
- get: function () { return chunkSLTXNLKN_cjs.circle_default; }
3203
+ get: function () { return chunkPIFLJKYF_cjs.circle_default; }
3204
+ });
3205
+ Object.defineProperty(exports, "depthOverlay", {
3206
+ enumerable: true,
3207
+ get: function () { return chunkPIFLJKYF_cjs.depthOverlay_default; }
1404
3208
  });
1405
3209
  Object.defineProperty(exports, "eightWaves", {
1406
3210
  enumerable: true,
1407
- get: function () { return chunkSLTXNLKN_cjs.eightWaves_default; }
3211
+ get: function () { return chunkPIFLJKYF_cjs.eightWaves_default; }
3212
+ });
3213
+ Object.defineProperty(exports, "elliottWave", {
3214
+ enumerable: true,
3215
+ get: function () { return chunkPIFLJKYF_cjs.elliottWave_default; }
3216
+ });
3217
+ Object.defineProperty(exports, "fibRetracement", {
3218
+ enumerable: true,
3219
+ get: function () { return chunkPIFLJKYF_cjs.fibRetracement_default; }
1408
3220
  });
1409
3221
  Object.defineProperty(exports, "fibonacciCircle", {
1410
3222
  enumerable: true,
1411
- get: function () { return chunkSLTXNLKN_cjs.fibonacciCircle_default; }
3223
+ get: function () { return chunkPIFLJKYF_cjs.fibonacciCircle_default; }
1412
3224
  });
1413
3225
  Object.defineProperty(exports, "fibonacciExtension", {
1414
3226
  enumerable: true,
1415
- get: function () { return chunkSLTXNLKN_cjs.fibonacciExtension_default; }
3227
+ get: function () { return chunkPIFLJKYF_cjs.fibonacciExtension_default; }
1416
3228
  });
1417
3229
  Object.defineProperty(exports, "fibonacciSegment", {
1418
3230
  enumerable: true,
1419
- get: function () { return chunkSLTXNLKN_cjs.fibonacciSegment_default; }
3231
+ get: function () { return chunkPIFLJKYF_cjs.fibonacciSegment_default; }
1420
3232
  });
1421
3233
  Object.defineProperty(exports, "fibonacciSpeedResistanceFan", {
1422
3234
  enumerable: true,
1423
- get: function () { return chunkSLTXNLKN_cjs.fibonacciSpeedResistanceFan_default; }
3235
+ get: function () { return chunkPIFLJKYF_cjs.fibonacciSpeedResistanceFan_default; }
1424
3236
  });
1425
3237
  Object.defineProperty(exports, "fibonacciSpiral", {
1426
3238
  enumerable: true,
1427
- get: function () { return chunkSLTXNLKN_cjs.fibonacciSpiral_default; }
3239
+ get: function () { return chunkPIFLJKYF_cjs.fibonacciSpiral_default; }
1428
3240
  });
1429
3241
  Object.defineProperty(exports, "fiveWaves", {
1430
3242
  enumerable: true,
1431
- get: function () { return chunkSLTXNLKN_cjs.fiveWaves_default; }
3243
+ get: function () { return chunkPIFLJKYF_cjs.fiveWaves_default; }
1432
3244
  });
1433
3245
  Object.defineProperty(exports, "gannBox", {
1434
3246
  enumerable: true,
1435
- get: function () { return chunkSLTXNLKN_cjs.gannBox_default; }
3247
+ get: function () { return chunkPIFLJKYF_cjs.gannBox_default; }
3248
+ });
3249
+ Object.defineProperty(exports, "gannFan", {
3250
+ enumerable: true,
3251
+ get: function () { return chunkPIFLJKYF_cjs.gannFan_default; }
3252
+ });
3253
+ Object.defineProperty(exports, "hma", {
3254
+ enumerable: true,
3255
+ get: function () { return chunkPIFLJKYF_cjs.hma_default; }
3256
+ });
3257
+ Object.defineProperty(exports, "ichimoku", {
3258
+ enumerable: true,
3259
+ get: function () { return chunkPIFLJKYF_cjs.ichimoku_default; }
3260
+ });
3261
+ Object.defineProperty(exports, "indicators", {
3262
+ enumerable: true,
3263
+ get: function () { return chunkPIFLJKYF_cjs.indicators; }
3264
+ });
3265
+ Object.defineProperty(exports, "longPosition", {
3266
+ enumerable: true,
3267
+ get: function () { return chunkPIFLJKYF_cjs.longPosition_default; }
3268
+ });
3269
+ Object.defineProperty(exports, "maRibbon", {
3270
+ enumerable: true,
3271
+ get: function () { return chunkPIFLJKYF_cjs.maRibbon_default; }
3272
+ });
3273
+ Object.defineProperty(exports, "macdTv", {
3274
+ enumerable: true,
3275
+ get: function () { return chunkPIFLJKYF_cjs.macdTv_default; }
3276
+ });
3277
+ Object.defineProperty(exports, "measure", {
3278
+ enumerable: true,
3279
+ get: function () { return chunkPIFLJKYF_cjs.measure_default; }
1436
3280
  });
1437
3281
  Object.defineProperty(exports, "orderLine", {
1438
3282
  enumerable: true,
1439
- get: function () { return chunkSLTXNLKN_cjs.orderLine_default; }
3283
+ get: function () { return chunkPIFLJKYF_cjs.orderLine_default; }
1440
3284
  });
1441
3285
  Object.defineProperty(exports, "overlays", {
1442
3286
  enumerable: true,
1443
- get: function () { return chunkSLTXNLKN_cjs.overlays; }
3287
+ get: function () { return chunkPIFLJKYF_cjs.overlays; }
3288
+ });
3289
+ Object.defineProperty(exports, "parallelChannel", {
3290
+ enumerable: true,
3291
+ get: function () { return chunkPIFLJKYF_cjs.parallelChannel_default; }
1444
3292
  });
1445
3293
  Object.defineProperty(exports, "parallelogram", {
1446
3294
  enumerable: true,
1447
- get: function () { return chunkSLTXNLKN_cjs.parallelogram_default; }
3295
+ get: function () { return chunkPIFLJKYF_cjs.parallelogram_default; }
3296
+ });
3297
+ Object.defineProperty(exports, "pivotPoints", {
3298
+ enumerable: true,
3299
+ get: function () { return chunkPIFLJKYF_cjs.pivotPoints_default; }
3300
+ });
3301
+ Object.defineProperty(exports, "ray", {
3302
+ enumerable: true,
3303
+ get: function () { return chunkPIFLJKYF_cjs.ray_default; }
1448
3304
  });
1449
3305
  Object.defineProperty(exports, "rect", {
1450
3306
  enumerable: true,
1451
- get: function () { return chunkSLTXNLKN_cjs.rect_default; }
3307
+ get: function () { return chunkPIFLJKYF_cjs.rect_default; }
1452
3308
  });
1453
3309
  Object.defineProperty(exports, "registerExtensions", {
1454
3310
  enumerable: true,
1455
- get: function () { return chunkSLTXNLKN_cjs.registerExtensions; }
3311
+ get: function () { return chunkPIFLJKYF_cjs.registerExtensions; }
3312
+ });
3313
+ Object.defineProperty(exports, "rsiTv", {
3314
+ enumerable: true,
3315
+ get: function () { return chunkPIFLJKYF_cjs.rsiTv_default; }
3316
+ });
3317
+ Object.defineProperty(exports, "shortPosition", {
3318
+ enumerable: true,
3319
+ get: function () { return chunkPIFLJKYF_cjs.shortPosition_default; }
3320
+ });
3321
+ Object.defineProperty(exports, "stochastic", {
3322
+ enumerable: true,
3323
+ get: function () { return chunkPIFLJKYF_cjs.stochastic_default; }
3324
+ });
3325
+ Object.defineProperty(exports, "superTrend", {
3326
+ enumerable: true,
3327
+ get: function () { return chunkPIFLJKYF_cjs.superTrend_default; }
1456
3328
  });
1457
3329
  Object.defineProperty(exports, "threeWaves", {
1458
3330
  enumerable: true,
1459
- get: function () { return chunkSLTXNLKN_cjs.threeWaves_default; }
3331
+ get: function () { return chunkPIFLJKYF_cjs.threeWaves_default; }
1460
3332
  });
1461
3333
  Object.defineProperty(exports, "triangle", {
1462
3334
  enumerable: true,
1463
- get: function () { return chunkSLTXNLKN_cjs.triangle_default; }
3335
+ get: function () { return chunkPIFLJKYF_cjs.triangle_default; }
3336
+ });
3337
+ Object.defineProperty(exports, "vwap", {
3338
+ enumerable: true,
3339
+ get: function () { return chunkPIFLJKYF_cjs.vwap_default; }
1464
3340
  });
1465
3341
  Object.defineProperty(exports, "xabcd", {
1466
3342
  enumerable: true,
1467
- get: function () { return chunkSLTXNLKN_cjs.xabcd_default; }
3343
+ get: function () { return chunkPIFLJKYF_cjs.xabcd_default; }
1468
3344
  });
1469
3345
  exports.CANDLE_TYPES = CANDLE_TYPES;
1470
3346
  exports.COMPARE_RULES = COMPARE_RULES;
@@ -1481,6 +3357,11 @@ exports.TIMEZONES = TIMEZONES;
1481
3357
  exports.TOOLTIP_SHOW_RULES = TOOLTIP_SHOW_RULES;
1482
3358
  exports.YAXIS_POSITIONS = YAXIS_POSITIONS;
1483
3359
  exports.createDataLoader = createDataLoader;
3360
+ exports.useAlerts = useAlerts;
3361
+ exports.useAnnotations = useAnnotations;
3362
+ exports.useCompare = useCompare;
3363
+ exports.useCrosshair = useCrosshair;
3364
+ exports.useDataExport = useDataExport;
1484
3365
  exports.useDrawingTools = useDrawingTools;
1485
3366
  exports.useFullscreen = useFullscreen;
1486
3367
  exports.useIndicators = useIndicators;
@@ -1488,10 +3369,16 @@ exports.useKlinechartsUI = useKlinechartsUI;
1488
3369
  exports.useKlinechartsUILoading = useKlinechartsUILoading;
1489
3370
  exports.useKlinechartsUISettings = useKlinechartsUISettings;
1490
3371
  exports.useKlinechartsUITheme = useKlinechartsUITheme;
3372
+ exports.useLayoutManager = useLayoutManager;
3373
+ exports.useMeasure = useMeasure;
1491
3374
  exports.useOrderLines = useOrderLines;
1492
3375
  exports.usePeriods = usePeriods;
3376
+ exports.useReplay = useReplay;
1493
3377
  exports.useScreenshot = useScreenshot;
3378
+ exports.useScriptEditor = useScriptEditor;
1494
3379
  exports.useSymbolSearch = useSymbolSearch;
1495
3380
  exports.useTimezone = useTimezone;
3381
+ exports.useUndoRedo = useUndoRedo;
3382
+ exports.useWatchlist = useWatchlist;
1496
3383
  //# sourceMappingURL=index.cjs.map
1497
3384
  //# sourceMappingURL=index.cjs.map