hyperprop-charting-library 0.1.63 → 0.1.65

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.
@@ -1065,6 +1065,10 @@ function createChart(element, options = {}) {
1065
1065
  }
1066
1066
  canvas.style.display = "block";
1067
1067
  canvas.style.touchAction = "none";
1068
+ canvas.style.userSelect = "none";
1069
+ canvas.style.setProperty("-webkit-user-select", "none");
1070
+ canvas.style.setProperty("-webkit-touch-callout", "none");
1071
+ canvas.setAttribute("draggable", "false");
1068
1072
  element.innerHTML = "";
1069
1073
  element.appendChild(canvas);
1070
1074
  const margin = { top: 16, right: 72, bottom: 34, left: 12 };
@@ -3518,10 +3522,15 @@ function createChart(element, options = {}) {
3518
3522
  }
3519
3523
  const midpoint = getMidpoint(first, second);
3520
3524
  const anchorRatio = clamp((midpoint.x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
3525
+ const startYMin = yMinOverride ?? drawState.yMin;
3526
+ const startYMax = yMaxOverride ?? drawState.yMax;
3521
3527
  pinchZoomState = {
3522
3528
  startDistance: Math.max(1, getPointerDistance(first, second)),
3523
3529
  startSpan: xSpan,
3524
- anchorIndex: drawState.xStart + anchorRatio * xSpan
3530
+ anchorIndex: drawState.xStart + anchorRatio * xSpan,
3531
+ startMidpoint: midpoint,
3532
+ startYMin,
3533
+ startYMax
3525
3534
  };
3526
3535
  isDragging = false;
3527
3536
  dragMode = null;
@@ -3550,11 +3559,19 @@ function createChart(element, options = {}) {
3550
3559
  xSpan = nextSpan;
3551
3560
  xCenter = nextStart + nextSpan / 2;
3552
3561
  clampXViewport();
3562
+ const startYRange = pinchZoomState.startYMax - pinchZoomState.startYMin || 1;
3563
+ const priceShift = (midpoint.y - pinchZoomState.startMidpoint.y) / drawState.chartHeight * startYRange;
3564
+ const clampedY = clampYRange(pinchZoomState.startYMin + priceShift, pinchZoomState.startYMax + priceShift);
3565
+ yMinOverride = clampedY.min;
3566
+ yMaxOverride = clampedY.max;
3553
3567
  updateFollowLatest(false);
3554
3568
  emitViewportChange();
3555
3569
  draw();
3556
3570
  };
3557
3571
  const onPointerDown = (event) => {
3572
+ if (event.pointerType === "touch" || event.pointerType === "pen") {
3573
+ event.preventDefault();
3574
+ }
3558
3575
  const point = getCanvasPoint(event);
3559
3576
  if (event.pointerType === "touch") {
3560
3577
  touchPointers.set(event.pointerId, point);
@@ -3653,12 +3670,29 @@ function createChart(element, options = {}) {
3653
3670
  isDragging = true;
3654
3671
  dragMode = region;
3655
3672
  activePointerId = event.pointerId;
3656
- pointerDownInfo = { pointerId: event.pointerId, x: point.x, y: point.y, region, moved: false };
3673
+ const crosshairDrag = (event.pointerType === "touch" || event.pointerType === "pen") && region === "plot";
3674
+ pointerDownInfo = {
3675
+ pointerId: event.pointerId,
3676
+ pointerType: event.pointerType,
3677
+ x: point.x,
3678
+ y: point.y,
3679
+ region,
3680
+ moved: false,
3681
+ crosshairDrag
3682
+ };
3657
3683
  lastPointerX = point.x;
3658
3684
  lastPointerY = point.y;
3659
3685
  canvas.setPointerCapture(event.pointerId);
3686
+ if (crosshairDrag) {
3687
+ canvas.style.cursor = "crosshair";
3688
+ setCrosshairPoint(point);
3689
+ emitCrosshairMove(point.x, point.y, "plot");
3690
+ }
3660
3691
  };
3661
3692
  const onPointerMove = (event) => {
3693
+ if (event.pointerType === "touch" || event.pointerType === "pen") {
3694
+ event.preventDefault();
3695
+ }
3662
3696
  const point = getCanvasPoint(event);
3663
3697
  if (event.pointerType === "touch" && touchPointers.has(event.pointerId)) {
3664
3698
  touchPointers.set(event.pointerId, point);
@@ -3681,6 +3715,16 @@ function createChart(element, options = {}) {
3681
3715
  pointerDownInfo.moved = true;
3682
3716
  }
3683
3717
  }
3718
+ if (pointerDownInfo?.crosshairDrag && pointerDownInfo.pointerId === event.pointerId) {
3719
+ if (getHitRegion(point.x, point.y) === "plot") {
3720
+ canvas.style.cursor = "crosshair";
3721
+ setCrosshairPoint(point);
3722
+ emitCrosshairMove(point.x, point.y, "plot");
3723
+ lastPointerX = point.x;
3724
+ lastPointerY = point.y;
3725
+ }
3726
+ return;
3727
+ }
3684
3728
  if (drawingDragState) {
3685
3729
  if (activePointerId !== null && event.pointerId !== activePointerId) {
3686
3730
  return;
@@ -3894,7 +3938,13 @@ function createChart(element, options = {}) {
3894
3938
  activePointerId = null;
3895
3939
  canvas.style.cursor = "default";
3896
3940
  if (event && pointerDownInfo && event.pointerId === pointerDownInfo.pointerId) {
3897
- if (!pointerDownInfo.moved) {
3941
+ if (pointerDownInfo.crosshairDrag) {
3942
+ const point = getCanvasPoint(event);
3943
+ if (getHitRegion(point.x, point.y) === "plot") {
3944
+ setCrosshairPoint(point);
3945
+ emitCrosshairMove(point.x, point.y, "plot");
3946
+ }
3947
+ } else if (!pointerDownInfo.moved) {
3898
3948
  const clickPrice = pointerDownInfo.region === "plot" ? roundToPricePrecision(priceFromCanvasY(pointerDownInfo.y)) : void 0;
3899
3949
  chartClickHandler?.({
3900
3950
  x: pointerDownInfo.x,
@@ -3941,6 +3991,7 @@ function createChart(element, options = {}) {
3941
3991
  zoomX(factor, point.x);
3942
3992
  };
3943
3993
  const onDoubleClick = (event) => {
3994
+ event.preventDefault();
3944
3995
  if (!doubleClickEnabled) {
3945
3996
  return;
3946
3997
  }
@@ -3966,6 +4017,9 @@ function createChart(element, options = {}) {
3966
4017
  }
3967
4018
  resetViewport();
3968
4019
  };
4020
+ const onContextMenu = (event) => {
4021
+ event.preventDefault();
4022
+ };
3969
4023
  canvas.addEventListener("pointerdown", onPointerDown);
3970
4024
  canvas.addEventListener("pointermove", onPointerMove);
3971
4025
  canvas.addEventListener("pointerup", endPointerDrag);
@@ -3973,6 +4027,7 @@ function createChart(element, options = {}) {
3973
4027
  canvas.addEventListener("pointerleave", endPointerDrag);
3974
4028
  canvas.addEventListener("wheel", onWheel, { passive: false });
3975
4029
  canvas.addEventListener("dblclick", onDoubleClick);
4030
+ canvas.addEventListener("contextmenu", onContextMenu);
3976
4031
  const updateOptions = (nextOptions) => {
3977
4032
  const wasTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
3978
4033
  const previousWidth = width;
@@ -4270,6 +4325,7 @@ function createChart(element, options = {}) {
4270
4325
  canvas.removeEventListener("pointerleave", endPointerDrag);
4271
4326
  canvas.removeEventListener("wheel", onWheel);
4272
4327
  canvas.removeEventListener("dblclick", onDoubleClick);
4328
+ canvas.removeEventListener("contextmenu", onContextMenu);
4273
4329
  element.innerHTML = "";
4274
4330
  };
4275
4331
  draw();
@@ -1041,6 +1041,10 @@ function createChart(element, options = {}) {
1041
1041
  }
1042
1042
  canvas.style.display = "block";
1043
1043
  canvas.style.touchAction = "none";
1044
+ canvas.style.userSelect = "none";
1045
+ canvas.style.setProperty("-webkit-user-select", "none");
1046
+ canvas.style.setProperty("-webkit-touch-callout", "none");
1047
+ canvas.setAttribute("draggable", "false");
1044
1048
  element.innerHTML = "";
1045
1049
  element.appendChild(canvas);
1046
1050
  const margin = { top: 16, right: 72, bottom: 34, left: 12 };
@@ -3494,10 +3498,15 @@ function createChart(element, options = {}) {
3494
3498
  }
3495
3499
  const midpoint = getMidpoint(first, second);
3496
3500
  const anchorRatio = clamp((midpoint.x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
3501
+ const startYMin = yMinOverride ?? drawState.yMin;
3502
+ const startYMax = yMaxOverride ?? drawState.yMax;
3497
3503
  pinchZoomState = {
3498
3504
  startDistance: Math.max(1, getPointerDistance(first, second)),
3499
3505
  startSpan: xSpan,
3500
- anchorIndex: drawState.xStart + anchorRatio * xSpan
3506
+ anchorIndex: drawState.xStart + anchorRatio * xSpan,
3507
+ startMidpoint: midpoint,
3508
+ startYMin,
3509
+ startYMax
3501
3510
  };
3502
3511
  isDragging = false;
3503
3512
  dragMode = null;
@@ -3526,11 +3535,19 @@ function createChart(element, options = {}) {
3526
3535
  xSpan = nextSpan;
3527
3536
  xCenter = nextStart + nextSpan / 2;
3528
3537
  clampXViewport();
3538
+ const startYRange = pinchZoomState.startYMax - pinchZoomState.startYMin || 1;
3539
+ const priceShift = (midpoint.y - pinchZoomState.startMidpoint.y) / drawState.chartHeight * startYRange;
3540
+ const clampedY = clampYRange(pinchZoomState.startYMin + priceShift, pinchZoomState.startYMax + priceShift);
3541
+ yMinOverride = clampedY.min;
3542
+ yMaxOverride = clampedY.max;
3529
3543
  updateFollowLatest(false);
3530
3544
  emitViewportChange();
3531
3545
  draw();
3532
3546
  };
3533
3547
  const onPointerDown = (event) => {
3548
+ if (event.pointerType === "touch" || event.pointerType === "pen") {
3549
+ event.preventDefault();
3550
+ }
3534
3551
  const point = getCanvasPoint(event);
3535
3552
  if (event.pointerType === "touch") {
3536
3553
  touchPointers.set(event.pointerId, point);
@@ -3629,12 +3646,29 @@ function createChart(element, options = {}) {
3629
3646
  isDragging = true;
3630
3647
  dragMode = region;
3631
3648
  activePointerId = event.pointerId;
3632
- pointerDownInfo = { pointerId: event.pointerId, x: point.x, y: point.y, region, moved: false };
3649
+ const crosshairDrag = (event.pointerType === "touch" || event.pointerType === "pen") && region === "plot";
3650
+ pointerDownInfo = {
3651
+ pointerId: event.pointerId,
3652
+ pointerType: event.pointerType,
3653
+ x: point.x,
3654
+ y: point.y,
3655
+ region,
3656
+ moved: false,
3657
+ crosshairDrag
3658
+ };
3633
3659
  lastPointerX = point.x;
3634
3660
  lastPointerY = point.y;
3635
3661
  canvas.setPointerCapture(event.pointerId);
3662
+ if (crosshairDrag) {
3663
+ canvas.style.cursor = "crosshair";
3664
+ setCrosshairPoint(point);
3665
+ emitCrosshairMove(point.x, point.y, "plot");
3666
+ }
3636
3667
  };
3637
3668
  const onPointerMove = (event) => {
3669
+ if (event.pointerType === "touch" || event.pointerType === "pen") {
3670
+ event.preventDefault();
3671
+ }
3638
3672
  const point = getCanvasPoint(event);
3639
3673
  if (event.pointerType === "touch" && touchPointers.has(event.pointerId)) {
3640
3674
  touchPointers.set(event.pointerId, point);
@@ -3657,6 +3691,16 @@ function createChart(element, options = {}) {
3657
3691
  pointerDownInfo.moved = true;
3658
3692
  }
3659
3693
  }
3694
+ if (pointerDownInfo?.crosshairDrag && pointerDownInfo.pointerId === event.pointerId) {
3695
+ if (getHitRegion(point.x, point.y) === "plot") {
3696
+ canvas.style.cursor = "crosshair";
3697
+ setCrosshairPoint(point);
3698
+ emitCrosshairMove(point.x, point.y, "plot");
3699
+ lastPointerX = point.x;
3700
+ lastPointerY = point.y;
3701
+ }
3702
+ return;
3703
+ }
3660
3704
  if (drawingDragState) {
3661
3705
  if (activePointerId !== null && event.pointerId !== activePointerId) {
3662
3706
  return;
@@ -3870,7 +3914,13 @@ function createChart(element, options = {}) {
3870
3914
  activePointerId = null;
3871
3915
  canvas.style.cursor = "default";
3872
3916
  if (event && pointerDownInfo && event.pointerId === pointerDownInfo.pointerId) {
3873
- if (!pointerDownInfo.moved) {
3917
+ if (pointerDownInfo.crosshairDrag) {
3918
+ const point = getCanvasPoint(event);
3919
+ if (getHitRegion(point.x, point.y) === "plot") {
3920
+ setCrosshairPoint(point);
3921
+ emitCrosshairMove(point.x, point.y, "plot");
3922
+ }
3923
+ } else if (!pointerDownInfo.moved) {
3874
3924
  const clickPrice = pointerDownInfo.region === "plot" ? roundToPricePrecision(priceFromCanvasY(pointerDownInfo.y)) : void 0;
3875
3925
  chartClickHandler?.({
3876
3926
  x: pointerDownInfo.x,
@@ -3917,6 +3967,7 @@ function createChart(element, options = {}) {
3917
3967
  zoomX(factor, point.x);
3918
3968
  };
3919
3969
  const onDoubleClick = (event) => {
3970
+ event.preventDefault();
3920
3971
  if (!doubleClickEnabled) {
3921
3972
  return;
3922
3973
  }
@@ -3942,6 +3993,9 @@ function createChart(element, options = {}) {
3942
3993
  }
3943
3994
  resetViewport();
3944
3995
  };
3996
+ const onContextMenu = (event) => {
3997
+ event.preventDefault();
3998
+ };
3945
3999
  canvas.addEventListener("pointerdown", onPointerDown);
3946
4000
  canvas.addEventListener("pointermove", onPointerMove);
3947
4001
  canvas.addEventListener("pointerup", endPointerDrag);
@@ -3949,6 +4003,7 @@ function createChart(element, options = {}) {
3949
4003
  canvas.addEventListener("pointerleave", endPointerDrag);
3950
4004
  canvas.addEventListener("wheel", onWheel, { passive: false });
3951
4005
  canvas.addEventListener("dblclick", onDoubleClick);
4006
+ canvas.addEventListener("contextmenu", onContextMenu);
3952
4007
  const updateOptions = (nextOptions) => {
3953
4008
  const wasTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
3954
4009
  const previousWidth = width;
@@ -4246,6 +4301,7 @@ function createChart(element, options = {}) {
4246
4301
  canvas.removeEventListener("pointerleave", endPointerDrag);
4247
4302
  canvas.removeEventListener("wheel", onWheel);
4248
4303
  canvas.removeEventListener("dblclick", onDoubleClick);
4304
+ canvas.removeEventListener("contextmenu", onContextMenu);
4249
4305
  element.innerHTML = "";
4250
4306
  };
4251
4307
  draw();
package/dist/index.cjs CHANGED
@@ -1065,6 +1065,10 @@ function createChart(element, options = {}) {
1065
1065
  }
1066
1066
  canvas.style.display = "block";
1067
1067
  canvas.style.touchAction = "none";
1068
+ canvas.style.userSelect = "none";
1069
+ canvas.style.setProperty("-webkit-user-select", "none");
1070
+ canvas.style.setProperty("-webkit-touch-callout", "none");
1071
+ canvas.setAttribute("draggable", "false");
1068
1072
  element.innerHTML = "";
1069
1073
  element.appendChild(canvas);
1070
1074
  const margin = { top: 16, right: 72, bottom: 34, left: 12 };
@@ -3518,10 +3522,15 @@ function createChart(element, options = {}) {
3518
3522
  }
3519
3523
  const midpoint = getMidpoint(first, second);
3520
3524
  const anchorRatio = clamp((midpoint.x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
3525
+ const startYMin = yMinOverride ?? drawState.yMin;
3526
+ const startYMax = yMaxOverride ?? drawState.yMax;
3521
3527
  pinchZoomState = {
3522
3528
  startDistance: Math.max(1, getPointerDistance(first, second)),
3523
3529
  startSpan: xSpan,
3524
- anchorIndex: drawState.xStart + anchorRatio * xSpan
3530
+ anchorIndex: drawState.xStart + anchorRatio * xSpan,
3531
+ startMidpoint: midpoint,
3532
+ startYMin,
3533
+ startYMax
3525
3534
  };
3526
3535
  isDragging = false;
3527
3536
  dragMode = null;
@@ -3550,11 +3559,19 @@ function createChart(element, options = {}) {
3550
3559
  xSpan = nextSpan;
3551
3560
  xCenter = nextStart + nextSpan / 2;
3552
3561
  clampXViewport();
3562
+ const startYRange = pinchZoomState.startYMax - pinchZoomState.startYMin || 1;
3563
+ const priceShift = (midpoint.y - pinchZoomState.startMidpoint.y) / drawState.chartHeight * startYRange;
3564
+ const clampedY = clampYRange(pinchZoomState.startYMin + priceShift, pinchZoomState.startYMax + priceShift);
3565
+ yMinOverride = clampedY.min;
3566
+ yMaxOverride = clampedY.max;
3553
3567
  updateFollowLatest(false);
3554
3568
  emitViewportChange();
3555
3569
  draw();
3556
3570
  };
3557
3571
  const onPointerDown = (event) => {
3572
+ if (event.pointerType === "touch" || event.pointerType === "pen") {
3573
+ event.preventDefault();
3574
+ }
3558
3575
  const point = getCanvasPoint(event);
3559
3576
  if (event.pointerType === "touch") {
3560
3577
  touchPointers.set(event.pointerId, point);
@@ -3653,12 +3670,29 @@ function createChart(element, options = {}) {
3653
3670
  isDragging = true;
3654
3671
  dragMode = region;
3655
3672
  activePointerId = event.pointerId;
3656
- pointerDownInfo = { pointerId: event.pointerId, x: point.x, y: point.y, region, moved: false };
3673
+ const crosshairDrag = (event.pointerType === "touch" || event.pointerType === "pen") && region === "plot";
3674
+ pointerDownInfo = {
3675
+ pointerId: event.pointerId,
3676
+ pointerType: event.pointerType,
3677
+ x: point.x,
3678
+ y: point.y,
3679
+ region,
3680
+ moved: false,
3681
+ crosshairDrag
3682
+ };
3657
3683
  lastPointerX = point.x;
3658
3684
  lastPointerY = point.y;
3659
3685
  canvas.setPointerCapture(event.pointerId);
3686
+ if (crosshairDrag) {
3687
+ canvas.style.cursor = "crosshair";
3688
+ setCrosshairPoint(point);
3689
+ emitCrosshairMove(point.x, point.y, "plot");
3690
+ }
3660
3691
  };
3661
3692
  const onPointerMove = (event) => {
3693
+ if (event.pointerType === "touch" || event.pointerType === "pen") {
3694
+ event.preventDefault();
3695
+ }
3662
3696
  const point = getCanvasPoint(event);
3663
3697
  if (event.pointerType === "touch" && touchPointers.has(event.pointerId)) {
3664
3698
  touchPointers.set(event.pointerId, point);
@@ -3681,6 +3715,16 @@ function createChart(element, options = {}) {
3681
3715
  pointerDownInfo.moved = true;
3682
3716
  }
3683
3717
  }
3718
+ if (pointerDownInfo?.crosshairDrag && pointerDownInfo.pointerId === event.pointerId) {
3719
+ if (getHitRegion(point.x, point.y) === "plot") {
3720
+ canvas.style.cursor = "crosshair";
3721
+ setCrosshairPoint(point);
3722
+ emitCrosshairMove(point.x, point.y, "plot");
3723
+ lastPointerX = point.x;
3724
+ lastPointerY = point.y;
3725
+ }
3726
+ return;
3727
+ }
3684
3728
  if (drawingDragState) {
3685
3729
  if (activePointerId !== null && event.pointerId !== activePointerId) {
3686
3730
  return;
@@ -3894,7 +3938,13 @@ function createChart(element, options = {}) {
3894
3938
  activePointerId = null;
3895
3939
  canvas.style.cursor = "default";
3896
3940
  if (event && pointerDownInfo && event.pointerId === pointerDownInfo.pointerId) {
3897
- if (!pointerDownInfo.moved) {
3941
+ if (pointerDownInfo.crosshairDrag) {
3942
+ const point = getCanvasPoint(event);
3943
+ if (getHitRegion(point.x, point.y) === "plot") {
3944
+ setCrosshairPoint(point);
3945
+ emitCrosshairMove(point.x, point.y, "plot");
3946
+ }
3947
+ } else if (!pointerDownInfo.moved) {
3898
3948
  const clickPrice = pointerDownInfo.region === "plot" ? roundToPricePrecision(priceFromCanvasY(pointerDownInfo.y)) : void 0;
3899
3949
  chartClickHandler?.({
3900
3950
  x: pointerDownInfo.x,
@@ -3941,6 +3991,7 @@ function createChart(element, options = {}) {
3941
3991
  zoomX(factor, point.x);
3942
3992
  };
3943
3993
  const onDoubleClick = (event) => {
3994
+ event.preventDefault();
3944
3995
  if (!doubleClickEnabled) {
3945
3996
  return;
3946
3997
  }
@@ -3966,6 +4017,9 @@ function createChart(element, options = {}) {
3966
4017
  }
3967
4018
  resetViewport();
3968
4019
  };
4020
+ const onContextMenu = (event) => {
4021
+ event.preventDefault();
4022
+ };
3969
4023
  canvas.addEventListener("pointerdown", onPointerDown);
3970
4024
  canvas.addEventListener("pointermove", onPointerMove);
3971
4025
  canvas.addEventListener("pointerup", endPointerDrag);
@@ -3973,6 +4027,7 @@ function createChart(element, options = {}) {
3973
4027
  canvas.addEventListener("pointerleave", endPointerDrag);
3974
4028
  canvas.addEventListener("wheel", onWheel, { passive: false });
3975
4029
  canvas.addEventListener("dblclick", onDoubleClick);
4030
+ canvas.addEventListener("contextmenu", onContextMenu);
3976
4031
  const updateOptions = (nextOptions) => {
3977
4032
  const wasTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
3978
4033
  const previousWidth = width;
@@ -4270,6 +4325,7 @@ function createChart(element, options = {}) {
4270
4325
  canvas.removeEventListener("pointerleave", endPointerDrag);
4271
4326
  canvas.removeEventListener("wheel", onWheel);
4272
4327
  canvas.removeEventListener("dblclick", onDoubleClick);
4328
+ canvas.removeEventListener("contextmenu", onContextMenu);
4273
4329
  element.innerHTML = "";
4274
4330
  };
4275
4331
  draw();
package/dist/index.js CHANGED
@@ -1041,6 +1041,10 @@ function createChart(element, options = {}) {
1041
1041
  }
1042
1042
  canvas.style.display = "block";
1043
1043
  canvas.style.touchAction = "none";
1044
+ canvas.style.userSelect = "none";
1045
+ canvas.style.setProperty("-webkit-user-select", "none");
1046
+ canvas.style.setProperty("-webkit-touch-callout", "none");
1047
+ canvas.setAttribute("draggable", "false");
1044
1048
  element.innerHTML = "";
1045
1049
  element.appendChild(canvas);
1046
1050
  const margin = { top: 16, right: 72, bottom: 34, left: 12 };
@@ -3494,10 +3498,15 @@ function createChart(element, options = {}) {
3494
3498
  }
3495
3499
  const midpoint = getMidpoint(first, second);
3496
3500
  const anchorRatio = clamp((midpoint.x - drawState.chartLeft) / drawState.chartWidth, 0, 1);
3501
+ const startYMin = yMinOverride ?? drawState.yMin;
3502
+ const startYMax = yMaxOverride ?? drawState.yMax;
3497
3503
  pinchZoomState = {
3498
3504
  startDistance: Math.max(1, getPointerDistance(first, second)),
3499
3505
  startSpan: xSpan,
3500
- anchorIndex: drawState.xStart + anchorRatio * xSpan
3506
+ anchorIndex: drawState.xStart + anchorRatio * xSpan,
3507
+ startMidpoint: midpoint,
3508
+ startYMin,
3509
+ startYMax
3501
3510
  };
3502
3511
  isDragging = false;
3503
3512
  dragMode = null;
@@ -3526,11 +3535,19 @@ function createChart(element, options = {}) {
3526
3535
  xSpan = nextSpan;
3527
3536
  xCenter = nextStart + nextSpan / 2;
3528
3537
  clampXViewport();
3538
+ const startYRange = pinchZoomState.startYMax - pinchZoomState.startYMin || 1;
3539
+ const priceShift = (midpoint.y - pinchZoomState.startMidpoint.y) / drawState.chartHeight * startYRange;
3540
+ const clampedY = clampYRange(pinchZoomState.startYMin + priceShift, pinchZoomState.startYMax + priceShift);
3541
+ yMinOverride = clampedY.min;
3542
+ yMaxOverride = clampedY.max;
3529
3543
  updateFollowLatest(false);
3530
3544
  emitViewportChange();
3531
3545
  draw();
3532
3546
  };
3533
3547
  const onPointerDown = (event) => {
3548
+ if (event.pointerType === "touch" || event.pointerType === "pen") {
3549
+ event.preventDefault();
3550
+ }
3534
3551
  const point = getCanvasPoint(event);
3535
3552
  if (event.pointerType === "touch") {
3536
3553
  touchPointers.set(event.pointerId, point);
@@ -3629,12 +3646,29 @@ function createChart(element, options = {}) {
3629
3646
  isDragging = true;
3630
3647
  dragMode = region;
3631
3648
  activePointerId = event.pointerId;
3632
- pointerDownInfo = { pointerId: event.pointerId, x: point.x, y: point.y, region, moved: false };
3649
+ const crosshairDrag = (event.pointerType === "touch" || event.pointerType === "pen") && region === "plot";
3650
+ pointerDownInfo = {
3651
+ pointerId: event.pointerId,
3652
+ pointerType: event.pointerType,
3653
+ x: point.x,
3654
+ y: point.y,
3655
+ region,
3656
+ moved: false,
3657
+ crosshairDrag
3658
+ };
3633
3659
  lastPointerX = point.x;
3634
3660
  lastPointerY = point.y;
3635
3661
  canvas.setPointerCapture(event.pointerId);
3662
+ if (crosshairDrag) {
3663
+ canvas.style.cursor = "crosshair";
3664
+ setCrosshairPoint(point);
3665
+ emitCrosshairMove(point.x, point.y, "plot");
3666
+ }
3636
3667
  };
3637
3668
  const onPointerMove = (event) => {
3669
+ if (event.pointerType === "touch" || event.pointerType === "pen") {
3670
+ event.preventDefault();
3671
+ }
3638
3672
  const point = getCanvasPoint(event);
3639
3673
  if (event.pointerType === "touch" && touchPointers.has(event.pointerId)) {
3640
3674
  touchPointers.set(event.pointerId, point);
@@ -3657,6 +3691,16 @@ function createChart(element, options = {}) {
3657
3691
  pointerDownInfo.moved = true;
3658
3692
  }
3659
3693
  }
3694
+ if (pointerDownInfo?.crosshairDrag && pointerDownInfo.pointerId === event.pointerId) {
3695
+ if (getHitRegion(point.x, point.y) === "plot") {
3696
+ canvas.style.cursor = "crosshair";
3697
+ setCrosshairPoint(point);
3698
+ emitCrosshairMove(point.x, point.y, "plot");
3699
+ lastPointerX = point.x;
3700
+ lastPointerY = point.y;
3701
+ }
3702
+ return;
3703
+ }
3660
3704
  if (drawingDragState) {
3661
3705
  if (activePointerId !== null && event.pointerId !== activePointerId) {
3662
3706
  return;
@@ -3870,7 +3914,13 @@ function createChart(element, options = {}) {
3870
3914
  activePointerId = null;
3871
3915
  canvas.style.cursor = "default";
3872
3916
  if (event && pointerDownInfo && event.pointerId === pointerDownInfo.pointerId) {
3873
- if (!pointerDownInfo.moved) {
3917
+ if (pointerDownInfo.crosshairDrag) {
3918
+ const point = getCanvasPoint(event);
3919
+ if (getHitRegion(point.x, point.y) === "plot") {
3920
+ setCrosshairPoint(point);
3921
+ emitCrosshairMove(point.x, point.y, "plot");
3922
+ }
3923
+ } else if (!pointerDownInfo.moved) {
3874
3924
  const clickPrice = pointerDownInfo.region === "plot" ? roundToPricePrecision(priceFromCanvasY(pointerDownInfo.y)) : void 0;
3875
3925
  chartClickHandler?.({
3876
3926
  x: pointerDownInfo.x,
@@ -3917,6 +3967,7 @@ function createChart(element, options = {}) {
3917
3967
  zoomX(factor, point.x);
3918
3968
  };
3919
3969
  const onDoubleClick = (event) => {
3970
+ event.preventDefault();
3920
3971
  if (!doubleClickEnabled) {
3921
3972
  return;
3922
3973
  }
@@ -3942,6 +3993,9 @@ function createChart(element, options = {}) {
3942
3993
  }
3943
3994
  resetViewport();
3944
3995
  };
3996
+ const onContextMenu = (event) => {
3997
+ event.preventDefault();
3998
+ };
3945
3999
  canvas.addEventListener("pointerdown", onPointerDown);
3946
4000
  canvas.addEventListener("pointermove", onPointerMove);
3947
4001
  canvas.addEventListener("pointerup", endPointerDrag);
@@ -3949,6 +4003,7 @@ function createChart(element, options = {}) {
3949
4003
  canvas.addEventListener("pointerleave", endPointerDrag);
3950
4004
  canvas.addEventListener("wheel", onWheel, { passive: false });
3951
4005
  canvas.addEventListener("dblclick", onDoubleClick);
4006
+ canvas.addEventListener("contextmenu", onContextMenu);
3952
4007
  const updateOptions = (nextOptions) => {
3953
4008
  const wasTickerSmoothingEnabled = mergedOptions.tickerLine?.smoothing ?? false;
3954
4009
  const previousWidth = width;
@@ -4246,6 +4301,7 @@ function createChart(element, options = {}) {
4246
4301
  canvas.removeEventListener("pointerleave", endPointerDrag);
4247
4302
  canvas.removeEventListener("wheel", onWheel);
4248
4303
  canvas.removeEventListener("dblclick", onDoubleClick);
4304
+ canvas.removeEventListener("contextmenu", onContextMenu);
4249
4305
  element.innerHTML = "";
4250
4306
  };
4251
4307
  draw();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.63",
3
+ "version": "0.1.65",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",