react-klinecharts-ui 0.2.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.js CHANGED
@@ -1,5 +1,5 @@
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';
1
+ import { registerExtensions, TA_default } from './chunk-U325AHHX.js';
2
+ export { TA_default as TA, abcd_default as abcd, anyWaves_default as anyWaves, arrow_default as arrow, bollTv_default as bollTv, brush_default as brush, cci_default as cci, circle_default as circle, depthOverlay_default as depthOverlay, eightWaves_default as eightWaves, elliottWave_default as elliottWave, fibRetracement_default as fibRetracement, fibonacciCircle_default as fibonacciCircle, fibonacciExtension_default as fibonacciExtension, fibonacciSegment_default as fibonacciSegment, fibonacciSpeedResistanceFan_default as fibonacciSpeedResistanceFan, fibonacciSpiral_default as fibonacciSpiral, fiveWaves_default as fiveWaves, gannBox_default as gannBox, gannFan_default as gannFan, hma_default as hma, ichimoku_default as ichimoku, indicators, longPosition_default as longPosition, maRibbon_default as maRibbon, macdTv_default as macdTv, measure_default as measure, orderLine_default as orderLine, overlays, parallelChannel_default as parallelChannel, parallelogram_default as parallelogram, pivotPoints_default as pivotPoints, ray_default as ray, rect_default as rect, registerExtensions, rsiTv_default as rsiTv, shortPosition_default as shortPosition, stochastic_default as stochastic, superTrend_default as superTrend, threeWaves_default as threeWaves, triangle_default as triangle, vwap_default as vwap, xabcd_default as xabcd } from './chunk-U325AHHX.js';
3
3
  import { createContext, useContext, useReducer, useRef, useCallback, useEffect, useMemo, useState } from 'react';
4
4
  import { registerOverlay, registerIndicator } from 'react-klinecharts';
5
5
  import { jsx } from 'react/jsx-runtime';
@@ -643,9 +643,12 @@ var INDICATOR_PARAMS = {
643
643
  };
644
644
 
645
645
  // src/hooks/useIndicators.ts
646
+ var COLLAPSED_HEIGHT = 30;
646
647
  function useIndicators() {
647
648
  const { state, dispatch } = useKlinechartsUI();
648
649
  const { undoRedoListenerRef } = useKlinechartsUIDispatch();
650
+ const paneHeightsRef = useRef({});
651
+ const collapsedPanesRef = useRef(/* @__PURE__ */ new Set());
649
652
  const mainIndicators = useMemo(() => {
650
653
  const activeNames = state.mainIndicators;
651
654
  const inactive = MAIN_INDICATORS.filter((n) => !activeNames.includes(n));
@@ -815,6 +818,90 @@ function useIndicators() {
815
818
  (name) => name in state.subIndicators,
816
819
  [state.subIndicators]
817
820
  );
821
+ const collapseSubIndicator = useCallback(
822
+ (name) => {
823
+ const paneId = state.subIndicators[name];
824
+ if (!state.chart || !paneId) return;
825
+ const currentSize = state.chart.getSize?.(paneId);
826
+ if (currentSize?.height && currentSize.height > COLLAPSED_HEIGHT) {
827
+ paneHeightsRef.current[paneId] = currentSize.height;
828
+ }
829
+ collapsedPanesRef.current.add(paneId);
830
+ state.chart.setPaneOptions?.({
831
+ id: paneId,
832
+ height: COLLAPSED_HEIGHT
833
+ });
834
+ state.chart.overrideIndicator({ name, id: `sub_${name}`, visible: false });
835
+ },
836
+ [state.chart, state.subIndicators]
837
+ );
838
+ const expandSubIndicator = useCallback(
839
+ (name) => {
840
+ const paneId = state.subIndicators[name];
841
+ if (!state.chart || !paneId) return;
842
+ const savedHeight = paneHeightsRef.current[paneId] ?? 100;
843
+ collapsedPanesRef.current.delete(paneId);
844
+ state.chart.setPaneOptions?.({
845
+ id: paneId,
846
+ height: savedHeight
847
+ });
848
+ state.chart.overrideIndicator({ name, id: `sub_${name}`, visible: true });
849
+ },
850
+ [state.chart, state.subIndicators]
851
+ );
852
+ const isSubIndicatorCollapsed = useCallback(
853
+ (name) => {
854
+ const paneId = state.subIndicators[name];
855
+ return paneId ? collapsedPanesRef.current.has(paneId) : false;
856
+ },
857
+ [state.subIndicators]
858
+ );
859
+ const reorderSubIndicator = useCallback(
860
+ (name, direction) => {
861
+ if (!state.chart) return;
862
+ const subNames = Object.keys(state.subIndicators);
863
+ const idx = subNames.indexOf(name);
864
+ if (idx === -1) return;
865
+ const swapIdx = direction === "up" ? idx - 1 : idx + 1;
866
+ if (swapIdx < 0 || swapIdx >= subNames.length) return;
867
+ const subStates = subNames.map((n) => {
868
+ const id = `sub_${n}`;
869
+ const indicator = state.chart.getIndicators({ id })?.[0];
870
+ return {
871
+ name: n,
872
+ calcParams: indicator?.calcParams,
873
+ visible: indicator?.visible ?? true,
874
+ styles: indicator?.styles,
875
+ paneId: state.subIndicators[n]
876
+ };
877
+ });
878
+ [subStates[idx], subStates[swapIdx]] = [subStates[swapIdx], subStates[idx]];
879
+ for (const n of subNames) {
880
+ state.chart.removeIndicator({ id: `sub_${n}` });
881
+ }
882
+ const newSubIndicators = {};
883
+ for (const sub of subStates) {
884
+ const id = `sub_${sub.name}`;
885
+ state.chart.createIndicator({
886
+ name: sub.name,
887
+ id,
888
+ ...sub.calcParams ? { calcParams: sub.calcParams } : {},
889
+ visible: sub.visible
890
+ });
891
+ const paneId = state.chart.getIndicators({ id })?.[0]?.paneId ?? "";
892
+ newSubIndicators[sub.name] = paneId;
893
+ if (sub.styles) {
894
+ state.chart.overrideIndicator({
895
+ name: sub.name,
896
+ paneId,
897
+ styles: sub.styles
898
+ });
899
+ }
900
+ }
901
+ dispatch({ type: "SET_SUB_INDICATORS", indicators: newSubIndicators });
902
+ },
903
+ [state.chart, state.subIndicators, dispatch]
904
+ );
818
905
  return {
819
906
  mainIndicators,
820
907
  subIndicators,
@@ -834,7 +921,11 @@ function useIndicators() {
834
921
  updateIndicatorParams,
835
922
  getIndicatorParams,
836
923
  isMainIndicatorActive,
837
- isSubIndicatorActive
924
+ isSubIndicatorActive,
925
+ collapseSubIndicator,
926
+ expandSubIndicator,
927
+ isSubIndicatorCollapsed,
928
+ reorderSubIndicator
838
929
  };
839
930
  }
840
931
 
@@ -933,6 +1024,17 @@ function useDrawingTools() {
933
1024
  const [magnetMode, setMagnetModeState] = useState("normal");
934
1025
  const [isLocked, setIsLocked] = useState(false);
935
1026
  const [isVisible, setIsVisible] = useState(true);
1027
+ const [autoRetrigger, setAutoRetrigger] = useState(true);
1028
+ const activeToolRef = useRef(activeTool);
1029
+ activeToolRef.current = activeTool;
1030
+ const autoRetriggerRef = useRef(autoRetrigger);
1031
+ autoRetriggerRef.current = autoRetrigger;
1032
+ const isLockedRef = useRef(isLocked);
1033
+ isLockedRef.current = isLocked;
1034
+ const isVisibleRef = useRef(isVisible);
1035
+ isVisibleRef.current = isVisible;
1036
+ const magnetModeRef = useRef(magnetMode);
1037
+ magnetModeRef.current = magnetMode;
936
1038
  const categories = useMemo(
937
1039
  () => DRAWING_CATEGORIES.map((cat) => ({
938
1040
  key: cat.key,
@@ -943,15 +1045,15 @@ function useDrawingTools() {
943
1045
  })),
944
1046
  []
945
1047
  );
946
- const selectTool = useCallback(
1048
+ const createOverlayForTool = useCallback(
947
1049
  (name) => {
948
- setActiveTool(name);
1050
+ const mode = magnetModeRef.current === "strong" ? "strong_magnet" : magnetModeRef.current === "weak" ? "weak_magnet" : "normal";
949
1051
  state.chart?.createOverlay({
950
1052
  name,
951
1053
  groupId: DRAWING_GROUP_ID,
952
- lock: isLocked,
953
- visible: isVisible,
954
- mode: magnetMode === "strong" ? "strong_magnet" : magnetMode === "weak" ? "weak_magnet" : "normal",
1054
+ lock: isLockedRef.current,
1055
+ visible: isVisibleRef.current,
1056
+ mode,
955
1057
  onDrawEnd: (event) => {
956
1058
  const o = event.overlay;
957
1059
  undoRedoListenerRef.current?.({
@@ -966,10 +1068,22 @@ function useDrawingTools() {
966
1068
  }
967
1069
  }
968
1070
  });
1071
+ if (autoRetriggerRef.current && activeToolRef.current === name) {
1072
+ requestAnimationFrame(() => {
1073
+ createOverlayForTool(name);
1074
+ });
1075
+ }
969
1076
  }
970
1077
  });
971
1078
  },
972
- [state.chart, isLocked, isVisible, magnetMode, undoRedoListenerRef]
1079
+ [state.chart, undoRedoListenerRef]
1080
+ );
1081
+ const selectTool = useCallback(
1082
+ (name) => {
1083
+ setActiveTool(name);
1084
+ createOverlayForTool(name);
1085
+ },
1086
+ [createOverlayForTool]
973
1087
  );
974
1088
  const clearActiveTool = useCallback(() => {
975
1089
  setActiveTool(null);
@@ -1040,12 +1154,14 @@ function useDrawingTools() {
1040
1154
  magnetMode,
1041
1155
  isLocked,
1042
1156
  isVisible,
1157
+ autoRetrigger,
1043
1158
  selectTool,
1044
1159
  clearActiveTool,
1045
1160
  setMagnetMode,
1046
1161
  toggleLock,
1047
1162
  toggleVisibility,
1048
- removeAllDrawings
1163
+ removeAllDrawings,
1164
+ setAutoRetrigger
1049
1165
  };
1050
1166
  }
1051
1167
 
@@ -2244,6 +2360,750 @@ ${code}`
2244
2360
  defaultScript: DEFAULT_SCRIPT
2245
2361
  };
2246
2362
  }
2363
+ function useCrosshair() {
2364
+ const { state } = useKlinechartsUI();
2365
+ const [barData, setBarData] = useState(null);
2366
+ const rafRef = useRef(0);
2367
+ const handler = useCallback(
2368
+ (event) => {
2369
+ if (rafRef.current) {
2370
+ cancelAnimationFrame(rafRef.current);
2371
+ }
2372
+ rafRef.current = requestAnimationFrame(() => {
2373
+ rafRef.current = 0;
2374
+ const klineData = event?.kLineData;
2375
+ if (!klineData) {
2376
+ setBarData(null);
2377
+ return;
2378
+ }
2379
+ const pricePrecision = state.symbol?.pricePrecision ?? 2;
2380
+ const open = klineData.open ?? 0;
2381
+ const close = klineData.close ?? 0;
2382
+ const change = parseFloat((close - open).toFixed(pricePrecision));
2383
+ const changePercent = open !== 0 ? parseFloat((change / open * 100).toFixed(2)) : 0;
2384
+ setBarData({
2385
+ open,
2386
+ high: klineData.high ?? 0,
2387
+ low: klineData.low ?? 0,
2388
+ close,
2389
+ volume: klineData.volume ?? 0,
2390
+ timestamp: klineData.timestamp ?? 0,
2391
+ change,
2392
+ changePercent
2393
+ });
2394
+ });
2395
+ },
2396
+ [state.symbol?.pricePrecision]
2397
+ );
2398
+ useEffect(() => {
2399
+ const chart = state.chart;
2400
+ if (!chart) return;
2401
+ chart.subscribeAction?.("onCrosshairChange", handler);
2402
+ return () => {
2403
+ if (rafRef.current) {
2404
+ cancelAnimationFrame(rafRef.current);
2405
+ rafRef.current = 0;
2406
+ }
2407
+ chart.unsubscribeAction?.("onCrosshairChange", handler);
2408
+ };
2409
+ }, [state.chart, handler]);
2410
+ return { barData };
2411
+ }
2412
+ var alertCounter = 0;
2413
+ function useAlerts() {
2414
+ const { state } = useKlinechartsUI();
2415
+ const [alerts, setAlerts] = useState([]);
2416
+ const callbackRef = useRef(null);
2417
+ const prevCloseRef = useRef(null);
2418
+ const addAlert = useCallback(
2419
+ (price, condition, message) => {
2420
+ const id = `alert_${++alertCounter}`;
2421
+ const alert = {
2422
+ id,
2423
+ price,
2424
+ condition,
2425
+ message,
2426
+ triggered: false
2427
+ };
2428
+ setAlerts((prev) => [...prev, alert]);
2429
+ if (state.chart) {
2430
+ state.chart.createOverlay({
2431
+ name: "horizontalStraightLine",
2432
+ id,
2433
+ groupId: "price_alerts",
2434
+ points: [{ value: price }],
2435
+ styles: {
2436
+ line: {
2437
+ style: "dashed",
2438
+ color: "#ff9800",
2439
+ size: 1
2440
+ }
2441
+ },
2442
+ lock: true
2443
+ });
2444
+ }
2445
+ return id;
2446
+ },
2447
+ [state.chart]
2448
+ );
2449
+ const removeAlert = useCallback(
2450
+ (id) => {
2451
+ setAlerts((prev) => prev.filter((a) => a.id !== id));
2452
+ state.chart?.removeOverlay({ id });
2453
+ },
2454
+ [state.chart]
2455
+ );
2456
+ const clearAlerts = useCallback(() => {
2457
+ setAlerts((prev) => {
2458
+ for (const alert of prev) {
2459
+ state.chart?.removeOverlay({ id: alert.id });
2460
+ }
2461
+ return [];
2462
+ });
2463
+ }, [state.chart]);
2464
+ const onAlertTriggered = useCallback(
2465
+ (callback) => {
2466
+ callbackRef.current = callback;
2467
+ },
2468
+ []
2469
+ );
2470
+ useEffect(() => {
2471
+ if (!state.chart) return;
2472
+ const interval = setInterval(() => {
2473
+ const dataList = state.chart?.getDataList();
2474
+ if (!dataList || dataList.length === 0) return;
2475
+ const lastBar = dataList[dataList.length - 1];
2476
+ const currentClose = lastBar.close;
2477
+ const prevClose = prevCloseRef.current;
2478
+ if (prevClose === null) {
2479
+ prevCloseRef.current = currentClose;
2480
+ return;
2481
+ }
2482
+ prevCloseRef.current = currentClose;
2483
+ setAlerts((prev) => {
2484
+ let changed = false;
2485
+ const next = prev.map((alert) => {
2486
+ if (alert.triggered) return alert;
2487
+ let shouldTrigger = false;
2488
+ if (alert.condition === "crossing_up") {
2489
+ shouldTrigger = prevClose < alert.price && currentClose >= alert.price;
2490
+ } else if (alert.condition === "crossing_down") {
2491
+ shouldTrigger = prevClose > alert.price && currentClose <= alert.price;
2492
+ } else if (alert.condition === "crossing") {
2493
+ shouldTrigger = prevClose < alert.price && currentClose >= alert.price || prevClose > alert.price && currentClose <= alert.price;
2494
+ }
2495
+ if (shouldTrigger) {
2496
+ changed = true;
2497
+ const triggered = { ...alert, triggered: true };
2498
+ callbackRef.current?.(triggered);
2499
+ return triggered;
2500
+ }
2501
+ return alert;
2502
+ });
2503
+ return changed ? next : prev;
2504
+ });
2505
+ }, 1e3);
2506
+ return () => clearInterval(interval);
2507
+ }, [state.chart]);
2508
+ return {
2509
+ alerts,
2510
+ addAlert,
2511
+ removeAlert,
2512
+ clearAlerts,
2513
+ onAlertTriggered
2514
+ };
2515
+ }
2516
+ function useDataExport() {
2517
+ const { state } = useKlinechartsUI();
2518
+ const exportAll = useCallback(
2519
+ (format) => {
2520
+ if (!state.chart) return;
2521
+ const dataList = state.chart.getDataList();
2522
+ if (!dataList || dataList.length === 0) return;
2523
+ downloadData(dataList, format, state.symbol?.ticker);
2524
+ },
2525
+ [state.chart, state.symbol?.ticker]
2526
+ );
2527
+ const exportVisible = useCallback(
2528
+ (format) => {
2529
+ if (!state.chart) return;
2530
+ const dataList = state.chart.getDataList();
2531
+ if (!dataList || dataList.length === 0) return;
2532
+ const visibleRange = state.chart.getVisibleRange?.();
2533
+ if (!visibleRange) {
2534
+ downloadData(dataList, format, state.symbol?.ticker);
2535
+ return;
2536
+ }
2537
+ const { from, to } = visibleRange;
2538
+ const visibleData = dataList.slice(from, to);
2539
+ downloadData(visibleData, format, state.symbol?.ticker);
2540
+ },
2541
+ [state.chart, state.symbol?.ticker]
2542
+ );
2543
+ return { exportAll, exportVisible };
2544
+ }
2545
+ function formatRow(bar) {
2546
+ return {
2547
+ date: new Date(bar.timestamp).toISOString(),
2548
+ open: bar.open,
2549
+ high: bar.high,
2550
+ low: bar.low,
2551
+ close: bar.close,
2552
+ volume: bar.volume ?? 0
2553
+ };
2554
+ }
2555
+ function buildCsv(dataList) {
2556
+ const header = "Date,Open,High,Low,Close,Volume\n";
2557
+ const rows = dataList.map((bar) => {
2558
+ const r = formatRow(bar);
2559
+ return `${r.date},${r.open},${r.high},${r.low},${r.close},${r.volume}`;
2560
+ });
2561
+ return header + rows.join("\n");
2562
+ }
2563
+ function buildJson(dataList) {
2564
+ return JSON.stringify(dataList.map(formatRow), null, 2);
2565
+ }
2566
+ function downloadData(dataList, format, ticker) {
2567
+ const content = format === "csv" ? buildCsv(dataList) : buildJson(dataList);
2568
+ const mimeType = format === "csv" ? "text/csv" : "application/json";
2569
+ const blob = new Blob([content], { type: mimeType });
2570
+ const url = URL.createObjectURL(blob);
2571
+ const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
2572
+ const prefix = ticker ?? "chart";
2573
+ const filename = `${prefix}_${date}.${format}`;
2574
+ const link = document.createElement("a");
2575
+ link.href = url;
2576
+ link.download = filename;
2577
+ document.body.appendChild(link);
2578
+ link.click();
2579
+ document.body.removeChild(link);
2580
+ URL.revokeObjectURL(url);
2581
+ }
2582
+ function useWatchlist() {
2583
+ const { state } = useKlinechartsUI();
2584
+ const { dispatch, datafeed } = useKlinechartsUIDispatch();
2585
+ const [items, setItems] = useState([]);
2586
+ const subscriptionsRef = useRef(/* @__PURE__ */ new Map());
2587
+ const activeSymbol = state.symbol?.ticker ?? null;
2588
+ const addSymbol = useCallback(
2589
+ (ticker) => {
2590
+ setItems((prev) => {
2591
+ if (prev.some((item) => item.ticker === ticker)) return prev;
2592
+ return [
2593
+ ...prev,
2594
+ { ticker, lastPrice: null, change: null, changePercent: null }
2595
+ ];
2596
+ });
2597
+ if (subscriptionsRef.current.has(ticker)) return;
2598
+ const symbolInfo = { ticker };
2599
+ const period = state.period;
2600
+ subscriptionsRef.current.set(ticker, { symbolInfo, period });
2601
+ datafeed.subscribe(symbolInfo, period, (bar) => {
2602
+ setItems(
2603
+ (prev) => prev.map((item) => {
2604
+ if (item.ticker !== ticker) return item;
2605
+ const change = bar.close - bar.open;
2606
+ const changePercent = bar.open !== 0 ? change / bar.open * 100 : null;
2607
+ return {
2608
+ ...item,
2609
+ lastPrice: bar.close,
2610
+ change,
2611
+ changePercent
2612
+ };
2613
+ })
2614
+ );
2615
+ });
2616
+ },
2617
+ [datafeed, state.period]
2618
+ );
2619
+ const removeSymbol = useCallback(
2620
+ (ticker) => {
2621
+ const sub = subscriptionsRef.current.get(ticker);
2622
+ if (sub) {
2623
+ datafeed.unsubscribe(sub.symbolInfo, sub.period);
2624
+ subscriptionsRef.current.delete(ticker);
2625
+ }
2626
+ setItems((prev) => prev.filter((item) => item.ticker !== ticker));
2627
+ },
2628
+ [datafeed]
2629
+ );
2630
+ const switchSymbol = useCallback(
2631
+ (ticker) => {
2632
+ dispatch({ type: "SET_SYMBOL", symbol: { ticker } });
2633
+ },
2634
+ [dispatch]
2635
+ );
2636
+ useEffect(() => {
2637
+ return () => {
2638
+ subscriptionsRef.current.forEach((sub) => {
2639
+ datafeed.unsubscribe(sub.symbolInfo, sub.period);
2640
+ });
2641
+ subscriptionsRef.current.clear();
2642
+ };
2643
+ }, [datafeed]);
2644
+ return {
2645
+ items,
2646
+ addSymbol,
2647
+ removeSymbol,
2648
+ switchSymbol,
2649
+ activeSymbol
2650
+ };
2651
+ }
2652
+ function useReplay() {
2653
+ const { state } = useKlinechartsUI();
2654
+ const [isReplaying, setIsReplaying] = useState(false);
2655
+ const [isPaused, setIsPaused] = useState(false);
2656
+ const [speed, setSpeedState] = useState(1);
2657
+ const [barIndex, setBarIndex] = useState(0);
2658
+ const [totalBars, setTotalBars] = useState(0);
2659
+ const intervalRef = useRef(null);
2660
+ const savedDataRef = useRef([]);
2661
+ const currentIndexRef = useRef(0);
2662
+ const clearInterval_ = useCallback(() => {
2663
+ if (intervalRef.current !== null) {
2664
+ clearInterval(intervalRef.current);
2665
+ intervalRef.current = null;
2666
+ }
2667
+ }, []);
2668
+ const addNextBar = useCallback(() => {
2669
+ if (!state.chart) return;
2670
+ const data = savedDataRef.current;
2671
+ const idx = currentIndexRef.current;
2672
+ if (idx >= data.length) {
2673
+ clearInterval_();
2674
+ setIsPaused(true);
2675
+ return;
2676
+ }
2677
+ const bar = data[idx];
2678
+ state.chart.updateData?.(bar);
2679
+ currentIndexRef.current = idx + 1;
2680
+ setBarIndex(idx + 1);
2681
+ }, [state.chart, clearInterval_]);
2682
+ const startInterval = useCallback(
2683
+ (currentSpeed) => {
2684
+ clearInterval_();
2685
+ intervalRef.current = setInterval(addNextBar, 1e3 / currentSpeed);
2686
+ },
2687
+ [addNextBar, clearInterval_]
2688
+ );
2689
+ const startReplay = useCallback(() => {
2690
+ if (!state.chart) return;
2691
+ const dataList = state.chart.getDataList();
2692
+ if (!dataList || dataList.length === 0) return;
2693
+ savedDataRef.current = [...dataList];
2694
+ currentIndexRef.current = 0;
2695
+ setTotalBars(dataList.length);
2696
+ setBarIndex(0);
2697
+ setIsReplaying(true);
2698
+ setIsPaused(false);
2699
+ state.chart.clearData?.();
2700
+ startInterval(speed);
2701
+ }, [state.chart, speed, startInterval]);
2702
+ const stopReplay = useCallback(() => {
2703
+ if (!state.chart) return;
2704
+ clearInterval_();
2705
+ const data = savedDataRef.current;
2706
+ if (data.length > 0) {
2707
+ state.chart.clearData?.();
2708
+ for (const bar of data) {
2709
+ state.chart.updateData?.(bar);
2710
+ }
2711
+ }
2712
+ savedDataRef.current = [];
2713
+ currentIndexRef.current = 0;
2714
+ setIsReplaying(false);
2715
+ setIsPaused(false);
2716
+ setBarIndex(0);
2717
+ setTotalBars(0);
2718
+ }, [state.chart, clearInterval_]);
2719
+ const togglePause = useCallback(() => {
2720
+ if (!isReplaying) return;
2721
+ if (isPaused) {
2722
+ startInterval(speed);
2723
+ setIsPaused(false);
2724
+ } else {
2725
+ clearInterval_();
2726
+ setIsPaused(true);
2727
+ }
2728
+ }, [isReplaying, isPaused, speed, startInterval, clearInterval_]);
2729
+ const stepForward = useCallback(() => {
2730
+ if (!isReplaying || !isPaused) return;
2731
+ addNextBar();
2732
+ }, [isReplaying, isPaused, addNextBar]);
2733
+ const stepBackward = useCallback(() => {
2734
+ if (!isReplaying || !isPaused || !state.chart) return;
2735
+ const idx = currentIndexRef.current;
2736
+ if (idx <= 1) return;
2737
+ const data = savedDataRef.current;
2738
+ state.chart.clearData?.();
2739
+ for (let i = 0; i < idx - 1; i++) {
2740
+ state.chart.updateData?.(data[i]);
2741
+ }
2742
+ currentIndexRef.current = idx - 1;
2743
+ setBarIndex(idx - 1);
2744
+ }, [isReplaying, isPaused, state.chart]);
2745
+ const seekTo = useCallback(
2746
+ (targetIndex) => {
2747
+ if (!isReplaying || !state.chart) return;
2748
+ const data = savedDataRef.current;
2749
+ const clamped = Math.max(0, Math.min(targetIndex, data.length));
2750
+ clearInterval_();
2751
+ setIsPaused(true);
2752
+ state.chart.clearData?.();
2753
+ for (let i = 0; i < clamped; i++) {
2754
+ state.chart.updateData?.(data[i]);
2755
+ }
2756
+ currentIndexRef.current = clamped;
2757
+ setBarIndex(clamped);
2758
+ },
2759
+ [isReplaying, state.chart, clearInterval_]
2760
+ );
2761
+ const setSpeed = useCallback(
2762
+ (newSpeed) => {
2763
+ setSpeedState(newSpeed);
2764
+ if (isReplaying && !isPaused) {
2765
+ startInterval(newSpeed);
2766
+ }
2767
+ },
2768
+ [isReplaying, isPaused, startInterval]
2769
+ );
2770
+ useEffect(() => {
2771
+ return () => {
2772
+ clearInterval_();
2773
+ };
2774
+ }, [clearInterval_]);
2775
+ return {
2776
+ isReplaying,
2777
+ isPaused,
2778
+ speed,
2779
+ barIndex,
2780
+ totalBars,
2781
+ startReplay,
2782
+ stopReplay,
2783
+ togglePause,
2784
+ stepForward,
2785
+ stepBackward,
2786
+ seekTo,
2787
+ setSpeed
2788
+ };
2789
+ }
2790
+ var DEFAULT_COLORS = [
2791
+ "#2196f3",
2792
+ "#ff9800",
2793
+ "#4caf50",
2794
+ "#e91e63",
2795
+ "#9c27b0",
2796
+ "#00bcd4",
2797
+ "#ff5722",
2798
+ "#8bc34a"
2799
+ ];
2800
+ var colorIndex = 0;
2801
+ var compareCounter = 0;
2802
+ function useCompare() {
2803
+ const { state, datafeed } = useKlinechartsUI();
2804
+ const [symbols, setSymbols] = useState([]);
2805
+ const indicatorsRef = useRef(/* @__PURE__ */ new Map());
2806
+ const addSymbol = useCallback(
2807
+ async (ticker, color) => {
2808
+ if (!state.chart || !datafeed) return;
2809
+ if (indicatorsRef.current.has(ticker)) return;
2810
+ const assignedColor = color ?? DEFAULT_COLORS[colorIndex++ % DEFAULT_COLORS.length];
2811
+ const mainDataList = state.chart.getDataList();
2812
+ if (!mainDataList || mainDataList.length === 0) return;
2813
+ const from = mainDataList[0].timestamp;
2814
+ const to = mainDataList[mainDataList.length - 1].timestamp;
2815
+ const compareData = await datafeed.getHistoryKLineData(
2816
+ { ticker },
2817
+ { ...state.period, label: "" },
2818
+ from,
2819
+ to
2820
+ );
2821
+ if (!compareData || compareData.length === 0) return;
2822
+ const compareMap = /* @__PURE__ */ new Map();
2823
+ for (const bar of compareData) {
2824
+ compareMap.set(bar.timestamp, bar.close);
2825
+ }
2826
+ let basePrice = null;
2827
+ for (const bar of mainDataList) {
2828
+ const close = compareMap.get(bar.timestamp);
2829
+ if (close != null) {
2830
+ basePrice = close;
2831
+ break;
2832
+ }
2833
+ }
2834
+ if (basePrice === null || basePrice === 0) return;
2835
+ const bp = basePrice;
2836
+ const normalizedData = mainDataList.map((mainBar) => {
2837
+ const close = compareMap.get(mainBar.timestamp);
2838
+ return {
2839
+ pct: close != null ? (close - bp) / bp * 100 : NaN
2840
+ };
2841
+ });
2842
+ const indicatorName = `__cmp_${++compareCounter}_${ticker}`;
2843
+ registerIndicator({
2844
+ name: indicatorName,
2845
+ shortName: ticker,
2846
+ figures: [
2847
+ {
2848
+ key: "pct",
2849
+ title: `${ticker} %: `,
2850
+ type: "line",
2851
+ styles: () => ({ color: assignedColor })
2852
+ }
2853
+ ],
2854
+ calc: () => normalizedData
2855
+ });
2856
+ const paneId = state.chart.createIndicator(
2857
+ { name: indicatorName },
2858
+ true,
2859
+ { id: "candle_pane" }
2860
+ );
2861
+ indicatorsRef.current.set(ticker, {
2862
+ name: indicatorName,
2863
+ paneId: typeof paneId === "string" ? paneId : "candle_pane"
2864
+ });
2865
+ setSymbols((prev) => {
2866
+ if (prev.some((s) => s.ticker === ticker)) return prev;
2867
+ return [
2868
+ ...prev,
2869
+ { ticker, basePrice: bp, color: assignedColor, visible: true }
2870
+ ];
2871
+ });
2872
+ },
2873
+ [state.chart, state.period, datafeed]
2874
+ );
2875
+ const removeSymbol = useCallback(
2876
+ (ticker) => {
2877
+ const info = indicatorsRef.current.get(ticker);
2878
+ if (info && state.chart) {
2879
+ try {
2880
+ state.chart.removeIndicator({ name: info.name });
2881
+ } catch {
2882
+ }
2883
+ indicatorsRef.current.delete(ticker);
2884
+ }
2885
+ setSymbols((prev) => prev.filter((s) => s.ticker !== ticker));
2886
+ },
2887
+ [state.chart]
2888
+ );
2889
+ const toggleSymbol = useCallback(
2890
+ (ticker) => {
2891
+ const info = indicatorsRef.current.get(ticker);
2892
+ if (!info || !state.chart) return;
2893
+ setSymbols((prev) => {
2894
+ const sym = prev.find((s) => s.ticker === ticker);
2895
+ if (!sym) return prev;
2896
+ const newVisible = !sym.visible;
2897
+ state.chart?.overrideIndicator({
2898
+ name: info.name,
2899
+ visible: newVisible
2900
+ });
2901
+ return prev.map(
2902
+ (s) => s.ticker === ticker ? { ...s, visible: newVisible } : s
2903
+ );
2904
+ });
2905
+ },
2906
+ [state.chart]
2907
+ );
2908
+ const clearAll = useCallback(() => {
2909
+ indicatorsRef.current.forEach((info) => {
2910
+ try {
2911
+ state.chart?.removeIndicator({ name: info.name });
2912
+ } catch {
2913
+ }
2914
+ });
2915
+ indicatorsRef.current.clear();
2916
+ setSymbols([]);
2917
+ }, [state.chart]);
2918
+ useEffect(() => {
2919
+ return () => {
2920
+ indicatorsRef.current.forEach((info) => {
2921
+ try {
2922
+ state.chart?.removeIndicator({ name: info.name });
2923
+ } catch {
2924
+ }
2925
+ });
2926
+ indicatorsRef.current.clear();
2927
+ };
2928
+ }, [state.chart]);
2929
+ return { symbols, addSymbol, removeSymbol, toggleSymbol, clearAll };
2930
+ }
2931
+ var MEASURE_OVERLAY_ID = "__measure_line__";
2932
+ function useMeasure() {
2933
+ const { state } = useKlinechartsUI();
2934
+ const [isActive, setIsActive] = useState(false);
2935
+ const [fromPoint, setFromPoint] = useState(null);
2936
+ const [result, setResult] = useState(null);
2937
+ const clickCountRef = useRef(0);
2938
+ const computeResult = useCallback(
2939
+ (from, to) => {
2940
+ const priceDiff = to.price - from.price;
2941
+ const pricePercent = from.price !== 0 ? priceDiff / from.price * 100 : 0;
2942
+ const bars = Math.abs(to.barIndex - from.barIndex);
2943
+ const timeDiff = Math.abs(to.timestamp - from.timestamp);
2944
+ return { from, to, priceDiff, pricePercent, bars, timeDiff };
2945
+ },
2946
+ []
2947
+ );
2948
+ const cleanup = useCallback(() => {
2949
+ state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
2950
+ }, [state.chart]);
2951
+ const startMeasure = useCallback(() => {
2952
+ cleanup();
2953
+ setIsActive(true);
2954
+ setFromPoint(null);
2955
+ setResult(null);
2956
+ clickCountRef.current = 0;
2957
+ if (!state.chart) return;
2958
+ state.chart.createOverlay({
2959
+ name: "segment",
2960
+ id: MEASURE_OVERLAY_ID,
2961
+ groupId: "measure",
2962
+ styles: {
2963
+ line: {
2964
+ style: "dashed",
2965
+ color: "#FFD600",
2966
+ size: 1
2967
+ }
2968
+ },
2969
+ lock: true,
2970
+ onDrawEnd: (event) => {
2971
+ const overlay = event?.overlay;
2972
+ const points = overlay?.points;
2973
+ if (!points || points.length < 2) return;
2974
+ const chart = state.chart;
2975
+ if (!chart) return;
2976
+ const dataList = chart.getDataList();
2977
+ if (!dataList || dataList.length === 0) return;
2978
+ const makePoint = (p) => {
2979
+ const idx = Math.max(
2980
+ 0,
2981
+ Math.min(p.dataIndex ?? 0, dataList.length - 1)
2982
+ );
2983
+ const bar = dataList[idx];
2984
+ return {
2985
+ price: p.value ?? bar?.close ?? 0,
2986
+ timestamp: bar?.timestamp ?? 0,
2987
+ barIndex: idx
2988
+ };
2989
+ };
2990
+ const from = makePoint(points[0]);
2991
+ const to = makePoint(points[1]);
2992
+ setFromPoint(from);
2993
+ setResult(computeResult(from, to));
2994
+ setIsActive(false);
2995
+ }
2996
+ });
2997
+ }, [state.chart, cleanup, computeResult]);
2998
+ const cancelMeasure = useCallback(() => {
2999
+ cleanup();
3000
+ setIsActive(false);
3001
+ setFromPoint(null);
3002
+ clickCountRef.current = 0;
3003
+ }, [cleanup]);
3004
+ const clearResult = useCallback(() => {
3005
+ cleanup();
3006
+ setResult(null);
3007
+ setFromPoint(null);
3008
+ }, [cleanup]);
3009
+ useEffect(() => {
3010
+ return () => {
3011
+ state.chart?.removeOverlay({ id: MEASURE_OVERLAY_ID });
3012
+ };
3013
+ }, [state.chart]);
3014
+ return {
3015
+ isActive,
3016
+ startMeasure,
3017
+ cancelMeasure,
3018
+ result,
3019
+ clearResult,
3020
+ fromPoint
3021
+ };
3022
+ }
3023
+ var annotationCounter = 0;
3024
+ function useAnnotations() {
3025
+ const { state } = useKlinechartsUI();
3026
+ const [annotations, setAnnotations] = useState([]);
3027
+ const addAnnotation = useCallback(
3028
+ (text, price, timestamp, color) => {
3029
+ const id = `annotation_${++annotationCounter}`;
3030
+ const annotation = {
3031
+ id,
3032
+ text,
3033
+ price,
3034
+ timestamp,
3035
+ color
3036
+ };
3037
+ setAnnotations((prev) => [...prev, annotation]);
3038
+ if (state.chart) {
3039
+ state.chart.createOverlay({
3040
+ name: "simpleAnnotation",
3041
+ id,
3042
+ groupId: "annotations",
3043
+ points: [{ timestamp, value: price }],
3044
+ extendData: text,
3045
+ styles: color ? {
3046
+ text: {
3047
+ color
3048
+ }
3049
+ } : void 0,
3050
+ lock: true
3051
+ });
3052
+ }
3053
+ return id;
3054
+ },
3055
+ [state.chart]
3056
+ );
3057
+ const removeAnnotation = useCallback(
3058
+ (id) => {
3059
+ setAnnotations((prev) => prev.filter((a) => a.id !== id));
3060
+ state.chart?.removeOverlay({ id });
3061
+ },
3062
+ [state.chart]
3063
+ );
3064
+ const updateAnnotation = useCallback(
3065
+ (id, updates) => {
3066
+ setAnnotations(
3067
+ (prev) => prev.map((a) => a.id === id ? { ...a, ...updates } : a)
3068
+ );
3069
+ if (state.chart) {
3070
+ const overrideData = { id };
3071
+ if (updates.text !== void 0) {
3072
+ overrideData.extendData = updates.text;
3073
+ }
3074
+ if (updates.color !== void 0) {
3075
+ overrideData.styles = {
3076
+ text: {
3077
+ color: updates.color
3078
+ }
3079
+ };
3080
+ }
3081
+ state.chart.overrideOverlay(overrideData);
3082
+ }
3083
+ },
3084
+ [state.chart]
3085
+ );
3086
+ const clearAnnotations = useCallback(() => {
3087
+ setAnnotations((prev) => {
3088
+ for (const annotation of prev) {
3089
+ state.chart?.removeOverlay({ id: annotation.id });
3090
+ }
3091
+ return [];
3092
+ });
3093
+ }, [state.chart]);
3094
+ useEffect(() => {
3095
+ return () => {
3096
+ state.chart?.removeOverlay({ groupId: "annotations" });
3097
+ };
3098
+ }, [state.chart]);
3099
+ return {
3100
+ annotations,
3101
+ addAnnotation,
3102
+ removeAnnotation,
3103
+ updateAnnotation,
3104
+ clearAnnotations
3105
+ };
3106
+ }
2247
3107
 
2248
3108
  // src/utils/createDataLoader.ts
2249
3109
  function createDataLoader(datafeed, dispatch) {
@@ -2309,6 +3169,6 @@ function createDataLoader(datafeed, dispatch) {
2309
3169
  };
2310
3170
  }
2311
3171
 
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 };
3172
+ export { CANDLE_TYPES, COMPARE_RULES, DEFAULT_PERIODS, DRAWING_CATEGORIES, INDICATOR_PARAMS, KlinechartsUIDispatchContext, KlinechartsUIProvider, KlinechartsUIStateContext, MAIN_INDICATORS, PRICE_AXIS_TYPES, SUB_INDICATORS, TIMEZONES, TOOLTIP_SHOW_RULES, YAXIS_POSITIONS, createDataLoader, useAlerts, useAnnotations, useCompare, useCrosshair, useDataExport, useDrawingTools, useFullscreen, useIndicators, useKlinechartsUI, useKlinechartsUILoading, useKlinechartsUISettings, useKlinechartsUITheme, useLayoutManager, useMeasure, useOrderLines, usePeriods, useReplay, useScreenshot, useScriptEditor, useSymbolSearch, useTimezone, useUndoRedo, useWatchlist };
2313
3173
  //# sourceMappingURL=index.js.map
2314
3174
  //# sourceMappingURL=index.js.map