hyperprop-charting-library 0.1.55 → 0.1.57

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.
@@ -959,6 +959,7 @@ function createChart(element, options = {}) {
959
959
  let drawings = (options.drawings ?? []).map((drawing) => normalizeDrawingState(drawing));
960
960
  let activeDrawingTool = null;
961
961
  let draftDrawing = null;
962
+ let drawingDragState = null;
962
963
  let drawingsChangeHandler = null;
963
964
  let drawingSelectHandler = null;
964
965
  const emitDrawingsChange = () => {
@@ -2043,10 +2044,12 @@ function createChart(element, options = {}) {
2043
2044
  const point = drawing.points[0];
2044
2045
  if (point) {
2045
2046
  const y = clamp(yFromPrice(point.price), chartTop + 1, chartBottom - 1);
2047
+ const handleX = chartRight - 64;
2046
2048
  ctx.beginPath();
2047
2049
  ctx.moveTo(crisp(chartLeft), crisp(y));
2048
2050
  ctx.lineTo(crisp(chartRight), crisp(y));
2049
2051
  ctx.stroke();
2052
+ drawDrawingHandle(handleX, y, drawing.color);
2050
2053
  }
2051
2054
  } else if (drawing.type === "trendline") {
2052
2055
  const first = drawing.points[0];
@@ -3091,6 +3094,15 @@ function createChart(element, options = {}) {
3091
3094
  ...time ? { time: time.toISOString() } : {}
3092
3095
  };
3093
3096
  };
3097
+ const normalizeDrawingPoint = (index, price) => {
3098
+ const roundedIndex = Math.round(index);
3099
+ const time = getTimeForIndex(roundedIndex);
3100
+ return {
3101
+ index,
3102
+ price: roundToPricePrecision(price),
3103
+ ...time ? { time: time.toISOString() } : {}
3104
+ };
3105
+ };
3094
3106
  const canvasXFromDrawingPoint = (point) => {
3095
3107
  if (!drawState) return 0;
3096
3108
  return drawState.chartLeft + (point.index + 0.5 - drawState.xStart) / drawState.xSpan * drawState.chartWidth;
@@ -3116,6 +3128,10 @@ function createChart(element, options = {}) {
3116
3128
  const point = drawing.points[0];
3117
3129
  if (!point) continue;
3118
3130
  const lineY = canvasYFromDrawingPrice(point.price);
3131
+ const handleX = drawState ? drawState.chartRight - 64 : 0;
3132
+ if (Math.hypot(x - handleX, y - lineY) <= 8) {
3133
+ return { drawing, target: "handle", pointIndex: 0 };
3134
+ }
3119
3135
  if (Math.abs(y - lineY) <= 7) {
3120
3136
  return { drawing, target: "line" };
3121
3137
  }
@@ -3127,8 +3143,11 @@ function createChart(element, options = {}) {
3127
3143
  const y1 = canvasYFromDrawingPrice(first.price);
3128
3144
  const x2 = canvasXFromDrawingPoint(second);
3129
3145
  const y2 = canvasYFromDrawingPrice(second.price);
3130
- if (Math.hypot(x - x1, y - y1) <= 8 || Math.hypot(x - x2, y - y2) <= 8) {
3131
- return { drawing, target: "handle" };
3146
+ if (Math.hypot(x - x1, y - y1) <= 8) {
3147
+ return { drawing, target: "handle", pointIndex: 0 };
3148
+ }
3149
+ if (Math.hypot(x - x2, y - y2) <= 8) {
3150
+ return { drawing, target: "handle", pointIndex: 1 };
3132
3151
  }
3133
3152
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3134
3153
  return { drawing, target: "line" };
@@ -3236,6 +3255,39 @@ function createChart(element, options = {}) {
3236
3255
  }
3237
3256
  return false;
3238
3257
  };
3258
+ const updateDrawingDrag = (x, y) => {
3259
+ if (!drawingDragState) {
3260
+ return false;
3261
+ }
3262
+ const currentPoint = drawingPointFromCanvas(x, y);
3263
+ if (!currentPoint) {
3264
+ return true;
3265
+ }
3266
+ const deltaIndex = currentPoint.index - drawingDragState.startCanvasPoint.index;
3267
+ const deltaPrice = currentPoint.price - drawingDragState.startCanvasPoint.price;
3268
+ drawings = drawings.map((drawing) => {
3269
+ if (drawing.id !== drawingDragState?.drawingId || drawing.locked) {
3270
+ return drawing;
3271
+ }
3272
+ if (drawingDragState.target === "handle") {
3273
+ return {
3274
+ ...drawing,
3275
+ points: drawing.points.map(
3276
+ (point, index) => index === (drawingDragState?.pointIndex ?? 0) ? normalizeDrawingPoint(currentPoint.index, currentPoint.price) : point
3277
+ )
3278
+ };
3279
+ }
3280
+ return {
3281
+ ...drawing,
3282
+ points: drawingDragState.startPoints.map(
3283
+ (point) => normalizeDrawingPoint(point.index + deltaIndex, point.price + deltaPrice)
3284
+ )
3285
+ };
3286
+ });
3287
+ emitDrawingsChange();
3288
+ draw();
3289
+ return true;
3290
+ };
3239
3291
  let isDragging = false;
3240
3292
  let dragMode = null;
3241
3293
  let lastPointerX = 0;
@@ -3275,6 +3327,7 @@ function createChart(element, options = {}) {
3275
3327
  pointerDownInfo = null;
3276
3328
  orderDragState = null;
3277
3329
  actionDragState = null;
3330
+ drawingDragState = null;
3278
3331
  canvas.style.cursor = "default";
3279
3332
  setCrosshairPoint(null);
3280
3333
  };
@@ -3367,11 +3420,26 @@ function createChart(element, options = {}) {
3367
3420
  drawingSelectHandler?.({
3368
3421
  drawing: serializeDrawing(drawingHit.drawing),
3369
3422
  target: drawingHit.target,
3423
+ ...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
3370
3424
  x: point.x,
3371
3425
  y: point.y
3372
3426
  });
3427
+ if (!drawingHit.drawing.locked) {
3428
+ const startCanvasPoint = drawingPointFromCanvas(point.x, point.y);
3429
+ if (startCanvasPoint) {
3430
+ drawingDragState = {
3431
+ drawingId: drawingHit.drawing.id,
3432
+ target: drawingHit.target,
3433
+ ...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
3434
+ startCanvasPoint,
3435
+ startPoints: drawingHit.drawing.points.map((drawingPoint) => ({ ...drawingPoint }))
3436
+ };
3437
+ activePointerId = event.pointerId;
3438
+ canvas.setPointerCapture(event.pointerId);
3439
+ }
3440
+ }
3373
3441
  setCrosshairPoint(null);
3374
- canvas.style.cursor = "pointer";
3442
+ canvas.style.cursor = drawingHit.drawing.locked ? "pointer" : drawingHit.target === "handle" ? "grab" : "move";
3375
3443
  return;
3376
3444
  }
3377
3445
  }
@@ -3411,6 +3479,15 @@ function createChart(element, options = {}) {
3411
3479
  pointerDownInfo.moved = true;
3412
3480
  }
3413
3481
  }
3482
+ if (drawingDragState) {
3483
+ if (activePointerId !== null && event.pointerId !== activePointerId) {
3484
+ return;
3485
+ }
3486
+ updateDrawingDrag(point.x, point.y);
3487
+ canvas.style.cursor = drawingDragState.target === "handle" ? "grabbing" : "move";
3488
+ setCrosshairPoint(null);
3489
+ return;
3490
+ }
3414
3491
  if (draftDrawing && activeDrawingTool === "trendline") {
3415
3492
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3416
3493
  if (nextPoint) {
@@ -3494,7 +3571,8 @@ function createChart(element, options = {}) {
3494
3571
  return;
3495
3572
  }
3496
3573
  if (!activeDrawingTool && getDrawingHit(point.x, point.y)) {
3497
- canvas.style.cursor = "pointer";
3574
+ const drawingHit = getDrawingHit(point.x, point.y);
3575
+ canvas.style.cursor = drawingHit?.drawing.locked ? "pointer" : drawingHit?.target === "handle" ? "grab" : "move";
3498
3576
  setCrosshairPoint(null);
3499
3577
  return;
3500
3578
  }
@@ -3590,6 +3668,10 @@ function createChart(element, options = {}) {
3590
3668
  }
3591
3669
  actionDragState = null;
3592
3670
  }
3671
+ if (drawingDragState) {
3672
+ drawingDragState = null;
3673
+ emitDrawingsChange();
3674
+ }
3593
3675
  isDragging = false;
3594
3676
  dragMode = null;
3595
3677
  activePointerId = null;
@@ -63,6 +63,7 @@ interface DrawingObjectOptions {
63
63
  interface DrawingSelectEvent {
64
64
  drawing: DrawingObjectOptions;
65
65
  target: "line" | "handle";
66
+ pointIndex?: number;
66
67
  x: number;
67
68
  y: number;
68
69
  }
@@ -935,6 +935,7 @@ function createChart(element, options = {}) {
935
935
  let drawings = (options.drawings ?? []).map((drawing) => normalizeDrawingState(drawing));
936
936
  let activeDrawingTool = null;
937
937
  let draftDrawing = null;
938
+ let drawingDragState = null;
938
939
  let drawingsChangeHandler = null;
939
940
  let drawingSelectHandler = null;
940
941
  const emitDrawingsChange = () => {
@@ -2019,10 +2020,12 @@ function createChart(element, options = {}) {
2019
2020
  const point = drawing.points[0];
2020
2021
  if (point) {
2021
2022
  const y = clamp(yFromPrice(point.price), chartTop + 1, chartBottom - 1);
2023
+ const handleX = chartRight - 64;
2022
2024
  ctx.beginPath();
2023
2025
  ctx.moveTo(crisp(chartLeft), crisp(y));
2024
2026
  ctx.lineTo(crisp(chartRight), crisp(y));
2025
2027
  ctx.stroke();
2028
+ drawDrawingHandle(handleX, y, drawing.color);
2026
2029
  }
2027
2030
  } else if (drawing.type === "trendline") {
2028
2031
  const first = drawing.points[0];
@@ -3067,6 +3070,15 @@ function createChart(element, options = {}) {
3067
3070
  ...time ? { time: time.toISOString() } : {}
3068
3071
  };
3069
3072
  };
3073
+ const normalizeDrawingPoint = (index, price) => {
3074
+ const roundedIndex = Math.round(index);
3075
+ const time = getTimeForIndex(roundedIndex);
3076
+ return {
3077
+ index,
3078
+ price: roundToPricePrecision(price),
3079
+ ...time ? { time: time.toISOString() } : {}
3080
+ };
3081
+ };
3070
3082
  const canvasXFromDrawingPoint = (point) => {
3071
3083
  if (!drawState) return 0;
3072
3084
  return drawState.chartLeft + (point.index + 0.5 - drawState.xStart) / drawState.xSpan * drawState.chartWidth;
@@ -3092,6 +3104,10 @@ function createChart(element, options = {}) {
3092
3104
  const point = drawing.points[0];
3093
3105
  if (!point) continue;
3094
3106
  const lineY = canvasYFromDrawingPrice(point.price);
3107
+ const handleX = drawState ? drawState.chartRight - 64 : 0;
3108
+ if (Math.hypot(x - handleX, y - lineY) <= 8) {
3109
+ return { drawing, target: "handle", pointIndex: 0 };
3110
+ }
3095
3111
  if (Math.abs(y - lineY) <= 7) {
3096
3112
  return { drawing, target: "line" };
3097
3113
  }
@@ -3103,8 +3119,11 @@ function createChart(element, options = {}) {
3103
3119
  const y1 = canvasYFromDrawingPrice(first.price);
3104
3120
  const x2 = canvasXFromDrawingPoint(second);
3105
3121
  const y2 = canvasYFromDrawingPrice(second.price);
3106
- if (Math.hypot(x - x1, y - y1) <= 8 || Math.hypot(x - x2, y - y2) <= 8) {
3107
- return { drawing, target: "handle" };
3122
+ if (Math.hypot(x - x1, y - y1) <= 8) {
3123
+ return { drawing, target: "handle", pointIndex: 0 };
3124
+ }
3125
+ if (Math.hypot(x - x2, y - y2) <= 8) {
3126
+ return { drawing, target: "handle", pointIndex: 1 };
3108
3127
  }
3109
3128
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3110
3129
  return { drawing, target: "line" };
@@ -3212,6 +3231,39 @@ function createChart(element, options = {}) {
3212
3231
  }
3213
3232
  return false;
3214
3233
  };
3234
+ const updateDrawingDrag = (x, y) => {
3235
+ if (!drawingDragState) {
3236
+ return false;
3237
+ }
3238
+ const currentPoint = drawingPointFromCanvas(x, y);
3239
+ if (!currentPoint) {
3240
+ return true;
3241
+ }
3242
+ const deltaIndex = currentPoint.index - drawingDragState.startCanvasPoint.index;
3243
+ const deltaPrice = currentPoint.price - drawingDragState.startCanvasPoint.price;
3244
+ drawings = drawings.map((drawing) => {
3245
+ if (drawing.id !== drawingDragState?.drawingId || drawing.locked) {
3246
+ return drawing;
3247
+ }
3248
+ if (drawingDragState.target === "handle") {
3249
+ return {
3250
+ ...drawing,
3251
+ points: drawing.points.map(
3252
+ (point, index) => index === (drawingDragState?.pointIndex ?? 0) ? normalizeDrawingPoint(currentPoint.index, currentPoint.price) : point
3253
+ )
3254
+ };
3255
+ }
3256
+ return {
3257
+ ...drawing,
3258
+ points: drawingDragState.startPoints.map(
3259
+ (point) => normalizeDrawingPoint(point.index + deltaIndex, point.price + deltaPrice)
3260
+ )
3261
+ };
3262
+ });
3263
+ emitDrawingsChange();
3264
+ draw();
3265
+ return true;
3266
+ };
3215
3267
  let isDragging = false;
3216
3268
  let dragMode = null;
3217
3269
  let lastPointerX = 0;
@@ -3251,6 +3303,7 @@ function createChart(element, options = {}) {
3251
3303
  pointerDownInfo = null;
3252
3304
  orderDragState = null;
3253
3305
  actionDragState = null;
3306
+ drawingDragState = null;
3254
3307
  canvas.style.cursor = "default";
3255
3308
  setCrosshairPoint(null);
3256
3309
  };
@@ -3343,11 +3396,26 @@ function createChart(element, options = {}) {
3343
3396
  drawingSelectHandler?.({
3344
3397
  drawing: serializeDrawing(drawingHit.drawing),
3345
3398
  target: drawingHit.target,
3399
+ ...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
3346
3400
  x: point.x,
3347
3401
  y: point.y
3348
3402
  });
3403
+ if (!drawingHit.drawing.locked) {
3404
+ const startCanvasPoint = drawingPointFromCanvas(point.x, point.y);
3405
+ if (startCanvasPoint) {
3406
+ drawingDragState = {
3407
+ drawingId: drawingHit.drawing.id,
3408
+ target: drawingHit.target,
3409
+ ...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
3410
+ startCanvasPoint,
3411
+ startPoints: drawingHit.drawing.points.map((drawingPoint) => ({ ...drawingPoint }))
3412
+ };
3413
+ activePointerId = event.pointerId;
3414
+ canvas.setPointerCapture(event.pointerId);
3415
+ }
3416
+ }
3349
3417
  setCrosshairPoint(null);
3350
- canvas.style.cursor = "pointer";
3418
+ canvas.style.cursor = drawingHit.drawing.locked ? "pointer" : drawingHit.target === "handle" ? "grab" : "move";
3351
3419
  return;
3352
3420
  }
3353
3421
  }
@@ -3387,6 +3455,15 @@ function createChart(element, options = {}) {
3387
3455
  pointerDownInfo.moved = true;
3388
3456
  }
3389
3457
  }
3458
+ if (drawingDragState) {
3459
+ if (activePointerId !== null && event.pointerId !== activePointerId) {
3460
+ return;
3461
+ }
3462
+ updateDrawingDrag(point.x, point.y);
3463
+ canvas.style.cursor = drawingDragState.target === "handle" ? "grabbing" : "move";
3464
+ setCrosshairPoint(null);
3465
+ return;
3466
+ }
3390
3467
  if (draftDrawing && activeDrawingTool === "trendline") {
3391
3468
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3392
3469
  if (nextPoint) {
@@ -3470,7 +3547,8 @@ function createChart(element, options = {}) {
3470
3547
  return;
3471
3548
  }
3472
3549
  if (!activeDrawingTool && getDrawingHit(point.x, point.y)) {
3473
- canvas.style.cursor = "pointer";
3550
+ const drawingHit = getDrawingHit(point.x, point.y);
3551
+ canvas.style.cursor = drawingHit?.drawing.locked ? "pointer" : drawingHit?.target === "handle" ? "grab" : "move";
3474
3552
  setCrosshairPoint(null);
3475
3553
  return;
3476
3554
  }
@@ -3566,6 +3644,10 @@ function createChart(element, options = {}) {
3566
3644
  }
3567
3645
  actionDragState = null;
3568
3646
  }
3647
+ if (drawingDragState) {
3648
+ drawingDragState = null;
3649
+ emitDrawingsChange();
3650
+ }
3569
3651
  isDragging = false;
3570
3652
  dragMode = null;
3571
3653
  activePointerId = null;
package/dist/index.cjs CHANGED
@@ -959,6 +959,7 @@ function createChart(element, options = {}) {
959
959
  let drawings = (options.drawings ?? []).map((drawing) => normalizeDrawingState(drawing));
960
960
  let activeDrawingTool = null;
961
961
  let draftDrawing = null;
962
+ let drawingDragState = null;
962
963
  let drawingsChangeHandler = null;
963
964
  let drawingSelectHandler = null;
964
965
  const emitDrawingsChange = () => {
@@ -2043,10 +2044,12 @@ function createChart(element, options = {}) {
2043
2044
  const point = drawing.points[0];
2044
2045
  if (point) {
2045
2046
  const y = clamp(yFromPrice(point.price), chartTop + 1, chartBottom - 1);
2047
+ const handleX = chartRight - 64;
2046
2048
  ctx.beginPath();
2047
2049
  ctx.moveTo(crisp(chartLeft), crisp(y));
2048
2050
  ctx.lineTo(crisp(chartRight), crisp(y));
2049
2051
  ctx.stroke();
2052
+ drawDrawingHandle(handleX, y, drawing.color);
2050
2053
  }
2051
2054
  } else if (drawing.type === "trendline") {
2052
2055
  const first = drawing.points[0];
@@ -3091,6 +3094,15 @@ function createChart(element, options = {}) {
3091
3094
  ...time ? { time: time.toISOString() } : {}
3092
3095
  };
3093
3096
  };
3097
+ const normalizeDrawingPoint = (index, price) => {
3098
+ const roundedIndex = Math.round(index);
3099
+ const time = getTimeForIndex(roundedIndex);
3100
+ return {
3101
+ index,
3102
+ price: roundToPricePrecision(price),
3103
+ ...time ? { time: time.toISOString() } : {}
3104
+ };
3105
+ };
3094
3106
  const canvasXFromDrawingPoint = (point) => {
3095
3107
  if (!drawState) return 0;
3096
3108
  return drawState.chartLeft + (point.index + 0.5 - drawState.xStart) / drawState.xSpan * drawState.chartWidth;
@@ -3116,6 +3128,10 @@ function createChart(element, options = {}) {
3116
3128
  const point = drawing.points[0];
3117
3129
  if (!point) continue;
3118
3130
  const lineY = canvasYFromDrawingPrice(point.price);
3131
+ const handleX = drawState ? drawState.chartRight - 64 : 0;
3132
+ if (Math.hypot(x - handleX, y - lineY) <= 8) {
3133
+ return { drawing, target: "handle", pointIndex: 0 };
3134
+ }
3119
3135
  if (Math.abs(y - lineY) <= 7) {
3120
3136
  return { drawing, target: "line" };
3121
3137
  }
@@ -3127,8 +3143,11 @@ function createChart(element, options = {}) {
3127
3143
  const y1 = canvasYFromDrawingPrice(first.price);
3128
3144
  const x2 = canvasXFromDrawingPoint(second);
3129
3145
  const y2 = canvasYFromDrawingPrice(second.price);
3130
- if (Math.hypot(x - x1, y - y1) <= 8 || Math.hypot(x - x2, y - y2) <= 8) {
3131
- return { drawing, target: "handle" };
3146
+ if (Math.hypot(x - x1, y - y1) <= 8) {
3147
+ return { drawing, target: "handle", pointIndex: 0 };
3148
+ }
3149
+ if (Math.hypot(x - x2, y - y2) <= 8) {
3150
+ return { drawing, target: "handle", pointIndex: 1 };
3132
3151
  }
3133
3152
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3134
3153
  return { drawing, target: "line" };
@@ -3236,6 +3255,39 @@ function createChart(element, options = {}) {
3236
3255
  }
3237
3256
  return false;
3238
3257
  };
3258
+ const updateDrawingDrag = (x, y) => {
3259
+ if (!drawingDragState) {
3260
+ return false;
3261
+ }
3262
+ const currentPoint = drawingPointFromCanvas(x, y);
3263
+ if (!currentPoint) {
3264
+ return true;
3265
+ }
3266
+ const deltaIndex = currentPoint.index - drawingDragState.startCanvasPoint.index;
3267
+ const deltaPrice = currentPoint.price - drawingDragState.startCanvasPoint.price;
3268
+ drawings = drawings.map((drawing) => {
3269
+ if (drawing.id !== drawingDragState?.drawingId || drawing.locked) {
3270
+ return drawing;
3271
+ }
3272
+ if (drawingDragState.target === "handle") {
3273
+ return {
3274
+ ...drawing,
3275
+ points: drawing.points.map(
3276
+ (point, index) => index === (drawingDragState?.pointIndex ?? 0) ? normalizeDrawingPoint(currentPoint.index, currentPoint.price) : point
3277
+ )
3278
+ };
3279
+ }
3280
+ return {
3281
+ ...drawing,
3282
+ points: drawingDragState.startPoints.map(
3283
+ (point) => normalizeDrawingPoint(point.index + deltaIndex, point.price + deltaPrice)
3284
+ )
3285
+ };
3286
+ });
3287
+ emitDrawingsChange();
3288
+ draw();
3289
+ return true;
3290
+ };
3239
3291
  let isDragging = false;
3240
3292
  let dragMode = null;
3241
3293
  let lastPointerX = 0;
@@ -3275,6 +3327,7 @@ function createChart(element, options = {}) {
3275
3327
  pointerDownInfo = null;
3276
3328
  orderDragState = null;
3277
3329
  actionDragState = null;
3330
+ drawingDragState = null;
3278
3331
  canvas.style.cursor = "default";
3279
3332
  setCrosshairPoint(null);
3280
3333
  };
@@ -3367,11 +3420,26 @@ function createChart(element, options = {}) {
3367
3420
  drawingSelectHandler?.({
3368
3421
  drawing: serializeDrawing(drawingHit.drawing),
3369
3422
  target: drawingHit.target,
3423
+ ...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
3370
3424
  x: point.x,
3371
3425
  y: point.y
3372
3426
  });
3427
+ if (!drawingHit.drawing.locked) {
3428
+ const startCanvasPoint = drawingPointFromCanvas(point.x, point.y);
3429
+ if (startCanvasPoint) {
3430
+ drawingDragState = {
3431
+ drawingId: drawingHit.drawing.id,
3432
+ target: drawingHit.target,
3433
+ ...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
3434
+ startCanvasPoint,
3435
+ startPoints: drawingHit.drawing.points.map((drawingPoint) => ({ ...drawingPoint }))
3436
+ };
3437
+ activePointerId = event.pointerId;
3438
+ canvas.setPointerCapture(event.pointerId);
3439
+ }
3440
+ }
3373
3441
  setCrosshairPoint(null);
3374
- canvas.style.cursor = "pointer";
3442
+ canvas.style.cursor = drawingHit.drawing.locked ? "pointer" : drawingHit.target === "handle" ? "grab" : "move";
3375
3443
  return;
3376
3444
  }
3377
3445
  }
@@ -3411,6 +3479,15 @@ function createChart(element, options = {}) {
3411
3479
  pointerDownInfo.moved = true;
3412
3480
  }
3413
3481
  }
3482
+ if (drawingDragState) {
3483
+ if (activePointerId !== null && event.pointerId !== activePointerId) {
3484
+ return;
3485
+ }
3486
+ updateDrawingDrag(point.x, point.y);
3487
+ canvas.style.cursor = drawingDragState.target === "handle" ? "grabbing" : "move";
3488
+ setCrosshairPoint(null);
3489
+ return;
3490
+ }
3414
3491
  if (draftDrawing && activeDrawingTool === "trendline") {
3415
3492
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3416
3493
  if (nextPoint) {
@@ -3494,7 +3571,8 @@ function createChart(element, options = {}) {
3494
3571
  return;
3495
3572
  }
3496
3573
  if (!activeDrawingTool && getDrawingHit(point.x, point.y)) {
3497
- canvas.style.cursor = "pointer";
3574
+ const drawingHit = getDrawingHit(point.x, point.y);
3575
+ canvas.style.cursor = drawingHit?.drawing.locked ? "pointer" : drawingHit?.target === "handle" ? "grab" : "move";
3498
3576
  setCrosshairPoint(null);
3499
3577
  return;
3500
3578
  }
@@ -3590,6 +3668,10 @@ function createChart(element, options = {}) {
3590
3668
  }
3591
3669
  actionDragState = null;
3592
3670
  }
3671
+ if (drawingDragState) {
3672
+ drawingDragState = null;
3673
+ emitDrawingsChange();
3674
+ }
3593
3675
  isDragging = false;
3594
3676
  dragMode = null;
3595
3677
  activePointerId = null;
package/dist/index.d.cts CHANGED
@@ -63,6 +63,7 @@ interface DrawingObjectOptions {
63
63
  interface DrawingSelectEvent {
64
64
  drawing: DrawingObjectOptions;
65
65
  target: "line" | "handle";
66
+ pointIndex?: number;
66
67
  x: number;
67
68
  y: number;
68
69
  }
package/dist/index.d.ts CHANGED
@@ -63,6 +63,7 @@ interface DrawingObjectOptions {
63
63
  interface DrawingSelectEvent {
64
64
  drawing: DrawingObjectOptions;
65
65
  target: "line" | "handle";
66
+ pointIndex?: number;
66
67
  x: number;
67
68
  y: number;
68
69
  }
package/dist/index.js CHANGED
@@ -935,6 +935,7 @@ function createChart(element, options = {}) {
935
935
  let drawings = (options.drawings ?? []).map((drawing) => normalizeDrawingState(drawing));
936
936
  let activeDrawingTool = null;
937
937
  let draftDrawing = null;
938
+ let drawingDragState = null;
938
939
  let drawingsChangeHandler = null;
939
940
  let drawingSelectHandler = null;
940
941
  const emitDrawingsChange = () => {
@@ -2019,10 +2020,12 @@ function createChart(element, options = {}) {
2019
2020
  const point = drawing.points[0];
2020
2021
  if (point) {
2021
2022
  const y = clamp(yFromPrice(point.price), chartTop + 1, chartBottom - 1);
2023
+ const handleX = chartRight - 64;
2022
2024
  ctx.beginPath();
2023
2025
  ctx.moveTo(crisp(chartLeft), crisp(y));
2024
2026
  ctx.lineTo(crisp(chartRight), crisp(y));
2025
2027
  ctx.stroke();
2028
+ drawDrawingHandle(handleX, y, drawing.color);
2026
2029
  }
2027
2030
  } else if (drawing.type === "trendline") {
2028
2031
  const first = drawing.points[0];
@@ -3067,6 +3070,15 @@ function createChart(element, options = {}) {
3067
3070
  ...time ? { time: time.toISOString() } : {}
3068
3071
  };
3069
3072
  };
3073
+ const normalizeDrawingPoint = (index, price) => {
3074
+ const roundedIndex = Math.round(index);
3075
+ const time = getTimeForIndex(roundedIndex);
3076
+ return {
3077
+ index,
3078
+ price: roundToPricePrecision(price),
3079
+ ...time ? { time: time.toISOString() } : {}
3080
+ };
3081
+ };
3070
3082
  const canvasXFromDrawingPoint = (point) => {
3071
3083
  if (!drawState) return 0;
3072
3084
  return drawState.chartLeft + (point.index + 0.5 - drawState.xStart) / drawState.xSpan * drawState.chartWidth;
@@ -3092,6 +3104,10 @@ function createChart(element, options = {}) {
3092
3104
  const point = drawing.points[0];
3093
3105
  if (!point) continue;
3094
3106
  const lineY = canvasYFromDrawingPrice(point.price);
3107
+ const handleX = drawState ? drawState.chartRight - 64 : 0;
3108
+ if (Math.hypot(x - handleX, y - lineY) <= 8) {
3109
+ return { drawing, target: "handle", pointIndex: 0 };
3110
+ }
3095
3111
  if (Math.abs(y - lineY) <= 7) {
3096
3112
  return { drawing, target: "line" };
3097
3113
  }
@@ -3103,8 +3119,11 @@ function createChart(element, options = {}) {
3103
3119
  const y1 = canvasYFromDrawingPrice(first.price);
3104
3120
  const x2 = canvasXFromDrawingPoint(second);
3105
3121
  const y2 = canvasYFromDrawingPrice(second.price);
3106
- if (Math.hypot(x - x1, y - y1) <= 8 || Math.hypot(x - x2, y - y2) <= 8) {
3107
- return { drawing, target: "handle" };
3122
+ if (Math.hypot(x - x1, y - y1) <= 8) {
3123
+ return { drawing, target: "handle", pointIndex: 0 };
3124
+ }
3125
+ if (Math.hypot(x - x2, y - y2) <= 8) {
3126
+ return { drawing, target: "handle", pointIndex: 1 };
3108
3127
  }
3109
3128
  if (distanceToSegment(x, y, x1, y1, x2, y2) <= 6) {
3110
3129
  return { drawing, target: "line" };
@@ -3212,6 +3231,39 @@ function createChart(element, options = {}) {
3212
3231
  }
3213
3232
  return false;
3214
3233
  };
3234
+ const updateDrawingDrag = (x, y) => {
3235
+ if (!drawingDragState) {
3236
+ return false;
3237
+ }
3238
+ const currentPoint = drawingPointFromCanvas(x, y);
3239
+ if (!currentPoint) {
3240
+ return true;
3241
+ }
3242
+ const deltaIndex = currentPoint.index - drawingDragState.startCanvasPoint.index;
3243
+ const deltaPrice = currentPoint.price - drawingDragState.startCanvasPoint.price;
3244
+ drawings = drawings.map((drawing) => {
3245
+ if (drawing.id !== drawingDragState?.drawingId || drawing.locked) {
3246
+ return drawing;
3247
+ }
3248
+ if (drawingDragState.target === "handle") {
3249
+ return {
3250
+ ...drawing,
3251
+ points: drawing.points.map(
3252
+ (point, index) => index === (drawingDragState?.pointIndex ?? 0) ? normalizeDrawingPoint(currentPoint.index, currentPoint.price) : point
3253
+ )
3254
+ };
3255
+ }
3256
+ return {
3257
+ ...drawing,
3258
+ points: drawingDragState.startPoints.map(
3259
+ (point) => normalizeDrawingPoint(point.index + deltaIndex, point.price + deltaPrice)
3260
+ )
3261
+ };
3262
+ });
3263
+ emitDrawingsChange();
3264
+ draw();
3265
+ return true;
3266
+ };
3215
3267
  let isDragging = false;
3216
3268
  let dragMode = null;
3217
3269
  let lastPointerX = 0;
@@ -3251,6 +3303,7 @@ function createChart(element, options = {}) {
3251
3303
  pointerDownInfo = null;
3252
3304
  orderDragState = null;
3253
3305
  actionDragState = null;
3306
+ drawingDragState = null;
3254
3307
  canvas.style.cursor = "default";
3255
3308
  setCrosshairPoint(null);
3256
3309
  };
@@ -3343,11 +3396,26 @@ function createChart(element, options = {}) {
3343
3396
  drawingSelectHandler?.({
3344
3397
  drawing: serializeDrawing(drawingHit.drawing),
3345
3398
  target: drawingHit.target,
3399
+ ...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
3346
3400
  x: point.x,
3347
3401
  y: point.y
3348
3402
  });
3403
+ if (!drawingHit.drawing.locked) {
3404
+ const startCanvasPoint = drawingPointFromCanvas(point.x, point.y);
3405
+ if (startCanvasPoint) {
3406
+ drawingDragState = {
3407
+ drawingId: drawingHit.drawing.id,
3408
+ target: drawingHit.target,
3409
+ ...drawingHit.pointIndex === void 0 ? {} : { pointIndex: drawingHit.pointIndex },
3410
+ startCanvasPoint,
3411
+ startPoints: drawingHit.drawing.points.map((drawingPoint) => ({ ...drawingPoint }))
3412
+ };
3413
+ activePointerId = event.pointerId;
3414
+ canvas.setPointerCapture(event.pointerId);
3415
+ }
3416
+ }
3349
3417
  setCrosshairPoint(null);
3350
- canvas.style.cursor = "pointer";
3418
+ canvas.style.cursor = drawingHit.drawing.locked ? "pointer" : drawingHit.target === "handle" ? "grab" : "move";
3351
3419
  return;
3352
3420
  }
3353
3421
  }
@@ -3387,6 +3455,15 @@ function createChart(element, options = {}) {
3387
3455
  pointerDownInfo.moved = true;
3388
3456
  }
3389
3457
  }
3458
+ if (drawingDragState) {
3459
+ if (activePointerId !== null && event.pointerId !== activePointerId) {
3460
+ return;
3461
+ }
3462
+ updateDrawingDrag(point.x, point.y);
3463
+ canvas.style.cursor = drawingDragState.target === "handle" ? "grabbing" : "move";
3464
+ setCrosshairPoint(null);
3465
+ return;
3466
+ }
3390
3467
  if (draftDrawing && activeDrawingTool === "trendline") {
3391
3468
  const nextPoint = drawingPointFromCanvas(point.x, point.y);
3392
3469
  if (nextPoint) {
@@ -3470,7 +3547,8 @@ function createChart(element, options = {}) {
3470
3547
  return;
3471
3548
  }
3472
3549
  if (!activeDrawingTool && getDrawingHit(point.x, point.y)) {
3473
- canvas.style.cursor = "pointer";
3550
+ const drawingHit = getDrawingHit(point.x, point.y);
3551
+ canvas.style.cursor = drawingHit?.drawing.locked ? "pointer" : drawingHit?.target === "handle" ? "grab" : "move";
3474
3552
  setCrosshairPoint(null);
3475
3553
  return;
3476
3554
  }
@@ -3566,6 +3644,10 @@ function createChart(element, options = {}) {
3566
3644
  }
3567
3645
  actionDragState = null;
3568
3646
  }
3647
+ if (drawingDragState) {
3648
+ drawingDragState = null;
3649
+ emitDrawingsChange();
3650
+ }
3569
3651
  isDragging = false;
3570
3652
  dragMode = null;
3571
3653
  activePointerId = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hyperprop-charting-library",
3
- "version": "0.1.55",
3
+ "version": "0.1.57",
4
4
  "description": "Lightweight TypeScript charting core",
5
5
  "type": "module",
6
6
  "main": "./dist/hyperprop-charting-library.cjs",