@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)
@@ -14281,6 +14281,8 @@ function shapeSize(shape, originalSize) {
14281
14281
  return size(originalSize, 0.7);
14282
14282
  case 'tag':
14283
14283
  return size(originalSize, 1);
14284
+ case 'xcross':
14285
+ return size(originalSize, 0.7);
14284
14286
  }
14285
14287
  }
14286
14288
  function calculateShapeHeight(barSpacing) {
@@ -14481,6 +14483,27 @@ function hitTestText(textX, textY, textWidth, textHeight, x, y) {
14481
14483
  y >= textY - halfHeight && y <= textY + halfHeight;
14482
14484
  }
14483
14485
 
14486
+ function drawXCross(ctx, coords, size) {
14487
+ const crossSize = shapeSize('xcross', size);
14488
+ const halfSize = ((crossSize - 1) / 2) * coords._internal_pixelRatio;
14489
+ ctx.save();
14490
+ ctx.strokeStyle = ctx.fillStyle;
14491
+ ctx.lineWidth = Math.max(1, Math.round(1.5 * coords._internal_pixelRatio));
14492
+ ctx.lineCap = 'round';
14493
+ ctx.beginPath();
14494
+ ctx.moveTo(coords._internal_x - halfSize, coords._internal_y - halfSize);
14495
+ ctx.lineTo(coords._internal_x + halfSize, coords._internal_y + halfSize);
14496
+ ctx.moveTo(coords._internal_x + halfSize, coords._internal_y - halfSize);
14497
+ ctx.lineTo(coords._internal_x - halfSize, coords._internal_y + halfSize);
14498
+ ctx.stroke();
14499
+ ctx.restore();
14500
+ }
14501
+ function hitTestXCross(centerX, centerY, size, x, y) {
14502
+ const crossSize = shapeSize('xcross', size);
14503
+ const tolerance = 2 + crossSize / 2;
14504
+ return Math.abs(x - centerX) <= tolerance && Math.abs(y - centerY) <= tolerance;
14505
+ }
14506
+
14484
14507
  class SeriesMarkersRenderer {
14485
14508
  constructor() {
14486
14509
  this._private__data = null;
@@ -14624,6 +14647,9 @@ function drawShape(item, ctx, coordinates) {
14624
14647
  case 'square':
14625
14648
  drawSquare(ctx, coordinates, item._internal_size);
14626
14649
  return;
14650
+ case 'xcross':
14651
+ drawXCross(ctx, coordinates, item._internal_size);
14652
+ return;
14627
14653
  case 'tag':
14628
14654
  // Handled by drawTagItem, which needs both pixel ratios and the text.
14629
14655
  return;
@@ -14652,6 +14678,8 @@ function hitTestShape(item, x, y) {
14652
14678
  return hitTestCircle(item._internal_x, item._internal_y, item._internal_size, x, y);
14653
14679
  case 'square':
14654
14680
  return hitTestSquare(item._internal_x, item._internal_y, item._internal_size, x, y);
14681
+ case 'xcross':
14682
+ return hitTestXCross(item._internal_x, item._internal_y, item._internal_size, x, y);
14655
14683
  case 'tag':
14656
14684
  return false;
14657
14685
  }
@@ -21220,6 +21248,13 @@ class DrawingToolBase {
21220
21248
  series() {
21221
21249
  return this._series;
21222
21250
  }
21251
+ /**
21252
+ * Box the selection toolbar anchors to, in pane pixels. Null falls back to
21253
+ * the serialized points, which cannot place a line across the whole pane.
21254
+ */
21255
+ selectionBox() {
21256
+ return null;
21257
+ }
21223
21258
  getState() {
21224
21259
  return this._state;
21225
21260
  }
@@ -21896,6 +21931,53 @@ function createTrendLineTool(context, options) {
21896
21931
  return new TrendLineTool(context, options);
21897
21932
  }
21898
21933
 
21934
+ const END_HANDLE_MARGIN = 10;
21935
+ /**
21936
+ * Hands the next `clicks` clicks on the chart to `onClick`, past the last bar
21937
+ * too. A press that grabbed another drawing does not count.
21938
+ */
21939
+ function placeByClicks(chart, series, clicks, onClick) {
21940
+ let placed = 0;
21941
+ let handler = null;
21942
+ const stop = () => {
21943
+ if (handler !== null) {
21944
+ chart.unsubscribeClick(handler);
21945
+ handler = null;
21946
+ }
21947
+ };
21948
+ handler = (param) => {
21949
+ if (!param.point || consumeInputClaim(chart)) {
21950
+ return;
21951
+ }
21952
+ const price = series.coordinateToPrice(param.point.y);
21953
+ const time = timeFromCoordinate(chart, series, param.point.x);
21954
+ if (price === null || time === null) {
21955
+ return;
21956
+ }
21957
+ placed++;
21958
+ if (placed >= clicks) {
21959
+ stop();
21960
+ }
21961
+ onClick({ _internal_time: time, _internal_price: price, _internal_x: param.point.x, _internal_y: param.point.y }, placed - 1);
21962
+ };
21963
+ chart.subscribeClick(handler);
21964
+ return {
21965
+ _internal_pending: () => handler !== null,
21966
+ _internal_stop: stop,
21967
+ };
21968
+ }
21969
+ /** A fifth of the pane, and never less than a bar. */
21970
+ function defaultWidth(chart) {
21971
+ const timeScale = chart.timeScale();
21972
+ return Math.max(timeScale.width() / 5, timeScale.options().barSpacing);
21973
+ }
21974
+ /** Kept inside the pane: past it, the handles on the end hide under the price axis. */
21975
+ function placedEndX(chart, x) {
21976
+ const timeScale = chart.timeScale();
21977
+ const onScreen = Math.min(x + defaultWidth(chart), timeScale.width() - END_HANDLE_MARGIN);
21978
+ return Math.max(onScreen, x + timeScale.options().barSpacing);
21979
+ }
21980
+
21899
21981
  class HorizontalLinePaneRenderer {
21900
21982
  constructor(tool) {
21901
21983
  this._private__tool = tool;
@@ -21936,6 +22018,7 @@ class HorizontalLineTool extends DrawingToolBase {
21936
22018
  this._private__isDragging = false;
21937
22019
  this._private__grab = null;
21938
22020
  this._private__moveStartPrice = 0;
22021
+ this._private__placement = null;
21939
22022
  this._private__hLineOptions = {
21940
22023
  lineColor: '#2962ff',
21941
22024
  lineWidth: 2,
@@ -21948,6 +22031,26 @@ class HorizontalLineTool extends DrawingToolBase {
21948
22031
  };
21949
22032
  this._registerOptions(this._private__hLineOptions);
21950
22033
  this._private__createPrimitive();
22034
+ if (this._private__hLineOptions.interactive) {
22035
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
22036
+ }
22037
+ }
22038
+ _private__placeAt(point) {
22039
+ this._private__hLineOptions.price = point._internal_price;
22040
+ if (this._private__hLineOptions.startTime !== undefined) {
22041
+ this._private__hLineOptions.startTime = point._internal_time;
22042
+ }
22043
+ this._private__requestUpdate();
22044
+ }
22045
+ isComplete() {
22046
+ return !this._private__placement?._internal_pending();
22047
+ }
22048
+ selectionBox() {
22049
+ const y = this._private__hLineOptions.price === undefined ? null : this._series.priceToCoordinate(this._private__hLineOptions.price);
22050
+ if (y === null) {
22051
+ return null;
22052
+ }
22053
+ return { left: this._private__startX(), right: this._chart.timeScale().width(), top: y, bottom: y };
21951
22054
  }
21952
22055
  _private__createPrimitive() {
21953
22056
  this._private__paneView = this._private__createPaneView();
@@ -21985,7 +22088,7 @@ class HorizontalLineTool extends DrawingToolBase {
21985
22088
  return coordinateFromTime(this._chart, this._series, this._private__hLineOptions.startTime) ?? 0;
21986
22089
  }
21987
22090
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
21988
- if (this._private__hLineOptions.price === undefined)
22091
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined)
21989
22092
  return;
21990
22093
  const y = (this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0) * verticalPixelRatio;
21991
22094
  const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
@@ -22079,7 +22182,7 @@ class HorizontalLineTool extends DrawingToolBase {
22079
22182
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
22080
22183
  }
22081
22184
  hitTest(x, y) {
22082
- if (this._private__hLineOptions.price === undefined) {
22185
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined) {
22083
22186
  return false;
22084
22187
  }
22085
22188
  const lineY = this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0;
@@ -22154,6 +22257,7 @@ class HorizontalLineTool extends DrawingToolBase {
22154
22257
  this._private__requestUpdate();
22155
22258
  }
22156
22259
  remove() {
22260
+ this._private__placement?._internal_stop();
22157
22261
  if (this._private__primitive) {
22158
22262
  this._series.detachPrimitive(this._private__primitive);
22159
22263
  this._private__primitive = null;
@@ -22213,6 +22317,7 @@ class CrossLineTool extends DrawingToolBase {
22213
22317
  this._private__grab = null;
22214
22318
  this._private__moveStartTime = 0;
22215
22319
  this._private__moveStartPrice = 0;
22320
+ this._private__placement = null;
22216
22321
  this._private__crossOptions = {
22217
22322
  lineColor: '#787b86',
22218
22323
  lineWidth: 1,
@@ -22222,6 +22327,16 @@ class CrossLineTool extends DrawingToolBase {
22222
22327
  };
22223
22328
  this._registerOptions(this._private__crossOptions);
22224
22329
  this._private__createPrimitive();
22330
+ if (this._private__crossOptions.interactive) {
22331
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
22332
+ this._private__crossOptions.time = point._internal_time;
22333
+ this._private__crossOptions.price = point._internal_price;
22334
+ this._private__requestUpdate();
22335
+ });
22336
+ }
22337
+ }
22338
+ isComplete() {
22339
+ return !this._private__placement?._internal_pending();
22225
22340
  }
22226
22341
  _private__createPrimitive() {
22227
22342
  this._private__paneView = new CrossLinePaneView(this);
@@ -22249,7 +22364,7 @@ class CrossLineTool extends DrawingToolBase {
22249
22364
  this._private__requestUpdate();
22250
22365
  }
22251
22366
  _private__coordinates() {
22252
- if (this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
22367
+ if (!this.isComplete() || this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
22253
22368
  return null;
22254
22369
  }
22255
22370
  const x = coordinateFromTime(this._chart, this._series, this._private__crossOptions.time);
@@ -22372,6 +22487,7 @@ class CrossLineTool extends DrawingToolBase {
22372
22487
  return this._private__isHovered;
22373
22488
  }
22374
22489
  remove() {
22490
+ this._private__placement?._internal_stop();
22375
22491
  if (this._private__primitive) {
22376
22492
  try {
22377
22493
  this._series.detachPrimitive(this._private__primitive);
@@ -23794,6 +23910,7 @@ class VerticalLineTool extends DrawingToolBase {
23794
23910
  this._private__isDragging = false;
23795
23911
  this._private__grab = null;
23796
23912
  this._private__moveStartTime = 0;
23913
+ this._private__placement = null;
23797
23914
  this._private__vLineOptions = {
23798
23915
  lineColor: '#787b86',
23799
23916
  lineWidth: 1,
@@ -23805,6 +23922,29 @@ class VerticalLineTool extends DrawingToolBase {
23805
23922
  };
23806
23923
  this._registerOptions(this._private__vLineOptions);
23807
23924
  this._private__createPrimitive();
23925
+ if (this._private__vLineOptions.interactive) {
23926
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
23927
+ this._private__vLineOptions.time = point._internal_time;
23928
+ this._private__requestUpdate();
23929
+ });
23930
+ }
23931
+ }
23932
+ isComplete() {
23933
+ return !this._private__placement?._internal_pending();
23934
+ }
23935
+ selectionBox() {
23936
+ const x = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time);
23937
+ if (x === null) {
23938
+ return null;
23939
+ }
23940
+ let middle;
23941
+ try {
23942
+ middle = this._series.getPane().getHeight() / 2;
23943
+ }
23944
+ catch (error) {
23945
+ return null;
23946
+ }
23947
+ return { left: x, right: x, top: middle, bottom: middle };
23808
23948
  }
23809
23949
  _private__createPrimitive() {
23810
23950
  this._private__paneView = this._private__createPaneView();
@@ -23835,7 +23975,7 @@ class VerticalLineTool extends DrawingToolBase {
23835
23975
  return new VerticalLinePaneView(this);
23836
23976
  }
23837
23977
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
23838
- if (this._private__vLineOptions.time === undefined)
23978
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined)
23839
23979
  return;
23840
23980
  const x = (coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0) * horizontalPixelRatio;
23841
23981
  const chartHeight = this._chart.chartElement().clientHeight * verticalPixelRatio;
@@ -23922,7 +24062,7 @@ class VerticalLineTool extends DrawingToolBase {
23922
24062
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
23923
24063
  }
23924
24064
  hitTest(x, y) {
23925
- if (this._private__vLineOptions.time === undefined) {
24065
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined) {
23926
24066
  return false;
23927
24067
  }
23928
24068
  const lineX = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0;
@@ -23992,6 +24132,7 @@ class VerticalLineTool extends DrawingToolBase {
23992
24132
  this._private__requestUpdate();
23993
24133
  }
23994
24134
  remove() {
24135
+ this._private__placement?._internal_stop();
23995
24136
  if (this._private__primitive) {
23996
24137
  this._series.detachPrimitive(this._private__primitive);
23997
24138
  this._private__primitive = null;
@@ -24713,6 +24854,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
24713
24854
  this._private__isHovered = false;
24714
24855
  this._private__isDragging = false;
24715
24856
  this._private__dragTarget = null;
24857
+ this._private__placement = null;
24716
24858
  this._private__fibOptions = {
24717
24859
  levels: [
24718
24860
  { level: 0, color: '#787B86', label: '0%' },
@@ -24728,6 +24870,20 @@ class FibonacciExtensionTool extends DrawingToolBase {
24728
24870
  ...options,
24729
24871
  };
24730
24872
  this._private__createPrimitive();
24873
+ if (this._private__fibOptions.interactive) {
24874
+ this._private__placement = placeByClicks(this._chart, this._series, 3, (point, index) => {
24875
+ const placed = { time: point._internal_time, price: point._internal_price };
24876
+ if (index === 0) {
24877
+ this.setPoint1(placed);
24878
+ }
24879
+ else if (index === 1) {
24880
+ this.setPoint2(placed);
24881
+ }
24882
+ else {
24883
+ this.setPoint3(placed);
24884
+ }
24885
+ });
24886
+ }
24731
24887
  }
24732
24888
  _private__createPrimitive() {
24733
24889
  this._private__paneView = this._private__createPaneView();
@@ -24785,7 +24941,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
24785
24941
  this._private__requestUpdate();
24786
24942
  }
24787
24943
  isComplete() {
24788
- return !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
24944
+ return !this._private__placement?._internal_pending() && !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
24789
24945
  }
24790
24946
  setColor(color) {
24791
24947
  this._private__fibOptions.lineColor = color;
@@ -25080,6 +25236,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
25080
25236
  this._private__requestUpdate();
25081
25237
  }
25082
25238
  remove() {
25239
+ this._private__placement?._internal_stop();
25083
25240
  if (this._private__primitive) {
25084
25241
  this._series.detachPrimitive(this._private__primitive);
25085
25242
  this._private__primitive = null;
@@ -25731,17 +25888,19 @@ class RectangleTool extends DrawingToolBase {
25731
25888
  // Draw border
25732
25889
  const borderColor = this._private__rectOptions.borderColor;
25733
25890
  const borderWidth = this._private__isSelected ? this._private__rectOptions.borderWidth + 1 : this._private__rectOptions.borderWidth;
25734
- ctx.strokeStyle = borderColor;
25735
- ctx.lineWidth = borderWidth * horizontalPixelRatio;
25736
- // Set border style
25737
- if (this._private__rectOptions.borderStyle === 'dashed') {
25738
- ctx.setLineDash([10 * horizontalPixelRatio, 5 * horizontalPixelRatio]);
25739
- }
25740
- else if (this._private__rectOptions.borderStyle === 'dotted') {
25741
- ctx.setLineDash([2 * horizontalPixelRatio, 3 * horizontalPixelRatio]);
25891
+ if (borderWidth > 0) {
25892
+ ctx.strokeStyle = borderColor;
25893
+ ctx.lineWidth = borderWidth * horizontalPixelRatio;
25894
+ // Set border style
25895
+ if (this._private__rectOptions.borderStyle === 'dashed') {
25896
+ ctx.setLineDash([10 * horizontalPixelRatio, 5 * horizontalPixelRatio]);
25897
+ }
25898
+ else if (this._private__rectOptions.borderStyle === 'dotted') {
25899
+ ctx.setLineDash([2 * horizontalPixelRatio, 3 * horizontalPixelRatio]);
25900
+ }
25901
+ ctx.strokeRect(minX, minY, width, height);
25902
+ ctx.setLineDash([]);
25742
25903
  }
25743
- ctx.strokeRect(minX, minY, width, height);
25744
- ctx.setLineDash([]);
25745
25904
  // Draw corner handles if selected
25746
25905
  if ((this._private__isSelected || this._private__isHovered) && !this._private__isDrawing && !this.isLocked()) {
25747
25906
  this._private__drawCornerHandles(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, borderColor);
@@ -25769,17 +25928,20 @@ class RectangleTool extends DrawingToolBase {
25769
25928
  }
25770
25929
  _private__drawLabel(ctx, minX, minY, maxX, maxY, horizontalPixelRatio, verticalPixelRatio, color) {
25771
25930
  const padding = 6 * horizontalPixelRatio;
25772
- const fontSize = 12 * horizontalPixelRatio;
25931
+ const fontSize = (this._private__rectOptions.labelFontSize ?? 12) * horizontalPixelRatio;
25773
25932
  ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif`;
25774
25933
  const textMetrics = ctx.measureText(this._private__rectOptions.labelText);
25775
25934
  const textWidth = textMetrics.width;
25776
25935
  const textHeight = fontSize;
25936
+ const middleY = (minY + maxY) / 2 - textHeight / 2 - padding;
25937
+ // A box extended far past the pane keeps right-hand and centred labels on screen.
25938
+ const visibleMaxX = Math.min(maxX, ctx.canvas.width);
25777
25939
  // Calculate label position based on labelPosition option
25778
25940
  let labelX = minX + padding;
25779
25941
  let labelY = minY + padding;
25780
25942
  switch (this._private__rectOptions.labelPosition) {
25781
25943
  case 'top-right':
25782
- labelX = maxX - textWidth - padding * 2;
25944
+ labelX = visibleMaxX - textWidth - padding * 2;
25783
25945
  labelY = minY + padding;
25784
25946
  break;
25785
25947
  case 'bottom-left':
@@ -25787,19 +25949,27 @@ class RectangleTool extends DrawingToolBase {
25787
25949
  labelY = maxY - textHeight - padding * 2;
25788
25950
  break;
25789
25951
  case 'bottom-right':
25790
- labelX = maxX - textWidth - padding * 2;
25952
+ labelX = visibleMaxX - textWidth - padding * 2;
25791
25953
  labelY = maxY - textHeight - padding * 2;
25792
25954
  break;
25793
25955
  case 'center':
25794
- labelX = (minX + maxX) / 2 - textWidth / 2 - padding;
25795
- labelY = (minY + maxY) / 2 - textHeight / 2 - padding;
25956
+ labelX = (Math.max(minX, 0) + visibleMaxX) / 2 - textWidth / 2 - padding;
25957
+ labelY = middleY;
25958
+ break;
25959
+ case 'left':
25960
+ labelX = minX + padding;
25961
+ labelY = middleY;
25962
+ break;
25963
+ case 'right':
25964
+ labelX = visibleMaxX - textWidth - padding * 2;
25965
+ labelY = middleY;
25796
25966
  break;
25797
25967
  }
25798
25968
  // Draw background
25799
- ctx.fillStyle = color;
25969
+ ctx.fillStyle = this._private__rectOptions.labelBackgroundColor ?? color;
25800
25970
  ctx.fillRect(labelX, labelY, textWidth + padding * 2, textHeight + padding * 2);
25801
25971
  // Draw text
25802
- ctx.fillStyle = '#ffffff';
25972
+ ctx.fillStyle = this._private__rectOptions.labelColor ?? '#ffffff';
25803
25973
  ctx.textBaseline = 'top';
25804
25974
  ctx.fillText(this._private__rectOptions.labelText, labelX + padding, labelY + padding);
25805
25975
  }
@@ -26037,6 +26207,9 @@ class RectangleTool extends DrawingToolBase {
26037
26207
  showLabel: this._private__rectOptions.showLabel,
26038
26208
  labelText: this._private__rectOptions.labelText,
26039
26209
  labelPosition: this._private__rectOptions.labelPosition,
26210
+ labelColor: this._private__rectOptions.labelColor,
26211
+ labelBackgroundColor: this._private__rectOptions.labelBackgroundColor,
26212
+ labelFontSize: this._private__rectOptions.labelFontSize,
26040
26213
  },
26041
26214
  };
26042
26215
  }
@@ -27629,6 +27802,7 @@ class TextTool extends DrawingToolBase {
27629
27802
  this._private__moveStart = null;
27630
27803
  this._private__isEditing = false;
27631
27804
  this._private__clickOutsideHandler = null;
27805
+ this._private__placement = null;
27632
27806
  this._private__textOptions = {
27633
27807
  text: 'Double-click to edit',
27634
27808
  fontSize: 14,
@@ -27641,11 +27815,22 @@ class TextTool extends DrawingToolBase {
27641
27815
  borderWidth: 1,
27642
27816
  textAlign: 'left',
27643
27817
  maxWidth: 200,
27818
+ anchor: 'top-left',
27819
+ pointer: false,
27820
+ borderRadius: 0,
27644
27821
  ...options,
27645
27822
  };
27646
27823
  this._registerOptions(this._private__textOptions);
27647
27824
  this._private__createPrimitive();
27648
27825
  this._private__setupClickOutsideHandler();
27826
+ if (this._private__textOptions.interactive) {
27827
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
27828
+ this.setPosition(point._internal_time, point._internal_price);
27829
+ if (this._private__textOptions.onComplete) {
27830
+ this._private__textOptions.onComplete();
27831
+ }
27832
+ });
27833
+ }
27649
27834
  }
27650
27835
  _private__setupClickOutsideHandler() {
27651
27836
  // Set up click handler to finish editing when clicking outside
@@ -27731,7 +27916,7 @@ class TextTool extends DrawingToolBase {
27731
27916
  this._private__requestUpdate();
27732
27917
  }
27733
27918
  isComplete() {
27734
- return !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
27919
+ return !this._private__placement?._internal_pending() && !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
27735
27920
  }
27736
27921
  setColor(color) {
27737
27922
  this._private__textOptions.textColor = color;
@@ -27792,18 +27977,59 @@ class TextTool extends DrawingToolBase {
27792
27977
  const py = this._series.priceToCoordinate(price) ?? 0;
27793
27978
  // Calculate text box dimensions
27794
27979
  const fontSize = this._private__textOptions.fontSize || 14;
27795
- const padding = this._private__textOptions.padding || 8;
27980
+ const padding = this._private__textOptions.padding ?? 8;
27796
27981
  const text = this._private__textOptions.text || '';
27797
27982
  // Rough estimate of text width (will be more accurate in canvas)
27798
27983
  const charWidth = fontSize * 0.6;
27799
27984
  const textWidth = Math.min(text.length * charWidth, this._private__textOptions.maxWidth || 200);
27800
- const textHeight = fontSize + padding * 2;
27801
- // Check if click is within text box bounds
27802
- const left = px;
27803
- const right = px + textWidth + padding * 2;
27804
- const top = py;
27805
- const bottom = py + textHeight;
27806
- return x >= left && x <= right && y >= top && y <= bottom;
27985
+ const boxWidth = textWidth + padding * 2;
27986
+ const boxHeight = fontSize + padding * 2;
27987
+ const tail = this._private__tailSize(boxHeight);
27988
+ const box = this._private__boxOrigin(px, py, boxWidth, boxHeight, tail);
27989
+ const left = box._internal_left - (this._private__textOptions.anchor === 'left' ? tail : 0);
27990
+ const right = box._internal_left + boxWidth + (this._private__textOptions.anchor === 'right' ? tail : 0);
27991
+ return x >= left && x <= right && y >= box._internal_top && y <= box._internal_top + boxHeight;
27992
+ }
27993
+ _private__tailSize(boxHeight) {
27994
+ const anchor = this._private__textOptions.anchor;
27995
+ return this._private__textOptions.pointer && (anchor === 'left' || anchor === 'right') ? Math.round(boxHeight * 0.35) : 0;
27996
+ }
27997
+ _private__boxOrigin(x, y, width, height, tail) {
27998
+ switch (this._private__textOptions.anchor) {
27999
+ case 'left':
28000
+ return { _internal_left: x + tail, _internal_top: y - height / 2 };
28001
+ case 'right':
28002
+ return { _internal_left: x - tail - width, _internal_top: y - height / 2 };
28003
+ case 'center':
28004
+ return { _internal_left: x - width / 2, _internal_top: y - height / 2 };
28005
+ default:
28006
+ return { _internal_left: x, _internal_top: y };
28007
+ }
28008
+ }
28009
+ // eslint-disable-next-line max-params
28010
+ _private__traceBox(ctx, left, top, width, height, tail, anchorX, anchorY, radius) {
28011
+ const r = Math.max(0, Math.min(radius, width / 2, height / 2));
28012
+ ctx.beginPath();
28013
+ ctx.moveTo(left + r, top);
28014
+ ctx.lineTo(left + width - r, top);
28015
+ ctx.arcTo(left + width, top, left + width, top + r, r);
28016
+ if (tail > 0 && this._private__textOptions.anchor === 'right') {
28017
+ ctx.lineTo(left + width, anchorY - tail);
28018
+ ctx.lineTo(anchorX, anchorY);
28019
+ ctx.lineTo(left + width, anchorY + tail);
28020
+ }
28021
+ ctx.lineTo(left + width, top + height - r);
28022
+ ctx.arcTo(left + width, top + height, left + width - r, top + height, r);
28023
+ ctx.lineTo(left + r, top + height);
28024
+ ctx.arcTo(left, top + height, left, top + height - r, r);
28025
+ if (tail > 0 && this._private__textOptions.anchor === 'left') {
28026
+ ctx.lineTo(left, anchorY + tail);
28027
+ ctx.lineTo(anchorX, anchorY);
28028
+ ctx.lineTo(left, anchorY - tail);
28029
+ }
28030
+ ctx.lineTo(left, top + r);
28031
+ ctx.arcTo(left, top, left + r, top, r);
28032
+ ctx.closePath();
27807
28033
  }
27808
28034
  hitTestEndpoint(x, y) {
27809
28035
  if (!this.isComplete())
@@ -27855,7 +28081,7 @@ class TextTool extends DrawingToolBase {
27855
28081
  const x = (coordinateFromTime(this._chart, this._series, time) ?? 0) * horizontalPixelRatio;
27856
28082
  const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
27857
28083
  const fontSize = (this._private__textOptions.fontSize || 14) * horizontalPixelRatio;
27858
- const padding = (this._private__textOptions.padding || 8) * horizontalPixelRatio;
28084
+ const padding = (this._private__textOptions.padding ?? 8) * horizontalPixelRatio;
27859
28085
  const maxWidth = (this._private__textOptions.maxWidth || 200) * horizontalPixelRatio;
27860
28086
  // Set font
27861
28087
  ctx.font = `${fontSize}px ${this._private__textOptions.fontFamily || 'Arial'}`;
@@ -27887,32 +28113,39 @@ class TextTool extends DrawingToolBase {
27887
28113
  const boxWidth = maxLineWidth + padding * 2;
27888
28114
  const lineHeight = fontSize * 1.2;
27889
28115
  const boxHeight = lines.length * lineHeight + padding * 2;
28116
+ const tail = this._private__tailSize(boxHeight);
28117
+ const { _internal_left: left, _internal_top: top } = this._private__boxOrigin(x, y, boxWidth, boxHeight, tail);
28118
+ const radius = (this._private__textOptions.borderRadius ?? 0) * horizontalPixelRatio;
27890
28119
  // Draw background
27891
- const bgOpacity = this._private__textOptions.backgroundOpacity || 0.8;
28120
+ const bgOpacity = this._private__textOptions.backgroundOpacity ?? 0.8;
27892
28121
  const bgColor = this._private__textOptions.backgroundColor || '#2962FF';
27893
- ctx.fillStyle = this._private__hexToRgba(bgColor, bgOpacity);
27894
- ctx.fillRect(x, y, boxWidth, boxHeight);
28122
+ if (bgOpacity > 0) {
28123
+ ctx.fillStyle = this._private__hexToRgba(bgColor, bgOpacity);
28124
+ this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
28125
+ ctx.fill();
28126
+ }
27895
28127
  // Draw border
27896
28128
  if (this._private__isSelected || this._private__isHovered) {
27897
28129
  ctx.strokeStyle = this._private__textOptions.borderColor ?? '#FFFFFF';
27898
28130
  ctx.lineWidth = (this._private__textOptions.borderWidth || 1) * horizontalPixelRatio * (this._private__isSelected ? 2 : 1);
27899
- ctx.strokeRect(x, y, boxWidth, boxHeight);
28131
+ this._private__traceBox(ctx, left, top, boxWidth, boxHeight, tail, x, y, radius);
28132
+ ctx.stroke();
27900
28133
  }
27901
28134
  // Draw text
27902
28135
  ctx.fillStyle = this._private__textOptions.textColor || '#FFFFFF';
27903
28136
  for (let i = 0; i < lines.length; i++) {
27904
28137
  const line = lines[i];
27905
- let textX = x + padding;
28138
+ let textX = left + padding;
27906
28139
  // Handle text alignment
27907
28140
  if (this._private__textOptions.textAlign === 'center') {
27908
28141
  const metrics = ctx.measureText(line);
27909
- textX = x + (boxWidth - metrics.width) / 2;
28142
+ textX = left + (boxWidth - metrics.width) / 2;
27910
28143
  }
27911
28144
  else if (this._private__textOptions.textAlign === 'right') {
27912
28145
  const metrics = ctx.measureText(line);
27913
- textX = x + boxWidth - metrics.width - padding;
28146
+ textX = left + boxWidth - metrics.width - padding;
27914
28147
  }
27915
- const textY = y + padding + i * lineHeight;
28148
+ const textY = top + padding + i * lineHeight;
27916
28149
  ctx.fillText(line, textX, textY);
27917
28150
  }
27918
28151
  // Draw anchor point if selected
@@ -27956,6 +28189,9 @@ class TextTool extends DrawingToolBase {
27956
28189
  borderWidth: this._private__textOptions.borderWidth,
27957
28190
  textAlign: this._private__textOptions.textAlign,
27958
28191
  maxWidth: this._private__textOptions.maxWidth,
28192
+ anchor: this._private__textOptions.anchor,
28193
+ pointer: this._private__textOptions.pointer,
28194
+ borderRadius: this._private__textOptions.borderRadius,
27959
28195
  },
27960
28196
  extra: {
27961
28197
  text: this._private__textOptions.text,
@@ -27963,6 +28199,7 @@ class TextTool extends DrawingToolBase {
27963
28199
  };
27964
28200
  }
27965
28201
  remove() {
28202
+ this._private__placement?._internal_stop();
27966
28203
  // Clean up click handler
27967
28204
  if (this._private__clickOutsideHandler) {
27968
28205
  this._chart.unsubscribeClick(this._private__clickOutsideHandler);
@@ -28821,6 +29058,7 @@ class PositionTool extends DrawingToolBase {
28821
29058
  this._private__paneView = null;
28822
29059
  this._private__isSelected = false;
28823
29060
  this._private__isHovered = false;
29061
+ this._private__placement = null;
28824
29062
  this._private__isDragging = false;
28825
29063
  this._private__dragTarget = null;
28826
29064
  this._private__grab = null;
@@ -28843,6 +29081,32 @@ class PositionTool extends DrawingToolBase {
28843
29081
  // Create primitive and setup
28844
29082
  this._private__createPrimitive();
28845
29083
  this._private__updateMetrics();
29084
+ if (this._private__positionOptions.interactive) {
29085
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
29086
+ }
29087
+ }
29088
+ _private__placeAt(point) {
29089
+ const options = this._private__positionOptions;
29090
+ const shift = point._internal_price - options.entryPrice;
29091
+ options.entryPrice = point._internal_price;
29092
+ options.entryTime = point._internal_time;
29093
+ if (options.stopLoss !== undefined) {
29094
+ options.stopLoss += shift;
29095
+ }
29096
+ if (options.takeProfit !== undefined) {
29097
+ options.takeProfit += shift;
29098
+ }
29099
+ options.endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? undefined;
29100
+ this._private__updateMetrics();
29101
+ this._private__requestUpdate();
29102
+ }
29103
+ isComplete() {
29104
+ return !this._private__placement?._internal_pending();
29105
+ }
29106
+ _private__span() {
29107
+ const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29108
+ const endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? entryX + defaultWidth(this._chart);
29109
+ return { _internal_entryX: entryX, _internal_endX: endX };
28846
29110
  }
28847
29111
  _private__createPrimitive() {
28848
29112
  // Create and cache the paneView once
@@ -28874,6 +29138,9 @@ class PositionTool extends DrawingToolBase {
28874
29138
  return new PositionPaneView(this);
28875
29139
  }
28876
29140
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29141
+ if (!this.isComplete()) {
29142
+ return;
29143
+ }
28877
29144
  // Draw profit rectangle (green)
28878
29145
  if (this._private__profitRect && this._private__positionOptions.takeProfit) {
28879
29146
  const timeScale = this._chart.timeScale();
@@ -28907,16 +29174,9 @@ class PositionTool extends DrawingToolBase {
28907
29174
  }
28908
29175
  }
28909
29176
  _private__drawRectangle(ctx, rect, horizontalPixelRatio, verticalPixelRatio, timeScale) {
28910
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
28911
- // Use endTime if specified, otherwise extend to chart edge
28912
- let endX;
28913
- if (this._private__positionOptions.endTime) {
28914
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
28915
- }
28916
- else {
28917
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
28918
- endX = chartWidth;
28919
- }
29177
+ const span = this._private__span();
29178
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29179
+ const endX = span._internal_endX * horizontalPixelRatio;
28920
29180
  const y1 = (this._series.priceToCoordinate(rect._internal_price1) ?? 0) * verticalPixelRatio;
28921
29181
  const y2 = (this._series.priceToCoordinate(rect._internal_price2) ?? 0) * verticalPixelRatio;
28922
29182
  // Draw filled rectangle with semi-transparent fill
@@ -28931,16 +29191,10 @@ class PositionTool extends DrawingToolBase {
28931
29191
  ctx.stroke();
28932
29192
  }
28933
29193
  _private__drawEntryLine(ctx, horizontalPixelRatio, verticalPixelRatio, timeScale) {
28934
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29194
+ const span = this._private__span();
29195
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29196
+ const endX = span._internal_endX * horizontalPixelRatio;
28935
29197
  const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
28936
- let endX;
28937
- if (this._private__positionOptions.endTime) {
28938
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
28939
- }
28940
- else {
28941
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
28942
- endX = chartWidth;
28943
- }
28944
29198
  // Draw horizontal entry line (divider)
28945
29199
  const lineColor = this._private__positionOptions.entryLineColor;
28946
29200
  ctx.strokeStyle = lineColor;
@@ -29054,9 +29308,11 @@ class PositionTool extends DrawingToolBase {
29054
29308
  const padding = 6 * horizontalPixelRatio;
29055
29309
  const labelWidth = textMetrics.width + padding * 2;
29056
29310
  const labelHeight = 20 * horizontalPixelRatio;
29057
- // Position label at the start of the line
29311
+ // Outside the box, so a label wider than it does not cover the handles on its edge
29312
+ const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
29313
+ const gap = 3 * verticalPixelRatio;
29058
29314
  const labelX = entryX + 8 * horizontalPixelRatio;
29059
- const labelY = y - labelHeight / 2;
29315
+ const labelY = y <= entryY ? y - labelHeight - gap : y + gap;
29060
29316
  // Draw label background
29061
29317
  ctx.fillStyle = rect._internal_color;
29062
29318
  ctx.fillRect(labelX, labelY, labelWidth, labelHeight);
@@ -29064,10 +29320,12 @@ class PositionTool extends DrawingToolBase {
29064
29320
  ctx.fillStyle = '#ffffff';
29065
29321
  ctx.textAlign = 'left';
29066
29322
  ctx.textBaseline = 'middle';
29067
- ctx.fillText(labelText, labelX + padding, y);
29323
+ ctx.fillText(labelText, labelX + padding, labelY + labelHeight / 2);
29068
29324
  }
29069
29325
  _private__drawEndpoint(ctx, price, color, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29070
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29326
+ const span = this._private__span();
29327
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29328
+ const endX = span._internal_endX * horizontalPixelRatio;
29071
29329
  const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
29072
29330
  // Draw circle endpoint at entry
29073
29331
  const circleColor = color;
@@ -29079,14 +29337,6 @@ class PositionTool extends DrawingToolBase {
29079
29337
  ctx.fill();
29080
29338
  ctx.stroke();
29081
29339
  // Draw square endpoint at the right edge
29082
- let endX;
29083
- if (this._private__positionOptions.endTime) {
29084
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29085
- }
29086
- else {
29087
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29088
- endX = chartWidth - 10 * horizontalPixelRatio;
29089
- }
29090
29340
  const size = 8 * horizontalPixelRatio;
29091
29341
  const squareColor = color;
29092
29342
  ctx.fillStyle = squareColor;
@@ -29207,18 +29457,12 @@ class PositionTool extends DrawingToolBase {
29207
29457
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
29208
29458
  }
29209
29459
  hitTest(x, y) {
29210
- if (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit) {
29460
+ if (!this.isComplete() || (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit)) {
29211
29461
  return false;
29212
29462
  }
29213
- const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29214
- let endX;
29215
- if (this._private__positionOptions.endTime) {
29216
- endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
29217
- }
29218
- else {
29219
- const chartWidth = this._chart.chartElement().clientWidth;
29220
- endX = chartWidth;
29221
- }
29463
+ const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
29464
+ const left = Math.min(entryX, endX);
29465
+ const right = Math.max(entryX, endX);
29222
29466
  // Check if inside the entire Position Tool area (rectangles)
29223
29467
  if (this._private__positionOptions.takeProfit && this._private__positionOptions.stopLoss) {
29224
29468
  const tpY = this._series.priceToCoordinate(this._private__positionOptions.takeProfit) ?? 0;
@@ -29226,7 +29470,7 @@ class PositionTool extends DrawingToolBase {
29226
29470
  const minY = Math.min(tpY, slY);
29227
29471
  const maxY = Math.max(tpY, slY);
29228
29472
  // Check if inside the rectangle bounds (entire tool area)
29229
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29473
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29230
29474
  return true;
29231
29475
  }
29232
29476
  // Also check entry line area
@@ -29238,15 +29482,12 @@ class PositionTool extends DrawingToolBase {
29238
29482
  return false;
29239
29483
  }
29240
29484
  hitTestEndpoint(x, y) {
29241
- const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29242
- let endX;
29243
- if (this._private__positionOptions.endTime) {
29244
- endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
29245
- }
29246
- else {
29247
- const chartWidth = this._chart.chartElement().clientWidth;
29248
- endX = chartWidth;
29485
+ if (!this.isComplete()) {
29486
+ return null;
29249
29487
  }
29488
+ const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
29489
+ const left = Math.min(entryX, endX);
29490
+ const right = Math.max(entryX, endX);
29250
29491
  const tolerance = 10;
29251
29492
  // Check TP left endpoint (circle)
29252
29493
  if (this._private__positionOptions.takeProfit) {
@@ -29283,7 +29524,7 @@ class PositionTool extends DrawingToolBase {
29283
29524
  // Check entry line (middle divider line) - higher priority than 'move'
29284
29525
  const entryY = this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0;
29285
29526
  const lineTolerance = 8;
29286
- if (x >= entryX && x <= endX && Math.abs(y - entryY) <= lineTolerance) {
29527
+ if (x >= left && x <= right && Math.abs(y - entryY) <= lineTolerance) {
29287
29528
  return 'entry';
29288
29529
  }
29289
29530
  // Check if clicking on the body (rectangles) for moving entire tool
@@ -29293,7 +29534,7 @@ class PositionTool extends DrawingToolBase {
29293
29534
  const minY = Math.min(tpY, slY);
29294
29535
  const maxY = Math.max(tpY, slY);
29295
29536
  // Check if inside the rectangle bounds
29296
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29537
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29297
29538
  return 'move';
29298
29539
  }
29299
29540
  }
@@ -29393,6 +29634,7 @@ class PositionTool extends DrawingToolBase {
29393
29634
  this._private__requestUpdate();
29394
29635
  }
29395
29636
  remove() {
29637
+ this._private__placement?._internal_stop();
29396
29638
  if (this._private__primitive) {
29397
29639
  this._series.detachPrimitive(this._private__primitive);
29398
29640
  this._private__primitive = null;
@@ -29439,6 +29681,7 @@ class GanttPaneView {
29439
29681
  }
29440
29682
  }
29441
29683
 
29684
+ const TOP_PADDING = 50;
29442
29685
  class GanttTool extends DrawingToolBase {
29443
29686
  constructor(context, options) {
29444
29687
  super(context, 'gantt', options);
@@ -29452,6 +29695,7 @@ class GanttTool extends DrawingToolBase {
29452
29695
  this._private__grab = null;
29453
29696
  this._private__moveStart = null;
29454
29697
  this._private__hoveredSegmentIndex = -1;
29698
+ this._private__placement = null;
29455
29699
  this._private__ganttOptions = {
29456
29700
  segments: [],
29457
29701
  rowHeight: 40,
@@ -29464,6 +29708,43 @@ class GanttTool extends DrawingToolBase {
29464
29708
  };
29465
29709
  this._registerOptions(this._private__ganttOptions);
29466
29710
  this._private__createPrimitive();
29711
+ if (this._private__ganttOptions.interactive) {
29712
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
29713
+ }
29714
+ }
29715
+ _private__placeAt(point) {
29716
+ const segments = this._private__ganttOptions.segments ?? [];
29717
+ const endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? point._internal_time;
29718
+ const rowPitch = this._private__ganttOptions.rowHeight + this._private__ganttOptions.rowSpacing;
29719
+ const row = Math.max(0, Math.floor((point._internal_y - TOP_PADDING) / rowPitch));
29720
+ const shift = segments.length > 0 ? row - segments[0].level : 0;
29721
+ for (const segment of segments) {
29722
+ segment.startTime = point._internal_time;
29723
+ segment.endTime = endTime;
29724
+ segment.level = Math.max(0, segment.level + shift);
29725
+ }
29726
+ this._private__requestUpdate();
29727
+ }
29728
+ isComplete() {
29729
+ return !this._private__placement?._internal_pending();
29730
+ }
29731
+ selectionBox() {
29732
+ const segments = this._private__ganttOptions.segments ?? [];
29733
+ if (segments.length === 0) {
29734
+ return null;
29735
+ }
29736
+ const rowHeight = this._private__ganttOptions.rowHeight;
29737
+ const pitch = rowHeight + this._private__ganttOptions.rowSpacing;
29738
+ // Typed as the public shape: an inferred one gets its keys renamed by the build
29739
+ const box = { left: Infinity, top: Infinity, right: -Infinity, bottom: -Infinity };
29740
+ for (const segment of segments) {
29741
+ const top = TOP_PADDING + segment.level * pitch;
29742
+ box.left = Math.min(box.left, coordinateFromTime(this._chart, this._series, segment.startTime) ?? 0);
29743
+ box.right = Math.max(box.right, coordinateFromTime(this._chart, this._series, segment.endTime) ?? 0);
29744
+ box.top = Math.min(box.top, top);
29745
+ box.bottom = Math.max(box.bottom, top + rowHeight);
29746
+ }
29747
+ return box;
29467
29748
  }
29468
29749
  _private__createPrimitive() {
29469
29750
  this._private__paneView = this._private__createPaneView();
@@ -29494,11 +29775,10 @@ class GanttTool extends DrawingToolBase {
29494
29775
  return new GanttPaneView(this);
29495
29776
  }
29496
29777
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29497
- if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
29778
+ if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
29498
29779
  return;
29499
29780
  // Position gantt at top of chart with padding
29500
- const topPadding = 50; // pixels from top
29501
- const baseY = topPadding * verticalPixelRatio;
29781
+ const baseY = TOP_PADDING * verticalPixelRatio;
29502
29782
  const rowHeight = this._private__ganttOptions.rowHeight * verticalPixelRatio;
29503
29783
  const rowSpacing = this._private__ganttOptions.rowSpacing * verticalPixelRatio;
29504
29784
  // Draw grid lines if enabled
@@ -29677,11 +29957,10 @@ class GanttTool extends DrawingToolBase {
29677
29957
  this._private__moveStart = null;
29678
29958
  }
29679
29959
  hitTest(x, y) {
29680
- if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
29960
+ if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
29681
29961
  return false;
29682
29962
  }
29683
- const topPadding = 50;
29684
- const baseY = topPadding;
29963
+ const baseY = TOP_PADDING;
29685
29964
  const rowHeight = this._private__ganttOptions.rowHeight;
29686
29965
  const rowSpacing = this._private__ganttOptions.rowSpacing;
29687
29966
  // Check each segment
@@ -29735,6 +30014,7 @@ class GanttTool extends DrawingToolBase {
29735
30014
  this._private__requestUpdate();
29736
30015
  }
29737
30016
  remove() {
30017
+ this._private__placement?._internal_stop();
29738
30018
  if (this._private__primitive) {
29739
30019
  this._series.detachPrimitive(this._private__primitive);
29740
30020
  this._private__primitive = null;
@@ -29870,6 +30150,7 @@ class DrawingToolsManager {
29870
30150
  }
29871
30151
  this._private__completionCheck = setTimeout(() => {
29872
30152
  this._private__completionCheck = null;
30153
+ const completed = [];
29873
30154
  for (const id of Array.from(this._private__pendingToolIds)) {
29874
30155
  const tool = this._private__tools.get(id);
29875
30156
  if (!tool) {
@@ -29878,9 +30159,15 @@ class DrawingToolsManager {
29878
30159
  }
29879
30160
  if (tool.isComplete()) {
29880
30161
  this._private__pendingToolIds.delete(id);
29881
- this._private__drawingCompleted._internal_fire(id);
30162
+ completed.push(id);
29882
30163
  }
29883
30164
  }
30165
+ if (completed.length > 0) {
30166
+ this.recordChange();
30167
+ }
30168
+ for (const id of completed) {
30169
+ this._private__drawingCompleted._internal_fire(id);
30170
+ }
29884
30171
  }, 0);
29885
30172
  }
29886
30173
  /**
@@ -29940,12 +30227,16 @@ class DrawingToolsManager {
29940
30227
  if (this._private__restoring) {
29941
30228
  return;
29942
30229
  }
30230
+ const next = this.serializeAll();
30231
+ if (JSON.stringify(next) === JSON.stringify(this._private__committed)) {
30232
+ return;
30233
+ }
29943
30234
  this._private__past.push(this._private__committed);
29944
30235
  if (this._private__past.length > UNDO_HISTORY_LIMIT) {
29945
30236
  this._private__past.shift();
29946
30237
  }
29947
30238
  this._private__future = [];
29948
- this._private__committed = this.serializeAll();
30239
+ this._private__committed = next;
29949
30240
  }
29950
30241
  canUndo() {
29951
30242
  return this._private__past.length > 0;
@@ -30001,11 +30292,25 @@ class DrawingToolsManager {
30001
30292
  const tool = this._private__tools.get(this._private__selectedToolId);
30002
30293
  return tool ? this._private__boundsOf(tool) : null;
30003
30294
  }
30295
+ /** Relative to the chart element; the tool's box is relative to its pane. */
30296
+ _private__boundsOf(tool) {
30297
+ const box = tool.selectionBox() ?? this._private__boxFromPoints(tool);
30298
+ if (box === null) {
30299
+ return null;
30300
+ }
30301
+ const offset = this._private__paneOffsetOf(tool);
30302
+ return {
30303
+ left: box.left + offset._internal_x,
30304
+ right: box.right + offset._internal_x,
30305
+ top: box.top + offset._internal_y,
30306
+ bottom: box.bottom + offset._internal_y,
30307
+ };
30308
+ }
30004
30309
  /**
30005
30310
  * Bounds come from the tool's own serialized points, so this works for every
30006
30311
  * tool without each one having to report a box.
30007
30312
  */
30008
- _private__boundsOf(tool) {
30313
+ _private__boxFromPoints(tool) {
30009
30314
  const series = tool.series();
30010
30315
  const points = tool.serialize().points;
30011
30316
  let left = Number.POSITIVE_INFINITY;
@@ -30028,13 +30333,7 @@ class DrawingToolsManager {
30028
30333
  if (!found) {
30029
30334
  return null;
30030
30335
  }
30031
- const offset = this._private__paneOffsetOf(tool);
30032
- return {
30033
- left: left + offset._internal_x,
30034
- right: right + offset._internal_x,
30035
- top: top + offset._internal_y,
30036
- bottom: bottom + offset._internal_y,
30037
- };
30336
+ return { left, top, right, bottom };
30038
30337
  }
30039
30338
  /** Panes below the first one are offset inside the chart element. */
30040
30339
  _private__paneOffsetOf(tool) {
@@ -30118,7 +30417,10 @@ class DrawingToolsManager {
30118
30417
  serializeAll() {
30119
30418
  const result = [];
30120
30419
  for (const tool of this._private__tools.values()) {
30121
- result.push(tool.serialize());
30420
+ // Not drawn yet: undo would bring it back as a finished drawing
30421
+ if (tool.isComplete()) {
30422
+ result.push(tool.serialize());
30423
+ }
30122
30424
  }
30123
30425
  return result;
30124
30426
  }
@@ -30743,7 +31045,7 @@ const customSeriesDefaultOptions = {
30743
31045
  * Returns the current version as a string. For example `'1.0.0'`.
30744
31046
  */
30745
31047
  function version() {
30746
- return "2.3.12";
31048
+ return "2.3.14";
30747
31049
  }
30748
31050
 
30749
31051
  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 };