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.
package/dist/index.cjs CHANGED
@@ -2854,6 +2854,8 @@ var DEFAULT_OPTIONS = {
2854
2854
  keyboard: { enabled: true, panBars: 1, deleteSelectedDrawing: true },
2855
2855
  accessibility: { label: "Price chart", description: "", focusable: true },
2856
2856
  downsampling: { enabled: true, thresholdPx: 1.5 },
2857
+ touch: { hitToleranceScale: 2.2, longPressTooltip: true, longPressMs: 400 },
2858
+ datafeedOptions: { prefetchThresholdBars: 150, cooldownMs: 1200, chunkBars: 1500 },
2857
2859
  crosshair: DEFAULT_CROSSHAIR_OPTIONS,
2858
2860
  grid: DEFAULT_GRID_OPTIONS,
2859
2861
  watermark: DEFAULT_WATERMARK_OPTIONS,
@@ -2908,6 +2910,14 @@ var mergeChartOptions = (baseOptions, options = {}) => ({
2908
2910
  ...baseOptions.downsampling,
2909
2911
  ...options.downsampling ?? {}
2910
2912
  },
2913
+ touch: {
2914
+ ...baseOptions.touch,
2915
+ ...options.touch ?? {}
2916
+ },
2917
+ datafeedOptions: {
2918
+ ...baseOptions.datafeedOptions,
2919
+ ...options.datafeedOptions ?? {}
2920
+ },
2911
2921
  crosshair: {
2912
2922
  ...baseOptions.crosshair,
2913
2923
  ...options.crosshair ?? {}
@@ -3082,6 +3092,7 @@ function createChart(element, options = {}) {
3082
3092
  let drawingDoubleClickHandler = null;
3083
3093
  let drawingEditTextHandler = null;
3084
3094
  let selectionChangeHandler = null;
3095
+ let longPressHandler = null;
3085
3096
  let contextMenuHandler = null;
3086
3097
  let keyboardShortcutHandler = null;
3087
3098
  let lastSelectionSignature = null;
@@ -3841,6 +3852,92 @@ function createChart(element, options = {}) {
3841
3852
  scheduleDraw({ updateAutoScale: true });
3842
3853
  };
3843
3854
  const getCompareSeries = () => compareSeriesStates.map((state) => state.options);
3855
+ let datafeed = null;
3856
+ let datafeedUnsubscribe = null;
3857
+ let datafeedLoading = false;
3858
+ let datafeedExhausted = false;
3859
+ let datafeedLastRequestAt = 0;
3860
+ const estimateBarStepMs = () => {
3861
+ if (mergedOptions.timeStepMs > 0) return mergedOptions.timeStepMs;
3862
+ if (data.length < 2) return 6e4;
3863
+ const span = data[data.length - 1].time.getTime() - data[0].time.getTime();
3864
+ return Math.max(1e3, span / (data.length - 1));
3865
+ };
3866
+ const requestDatafeedBars = async (request) => {
3867
+ if (!datafeed) return [];
3868
+ try {
3869
+ return await datafeed.getBars(request) ?? [];
3870
+ } catch (error) {
3871
+ datafeed.onError?.(error, request);
3872
+ return [];
3873
+ }
3874
+ };
3875
+ const maybeLoadOlderHistory = () => {
3876
+ if (!datafeed || datafeedLoading || datafeedExhausted || data.length === 0) return;
3877
+ const options2 = mergedOptions.datafeedOptions ?? DEFAULT_OPTIONS.datafeedOptions;
3878
+ const threshold = options2?.prefetchThresholdBars ?? 150;
3879
+ const cooldown = options2?.cooldownMs ?? 1200;
3880
+ if (Date.now() - datafeedLastRequestAt < cooldown) return;
3881
+ if (xCenter - xSpan / 2 > threshold) return;
3882
+ const stepMs = estimateBarStepMs();
3883
+ const chunk = Math.max(10, options2?.chunkBars ?? 1500);
3884
+ const toMs = data[0].time.getTime();
3885
+ datafeedLoading = true;
3886
+ datafeedLastRequestAt = Date.now();
3887
+ void requestDatafeedBars({
3888
+ fromMs: toMs - chunk * stepMs,
3889
+ toMs,
3890
+ countBack: chunk,
3891
+ firstRequest: false
3892
+ }).then((bars) => {
3893
+ const older = bars.filter((bar) => new Date(bar.t).getTime() < toMs);
3894
+ if (older.length === 0) {
3895
+ datafeedExhausted = true;
3896
+ return;
3897
+ }
3898
+ const merged = [...older, ...data.map((bar) => ({
3899
+ t: bar.time.toISOString(),
3900
+ o: bar.o,
3901
+ h: bar.h,
3902
+ l: bar.l,
3903
+ c: bar.c,
3904
+ ...bar.v === void 0 ? {} : { v: bar.v }
3905
+ }))];
3906
+ setData(merged);
3907
+ }).finally(() => {
3908
+ datafeedLoading = false;
3909
+ });
3910
+ };
3911
+ const setDatafeed = async (nextDatafeed) => {
3912
+ datafeedUnsubscribe?.();
3913
+ datafeedUnsubscribe = null;
3914
+ datafeed = nextDatafeed;
3915
+ datafeedExhausted = false;
3916
+ datafeedLoading = false;
3917
+ datafeedLastRequestAt = 0;
3918
+ if (!nextDatafeed) return;
3919
+ await nextDatafeed.onReady?.();
3920
+ if (datafeed !== nextDatafeed) return;
3921
+ const stepMs = estimateBarStepMs();
3922
+ const countBack = Math.max(50, Math.round(mergedOptions.initialVisibleBars * 3));
3923
+ const toMs = Date.now();
3924
+ datafeedLoading = true;
3925
+ const bars = await requestDatafeedBars({
3926
+ fromMs: toMs - countBack * stepMs,
3927
+ toMs,
3928
+ countBack,
3929
+ firstRequest: true
3930
+ });
3931
+ datafeedLoading = false;
3932
+ if (datafeed !== nextDatafeed) return;
3933
+ if (bars.length > 0) {
3934
+ setData(bars);
3935
+ }
3936
+ const unsubscribe = nextDatafeed.subscribeBars?.((bar) => {
3937
+ if (datafeed === nextDatafeed) upsertBar(bar);
3938
+ });
3939
+ datafeedUnsubscribe = typeof unsubscribe === "function" ? unsubscribe : null;
3940
+ };
3844
3941
  const formatHoverTimeLabel = (time, mode) => {
3845
3942
  if (mode === "time") {
3846
3943
  return time.toLocaleTimeString(void 0, {
@@ -6835,6 +6932,7 @@ function createChart(element, options = {}) {
6835
6932
  };
6836
6933
  paintCrosshairOverlay();
6837
6934
  emitSelectionChangeIfMoved();
6935
+ maybeLoadOlderHistory();
6838
6936
  };
6839
6937
  const paintCrosshairOverlay = () => {
6840
6938
  crosshairPriceActionRegion = null;
@@ -7027,6 +7125,67 @@ function createChart(element, options = {}) {
7027
7125
  drawText(timeText, timeX + labelPaddingX, timeY + timeHeight / 2, "left", "middle", labelTextColor);
7028
7126
  }
7029
7127
  }
7128
+ if (longPressState && (mergedOptions.touch?.longPressTooltip ?? true)) {
7129
+ const index = indexFromCanvasX(cx);
7130
+ const bar = index === null ? void 0 : data[index];
7131
+ if (bar) {
7132
+ const barTime = getTimeForIndex(index);
7133
+ const changeAbs = bar.c - bar.o;
7134
+ const changePct = bar.o !== 0 ? changeAbs / bar.o * 100 : 0;
7135
+ const rows = [
7136
+ ["O", formatPrice(bar.o), null],
7137
+ ["H", formatPrice(bar.h), null],
7138
+ ["L", formatPrice(bar.l), null],
7139
+ ["C", formatPrice(bar.c), null],
7140
+ [
7141
+ "Chg",
7142
+ `${changeAbs >= 0 ? "+" : ""}${formatPrice(changeAbs)} (${changeAbs >= 0 ? "+" : ""}${changePct.toFixed(2)}%)`,
7143
+ changeAbs >= 0 ? mergedOptions.upColor : mergedOptions.downColor
7144
+ ]
7145
+ ];
7146
+ if (bar.v !== void 0) {
7147
+ rows.push([
7148
+ "Vol",
7149
+ bar.v >= 1e6 ? `${(bar.v / 1e6).toFixed(2)}M` : bar.v >= 1e3 ? `${(bar.v / 1e3).toFixed(1)}K` : String(Math.round(bar.v)),
7150
+ null
7151
+ ]);
7152
+ }
7153
+ const prevFont = ctx.font;
7154
+ const fontSize = 11;
7155
+ ctx.font = `${fontSize}px ${mergedOptions.fontFamily}`;
7156
+ const headerText = barTime ? formatHoverTimeLabel(barTime, "auto") : "";
7157
+ const rowGap = 3;
7158
+ const padding = 8;
7159
+ const labelColumn = 26;
7160
+ const contentWidth = Math.max(
7161
+ measureTextWidth(headerText),
7162
+ ...rows.map(([, value]) => labelColumn + measureTextWidth(value))
7163
+ );
7164
+ const boxWidth = Math.ceil(contentWidth) + padding * 2;
7165
+ const boxHeight = padding * 2 + (fontSize + rowGap) * (rows.length + (headerText ? 1 : 0)) - rowGap;
7166
+ const fingerGap = 26;
7167
+ const boxX = cx + fingerGap + boxWidth <= chartRight ? cx + fingerGap : Math.max(chartLeft + 4, cx - fingerGap - boxWidth);
7168
+ const boxY = clamp(cy - boxHeight - fingerGap, chartTop + 4, chartBottom - boxHeight - 4);
7169
+ ctx.save();
7170
+ ctx.fillStyle = "rgba(16,17,20,0.94)";
7171
+ fillRoundedRect(Math.round(boxX), Math.round(boxY), boxWidth, boxHeight, 6);
7172
+ ctx.strokeStyle = "rgba(255,255,255,0.14)";
7173
+ ctx.lineWidth = 1;
7174
+ ctx.strokeRect(Math.round(boxX) + 0.5, Math.round(boxY) + 0.5, boxWidth - 1, boxHeight - 1);
7175
+ let textY = boxY + padding + fontSize / 2;
7176
+ if (headerText) {
7177
+ drawText(headerText, boxX + padding, textY, "left", "middle", "rgba(255,255,255,0.55)");
7178
+ textY += fontSize + rowGap;
7179
+ }
7180
+ for (const [label, value, valueColor] of rows) {
7181
+ drawText(label, boxX + padding, textY, "left", "middle", "rgba(255,255,255,0.5)");
7182
+ drawText(value, boxX + padding + labelColumn, textY, "left", "middle", valueColor ?? "#e6e8ee");
7183
+ textY += fontSize + rowGap;
7184
+ }
7185
+ ctx.restore();
7186
+ ctx.font = prevFont;
7187
+ }
7188
+ }
7030
7189
  };
7031
7190
  const drawOverlayOnly = () => {
7032
7191
  if (!baseCtx || !overlayLayout || baseCanvas.width !== canvas.width || baseCanvas.height !== canvas.height || baseCanvas.width === 0) {
@@ -7536,6 +7695,7 @@ function createChart(element, options = {}) {
7536
7695
  return Math.hypot(x - (x1 + t * dx), y - (y1 + t * dy));
7537
7696
  };
7538
7697
  const getDrawingHit = (x, y) => {
7698
+ const hitTolerance = touchInputActive ? Math.max(1, mergedOptions.touch?.hitToleranceScale ?? 2.2) : 1;
7539
7699
  for (let index = drawings.length - 1; index >= 0; index -= 1) {
7540
7700
  const drawing = drawings[index];
7541
7701
  if (!drawing?.visible) continue;
@@ -7544,10 +7704,10 @@ function createChart(element, options = {}) {
7544
7704
  if (!point) continue;
7545
7705
  const lineY = canvasYFromDrawingPrice(point.price);
7546
7706
  const handleX = drawState ? drawState.chartRight - 64 : 0;
7547
- if (Math.hypot(x - handleX, y - lineY) <= 8) {
7707
+ if (Math.hypot(x - handleX, y - lineY) <= 8 * hitTolerance) {
7548
7708
  return { drawing, target: "handle", pointIndex: 0 };
7549
7709
  }
7550
- if (Math.abs(y - lineY) <= 7) {
7710
+ if (Math.abs(y - lineY) <= 7 * hitTolerance) {
7551
7711
  return { drawing, target: "line" };
7552
7712
  }
7553
7713
  } else if (drawing.type === "vertical-line") {
@@ -7555,10 +7715,10 @@ function createChart(element, options = {}) {
7555
7715
  if (!point) continue;
7556
7716
  const lineX = canvasXFromDrawingPoint(point);
7557
7717
  const handleY = drawState ? (drawState.chartTop + drawState.chartBottom) / 2 : 0;
7558
- if (Math.hypot(x - lineX, y - handleY) <= 8) {
7718
+ if (Math.hypot(x - lineX, y - handleY) <= 8 * hitTolerance) {
7559
7719
  return { drawing, target: "handle", pointIndex: 0 };
7560
7720
  }
7561
- if (Math.abs(x - lineX) <= 7) {
7721
+ if (Math.abs(x - lineX) <= 7 * hitTolerance) {
7562
7722
  return { drawing, target: "line" };
7563
7723
  }
7564
7724
  } else if (drawing.type === "trendline") {
@@ -7569,13 +7729,13 @@ function createChart(element, options = {}) {
7569
7729
  const y1 = canvasYFromDrawingPrice(first.price);
7570
7730
  const x2 = canvasXFromDrawingPoint(second);
7571
7731
  const y2 = canvasYFromDrawingPrice(second.price);
7572
- if (Math.hypot(x - x1, y - y1) <= 8) {
7732
+ if (Math.hypot(x - x1, y - y1) <= 8 * hitTolerance) {
7573
7733
  return { drawing, target: "handle", pointIndex: 0 };
7574
7734
  }
7575
- if (Math.hypot(x - x2, y - y2) <= 8) {
7735
+ if (Math.hypot(x - x2, y - y2) <= 8 * hitTolerance) {
7576
7736
  return { drawing, target: "handle", pointIndex: 1 };
7577
7737
  }
7578
- if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
7738
+ if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6 * hitTolerance) {
7579
7739
  return { drawing, target: "line" };
7580
7740
  }
7581
7741
  } else if (drawing.type === "ray") {
@@ -7586,10 +7746,10 @@ function createChart(element, options = {}) {
7586
7746
  const y1 = canvasYFromDrawingPrice(first.price);
7587
7747
  const x2 = canvasXFromDrawingPoint(second);
7588
7748
  const y2 = canvasYFromDrawingPrice(second.price);
7589
- if (Math.hypot(x - x1, y - y1) <= 8) {
7749
+ if (Math.hypot(x - x1, y - y1) <= 8 * hitTolerance) {
7590
7750
  return { drawing, target: "handle", pointIndex: 0 };
7591
7751
  }
7592
- if (Math.hypot(x - x2, y - y2) <= 8) {
7752
+ if (Math.hypot(x - x2, y - y2) <= 8 * hitTolerance) {
7593
7753
  return { drawing, target: "handle", pointIndex: 1 };
7594
7754
  }
7595
7755
  const dx = x2 - x1;
@@ -7599,7 +7759,7 @@ function createChart(element, options = {}) {
7599
7759
  const t = Math.max(0, ((x - x1) * dx + (y - y1) * dy) / lengthSq);
7600
7760
  const projX = x1 + t * dx;
7601
7761
  const projY = y1 + t * dy;
7602
- if (Math.hypot(x - projX, y - projY) <= 6) {
7762
+ if (Math.hypot(x - projX, y - projY) <= 6 * hitTolerance) {
7603
7763
  return { drawing, target: "line" };
7604
7764
  }
7605
7765
  }
@@ -7611,17 +7771,17 @@ function createChart(element, options = {}) {
7611
7771
  const y1 = canvasYFromDrawingPrice(first.price);
7612
7772
  const x2 = canvasXFromDrawingPoint(second);
7613
7773
  const y2 = canvasYFromDrawingPrice(second.price);
7614
- if (Math.hypot(x - x1, y - y1) <= 9) {
7774
+ if (Math.hypot(x - x1, y - y1) <= 9 * hitTolerance) {
7615
7775
  return { drawing, target: "handle", pointIndex: 0 };
7616
7776
  }
7617
- if (Math.hypot(x - x2, y - y2) <= 9) {
7777
+ if (Math.hypot(x - x2, y - y2) <= 9 * hitTolerance) {
7618
7778
  return { drawing, target: "handle", pointIndex: 1 };
7619
7779
  }
7620
7780
  const fibRatios = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1, 1.618];
7621
7781
  for (const ratio of fibRatios) {
7622
7782
  const price = first.price + (second.price - first.price) * (1 - ratio);
7623
7783
  const lineY = canvasYFromDrawingPrice(price);
7624
- if (Math.abs(y - lineY) <= 5) {
7784
+ if (Math.abs(y - lineY) <= 5 * hitTolerance) {
7625
7785
  return { drawing, target: "line" };
7626
7786
  }
7627
7787
  }
@@ -7634,30 +7794,30 @@ function createChart(element, options = {}) {
7634
7794
  const y0 = canvasYFromDrawingPrice(p0.price);
7635
7795
  const x1 = canvasXFromDrawingPoint(p1);
7636
7796
  const y1 = canvasYFromDrawingPrice(p1.price);
7637
- if (Math.hypot(x - x0, y - y0) <= 8) {
7797
+ if (Math.hypot(x - x0, y - y0) <= 8 * hitTolerance) {
7638
7798
  return { drawing, target: "handle", pointIndex: 0 };
7639
7799
  }
7640
- if (Math.hypot(x - x1, y - y1) <= 8) {
7800
+ if (Math.hypot(x - x1, y - y1) <= 8 * hitTolerance) {
7641
7801
  return { drawing, target: "handle", pointIndex: 1 };
7642
7802
  }
7643
7803
  if (p2) {
7644
7804
  const x2 = canvasXFromDrawingPoint(p2);
7645
7805
  const y2 = canvasYFromDrawingPrice(p2.price);
7646
- if (Math.hypot(x - x2, y - y2) <= 8) {
7806
+ if (Math.hypot(x - x2, y - y2) <= 8 * hitTolerance) {
7647
7807
  return { drawing, target: "handle", pointIndex: 2 };
7648
7808
  }
7649
7809
  const move = p1.price - p0.price;
7650
7810
  const fibExtRatios = [0, 0.382, 0.5, 0.618, 1, 1.272, 1.618, 2.618];
7651
7811
  for (const ratio of fibExtRatios) {
7652
7812
  const lineY = canvasYFromDrawingPrice(p2.price + move * ratio);
7653
- if (Math.abs(y - lineY) <= 5) {
7813
+ if (Math.abs(y - lineY) <= 5 * hitTolerance) {
7654
7814
  return { drawing, target: "line" };
7655
7815
  }
7656
7816
  }
7657
- if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 || distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
7817
+ if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 * hitTolerance || distanceToSegment(x, y, x1, y1, x2, y2) <= 6 * hitTolerance) {
7658
7818
  return { drawing, target: "line" };
7659
7819
  }
7660
- } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
7820
+ } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 * hitTolerance) {
7661
7821
  return { drawing, target: "line" };
7662
7822
  }
7663
7823
  } else if (drawing.type === "long-position" || drawing.type === "short-position") {
@@ -7671,10 +7831,10 @@ function createChart(element, options = {}) {
7671
7831
  const entryY = canvasYFromDrawingPrice(entry.price);
7672
7832
  const targetY = canvasYFromDrawingPrice(target.price);
7673
7833
  const stopY = canvasYFromDrawingPrice(stop.price);
7674
- if (Math.hypot(x - leftX, y - targetY) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7675
- if (Math.hypot(x - leftX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7676
- if (Math.hypot(x - leftX, y - stopY) <= 9) return { drawing, target: "handle", pointIndex: 2 };
7677
- if (Math.hypot(x - rightX, y - entryY) <= 9) return { drawing, target: "handle", pointIndex: 3 };
7834
+ if (Math.hypot(x - leftX, y - targetY) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
7835
+ if (Math.hypot(x - leftX, y - entryY) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7836
+ if (Math.hypot(x - leftX, y - stopY) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 2 };
7837
+ if (Math.hypot(x - rightX, y - entryY) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 3 };
7678
7838
  const x0 = Math.min(leftX, rightX);
7679
7839
  const x1 = Math.max(leftX, rightX);
7680
7840
  const yTop = Math.min(targetY, stopY, entryY);
@@ -7690,10 +7850,10 @@ function createChart(element, options = {}) {
7690
7850
  const px1 = canvasXFromDrawingPoint(p1);
7691
7851
  const y0 = canvasYFromDrawingPrice(p0.price);
7692
7852
  const y1 = canvasYFromDrawingPrice(p1.price);
7693
- if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7694
- if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7695
- if (Math.hypot(x - px0, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 2 };
7696
- if (Math.hypot(x - px1, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 3 };
7853
+ if (Math.hypot(x - px0, y - y0) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7854
+ if (Math.hypot(x - px1, y - y1) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
7855
+ if (Math.hypot(x - px0, y - y1) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 2 };
7856
+ if (Math.hypot(x - px1, y - y0) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 3 };
7697
7857
  if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1) && y >= Math.min(y0, y1) && y <= Math.max(y0, y1)) {
7698
7858
  return { drawing, target: "line" };
7699
7859
  }
@@ -7705,16 +7865,16 @@ function createChart(element, options = {}) {
7705
7865
  const px1 = canvasXFromDrawingPoint(p1);
7706
7866
  const y0 = canvasYFromDrawingPrice(p0.price);
7707
7867
  const y1 = canvasYFromDrawingPrice(p1.price);
7708
- if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7709
- if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7710
- if (Math.hypot(x - px0, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 2 };
7711
- if (Math.hypot(x - px1, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 3 };
7868
+ if (Math.hypot(x - px0, y - y0) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7869
+ if (Math.hypot(x - px1, y - y1) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
7870
+ if (Math.hypot(x - px0, y - y1) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 2 };
7871
+ if (Math.hypot(x - px1, y - y0) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 3 };
7712
7872
  const cx = (px0 + px1) / 2;
7713
7873
  const cy = (y0 + y1) / 2;
7714
7874
  const rx = Math.max(1, Math.abs(px1 - px0) / 2);
7715
7875
  const ry = Math.max(1, Math.abs(y1 - y0) / 2);
7716
7876
  const norm = ((x - cx) / rx) ** 2 + ((y - cy) / ry) ** 2;
7717
- if (norm <= 1) {
7877
+ if (norm <= 1 * hitTolerance) {
7718
7878
  return { drawing, target: "line" };
7719
7879
  }
7720
7880
  } else if (drawing.type === "parallel-channel") {
@@ -7726,18 +7886,18 @@ function createChart(element, options = {}) {
7726
7886
  const y0 = canvasYFromDrawingPrice(p0.price);
7727
7887
  const x1 = canvasXFromDrawingPoint(p1);
7728
7888
  const y1 = canvasYFromDrawingPrice(p1.price);
7729
- if (Math.hypot(x - x0, y - y0) <= 8) return { drawing, target: "handle", pointIndex: 0 };
7730
- if (Math.hypot(x - x1, y - y1) <= 8) return { drawing, target: "handle", pointIndex: 1 };
7889
+ if (Math.hypot(x - x0, y - y0) <= 8 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7890
+ if (Math.hypot(x - x1, y - y1) <= 8 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
7731
7891
  if (p2) {
7732
7892
  const x2 = canvasXFromDrawingPoint(p2);
7733
7893
  const y2 = canvasYFromDrawingPrice(p2.price);
7734
- if (Math.hypot(x - x2, y - y2) <= 8) return { drawing, target: "handle", pointIndex: 2 };
7894
+ if (Math.hypot(x - x2, y - y2) <= 8 * hitTolerance) return { drawing, target: "handle", pointIndex: 2 };
7735
7895
  const indexSpan = p1.index - p0.index;
7736
7896
  const slope = indexSpan !== 0 ? (p1.price - p0.price) / indexSpan : 0;
7737
7897
  const offset = p2.price - (p0.price + slope * (p2.index - p0.index));
7738
7898
  const q0y = canvasYFromDrawingPrice(p0.price + offset);
7739
7899
  const q1y = canvasYFromDrawingPrice(p1.price + offset);
7740
- if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 || distanceToSegment(x, y, x0, q0y, x1, q1y) <= 6) {
7900
+ if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 * hitTolerance || distanceToSegment(x, y, x0, q0y, x1, q1y) <= 6 * hitTolerance) {
7741
7901
  return { drawing, target: "line" };
7742
7902
  }
7743
7903
  const minX = Math.min(x0, x1);
@@ -7750,7 +7910,7 @@ function createChart(element, options = {}) {
7750
7910
  return { drawing, target: "line" };
7751
7911
  }
7752
7912
  }
7753
- } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6) {
7913
+ } else if (distanceToSegment(x, y, x0, y0, x1, y1) <= 6 * hitTolerance) {
7754
7914
  return { drawing, target: "line" };
7755
7915
  }
7756
7916
  } else if (drawing.type === "arrow") {
@@ -7761,13 +7921,13 @@ function createChart(element, options = {}) {
7761
7921
  const y1 = canvasYFromDrawingPrice(first.price);
7762
7922
  const x2 = canvasXFromDrawingPoint(second);
7763
7923
  const y2 = canvasYFromDrawingPrice(second.price);
7764
- if (Math.hypot(x - x1, y - y1) <= 8) {
7924
+ if (Math.hypot(x - x1, y - y1) <= 8 * hitTolerance) {
7765
7925
  return { drawing, target: "handle", pointIndex: 0 };
7766
7926
  }
7767
- if (Math.hypot(x - x2, y - y2) <= 8) {
7927
+ if (Math.hypot(x - x2, y - y2) <= 8 * hitTolerance) {
7768
7928
  return { drawing, target: "handle", pointIndex: 1 };
7769
7929
  }
7770
- if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
7930
+ if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6 * hitTolerance) {
7771
7931
  return { drawing, target: "line" };
7772
7932
  }
7773
7933
  } else if (drawing.type === "brush") {
@@ -7779,7 +7939,7 @@ function createChart(element, options = {}) {
7779
7939
  for (let i = 1; i < pts.length; i += 1) {
7780
7940
  const nextX = canvasXFromDrawingPoint(pts[i]);
7781
7941
  const nextY = canvasYFromDrawingPrice(pts[i].price);
7782
- if (distanceToSegment(x, y, prevX, prevY, nextX, nextY) <= 7) {
7942
+ if (distanceToSegment(x, y, prevX, prevY, nextX, nextY) <= 7 * hitTolerance) {
7783
7943
  hitBody = true;
7784
7944
  break;
7785
7945
  }
@@ -7797,8 +7957,8 @@ function createChart(element, options = {}) {
7797
7957
  const ay = canvasYFromDrawingPrice(anchor.price);
7798
7958
  const bx = canvasXFromDrawingPoint(boxPoint);
7799
7959
  const by = canvasYFromDrawingPrice(boxPoint.price);
7800
- if (Math.hypot(x - ax, y - ay) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7801
- if (Math.hypot(x - bx, y - by) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7960
+ if (Math.hypot(x - ax, y - ay) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7961
+ if (Math.hypot(x - bx, y - by) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
7802
7962
  const fontSize = Math.max(6, drawing.fontSize);
7803
7963
  const prevFont = ctx.font;
7804
7964
  ctx.font = `500 ${fontSize}px ${mergedOptions.fontFamily}`;
@@ -7814,7 +7974,7 @@ function createChart(element, options = {}) {
7814
7974
  if (x >= bx && x <= bx + blockW && y >= by && y <= by + blockH) {
7815
7975
  return { drawing, target: "line" };
7816
7976
  }
7817
- if (distanceToSegment(x, y, bx + blockW / 2, by + blockH / 2, ax, ay) <= 5) {
7977
+ if (distanceToSegment(x, y, bx + blockW / 2, by + blockH / 2, ax, ay) <= 5 * hitTolerance) {
7818
7978
  return { drawing, target: "line" };
7819
7979
  }
7820
7980
  } else if (drawing.type === "price-range") {
@@ -7825,8 +7985,8 @@ function createChart(element, options = {}) {
7825
7985
  const px1 = canvasXFromDrawingPoint(p1);
7826
7986
  const y0 = canvasYFromDrawingPrice(p0.price);
7827
7987
  const y1 = canvasYFromDrawingPrice(p1.price);
7828
- if (Math.hypot(x - px0, y - y0) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7829
- if (Math.hypot(x - px1, y - y1) <= 9) return { drawing, target: "handle", pointIndex: 1 };
7988
+ if (Math.hypot(x - px0, y - y0) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7989
+ if (Math.hypot(x - px1, y - y1) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 1 };
7830
7990
  if (x >= Math.min(px0, px1) && x <= Math.max(px0, px1) && y >= Math.min(y0, y1) && y <= Math.max(y0, y1)) {
7831
7991
  return { drawing, target: "line" };
7832
7992
  }
@@ -7835,7 +7995,7 @@ function createChart(element, options = {}) {
7835
7995
  if (!anchor) continue;
7836
7996
  const ax = canvasXFromDrawingPoint(anchor);
7837
7997
  const ay = canvasYFromDrawingPrice(anchor.price);
7838
- if (Math.hypot(x - ax, y - ay) <= 9) return { drawing, target: "handle", pointIndex: 0 };
7998
+ if (Math.hypot(x - ax, y - ay) <= 9 * hitTolerance) return { drawing, target: "handle", pointIndex: 0 };
7839
7999
  const fontSize = Math.max(6, drawing.fontSize);
7840
8000
  const prevFont = ctx.font;
7841
8001
  ctx.font = `500 ${fontSize}px ${mergedOptions.fontFamily}`;
@@ -7930,8 +8090,9 @@ function createChart(element, options = {}) {
7930
8090
  if (!drawState || x < drawState.chartLeft || x > drawState.chartRight) {
7931
8091
  return null;
7932
8092
  }
8093
+ const dividerTolerance = touchInputActive ? 4 * Math.max(1, mergedOptions.touch?.hitToleranceScale ?? 2.2) : 4;
7933
8094
  for (const pane of paneLayoutInfos) {
7934
- if (pane.visible && Math.abs(y - pane.top) <= 4) {
8095
+ if (pane.visible && Math.abs(y - pane.top) <= dividerTolerance) {
7935
8096
  return { id: pane.id, heightPx: pane.bottom - pane.top };
7936
8097
  }
7937
8098
  }
@@ -8424,7 +8585,43 @@ function createChart(element, options = {}) {
8424
8585
  let lastPointerX = 0;
8425
8586
  let lastPointerY = 0;
8426
8587
  let activePointerId = null;
8588
+ let touchInputActive = false;
8427
8589
  const touchPointers = /* @__PURE__ */ new Map();
8590
+ let longPressTimerId = null;
8591
+ let longPressState = null;
8592
+ const cancelLongPressTimer = () => {
8593
+ if (longPressTimerId !== null) {
8594
+ clearTimeout(longPressTimerId);
8595
+ longPressTimerId = null;
8596
+ }
8597
+ };
8598
+ const emitLongPress = (x, y, scrubbing) => {
8599
+ if (!longPressHandler) return;
8600
+ const rect = canvas.getBoundingClientRect();
8601
+ const index = indexFromCanvasX(x);
8602
+ const barTime = index === null ? null : getTimeForIndex(index);
8603
+ const bar = index === null ? void 0 : data[index];
8604
+ longPressHandler({
8605
+ x,
8606
+ y,
8607
+ clientX: rect.left + x,
8608
+ clientY: rect.top + y,
8609
+ scrubbing,
8610
+ ...getHitRegion(x, y) === "plot" ? { price: roundToPricePrecision(priceFromCanvasY(y)) } : {},
8611
+ ...index === null ? {} : { index },
8612
+ ...barTime ? { time: barTime.toISOString() } : {},
8613
+ ...bar ? {
8614
+ point: {
8615
+ t: bar.time.toISOString(),
8616
+ o: bar.o,
8617
+ h: bar.h,
8618
+ l: bar.l,
8619
+ c: bar.c,
8620
+ ...bar.v === void 0 ? {} : { v: bar.v }
8621
+ }
8622
+ } : {}
8623
+ });
8624
+ };
8428
8625
  let pinchZoomState = null;
8429
8626
  const getTouchPair = () => {
8430
8627
  const points = Array.from(touchPointers.values());
@@ -8490,7 +8687,8 @@ function createChart(element, options = {}) {
8490
8687
  }
8491
8688
  };
8492
8689
  const onPointerDown = (event) => {
8493
- if (event.pointerType === "touch" || event.pointerType === "pen") {
8690
+ touchInputActive = event.pointerType === "touch" || event.pointerType === "pen";
8691
+ if (touchInputActive) {
8494
8692
  event.preventDefault();
8495
8693
  }
8496
8694
  if (mergedOptions.accessibility?.focusable !== false && document.activeElement !== canvas) {
@@ -8507,9 +8705,25 @@ function createChart(element, options = {}) {
8507
8705
  capturePointer(event.pointerId);
8508
8706
  const touchPair = getTouchPair();
8509
8707
  if (touchPair) {
8708
+ cancelLongPressTimer();
8709
+ longPressState = null;
8510
8710
  beginPinchZoom(touchPair[0], touchPair[1]);
8511
8711
  return;
8512
8712
  }
8713
+ cancelLongPressTimer();
8714
+ longPressState = null;
8715
+ const touchOptions = mergedOptions.touch ?? {};
8716
+ const wantsTooltip = touchOptions.longPressTooltip !== false || longPressHandler !== null;
8717
+ if (wantsTooltip && !activeDrawingTool && !draftDrawing && getHitRegion(point.x, point.y) === "plot") {
8718
+ const pointerId = event.pointerId;
8719
+ longPressTimerId = window.setTimeout(() => {
8720
+ longPressTimerId = null;
8721
+ longPressState = { pointerId, x: point.x, y: point.y };
8722
+ setCrosshairPoint(point);
8723
+ scheduleDraw({ overlayOnly: true });
8724
+ emitLongPress(point.x, point.y, false);
8725
+ }, Math.max(120, touchOptions.longPressMs ?? 400));
8726
+ }
8513
8727
  }
8514
8728
  if (measureState) {
8515
8729
  if (measureState.dragging) {
@@ -8675,7 +8889,10 @@ function createChart(element, options = {}) {
8675
8889
  };
8676
8890
  const onPointerMove = (event) => {
8677
8891
  if (event.pointerType === "touch" || event.pointerType === "pen") {
8892
+ touchInputActive = true;
8678
8893
  event.preventDefault();
8894
+ } else if (event.pointerType === "mouse") {
8895
+ touchInputActive = false;
8679
8896
  }
8680
8897
  magnetModifierActive = event.metaKey || event.ctrlKey;
8681
8898
  shiftKeyActive = event.shiftKey;
@@ -8684,6 +8901,8 @@ function createChart(element, options = {}) {
8684
8901
  touchPointers.set(event.pointerId, point);
8685
8902
  const touchPair = getTouchPair();
8686
8903
  if (touchPair) {
8904
+ cancelLongPressTimer();
8905
+ longPressState = null;
8687
8906
  if (!pinchZoomState) {
8688
8907
  beginPinchZoom(touchPair[0], touchPair[1]);
8689
8908
  }
@@ -8693,6 +8912,21 @@ function createChart(element, options = {}) {
8693
8912
  if (pinchZoomState) {
8694
8913
  return;
8695
8914
  }
8915
+ if (longPressState && longPressState.pointerId === event.pointerId) {
8916
+ longPressState = { ...longPressState, x: point.x, y: point.y };
8917
+ setCrosshairPoint(point);
8918
+ emitCrosshairMove(point.x, point.y, getHitRegion(point.x, point.y) === "plot" ? "plot" : "plot");
8919
+ scheduleDraw({ overlayOnly: true });
8920
+ emitLongPress(point.x, point.y, true);
8921
+ return;
8922
+ }
8923
+ if (longPressTimerId !== null) {
8924
+ const dx = point.x - (pointerDownInfo?.x ?? point.x);
8925
+ const dy = point.y - (pointerDownInfo?.y ?? point.y);
8926
+ if (dx * dx + dy * dy > 36) {
8927
+ cancelLongPressTimer();
8928
+ }
8929
+ }
8696
8930
  }
8697
8931
  if (pointerDownInfo && pointerDownInfo.pointerId === event.pointerId && !pointerDownInfo.moved) {
8698
8932
  const dx = point.x - pointerDownInfo.x;
@@ -8956,6 +9190,18 @@ function createChart(element, options = {}) {
8956
9190
  lastPointerY = point.y;
8957
9191
  };
8958
9192
  const endPointerDrag = (event) => {
9193
+ cancelLongPressTimer();
9194
+ if (longPressState && (!event || event.pointerId === longPressState.pointerId)) {
9195
+ longPressState = null;
9196
+ touchPointers.delete(event?.pointerId ?? -1);
9197
+ setCrosshairPoint(null);
9198
+ isDragging = false;
9199
+ dragMode = null;
9200
+ activePointerId = null;
9201
+ pointerDownInfo = null;
9202
+ scheduleDraw({ overlayOnly: true });
9203
+ return;
9204
+ }
8959
9205
  if (event?.pointerType === "touch") {
8960
9206
  touchPointers.delete(event.pointerId);
8961
9207
  if (pinchZoomState) {
@@ -9772,6 +10018,9 @@ function createChart(element, options = {}) {
9772
10018
  const onKeyboardShortcut = (handler) => {
9773
10019
  keyboardShortcutHandler = handler;
9774
10020
  };
10021
+ const onLongPress = (handler) => {
10022
+ longPressHandler = handler;
10023
+ };
9775
10024
  const focus = () => {
9776
10025
  canvas.focus({ preventScroll: true });
9777
10026
  };
@@ -9825,6 +10074,10 @@ function createChart(element, options = {}) {
9825
10074
  return canvas.toDataURL(options2.type ?? "image/png", options2.quality);
9826
10075
  };
9827
10076
  const destroy = () => {
10077
+ cancelLongPressTimer();
10078
+ datafeedUnsubscribe?.();
10079
+ datafeedUnsubscribe = null;
10080
+ datafeed = null;
9828
10081
  cancelKineticPan();
9829
10082
  if (smoothingRafId !== null) {
9830
10083
  cancelAnimationFrame(smoothingRafId);
@@ -9916,6 +10169,8 @@ function createChart(element, options = {}) {
9916
10169
  addCompareSeries,
9917
10170
  removeCompareSeries,
9918
10171
  getCompareSeries,
10172
+ setDatafeed,
10173
+ onLongPress,
9919
10174
  cancelDrawing,
9920
10175
  setTradeMarkers,
9921
10176
  setSelectedDrawing,