@pipsend/charts 2.3.12 → 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.12
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)
@@ -14674,6 +14674,8 @@ function shapeSize(shape, originalSize) {
14674
14674
  return size(originalSize, 0.7);
14675
14675
  case 'tag':
14676
14676
  return size(originalSize, 1);
14677
+ case 'xcross':
14678
+ return size(originalSize, 0.7);
14677
14679
  }
14678
14680
  }
14679
14681
  function calculateShapeHeight(barSpacing) {
@@ -14874,6 +14876,27 @@ function hitTestText(textX, textY, textWidth, textHeight, x, y) {
14874
14876
  y >= textY - halfHeight && y <= textY + halfHeight;
14875
14877
  }
14876
14878
 
14879
+ function drawXCross(ctx, coords, size) {
14880
+ const crossSize = shapeSize('xcross', size);
14881
+ const halfSize = ((crossSize - 1) / 2) * coords._internal_pixelRatio;
14882
+ ctx.save();
14883
+ ctx.strokeStyle = ctx.fillStyle;
14884
+ ctx.lineWidth = Math.max(1, Math.round(1.5 * coords._internal_pixelRatio));
14885
+ ctx.lineCap = 'round';
14886
+ ctx.beginPath();
14887
+ ctx.moveTo(coords._internal_x - halfSize, coords._internal_y - halfSize);
14888
+ ctx.lineTo(coords._internal_x + halfSize, coords._internal_y + halfSize);
14889
+ ctx.moveTo(coords._internal_x + halfSize, coords._internal_y - halfSize);
14890
+ ctx.lineTo(coords._internal_x - halfSize, coords._internal_y + halfSize);
14891
+ ctx.stroke();
14892
+ ctx.restore();
14893
+ }
14894
+ function hitTestXCross(centerX, centerY, size, x, y) {
14895
+ const crossSize = shapeSize('xcross', size);
14896
+ const tolerance = 2 + crossSize / 2;
14897
+ return Math.abs(x - centerX) <= tolerance && Math.abs(y - centerY) <= tolerance;
14898
+ }
14899
+
14877
14900
  class SeriesMarkersRenderer {
14878
14901
  constructor() {
14879
14902
  this._private__data = null;
@@ -15017,6 +15040,9 @@ function drawShape(item, ctx, coordinates) {
15017
15040
  case 'square':
15018
15041
  drawSquare(ctx, coordinates, item._internal_size);
15019
15042
  return;
15043
+ case 'xcross':
15044
+ drawXCross(ctx, coordinates, item._internal_size);
15045
+ return;
15020
15046
  case 'tag':
15021
15047
  // Handled by drawTagItem, which needs both pixel ratios and the text.
15022
15048
  return;
@@ -15045,6 +15071,8 @@ function hitTestShape(item, x, y) {
15045
15071
  return hitTestCircle(item._internal_x, item._internal_y, item._internal_size, x, y);
15046
15072
  case 'square':
15047
15073
  return hitTestSquare(item._internal_x, item._internal_y, item._internal_size, x, y);
15074
+ case 'xcross':
15075
+ return hitTestXCross(item._internal_x, item._internal_y, item._internal_size, x, y);
15048
15076
  case 'tag':
15049
15077
  return false;
15050
15078
  }
@@ -21613,6 +21641,13 @@ class DrawingToolBase {
21613
21641
  series() {
21614
21642
  return this._series;
21615
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
+ }
21616
21651
  getState() {
21617
21652
  return this._state;
21618
21653
  }
@@ -22289,6 +22324,53 @@ function createTrendLineTool(context, options) {
22289
22324
  return new TrendLineTool(context, options);
22290
22325
  }
22291
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
+
22292
22374
  class HorizontalLinePaneRenderer {
22293
22375
  constructor(tool) {
22294
22376
  this._private__tool = tool;
@@ -22329,6 +22411,7 @@ class HorizontalLineTool extends DrawingToolBase {
22329
22411
  this._private__isDragging = false;
22330
22412
  this._private__grab = null;
22331
22413
  this._private__moveStartPrice = 0;
22414
+ this._private__placement = null;
22332
22415
  this._private__hLineOptions = {
22333
22416
  lineColor: '#2962ff',
22334
22417
  lineWidth: 2,
@@ -22341,6 +22424,26 @@ class HorizontalLineTool extends DrawingToolBase {
22341
22424
  };
22342
22425
  this._registerOptions(this._private__hLineOptions);
22343
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 };
22344
22447
  }
22345
22448
  _private__createPrimitive() {
22346
22449
  this._private__paneView = this._private__createPaneView();
@@ -22378,7 +22481,7 @@ class HorizontalLineTool extends DrawingToolBase {
22378
22481
  return coordinateFromTime(this._chart, this._series, this._private__hLineOptions.startTime) ?? 0;
22379
22482
  }
22380
22483
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
22381
- if (this._private__hLineOptions.price === undefined)
22484
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined)
22382
22485
  return;
22383
22486
  const y = (this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0) * verticalPixelRatio;
22384
22487
  const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
@@ -22472,7 +22575,7 @@ class HorizontalLineTool extends DrawingToolBase {
22472
22575
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
22473
22576
  }
22474
22577
  hitTest(x, y) {
22475
- if (this._private__hLineOptions.price === undefined) {
22578
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined) {
22476
22579
  return false;
22477
22580
  }
22478
22581
  const lineY = this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0;
@@ -22547,6 +22650,7 @@ class HorizontalLineTool extends DrawingToolBase {
22547
22650
  this._private__requestUpdate();
22548
22651
  }
22549
22652
  remove() {
22653
+ this._private__placement?._internal_stop();
22550
22654
  if (this._private__primitive) {
22551
22655
  this._series.detachPrimitive(this._private__primitive);
22552
22656
  this._private__primitive = null;
@@ -22606,6 +22710,7 @@ class CrossLineTool extends DrawingToolBase {
22606
22710
  this._private__grab = null;
22607
22711
  this._private__moveStartTime = 0;
22608
22712
  this._private__moveStartPrice = 0;
22713
+ this._private__placement = null;
22609
22714
  this._private__crossOptions = {
22610
22715
  lineColor: '#787b86',
22611
22716
  lineWidth: 1,
@@ -22615,6 +22720,16 @@ class CrossLineTool extends DrawingToolBase {
22615
22720
  };
22616
22721
  this._registerOptions(this._private__crossOptions);
22617
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();
22618
22733
  }
22619
22734
  _private__createPrimitive() {
22620
22735
  this._private__paneView = new CrossLinePaneView(this);
@@ -22642,7 +22757,7 @@ class CrossLineTool extends DrawingToolBase {
22642
22757
  this._private__requestUpdate();
22643
22758
  }
22644
22759
  _private__coordinates() {
22645
- 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) {
22646
22761
  return null;
22647
22762
  }
22648
22763
  const x = coordinateFromTime(this._chart, this._series, this._private__crossOptions.time);
@@ -22765,6 +22880,7 @@ class CrossLineTool extends DrawingToolBase {
22765
22880
  return this._private__isHovered;
22766
22881
  }
22767
22882
  remove() {
22883
+ this._private__placement?._internal_stop();
22768
22884
  if (this._private__primitive) {
22769
22885
  try {
22770
22886
  this._series.detachPrimitive(this._private__primitive);
@@ -24187,6 +24303,7 @@ class VerticalLineTool extends DrawingToolBase {
24187
24303
  this._private__isDragging = false;
24188
24304
  this._private__grab = null;
24189
24305
  this._private__moveStartTime = 0;
24306
+ this._private__placement = null;
24190
24307
  this._private__vLineOptions = {
24191
24308
  lineColor: '#787b86',
24192
24309
  lineWidth: 1,
@@ -24198,6 +24315,29 @@ class VerticalLineTool extends DrawingToolBase {
24198
24315
  };
24199
24316
  this._registerOptions(this._private__vLineOptions);
24200
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 };
24201
24341
  }
24202
24342
  _private__createPrimitive() {
24203
24343
  this._private__paneView = this._private__createPaneView();
@@ -24228,7 +24368,7 @@ class VerticalLineTool extends DrawingToolBase {
24228
24368
  return new VerticalLinePaneView(this);
24229
24369
  }
24230
24370
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
24231
- if (this._private__vLineOptions.time === undefined)
24371
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined)
24232
24372
  return;
24233
24373
  const x = (coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0) * horizontalPixelRatio;
24234
24374
  const chartHeight = this._chart.chartElement().clientHeight * verticalPixelRatio;
@@ -24315,7 +24455,7 @@ class VerticalLineTool extends DrawingToolBase {
24315
24455
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
24316
24456
  }
24317
24457
  hitTest(x, y) {
24318
- if (this._private__vLineOptions.time === undefined) {
24458
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined) {
24319
24459
  return false;
24320
24460
  }
24321
24461
  const lineX = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0;
@@ -24385,6 +24525,7 @@ class VerticalLineTool extends DrawingToolBase {
24385
24525
  this._private__requestUpdate();
24386
24526
  }
24387
24527
  remove() {
24528
+ this._private__placement?._internal_stop();
24388
24529
  if (this._private__primitive) {
24389
24530
  this._series.detachPrimitive(this._private__primitive);
24390
24531
  this._private__primitive = null;
@@ -25106,6 +25247,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
25106
25247
  this._private__isHovered = false;
25107
25248
  this._private__isDragging = false;
25108
25249
  this._private__dragTarget = null;
25250
+ this._private__placement = null;
25109
25251
  this._private__fibOptions = {
25110
25252
  levels: [
25111
25253
  { level: 0, color: '#787B86', label: '0%' },
@@ -25121,6 +25263,20 @@ class FibonacciExtensionTool extends DrawingToolBase {
25121
25263
  ...options,
25122
25264
  };
25123
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
+ }
25124
25280
  }
25125
25281
  _private__createPrimitive() {
25126
25282
  this._private__paneView = this._private__createPaneView();
@@ -25178,7 +25334,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
25178
25334
  this._private__requestUpdate();
25179
25335
  }
25180
25336
  isComplete() {
25181
- 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);
25182
25338
  }
25183
25339
  setColor(color) {
25184
25340
  this._private__fibOptions.lineColor = color;
@@ -25473,6 +25629,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
25473
25629
  this._private__requestUpdate();
25474
25630
  }
25475
25631
  remove() {
25632
+ this._private__placement?._internal_stop();
25476
25633
  if (this._private__primitive) {
25477
25634
  this._series.detachPrimitive(this._private__primitive);
25478
25635
  this._private__primitive = null;
@@ -26124,17 +26281,19 @@ class RectangleTool extends DrawingToolBase {
26124
26281
  // Draw border
26125
26282
  const borderColor = this._private__rectOptions.borderColor;
26126
26283
  const borderWidth = this._private__isSelected ? this._private__rectOptions.borderWidth + 1 : this._private__rectOptions.borderWidth;
26127
- ctx.strokeStyle = borderColor;
26128
- ctx.lineWidth = borderWidth * horizontalPixelRatio;
26129
- // Set border style
26130
- if (this._private__rectOptions.borderStyle === 'dashed') {
26131
- ctx.setLineDash([10 * horizontalPixelRatio, 5 * horizontalPixelRatio]);
26132
- }
26133
- else if (this._private__rectOptions.borderStyle === 'dotted') {
26134
- ctx.setLineDash([2 * horizontalPixelRatio, 3 * horizontalPixelRatio]);
26284
+ if (borderWidth > 0) {
26285
+ ctx.strokeStyle = borderColor;
26286
+ ctx.lineWidth = borderWidth * horizontalPixelRatio;
26287
+ // Set border style
26288
+ if (this._private__rectOptions.borderStyle === 'dashed') {
26289
+ ctx.setLineDash([10 * horizontalPixelRatio, 5 * horizontalPixelRatio]);
26290
+ }
26291
+ else if (this._private__rectOptions.borderStyle === 'dotted') {
26292
+ ctx.setLineDash([2 * horizontalPixelRatio, 3 * horizontalPixelRatio]);
26293
+ }
26294
+ ctx.strokeRect(minX, minY, width, height);
26295
+ ctx.setLineDash([]);
26135
26296
  }
26136
- ctx.strokeRect(minX, minY, width, height);
26137
- ctx.setLineDash([]);
26138
26297
  // Draw corner handles if selected
26139
26298
  if ((this._private__isSelected || this._private__isHovered) && !this._private__isDrawing && !this.isLocked()) {
26140
26299
  this._private__drawCornerHandles(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, borderColor);
@@ -26162,17 +26321,20 @@ class RectangleTool extends DrawingToolBase {
26162
26321
  }
26163
26322
  _private__drawLabel(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, verticalPixelRatio, color) {
26164
26323
  const padding = 6 * horizontalPixelRatio;
26165
- const fontSize = 12 * horizontalPixelRatio;
26324
+ const fontSize = (this._private__rectOptions.labelFontSize ?? 12) * horizontalPixelRatio;
26166
26325
  ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
26167
26326
  const textMetrics = ctx.measureText(this._private__rectOptions.labelText);
26168
26327
  const textWidth = textMetrics.width;
26169
26328
  const textHeight = fontSize;
26329
+ const middleY = (minY + maxY) / 2 - textHeight / 2 - padding;
26330
+ // A box extended far past the pane keeps right-hand and centred labels on screen.
26331
+ const visibleMaxX = Math.min(maxX, ctx.canvas.width);
26170
26332
  // Calculate label position based on labelPosition option
26171
26333
  let labelX = minX + padding;
26172
26334
  let labelY = minY + padding;
26173
26335
  switch (this._private__rectOptions.labelPosition) {
26174
26336
  case 'top-right':
26175
- labelX = maxX - textWidth - padding * 2;
26337
+ labelX = visibleMaxX - textWidth - padding * 2;
26176
26338
  labelY = minY + padding;
26177
26339
  break;
26178
26340
  case 'bottom-left':
@@ -26180,19 +26342,27 @@ class RectangleTool extends DrawingToolBase {
26180
26342
  labelY = maxY - textHeight - padding * 2;
26181
26343
  break;
26182
26344
  case 'bottom-right':
26183
- labelX = maxX - textWidth - padding * 2;
26345
+ labelX = visibleMaxX - textWidth - padding * 2;
26184
26346
  labelY = maxY - textHeight - padding * 2;
26185
26347
  break;
26186
26348
  case 'center':
26187
- labelX = (minX + maxX) / 2 - textWidth / 2 - padding;
26188
- labelY = (minY + maxY) / 2 - textHeight / 2 - padding;
26349
+ labelX = (Math.max(minX, 0) + visibleMaxX) / 2 - textWidth / 2 - padding;
26350
+ labelY = middleY;
26351
+ break;
26352
+ case 'left':
26353
+ labelX = minX + padding;
26354
+ labelY = middleY;
26355
+ break;
26356
+ case 'right':
26357
+ labelX = visibleMaxX - textWidth - padding * 2;
26358
+ labelY = middleY;
26189
26359
  break;
26190
26360
  }
26191
26361
  // Draw background
26192
- ctx.fillStyle = color;
26362
+ ctx.fillStyle = this._private__rectOptions.labelBackgroundColor ?? color;
26193
26363
  ctx.fillRect(labelX, labelY, textWidth + padding * 2, textHeight + padding * 2);
26194
26364
  // Draw text
26195
- ctx.fillStyle = '#ffffff';
26365
+ ctx.fillStyle = this._private__rectOptions.labelColor ?? '#ffffff';
26196
26366
  ctx.textBaseline = 'top';
26197
26367
  ctx.fillText(this._private__rectOptions.labelText, labelX + padding, labelY + padding);
26198
26368
  }
@@ -26430,6 +26600,9 @@ class RectangleTool extends DrawingToolBase {
26430
26600
  showLabel: this._private__rectOptions.showLabel,
26431
26601
  labelText: this._private__rectOptions.labelText,
26432
26602
  labelPosition: this._private__rectOptions.labelPosition,
26603
+ labelColor: this._private__rectOptions.labelColor,
26604
+ labelBackgroundColor: this._private__rectOptions.labelBackgroundColor,
26605
+ labelFontSize: this._private__rectOptions.labelFontSize,
26433
26606
  },
26434
26607
  };
26435
26608
  }
@@ -28022,6 +28195,7 @@ class TextTool extends DrawingToolBase {
28022
28195
  this._private__moveStart = null;
28023
28196
  this._private__isEditing = false;
28024
28197
  this._private__clickOutsideHandler = null;
28198
+ this._private__placement = null;
28025
28199
  this._private__textOptions = {
28026
28200
  text: 'Double-click to edit',
28027
28201
  fontSize: 14,
@@ -28034,11 +28208,22 @@ class TextTool extends DrawingToolBase {
28034
28208
  borderWidth: 1,
28035
28209
  textAlign: 'left',
28036
28210
  maxWidth: 200,
28211
+ anchor: 'top-left',
28212
+ pointer: false,
28213
+ borderRadius: 0,
28037
28214
  ...options,
28038
28215
  };
28039
28216
  this._registerOptions(this._private__textOptions);
28040
28217
  this._private__createPrimitive();
28041
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
+ }
28042
28227
  }
28043
28228
  _private__setupClickOutsideHandler() {
28044
28229
  // Set up click handler to finish editing when clicking outside
@@ -28124,7 +28309,7 @@ class TextTool extends DrawingToolBase {
28124
28309
  this._private__requestUpdate();
28125
28310
  }
28126
28311
  isComplete() {
28127
- 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);
28128
28313
  }
28129
28314
  setColor(color) {
28130
28315
  this._private__textOptions.textColor = color;
@@ -28185,18 +28370,59 @@ class TextTool extends DrawingToolBase {
28185
28370
  const py = this._series.priceToCoordinate(price) ?? 0;
28186
28371
  // Calculate text box dimensions
28187
28372
  const fontSize = this._private__textOptions.fontSize || 14;
28188
- const padding = this._private__textOptions.padding || 8;
28373
+ const padding = this._private__textOptions.padding ?? 8;
28189
28374
  const text = this._private__textOptions.text || '';
28190
28375
  // Rough estimate of text width (will be more accurate in canvas)
28191
28376
  const charWidth = fontSize * 0.6;
28192
28377
  const textWidth = Math.min(text.length * charWidth, this._private__textOptions.maxWidth || 200);
28193
- const textHeight = fontSize + padding * 2;
28194
- // Check if click is within text box bounds
28195
- const left = px;
28196
- const right = px + textWidth + padding * 2;
28197
- const top = py;
28198
- const bottom = py + textHeight;
28199
- return x >= left && x <= right && y >= top && y <= bottom;
28378
+ const boxWidth = textWidth + padding * 2;
28379
+ const boxHeight = fontSize + padding * 2;
28380
+ const tail = this._private__tailSize(boxHeight);
28381
+ const box = this._private__boxOrigin(px, py, boxWidth, boxHeight, tail);
28382
+ const left = box._internal_left - (this._private__textOptions.anchor === 'left' ? tail : 0);
28383
+ const right = box._internal_left + boxWidth + (this._private__textOptions.anchor === 'right' ? tail : 0);
28384
+ return x >= left && x <= right && y >= box._internal_top && y <= box._internal_top + boxHeight;
28385
+ }
28386
+ _private__tailSize(boxHeight) {
28387
+ const anchor = this._private__textOptions.anchor;
28388
+ return this._private__textOptions.pointer && (anchor === 'left' || anchor === 'right') ? Math.round(boxHeight * 0.35) : 0;
28389
+ }
28390
+ _private__boxOrigin(x, y, width, height, tail) {
28391
+ switch (this._private__textOptions.anchor) {
28392
+ case 'left':
28393
+ return { _internal_left: x + tail, _internal_top: y - height / 2 };
28394
+ case 'right':
28395
+ return { _internal_left: x - tail - width, _internal_top: y - height / 2 };
28396
+ case 'center':
28397
+ return { _internal_left: x - width / 2, _internal_top: y - height / 2 };
28398
+ default:
28399
+ return { _internal_left: x, _internal_top: y };
28400
+ }
28401
+ }
28402
+ // eslint-disable-next-line max-params
28403
+ _private__traceBox(ctx, left, top, width, height, tail, anchorX, anchorY, radius) {
28404
+ const r = Math.max(0, Math.min(radius, width / 2, height / 2));
28405
+ ctx.beginPath();
28406
+ ctx.moveTo(left + r, top);
28407
+ ctx.lineTo(left + width - r, top);
28408
+ ctx.arcTo(left + width, top, left + width, top + r, r);
28409
+ if (tail > 0 && this._private__textOptions.anchor === 'right') {
28410
+ ctx.lineTo(left + width, anchorY - tail);
28411
+ ctx.lineTo(anchorX, anchorY);
28412
+ ctx.lineTo(left + width, anchorY + tail);
28413
+ }
28414
+ ctx.lineTo(left + width, top + height - r);
28415
+ ctx.arcTo(left + width, top + height, left + width - r, top + height, r);
28416
+ ctx.lineTo(left + r, top + height);
28417
+ ctx.arcTo(left, top + height, left, top + height - r, r);
28418
+ if (tail > 0 && this._private__textOptions.anchor === 'left') {
28419
+ ctx.lineTo(left, anchorY + tail);
28420
+ ctx.lineTo(anchorX, anchorY);
28421
+ ctx.lineTo(left, anchorY - tail);
28422
+ }
28423
+ ctx.lineTo(left, top + r);
28424
+ ctx.arcTo(left, top, left + r, top, r);
28425
+ ctx.closePath();
28200
28426
  }
28201
28427
  hitTestEndpoint(x, y) {
28202
28428
  if (!this.isComplete())
@@ -28248,7 +28474,7 @@ class TextTool extends DrawingToolBase {
28248
28474
  const x = (coordinateFromTime(this._chart, this._series, time) ?? 0) * horizontalPixelRatio;
28249
28475
  const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
28250
28476
  const fontSize = (this._private__textOptions.fontSize || 14) * horizontalPixelRatio;
28251
- const padding = (this._private__textOptions.padding || 8) * horizontalPixelRatio;
28477
+ const padding = (this._private__textOptions.padding ?? 8) * horizontalPixelRatio;
28252
28478
  const maxWidth = (this._private__textOptions.maxWidth || 200) * horizontalPixelRatio;
28253
28479
  // Set font
28254
28480
  ctx.font = `${fontSize}px ${this._private__textOptions.fontFamily || 'Arial'}`;
@@ -28280,32 +28506,39 @@ class TextTool extends DrawingToolBase {
28280
28506
  const boxWidth = maxLineWidth + padding * 2;
28281
28507
  const lineHeight = fontSize * 1.2;
28282
28508
  const boxHeight = lines.length * lineHeight + padding * 2;
28509
+ const tail = this._private__tailSize(boxHeight);
28510
+ const { _internal_left: left, _internal_top: top } = this._private__boxOrigin(x, y, boxWidth, boxHeight, tail);
28511
+ const radius = (this._private__textOptions.borderRadius ?? 0) * horizontalPixelRatio;
28283
28512
  // Draw background
28284
- const bgOpacity = this._private__textOptions.backgroundOpacity || 0.8;
28513
+ const bgOpacity = this._private__textOptions.backgroundOpacity ?? 0.8;
28285
28514
  const bgColor = this._private__textOptions.backgroundColor || '#2962FF';
28286
- ctx.fillStyle = this._private__hexToRgba(bgColor, bgOpacity);
28287
- ctx.fillRect(x, y, boxWidth, boxHeight);
28515
+ if (bgOpacity > 0) {
28516
+ ctx.fillStyle = this._private__hexToRgba(bgColor, bgOpacity);
28517
+ this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
28518
+ ctx.fill();
28519
+ }
28288
28520
  // Draw border
28289
28521
  if (this._private__isSelected || this._private__isHovered) {
28290
28522
  ctx.strokeStyle = this._private__textOptions.borderColor ?? '#FFFFFF';
28291
28523
  ctx.lineWidth = (this._private__textOptions.borderWidth || 1) * horizontalPixelRatio * (this._private__isSelected ? 2 : 1);
28292
- ctx.strokeRect(x, y, boxWidth, boxHeight);
28524
+ this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
28525
+ ctx.stroke();
28293
28526
  }
28294
28527
  // Draw text
28295
28528
  ctx.fillStyle = this._private__textOptions.textColor || '#FFFFFF';
28296
28529
  for (let i = 0; i < lines.length; i++) {
28297
28530
  const line = lines[i];
28298
- let textX = x + padding;
28531
+ let textX = left + padding;
28299
28532
  // Handle text alignment
28300
28533
  if (this._private__textOptions.textAlign === 'center') {
28301
28534
  const metrics = ctx.measureText(line);
28302
- textX = x + (boxWidth - metrics.width) / 2;
28535
+ textX = left + (boxWidth - metrics.width) / 2;
28303
28536
  }
28304
28537
  else if (this._private__textOptions.textAlign === 'right') {
28305
28538
  const metrics = ctx.measureText(line);
28306
- textX = x + boxWidth - metrics.width - padding;
28539
+ textX = left + boxWidth - metrics.width - padding;
28307
28540
  }
28308
- const textY = y + padding + i * lineHeight;
28541
+ const textY = top + padding + i * lineHeight;
28309
28542
  ctx.fillText(line, textX, textY);
28310
28543
  }
28311
28544
  // Draw anchor point if selected
@@ -28349,6 +28582,9 @@ class TextTool extends DrawingToolBase {
28349
28582
  borderWidth: this._private__textOptions.borderWidth,
28350
28583
  textAlign: this._private__textOptions.textAlign,
28351
28584
  maxWidth: this._private__textOptions.maxWidth,
28585
+ anchor: this._private__textOptions.anchor,
28586
+ pointer: this._private__textOptions.pointer,
28587
+ borderRadius: this._private__textOptions.borderRadius,
28352
28588
  },
28353
28589
  extra: {
28354
28590
  text: this._private__textOptions.text,
@@ -28356,6 +28592,7 @@ class TextTool extends DrawingToolBase {
28356
28592
  };
28357
28593
  }
28358
28594
  remove() {
28595
+ this._private__placement?._internal_stop();
28359
28596
  // Clean up click handler
28360
28597
  if (this._private__clickOutsideHandler) {
28361
28598
  this._chart.unsubscribeClick(this._private__clickOutsideHandler);
@@ -29214,6 +29451,7 @@ class PositionTool extends DrawingToolBase {
29214
29451
  this._private__paneView = null;
29215
29452
  this._private__isSelected = false;
29216
29453
  this._private__isHovered = false;
29454
+ this._private__placement = null;
29217
29455
  this._private__isDragging = false;
29218
29456
  this._private__dragTarget = null;
29219
29457
  this._private__grab = null;
@@ -29236,6 +29474,32 @@ class PositionTool extends DrawingToolBase {
29236
29474
  // Create primitive and setup
29237
29475
  this._private__createPrimitive();
29238
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 };
29239
29503
  }
29240
29504
  _private__createPrimitive() {
29241
29505
  // Create and cache the paneView once
@@ -29267,6 +29531,9 @@ class PositionTool extends DrawingToolBase {
29267
29531
  return new PositionPaneView(this);
29268
29532
  }
29269
29533
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29534
+ if (!this.isComplete()) {
29535
+ return;
29536
+ }
29270
29537
  // Draw profit rectangle (green)
29271
29538
  if (this._private__profitRect && this._private__positionOptions.takeProfit) {
29272
29539
  const timeScale = this._chart.timeScale();
@@ -29300,16 +29567,9 @@ class PositionTool extends DrawingToolBase {
29300
29567
  }
29301
29568
  }
29302
29569
  _private__drawRectangle(ctx, rect, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29303
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29304
- // Use endTime if specified, otherwise extend to chart edge
29305
- let endX;
29306
- if (this._private__positionOptions.endTime) {
29307
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29308
- }
29309
- else {
29310
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29311
- endX = chartWidth;
29312
- }
29570
+ const span = this._private__span();
29571
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29572
+ const endX = span._internal_endX * horizontalPixelRatio;
29313
29573
  const y1 = (this._series.priceToCoordinate(rect._internal_price1) ?? 0) * verticalPixelRatio;
29314
29574
  const y2 = (this._series.priceToCoordinate(rect._internal_price2) ?? 0) * verticalPixelRatio;
29315
29575
  // Draw filled rectangle with semi-transparent fill
@@ -29324,16 +29584,10 @@ class PositionTool extends DrawingToolBase {
29324
29584
  ctx.stroke();
29325
29585
  }
29326
29586
  _private__drawEntryLine(ctx, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29327
- 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;
29328
29590
  const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
29329
- let endX;
29330
- if (this._private__positionOptions.endTime) {
29331
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29332
- }
29333
- else {
29334
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29335
- endX = chartWidth;
29336
- }
29337
29591
  // Draw horizontal entry line (divider)
29338
29592
  const lineColor = this._private__positionOptions.entryLineColor;
29339
29593
  ctx.strokeStyle = lineColor;
@@ -29447,9 +29701,11 @@ class PositionTool extends DrawingToolBase {
29447
29701
  const padding = 6 * horizontalPixelRatio;
29448
29702
  const labelWidth = textMetrics.width + padding * 2;
29449
29703
  const labelHeight = 20 * horizontalPixelRatio;
29450
- // 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;
29451
29707
  const labelX = entryX + 8 * horizontalPixelRatio;
29452
- const labelY = y - labelHeight / 2;
29708
+ const labelY = y <= entryY ? y - labelHeight - gap : y + gap;
29453
29709
  // Draw label background
29454
29710
  ctx.fillStyle = rect._internal_color;
29455
29711
  ctx.fillRect(labelX, labelY, labelWidth, labelHeight);
@@ -29457,10 +29713,12 @@ class PositionTool extends DrawingToolBase {
29457
29713
  ctx.fillStyle = '#ffffff';
29458
29714
  ctx.textAlign = 'left';
29459
29715
  ctx.textBaseline = 'middle';
29460
- ctx.fillText(labelText, labelX + padding, y);
29716
+ ctx.fillText(labelText, labelX + padding, labelY + labelHeight / 2);
29461
29717
  }
29462
29718
  _private__drawEndpoint(ctx, price, color, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29463
- 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;
29464
29722
  const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
29465
29723
  // Draw circle endpoint at entry
29466
29724
  const circleColor = color;
@@ -29472,14 +29730,6 @@ class PositionTool extends DrawingToolBase {
29472
29730
  ctx.fill();
29473
29731
  ctx.stroke();
29474
29732
  // Draw square endpoint at the right edge
29475
- let endX;
29476
- if (this._private__positionOptions.endTime) {
29477
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29478
- }
29479
- else {
29480
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29481
- endX = chartWidth - 10 * horizontalPixelRatio;
29482
- }
29483
29733
  const size = 8 * horizontalPixelRatio;
29484
29734
  const squareColor = color;
29485
29735
  ctx.fillStyle = squareColor;
@@ -29600,18 +29850,12 @@ class PositionTool extends DrawingToolBase {
29600
29850
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
29601
29851
  }
29602
29852
  hitTest(x, y) {
29603
- if (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit) {
29853
+ if (!this.isComplete() || (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit)) {
29604
29854
  return false;
29605
29855
  }
29606
- const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29607
- let endX;
29608
- if (this._private__positionOptions.endTime) {
29609
- endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
29610
- }
29611
- else {
29612
- const chartWidth = this._chart.chartElement().clientWidth;
29613
- endX = chartWidth;
29614
- }
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);
29615
29859
  // Check if inside the entire Position Tool area (rectangles)
29616
29860
  if (this._private__positionOptions.takeProfit && this._private__positionOptions.stopLoss) {
29617
29861
  const tpY = this._series.priceToCoordinate(this._private__positionOptions.takeProfit) ?? 0;
@@ -29619,7 +29863,7 @@ class PositionTool extends DrawingToolBase {
29619
29863
  const minY = Math.min(tpY, slY);
29620
29864
  const maxY = Math.max(tpY, slY);
29621
29865
  // Check if inside the rectangle bounds (entire tool area)
29622
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29866
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29623
29867
  return true;
29624
29868
  }
29625
29869
  // Also check entry line area
@@ -29631,15 +29875,12 @@ class PositionTool extends DrawingToolBase {
29631
29875
  return false;
29632
29876
  }
29633
29877
  hitTestEndpoint(x, y) {
29634
- const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29635
- let endX;
29636
- if (this._private__positionOptions.endTime) {
29637
- endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
29638
- }
29639
- else {
29640
- const chartWidth = this._chart.chartElement().clientWidth;
29641
- endX = chartWidth;
29878
+ if (!this.isComplete()) {
29879
+ return null;
29642
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);
29643
29884
  const tolerance = 10;
29644
29885
  // Check TP left endpoint (circle)
29645
29886
  if (this._private__positionOptions.takeProfit) {
@@ -29676,7 +29917,7 @@ class PositionTool extends DrawingToolBase {
29676
29917
  // Check entry line (middle divider line) - higher priority than 'move'
29677
29918
  const entryY = this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0;
29678
29919
  const lineTolerance = 8;
29679
- if (x >= entryX && x <= endX && Math.abs(y - entryY) <= lineTolerance) {
29920
+ if (x >= left && x <= right && Math.abs(y - entryY) <= lineTolerance) {
29680
29921
  return 'entry';
29681
29922
  }
29682
29923
  // Check if clicking on the body (rectangles) for moving entire tool
@@ -29686,7 +29927,7 @@ class PositionTool extends DrawingToolBase {
29686
29927
  const minY = Math.min(tpY, slY);
29687
29928
  const maxY = Math.max(tpY, slY);
29688
29929
  // Check if inside the rectangle bounds
29689
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29930
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29690
29931
  return 'move';
29691
29932
  }
29692
29933
  }
@@ -29786,6 +30027,7 @@ class PositionTool extends DrawingToolBase {
29786
30027
  this._private__requestUpdate();
29787
30028
  }
29788
30029
  remove() {
30030
+ this._private__placement?._internal_stop();
29789
30031
  if (this._private__primitive) {
29790
30032
  this._series.detachPrimitive(this._private__primitive);
29791
30033
  this._private__primitive = null;
@@ -29832,6 +30074,7 @@ class GanttPaneView {
29832
30074
  }
29833
30075
  }
29834
30076
 
30077
+ const TOP_PADDING = 50;
29835
30078
  class GanttTool extends DrawingToolBase {
29836
30079
  constructor(context, options) {
29837
30080
  super(context, 'gantt', options);
@@ -29845,6 +30088,7 @@ class GanttTool extends DrawingToolBase {
29845
30088
  this._private__grab = null;
29846
30089
  this._private__moveStart = null;
29847
30090
  this._private__hoveredSegmentIndex = -1;
30091
+ this._private__placement = null;
29848
30092
  this._private__ganttOptions = {
29849
30093
  segments: [],
29850
30094
  rowHeight: 40,
@@ -29857,6 +30101,43 @@ class GanttTool extends DrawingToolBase {
29857
30101
  };
29858
30102
  this._registerOptions(this._private__ganttOptions);
29859
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;
29860
30141
  }
29861
30142
  _private__createPrimitive() {
29862
30143
  this._private__paneView = this._private__createPaneView();
@@ -29887,11 +30168,10 @@ class GanttTool extends DrawingToolBase {
29887
30168
  return new GanttPaneView(this);
29888
30169
  }
29889
30170
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29890
- 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)
29891
30172
  return;
29892
30173
  // Position gantt at top of chart with padding
29893
- const topPadding = 50; // pixels from top
29894
- const baseY = topPadding * verticalPixelRatio;
30174
+ const baseY = TOP_PADDING * verticalPixelRatio;
29895
30175
  const rowHeight = this._private__ganttOptions.rowHeight * verticalPixelRatio;
29896
30176
  const rowSpacing = this._private__ganttOptions.rowSpacing * verticalPixelRatio;
29897
30177
  // Draw grid lines if enabled
@@ -30070,11 +30350,10 @@ class GanttTool extends DrawingToolBase {
30070
30350
  this._private__moveStart = null;
30071
30351
  }
30072
30352
  hitTest(x, y) {
30073
- 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) {
30074
30354
  return false;
30075
30355
  }
30076
- const topPadding = 50;
30077
- const baseY = topPadding;
30356
+ const baseY = TOP_PADDING;
30078
30357
  const rowHeight = this._private__ganttOptions.rowHeight;
30079
30358
  const rowSpacing = this._private__ganttOptions.rowSpacing;
30080
30359
  // Check each segment
@@ -30128,6 +30407,7 @@ class GanttTool extends DrawingToolBase {
30128
30407
  this._private__requestUpdate();
30129
30408
  }
30130
30409
  remove() {
30410
+ this._private__placement?._internal_stop();
30131
30411
  if (this._private__primitive) {
30132
30412
  this._series.detachPrimitive(this._private__primitive);
30133
30413
  this._private__primitive = null;
@@ -30263,6 +30543,7 @@ class DrawingToolsManager {
30263
30543
  }
30264
30544
  this._private__completionCheck = setTimeout(() => {
30265
30545
  this._private__completionCheck = null;
30546
+ const completed = [];
30266
30547
  for (const id of Array.from(this._private__pendingToolIds)) {
30267
30548
  const tool = this._private__tools.get(id);
30268
30549
  if (!tool) {
@@ -30271,9 +30552,15 @@ class DrawingToolsManager {
30271
30552
  }
30272
30553
  if (tool.isComplete()) {
30273
30554
  this._private__pendingToolIds.delete(id);
30274
- this._private__drawingCompleted._internal_fire(id);
30555
+ completed.push(id);
30275
30556
  }
30276
30557
  }
30558
+ if (completed.length > 0) {
30559
+ this.recordChange();
30560
+ }
30561
+ for (const id of completed) {
30562
+ this._private__drawingCompleted._internal_fire(id);
30563
+ }
30277
30564
  }, 0);
30278
30565
  }
30279
30566
  /**
@@ -30333,12 +30620,16 @@ class DrawingToolsManager {
30333
30620
  if (this._private__restoring) {
30334
30621
  return;
30335
30622
  }
30623
+ const next = this.serializeAll();
30624
+ if (JSON.stringify(next) === JSON.stringify(this._private__committed)) {
30625
+ return;
30626
+ }
30336
30627
  this._private__past.push(this._private__committed);
30337
30628
  if (this._private__past.length > UNDO_HISTORY_LIMIT) {
30338
30629
  this._private__past.shift();
30339
30630
  }
30340
30631
  this._private__future = [];
30341
- this._private__committed = this.serializeAll();
30632
+ this._private__committed = next;
30342
30633
  }
30343
30634
  canUndo() {
30344
30635
  return this._private__past.length > 0;
@@ -30394,11 +30685,25 @@ class DrawingToolsManager {
30394
30685
  const tool = this._private__tools.get(this._private__selectedToolId);
30395
30686
  return tool ? this._private__boundsOf(tool) : null;
30396
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
+ }
30397
30702
  /**
30398
30703
  * Bounds come from the tool's own serialized points, so this works for every
30399
30704
  * tool without each one having to report a box.
30400
30705
  */
30401
- _private__boundsOf(tool) {
30706
+ _private__boxFromPoints(tool) {
30402
30707
  const series = tool.series();
30403
30708
  const points = tool.serialize().points;
30404
30709
  let left = Number.POSITIVE_INFINITY;
@@ -30421,13 +30726,7 @@ class DrawingToolsManager {
30421
30726
  if (!found) {
30422
30727
  return null;
30423
30728
  }
30424
- const offset = this._private__paneOffsetOf(tool);
30425
- return {
30426
- left: left + offset._internal_x,
30427
- right: right + offset._internal_x,
30428
- top: top + offset._internal_y,
30429
- bottom: bottom + offset._internal_y,
30430
- };
30729
+ return { left, top, right, bottom };
30431
30730
  }
30432
30731
  /** Panes below the first one are offset inside the chart element. */
30433
30732
  _private__paneOffsetOf(tool) {
@@ -30511,7 +30810,10 @@ class DrawingToolsManager {
30511
30810
  serializeAll() {
30512
30811
  const result = [];
30513
30812
  for (const tool of this._private__tools.values()) {
30514
- 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
+ }
30515
30817
  }
30516
30818
  return result;
30517
30819
  }
@@ -31136,7 +31438,7 @@ const customSeriesDefaultOptions = {
31136
31438
  * Returns the current version as a string. For example `'1.0.0'`.
31137
31439
  */
31138
31440
  function version() {
31139
- return "2.3.12";
31441
+ return "2.3.14";
31140
31442
  }
31141
31443
 
31142
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 };