@pipsend/charts 2.3.13 → 2.3.14

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.
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @license
3
- * Pipsend Charts v2.3.13-dev+202609280001
3
+ * Pipsend Charts v2.3.14
4
4
  * Copyright (c) 2026 Pipsend
5
5
  * Licensed under MIT License
6
6
  * Built on TradingView Lightweight Charts™ (Apache 2.0)
@@ -21641,6 +21641,13 @@ class DrawingToolBase {
21641
21641
  series() {
21642
21642
  return this._series;
21643
21643
  }
21644
+ /**
21645
+ * Box the selection toolbar anchors to, in pane pixels. Null falls back to
21646
+ * the serialized points, which cannot place a line across the whole pane.
21647
+ */
21648
+ selectionBox() {
21649
+ return null;
21650
+ }
21644
21651
  getState() {
21645
21652
  return this._state;
21646
21653
  }
@@ -22317,6 +22324,53 @@ function createTrendLineTool(context, options) {
22317
22324
  return new TrendLineTool(context, options);
22318
22325
  }
22319
22326
 
22327
+ const END_HANDLE_MARGIN = 10;
22328
+ /**
22329
+ * Hands the next `clicks` clicks on the chart to `onClick`, past the last bar
22330
+ * too. A press that grabbed another drawing does not count.
22331
+ */
22332
+ function placeByClicks(chart, series, clicks, onClick) {
22333
+ let placed = 0;
22334
+ let handler = null;
22335
+ const stop = () => {
22336
+ if (handler !== null) {
22337
+ chart.unsubscribeClick(handler);
22338
+ handler = null;
22339
+ }
22340
+ };
22341
+ handler = (param) => {
22342
+ if (!param.point || consumeInputClaim(chart)) {
22343
+ return;
22344
+ }
22345
+ const price = series.coordinateToPrice(param.point.y);
22346
+ const time = timeFromCoordinate(chart, series, param.point.x);
22347
+ if (price === null || time === null) {
22348
+ return;
22349
+ }
22350
+ placed++;
22351
+ if (placed >= clicks) {
22352
+ stop();
22353
+ }
22354
+ onClick({ _internal_time: time, _internal_price: price, _internal_x: param.point.x, _internal_y: param.point.y }, placed - 1);
22355
+ };
22356
+ chart.subscribeClick(handler);
22357
+ return {
22358
+ _internal_pending: () => handler !== null,
22359
+ _internal_stop: stop,
22360
+ };
22361
+ }
22362
+ /** A fifth of the pane, and never less than a bar. */
22363
+ function defaultWidth(chart) {
22364
+ const timeScale = chart.timeScale();
22365
+ return Math.max(timeScale.width() / 5, timeScale.options().barSpacing);
22366
+ }
22367
+ /** Kept inside the pane: past it, the handles on the end hide under the price axis. */
22368
+ function placedEndX(chart, x) {
22369
+ const timeScale = chart.timeScale();
22370
+ const onScreen = Math.min(x + defaultWidth(chart), timeScale.width() - END_HANDLE_MARGIN);
22371
+ return Math.max(onScreen, x + timeScale.options().barSpacing);
22372
+ }
22373
+
22320
22374
  class HorizontalLinePaneRenderer {
22321
22375
  constructor(tool) {
22322
22376
  this._private__tool = tool;
@@ -22357,6 +22411,7 @@ class HorizontalLineTool extends DrawingToolBase {
22357
22411
  this._private__isDragging = false;
22358
22412
  this._private__grab = null;
22359
22413
  this._private__moveStartPrice = 0;
22414
+ this._private__placement = null;
22360
22415
  this._private__hLineOptions = {
22361
22416
  lineColor: '#2962ff',
22362
22417
  lineWidth: 2,
@@ -22369,6 +22424,26 @@ class HorizontalLineTool extends DrawingToolBase {
22369
22424
  };
22370
22425
  this._registerOptions(this._private__hLineOptions);
22371
22426
  this._private__createPrimitive();
22427
+ if (this._private__hLineOptions.interactive) {
22428
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
22429
+ }
22430
+ }
22431
+ _private__placeAt(point) {
22432
+ this._private__hLineOptions.price = point._internal_price;
22433
+ if (this._private__hLineOptions.startTime !== undefined) {
22434
+ this._private__hLineOptions.startTime = point._internal_time;
22435
+ }
22436
+ this._private__requestUpdate();
22437
+ }
22438
+ isComplete() {
22439
+ return !this._private__placement?._internal_pending();
22440
+ }
22441
+ selectionBox() {
22442
+ const y = this._private__hLineOptions.price === undefined ? null : this._series.priceToCoordinate(this._private__hLineOptions.price);
22443
+ if (y === null) {
22444
+ return null;
22445
+ }
22446
+ return { left: this._private__startX(), right: this._chart.timeScale().width(), top: y, bottom: y };
22372
22447
  }
22373
22448
  _private__createPrimitive() {
22374
22449
  this._private__paneView = this._private__createPaneView();
@@ -22406,7 +22481,7 @@ class HorizontalLineTool extends DrawingToolBase {
22406
22481
  return coordinateFromTime(this._chart, this._series, this._private__hLineOptions.startTime) ?? 0;
22407
22482
  }
22408
22483
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
22409
- if (this._private__hLineOptions.price === undefined)
22484
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined)
22410
22485
  return;
22411
22486
  const y = (this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0) * verticalPixelRatio;
22412
22487
  const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
@@ -22500,7 +22575,7 @@ class HorizontalLineTool extends DrawingToolBase {
22500
22575
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
22501
22576
  }
22502
22577
  hitTest(x, y) {
22503
- if (this._private__hLineOptions.price === undefined) {
22578
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined) {
22504
22579
  return false;
22505
22580
  }
22506
22581
  const lineY = this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0;
@@ -22575,6 +22650,7 @@ class HorizontalLineTool extends DrawingToolBase {
22575
22650
  this._private__requestUpdate();
22576
22651
  }
22577
22652
  remove() {
22653
+ this._private__placement?._internal_stop();
22578
22654
  if (this._private__primitive) {
22579
22655
  this._series.detachPrimitive(this._private__primitive);
22580
22656
  this._private__primitive = null;
@@ -22634,6 +22710,7 @@ class CrossLineTool extends DrawingToolBase {
22634
22710
  this._private__grab = null;
22635
22711
  this._private__moveStartTime = 0;
22636
22712
  this._private__moveStartPrice = 0;
22713
+ this._private__placement = null;
22637
22714
  this._private__crossOptions = {
22638
22715
  lineColor: '#787b86',
22639
22716
  lineWidth: 1,
@@ -22643,6 +22720,16 @@ class CrossLineTool extends DrawingToolBase {
22643
22720
  };
22644
22721
  this._registerOptions(this._private__crossOptions);
22645
22722
  this._private__createPrimitive();
22723
+ if (this._private__crossOptions.interactive) {
22724
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
22725
+ this._private__crossOptions.time = point._internal_time;
22726
+ this._private__crossOptions.price = point._internal_price;
22727
+ this._private__requestUpdate();
22728
+ });
22729
+ }
22730
+ }
22731
+ isComplete() {
22732
+ return !this._private__placement?._internal_pending();
22646
22733
  }
22647
22734
  _private__createPrimitive() {
22648
22735
  this._private__paneView = new CrossLinePaneView(this);
@@ -22670,7 +22757,7 @@ class CrossLineTool extends DrawingToolBase {
22670
22757
  this._private__requestUpdate();
22671
22758
  }
22672
22759
  _private__coordinates() {
22673
- if (this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
22760
+ if (!this.isComplete() || this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
22674
22761
  return null;
22675
22762
  }
22676
22763
  const x = coordinateFromTime(this._chart, this._series, this._private__crossOptions.time);
@@ -22793,6 +22880,7 @@ class CrossLineTool extends DrawingToolBase {
22793
22880
  return this._private__isHovered;
22794
22881
  }
22795
22882
  remove() {
22883
+ this._private__placement?._internal_stop();
22796
22884
  if (this._private__primitive) {
22797
22885
  try {
22798
22886
  this._series.detachPrimitive(this._private__primitive);
@@ -24215,6 +24303,7 @@ class VerticalLineTool extends DrawingToolBase {
24215
24303
  this._private__isDragging = false;
24216
24304
  this._private__grab = null;
24217
24305
  this._private__moveStartTime = 0;
24306
+ this._private__placement = null;
24218
24307
  this._private__vLineOptions = {
24219
24308
  lineColor: '#787b86',
24220
24309
  lineWidth: 1,
@@ -24226,6 +24315,29 @@ class VerticalLineTool extends DrawingToolBase {
24226
24315
  };
24227
24316
  this._registerOptions(this._private__vLineOptions);
24228
24317
  this._private__createPrimitive();
24318
+ if (this._private__vLineOptions.interactive) {
24319
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
24320
+ this._private__vLineOptions.time = point._internal_time;
24321
+ this._private__requestUpdate();
24322
+ });
24323
+ }
24324
+ }
24325
+ isComplete() {
24326
+ return !this._private__placement?._internal_pending();
24327
+ }
24328
+ selectionBox() {
24329
+ const x = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time);
24330
+ if (x === null) {
24331
+ return null;
24332
+ }
24333
+ let middle;
24334
+ try {
24335
+ middle = this._series.getPane().getHeight() / 2;
24336
+ }
24337
+ catch (error) {
24338
+ return null;
24339
+ }
24340
+ return { left: x, right: x, top: middle, bottom: middle };
24229
24341
  }
24230
24342
  _private__createPrimitive() {
24231
24343
  this._private__paneView = this._private__createPaneView();
@@ -24256,7 +24368,7 @@ class VerticalLineTool extends DrawingToolBase {
24256
24368
  return new VerticalLinePaneView(this);
24257
24369
  }
24258
24370
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
24259
- if (this._private__vLineOptions.time === undefined)
24371
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined)
24260
24372
  return;
24261
24373
  const x = (coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0) * horizontalPixelRatio;
24262
24374
  const chartHeight = this._chart.chartElement().clientHeight * verticalPixelRatio;
@@ -24343,7 +24455,7 @@ class VerticalLineTool extends DrawingToolBase {
24343
24455
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
24344
24456
  }
24345
24457
  hitTest(x, y) {
24346
- if (this._private__vLineOptions.time === undefined) {
24458
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined) {
24347
24459
  return false;
24348
24460
  }
24349
24461
  const lineX = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0;
@@ -24413,6 +24525,7 @@ class VerticalLineTool extends DrawingToolBase {
24413
24525
  this._private__requestUpdate();
24414
24526
  }
24415
24527
  remove() {
24528
+ this._private__placement?._internal_stop();
24416
24529
  if (this._private__primitive) {
24417
24530
  this._series.detachPrimitive(this._private__primitive);
24418
24531
  this._private__primitive = null;
@@ -25134,6 +25247,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
25134
25247
  this._private__isHovered = false;
25135
25248
  this._private__isDragging = false;
25136
25249
  this._private__dragTarget = null;
25250
+ this._private__placement = null;
25137
25251
  this._private__fibOptions = {
25138
25252
  levels: [
25139
25253
  { level: 0, color: '#787B86', label: '0%' },
@@ -25149,6 +25263,20 @@ class FibonacciExtensionTool extends DrawingToolBase {
25149
25263
  ...options,
25150
25264
  };
25151
25265
  this._private__createPrimitive();
25266
+ if (this._private__fibOptions.interactive) {
25267
+ this._private__placement = placeByClicks(this._chart, this._series, 3, (point, index) => {
25268
+ const placed = { time: point._internal_time, price: point._internal_price };
25269
+ if (index === 0) {
25270
+ this.setPoint1(placed);
25271
+ }
25272
+ else if (index === 1) {
25273
+ this.setPoint2(placed);
25274
+ }
25275
+ else {
25276
+ this.setPoint3(placed);
25277
+ }
25278
+ });
25279
+ }
25152
25280
  }
25153
25281
  _private__createPrimitive() {
25154
25282
  this._private__paneView = this._private__createPaneView();
@@ -25206,7 +25334,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
25206
25334
  this._private__requestUpdate();
25207
25335
  }
25208
25336
  isComplete() {
25209
- return !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
25337
+ return !this._private__placement?._internal_pending() && !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
25210
25338
  }
25211
25339
  setColor(color) {
25212
25340
  this._private__fibOptions.lineColor = color;
@@ -25501,6 +25629,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
25501
25629
  this._private__requestUpdate();
25502
25630
  }
25503
25631
  remove() {
25632
+ this._private__placement?._internal_stop();
25504
25633
  if (this._private__primitive) {
25505
25634
  this._series.detachPrimitive(this._private__primitive);
25506
25635
  this._private__primitive = null;
@@ -28066,6 +28195,7 @@ class TextTool extends DrawingToolBase {
28066
28195
  this._private__moveStart = null;
28067
28196
  this._private__isEditing = false;
28068
28197
  this._private__clickOutsideHandler = null;
28198
+ this._private__placement = null;
28069
28199
  this._private__textOptions = {
28070
28200
  text: 'Double-click to edit',
28071
28201
  fontSize: 14,
@@ -28086,6 +28216,14 @@ class TextTool extends DrawingToolBase {
28086
28216
  this._registerOptions(this._private__textOptions);
28087
28217
  this._private__createPrimitive();
28088
28218
  this._private__setupClickOutsideHandler();
28219
+ if (this._private__textOptions.interactive) {
28220
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
28221
+ this.setPosition(point._internal_time, point._internal_price);
28222
+ if (this._private__textOptions.onComplete) {
28223
+ this._private__textOptions.onComplete();
28224
+ }
28225
+ });
28226
+ }
28089
28227
  }
28090
28228
  _private__setupClickOutsideHandler() {
28091
28229
  // Set up click handler to finish editing when clicking outside
@@ -28171,7 +28309,7 @@ class TextTool extends DrawingToolBase {
28171
28309
  this._private__requestUpdate();
28172
28310
  }
28173
28311
  isComplete() {
28174
- return !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
28312
+ return !this._private__placement?._internal_pending() && !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
28175
28313
  }
28176
28314
  setColor(color) {
28177
28315
  this._private__textOptions.textColor = color;
@@ -28454,6 +28592,7 @@ class TextTool extends DrawingToolBase {
28454
28592
  };
28455
28593
  }
28456
28594
  remove() {
28595
+ this._private__placement?._internal_stop();
28457
28596
  // Clean up click handler
28458
28597
  if (this._private__clickOutsideHandler) {
28459
28598
  this._chart.unsubscribeClick(this._private__clickOutsideHandler);
@@ -29312,6 +29451,7 @@ class PositionTool extends DrawingToolBase {
29312
29451
  this._private__paneView = null;
29313
29452
  this._private__isSelected = false;
29314
29453
  this._private__isHovered = false;
29454
+ this._private__placement = null;
29315
29455
  this._private__isDragging = false;
29316
29456
  this._private__dragTarget = null;
29317
29457
  this._private__grab = null;
@@ -29334,6 +29474,32 @@ class PositionTool extends DrawingToolBase {
29334
29474
  // Create primitive and setup
29335
29475
  this._private__createPrimitive();
29336
29476
  this._private__updateMetrics();
29477
+ if (this._private__positionOptions.interactive) {
29478
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
29479
+ }
29480
+ }
29481
+ _private__placeAt(point) {
29482
+ const options = this._private__positionOptions;
29483
+ const shift = point._internal_price - options.entryPrice;
29484
+ options.entryPrice = point._internal_price;
29485
+ options.entryTime = point._internal_time;
29486
+ if (options.stopLoss !== undefined) {
29487
+ options.stopLoss += shift;
29488
+ }
29489
+ if (options.takeProfit !== undefined) {
29490
+ options.takeProfit += shift;
29491
+ }
29492
+ options.endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? undefined;
29493
+ this._private__updateMetrics();
29494
+ this._private__requestUpdate();
29495
+ }
29496
+ isComplete() {
29497
+ return !this._private__placement?._internal_pending();
29498
+ }
29499
+ _private__span() {
29500
+ const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29501
+ const endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? entryX + defaultWidth(this._chart);
29502
+ return { _internal_entryX: entryX, _internal_endX: endX };
29337
29503
  }
29338
29504
  _private__createPrimitive() {
29339
29505
  // Create and cache the paneView once
@@ -29365,6 +29531,9 @@ class PositionTool extends DrawingToolBase {
29365
29531
  return new PositionPaneView(this);
29366
29532
  }
29367
29533
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29534
+ if (!this.isComplete()) {
29535
+ return;
29536
+ }
29368
29537
  // Draw profit rectangle (green)
29369
29538
  if (this._private__profitRect && this._private__positionOptions.takeProfit) {
29370
29539
  const timeScale = this._chart.timeScale();
@@ -29398,16 +29567,9 @@ class PositionTool extends DrawingToolBase {
29398
29567
  }
29399
29568
  }
29400
29569
  _private__drawRectangle(ctx, rect, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29401
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29402
- // Use endTime if specified, otherwise extend to chart edge
29403
- let endX;
29404
- if (this._private__positionOptions.endTime) {
29405
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29406
- }
29407
- else {
29408
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29409
- endX = chartWidth;
29410
- }
29570
+ const span = this._private__span();
29571
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29572
+ const endX = span._internal_endX * horizontalPixelRatio;
29411
29573
  const y1 = (this._series.priceToCoordinate(rect._internal_price1) ?? 0) * verticalPixelRatio;
29412
29574
  const y2 = (this._series.priceToCoordinate(rect._internal_price2) ?? 0) * verticalPixelRatio;
29413
29575
  // Draw filled rectangle with semi-transparent fill
@@ -29422,16 +29584,10 @@ class PositionTool extends DrawingToolBase {
29422
29584
  ctx.stroke();
29423
29585
  }
29424
29586
  _private__drawEntryLine(ctx, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29425
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29587
+ const span = this._private__span();
29588
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29589
+ const endX = span._internal_endX * horizontalPixelRatio;
29426
29590
  const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
29427
- let endX;
29428
- if (this._private__positionOptions.endTime) {
29429
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29430
- }
29431
- else {
29432
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29433
- endX = chartWidth;
29434
- }
29435
29591
  // Draw horizontal entry line (divider)
29436
29592
  const lineColor = this._private__positionOptions.entryLineColor;
29437
29593
  ctx.strokeStyle = lineColor;
@@ -29545,9 +29701,11 @@ class PositionTool extends DrawingToolBase {
29545
29701
  const padding = 6 * horizontalPixelRatio;
29546
29702
  const labelWidth = textMetrics.width + padding * 2;
29547
29703
  const labelHeight = 20 * horizontalPixelRatio;
29548
- // Position label at the start of the line
29704
+ // Outside the box, so a label wider than it does not cover the handles on its edge
29705
+ const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
29706
+ const gap = 3 * verticalPixelRatio;
29549
29707
  const labelX = entryX + 8 * horizontalPixelRatio;
29550
- const labelY = y - labelHeight / 2;
29708
+ const labelY = y <= entryY ? y - labelHeight - gap : y + gap;
29551
29709
  // Draw label background
29552
29710
  ctx.fillStyle = rect._internal_color;
29553
29711
  ctx.fillRect(labelX, labelY, labelWidth, labelHeight);
@@ -29555,10 +29713,12 @@ class PositionTool extends DrawingToolBase {
29555
29713
  ctx.fillStyle = '#ffffff';
29556
29714
  ctx.textAlign = 'left';
29557
29715
  ctx.textBaseline = 'middle';
29558
- ctx.fillText(labelText, labelX + padding, y);
29716
+ ctx.fillText(labelText, labelX + padding, labelY + labelHeight / 2);
29559
29717
  }
29560
29718
  _private__drawEndpoint(ctx, price, color, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29561
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29719
+ const span = this._private__span();
29720
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29721
+ const endX = span._internal_endX * horizontalPixelRatio;
29562
29722
  const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
29563
29723
  // Draw circle endpoint at entry
29564
29724
  const circleColor = color;
@@ -29570,14 +29730,6 @@ class PositionTool extends DrawingToolBase {
29570
29730
  ctx.fill();
29571
29731
  ctx.stroke();
29572
29732
  // Draw square endpoint at the right edge
29573
- let endX;
29574
- if (this._private__positionOptions.endTime) {
29575
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29576
- }
29577
- else {
29578
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29579
- endX = chartWidth - 10 * horizontalPixelRatio;
29580
- }
29581
29733
  const size = 8 * horizontalPixelRatio;
29582
29734
  const squareColor = color;
29583
29735
  ctx.fillStyle = squareColor;
@@ -29698,18 +29850,12 @@ class PositionTool extends DrawingToolBase {
29698
29850
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
29699
29851
  }
29700
29852
  hitTest(x, y) {
29701
- if (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit) {
29853
+ if (!this.isComplete() || (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit)) {
29702
29854
  return false;
29703
29855
  }
29704
- const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29705
- let endX;
29706
- if (this._private__positionOptions.endTime) {
29707
- endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
29708
- }
29709
- else {
29710
- const chartWidth = this._chart.chartElement().clientWidth;
29711
- endX = chartWidth;
29712
- }
29856
+ const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
29857
+ const left = Math.min(entryX, endX);
29858
+ const right = Math.max(entryX, endX);
29713
29859
  // Check if inside the entire Position Tool area (rectangles)
29714
29860
  if (this._private__positionOptions.takeProfit && this._private__positionOptions.stopLoss) {
29715
29861
  const tpY = this._series.priceToCoordinate(this._private__positionOptions.takeProfit) ?? 0;
@@ -29717,7 +29863,7 @@ class PositionTool extends DrawingToolBase {
29717
29863
  const minY = Math.min(tpY, slY);
29718
29864
  const maxY = Math.max(tpY, slY);
29719
29865
  // Check if inside the rectangle bounds (entire tool area)
29720
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29866
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29721
29867
  return true;
29722
29868
  }
29723
29869
  // Also check entry line area
@@ -29729,15 +29875,12 @@ class PositionTool extends DrawingToolBase {
29729
29875
  return false;
29730
29876
  }
29731
29877
  hitTestEndpoint(x, y) {
29732
- const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29733
- let endX;
29734
- if (this._private__positionOptions.endTime) {
29735
- endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
29736
- }
29737
- else {
29738
- const chartWidth = this._chart.chartElement().clientWidth;
29739
- endX = chartWidth;
29878
+ if (!this.isComplete()) {
29879
+ return null;
29740
29880
  }
29881
+ const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
29882
+ const left = Math.min(entryX, endX);
29883
+ const right = Math.max(entryX, endX);
29741
29884
  const tolerance = 10;
29742
29885
  // Check TP left endpoint (circle)
29743
29886
  if (this._private__positionOptions.takeProfit) {
@@ -29774,7 +29917,7 @@ class PositionTool extends DrawingToolBase {
29774
29917
  // Check entry line (middle divider line) - higher priority than 'move'
29775
29918
  const entryY = this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0;
29776
29919
  const lineTolerance = 8;
29777
- if (x >= entryX && x <= endX && Math.abs(y - entryY) <= lineTolerance) {
29920
+ if (x >= left && x <= right && Math.abs(y - entryY) <= lineTolerance) {
29778
29921
  return 'entry';
29779
29922
  }
29780
29923
  // Check if clicking on the body (rectangles) for moving entire tool
@@ -29784,7 +29927,7 @@ class PositionTool extends DrawingToolBase {
29784
29927
  const minY = Math.min(tpY, slY);
29785
29928
  const maxY = Math.max(tpY, slY);
29786
29929
  // Check if inside the rectangle bounds
29787
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29930
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29788
29931
  return 'move';
29789
29932
  }
29790
29933
  }
@@ -29884,6 +30027,7 @@ class PositionTool extends DrawingToolBase {
29884
30027
  this._private__requestUpdate();
29885
30028
  }
29886
30029
  remove() {
30030
+ this._private__placement?._internal_stop();
29887
30031
  if (this._private__primitive) {
29888
30032
  this._series.detachPrimitive(this._private__primitive);
29889
30033
  this._private__primitive = null;
@@ -29930,6 +30074,7 @@ class GanttPaneView {
29930
30074
  }
29931
30075
  }
29932
30076
 
30077
+ const TOP_PADDING = 50;
29933
30078
  class GanttTool extends DrawingToolBase {
29934
30079
  constructor(context, options) {
29935
30080
  super(context, 'gantt', options);
@@ -29943,6 +30088,7 @@ class GanttTool extends DrawingToolBase {
29943
30088
  this._private__grab = null;
29944
30089
  this._private__moveStart = null;
29945
30090
  this._private__hoveredSegmentIndex = -1;
30091
+ this._private__placement = null;
29946
30092
  this._private__ganttOptions = {
29947
30093
  segments: [],
29948
30094
  rowHeight: 40,
@@ -29955,6 +30101,43 @@ class GanttTool extends DrawingToolBase {
29955
30101
  };
29956
30102
  this._registerOptions(this._private__ganttOptions);
29957
30103
  this._private__createPrimitive();
30104
+ if (this._private__ganttOptions.interactive) {
30105
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
30106
+ }
30107
+ }
30108
+ _private__placeAt(point) {
30109
+ const segments = this._private__ganttOptions.segments ?? [];
30110
+ const endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? point._internal_time;
30111
+ const rowPitch = this._private__ganttOptions.rowHeight + this._private__ganttOptions.rowSpacing;
30112
+ const row = Math.max(0, Math.floor((point._internal_y - TOP_PADDING) / rowPitch));
30113
+ const shift = segments.length > 0 ? row - segments[0].level : 0;
30114
+ for (const segment of segments) {
30115
+ segment.startTime = point._internal_time;
30116
+ segment.endTime = endTime;
30117
+ segment.level = Math.max(0, segment.level + shift);
30118
+ }
30119
+ this._private__requestUpdate();
30120
+ }
30121
+ isComplete() {
30122
+ return !this._private__placement?._internal_pending();
30123
+ }
30124
+ selectionBox() {
30125
+ const segments = this._private__ganttOptions.segments ?? [];
30126
+ if (segments.length === 0) {
30127
+ return null;
30128
+ }
30129
+ const rowHeight = this._private__ganttOptions.rowHeight;
30130
+ const pitch = rowHeight + this._private__ganttOptions.rowSpacing;
30131
+ // Typed as the public shape: an inferred one gets its keys renamed by the build
30132
+ const box = { left: Infinity, top: Infinity, right: -Infinity, bottom: -Infinity };
30133
+ for (const segment of segments) {
30134
+ const top = TOP_PADDING + segment.level * pitch;
30135
+ box.left = Math.min(box.left, coordinateFromTime(this._chart, this._series, segment.startTime) ?? 0);
30136
+ box.right = Math.max(box.right, coordinateFromTime(this._chart, this._series, segment.endTime) ?? 0);
30137
+ box.top = Math.min(box.top, top);
30138
+ box.bottom = Math.max(box.bottom, top + rowHeight);
30139
+ }
30140
+ return box;
29958
30141
  }
29959
30142
  _private__createPrimitive() {
29960
30143
  this._private__paneView = this._private__createPaneView();
@@ -29985,11 +30168,10 @@ class GanttTool extends DrawingToolBase {
29985
30168
  return new GanttPaneView(this);
29986
30169
  }
29987
30170
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29988
- if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
30171
+ if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
29989
30172
  return;
29990
30173
  // Position gantt at top of chart with padding
29991
- const topPadding = 50; // pixels from top
29992
- const baseY = topPadding * verticalPixelRatio;
30174
+ const baseY = TOP_PADDING * verticalPixelRatio;
29993
30175
  const rowHeight = this._private__ganttOptions.rowHeight * verticalPixelRatio;
29994
30176
  const rowSpacing = this._private__ganttOptions.rowSpacing * verticalPixelRatio;
29995
30177
  // Draw grid lines if enabled
@@ -30168,11 +30350,10 @@ class GanttTool extends DrawingToolBase {
30168
30350
  this._private__moveStart = null;
30169
30351
  }
30170
30352
  hitTest(x, y) {
30171
- if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
30353
+ if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
30172
30354
  return false;
30173
30355
  }
30174
- const topPadding = 50;
30175
- const baseY = topPadding;
30356
+ const baseY = TOP_PADDING;
30176
30357
  const rowHeight = this._private__ganttOptions.rowHeight;
30177
30358
  const rowSpacing = this._private__ganttOptions.rowSpacing;
30178
30359
  // Check each segment
@@ -30226,6 +30407,7 @@ class GanttTool extends DrawingToolBase {
30226
30407
  this._private__requestUpdate();
30227
30408
  }
30228
30409
  remove() {
30410
+ this._private__placement?._internal_stop();
30229
30411
  if (this._private__primitive) {
30230
30412
  this._series.detachPrimitive(this._private__primitive);
30231
30413
  this._private__primitive = null;
@@ -30361,6 +30543,7 @@ class DrawingToolsManager {
30361
30543
  }
30362
30544
  this._private__completionCheck = setTimeout(() => {
30363
30545
  this._private__completionCheck = null;
30546
+ const completed = [];
30364
30547
  for (const id of Array.from(this._private__pendingToolIds)) {
30365
30548
  const tool = this._private__tools.get(id);
30366
30549
  if (!tool) {
@@ -30369,9 +30552,15 @@ class DrawingToolsManager {
30369
30552
  }
30370
30553
  if (tool.isComplete()) {
30371
30554
  this._private__pendingToolIds.delete(id);
30372
- this._private__drawingCompleted._internal_fire(id);
30555
+ completed.push(id);
30373
30556
  }
30374
30557
  }
30558
+ if (completed.length > 0) {
30559
+ this.recordChange();
30560
+ }
30561
+ for (const id of completed) {
30562
+ this._private__drawingCompleted._internal_fire(id);
30563
+ }
30375
30564
  }, 0);
30376
30565
  }
30377
30566
  /**
@@ -30431,12 +30620,16 @@ class DrawingToolsManager {
30431
30620
  if (this._private__restoring) {
30432
30621
  return;
30433
30622
  }
30623
+ const next = this.serializeAll();
30624
+ if (JSON.stringify(next) === JSON.stringify(this._private__committed)) {
30625
+ return;
30626
+ }
30434
30627
  this._private__past.push(this._private__committed);
30435
30628
  if (this._private__past.length > UNDO_HISTORY_LIMIT) {
30436
30629
  this._private__past.shift();
30437
30630
  }
30438
30631
  this._private__future = [];
30439
- this._private__committed = this.serializeAll();
30632
+ this._private__committed = next;
30440
30633
  }
30441
30634
  canUndo() {
30442
30635
  return this._private__past.length > 0;
@@ -30492,11 +30685,25 @@ class DrawingToolsManager {
30492
30685
  const tool = this._private__tools.get(this._private__selectedToolId);
30493
30686
  return tool ? this._private__boundsOf(tool) : null;
30494
30687
  }
30688
+ /** Relative to the chart element; the tool's box is relative to its pane. */
30689
+ _private__boundsOf(tool) {
30690
+ const box = tool.selectionBox() ?? this._private__boxFromPoints(tool);
30691
+ if (box === null) {
30692
+ return null;
30693
+ }
30694
+ const offset = this._private__paneOffsetOf(tool);
30695
+ return {
30696
+ left: box.left + offset._internal_x,
30697
+ right: box.right + offset._internal_x,
30698
+ top: box.top + offset._internal_y,
30699
+ bottom: box.bottom + offset._internal_y,
30700
+ };
30701
+ }
30495
30702
  /**
30496
30703
  * Bounds come from the tool's own serialized points, so this works for every
30497
30704
  * tool without each one having to report a box.
30498
30705
  */
30499
- _private__boundsOf(tool) {
30706
+ _private__boxFromPoints(tool) {
30500
30707
  const series = tool.series();
30501
30708
  const points = tool.serialize().points;
30502
30709
  let left = Number.POSITIVE_INFINITY;
@@ -30519,13 +30726,7 @@ class DrawingToolsManager {
30519
30726
  if (!found) {
30520
30727
  return null;
30521
30728
  }
30522
- const offset = this._private__paneOffsetOf(tool);
30523
- return {
30524
- left: left + offset._internal_x,
30525
- right: right + offset._internal_x,
30526
- top: top + offset._internal_y,
30527
- bottom: bottom + offset._internal_y,
30528
- };
30729
+ return { left, top, right, bottom };
30529
30730
  }
30530
30731
  /** Panes below the first one are offset inside the chart element. */
30531
30732
  _private__paneOffsetOf(tool) {
@@ -30609,7 +30810,10 @@ class DrawingToolsManager {
30609
30810
  serializeAll() {
30610
30811
  const result = [];
30611
30812
  for (const tool of this._private__tools.values()) {
30612
- result.push(tool.serialize());
30813
+ // Not drawn yet: undo would bring it back as a finished drawing
30814
+ if (tool.isComplete()) {
30815
+ result.push(tool.serialize());
30816
+ }
30613
30817
  }
30614
30818
  return result;
30615
30819
  }
@@ -31234,7 +31438,7 @@ const customSeriesDefaultOptions = {
31234
31438
  * Returns the current version as a string. For example `'1.0.0'`.
31235
31439
  */
31236
31440
  function version() {
31237
- return "2.3.13-dev+202609280001";
31441
+ return "2.3.14";
31238
31442
  }
31239
31443
 
31240
31444
  export { areaSeries as AreaSeries, ArrowTool, barSeries as BarSeries, baselineSeries as BaselineSeries, BrushTool, candlestickSeries as CandlestickSeries, CircleTool, ColorType, CrossLineTool, CrosshairMode, DEFAULT_SESSIONS, DraggablePriceLine, DrawingToolsManager, EXTENDED_FIBONACCI_RATIOS, FibonacciCalculator, FibonacciExtensionTool, FibonacciFanTool, FibonacciTool, GanttTool, histogramSeries as HistogramSeries, HorizontalLineTool, InteractiveLineManager, LastPriceAnimationMode, lineSeries as LineSeries, LineStyle, LineType, MismatchDirection, ParallelChannelTool, PitchforkTool, PolygonTool, PositionCalculator, PositionLine, PositionMarkersManager, PositionTool, PriceLineSource, PriceScaleMode, RectangleTool, RulerCalculator, RulerTool, STANDARD_FIBONACCI_RATIOS, TextTool, TickMarkType, TrackingModeExitMode, TradingSessionsIndicator, TrendLineTool, VerticalLineTool, VolumeProfileIndicator, applyADX, applyATR, applyBollingerBands, applyCCI, applyDonchianChannels, applyEMA, applyHeikinAshi, applyIchimoku, applyKeltnerChannels, applyMACD, applyMFI, applyOBV, applyParabolicSAR, applyPivotPoints, applyRSI, applySMA, applyStochastic, applySuperTrend, applyVWAP, applyVolume, applyWMA, applyWilliamsR, calculateHeikinAshi, calculatePipConfig, coordinateFromTime, createArrowTool, createBrushTool, createChart, createChartEx, createCircleTool, createCrossLineTool, createFibonacciExtensionTool, createFibonacciFanTool, createFibonacciTool, createGanttTool, createHorizontalLineTool, createImageWatermark, createInteractiveLineManager, createOptionsChart, createParallelChannelTool, createPitchforkTool, createPolygonTool, createPositionLine, createPositionMarkers, createPositionTool, createRectangleTool, createRulerTool, createSeriesMarkers, createTextTool, createTextWatermark, createTradingLine, createTradingSessionsIndicator, createTrendLineTool, createUpDownMarkers, createVerticalLineTool, createVolumeProfileIndicator, createYieldCurveChart, customSeriesDefaultOptions, defaultHorzScaleBehavior, getPipConfig, hideSeriesPaint, isBusinessDay, isUTCTimestamp, setSeriesVolumes, setVolumeProfileData, timeFromCoordinate, version };