@pipsend/charts 2.3.13 → 2.3.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  /*!
2
2
  * @license
3
- * Pipsend Charts v2.3.13-dev+202609280001
3
+ * Pipsend Charts v2.3.14
4
4
  * Copyright (c) 2026 Pipsend
5
5
  * Licensed under MIT License
6
6
  * Built on TradingView Lightweight Charts™ (Apache 2.0)
@@ -21248,6 +21248,13 @@ class DrawingToolBase {
21248
21248
  series() {
21249
21249
  return this._series;
21250
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
+ }
21251
21258
  getState() {
21252
21259
  return this._state;
21253
21260
  }
@@ -21924,6 +21931,53 @@ function createTrendLineTool(context, options) {
21924
21931
  return new TrendLineTool(context, options);
21925
21932
  }
21926
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
+
21927
21981
  class HorizontalLinePaneRenderer {
21928
21982
  constructor(tool) {
21929
21983
  this._private__tool = tool;
@@ -21964,6 +22018,7 @@ class HorizontalLineTool extends DrawingToolBase {
21964
22018
  this._private__isDragging = false;
21965
22019
  this._private__grab = null;
21966
22020
  this._private__moveStartPrice = 0;
22021
+ this._private__placement = null;
21967
22022
  this._private__hLineOptions = {
21968
22023
  lineColor: '#2962ff',
21969
22024
  lineWidth: 2,
@@ -21976,6 +22031,26 @@ class HorizontalLineTool extends DrawingToolBase {
21976
22031
  };
21977
22032
  this._registerOptions(this._private__hLineOptions);
21978
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 };
21979
22054
  }
21980
22055
  _private__createPrimitive() {
21981
22056
  this._private__paneView = this._private__createPaneView();
@@ -22013,7 +22088,7 @@ class HorizontalLineTool extends DrawingToolBase {
22013
22088
  return coordinateFromTime(this._chart, this._series, this._private__hLineOptions.startTime) ?? 0;
22014
22089
  }
22015
22090
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
22016
- if (this._private__hLineOptions.price === undefined)
22091
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined)
22017
22092
  return;
22018
22093
  const y = (this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0) * verticalPixelRatio;
22019
22094
  const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
@@ -22107,7 +22182,7 @@ class HorizontalLineTool extends DrawingToolBase {
22107
22182
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
22108
22183
  }
22109
22184
  hitTest(x, y) {
22110
- if (this._private__hLineOptions.price === undefined) {
22185
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined) {
22111
22186
  return false;
22112
22187
  }
22113
22188
  const lineY = this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0;
@@ -22182,6 +22257,7 @@ class HorizontalLineTool extends DrawingToolBase {
22182
22257
  this._private__requestUpdate();
22183
22258
  }
22184
22259
  remove() {
22260
+ this._private__placement?._internal_stop();
22185
22261
  if (this._private__primitive) {
22186
22262
  this._series.detachPrimitive(this._private__primitive);
22187
22263
  this._private__primitive = null;
@@ -22241,6 +22317,7 @@ class CrossLineTool extends DrawingToolBase {
22241
22317
  this._private__grab = null;
22242
22318
  this._private__moveStartTime = 0;
22243
22319
  this._private__moveStartPrice = 0;
22320
+ this._private__placement = null;
22244
22321
  this._private__crossOptions = {
22245
22322
  lineColor: '#787b86',
22246
22323
  lineWidth: 1,
@@ -22250,6 +22327,16 @@ class CrossLineTool extends DrawingToolBase {
22250
22327
  };
22251
22328
  this._registerOptions(this._private__crossOptions);
22252
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();
22253
22340
  }
22254
22341
  _private__createPrimitive() {
22255
22342
  this._private__paneView = new CrossLinePaneView(this);
@@ -22277,7 +22364,7 @@ class CrossLineTool extends DrawingToolBase {
22277
22364
  this._private__requestUpdate();
22278
22365
  }
22279
22366
  _private__coordinates() {
22280
- 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) {
22281
22368
  return null;
22282
22369
  }
22283
22370
  const x = coordinateFromTime(this._chart, this._series, this._private__crossOptions.time);
@@ -22400,6 +22487,7 @@ class CrossLineTool extends DrawingToolBase {
22400
22487
  return this._private__isHovered;
22401
22488
  }
22402
22489
  remove() {
22490
+ this._private__placement?._internal_stop();
22403
22491
  if (this._private__primitive) {
22404
22492
  try {
22405
22493
  this._series.detachPrimitive(this._private__primitive);
@@ -23822,6 +23910,7 @@ class VerticalLineTool extends DrawingToolBase {
23822
23910
  this._private__isDragging = false;
23823
23911
  this._private__grab = null;
23824
23912
  this._private__moveStartTime = 0;
23913
+ this._private__placement = null;
23825
23914
  this._private__vLineOptions = {
23826
23915
  lineColor: '#787b86',
23827
23916
  lineWidth: 1,
@@ -23833,6 +23922,29 @@ class VerticalLineTool extends DrawingToolBase {
23833
23922
  };
23834
23923
  this._registerOptions(this._private__vLineOptions);
23835
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 };
23836
23948
  }
23837
23949
  _private__createPrimitive() {
23838
23950
  this._private__paneView = this._private__createPaneView();
@@ -23863,7 +23975,7 @@ class VerticalLineTool extends DrawingToolBase {
23863
23975
  return new VerticalLinePaneView(this);
23864
23976
  }
23865
23977
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
23866
- if (this._private__vLineOptions.time === undefined)
23978
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined)
23867
23979
  return;
23868
23980
  const x = (coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0) * horizontalPixelRatio;
23869
23981
  const chartHeight = this._chart.chartElement().clientHeight * verticalPixelRatio;
@@ -23950,7 +24062,7 @@ class VerticalLineTool extends DrawingToolBase {
23950
24062
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
23951
24063
  }
23952
24064
  hitTest(x, y) {
23953
- if (this._private__vLineOptions.time === undefined) {
24065
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined) {
23954
24066
  return false;
23955
24067
  }
23956
24068
  const lineX = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0;
@@ -24020,6 +24132,7 @@ class VerticalLineTool extends DrawingToolBase {
24020
24132
  this._private__requestUpdate();
24021
24133
  }
24022
24134
  remove() {
24135
+ this._private__placement?._internal_stop();
24023
24136
  if (this._private__primitive) {
24024
24137
  this._series.detachPrimitive(this._private__primitive);
24025
24138
  this._private__primitive = null;
@@ -24741,6 +24854,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
24741
24854
  this._private__isHovered = false;
24742
24855
  this._private__isDragging = false;
24743
24856
  this._private__dragTarget = null;
24857
+ this._private__placement = null;
24744
24858
  this._private__fibOptions = {
24745
24859
  levels: [
24746
24860
  { level: 0, color: '#787B86', label: '0%' },
@@ -24756,6 +24870,20 @@ class FibonacciExtensionTool extends DrawingToolBase {
24756
24870
  ...options,
24757
24871
  };
24758
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
+ }
24759
24887
  }
24760
24888
  _private__createPrimitive() {
24761
24889
  this._private__paneView = this._private__createPaneView();
@@ -24813,7 +24941,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
24813
24941
  this._private__requestUpdate();
24814
24942
  }
24815
24943
  isComplete() {
24816
- 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);
24817
24945
  }
24818
24946
  setColor(color) {
24819
24947
  this._private__fibOptions.lineColor = color;
@@ -25108,6 +25236,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
25108
25236
  this._private__requestUpdate();
25109
25237
  }
25110
25238
  remove() {
25239
+ this._private__placement?._internal_stop();
25111
25240
  if (this._private__primitive) {
25112
25241
  this._series.detachPrimitive(this._private__primitive);
25113
25242
  this._private__primitive = null;
@@ -27673,6 +27802,7 @@ class TextTool extends DrawingToolBase {
27673
27802
  this._private__moveStart = null;
27674
27803
  this._private__isEditing = false;
27675
27804
  this._private__clickOutsideHandler = null;
27805
+ this._private__placement = null;
27676
27806
  this._private__textOptions = {
27677
27807
  text: 'Double-click to edit',
27678
27808
  fontSize: 14,
@@ -27693,6 +27823,14 @@ class TextTool extends DrawingToolBase {
27693
27823
  this._registerOptions(this._private__textOptions);
27694
27824
  this._private__createPrimitive();
27695
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
+ }
27696
27834
  }
27697
27835
  _private__setupClickOutsideHandler() {
27698
27836
  // Set up click handler to finish editing when clicking outside
@@ -27778,7 +27916,7 @@ class TextTool extends DrawingToolBase {
27778
27916
  this._private__requestUpdate();
27779
27917
  }
27780
27918
  isComplete() {
27781
- 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);
27782
27920
  }
27783
27921
  setColor(color) {
27784
27922
  this._private__textOptions.textColor = color;
@@ -28061,6 +28199,7 @@ class TextTool extends DrawingToolBase {
28061
28199
  };
28062
28200
  }
28063
28201
  remove() {
28202
+ this._private__placement?._internal_stop();
28064
28203
  // Clean up click handler
28065
28204
  if (this._private__clickOutsideHandler) {
28066
28205
  this._chart.unsubscribeClick(this._private__clickOutsideHandler);
@@ -28919,6 +29058,7 @@ class PositionTool extends DrawingToolBase {
28919
29058
  this._private__paneView = null;
28920
29059
  this._private__isSelected = false;
28921
29060
  this._private__isHovered = false;
29061
+ this._private__placement = null;
28922
29062
  this._private__isDragging = false;
28923
29063
  this._private__dragTarget = null;
28924
29064
  this._private__grab = null;
@@ -28941,6 +29081,32 @@ class PositionTool extends DrawingToolBase {
28941
29081
  // Create primitive and setup
28942
29082
  this._private__createPrimitive();
28943
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 };
28944
29110
  }
28945
29111
  _private__createPrimitive() {
28946
29112
  // Create and cache the paneView once
@@ -28972,6 +29138,9 @@ class PositionTool extends DrawingToolBase {
28972
29138
  return new PositionPaneView(this);
28973
29139
  }
28974
29140
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29141
+ if (!this.isComplete()) {
29142
+ return;
29143
+ }
28975
29144
  // Draw profit rectangle (green)
28976
29145
  if (this._private__profitRect && this._private__positionOptions.takeProfit) {
28977
29146
  const timeScale = this._chart.timeScale();
@@ -29005,16 +29174,9 @@ class PositionTool extends DrawingToolBase {
29005
29174
  }
29006
29175
  }
29007
29176
  _private__drawRectangle(ctx, rect, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29008
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29009
- // Use endTime if specified, otherwise extend to chart edge
29010
- let endX;
29011
- if (this._private__positionOptions.endTime) {
29012
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29013
- }
29014
- else {
29015
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29016
- endX = chartWidth;
29017
- }
29177
+ const span = this._private__span();
29178
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29179
+ const endX = span._internal_endX * horizontalPixelRatio;
29018
29180
  const y1 = (this._series.priceToCoordinate(rect._internal_price1) ?? 0) * verticalPixelRatio;
29019
29181
  const y2 = (this._series.priceToCoordinate(rect._internal_price2) ?? 0) * verticalPixelRatio;
29020
29182
  // Draw filled rectangle with semi-transparent fill
@@ -29029,16 +29191,10 @@ class PositionTool extends DrawingToolBase {
29029
29191
  ctx.stroke();
29030
29192
  }
29031
29193
  _private__drawEntryLine(ctx, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29032
- 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;
29033
29197
  const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
29034
- let endX;
29035
- if (this._private__positionOptions.endTime) {
29036
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29037
- }
29038
- else {
29039
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29040
- endX = chartWidth;
29041
- }
29042
29198
  // Draw horizontal entry line (divider)
29043
29199
  const lineColor = this._private__positionOptions.entryLineColor;
29044
29200
  ctx.strokeStyle = lineColor;
@@ -29152,9 +29308,11 @@ class PositionTool extends DrawingToolBase {
29152
29308
  const padding = 6 * horizontalPixelRatio;
29153
29309
  const labelWidth = textMetrics.width + padding * 2;
29154
29310
  const labelHeight = 20 * horizontalPixelRatio;
29155
- // 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;
29156
29314
  const labelX = entryX + 8 * horizontalPixelRatio;
29157
- const labelY = y - labelHeight / 2;
29315
+ const labelY = y <= entryY ? y - labelHeight - gap : y + gap;
29158
29316
  // Draw label background
29159
29317
  ctx.fillStyle = rect._internal_color;
29160
29318
  ctx.fillRect(labelX, labelY, labelWidth, labelHeight);
@@ -29162,10 +29320,12 @@ class PositionTool extends DrawingToolBase {
29162
29320
  ctx.fillStyle = '#ffffff';
29163
29321
  ctx.textAlign = 'left';
29164
29322
  ctx.textBaseline = 'middle';
29165
- ctx.fillText(labelText, labelX + padding, y);
29323
+ ctx.fillText(labelText, labelX + padding, labelY + labelHeight / 2);
29166
29324
  }
29167
29325
  _private__drawEndpoint(ctx, price, color, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29168
- 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;
29169
29329
  const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
29170
29330
  // Draw circle endpoint at entry
29171
29331
  const circleColor = color;
@@ -29177,14 +29337,6 @@ class PositionTool extends DrawingToolBase {
29177
29337
  ctx.fill();
29178
29338
  ctx.stroke();
29179
29339
  // Draw square endpoint at the right edge
29180
- let endX;
29181
- if (this._private__positionOptions.endTime) {
29182
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29183
- }
29184
- else {
29185
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29186
- endX = chartWidth - 10 * horizontalPixelRatio;
29187
- }
29188
29340
  const size = 8 * horizontalPixelRatio;
29189
29341
  const squareColor = color;
29190
29342
  ctx.fillStyle = squareColor;
@@ -29305,18 +29457,12 @@ class PositionTool extends DrawingToolBase {
29305
29457
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
29306
29458
  }
29307
29459
  hitTest(x, y) {
29308
- if (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit) {
29460
+ if (!this.isComplete() || (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit)) {
29309
29461
  return false;
29310
29462
  }
29311
- const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29312
- let endX;
29313
- if (this._private__positionOptions.endTime) {
29314
- endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
29315
- }
29316
- else {
29317
- const chartWidth = this._chart.chartElement().clientWidth;
29318
- endX = chartWidth;
29319
- }
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);
29320
29466
  // Check if inside the entire Position Tool area (rectangles)
29321
29467
  if (this._private__positionOptions.takeProfit && this._private__positionOptions.stopLoss) {
29322
29468
  const tpY = this._series.priceToCoordinate(this._private__positionOptions.takeProfit) ?? 0;
@@ -29324,7 +29470,7 @@ class PositionTool extends DrawingToolBase {
29324
29470
  const minY = Math.min(tpY, slY);
29325
29471
  const maxY = Math.max(tpY, slY);
29326
29472
  // Check if inside the rectangle bounds (entire tool area)
29327
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29473
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29328
29474
  return true;
29329
29475
  }
29330
29476
  // Also check entry line area
@@ -29336,15 +29482,12 @@ class PositionTool extends DrawingToolBase {
29336
29482
  return false;
29337
29483
  }
29338
29484
  hitTestEndpoint(x, y) {
29339
- const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29340
- let endX;
29341
- if (this._private__positionOptions.endTime) {
29342
- endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
29343
- }
29344
- else {
29345
- const chartWidth = this._chart.chartElement().clientWidth;
29346
- endX = chartWidth;
29485
+ if (!this.isComplete()) {
29486
+ return null;
29347
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);
29348
29491
  const tolerance = 10;
29349
29492
  // Check TP left endpoint (circle)
29350
29493
  if (this._private__positionOptions.takeProfit) {
@@ -29381,7 +29524,7 @@ class PositionTool extends DrawingToolBase {
29381
29524
  // Check entry line (middle divider line) - higher priority than 'move'
29382
29525
  const entryY = this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0;
29383
29526
  const lineTolerance = 8;
29384
- if (x >= entryX && x <= endX && Math.abs(y - entryY) <= lineTolerance) {
29527
+ if (x >= left && x <= right && Math.abs(y - entryY) <= lineTolerance) {
29385
29528
  return 'entry';
29386
29529
  }
29387
29530
  // Check if clicking on the body (rectangles) for moving entire tool
@@ -29391,7 +29534,7 @@ class PositionTool extends DrawingToolBase {
29391
29534
  const minY = Math.min(tpY, slY);
29392
29535
  const maxY = Math.max(tpY, slY);
29393
29536
  // Check if inside the rectangle bounds
29394
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29537
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29395
29538
  return 'move';
29396
29539
  }
29397
29540
  }
@@ -29491,6 +29634,7 @@ class PositionTool extends DrawingToolBase {
29491
29634
  this._private__requestUpdate();
29492
29635
  }
29493
29636
  remove() {
29637
+ this._private__placement?._internal_stop();
29494
29638
  if (this._private__primitive) {
29495
29639
  this._series.detachPrimitive(this._private__primitive);
29496
29640
  this._private__primitive = null;
@@ -29537,6 +29681,7 @@ class GanttPaneView {
29537
29681
  }
29538
29682
  }
29539
29683
 
29684
+ const TOP_PADDING = 50;
29540
29685
  class GanttTool extends DrawingToolBase {
29541
29686
  constructor(context, options) {
29542
29687
  super(context, 'gantt', options);
@@ -29550,6 +29695,7 @@ class GanttTool extends DrawingToolBase {
29550
29695
  this._private__grab = null;
29551
29696
  this._private__moveStart = null;
29552
29697
  this._private__hoveredSegmentIndex = -1;
29698
+ this._private__placement = null;
29553
29699
  this._private__ganttOptions = {
29554
29700
  segments: [],
29555
29701
  rowHeight: 40,
@@ -29562,6 +29708,43 @@ class GanttTool extends DrawingToolBase {
29562
29708
  };
29563
29709
  this._registerOptions(this._private__ganttOptions);
29564
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;
29565
29748
  }
29566
29749
  _private__createPrimitive() {
29567
29750
  this._private__paneView = this._private__createPaneView();
@@ -29592,11 +29775,10 @@ class GanttTool extends DrawingToolBase {
29592
29775
  return new GanttPaneView(this);
29593
29776
  }
29594
29777
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29595
- 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)
29596
29779
  return;
29597
29780
  // Position gantt at top of chart with padding
29598
- const topPadding = 50; // pixels from top
29599
- const baseY = topPadding * verticalPixelRatio;
29781
+ const baseY = TOP_PADDING * verticalPixelRatio;
29600
29782
  const rowHeight = this._private__ganttOptions.rowHeight * verticalPixelRatio;
29601
29783
  const rowSpacing = this._private__ganttOptions.rowSpacing * verticalPixelRatio;
29602
29784
  // Draw grid lines if enabled
@@ -29775,11 +29957,10 @@ class GanttTool extends DrawingToolBase {
29775
29957
  this._private__moveStart = null;
29776
29958
  }
29777
29959
  hitTest(x, y) {
29778
- 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) {
29779
29961
  return false;
29780
29962
  }
29781
- const topPadding = 50;
29782
- const baseY = topPadding;
29963
+ const baseY = TOP_PADDING;
29783
29964
  const rowHeight = this._private__ganttOptions.rowHeight;
29784
29965
  const rowSpacing = this._private__ganttOptions.rowSpacing;
29785
29966
  // Check each segment
@@ -29833,6 +30014,7 @@ class GanttTool extends DrawingToolBase {
29833
30014
  this._private__requestUpdate();
29834
30015
  }
29835
30016
  remove() {
30017
+ this._private__placement?._internal_stop();
29836
30018
  if (this._private__primitive) {
29837
30019
  this._series.detachPrimitive(this._private__primitive);
29838
30020
  this._private__primitive = null;
@@ -29968,6 +30150,7 @@ class DrawingToolsManager {
29968
30150
  }
29969
30151
  this._private__completionCheck = setTimeout(() => {
29970
30152
  this._private__completionCheck = null;
30153
+ const completed = [];
29971
30154
  for (const id of Array.from(this._private__pendingToolIds)) {
29972
30155
  const tool = this._private__tools.get(id);
29973
30156
  if (!tool) {
@@ -29976,9 +30159,15 @@ class DrawingToolsManager {
29976
30159
  }
29977
30160
  if (tool.isComplete()) {
29978
30161
  this._private__pendingToolIds.delete(id);
29979
- this._private__drawingCompleted._internal_fire(id);
30162
+ completed.push(id);
29980
30163
  }
29981
30164
  }
30165
+ if (completed.length > 0) {
30166
+ this.recordChange();
30167
+ }
30168
+ for (const id of completed) {
30169
+ this._private__drawingCompleted._internal_fire(id);
30170
+ }
29982
30171
  }, 0);
29983
30172
  }
29984
30173
  /**
@@ -30038,12 +30227,16 @@ class DrawingToolsManager {
30038
30227
  if (this._private__restoring) {
30039
30228
  return;
30040
30229
  }
30230
+ const next = this.serializeAll();
30231
+ if (JSON.stringify(next) === JSON.stringify(this._private__committed)) {
30232
+ return;
30233
+ }
30041
30234
  this._private__past.push(this._private__committed);
30042
30235
  if (this._private__past.length > UNDO_HISTORY_LIMIT) {
30043
30236
  this._private__past.shift();
30044
30237
  }
30045
30238
  this._private__future = [];
30046
- this._private__committed = this.serializeAll();
30239
+ this._private__committed = next;
30047
30240
  }
30048
30241
  canUndo() {
30049
30242
  return this._private__past.length > 0;
@@ -30099,11 +30292,25 @@ class DrawingToolsManager {
30099
30292
  const tool = this._private__tools.get(this._private__selectedToolId);
30100
30293
  return tool ? this._private__boundsOf(tool) : null;
30101
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
+ }
30102
30309
  /**
30103
30310
  * Bounds come from the tool's own serialized points, so this works for every
30104
30311
  * tool without each one having to report a box.
30105
30312
  */
30106
- _private__boundsOf(tool) {
30313
+ _private__boxFromPoints(tool) {
30107
30314
  const series = tool.series();
30108
30315
  const points = tool.serialize().points;
30109
30316
  let left = Number.POSITIVE_INFINITY;
@@ -30126,13 +30333,7 @@ class DrawingToolsManager {
30126
30333
  if (!found) {
30127
30334
  return null;
30128
30335
  }
30129
- const offset = this._private__paneOffsetOf(tool);
30130
- return {
30131
- left: left + offset._internal_x,
30132
- right: right + offset._internal_x,
30133
- top: top + offset._internal_y,
30134
- bottom: bottom + offset._internal_y,
30135
- };
30336
+ return { left, top, right, bottom };
30136
30337
  }
30137
30338
  /** Panes below the first one are offset inside the chart element. */
30138
30339
  _private__paneOffsetOf(tool) {
@@ -30216,7 +30417,10 @@ class DrawingToolsManager {
30216
30417
  serializeAll() {
30217
30418
  const result = [];
30218
30419
  for (const tool of this._private__tools.values()) {
30219
- 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
+ }
30220
30424
  }
30221
30425
  return result;
30222
30426
  }
@@ -30841,7 +31045,7 @@ const customSeriesDefaultOptions = {
30841
31045
  * Returns the current version as a string. For example `'1.0.0'`.
30842
31046
  */
30843
31047
  function version() {
30844
- return "2.3.13-dev+202609280001";
31048
+ return "2.3.14";
30845
31049
  }
30846
31050
 
30847
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 };