@adrienhobbs/candlekit 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.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
2
3
  import { z } from 'zod';
3
4
 
4
5
  interface OHLCVBar {
@@ -18,7 +19,15 @@ interface ChartLine {
18
19
  lineWidth?: number;
19
20
  lineStyle?: 'solid' | 'dashed' | 'dotted';
20
21
  title?: string;
21
- type?: 'entry' | 'stopLoss' | 'takeProfit';
22
+ type?: 'entry' | 'stopLoss' | 'takeProfit' | 'mfe' | 'mae';
23
+ }
24
+ interface ChartTrade {
25
+ id: string;
26
+ entryTime: number;
27
+ exitTime: number;
28
+ entryPrice: number;
29
+ exitPrice: number;
30
+ outcome: 'win' | 'loss';
22
31
  }
23
32
  interface IndicatorSettings$1 {
24
33
  [key: string]: any;
@@ -154,8 +163,11 @@ interface ChartComponentProps {
154
163
  onClearAllLines?: () => void;
155
164
  enableBarSelection?: boolean;
156
165
  onBarClick?: (bar: OHLCVBar | null) => void;
166
+ trades?: ChartTrade[];
167
+ selectedTradeId?: string | null;
168
+ renderTradePopup?: (trade: ChartTrade) => ReactNode;
157
169
  }
158
- declare function ChartComponent({ bars, onLoadMoreData, indicators, lines, onBarUpdate, onNewBar, onDeleteLine, onAddLine, onClearAllLines, enableBarSelection, onBarClick, }: ChartComponentProps): react_jsx_runtime.JSX.Element;
170
+ declare function ChartComponent({ bars, onLoadMoreData, indicators, lines, onBarUpdate, onNewBar, onDeleteLine, onAddLine, onClearAllLines, enableBarSelection, onBarClick, trades, selectedTradeId, renderTradePopup, }: ChartComponentProps): react_jsx_runtime.JSX.Element;
159
171
 
160
172
  interface IndicatorBrowserProps {
161
173
  isOpen: boolean;
@@ -422,4 +434,4 @@ declare function getOldestBar(bars: OHLCVBar[]): OHLCVBar | null;
422
434
  declare function getNewestBar(bars: OHLCVBar[]): OHLCVBar | null;
423
435
  declare function updateCurrentBar(bars: OHLCVBar[], tradePrice: number, tradeVolume: number): OHLCVBar[];
424
436
 
425
- export { ADXIndicator, ATRIndicator, AlpacaBarAdapter, type BarDataAdapter, type BarDataAdapterOptions, BollingerBandsIndicator, CCIIndicator, ChartComponent, type ChartLine, ChartSeriesType, DonchianChannelsIndicator, EMAIndicator, ForceIndexIndicator, type HistoricalDataParams, IchimokuIndicator, IndicatorBrowser, type IndicatorCalculation, IndicatorCategory, type IndicatorDefinition, type IndicatorInstance, IndicatorInstanceSchema, type IndicatorMetadata, IndicatorMetadataSchema, type IndicatorOutput, type IndicatorPanel, type IndicatorSettings$1 as IndicatorSettings, IndicatorSettingsForm, IndicatorSettingsSchema, KeltnerChannelsIndicator, type LineStyle, LineStyleSchema, LocalStoragePersistenceAdapter, MACDIndicator, MFIIndicator, MockAdapter, NoOpPersistenceAdapter, OBVIndicator, type OHLCVBar, PSARIndicator, type PersistenceAdapter, ROCIndicator, RSIIndicator, type RealtimeHandlers, type RealtimeSubscription, type RenderConfig, RenderConfigSchema, SMAIndicator, type SettingField, SettingFieldSchema, type SettingFieldType, SettingFieldTypeSchema, SettingsDialog, StochRSIIndicator, StochasticIndicator, SuperTrendIndicator, VWAPIndicator, WMAIndicator, WilliamsRIndicator, appendBar, calculateBollingerBands, calculateEMA, calculateRSI, calculateSMA, calculateStandardDeviation, createPersistenceAdapter, deduplicateBars, displaceArray, getNewestBar, getOldestBar, indicatorCalculator, indicatorRegistry, isValidBar, mergeBars, normalizeTimestamp, padIndicatorArray, prependBars, registerBuiltInIndicators, sortBars, updateBarInArray, updateCurrentBar, useBarsData, useChartAPI, useRealtimeUpdates, validateAndNormalizeBars, validateBar };
437
+ export { ADXIndicator, ATRIndicator, AlpacaBarAdapter, type BarDataAdapter, type BarDataAdapterOptions, BollingerBandsIndicator, CCIIndicator, ChartComponent, type ChartLine, ChartSeriesType, type ChartTrade, DonchianChannelsIndicator, EMAIndicator, ForceIndexIndicator, type HistoricalDataParams, IchimokuIndicator, IndicatorBrowser, type IndicatorCalculation, IndicatorCategory, type IndicatorDefinition, type IndicatorInstance, IndicatorInstanceSchema, type IndicatorMetadata, IndicatorMetadataSchema, type IndicatorOutput, type IndicatorPanel, type IndicatorSettings$1 as IndicatorSettings, IndicatorSettingsForm, IndicatorSettingsSchema, KeltnerChannelsIndicator, type LineStyle, LineStyleSchema, LocalStoragePersistenceAdapter, MACDIndicator, MFIIndicator, MockAdapter, NoOpPersistenceAdapter, OBVIndicator, type OHLCVBar, PSARIndicator, type PersistenceAdapter, ROCIndicator, RSIIndicator, type RealtimeHandlers, type RealtimeSubscription, type RenderConfig, RenderConfigSchema, SMAIndicator, type SettingField, SettingFieldSchema, type SettingFieldType, SettingFieldTypeSchema, SettingsDialog, StochRSIIndicator, StochasticIndicator, SuperTrendIndicator, VWAPIndicator, WMAIndicator, WilliamsRIndicator, appendBar, calculateBollingerBands, calculateEMA, calculateRSI, calculateSMA, calculateStandardDeviation, createPersistenceAdapter, deduplicateBars, displaceArray, getNewestBar, getOldestBar, indicatorCalculator, indicatorRegistry, isValidBar, mergeBars, normalizeTimestamp, padIndicatorArray, prependBars, registerBuiltInIndicators, sortBars, updateBarInArray, updateCurrentBar, useBarsData, useChartAPI, useRealtimeUpdates, validateAndNormalizeBars, validateBar };
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { useRef, useState, useEffect, useMemo, useCallback } from 'react';
2
+ import { createPortal } from 'react-dom';
2
3
  import { createChart, ColorType, CandlestickSeries, HistogramSeries, createSeriesMarkers, LineSeries, AreaSeries, LineStyle } from 'lightweight-charts';
3
4
  import { jsxs, jsx } from 'react/jsx-runtime';
4
5
  import { X, Search, Plus } from 'lucide-react';
@@ -255,6 +256,35 @@ var BandsPrimitive = class {
255
256
  this._paneViews.forEach((pw) => pw.update());
256
257
  }
257
258
  };
259
+
260
+ // src/components/trade-markers.ts
261
+ var WIN = "#10b981";
262
+ var LOSS = "#ef4444";
263
+ var SEL = "#3b82f6";
264
+ function buildTradeMarkers(trades, selectedTradeId) {
265
+ const markers = [];
266
+ for (const t of trades) {
267
+ const selected = t.id === selectedTradeId;
268
+ const base = t.outcome === "win" ? WIN : LOSS;
269
+ const color = selected ? SEL : base;
270
+ markers.push({
271
+ time: t.entryTime / 1e3,
272
+ position: "belowBar",
273
+ shape: "arrowUp",
274
+ color,
275
+ text: selected ? `${t.outcome === "win" ? "+" : ""}entry` : ""
276
+ });
277
+ markers.push({
278
+ time: t.exitTime / 1e3,
279
+ position: "aboveBar",
280
+ shape: "arrowDown",
281
+ color,
282
+ text: selected ? "exit" : ""
283
+ });
284
+ }
285
+ markers.sort((a, b) => a.time - b.time);
286
+ return markers;
287
+ }
258
288
  function ChartComponent({
259
289
  bars,
260
290
  onLoadMoreData,
@@ -266,7 +296,10 @@ function ChartComponent({
266
296
  onAddLine,
267
297
  onClearAllLines,
268
298
  enableBarSelection = true,
269
- onBarClick
299
+ onBarClick,
300
+ trades = [],
301
+ selectedTradeId = null,
302
+ renderTradePopup
270
303
  }) {
271
304
  const chartContainerRef = useRef(null);
272
305
  const chartRef = useRef(null);
@@ -283,6 +316,7 @@ function ChartComponent({
283
316
  const [selectedBar, setSelectedBar] = useState(null);
284
317
  const selectedBarRef = useRef(null);
285
318
  const [spotlightPosition, setSpotlightPosition] = useState(null);
319
+ const [tradePopupPos, setTradePopupPos] = useState(null);
286
320
  const previousBarsRef = useRef([]);
287
321
  const previousBarsLengthRef = useRef(0);
288
322
  const isLoadingRef = useRef(false);
@@ -291,6 +325,7 @@ function ChartComponent({
291
325
  const mouseDownPosRef = useRef(null);
292
326
  useEffect(() => {
293
327
  if (!chartContainerRef.current) return;
328
+ chartContainerRef.current.style.position = "relative";
294
329
  const chart = createChart(chartContainerRef.current, {
295
330
  layout: {
296
331
  background: { type: ColorType.Solid, color: "#0f172a" },
@@ -349,11 +384,9 @@ function ChartComponent({
349
384
  seriesMarkersRef.current = createSeriesMarkers(candlestickSeries, []);
350
385
  chart.timeScale().subscribeVisibleLogicalRangeChange((logicalRange) => {
351
386
  if (logicalRange && logicalRange.from < 5 && !isLoadingRef.current && onLoadMoreData) {
352
- console.log("Triggering load more, logicalRange.from:", logicalRange.from);
353
387
  isLoadingRef.current = true;
354
388
  setIsLoadingMore(true);
355
389
  previousBarsLengthRef.current = barsRef.current.length;
356
- console.log("Set previousBarsLengthRef to:", previousBarsLengthRef.current);
357
390
  const sortedBars = [...barsRef.current].sort((a, b) => a.timestamp - b.timestamp);
358
391
  const oldestTimestamp = sortedBars[0]?.timestamp;
359
392
  if (oldestTimestamp) {
@@ -367,25 +400,20 @@ function ChartComponent({
367
400
  }
368
401
  };
369
402
  const handleContextMenu = (e) => {
370
- console.log("Context menu event triggered!", e);
371
403
  e.preventDefault();
372
404
  e.stopPropagation();
373
405
  if (!candlestickSeriesRef.current) {
374
- console.log("No candlestick series yet");
375
406
  return;
376
407
  }
377
408
  const rect = chartContainerRef.current.getBoundingClientRect();
378
409
  const y = e.clientY - rect.top;
379
- console.log("Y position:", y, "Rect:", rect);
380
410
  const price = candlestickSeriesRef.current.coordinateToPrice(y);
381
- console.log("Calculated price:", price);
382
411
  if (price !== null) {
383
412
  setContextMenu({
384
413
  x: e.clientX - rect.left,
385
414
  y,
386
415
  price
387
416
  });
388
- console.log("Context menu set:", { x: e.clientX - rect.left, y, price });
389
417
  }
390
418
  };
391
419
  const handleMouseDown = (e) => {
@@ -405,54 +433,34 @@ function ChartComponent({
405
433
  mouseDownPosRef.current = null;
406
434
  };
407
435
  const handleClick = (e) => {
408
- console.log("=== CLICK DEBUG START ===");
409
- console.log("Click event - closing context menu");
410
436
  setContextMenu(null);
411
437
  if (isDraggingRef.current) {
412
- console.log("Ignoring click - user was dragging");
413
438
  isDraggingRef.current = false;
414
439
  return;
415
440
  }
416
- console.log("enableBarSelection prop:", enableBarSelection);
417
- console.log("chartRef.current:", !!chartRef.current);
418
- console.log("candlestickSeriesRef.current:", !!candlestickSeriesRef.current);
419
- console.log("bars.length:", bars.length);
420
441
  if (!enableBarSelection) {
421
- console.log("Bar selection is DISABLED - exiting");
422
442
  return;
423
443
  }
424
444
  if (!chartRef.current) {
425
- console.log("Chart ref is NULL - exiting");
426
445
  return;
427
446
  }
428
447
  if (!candlestickSeriesRef.current) {
429
- console.log("Candlestick series ref is NULL - exiting");
430
448
  return;
431
449
  }
432
- console.log("All checks passed - proceeding with bar selection");
433
450
  const rect = chartContainerRef.current.getBoundingClientRect();
434
451
  const x = e.clientX - rect.left;
435
- console.log("Click X coordinate:", x);
436
452
  const timeScale = chartRef.current.timeScale();
437
453
  const coordinate = timeScale.coordinateToTime(x);
438
- console.log("Coordinate from timeScale:", coordinate);
439
454
  if (!coordinate) {
440
- console.log("No coordinate from timeScale - exiting");
441
455
  return;
442
456
  }
443
457
  const clickedTime = coordinate;
444
- console.log("Clicked time:", clickedTime);
445
- console.log("Sample bar timestamps (first 3):", barsRef.current.slice(0, 3).map((b) => b.timestamp / 1e3));
446
458
  const clickedBar = barsRef.current.find((bar) => {
447
459
  const diff = Math.abs(bar.timestamp / 1e3 - clickedTime);
448
460
  return diff < 300;
449
461
  });
450
- console.log("Clicked bar found:", !!clickedBar);
451
462
  if (clickedBar) {
452
- console.log("Clicked bar details:", clickedBar);
453
- console.log("Current selectedBarRef.current:", selectedBarRef.current);
454
463
  if (selectedBarRef.current && selectedBarRef.current.timestamp === clickedBar.timestamp) {
455
- console.log("Deselecting bar (same bar clicked)");
456
464
  selectedBarRef.current = null;
457
465
  setSelectedBar(null);
458
466
  setSpotlightPosition(null);
@@ -460,24 +468,18 @@ function ChartComponent({
460
468
  onBarClick(null);
461
469
  }
462
470
  } else {
463
- console.log("Selecting new bar");
464
471
  selectedBarRef.current = clickedBar;
465
472
  setSelectedBar(clickedBar);
466
473
  const barX = timeScale.timeToCoordinate(clickedBar.timestamp / 1e3);
467
- console.log("Bar X position:", barX);
468
474
  if (barX !== null) {
469
475
  const barWidth = Math.max(8, rect.width / barsRef.current.length);
470
- console.log("Bar width:", barWidth);
471
476
  setSpotlightPosition({ x: barX - barWidth / 2, width: barWidth });
472
477
  }
473
478
  if (onBarClick) {
474
479
  onBarClick(clickedBar);
475
480
  }
476
481
  }
477
- } else {
478
- console.log("No bar found at clicked position");
479
482
  }
480
- console.log("=== CLICK DEBUG END ===");
481
483
  };
482
484
  window.addEventListener("resize", handleResize);
483
485
  chartContainerRef.current.addEventListener("contextmenu", handleContextMenu, true);
@@ -509,21 +511,15 @@ function ChartComponent({
509
511
  }
510
512
  return acc;
511
513
  }, []);
512
- console.log("Debug - bars.length:", bars.length, "uniqueBars.length:", uniqueBars.length, "isLoadingRef:", isLoadingRef.current, "previousBarsLength:", previousBarsLengthRef.current);
513
514
  const previousBars = previousBarsRef.current;
514
515
  const isInitialLoad = previousBars.length === 0;
515
516
  const hasNewBars = uniqueBars.length > previousBars.length;
516
517
  const lastBarChanged = uniqueBars.length > 0 && previousBars.length > 0 && uniqueBars[uniqueBars.length - 1].timestamp === previousBars[previousBars.length - 1].timestamp;
517
- console.log("isInitialLoad:", isInitialLoad, "hasNewBars:", hasNewBars, "lastBarChanged:", lastBarChanged);
518
- console.log("previousBars.length:", previousBars.length, "uniqueBars.length:", uniqueBars.length);
519
518
  if (isLoadingRef.current && uniqueBars.length > previousBarsLengthRef.current) {
520
- console.log("Hiding loading indicator");
521
519
  isLoadingRef.current = false;
522
520
  setIsLoadingMore(false);
523
521
  }
524
- const firstBarChanged = uniqueBars.length > 0 && previousBars.length > 0 && uniqueBars[0].timestamp !== previousBars[0].timestamp;
525
522
  if (isInitialLoad || hasNewBars) {
526
- console.log("Setting data with", uniqueBars.length, "bars", "firstBarChanged:", firstBarChanged);
527
523
  const candleData = uniqueBars.map((bar) => ({
528
524
  time: bar.timestamp / 1e3,
529
525
  open: bar.open,
@@ -536,8 +532,6 @@ function ChartComponent({
536
532
  value: bar.volume,
537
533
  color: bar.close >= bar.open ? "#10b98180" : "#ef444480"
538
534
  }));
539
- console.log("candleData sample (first 3):", candleData.slice(0, 3));
540
- console.log("candleData sample (last 3):", candleData.slice(-3));
541
535
  candlestickSeriesRef.current.setData(candleData);
542
536
  volumeSeriesRef.current.setData(volumeData);
543
537
  } else if (lastBarChanged && uniqueBars.length > 0 && !hasNewBars) {
@@ -565,7 +559,6 @@ function ChartComponent({
565
559
  candlestickSeriesRef.current?.removePriceLine(priceLine);
566
560
  });
567
561
  priceLineRefs.current.clear();
568
- const newPositions = /* @__PURE__ */ new Map();
569
562
  lines.forEach((line) => {
570
563
  const priceLine = candlestickSeriesRef.current?.createPriceLine({
571
564
  price: line.price,
@@ -578,28 +571,47 @@ function ChartComponent({
578
571
  if (priceLine) {
579
572
  priceLineRefs.current.set(line.id, priceLine);
580
573
  }
581
- const y = candlestickSeriesRef.current?.priceToCoordinate(line.price);
582
- if (y !== null && y !== void 0) {
583
- newPositions.set(line.id, y);
584
- }
585
574
  });
586
- setLinePositions(newPositions);
575
+ requestAnimationFrame(() => {
576
+ if (!candlestickSeriesRef.current || !chartContainerRef.current) return;
577
+ const rect = chartContainerRef.current.getBoundingClientRect();
578
+ const newPositions = /* @__PURE__ */ new Map();
579
+ lines.forEach((line) => {
580
+ const y = candlestickSeriesRef.current?.priceToCoordinate(line.price);
581
+ if (y !== null && y !== void 0) {
582
+ newPositions.set(line.id, {
583
+ top: rect.top + y,
584
+ right: window.innerWidth - rect.right + 68 + estimateLabelTitleWidth(line.title)
585
+ });
586
+ }
587
+ });
588
+ setLinePositions(newPositions);
589
+ });
587
590
  }, [lines]);
588
591
  useEffect(() => {
589
592
  if (!chartRef.current || !candlestickSeriesRef.current) return;
590
593
  const updatePositions = () => {
594
+ if (!candlestickSeriesRef.current || !chartContainerRef.current) return;
595
+ const rect = chartContainerRef.current.getBoundingClientRect();
591
596
  const newPositions = /* @__PURE__ */ new Map();
592
597
  lines.forEach((line) => {
593
598
  const y = candlestickSeriesRef.current?.priceToCoordinate(line.price);
594
599
  if (y !== null && y !== void 0) {
595
- newPositions.set(line.id, y);
600
+ newPositions.set(line.id, {
601
+ top: rect.top + y,
602
+ right: window.innerWidth - rect.right + 68 + estimateLabelTitleWidth(line.title)
603
+ });
596
604
  }
597
605
  });
598
606
  setLinePositions(newPositions);
599
607
  };
600
608
  chartRef.current.timeScale().subscribeVisibleLogicalRangeChange(updatePositions);
609
+ window.addEventListener("scroll", updatePositions, { passive: true });
610
+ window.addEventListener("resize", updatePositions);
601
611
  return () => {
602
612
  chartRef.current?.timeScale().unsubscribeVisibleLogicalRangeChange(updatePositions);
613
+ window.removeEventListener("scroll", updatePositions);
614
+ window.removeEventListener("resize", updatePositions);
603
615
  };
604
616
  }, [lines]);
605
617
  useEffect(() => {
@@ -719,31 +731,22 @@ function ChartComponent({
719
731
  });
720
732
  }, [bars, indicators]);
721
733
  useEffect(() => {
722
- console.log("Marker effect running, selectedBar:", selectedBar);
723
- if (!seriesMarkersRef.current) {
724
- console.log("Marker API not initialized yet.");
725
- return;
726
- }
727
- if (!enableBarSelection) {
728
- console.log("Marker effect: clearing markers, selection disabled");
729
- seriesMarkersRef.current.setMarkers([]);
730
- return;
731
- }
732
- if (!selectedBar) {
733
- console.log("Marker effect: clearing markers, no bar selected");
734
- seriesMarkersRef.current.setMarkers([]);
735
- return;
736
- }
737
- console.log("Marker effect: setting marker for bar:", selectedBar.timestamp);
738
- const marker = {
739
- time: selectedBar.timestamp / 1e3,
740
- position: "aboveBar",
741
- color: "#3b82f6",
742
- shape: "circle",
743
- text: "Selected"
744
- };
745
- seriesMarkersRef.current.setMarkers([marker]);
746
- }, [selectedBar, enableBarSelection]);
734
+ if (!seriesMarkersRef.current) return;
735
+ const tradeMarkers = buildTradeMarkers(trades, selectedTradeId);
736
+ const selectionMarker = enableBarSelection && selectedBar ? [
737
+ {
738
+ time: selectedBar.timestamp / 1e3,
739
+ position: "aboveBar",
740
+ color: "#3b82f6",
741
+ shape: "circle",
742
+ text: ""
743
+ }
744
+ ] : [];
745
+ const all = [...tradeMarkers, ...selectionMarker].sort(
746
+ (a, b) => a.time - b.time
747
+ );
748
+ seriesMarkersRef.current.setMarkers(all);
749
+ }, [trades, selectedTradeId, selectedBar, enableBarSelection]);
747
750
  useEffect(() => {
748
751
  if (!chartRef.current || !selectedBar || !enableBarSelection) {
749
752
  setSpotlightPosition(null);
@@ -768,6 +771,28 @@ function ChartComponent({
768
771
  }
769
772
  };
770
773
  }, [selectedBar, enableBarSelection, bars.length]);
774
+ const selectedTrade = trades.find((t) => t.id === selectedTradeId) ?? null;
775
+ useEffect(() => {
776
+ if (!chartRef.current || !selectedTrade) {
777
+ setTradePopupPos(null);
778
+ return;
779
+ }
780
+ const updateTradePopupPos = () => {
781
+ if (!chartRef.current || !selectedTrade || !chartContainerRef.current) return;
782
+ const timeScale2 = chartRef.current.timeScale();
783
+ const x = timeScale2.timeToCoordinate(selectedTrade.entryTime / 1e3);
784
+ const width = chartContainerRef.current.getBoundingClientRect().width;
785
+ setTradePopupPos(x !== null && x >= 0 && x <= width ? { x } : null);
786
+ };
787
+ updateTradePopupPos();
788
+ const timeScale = chartRef.current.timeScale();
789
+ timeScale.subscribeVisibleLogicalRangeChange(updateTradePopupPos);
790
+ return () => {
791
+ if (chartRef.current) {
792
+ chartRef.current.timeScale().unsubscribeVisibleLogicalRangeChange(updateTradePopupPos);
793
+ }
794
+ };
795
+ }, [selectedTrade, selectedTrade?.entryTime]);
771
796
  const handleDeleteLine = (lineId) => {
772
797
  if (onDeleteLine) {
773
798
  onDeleteLine(lineId);
@@ -802,25 +827,38 @@ function ChartComponent({
802
827
  }
803
828
  }
804
829
  ),
805
- lines.map((line) => {
806
- const y = linePositions.get(line.id);
807
- if (y === null || y === void 0) return null;
808
- return /* @__PURE__ */ jsx(
830
+ selectedTrade && tradePopupPos && renderTradePopup && /* @__PURE__ */ jsx(
831
+ "div",
832
+ {
833
+ className: "absolute z-20 pointer-events-auto",
834
+ style: { left: `${tradePopupPos.x}px`, top: 8, transform: "translateX(-50%)" },
835
+ children: renderTradePopup(selectedTrade)
836
+ }
837
+ )
838
+ ] }),
839
+ chartContainerRef.current && lines.map((line) => {
840
+ const pos = linePositions.get(line.id);
841
+ if (!pos) return null;
842
+ return createPortal(
843
+ /* @__PURE__ */ jsx(
809
844
  "button",
810
845
  {
811
846
  onClick: () => handleDeleteLine(line.id),
812
- className: "absolute bg-red-500/70 hover:bg-red-500 text-white rounded-full w-4 h-4 flex items-center justify-center text-xs font-bold transition-colors z-20 shadow-md",
847
+ className: "bg-red-500/70 hover:bg-red-500 text-white rounded-full w-4 h-4 flex items-center justify-center text-xs font-bold transition-colors shadow-md",
813
848
  style: {
814
- right: "68px",
815
- top: `${y - 8}px`
849
+ position: "fixed",
850
+ top: `${pos.top - 8}px`,
851
+ right: `${pos.right}px`,
852
+ zIndex: 9999
816
853
  },
817
854
  title: "Delete line",
818
855
  children: "\xD7"
819
856
  },
820
857
  line.id
821
- );
822
- })
823
- ] }),
858
+ ),
859
+ document.body
860
+ );
861
+ }),
824
862
  contextMenu && /* @__PURE__ */ jsxs(
825
863
  "div",
826
864
  {
@@ -889,6 +927,10 @@ function ChartComponent({
889
927
  )
890
928
  ] });
891
929
  }
930
+ function estimateLabelTitleWidth(title) {
931
+ if (!title) return 0;
932
+ return title.length * 7 + 18;
933
+ }
892
934
  function getLineStyle(style) {
893
935
  switch (style) {
894
936
  case "dashed":