react-klinecharts-ui 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { registerExtensions } from './chunk-PUULIBFN.js';
2
- export { abcd_default as abcd, anyWaves_default as anyWaves, arrow_default as arrow, circle_default as circle, eightWaves_default as eightWaves, 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, orderLine_default as orderLine, overlays, parallelogram_default as parallelogram, rect_default as rect, registerExtensions, threeWaves_default as threeWaves, triangle_default as triangle, xabcd_default as xabcd } from './chunk-PUULIBFN.js';
1
+ import { registerExtensions, TA_default } from './chunk-B3NZ66YA.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, 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-B3NZ66YA.js';
3
3
  import { createContext, useContext, useReducer, useRef, useCallback, useEffect, useMemo, useState } from 'react';
4
- import { registerOverlay } from 'react-klinecharts';
4
+ import { registerOverlay, registerIndicator } from 'react-klinecharts';
5
5
  import { jsx } from 'react/jsx-runtime';
6
6
 
7
7
  var KlinechartsUIStateContext = createContext(null);
@@ -124,6 +124,7 @@ function KlinechartsUIProvider({
124
124
  })
125
125
  );
126
126
  const fullscreenContainerRef = useRef(null);
127
+ const undoRedoListenerRef = useRef(null);
127
128
  const stateRef = useRef(state);
128
129
  stateRef.current = state;
129
130
  const callbacksRef = useRef({
@@ -189,7 +190,8 @@ function KlinechartsUIProvider({
189
190
  dispatch: enhancedDispatch,
190
191
  datafeed,
191
192
  onSettingsChange,
192
- fullscreenContainerRef
193
+ fullscreenContainerRef,
194
+ undoRedoListenerRef
193
195
  }),
194
196
  [enhancedDispatch, datafeed, onSettingsChange]
195
197
  );
@@ -357,7 +359,14 @@ var MAIN_INDICATORS = [
357
359
  "SMA",
358
360
  "BOLL",
359
361
  "SAR",
360
- "BBI"
362
+ "BBI",
363
+ "BOLL_TV",
364
+ "Ichimoku",
365
+ "SuperTrend",
366
+ "HMA",
367
+ "VWAP",
368
+ "PivotPoints",
369
+ "MA_Ribbon"
361
370
  ];
362
371
  var SUB_INDICATORS = [
363
372
  "MA",
@@ -385,7 +394,11 @@ var SUB_INDICATORS = [
385
394
  "ROC",
386
395
  "PVT",
387
396
  "BBI",
388
- "AO"
397
+ "AO",
398
+ "MACD_TV",
399
+ "RSI_TV",
400
+ "CCI_TV",
401
+ "Stochastic"
389
402
  ];
390
403
  var INDICATOR_PARAMS = {
391
404
  SMA: {
@@ -544,12 +557,95 @@ var INDICATOR_PARAMS = {
544
557
  { label: "params_1", defaultValue: 5 },
545
558
  { label: "params_2", defaultValue: 34 }
546
559
  ]
560
+ },
561
+ BOLL_TV: {
562
+ name: "BOLL_TV",
563
+ localeKey: "boll_tv",
564
+ params: [
565
+ { label: "period", defaultValue: 20 },
566
+ { label: "multiplier", defaultValue: 2 }
567
+ ]
568
+ },
569
+ Ichimoku: {
570
+ name: "Ichimoku",
571
+ localeKey: "ichimoku",
572
+ params: [
573
+ { label: "tenkan", defaultValue: 9 },
574
+ { label: "kijun", defaultValue: 26 },
575
+ { label: "senkou_b", defaultValue: 52 },
576
+ { label: "offset", defaultValue: 26 }
577
+ ]
578
+ },
579
+ SuperTrend: {
580
+ name: "SuperTrend",
581
+ localeKey: "super_trend",
582
+ params: [
583
+ { label: "period", defaultValue: 10 },
584
+ { label: "factor", defaultValue: 3 }
585
+ ]
586
+ },
587
+ HMA: {
588
+ name: "HMA",
589
+ localeKey: "hma",
590
+ params: [{ label: "period", defaultValue: 9 }]
591
+ },
592
+ VWAP: {
593
+ name: "VWAP",
594
+ localeKey: "vwap",
595
+ params: []
596
+ },
597
+ PivotPoints: {
598
+ name: "PivotPoints",
599
+ localeKey: "pivot_points",
600
+ params: []
601
+ },
602
+ MA_Ribbon: {
603
+ name: "MA_Ribbon",
604
+ localeKey: "ma_ribbon",
605
+ params: [
606
+ { label: "period_1", defaultValue: 10 },
607
+ { label: "period_2", defaultValue: 20 },
608
+ { label: "period_3", defaultValue: 30 },
609
+ { label: "period_4", defaultValue: 40 }
610
+ ]
611
+ },
612
+ MACD_TV: {
613
+ name: "MACD_TV",
614
+ localeKey: "macd_tv",
615
+ params: [
616
+ { label: "fast", defaultValue: 12 },
617
+ { label: "slow", defaultValue: 26 },
618
+ { label: "signal", defaultValue: 9 }
619
+ ]
620
+ },
621
+ RSI_TV: {
622
+ name: "RSI_TV",
623
+ localeKey: "rsi_tv",
624
+ params: [
625
+ { label: "rsi_period", defaultValue: 14 },
626
+ { label: "ma_period", defaultValue: 14 }
627
+ ]
628
+ },
629
+ CCI_TV: {
630
+ name: "CCI_TV",
631
+ localeKey: "cci_tv",
632
+ params: [{ label: "period", defaultValue: 20 }]
633
+ },
634
+ Stochastic: {
635
+ name: "Stochastic",
636
+ localeKey: "stochastic",
637
+ params: [
638
+ { label: "period", defaultValue: 14 },
639
+ { label: "smooth_k", defaultValue: 3 },
640
+ { label: "smooth_d", defaultValue: 3 }
641
+ ]
547
642
  }
548
643
  };
549
644
 
550
645
  // src/hooks/useIndicators.ts
551
646
  function useIndicators() {
552
647
  const { state, dispatch } = useKlinechartsUI();
648
+ const { undoRedoListenerRef } = useKlinechartsUIDispatch();
553
649
  const mainIndicators = useMemo(() => {
554
650
  const activeNames = state.mainIndicators;
555
651
  const inactive = MAIN_INDICATORS.filter((n) => !activeNames.includes(n));
@@ -576,16 +672,24 @@ function useIndicators() {
576
672
  );
577
673
  const newIndicators = [...state.mainIndicators, name];
578
674
  dispatch({ type: "SET_MAIN_INDICATORS", indicators: newIndicators });
675
+ undoRedoListenerRef.current?.({
676
+ type: "indicator_toggled",
677
+ data: { name, wasActive: false, isMain: true, paneId: "candle_pane" }
678
+ });
579
679
  },
580
- [state.chart, state.mainIndicators, dispatch]
680
+ [state.chart, state.mainIndicators, dispatch, undoRedoListenerRef]
581
681
  );
582
682
  const removeMainIndicator = useCallback(
583
683
  (name) => {
584
684
  state.chart?.removeIndicator({ id: `main_${name}` });
585
685
  const newIndicators = state.mainIndicators.filter((n) => n !== name);
586
686
  dispatch({ type: "SET_MAIN_INDICATORS", indicators: newIndicators });
687
+ undoRedoListenerRef.current?.({
688
+ type: "indicator_toggled",
689
+ data: { name, wasActive: true, isMain: true, paneId: "candle_pane" }
690
+ });
587
691
  },
588
- [state.chart, state.mainIndicators, dispatch]
692
+ [state.chart, state.mainIndicators, dispatch, undoRedoListenerRef]
589
693
  );
590
694
  const addSubIndicator = useCallback(
591
695
  (name) => {
@@ -595,17 +699,26 @@ function useIndicators() {
595
699
  const paneId = state.chart?.getIndicators({ id })?.[0]?.paneId ?? "";
596
700
  const newIndicators = { ...state.subIndicators, [name]: paneId };
597
701
  dispatch({ type: "SET_SUB_INDICATORS", indicators: newIndicators });
702
+ undoRedoListenerRef.current?.({
703
+ type: "indicator_toggled",
704
+ data: { name, wasActive: false, isMain: false, paneId }
705
+ });
598
706
  },
599
- [state.chart, state.subIndicators, dispatch]
707
+ [state.chart, state.subIndicators, dispatch, undoRedoListenerRef]
600
708
  );
601
709
  const removeSubIndicator = useCallback(
602
710
  (name) => {
711
+ const paneId = state.subIndicators[name] ?? "";
603
712
  state.chart?.removeIndicator({ id: `sub_${name}` });
604
713
  const newIndicators = { ...state.subIndicators };
605
714
  delete newIndicators[name];
606
715
  dispatch({ type: "SET_SUB_INDICATORS", indicators: newIndicators });
716
+ undoRedoListenerRef.current?.({
717
+ type: "indicator_toggled",
718
+ data: { name, wasActive: true, isMain: false, paneId }
719
+ });
607
720
  },
608
- [state.chart, state.subIndicators, dispatch]
721
+ [state.chart, state.subIndicators, dispatch, undoRedoListenerRef]
609
722
  );
610
723
  const toggleMainIndicator = useCallback(
611
724
  (name) => {
@@ -651,9 +764,15 @@ function useIndicators() {
651
764
  const moveToMain = useCallback(
652
765
  (name) => {
653
766
  if (!state.chart) return;
767
+ const prevCalcParams = state.chart.getIndicators({ id: `sub_${name}` })?.[0]?.calcParams;
654
768
  state.chart.removeIndicator({ id: `sub_${name}` });
655
769
  state.chart.createIndicator(
656
- { name, id: `main_${name}`, paneId: "candle_pane" },
770
+ {
771
+ name,
772
+ id: `main_${name}`,
773
+ paneId: "candle_pane",
774
+ ...prevCalcParams ? { calcParams: prevCalcParams } : {}
775
+ },
657
776
  true,
658
777
  { id: "candle_pane" }
659
778
  );
@@ -670,9 +789,14 @@ function useIndicators() {
670
789
  const moveToSub = useCallback(
671
790
  (name) => {
672
791
  if (!state.chart) return;
792
+ const prevCalcParams = state.chart.getIndicators({ id: `main_${name}` })?.[0]?.calcParams;
673
793
  state.chart.removeIndicator({ id: `main_${name}` });
674
794
  const subId = `sub_${name}`;
675
- state.chart.createIndicator({ name, id: subId });
795
+ state.chart.createIndicator({
796
+ name,
797
+ id: subId,
798
+ ...prevCalcParams ? { calcParams: prevCalcParams } : {}
799
+ });
676
800
  const paneId = state.chart.getIndicators({ id: subId })?.[0]?.paneId ?? "";
677
801
  const newMain = state.mainIndicators.filter((n) => n !== name);
678
802
  dispatch({ type: "SET_MAIN_INDICATORS", indicators: newMain });
@@ -736,7 +860,9 @@ var DRAWING_CATEGORIES = [
736
860
  key: "moreLine",
737
861
  tools: [
738
862
  { name: "priceChannelLine", localeKey: "price_channel_line" },
739
- { name: "parallelStraightLine", localeKey: "parallel_straight_line" }
863
+ { name: "parallelStraightLine", localeKey: "parallel_straight_line" },
864
+ { name: "ray", localeKey: "ray" },
865
+ { name: "parallelChannel", localeKey: "parallel_channel" }
740
866
  ]
741
867
  },
742
868
  {
@@ -760,7 +886,9 @@ var DRAWING_CATEGORIES = [
760
886
  localeKey: "fibonacci_speed_resistance_fan"
761
887
  },
762
888
  { name: "fibonacciExtension", localeKey: "fibonacci_extension" },
763
- { name: "gannBox", localeKey: "gann_box" }
889
+ { name: "fibRetracement", localeKey: "fib_retracement" },
890
+ { name: "gannBox", localeKey: "gann_box" },
891
+ { name: "gannFan", localeKey: "gann_fan" }
764
892
  ]
765
893
  },
766
894
  {
@@ -771,7 +899,27 @@ var DRAWING_CATEGORIES = [
771
899
  { name: "threeWaves", localeKey: "three_waves" },
772
900
  { name: "fiveWaves", localeKey: "five_waves" },
773
901
  { name: "eightWaves", localeKey: "eight_waves" },
774
- { name: "anyWaves", localeKey: "any_waves" }
902
+ { name: "anyWaves", localeKey: "any_waves" },
903
+ { name: "elliottWave", localeKey: "elliott_wave" }
904
+ ]
905
+ },
906
+ {
907
+ key: "measure",
908
+ tools: [
909
+ { name: "measure", localeKey: "measure" }
910
+ ]
911
+ },
912
+ {
913
+ key: "position",
914
+ tools: [
915
+ { name: "longPosition", localeKey: "long_position" },
916
+ { name: "shortPosition", localeKey: "short_position" }
917
+ ]
918
+ },
919
+ {
920
+ key: "annotation",
921
+ tools: [
922
+ { name: "brush", localeKey: "brush" }
775
923
  ]
776
924
  }
777
925
  ];
@@ -780,6 +928,7 @@ var DRAWING_CATEGORIES = [
780
928
  var DRAWING_GROUP_ID = "drawing_tools";
781
929
  function useDrawingTools() {
782
930
  const { state } = useKlinechartsUI();
931
+ const { undoRedoListenerRef } = useKlinechartsUIDispatch();
783
932
  const [activeTool, setActiveTool] = useState(null);
784
933
  const [magnetMode, setMagnetModeState] = useState("normal");
785
934
  const [isLocked, setIsLocked] = useState(false);
@@ -802,10 +951,25 @@ function useDrawingTools() {
802
951
  groupId: DRAWING_GROUP_ID,
803
952
  lock: isLocked,
804
953
  visible: isVisible,
805
- mode: magnetMode === "strong" ? "strong_magnet" : magnetMode === "weak" ? "weak_magnet" : "normal"
954
+ mode: magnetMode === "strong" ? "strong_magnet" : magnetMode === "weak" ? "weak_magnet" : "normal",
955
+ onDrawEnd: (event) => {
956
+ const o = event.overlay;
957
+ undoRedoListenerRef.current?.({
958
+ type: "overlay_added",
959
+ data: {
960
+ id: o.id,
961
+ overlayData: {
962
+ name: o.name,
963
+ points: o.points,
964
+ styles: o.styles,
965
+ extendData: o.extendData
966
+ }
967
+ }
968
+ });
969
+ }
806
970
  });
807
971
  },
808
- [state.chart, isLocked, isVisible, magnetMode]
972
+ [state.chart, isLocked, isVisible, magnetMode, undoRedoListenerRef]
809
973
  );
810
974
  const clearActiveTool = useCallback(() => {
811
975
  setActiveTool(null);
@@ -853,9 +1017,23 @@ function useDrawingTools() {
853
1017
  }
854
1018
  }, [state.chart, isVisible]);
855
1019
  const removeAllDrawings = useCallback(() => {
1020
+ const overlays2 = state.chart?.getOverlays({ groupId: DRAWING_GROUP_ID });
1021
+ if (overlays2 && overlays2.length > 0) {
1022
+ const snapshot = overlays2.map((o) => ({
1023
+ name: o.name,
1024
+ id: o.id,
1025
+ points: o.points,
1026
+ styles: o.styles,
1027
+ extendData: o.extendData
1028
+ }));
1029
+ undoRedoListenerRef.current?.({
1030
+ type: "overlays_removed",
1031
+ data: { overlays: snapshot }
1032
+ });
1033
+ }
856
1034
  state.chart?.removeOverlay({ groupId: DRAWING_GROUP_ID });
857
1035
  setActiveTool(null);
858
- }, [state.chart]);
1036
+ }, [state.chart, undoRedoListenerRef]);
859
1037
  return {
860
1038
  categories,
861
1039
  activeTool,
@@ -1259,12 +1437,12 @@ function useOrderLines() {
1259
1437
  if (onPriceChange) {
1260
1438
  callbacksRef.current.set(id, onPriceChange);
1261
1439
  }
1262
- const dataCount = state.chart.getDataList().length;
1263
- const anchorIndex = dataCount > 0 ? dataCount - 1 : 0;
1440
+ const dataList = state.chart.getDataList();
1441
+ const anchorTimestamp = dataList.length > 0 ? dataList[dataList.length - 1].timestamp : Date.now();
1264
1442
  state.chart.createOverlay({
1265
1443
  name: "orderLine",
1266
1444
  id,
1267
- points: [{ dataIndex: anchorIndex, value: price }],
1445
+ points: [{ timestamp: anchorTimestamp, value: price }],
1268
1446
  extendData,
1269
1447
  lock: !draggable,
1270
1448
  mode: "normal",
@@ -1288,11 +1466,11 @@ function useOrderLines() {
1288
1466
  if (onPriceChange !== void 0) {
1289
1467
  callbacksRef.current.set(id, onPriceChange);
1290
1468
  }
1291
- const dataCount = state.chart.getDataList().length;
1292
- const anchorIndex = dataCount > 0 ? dataCount - 1 : 0;
1469
+ const dataList = state.chart.getDataList();
1470
+ const anchorTimestamp = dataList.length > 0 ? dataList[dataList.length - 1].timestamp : Date.now();
1293
1471
  state.chart.overrideOverlay({
1294
1472
  id,
1295
- ...price != null ? { points: [{ dataIndex: anchorIndex, value: price }] } : {},
1473
+ ...price != null ? { points: [{ timestamp: anchorTimestamp, value: price }] } : {},
1296
1474
  ...draggable != null ? { lock: !draggable } : {},
1297
1475
  extendData: {
1298
1476
  ...existing.extendData,
@@ -1320,6 +1498,752 @@ function useOrderLines() {
1320
1498
  removeAllOrderLines
1321
1499
  };
1322
1500
  }
1501
+ var DRAWING_GROUP_ID2 = "drawing_tools";
1502
+ function useUndoRedo() {
1503
+ const { state, dispatch } = useKlinechartsUI();
1504
+ const { undoRedoListenerRef } = useKlinechartsUIDispatch();
1505
+ const [undoStack, setUndoStack] = useState([]);
1506
+ const [redoStack, setRedoStack] = useState([]);
1507
+ const isProcessingRef = useRef(false);
1508
+ const canUndo = undoStack.length > 0;
1509
+ const canRedo = redoStack.length > 0;
1510
+ const pushAction = useCallback((action) => {
1511
+ if (isProcessingRef.current) return;
1512
+ setUndoStack((prev) => [...prev, action]);
1513
+ setRedoStack([]);
1514
+ }, []);
1515
+ useEffect(() => {
1516
+ undoRedoListenerRef.current = pushAction;
1517
+ return () => {
1518
+ undoRedoListenerRef.current = null;
1519
+ };
1520
+ }, [pushAction, undoRedoListenerRef]);
1521
+ const clear = useCallback(() => {
1522
+ setUndoStack([]);
1523
+ setRedoStack([]);
1524
+ }, []);
1525
+ const undo = useCallback(() => {
1526
+ if (undoStack.length === 0 || !state.chart || isProcessingRef.current)
1527
+ return;
1528
+ isProcessingRef.current = true;
1529
+ const action = undoStack[undoStack.length - 1];
1530
+ setUndoStack((prev) => prev.slice(0, -1));
1531
+ try {
1532
+ switch (action.type) {
1533
+ case "overlay_added": {
1534
+ const { id, overlayData } = action.data;
1535
+ state.chart.removeOverlay({ id });
1536
+ setRedoStack((prev) => [
1537
+ ...prev,
1538
+ {
1539
+ type: "overlay_added",
1540
+ data: { id, overlayData }
1541
+ }
1542
+ ]);
1543
+ break;
1544
+ }
1545
+ case "overlays_removed": {
1546
+ const { overlays: overlays2 } = action.data;
1547
+ const restoredIds = [];
1548
+ for (const overlay of overlays2) {
1549
+ const newId = state.chart.createOverlay({
1550
+ ...overlay,
1551
+ groupId: DRAWING_GROUP_ID2
1552
+ });
1553
+ if (typeof newId === "string") {
1554
+ restoredIds.push(newId);
1555
+ }
1556
+ }
1557
+ setRedoStack((prev) => [
1558
+ ...prev,
1559
+ {
1560
+ type: "overlays_removed",
1561
+ data: {
1562
+ overlays: overlays2.map((o, i) => ({
1563
+ ...o,
1564
+ id: restoredIds[i]
1565
+ }))
1566
+ }
1567
+ }
1568
+ ]);
1569
+ break;
1570
+ }
1571
+ case "indicator_toggled": {
1572
+ const { name, wasActive, isMain, paneId } = action.data;
1573
+ if (wasActive) {
1574
+ if (isMain) {
1575
+ state.chart.createIndicator(
1576
+ { name, id: `main_${name}`, paneId: "candle_pane" },
1577
+ true,
1578
+ { id: "candle_pane" }
1579
+ );
1580
+ dispatch({
1581
+ type: "SET_MAIN_INDICATORS",
1582
+ indicators: [...state.mainIndicators, name]
1583
+ });
1584
+ } else {
1585
+ state.chart.createIndicator({ name, id: `sub_${name}` });
1586
+ const newPaneId = state.chart.getIndicators({ id: `sub_${name}` })?.[0]?.paneId ?? "";
1587
+ dispatch({
1588
+ type: "SET_SUB_INDICATORS",
1589
+ indicators: {
1590
+ ...state.subIndicators,
1591
+ [name]: newPaneId
1592
+ }
1593
+ });
1594
+ }
1595
+ } else {
1596
+ if (isMain) {
1597
+ state.chart.removeIndicator({ id: `main_${name}` });
1598
+ dispatch({
1599
+ type: "SET_MAIN_INDICATORS",
1600
+ indicators: state.mainIndicators.filter(
1601
+ (n) => n !== name
1602
+ )
1603
+ });
1604
+ } else {
1605
+ state.chart.removeIndicator({ id: `sub_${name}` });
1606
+ const newSub = { ...state.subIndicators };
1607
+ delete newSub[name];
1608
+ dispatch({
1609
+ type: "SET_SUB_INDICATORS",
1610
+ indicators: newSub
1611
+ });
1612
+ }
1613
+ }
1614
+ setRedoStack((prev) => [
1615
+ ...prev,
1616
+ {
1617
+ type: "indicator_toggled",
1618
+ data: {
1619
+ name,
1620
+ wasActive: !wasActive,
1621
+ isMain,
1622
+ paneId
1623
+ }
1624
+ }
1625
+ ]);
1626
+ break;
1627
+ }
1628
+ }
1629
+ } finally {
1630
+ isProcessingRef.current = false;
1631
+ }
1632
+ }, [undoStack, state.chart, state.mainIndicators, state.subIndicators, dispatch]);
1633
+ const redo = useCallback(() => {
1634
+ if (redoStack.length === 0 || !state.chart || isProcessingRef.current)
1635
+ return;
1636
+ isProcessingRef.current = true;
1637
+ const action = redoStack[redoStack.length - 1];
1638
+ setRedoStack((prev) => prev.slice(0, -1));
1639
+ try {
1640
+ switch (action.type) {
1641
+ case "overlay_added": {
1642
+ const { overlayData } = action.data;
1643
+ const newId = state.chart.createOverlay({
1644
+ ...overlayData,
1645
+ groupId: DRAWING_GROUP_ID2
1646
+ });
1647
+ setUndoStack((prev) => [
1648
+ ...prev,
1649
+ {
1650
+ type: "overlay_added",
1651
+ data: {
1652
+ id: typeof newId === "string" ? newId : action.data.id,
1653
+ overlayData
1654
+ }
1655
+ }
1656
+ ]);
1657
+ break;
1658
+ }
1659
+ case "overlays_removed": {
1660
+ const { overlays: overlays2 } = action.data;
1661
+ const snapshotOverlays = overlays2.map((o) => {
1662
+ const allOverlays = state.chart?.getOverlays({ id: o.id });
1663
+ const actual = allOverlays?.[0];
1664
+ return actual ? {
1665
+ name: actual.name,
1666
+ points: actual.points,
1667
+ styles: actual.styles,
1668
+ extendData: actual.extendData
1669
+ } : o;
1670
+ });
1671
+ for (const overlay of overlays2) {
1672
+ state.chart.removeOverlay({ id: overlay.id });
1673
+ }
1674
+ setUndoStack((prev) => [
1675
+ ...prev,
1676
+ {
1677
+ type: "overlays_removed",
1678
+ data: { overlays: snapshotOverlays }
1679
+ }
1680
+ ]);
1681
+ break;
1682
+ }
1683
+ case "indicator_toggled": {
1684
+ const { name, wasActive, isMain } = action.data;
1685
+ if (wasActive) {
1686
+ if (isMain) {
1687
+ state.chart.createIndicator(
1688
+ { name, id: `main_${name}`, paneId: "candle_pane" },
1689
+ true,
1690
+ { id: "candle_pane" }
1691
+ );
1692
+ dispatch({
1693
+ type: "SET_MAIN_INDICATORS",
1694
+ indicators: [...state.mainIndicators, name]
1695
+ });
1696
+ } else {
1697
+ state.chart.createIndicator({ name, id: `sub_${name}` });
1698
+ const newPaneId = state.chart.getIndicators({ id: `sub_${name}` })?.[0]?.paneId ?? "";
1699
+ dispatch({
1700
+ type: "SET_SUB_INDICATORS",
1701
+ indicators: {
1702
+ ...state.subIndicators,
1703
+ [name]: newPaneId
1704
+ }
1705
+ });
1706
+ }
1707
+ } else {
1708
+ if (isMain) {
1709
+ state.chart.removeIndicator({ id: `main_${name}` });
1710
+ dispatch({
1711
+ type: "SET_MAIN_INDICATORS",
1712
+ indicators: state.mainIndicators.filter(
1713
+ (n) => n !== name
1714
+ )
1715
+ });
1716
+ } else {
1717
+ state.chart.removeIndicator({ id: `sub_${name}` });
1718
+ const newSub = { ...state.subIndicators };
1719
+ delete newSub[name];
1720
+ dispatch({
1721
+ type: "SET_SUB_INDICATORS",
1722
+ indicators: newSub
1723
+ });
1724
+ }
1725
+ }
1726
+ setUndoStack((prev) => [
1727
+ ...prev,
1728
+ {
1729
+ type: "indicator_toggled",
1730
+ data: {
1731
+ name,
1732
+ wasActive: !wasActive,
1733
+ isMain
1734
+ }
1735
+ }
1736
+ ]);
1737
+ break;
1738
+ }
1739
+ }
1740
+ } finally {
1741
+ isProcessingRef.current = false;
1742
+ }
1743
+ }, [redoStack, state.chart, state.mainIndicators, state.subIndicators, dispatch]);
1744
+ useEffect(() => {
1745
+ const handleKeyDown = (e) => {
1746
+ const isCtrlOrMeta = e.ctrlKey || e.metaKey;
1747
+ if (!isCtrlOrMeta) return;
1748
+ if (e.key === "z" && !e.shiftKey) {
1749
+ e.preventDefault();
1750
+ undo();
1751
+ } else if (e.key === "y" || e.key === "z" && e.shiftKey) {
1752
+ e.preventDefault();
1753
+ redo();
1754
+ }
1755
+ };
1756
+ window.addEventListener("keydown", handleKeyDown);
1757
+ return () => window.removeEventListener("keydown", handleKeyDown);
1758
+ }, [undo, redo]);
1759
+ return {
1760
+ canUndo,
1761
+ canRedo,
1762
+ undo,
1763
+ redo,
1764
+ pushAction,
1765
+ clear
1766
+ };
1767
+ }
1768
+ var STORAGE_KEY_PREFIX = "klinecharts_layout:";
1769
+ var INDEX_KEY = "klinecharts_layout_index";
1770
+ var STATE_VERSION = "1.0";
1771
+ function generateId() {
1772
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
1773
+ /[xy]/g,
1774
+ (c) => {
1775
+ const r = Math.random() * 16 | 0;
1776
+ return (c === "x" ? r : r & 3 | 8).toString(16);
1777
+ }
1778
+ );
1779
+ }
1780
+ function getLayoutIds() {
1781
+ try {
1782
+ const raw = localStorage.getItem(INDEX_KEY);
1783
+ return raw ? JSON.parse(raw) : [];
1784
+ } catch {
1785
+ return [];
1786
+ }
1787
+ }
1788
+ function getEntry(id) {
1789
+ try {
1790
+ const raw = localStorage.getItem(STORAGE_KEY_PREFIX + id);
1791
+ return raw ? JSON.parse(raw) : null;
1792
+ } catch {
1793
+ return null;
1794
+ }
1795
+ }
1796
+ function useLayoutManager() {
1797
+ const { state, dispatch } = useKlinechartsUI();
1798
+ const [layouts, setLayouts] = useState([]);
1799
+ const [autoSaveEnabled, setAutoSaveEnabled] = useState(false);
1800
+ const autoSaveTimerRef = useRef(null);
1801
+ const autoSaveIdRef = useRef(null);
1802
+ const refreshLayouts = useCallback(() => {
1803
+ const entries = getLayoutIds().map((id) => getEntry(id)).filter((e) => e !== null);
1804
+ setLayouts(entries);
1805
+ }, []);
1806
+ useEffect(() => {
1807
+ refreshLayouts();
1808
+ }, [refreshLayouts]);
1809
+ const serializeState = useCallback(() => {
1810
+ const chart = state.chart;
1811
+ if (!chart) return null;
1812
+ const indicators2 = [];
1813
+ const indicatorMap = chart.getIndicatorByPaneId?.();
1814
+ if (indicatorMap) {
1815
+ indicatorMap.forEach((paneIndicators, paneId) => {
1816
+ paneIndicators.forEach((indicator) => {
1817
+ indicators2.push({
1818
+ paneId,
1819
+ name: indicator.name,
1820
+ calcParams: indicator.calcParams,
1821
+ visible: indicator.visible,
1822
+ ...indicator.styles ? { styles: indicator.styles } : {}
1823
+ });
1824
+ });
1825
+ });
1826
+ }
1827
+ const drawings = [];
1828
+ const allOverlays = chart.getOverlays({});
1829
+ if (allOverlays) {
1830
+ for (const overlay of allOverlays) {
1831
+ drawings.push({
1832
+ name: overlay.name,
1833
+ points: overlay.points,
1834
+ styles: overlay.styles,
1835
+ extendData: overlay.extendData
1836
+ });
1837
+ }
1838
+ }
1839
+ return {
1840
+ version: STATE_VERSION,
1841
+ meta: {
1842
+ symbol: state.symbol?.ticker ?? "",
1843
+ period: state.period?.label ?? "",
1844
+ timestamp: Date.now(),
1845
+ lastModified: Date.now()
1846
+ },
1847
+ indicators: indicators2,
1848
+ drawings
1849
+ };
1850
+ }, [state.chart, state.symbol, state.period]);
1851
+ const saveLayout = useCallback(
1852
+ (name) => {
1853
+ const chartState = serializeState();
1854
+ if (!chartState) return null;
1855
+ const id = generateId();
1856
+ const now = Date.now();
1857
+ const entry = {
1858
+ id,
1859
+ name: name.trim() || `Layout ${new Date(now).toLocaleString()}`,
1860
+ symbol: chartState.meta.symbol,
1861
+ period: chartState.meta.period,
1862
+ timestamp: now,
1863
+ lastModified: now,
1864
+ state: chartState
1865
+ };
1866
+ localStorage.setItem(
1867
+ STORAGE_KEY_PREFIX + id,
1868
+ JSON.stringify(entry)
1869
+ );
1870
+ const ids = getLayoutIds();
1871
+ if (!ids.includes(id)) {
1872
+ ids.push(id);
1873
+ localStorage.setItem(INDEX_KEY, JSON.stringify(ids));
1874
+ }
1875
+ refreshLayouts();
1876
+ return id;
1877
+ },
1878
+ [serializeState, refreshLayouts]
1879
+ );
1880
+ const loadLayout = useCallback(
1881
+ (id) => {
1882
+ const entry = getEntry(id);
1883
+ if (!entry || !state.chart) return false;
1884
+ const chartState = entry.state;
1885
+ if (chartState.version !== STATE_VERSION) return false;
1886
+ const chart = state.chart;
1887
+ chart.removeOverlay();
1888
+ const indicatorMap = chart.getIndicatorByPaneId?.();
1889
+ if (indicatorMap) {
1890
+ indicatorMap.forEach((_paneIndicators, paneId) => {
1891
+ _paneIndicators.forEach((_, indicatorName) => {
1892
+ chart.removeIndicator({ paneId, name: indicatorName });
1893
+ });
1894
+ });
1895
+ }
1896
+ const newMainIndicators = [];
1897
+ const newSubIndicators = {};
1898
+ if (chartState.indicators) {
1899
+ for (const ind of chartState.indicators) {
1900
+ chart.createIndicator(
1901
+ {
1902
+ name: ind.name,
1903
+ calcParams: ind.calcParams,
1904
+ visible: ind.visible
1905
+ },
1906
+ ind.paneId !== "candle_pane",
1907
+ { id: ind.paneId }
1908
+ );
1909
+ if (ind.styles) {
1910
+ chart.overrideIndicator({
1911
+ name: ind.name,
1912
+ paneId: ind.paneId,
1913
+ styles: ind.styles
1914
+ });
1915
+ }
1916
+ if (ind.paneId === "candle_pane") {
1917
+ newMainIndicators.push(ind.name);
1918
+ } else {
1919
+ newSubIndicators[ind.name] = ind.paneId;
1920
+ }
1921
+ }
1922
+ }
1923
+ dispatch({
1924
+ type: "SET_MAIN_INDICATORS",
1925
+ indicators: newMainIndicators
1926
+ });
1927
+ dispatch({
1928
+ type: "SET_SUB_INDICATORS",
1929
+ indicators: newSubIndicators
1930
+ });
1931
+ if (chartState.drawings) {
1932
+ for (const drawing of chartState.drawings) {
1933
+ chart.createOverlay({
1934
+ name: drawing.name,
1935
+ points: drawing.points,
1936
+ styles: drawing.styles,
1937
+ extendData: drawing.extendData
1938
+ });
1939
+ }
1940
+ }
1941
+ return true;
1942
+ },
1943
+ [state.chart, dispatch]
1944
+ );
1945
+ const deleteLayout = useCallback(
1946
+ (id) => {
1947
+ localStorage.removeItem(STORAGE_KEY_PREFIX + id);
1948
+ const ids = getLayoutIds().filter((i) => i !== id);
1949
+ localStorage.setItem(INDEX_KEY, JSON.stringify(ids));
1950
+ refreshLayouts();
1951
+ },
1952
+ [refreshLayouts]
1953
+ );
1954
+ const renameLayout = useCallback(
1955
+ (id, name) => {
1956
+ const entry = getEntry(id);
1957
+ if (!entry) return false;
1958
+ const updated = {
1959
+ ...entry,
1960
+ name: name.trim(),
1961
+ lastModified: Date.now()
1962
+ };
1963
+ localStorage.setItem(
1964
+ STORAGE_KEY_PREFIX + id,
1965
+ JSON.stringify(updated)
1966
+ );
1967
+ refreshLayouts();
1968
+ return true;
1969
+ },
1970
+ [refreshLayouts]
1971
+ );
1972
+ useEffect(() => {
1973
+ if (!autoSaveEnabled || !state.chart) return;
1974
+ if (autoSaveTimerRef.current) {
1975
+ clearTimeout(autoSaveTimerRef.current);
1976
+ }
1977
+ autoSaveTimerRef.current = setTimeout(() => {
1978
+ const chartState = serializeState();
1979
+ if (!chartState) return;
1980
+ if (autoSaveIdRef.current) {
1981
+ const entry = getEntry(autoSaveIdRef.current);
1982
+ if (entry) {
1983
+ const updated = {
1984
+ ...entry,
1985
+ lastModified: Date.now(),
1986
+ state: chartState
1987
+ };
1988
+ localStorage.setItem(
1989
+ STORAGE_KEY_PREFIX + autoSaveIdRef.current,
1990
+ JSON.stringify(updated)
1991
+ );
1992
+ refreshLayouts();
1993
+ }
1994
+ } else {
1995
+ const id = generateId();
1996
+ const now = Date.now();
1997
+ const entry = {
1998
+ id,
1999
+ name: "Auto-save",
2000
+ symbol: chartState.meta.symbol,
2001
+ period: chartState.meta.period,
2002
+ timestamp: now,
2003
+ lastModified: now,
2004
+ state: chartState
2005
+ };
2006
+ localStorage.setItem(
2007
+ STORAGE_KEY_PREFIX + id,
2008
+ JSON.stringify(entry)
2009
+ );
2010
+ const ids = getLayoutIds();
2011
+ ids.push(id);
2012
+ localStorage.setItem(INDEX_KEY, JSON.stringify(ids));
2013
+ autoSaveIdRef.current = id;
2014
+ refreshLayouts();
2015
+ }
2016
+ }, 5e3);
2017
+ return () => {
2018
+ if (autoSaveTimerRef.current) {
2019
+ clearTimeout(autoSaveTimerRef.current);
2020
+ }
2021
+ };
2022
+ }, [
2023
+ autoSaveEnabled,
2024
+ state.chart,
2025
+ state.mainIndicators,
2026
+ state.subIndicators,
2027
+ serializeState,
2028
+ refreshLayouts
2029
+ ]);
2030
+ return {
2031
+ layouts,
2032
+ saveLayout,
2033
+ loadLayout,
2034
+ deleteLayout,
2035
+ renameLayout,
2036
+ refreshLayouts,
2037
+ autoSaveEnabled,
2038
+ setAutoSaveEnabled
2039
+ };
2040
+ }
2041
+ var DEFAULT_SCRIPT = `// Custom Indicator \u2014 Pine Script Style
2042
+ // Available: TA, dataList, params
2043
+
2044
+ const period = params[0] ?? 14
2045
+ const closes = dataList.map(d => d.close)
2046
+ const highs = dataList.map(d => d.high)
2047
+ const lows = dataList.map(d => d.low)
2048
+
2049
+ // Example: RSI + Bollinger Bands
2050
+ const rsi = TA.rsi(closes, period)
2051
+ const boll = TA.bollinger(closes, period, 2)
2052
+
2053
+ // Return one object per candle \u2014 each key = one line on chart
2054
+ return rsi.map((v, i) => ({
2055
+ rsi: v,
2056
+ upper: boll.upper[i],
2057
+ mid: boll.mid[i],
2058
+ lower: boll.lower[i],
2059
+ }))`;
2060
+ var SERIES_COLORS = [
2061
+ "#2962FF",
2062
+ "#FF6D00",
2063
+ "#00BCD4",
2064
+ "#E91E63",
2065
+ "#76FF03",
2066
+ "#FFD600"
2067
+ ];
2068
+ var SHADOW_KEYS = [
2069
+ "fetch",
2070
+ "XMLHttpRequest",
2071
+ "WebSocket",
2072
+ "Worker",
2073
+ "SharedWorker",
2074
+ "importScripts",
2075
+ "self",
2076
+ "caches",
2077
+ "indexedDB"
2078
+ ];
2079
+ var scriptCounter = 0;
2080
+ function useScriptEditor() {
2081
+ const { state } = useKlinechartsUI();
2082
+ const [code, setCode] = useState(DEFAULT_SCRIPT);
2083
+ const [scriptName, setScriptName] = useState("");
2084
+ const [params, setParams] = useState("14");
2085
+ const [placement, setPlacement] = useState("sub");
2086
+ const [error, setError] = useState("");
2087
+ const [status, setStatus] = useState("");
2088
+ const [isRunning, setIsRunning] = useState(false);
2089
+ const activeIdRef = useRef(null);
2090
+ const activeNameRef = useRef(null);
2091
+ const hasActiveScript = activeNameRef.current !== null;
2092
+ const runScript = useCallback(() => {
2093
+ const chart = state.chart;
2094
+ if (!chart) {
2095
+ setError("Chart not initialized");
2096
+ return;
2097
+ }
2098
+ setError("");
2099
+ setStatus("Running...");
2100
+ setIsRunning(true);
2101
+ try {
2102
+ const parsedParams = params.split(",").map((s) => parseFloat(s.trim())).filter((n) => !isNaN(n));
2103
+ const dataList = chart.getDataList?.() ?? [];
2104
+ const allArgs = [
2105
+ ...SHADOW_KEYS,
2106
+ "TA",
2107
+ "dataList",
2108
+ "params",
2109
+ `"use strict";
2110
+ ${code}`
2111
+ ];
2112
+ const fn = new Function(...allArgs);
2113
+ const shadowValues = SHADOW_KEYS.map(() => void 0);
2114
+ const result = fn(...shadowValues, TA_default, dataList, parsedParams);
2115
+ if (!Array.isArray(result)) {
2116
+ throw new Error("Script must return an Array.");
2117
+ }
2118
+ const sample = result.find((v) => v !== null && v !== void 0);
2119
+ const seriesKeys = sample ? Object.keys(sample) : ["value"];
2120
+ if (seriesKeys.length === 0) {
2121
+ throw new Error("Returned objects have no keys.");
2122
+ }
2123
+ scriptCounter++;
2124
+ const indicatorName = `_custom_script_${scriptCounter}`;
2125
+ const figures = seriesKeys.map((key, i) => ({
2126
+ key,
2127
+ title: `${key}: `,
2128
+ type: "line",
2129
+ styles: () => ({ color: SERIES_COLORS[i % SERIES_COLORS.length] })
2130
+ }));
2131
+ registerIndicator({
2132
+ name: indicatorName,
2133
+ shortName: (scriptName.trim() || `Script #${scriptCounter}`) + (placement === "main" ? " (Main)" : " (Sub)"),
2134
+ calcParams: parsedParams,
2135
+ figures,
2136
+ calc: () => result,
2137
+ extendData: {
2138
+ isCustomScript: true,
2139
+ code,
2140
+ placement
2141
+ }
2142
+ });
2143
+ if (activeNameRef.current) {
2144
+ try {
2145
+ chart.removeIndicator({
2146
+ id: activeIdRef.current === "candle_pane" ? "candle_pane" : activeIdRef.current,
2147
+ name: activeNameRef.current
2148
+ });
2149
+ } catch {
2150
+ }
2151
+ }
2152
+ let paneId = null;
2153
+ if (placement === "main") {
2154
+ paneId = chart.createIndicator(
2155
+ { name: indicatorName },
2156
+ true,
2157
+ { id: "candle_pane" }
2158
+ );
2159
+ } else {
2160
+ paneId = chart.createIndicator({ name: indicatorName });
2161
+ }
2162
+ activeIdRef.current = typeof paneId === "string" ? paneId : null;
2163
+ activeNameRef.current = indicatorName;
2164
+ const title = scriptName.trim() || `Script #${scriptCounter}`;
2165
+ setStatus(
2166
+ `${title} applied to ${placement === "main" ? "Main Chart" : "Sub Pane"} \u2014 ${seriesKeys.length} series: ${seriesKeys.join(", ")}`
2167
+ );
2168
+ } catch (e) {
2169
+ setError(e?.message ?? String(e));
2170
+ } finally {
2171
+ setIsRunning(false);
2172
+ }
2173
+ }, [state.chart, code, scriptName, params, placement]);
2174
+ const removeScript = useCallback(() => {
2175
+ const chart = state.chart;
2176
+ if (chart && activeNameRef.current && activeIdRef.current) {
2177
+ try {
2178
+ chart.removeIndicator({
2179
+ id: activeIdRef.current,
2180
+ name: activeNameRef.current
2181
+ });
2182
+ } catch {
2183
+ }
2184
+ activeIdRef.current = null;
2185
+ activeNameRef.current = null;
2186
+ setStatus("Indicator removed.");
2187
+ }
2188
+ }, [state.chart]);
2189
+ const resetCode = useCallback(() => {
2190
+ setCode(DEFAULT_SCRIPT);
2191
+ setError("");
2192
+ setStatus("");
2193
+ }, []);
2194
+ const exportScript = useCallback(() => {
2195
+ const blob = new Blob([code], {
2196
+ type: "text/javascript;charset=utf-8"
2197
+ });
2198
+ const url = URL.createObjectURL(blob);
2199
+ const a = document.createElement("a");
2200
+ a.href = url;
2201
+ a.download = `${scriptName.trim() || "custom_indicator"}.js`;
2202
+ document.body.appendChild(a);
2203
+ a.click();
2204
+ document.body.removeChild(a);
2205
+ URL.revokeObjectURL(url);
2206
+ setStatus("Script exported.");
2207
+ }, [code, scriptName]);
2208
+ const importScript = useCallback(
2209
+ (file) => {
2210
+ const reader = new FileReader();
2211
+ reader.onload = (e) => {
2212
+ const content = e.target?.result;
2213
+ if (content) {
2214
+ setCode(content);
2215
+ if (!scriptName) {
2216
+ setScriptName(file.name.replace(/\.(js|ts|txt)$/i, ""));
2217
+ }
2218
+ setError("");
2219
+ setStatus(`Loaded ${file.name}`);
2220
+ }
2221
+ };
2222
+ reader.readAsText(file);
2223
+ },
2224
+ [scriptName]
2225
+ );
2226
+ return {
2227
+ code,
2228
+ setCode,
2229
+ scriptName,
2230
+ setScriptName,
2231
+ params,
2232
+ setParams,
2233
+ placement,
2234
+ setPlacement,
2235
+ error,
2236
+ status,
2237
+ isRunning,
2238
+ hasActiveScript,
2239
+ runScript,
2240
+ removeScript,
2241
+ resetCode,
2242
+ exportScript,
2243
+ importScript,
2244
+ defaultScript: DEFAULT_SCRIPT
2245
+ };
2246
+ }
1323
2247
 
1324
2248
  // src/utils/createDataLoader.ts
1325
2249
  function createDataLoader(datafeed, dispatch) {
@@ -1385,6 +2309,6 @@ function createDataLoader(datafeed, dispatch) {
1385
2309
  };
1386
2310
  }
1387
2311
 
1388
- 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, useDrawingTools, useFullscreen, useIndicators, useKlinechartsUI, useKlinechartsUILoading, useKlinechartsUISettings, useKlinechartsUITheme, useOrderLines, usePeriods, useScreenshot, useSymbolSearch, useTimezone };
2312
+ 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, useDrawingTools, useFullscreen, useIndicators, useKlinechartsUI, useKlinechartsUILoading, useKlinechartsUISettings, useKlinechartsUITheme, useLayoutManager, useOrderLines, usePeriods, useScreenshot, useScriptEditor, useSymbolSearch, useTimezone, useUndoRedo };
1389
2313
  //# sourceMappingURL=index.js.map
1390
2314
  //# sourceMappingURL=index.js.map