hyperprop-charting-library 0.1.139 → 0.1.140

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.
@@ -2822,6 +2822,8 @@ var DEFAULT_OPTIONS = {
2822
2822
  keyboard: { enabled: true, panBars: 1, deleteSelectedDrawing: true },
2823
2823
  accessibility: { label: "Price chart", description: "", focusable: true },
2824
2824
  downsampling: { enabled: true, thresholdPx: 1.5 },
2825
+ touch: { hitToleranceScale: 2.2, longPressTooltip: true, longPressMs: 400 },
2826
+ datafeedOptions: { prefetchThresholdBars: 150, cooldownMs: 1200, chunkBars: 1500 },
2825
2827
  crosshair: DEFAULT_CROSSHAIR_OPTIONS,
2826
2828
  grid: DEFAULT_GRID_OPTIONS,
2827
2829
  watermark: DEFAULT_WATERMARK_OPTIONS,
@@ -2876,6 +2878,14 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
2876
2878
  ...baseOptions.downsampling,
2877
2879
  ...options.downsampling ?? {}
2878
2880
  },
2881
+ touch: {
2882
+ ...baseOptions.touch,
2883
+ ...options.touch ?? {}
2884
+ },
2885
+ datafeedOptions: {
2886
+ ...baseOptions.datafeedOptions,
2887
+ ...options.datafeedOptions ?? {}
2888
+ },
2879
2889
  crosshair: {
2880
2890
  ...baseOptions.crosshair,
2881
2891
  ...options.crosshair ?? {}
@@ -3050,6 +3060,7 @@ function createChart(element, options = {}) {
3050
3060
  let drawingDoubleClickHandler = null;
3051
3061
  let drawingEditTextHandler = null;
3052
3062
  let selectionChangeHandler = null;
3063
+ let longPressHandler = null;
3053
3064
  let contextMenuHandler = null;
3054
3065
  let keyboardShortcutHandler = null;
3055
3066
  let lastSelectionSignature = null;
@@ -3809,6 +3820,92 @@ function createChart(element, options = {}) {
3809
3820
  scheduleDraw({ updateAutoScale: true });
3810
3821
  };
3811
3822
  const getCompareSeries = () => compareSeriesStates.map((state) => state.options);
3823
+ let datafeed = null;
3824
+ let datafeedUnsubscribe = null;
3825
+ let datafeedLoading = false;
3826
+ let datafeedExhausted = false;
3827
+ let datafeedLastRequestAt = 0;
3828
+ const estimateBarStepMs = () => {
3829
+ if (mergedOptions.timeStepMs > 0) return mergedOptions.timeStepMs;
3830
+ if (data.length < 2) return 6e4;
3831
+ const span = data[data.length - 1].time.getTime() - data[0].time.getTime();
3832
+ return Math.max(1e3, span / (data.length - 1));
3833
+ };
3834
+ const requestDatafeedBars = async (request) => {
3835
+ if (!datafeed) return [];
3836
+ try {
3837
+ return await datafeed.getBars(request) ?? [];
3838
+ } catch (error) {
3839
+ datafeed.onError?.(error, request);
3840
+ return [];
3841
+ }
3842
+ };
3843
+ const maybeLoadOlderHistory = () => {
3844
+ if (!datafeed || datafeedLoading || datafeedExhausted || data.length === 0) return;
3845
+ const options2 = mergedOptions.datafeedOptions ?? DEFAULT_OPTIONS.datafeedOptions;
3846
+ const threshold = options2?.prefetchThresholdBars ?? 150;
3847
+ const cooldown = options2?.cooldownMs ?? 1200;
3848
+ if (Date.now() - datafeedLastRequestAt < cooldown) return;
3849
+ if (xCenter - xSpan / 2 > threshold) return;
3850
+ const stepMs = estimateBarStepMs();
3851
+ const chunk = Math.max(10, options2?.chunkBars ?? 1500);
3852
+ const toMs = data[0].time.getTime();
3853
+ datafeedLoading = true;
3854
+ datafeedLastRequestAt = Date.now();
3855
+ void requestDatafeedBars({
3856
+ fromMs: toMs - chunk * stepMs,
3857
+ toMs,
3858
+ countBack: chunk,
3859
+ firstRequest: false
3860
+ }).then((bars) => {
3861
+ const older = bars.filter((bar) => new Date(bar.t).getTime() < toMs);
3862
+ if (older.length === 0) {
3863
+ datafeedExhausted = true;
3864
+ return;
3865
+ }
3866
+ const merged = [...older, ...data.map((bar) => ({
3867
+ t: bar.time.toISOString(),
3868
+ o: bar.o,
3869
+ h: bar.h,
3870
+ l: bar.l,
3871
+ c: bar.c,
3872
+ ...bar.v === void 0 ? {} : { v: bar.v }
3873
+ }))];
3874
+ setData(merged);
3875
+ }).finally(() => {
3876
+ datafeedLoading = false;
3877
+ });
3878
+ };
3879
+ const setDatafeed = async (nextDatafeed) => {
3880
+ datafeedUnsubscribe?.();
3881
+ datafeedUnsubscribe = null;
3882
+ datafeed = nextDatafeed;
3883
+ datafeedExhausted = false;
3884
+ datafeedLoading = false;
3885
+ datafeedLastRequestAt = 0;
3886
+ if (!nextDatafeed) return;
3887
+ await nextDatafeed.onReady?.();
3888
+ if (datafeed !== nextDatafeed) return;
3889
+ const stepMs = estimateBarStepMs();
3890
+ const countBack = Math.max(50, Math.round(mergedOptions.initialVisibleBars * 3));
3891
+ const toMs = Date.now();
3892
+ datafeedLoading = true;
3893
+ const bars = await requestDatafeedBars({
3894
+ fromMs: toMs - countBack * stepMs,
3895
+ toMs,
3896
+ countBack,
3897
+ firstRequest: true
3898
+ });
3899
+ datafeedLoading = false;
3900
+ if (datafeed !== nextDatafeed) return;
3901
+ if (bars.length > 0) {
3902
+ setData(bars);
3903
+ }
3904
+ const unsubscribe = nextDatafeed.subscribeBars?.((bar) => {
3905
+ if (datafeed === nextDatafeed) upsertBar(bar);
3906
+ });
3907
+ datafeedUnsubscribe = typeof unsubscribe === "function" ? unsubscribe : null;
3908
+ };
3812
3909
  const formatHoverTimeLabel = (time, mode) => {
3813
3910
  if (mode === "time") {
3814
3911
  return time.toLocaleTimeString(void 0, {
@@ -6803,6 +6900,7 @@ function createChart(element, options = {}) {
6803
6900
  };
6804
6901
  paintCrosshairOverlay();
6805
6902
  emitSelectionChangeIfMoved();
6903
+ maybeLoadOlderHistory();
6806
6904
  };
6807
6905
  const paintCrosshairOverlay = () => {
6808
6906
  crosshairPriceActionRegion = null;
@@ -6995,6 +7093,67 @@ function createChart(element, options = {}) {
6995
7093
  drawText(timeText, timeX + labelPaddingX, timeY + timeHeight / 2, "left", "middle", labelTextColor);
6996
7094
  }
6997
7095
  }
7096
+ if (longPressState && (mergedOptions.touch?.longPressTooltip ?? true)) {
7097
+ const index = indexFromCanvasX(cx);
7098
+ const bar = index === null ? void 0 : data[index];
7099
+ if (bar) {
7100
+ const barTime = getTimeForIndex(index);
7101
+ const changeAbs = bar.c - bar.o;
7102
+ const changePct = bar.o !== 0 ? changeAbs / bar.o * 100 : 0;
7103
+ const rows = [
7104
+ ["O", formatPrice(bar.o), null],
7105
+ ["H", formatPrice(bar.h), null],
7106
+ ["L", formatPrice(bar.l), null],
7107
+ ["C", formatPrice(bar.c), null],
7108
+ [
7109
+ "Chg",
7110
+ `${changeAbs >= 0 ? "+" : ""}${formatPrice(changeAbs)} (${changeAbs >= 0 ? "+" : ""}${changePct.toFixed(2)}%)`,
7111
+ changeAbs >= 0 ? mergedOptions.upColor : mergedOptions.downColor
7112
+ ]
7113
+ ];
7114
+ if (bar.v !== void 0) {
7115
+ rows.push([
7116
+ "Vol",
7117
+ bar.v >= 1e6 ? `${(bar.v / 1e6).toFixed(2)}M` : bar.v >= 1e3 ? `${(bar.v / 1e3).toFixed(1)}K` : String(Math.round(bar.v)),
7118
+ null
7119
+ ]);
7120
+ }
7121
+ const prevFont = ctx.font;
7122
+ const fontSize = 11;
7123
+ ctx.font = `${fontSize}px ${mergedOptions.fontFamily}`;
7124
+ const headerText = barTime ? formatHoverTimeLabel(barTime, "auto") : "";
7125
+ const rowGap = 3;
7126
+ const padding = 8;
7127
+ const labelColumn = 26;
7128
+ const contentWidth = Math.max(
7129
+ measureTextWidth(headerText),
7130
+ ...rows.map(([, value]) => labelColumn + measureTextWidth(value))
7131
+ );
7132
+ const boxWidth = Math.ceil(contentWidth) + padding * 2;
7133
+ const boxHeight = padding * 2 + (fontSize + rowGap) * (rows.length + (headerText ? 1 : 0)) - rowGap;
7134
+ const fingerGap = 26;
7135
+ const boxX = cx + fingerGap + boxWidth <= chartRight ? cx + fingerGap : Math.max(chartLeft + 4, cx - fingerGap - boxWidth);
7136
+ const boxY = clamp(cy - boxHeight - fingerGap, chartTop + 4, chartBottom - boxHeight - 4);
7137
+ ctx.save();
7138
+ ctx.fillStyle = "rgba(16,17,20,0.94)";
7139
+ fillRoundedRect(Math.round(boxX), Math.round(boxY), boxWidth, boxHeight, 6);
7140
+ ctx.strokeStyle = "rgba(255,255,255,0.14)";
7141
+ ctx.lineWidth = 1;
7142
+ ctx.strokeRect(Math.round(boxX) + 0.5, Math.round(boxY) + 0.5, boxWidth - 1, boxHeight - 1);
7143
+ let textY = boxY + padding + fontSize / 2;
7144
+ if (headerText) {
7145
+ drawText(headerText, boxX + padding, textY, "left", "middle", "rgba(255,255,255,0.55)");
7146
+ textY += fontSize + rowGap;
7147
+ }
7148
+ for (const [label, value, valueColor] of rows) {
7149
+ drawText(label, boxX + padding, textY, "left", "middle", "rgba(255,255,255,0.5)");
7150
+ drawText(value, boxX + padding + labelColumn, textY, "left", "middle", valueColor ?? "#e6e8ee");
7151
+ textY += fontSize + rowGap;
7152
+ }
7153
+ ctx.restore();
7154
+ ctx.font = prevFont;
7155
+ }
7156
+ }
6998
7157
  };
6999
7158
  const drawOverlayOnly = () => {
7000
7159
  if (!baseCtx || !overlayLayout || baseCanvas.width !== canvas.width || baseCanvas.height !== canvas.height || baseCanvas.width === 0) {
@@ -7504,6 +7663,7 @@ function createChart(element, options = {}) {
7504
7663
  return Math.hypot(x - (x1 + t * dx), y - (y1 + t * dy));
7505
7664
  };
7506
7665
  const getDrawingHit = (x, y) => {
7666
+ const hitTolerance = touchInputActive ? Math.max(1, mergedOptions.touch?.hitToleranceScale ?? 2.2) : 1;
7507
7667
  for (let index = drawings.length - 1; index >= 0; index -= 1) {
7508
7668
  const drawing = drawings[index];
7509
7669
  if (!drawing?.visible) continue;
@@ -7512,10 +7672,10 @@ function createChart(element, options = {}) {
7512
7672
  if (!point) continue;
7513
7673
  const lineY = canvasYFromDrawingPrice(point.price);
7514
7674
  const handleX = drawState ? drawState.chartRight - 64 : 0;
7515
- if (Math.hypot(x - handleX, y - lineY) <= 8) {
7675
+ if (Math.hypot(x - handleX, y - lineY) <= 8 * hitTolerance) {
7516
7676
  return { drawing, target: "handle", pointIndex: 0 };
7517
7677
  }
7518
- if (Math.abs(y - lineY) <= 7) {
7678
+ if (Math.abs(y - lineY) <= 7 * hitTolerance) {
7519
7679
  return { drawing, target: "line" };
7520
7680
  }
7521
7681
  } else if (drawing.type === "vertical-line") {
@@ -7523,10 +7683,10 @@ function createChart(element, options = {}) {
7523
7683
  if (!point) continue;
7524
7684
  const lineX = canvasXFromDrawingPoint(point);
7525
7685
  const handleY = drawState ? (drawState.chartTop + drawState.chartBottom) / 2 : 0;
7526
- if (Math.hypot(x - lineX, y - handleY) <= 8) {
7686
+ if (Math.hypot(x - lineX, y - handleY) <= 8 * hitTolerance) {
7527
7687
  return { drawing, target: "handle", pointIndex: 0 };
7528
7688
  }
7529
- if (Math.abs(x - lineX) <= 7) {
7689
+ if (Math.abs(x - lineX) <= 7 * hitTolerance) {
7530
7690
  return { drawing, target: "line" };
7531
7691
  }
7532
7692
  } else if (drawing.type === "trendline") {
@@ -7537,13 +7697,13 @@ function createChart(element, options = {}) {
7537
7697
  const y1 = canvasYFromDrawingPrice(first.price);
7538
7698
  const x2 = canvasXFromDrawingPoint(second);
7539
7699
  const y2 = canvasYFromDrawingPrice(second.price);
7540
- if (Math.hypot(x - x1, y - y1) <= 8) {
7700
+ if (Math.hypot(x - x1, y - y1) <= 8 * hitTolerance) {
7541
7701
  return { drawing, target: "handle", pointIndex: 0 };
7542
7702
  }
7543
- if (Math.hypot(x - x2, y - y2) <= 8) {
7703
+ if (Math.hypot(x - x2, y - y2) <= 8 * hitTolerance) {
7544
7704
  return { drawing, target: "handle", pointIndex: 1 };
7545
7705
  }
7546
- if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
7706
+ if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6 * hitTolerance) {
7547
7707
  return { drawing, target: "line" };
7548
7708
  }
7549
7709
  } else if (drawing.type === "ray") {
@@ -7554,10 +7714,10 @@ function createChart(element, options = {}) {
7554
7714
  const y1 = canvasYFromDrawingPrice(first.price);
7555
7715
  const x2 = canvasXFromDrawingPoint(second);
7556
7716
  const y2 = canvasYFromDrawingPrice(second.price);
7557
- if (Math.hypot(x - x1, y - y1) <= 8) {
7717
+ if (Math.hypot(x - x1, y - y1) <= 8 * hitTolerance) {
7558
7718
  return { drawing, target: "handle", pointIndex: 0 };
7559
7719
  }
7560
- if (Math.hypot(x - x2, y - y2) <= 8) {
7720
+ if (Math.hypot(x - x2, y - y2) <= 8 * hitTolerance) {
7561
7721
  return { drawing, target: "handle", pointIndex: 1 };
7562
7722
  }
7563
7723
  const dx = x2 - x1;
@@ -7567,7 +7727,7 @@ function createChart(element, options = {}) {
7567
7727
  const t = Math.max(0, ((x - x1) * dx + (y - y1) * dy) / lengthSq);
7568
7728
  const projX = x1 + t * dx;
7569
7729
  const projY = y1 + t * dy;
7570
- if (Math.hypot(x - projX, y - projY) <= 6) {
7730
+ if (Math.hypot(x - projX, y - projY) <= 6 * hitTolerance) {
7571
7731
  return { drawing, target: "line" };
7572
7732
  }
7573
7733
  }
@@ -7579,17 +7739,17 @@ function createChart(element, options = {}) {
7579
7739
  const y1 = canvasYFromDrawingPrice(first.price);
7580
7740
  const x2 = canvasXFromDrawingPoint(second);
7581
7741
  const y2 = canvasYFromDrawingPrice(second.price);
7582
- if (Math.hypot(x - x1, y - y1) <= 9) {
7742
+ if (Math.hypot(x - x1, y - y1) <= 9 * hitTolerance) {
7583
7743
  return { drawing, target: "handle", pointIndex: 0 };
7584
7744
  }
7585
- if (Math.hypot(x - x2, y - y2) <= 9) {
7745
+ if (Math.hypot(x - x2, y - y2) <= 9 * hitTolerance) {
7586
7746
  return { drawing, target: "handle", pointIndex: 1 };
7587
7747
  }
7588
7748
  const fibRatios = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
7589
7749
  for (const ratio of fibRatios) {
7590
7750
  const price = first.price + (second.price - first.price) * (1 - ratio);
7591
7751
  const lineY = canvasYFromDrawingPrice(price);
7592
- if (Math.abs(y - lineY) <= 5) {
7752
+ if (Math.abs(y - lineY) <= 5 * hitTolerance) {
7593
7753
  return { drawing, target: "line" };
7594
7754
  }
7595
7755
  }
@@ -7602,30 +7762,30 @@ function createChart(element, options = {}) {
7602
7762
  const y0 = canvasYFromDrawingPrice(p0.price);
7603
7763
  const x1 = canvasXFromDrawingPoint(p1);
7604
7764
  const y1 = canvasYFromDrawingPrice(p1.price);
7605
- if (Math.hypot(x - x0, y - y0) <= 8) {
7765
+ if (Math.hypot(x - x0, y - y0) <= 8 * hitTolerance) {
7606
7766
  return { drawing, target: "handle", pointIndex: 0 };
7607
7767
  }
7608
- if (Math.hypot(x - x1, y - y1) <= 8) {
7768
+ if (Math.hypot(x - x1, y - y1) <= 8 * hitTolerance) {
7609
7769
  return { drawing, target: "handle", pointIndex: 1 };
7610
7770
  }
7611
7771
  if (p2) {
7612
7772
  const x2 = canvasXFromDrawingPoint(p2);
7613
7773
  const y2 = canvasYFromDrawingPrice(p2.price);
7614
- if (Math.hypot(x - x2, y - y2) <= 8) {
7774
+ if (Math.hypot(x - x2, y - y2) <= 8 * hitTolerance) {
7615
7775
  return { drawing, target: "handle", pointIndex: 2 };
7616
7776
  }
7617
7777
  const move = p1.price - p0.price;
7618
7778
  const fibExtRatios = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
7619
7779
  for (const ratio of fibExtRatios) {
7620
7780
  const lineY = canvasYFromDrawingPrice(p2.price + move * ratio);
7621
- if (Math.abs(y - lineY) <= 5) {
7781
+ if (Math.abs(y - lineY) <= 5 * hitTolerance) {
7622
7782
  return { drawing, target: "line" };
7623
7783
  }
7624
7784
  }
7625
- if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 || distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
7785
+ if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 * hitTolerance || distanceToSegment(x, y, x1, y1, x2, y2) <= 6 * hitTolerance) {
7626
7786
  return { drawing, target: "line" };
7627
7787
  }
7628
- } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
7788
+ } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 * hitTolerance) {
7629
7789
  return { drawing, target: "line" };
7630
7790
  }
7631
7791
  } else if (drawing.type === "long-position" || drawing.type === "short-position") {
@@ -7639,10 +7799,10 @@ function createChart(element, options = {}) {
7639
7799
  const entryY = canvasYFromDrawingPrice(entry.price);
7640
7800
  const targetY = canvasYFromDrawingPrice(target.price);
7641
7801
  const stopY = canvasYFromDrawingPrice(stop.price);
7642
- if (Math.hypot(x - leftX, y - targetY) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7643
- if (Math.hypot(x - leftX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7644
- if (Math.hypot(x - leftX, y - stopY) <= 9) return { drawing, target: "handle", pointIndex: 2 };
7645
- if (Math.hypot(x - rightX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 3 };
7802
+ if (Math.hypot(x - leftX, y - targetY) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
7803
+ if (Math.hypot(x - leftX, y - entryY) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7804
+ if (Math.hypot(x - leftX, y - stopY) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 2 };
7805
+ if (Math.hypot(x - rightX, y - entryY) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 3 };
7646
7806
  const x0 = Math.min(leftX, rightX);
7647
7807
  const x1 = Math.max(leftX, rightX);
7648
7808
  const yTop = Math.min(targetY, stopY, entryY);
@@ -7658,10 +7818,10 @@ function createChart(element, options = {}) {
7658
7818
  const px1 = canvasXFromDrawingPoint(p1);
7659
7819
  const y0 = canvasYFromDrawingPrice(p0.price);
7660
7820
  const y1 = canvasYFromDrawingPrice(p1.price);
7661
- if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7662
- if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7663
- if (Math.hypot(x - px0, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 2 };
7664
- if (Math.hypot(x - px1, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 3 };
7821
+ if (Math.hypot(x - px0, y - y0) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7822
+ if (Math.hypot(x - px1, y - y1) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
7823
+ if (Math.hypot(x - px0, y - y1) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 2 };
7824
+ if (Math.hypot(x - px1, y - y0) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 3 };
7665
7825
  if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1) && y >= Math.min(y0, y1) && y <= Math.max(y0, y1)) {
7666
7826
  return { drawing, target: "line" };
7667
7827
  }
@@ -7673,16 +7833,16 @@ function createChart(element, options = {}) {
7673
7833
  const px1 = canvasXFromDrawingPoint(p1);
7674
7834
  const y0 = canvasYFromDrawingPrice(p0.price);
7675
7835
  const y1 = canvasYFromDrawingPrice(p1.price);
7676
- if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7677
- if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7678
- if (Math.hypot(x - px0, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 2 };
7679
- if (Math.hypot(x - px1, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 3 };
7836
+ if (Math.hypot(x - px0, y - y0) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7837
+ if (Math.hypot(x - px1, y - y1) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
7838
+ if (Math.hypot(x - px0, y - y1) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 2 };
7839
+ if (Math.hypot(x - px1, y - y0) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 3 };
7680
7840
  const cx = (px0 + px1) / 2;
7681
7841
  const cy = (y0 + y1) / 2;
7682
7842
  const rx = Math.max(1, Math.abs(px1 - px0) / 2);
7683
7843
  const ry = Math.max(1, Math.abs(y1 - y0) / 2);
7684
7844
  const norm = ((x - cx) / rx) ** 2 + ((y - cy) / ry) ** 2;
7685
- if (norm <= 1) {
7845
+ if (norm <= 1 * hitTolerance) {
7686
7846
  return { drawing, target: "line" };
7687
7847
  }
7688
7848
  } else if (drawing.type === "parallel-channel") {
@@ -7694,18 +7854,18 @@ function createChart(element, options = {}) {
7694
7854
  const y0 = canvasYFromDrawingPrice(p0.price);
7695
7855
  const x1 = canvasXFromDrawingPoint(p1);
7696
7856
  const y1 = canvasYFromDrawingPrice(p1.price);
7697
- if (Math.hypot(x - x0, y - y0) <= 8) return { drawing, target: "handle", pointIndex: 0 };
7698
- if (Math.hypot(x - x1, y - y1) <= 8) return { drawing, target: "handle", pointIndex: 1 };
7857
+ if (Math.hypot(x - x0, y - y0) <= 8 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7858
+ if (Math.hypot(x - x1, y - y1) <= 8 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
7699
7859
  if (p2) {
7700
7860
  const x2 = canvasXFromDrawingPoint(p2);
7701
7861
  const y2 = canvasYFromDrawingPrice(p2.price);
7702
- if (Math.hypot(x - x2, y - y2) <= 8) return { drawing, target: "handle", pointIndex: 2 };
7862
+ if (Math.hypot(x - x2, y - y2) <= 8 * hitTolerance) return { drawing, target: "handle", pointIndex: 2 };
7703
7863
  const indexSpan = p1.index - p0.index;
7704
7864
  const slope = indexSpan !== 0 ? (p1.price - p0.price) / indexSpan : 0;
7705
7865
  const offset = p2.price - (p0.price + slope * (p2.index - p0.index));
7706
7866
  const q0y = canvasYFromDrawingPrice(p0.price + offset);
7707
7867
  const q1y = canvasYFromDrawingPrice(p1.price + offset);
7708
- if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 || distanceToSegment(x, y, x0, q0y, x1, q1y) <= 6) {
7868
+ if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 * hitTolerance || distanceToSegment(x, y, x0, q0y, x1, q1y) <= 6 * hitTolerance) {
7709
7869
  return { drawing, target: "line" };
7710
7870
  }
7711
7871
  const minX = Math.min(x0, x1);
@@ -7718,7 +7878,7 @@ function createChart(element, options = {}) {
7718
7878
  return { drawing, target: "line" };
7719
7879
  }
7720
7880
  }
7721
- } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
7881
+ } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 * hitTolerance) {
7722
7882
  return { drawing, target: "line" };
7723
7883
  }
7724
7884
  } else if (drawing.type === "arrow") {
@@ -7729,13 +7889,13 @@ function createChart(element, options = {}) {
7729
7889
  const y1 = canvasYFromDrawingPrice(first.price);
7730
7890
  const x2 = canvasXFromDrawingPoint(second);
7731
7891
  const y2 = canvasYFromDrawingPrice(second.price);
7732
- if (Math.hypot(x - x1, y - y1) <= 8) {
7892
+ if (Math.hypot(x - x1, y - y1) <= 8 * hitTolerance) {
7733
7893
  return { drawing, target: "handle", pointIndex: 0 };
7734
7894
  }
7735
- if (Math.hypot(x - x2, y - y2) <= 8) {
7895
+ if (Math.hypot(x - x2, y - y2) <= 8 * hitTolerance) {
7736
7896
  return { drawing, target: "handle", pointIndex: 1 };
7737
7897
  }
7738
- if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
7898
+ if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6 * hitTolerance) {
7739
7899
  return { drawing, target: "line" };
7740
7900
  }
7741
7901
  } else if (drawing.type === "brush") {
@@ -7747,7 +7907,7 @@ function createChart(element, options = {}) {
7747
7907
  for (let i = 1; i < pts.length; i += 1) {
7748
7908
  const nextX = canvasXFromDrawingPoint(pts[i]);
7749
7909
  const nextY = canvasYFromDrawingPrice(pts[i].price);
7750
- if (distanceToSegment(x, y, prevX, prevY, nextX, nextY) <= 7) {
7910
+ if (distanceToSegment(x, y, prevX, prevY, nextX, nextY) <= 7 * hitTolerance) {
7751
7911
  hitBody = true;
7752
7912
  break;
7753
7913
  }
@@ -7765,8 +7925,8 @@ function createChart(element, options = {}) {
7765
7925
  const ay = canvasYFromDrawingPrice(anchor.price);
7766
7926
  const bx = canvasXFromDrawingPoint(boxPoint);
7767
7927
  const by = canvasYFromDrawingPrice(boxPoint.price);
7768
- if (Math.hypot(x - ax, y - ay) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7769
- if (Math.hypot(x - bx, y - by) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7928
+ if (Math.hypot(x - ax, y - ay) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7929
+ if (Math.hypot(x - bx, y - by) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
7770
7930
  const fontSize = Math.max(6, drawing.fontSize);
7771
7931
  const prevFont = ctx.font;
7772
7932
  ctx.font = `500 ${fontSize}px ${mergedOptions.fontFamily}`;
@@ -7782,7 +7942,7 @@ function createChart(element, options = {}) {
7782
7942
  if (x >= bx && x <= bx + blockW && y >= by && y <= by + blockH) {
7783
7943
  return { drawing, target: "line" };
7784
7944
  }
7785
- if (distanceToSegment(x, y, bx + blockW / 2, by + blockH / 2, ax, ay) <= 5) {
7945
+ if (distanceToSegment(x, y, bx + blockW / 2, by + blockH / 2, ax, ay) <= 5 * hitTolerance) {
7786
7946
  return { drawing, target: "line" };
7787
7947
  }
7788
7948
  } else if (drawing.type === "price-range") {
@@ -7793,8 +7953,8 @@ function createChart(element, options = {}) {
7793
7953
  const px1 = canvasXFromDrawingPoint(p1);
7794
7954
  const y0 = canvasYFromDrawingPrice(p0.price);
7795
7955
  const y1 = canvasYFromDrawingPrice(p1.price);
7796
- if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7797
- if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7956
+ if (Math.hypot(x - px0, y - y0) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7957
+ if (Math.hypot(x - px1, y - y1) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
7798
7958
  if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1) && y >= Math.min(y0, y1) && y <= Math.max(y0, y1)) {
7799
7959
  return { drawing, target: "line" };
7800
7960
  }
@@ -7803,7 +7963,7 @@ function createChart(element, options = {}) {
7803
7963
  if (!anchor) continue;
7804
7964
  const ax = canvasXFromDrawingPoint(anchor);
7805
7965
  const ay = canvasYFromDrawingPrice(anchor.price);
7806
- if (Math.hypot(x - ax, y - ay) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7966
+ if (Math.hypot(x - ax, y - ay) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7807
7967
  const fontSize = Math.max(6, drawing.fontSize);
7808
7968
  const prevFont = ctx.font;
7809
7969
  ctx.font = `500 ${fontSize}px ${mergedOptions.fontFamily}`;
@@ -7898,8 +8058,9 @@ function createChart(element, options = {}) {
7898
8058
  if (!drawState || x < drawState.chartLeft || x > drawState.chartRight) {
7899
8059
  return null;
7900
8060
  }
8061
+ const dividerTolerance = touchInputActive ? 4 * Math.max(1, mergedOptions.touch?.hitToleranceScale ?? 2.2) : 4;
7901
8062
  for (const pane of paneLayoutInfos) {
7902
- if (pane.visible && Math.abs(y - pane.top) <= 4) {
8063
+ if (pane.visible && Math.abs(y - pane.top) <= dividerTolerance) {
7903
8064
  return { id: pane.id, heightPx: pane.bottom - pane.top };
7904
8065
  }
7905
8066
  }
@@ -8392,7 +8553,43 @@ function createChart(element, options = {}) {
8392
8553
  let lastPointerX = 0;
8393
8554
  let lastPointerY = 0;
8394
8555
  let activePointerId = null;
8556
+ let touchInputActive = false;
8395
8557
  const touchPointers = /* @__PURE__ */ new Map();
8558
+ let longPressTimerId = null;
8559
+ let longPressState = null;
8560
+ const cancelLongPressTimer = () => {
8561
+ if (longPressTimerId !== null) {
8562
+ clearTimeout(longPressTimerId);
8563
+ longPressTimerId = null;
8564
+ }
8565
+ };
8566
+ const emitLongPress = (x, y, scrubbing) => {
8567
+ if (!longPressHandler) return;
8568
+ const rect = canvas.getBoundingClientRect();
8569
+ const index = indexFromCanvasX(x);
8570
+ const barTime = index === null ? null : getTimeForIndex(index);
8571
+ const bar = index === null ? void 0 : data[index];
8572
+ longPressHandler({
8573
+ x,
8574
+ y,
8575
+ clientX: rect.left + x,
8576
+ clientY: rect.top + y,
8577
+ scrubbing,
8578
+ ...getHitRegion(x, y) === "plot" ? { price: roundToPricePrecision(priceFromCanvasY(y)) } : {},
8579
+ ...index === null ? {} : { index },
8580
+ ...barTime ? { time: barTime.toISOString() } : {},
8581
+ ...bar ? {
8582
+ point: {
8583
+ t: bar.time.toISOString(),
8584
+ o: bar.o,
8585
+ h: bar.h,
8586
+ l: bar.l,
8587
+ c: bar.c,
8588
+ ...bar.v === void 0 ? {} : { v: bar.v }
8589
+ }
8590
+ } : {}
8591
+ });
8592
+ };
8396
8593
  let pinchZoomState = null;
8397
8594
  const getTouchPair = () => {
8398
8595
  const points = Array.from(touchPointers.values());
@@ -8458,7 +8655,8 @@ function createChart(element, options = {}) {
8458
8655
  }
8459
8656
  };
8460
8657
  const onPointerDown = (event) => {
8461
- if (event.pointerType === "touch" || event.pointerType === "pen") {
8658
+ touchInputActive = event.pointerType === "touch" || event.pointerType === "pen";
8659
+ if (touchInputActive) {
8462
8660
  event.preventDefault();
8463
8661
  }
8464
8662
  if (mergedOptions.accessibility?.focusable !== false && document.activeElement !== canvas) {
@@ -8475,9 +8673,25 @@ function createChart(element, options = {}) {
8475
8673
  capturePointer(event.pointerId);
8476
8674
  const touchPair = getTouchPair();
8477
8675
  if (touchPair) {
8676
+ cancelLongPressTimer();
8677
+ longPressState = null;
8478
8678
  beginPinchZoom(touchPair[0], touchPair[1]);
8479
8679
  return;
8480
8680
  }
8681
+ cancelLongPressTimer();
8682
+ longPressState = null;
8683
+ const touchOptions = mergedOptions.touch ?? {};
8684
+ const wantsTooltip = touchOptions.longPressTooltip !== false || longPressHandler !== null;
8685
+ if (wantsTooltip && !activeDrawingTool && !draftDrawing && getHitRegion(point.x, point.y) === "plot") {
8686
+ const pointerId = event.pointerId;
8687
+ longPressTimerId = window.setTimeout(() => {
8688
+ longPressTimerId = null;
8689
+ longPressState = { pointerId, x: point.x, y: point.y };
8690
+ setCrosshairPoint(point);
8691
+ scheduleDraw({ overlayOnly: true });
8692
+ emitLongPress(point.x, point.y, false);
8693
+ }, Math.max(120, touchOptions.longPressMs ?? 400));
8694
+ }
8481
8695
  }
8482
8696
  if (measureState) {
8483
8697
  if (measureState.dragging) {
@@ -8643,7 +8857,10 @@ function createChart(element, options = {}) {
8643
8857
  };
8644
8858
  const onPointerMove = (event) => {
8645
8859
  if (event.pointerType === "touch" || event.pointerType === "pen") {
8860
+ touchInputActive = true;
8646
8861
  event.preventDefault();
8862
+ } else if (event.pointerType === "mouse") {
8863
+ touchInputActive = false;
8647
8864
  }
8648
8865
  magnetModifierActive = event.metaKey || event.ctrlKey;
8649
8866
  shiftKeyActive = event.shiftKey;
@@ -8652,6 +8869,8 @@ function createChart(element, options = {}) {
8652
8869
  touchPointers.set(event.pointerId, point);
8653
8870
  const touchPair = getTouchPair();
8654
8871
  if (touchPair) {
8872
+ cancelLongPressTimer();
8873
+ longPressState = null;
8655
8874
  if (!pinchZoomState) {
8656
8875
  beginPinchZoom(touchPair[0], touchPair[1]);
8657
8876
  }
@@ -8661,6 +8880,21 @@ function createChart(element, options = {}) {
8661
8880
  if (pinchZoomState) {
8662
8881
  return;
8663
8882
  }
8883
+ if (longPressState && longPressState.pointerId === event.pointerId) {
8884
+ longPressState = { ...longPressState, x: point.x, y: point.y };
8885
+ setCrosshairPoint(point);
8886
+ emitCrosshairMove(point.x, point.y, getHitRegion(point.x, point.y) === "plot" ? "plot" : "plot");
8887
+ scheduleDraw({ overlayOnly: true });
8888
+ emitLongPress(point.x, point.y, true);
8889
+ return;
8890
+ }
8891
+ if (longPressTimerId !== null) {
8892
+ const dx = point.x - (pointerDownInfo?.x ?? point.x);
8893
+ const dy = point.y - (pointerDownInfo?.y ?? point.y);
8894
+ if (dx * dx + dy * dy > 36) {
8895
+ cancelLongPressTimer();
8896
+ }
8897
+ }
8664
8898
  }
8665
8899
  if (pointerDownInfo && pointerDownInfo.pointerId === event.pointerId && !pointerDownInfo.moved) {
8666
8900
  const dx = point.x - pointerDownInfo.x;
@@ -8924,6 +9158,18 @@ function createChart(element, options = {}) {
8924
9158
  lastPointerY = point.y;
8925
9159
  };
8926
9160
  const endPointerDrag = (event) => {
9161
+ cancelLongPressTimer();
9162
+ if (longPressState && (!event || event.pointerId === longPressState.pointerId)) {
9163
+ longPressState = null;
9164
+ touchPointers.delete(event?.pointerId ?? -1);
9165
+ setCrosshairPoint(null);
9166
+ isDragging = false;
9167
+ dragMode = null;
9168
+ activePointerId = null;
9169
+ pointerDownInfo = null;
9170
+ scheduleDraw({ overlayOnly: true });
9171
+ return;
9172
+ }
8927
9173
  if (event?.pointerType === "touch") {
8928
9174
  touchPointers.delete(event.pointerId);
8929
9175
  if (pinchZoomState) {
@@ -9740,6 +9986,9 @@ function createChart(element, options = {}) {
9740
9986
  const onKeyboardShortcut = (handler) => {
9741
9987
  keyboardShortcutHandler = handler;
9742
9988
  };
9989
+ const onLongPress = (handler) => {
9990
+ longPressHandler = handler;
9991
+ };
9743
9992
  const focus = () => {
9744
9993
  canvas.focus({ preventScroll: true });
9745
9994
  };
@@ -9793,6 +10042,10 @@ function createChart(element, options = {}) {
9793
10042
  return canvas.toDataURL(options2.type ?? "image/png", options2.quality);
9794
10043
  };
9795
10044
  const destroy = () => {
10045
+ cancelLongPressTimer();
10046
+ datafeedUnsubscribe?.();
10047
+ datafeedUnsubscribe = null;
10048
+ datafeed = null;
9796
10049
  cancelKineticPan();
9797
10050
  if (smoothingRafId !== null) {
9798
10051
  cancelAnimationFrame(smoothingRafId);
@@ -9884,6 +10137,8 @@ function createChart(element, options = {}) {
9884
10137
  addCompareSeries,
9885
10138
  removeCompareSeries,
9886
10139
  getCompareSeries,
10140
+ setDatafeed,
10141
+ onLongPress,
9887
10142
  cancelDrawing,
9888
10143
  setTradeMarkers,
9889
10144
  setSelectedDrawing,