@pipsend/charts 2.3.13 → 2.3.15

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.15
4
4
  * Copyright (c) 2026 Pipsend
5
5
  * Licensed under MIT License
6
6
  * Built on TradingView Lightweight Charts™ (Apache 2.0)
@@ -14291,6 +14291,9 @@ function calculateShapeHeight(barSpacing) {
14291
14291
  function shapeMargin(barSpacing) {
14292
14292
  return Math.max(size(barSpacing, 0.1), 3 /* Constants.MinShapeMargin */);
14293
14293
  }
14294
+ function barGap(barSpacing) {
14295
+ return Math.max(shapeMargin(barSpacing), 10 /* Constants.MinBarGap */);
14296
+ }
14294
14297
  function calculateAdjustedMargin(margin, hasSide, hasInBar) {
14295
14298
  if (hasSide) {
14296
14299
  return margin;
@@ -14917,17 +14920,18 @@ class SeriesMarkersPaneView {
14917
14920
  }
14918
14921
  let prevTimeIndex = NaN;
14919
14922
  const shapeMargin$1 = shapeMargin(timeScale.options().barSpacing);
14923
+ const barGap$1 = barGap(timeScale.options().barSpacing);
14920
14924
  const offsets = {
14921
- _internal_aboveBar: shapeMargin$1,
14922
- _internal_belowBar: shapeMargin$1,
14925
+ _internal_aboveBar: barGap$1,
14926
+ _internal_belowBar: barGap$1,
14923
14927
  };
14924
14928
  this._private__data._internal_visibleRange = visibleTimedValues(this._private__data._internal_items, visibleBarsRange, true);
14925
14929
  for (let index = this._private__data._internal_visibleRange.from; index < this._private__data._internal_visibleRange.to; index++) {
14926
14930
  const marker = seriesMarkers[index];
14927
14931
  if (marker.time !== prevTimeIndex) {
14928
14932
  // new bar, reset stack counter
14929
- offsets._internal_aboveBar = shapeMargin$1;
14930
- offsets._internal_belowBar = shapeMargin$1;
14933
+ offsets._internal_aboveBar = barGap$1;
14934
+ offsets._internal_belowBar = barGap$1;
14931
14935
  prevTimeIndex = marker.time;
14932
14936
  }
14933
14937
  const rendererItem = this._private__data._internal_items[index];
@@ -15044,7 +15048,7 @@ class SeriesMarkersPrimitive {
15044
15048
  this._private__cachedBarSpacing = barSpacing;
15045
15049
  if (this._private__markers.length > 0) {
15046
15050
  const shapeMargin$1 = shapeMargin(barSpacing);
15047
- const marginValue = calculateShapeHeight(barSpacing) * 1.5 + shapeMargin$1 * 2;
15051
+ const marginValue = calculateShapeHeight(barSpacing) * 1.5 + shapeMargin$1 + barGap(barSpacing);
15048
15052
  const positions = this._private__getMarkerPositions();
15049
15053
  this._private__autoScaleMargins = {
15050
15054
  above: calculateAdjustedMargin(marginValue, positions.aboveBar, positions.inBar),
@@ -17547,10 +17551,13 @@ function applyHeikinAshi(series, chart, options = {}) {
17547
17551
  const blankSource = (blank) => {
17548
17552
  if (blank && restoreSourcePaint === null) {
17549
17553
  restoreSourcePaint = hideSeriesPaint(series);
17554
+ // Under the blank source, so what hangs off it draws over the candles.
17555
+ haSeries.setSeriesOrder(series.seriesOrder());
17550
17556
  }
17551
17557
  else if (!blank && restoreSourcePaint !== null) {
17552
17558
  restoreSourcePaint();
17553
17559
  restoreSourcePaint = null;
17560
+ haSeries.setSeriesOrder(series.seriesOrder() + 1);
17554
17561
  }
17555
17562
  };
17556
17563
  const runtime = bindIndicator({
@@ -17579,8 +17586,9 @@ function applyHeikinAshi(series, chart, options = {}) {
17579
17586
  blankSource(current.hideSource === true);
17580
17587
  },
17581
17588
  });
17582
- const handle = {
17583
- _internal_series: haSeries,
17589
+ // Returned as a literal: a local annotation would let the property renamer mangle `series`.
17590
+ return {
17591
+ series: haSeries,
17584
17592
  options: runtime.options,
17585
17593
  applyOptions: runtime.applyOptions,
17586
17594
  recalculate: runtime.recalculate,
@@ -17596,7 +17604,6 @@ function applyHeikinAshi(series, chart, options = {}) {
17596
17604
  }
17597
17605
  },
17598
17606
  };
17599
- return handle;
17600
17607
  }
17601
17608
 
17602
17609
  /**
@@ -21248,6 +21255,13 @@ class DrawingToolBase {
21248
21255
  series() {
21249
21256
  return this._series;
21250
21257
  }
21258
+ /**
21259
+ * Box the selection toolbar anchors to, in pane pixels. Null falls back to
21260
+ * the serialized points, which cannot place a line across the whole pane.
21261
+ */
21262
+ selectionBox() {
21263
+ return null;
21264
+ }
21251
21265
  getState() {
21252
21266
  return this._state;
21253
21267
  }
@@ -21924,6 +21938,53 @@ function createTrendLineTool(context, options) {
21924
21938
  return new TrendLineTool(context, options);
21925
21939
  }
21926
21940
 
21941
+ const END_HANDLE_MARGIN = 10;
21942
+ /**
21943
+ * Hands the next `clicks` clicks on the chart to `onClick`, past the last bar
21944
+ * too. A press that grabbed another drawing does not count.
21945
+ */
21946
+ function placeByClicks(chart, series, clicks, onClick) {
21947
+ let placed = 0;
21948
+ let handler = null;
21949
+ const stop = () => {
21950
+ if (handler !== null) {
21951
+ chart.unsubscribeClick(handler);
21952
+ handler = null;
21953
+ }
21954
+ };
21955
+ handler = (param) => {
21956
+ if (!param.point || consumeInputClaim(chart)) {
21957
+ return;
21958
+ }
21959
+ const price = series.coordinateToPrice(param.point.y);
21960
+ const time = timeFromCoordinate(chart, series, param.point.x);
21961
+ if (price === null || time === null) {
21962
+ return;
21963
+ }
21964
+ placed++;
21965
+ if (placed >= clicks) {
21966
+ stop();
21967
+ }
21968
+ onClick({ _internal_time: time, _internal_price: price, _internal_x: param.point.x, _internal_y: param.point.y }, placed - 1);
21969
+ };
21970
+ chart.subscribeClick(handler);
21971
+ return {
21972
+ _internal_pending: () => handler !== null,
21973
+ _internal_stop: stop,
21974
+ };
21975
+ }
21976
+ /** A fifth of the pane, and never less than a bar. */
21977
+ function defaultWidth(chart) {
21978
+ const timeScale = chart.timeScale();
21979
+ return Math.max(timeScale.width() / 5, timeScale.options().barSpacing);
21980
+ }
21981
+ /** Kept inside the pane: past it, the handles on the end hide under the price axis. */
21982
+ function placedEndX(chart, x) {
21983
+ const timeScale = chart.timeScale();
21984
+ const onScreen = Math.min(x + defaultWidth(chart), timeScale.width() - END_HANDLE_MARGIN);
21985
+ return Math.max(onScreen, x + timeScale.options().barSpacing);
21986
+ }
21987
+
21927
21988
  class HorizontalLinePaneRenderer {
21928
21989
  constructor(tool) {
21929
21990
  this._private__tool = tool;
@@ -21964,6 +22025,7 @@ class HorizontalLineTool extends DrawingToolBase {
21964
22025
  this._private__isDragging = false;
21965
22026
  this._private__grab = null;
21966
22027
  this._private__moveStartPrice = 0;
22028
+ this._private__placement = null;
21967
22029
  this._private__hLineOptions = {
21968
22030
  lineColor: '#2962ff',
21969
22031
  lineWidth: 2,
@@ -21976,6 +22038,26 @@ class HorizontalLineTool extends DrawingToolBase {
21976
22038
  };
21977
22039
  this._registerOptions(this._private__hLineOptions);
21978
22040
  this._private__createPrimitive();
22041
+ if (this._private__hLineOptions.interactive) {
22042
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
22043
+ }
22044
+ }
22045
+ _private__placeAt(point) {
22046
+ this._private__hLineOptions.price = point._internal_price;
22047
+ if (this._private__hLineOptions.startTime !== undefined) {
22048
+ this._private__hLineOptions.startTime = point._internal_time;
22049
+ }
22050
+ this._private__requestUpdate();
22051
+ }
22052
+ isComplete() {
22053
+ return !this._private__placement?._internal_pending();
22054
+ }
22055
+ selectionBox() {
22056
+ const y = this._private__hLineOptions.price === undefined ? null : this._series.priceToCoordinate(this._private__hLineOptions.price);
22057
+ if (y === null) {
22058
+ return null;
22059
+ }
22060
+ return { left: this._private__startX(), right: this._chart.timeScale().width(), top: y, bottom: y };
21979
22061
  }
21980
22062
  _private__createPrimitive() {
21981
22063
  this._private__paneView = this._private__createPaneView();
@@ -22013,7 +22095,7 @@ class HorizontalLineTool extends DrawingToolBase {
22013
22095
  return coordinateFromTime(this._chart, this._series, this._private__hLineOptions.startTime) ?? 0;
22014
22096
  }
22015
22097
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
22016
- if (this._private__hLineOptions.price === undefined)
22098
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined)
22017
22099
  return;
22018
22100
  const y = (this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0) * verticalPixelRatio;
22019
22101
  const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
@@ -22107,7 +22189,7 @@ class HorizontalLineTool extends DrawingToolBase {
22107
22189
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
22108
22190
  }
22109
22191
  hitTest(x, y) {
22110
- if (this._private__hLineOptions.price === undefined) {
22192
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined) {
22111
22193
  return false;
22112
22194
  }
22113
22195
  const lineY = this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0;
@@ -22182,6 +22264,7 @@ class HorizontalLineTool extends DrawingToolBase {
22182
22264
  this._private__requestUpdate();
22183
22265
  }
22184
22266
  remove() {
22267
+ this._private__placement?._internal_stop();
22185
22268
  if (this._private__primitive) {
22186
22269
  this._series.detachPrimitive(this._private__primitive);
22187
22270
  this._private__primitive = null;
@@ -22241,6 +22324,7 @@ class CrossLineTool extends DrawingToolBase {
22241
22324
  this._private__grab = null;
22242
22325
  this._private__moveStartTime = 0;
22243
22326
  this._private__moveStartPrice = 0;
22327
+ this._private__placement = null;
22244
22328
  this._private__crossOptions = {
22245
22329
  lineColor: '#787b86',
22246
22330
  lineWidth: 1,
@@ -22250,6 +22334,16 @@ class CrossLineTool extends DrawingToolBase {
22250
22334
  };
22251
22335
  this._registerOptions(this._private__crossOptions);
22252
22336
  this._private__createPrimitive();
22337
+ if (this._private__crossOptions.interactive) {
22338
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
22339
+ this._private__crossOptions.time = point._internal_time;
22340
+ this._private__crossOptions.price = point._internal_price;
22341
+ this._private__requestUpdate();
22342
+ });
22343
+ }
22344
+ }
22345
+ isComplete() {
22346
+ return !this._private__placement?._internal_pending();
22253
22347
  }
22254
22348
  _private__createPrimitive() {
22255
22349
  this._private__paneView = new CrossLinePaneView(this);
@@ -22277,7 +22371,7 @@ class CrossLineTool extends DrawingToolBase {
22277
22371
  this._private__requestUpdate();
22278
22372
  }
22279
22373
  _private__coordinates() {
22280
- if (this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
22374
+ if (!this.isComplete() || this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
22281
22375
  return null;
22282
22376
  }
22283
22377
  const x = coordinateFromTime(this._chart, this._series, this._private__crossOptions.time);
@@ -22400,6 +22494,7 @@ class CrossLineTool extends DrawingToolBase {
22400
22494
  return this._private__isHovered;
22401
22495
  }
22402
22496
  remove() {
22497
+ this._private__placement?._internal_stop();
22403
22498
  if (this._private__primitive) {
22404
22499
  try {
22405
22500
  this._series.detachPrimitive(this._private__primitive);
@@ -23822,6 +23917,7 @@ class VerticalLineTool extends DrawingToolBase {
23822
23917
  this._private__isDragging = false;
23823
23918
  this._private__grab = null;
23824
23919
  this._private__moveStartTime = 0;
23920
+ this._private__placement = null;
23825
23921
  this._private__vLineOptions = {
23826
23922
  lineColor: '#787b86',
23827
23923
  lineWidth: 1,
@@ -23833,6 +23929,29 @@ class VerticalLineTool extends DrawingToolBase {
23833
23929
  };
23834
23930
  this._registerOptions(this._private__vLineOptions);
23835
23931
  this._private__createPrimitive();
23932
+ if (this._private__vLineOptions.interactive) {
23933
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
23934
+ this._private__vLineOptions.time = point._internal_time;
23935
+ this._private__requestUpdate();
23936
+ });
23937
+ }
23938
+ }
23939
+ isComplete() {
23940
+ return !this._private__placement?._internal_pending();
23941
+ }
23942
+ selectionBox() {
23943
+ const x = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time);
23944
+ if (x === null) {
23945
+ return null;
23946
+ }
23947
+ let middle;
23948
+ try {
23949
+ middle = this._series.getPane().getHeight() / 2;
23950
+ }
23951
+ catch (error) {
23952
+ return null;
23953
+ }
23954
+ return { left: x, right: x, top: middle, bottom: middle };
23836
23955
  }
23837
23956
  _private__createPrimitive() {
23838
23957
  this._private__paneView = this._private__createPaneView();
@@ -23863,7 +23982,7 @@ class VerticalLineTool extends DrawingToolBase {
23863
23982
  return new VerticalLinePaneView(this);
23864
23983
  }
23865
23984
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
23866
- if (this._private__vLineOptions.time === undefined)
23985
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined)
23867
23986
  return;
23868
23987
  const x = (coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0) * horizontalPixelRatio;
23869
23988
  const chartHeight = this._chart.chartElement().clientHeight * verticalPixelRatio;
@@ -23950,7 +24069,7 @@ class VerticalLineTool extends DrawingToolBase {
23950
24069
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
23951
24070
  }
23952
24071
  hitTest(x, y) {
23953
- if (this._private__vLineOptions.time === undefined) {
24072
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined) {
23954
24073
  return false;
23955
24074
  }
23956
24075
  const lineX = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0;
@@ -24020,6 +24139,7 @@ class VerticalLineTool extends DrawingToolBase {
24020
24139
  this._private__requestUpdate();
24021
24140
  }
24022
24141
  remove() {
24142
+ this._private__placement?._internal_stop();
24023
24143
  if (this._private__primitive) {
24024
24144
  this._series.detachPrimitive(this._private__primitive);
24025
24145
  this._private__primitive = null;
@@ -24741,6 +24861,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
24741
24861
  this._private__isHovered = false;
24742
24862
  this._private__isDragging = false;
24743
24863
  this._private__dragTarget = null;
24864
+ this._private__placement = null;
24744
24865
  this._private__fibOptions = {
24745
24866
  levels: [
24746
24867
  { level: 0, color: '#787B86', label: '0%' },
@@ -24756,6 +24877,20 @@ class FibonacciExtensionTool extends DrawingToolBase {
24756
24877
  ...options,
24757
24878
  };
24758
24879
  this._private__createPrimitive();
24880
+ if (this._private__fibOptions.interactive) {
24881
+ this._private__placement = placeByClicks(this._chart, this._series, 3, (point, index) => {
24882
+ const placed = { time: point._internal_time, price: point._internal_price };
24883
+ if (index === 0) {
24884
+ this.setPoint1(placed);
24885
+ }
24886
+ else if (index === 1) {
24887
+ this.setPoint2(placed);
24888
+ }
24889
+ else {
24890
+ this.setPoint3(placed);
24891
+ }
24892
+ });
24893
+ }
24759
24894
  }
24760
24895
  _private__createPrimitive() {
24761
24896
  this._private__paneView = this._private__createPaneView();
@@ -24813,7 +24948,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
24813
24948
  this._private__requestUpdate();
24814
24949
  }
24815
24950
  isComplete() {
24816
- return !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
24951
+ return !this._private__placement?._internal_pending() && !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
24817
24952
  }
24818
24953
  setColor(color) {
24819
24954
  this._private__fibOptions.lineColor = color;
@@ -25108,6 +25243,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
25108
25243
  this._private__requestUpdate();
25109
25244
  }
25110
25245
  remove() {
25246
+ this._private__placement?._internal_stop();
25111
25247
  if (this._private__primitive) {
25112
25248
  this._series.detachPrimitive(this._private__primitive);
25113
25249
  this._private__primitive = null;
@@ -27673,6 +27809,7 @@ class TextTool extends DrawingToolBase {
27673
27809
  this._private__moveStart = null;
27674
27810
  this._private__isEditing = false;
27675
27811
  this._private__clickOutsideHandler = null;
27812
+ this._private__placement = null;
27676
27813
  this._private__textOptions = {
27677
27814
  text: 'Double-click to edit',
27678
27815
  fontSize: 14,
@@ -27693,6 +27830,14 @@ class TextTool extends DrawingToolBase {
27693
27830
  this._registerOptions(this._private__textOptions);
27694
27831
  this._private__createPrimitive();
27695
27832
  this._private__setupClickOutsideHandler();
27833
+ if (this._private__textOptions.interactive) {
27834
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
27835
+ this.setPosition(point._internal_time, point._internal_price);
27836
+ if (this._private__textOptions.onComplete) {
27837
+ this._private__textOptions.onComplete();
27838
+ }
27839
+ });
27840
+ }
27696
27841
  }
27697
27842
  _private__setupClickOutsideHandler() {
27698
27843
  // Set up click handler to finish editing when clicking outside
@@ -27778,7 +27923,7 @@ class TextTool extends DrawingToolBase {
27778
27923
  this._private__requestUpdate();
27779
27924
  }
27780
27925
  isComplete() {
27781
- return !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
27926
+ return !this._private__placement?._internal_pending() && !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
27782
27927
  }
27783
27928
  setColor(color) {
27784
27929
  this._private__textOptions.textColor = color;
@@ -28061,6 +28206,7 @@ class TextTool extends DrawingToolBase {
28061
28206
  };
28062
28207
  }
28063
28208
  remove() {
28209
+ this._private__placement?._internal_stop();
28064
28210
  // Clean up click handler
28065
28211
  if (this._private__clickOutsideHandler) {
28066
28212
  this._chart.unsubscribeClick(this._private__clickOutsideHandler);
@@ -28919,6 +29065,7 @@ class PositionTool extends DrawingToolBase {
28919
29065
  this._private__paneView = null;
28920
29066
  this._private__isSelected = false;
28921
29067
  this._private__isHovered = false;
29068
+ this._private__placement = null;
28922
29069
  this._private__isDragging = false;
28923
29070
  this._private__dragTarget = null;
28924
29071
  this._private__grab = null;
@@ -28941,6 +29088,32 @@ class PositionTool extends DrawingToolBase {
28941
29088
  // Create primitive and setup
28942
29089
  this._private__createPrimitive();
28943
29090
  this._private__updateMetrics();
29091
+ if (this._private__positionOptions.interactive) {
29092
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
29093
+ }
29094
+ }
29095
+ _private__placeAt(point) {
29096
+ const options = this._private__positionOptions;
29097
+ const shift = point._internal_price - options.entryPrice;
29098
+ options.entryPrice = point._internal_price;
29099
+ options.entryTime = point._internal_time;
29100
+ if (options.stopLoss !== undefined) {
29101
+ options.stopLoss += shift;
29102
+ }
29103
+ if (options.takeProfit !== undefined) {
29104
+ options.takeProfit += shift;
29105
+ }
29106
+ options.endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? undefined;
29107
+ this._private__updateMetrics();
29108
+ this._private__requestUpdate();
29109
+ }
29110
+ isComplete() {
29111
+ return !this._private__placement?._internal_pending();
29112
+ }
29113
+ _private__span() {
29114
+ const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29115
+ const endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? entryX + defaultWidth(this._chart);
29116
+ return { _internal_entryX: entryX, _internal_endX: endX };
28944
29117
  }
28945
29118
  _private__createPrimitive() {
28946
29119
  // Create and cache the paneView once
@@ -28972,6 +29145,9 @@ class PositionTool extends DrawingToolBase {
28972
29145
  return new PositionPaneView(this);
28973
29146
  }
28974
29147
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29148
+ if (!this.isComplete()) {
29149
+ return;
29150
+ }
28975
29151
  // Draw profit rectangle (green)
28976
29152
  if (this._private__profitRect && this._private__positionOptions.takeProfit) {
28977
29153
  const timeScale = this._chart.timeScale();
@@ -29005,16 +29181,9 @@ class PositionTool extends DrawingToolBase {
29005
29181
  }
29006
29182
  }
29007
29183
  _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
- }
29184
+ const span = this._private__span();
29185
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29186
+ const endX = span._internal_endX * horizontalPixelRatio;
29018
29187
  const y1 = (this._series.priceToCoordinate(rect._internal_price1) ?? 0) * verticalPixelRatio;
29019
29188
  const y2 = (this._series.priceToCoordinate(rect._internal_price2) ?? 0) * verticalPixelRatio;
29020
29189
  // Draw filled rectangle with semi-transparent fill
@@ -29029,16 +29198,10 @@ class PositionTool extends DrawingToolBase {
29029
29198
  ctx.stroke();
29030
29199
  }
29031
29200
  _private__drawEntryLine(ctx, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29032
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29201
+ const span = this._private__span();
29202
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29203
+ const endX = span._internal_endX * horizontalPixelRatio;
29033
29204
  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
29205
  // Draw horizontal entry line (divider)
29043
29206
  const lineColor = this._private__positionOptions.entryLineColor;
29044
29207
  ctx.strokeStyle = lineColor;
@@ -29152,9 +29315,11 @@ class PositionTool extends DrawingToolBase {
29152
29315
  const padding = 6 * horizontalPixelRatio;
29153
29316
  const labelWidth = textMetrics.width + padding * 2;
29154
29317
  const labelHeight = 20 * horizontalPixelRatio;
29155
- // Position label at the start of the line
29318
+ // Outside the box, so a label wider than it does not cover the handles on its edge
29319
+ const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
29320
+ const gap = 3 * verticalPixelRatio;
29156
29321
  const labelX = entryX + 8 * horizontalPixelRatio;
29157
- const labelY = y - labelHeight / 2;
29322
+ const labelY = y <= entryY ? y - labelHeight - gap : y + gap;
29158
29323
  // Draw label background
29159
29324
  ctx.fillStyle = rect._internal_color;
29160
29325
  ctx.fillRect(labelX, labelY, labelWidth, labelHeight);
@@ -29162,10 +29327,12 @@ class PositionTool extends DrawingToolBase {
29162
29327
  ctx.fillStyle = '#ffffff';
29163
29328
  ctx.textAlign = 'left';
29164
29329
  ctx.textBaseline = 'middle';
29165
- ctx.fillText(labelText, labelX + padding, y);
29330
+ ctx.fillText(labelText, labelX + padding, labelY + labelHeight / 2);
29166
29331
  }
29167
29332
  _private__drawEndpoint(ctx, price, color, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29168
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29333
+ const span = this._private__span();
29334
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29335
+ const endX = span._internal_endX * horizontalPixelRatio;
29169
29336
  const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
29170
29337
  // Draw circle endpoint at entry
29171
29338
  const circleColor = color;
@@ -29177,14 +29344,6 @@ class PositionTool extends DrawingToolBase {
29177
29344
  ctx.fill();
29178
29345
  ctx.stroke();
29179
29346
  // 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
29347
  const size = 8 * horizontalPixelRatio;
29189
29348
  const squareColor = color;
29190
29349
  ctx.fillStyle = squareColor;
@@ -29305,18 +29464,12 @@ class PositionTool extends DrawingToolBase {
29305
29464
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
29306
29465
  }
29307
29466
  hitTest(x, y) {
29308
- if (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit) {
29467
+ if (!this.isComplete() || (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit)) {
29309
29468
  return false;
29310
29469
  }
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
- }
29470
+ const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
29471
+ const left = Math.min(entryX, endX);
29472
+ const right = Math.max(entryX, endX);
29320
29473
  // Check if inside the entire Position Tool area (rectangles)
29321
29474
  if (this._private__positionOptions.takeProfit && this._private__positionOptions.stopLoss) {
29322
29475
  const tpY = this._series.priceToCoordinate(this._private__positionOptions.takeProfit) ?? 0;
@@ -29324,7 +29477,7 @@ class PositionTool extends DrawingToolBase {
29324
29477
  const minY = Math.min(tpY, slY);
29325
29478
  const maxY = Math.max(tpY, slY);
29326
29479
  // Check if inside the rectangle bounds (entire tool area)
29327
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29480
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29328
29481
  return true;
29329
29482
  }
29330
29483
  // Also check entry line area
@@ -29336,15 +29489,12 @@ class PositionTool extends DrawingToolBase {
29336
29489
  return false;
29337
29490
  }
29338
29491
  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;
29492
+ if (!this.isComplete()) {
29493
+ return null;
29347
29494
  }
29495
+ const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
29496
+ const left = Math.min(entryX, endX);
29497
+ const right = Math.max(entryX, endX);
29348
29498
  const tolerance = 10;
29349
29499
  // Check TP left endpoint (circle)
29350
29500
  if (this._private__positionOptions.takeProfit) {
@@ -29381,7 +29531,7 @@ class PositionTool extends DrawingToolBase {
29381
29531
  // Check entry line (middle divider line) - higher priority than 'move'
29382
29532
  const entryY = this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0;
29383
29533
  const lineTolerance = 8;
29384
- if (x >= entryX && x <= endX && Math.abs(y - entryY) <= lineTolerance) {
29534
+ if (x >= left && x <= right && Math.abs(y - entryY) <= lineTolerance) {
29385
29535
  return 'entry';
29386
29536
  }
29387
29537
  // Check if clicking on the body (rectangles) for moving entire tool
@@ -29391,7 +29541,7 @@ class PositionTool extends DrawingToolBase {
29391
29541
  const minY = Math.min(tpY, slY);
29392
29542
  const maxY = Math.max(tpY, slY);
29393
29543
  // Check if inside the rectangle bounds
29394
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29544
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29395
29545
  return 'move';
29396
29546
  }
29397
29547
  }
@@ -29491,6 +29641,7 @@ class PositionTool extends DrawingToolBase {
29491
29641
  this._private__requestUpdate();
29492
29642
  }
29493
29643
  remove() {
29644
+ this._private__placement?._internal_stop();
29494
29645
  if (this._private__primitive) {
29495
29646
  this._series.detachPrimitive(this._private__primitive);
29496
29647
  this._private__primitive = null;
@@ -29537,6 +29688,7 @@ class GanttPaneView {
29537
29688
  }
29538
29689
  }
29539
29690
 
29691
+ const TOP_PADDING = 50;
29540
29692
  class GanttTool extends DrawingToolBase {
29541
29693
  constructor(context, options) {
29542
29694
  super(context, 'gantt', options);
@@ -29550,6 +29702,7 @@ class GanttTool extends DrawingToolBase {
29550
29702
  this._private__grab = null;
29551
29703
  this._private__moveStart = null;
29552
29704
  this._private__hoveredSegmentIndex = -1;
29705
+ this._private__placement = null;
29553
29706
  this._private__ganttOptions = {
29554
29707
  segments: [],
29555
29708
  rowHeight: 40,
@@ -29562,6 +29715,43 @@ class GanttTool extends DrawingToolBase {
29562
29715
  };
29563
29716
  this._registerOptions(this._private__ganttOptions);
29564
29717
  this._private__createPrimitive();
29718
+ if (this._private__ganttOptions.interactive) {
29719
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
29720
+ }
29721
+ }
29722
+ _private__placeAt(point) {
29723
+ const segments = this._private__ganttOptions.segments ?? [];
29724
+ const endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? point._internal_time;
29725
+ const rowPitch = this._private__ganttOptions.rowHeight + this._private__ganttOptions.rowSpacing;
29726
+ const row = Math.max(0, Math.floor((point._internal_y - TOP_PADDING) / rowPitch));
29727
+ const shift = segments.length > 0 ? row - segments[0].level : 0;
29728
+ for (const segment of segments) {
29729
+ segment.startTime = point._internal_time;
29730
+ segment.endTime = endTime;
29731
+ segment.level = Math.max(0, segment.level + shift);
29732
+ }
29733
+ this._private__requestUpdate();
29734
+ }
29735
+ isComplete() {
29736
+ return !this._private__placement?._internal_pending();
29737
+ }
29738
+ selectionBox() {
29739
+ const segments = this._private__ganttOptions.segments ?? [];
29740
+ if (segments.length === 0) {
29741
+ return null;
29742
+ }
29743
+ const rowHeight = this._private__ganttOptions.rowHeight;
29744
+ const pitch = rowHeight + this._private__ganttOptions.rowSpacing;
29745
+ // Typed as the public shape: an inferred one gets its keys renamed by the build
29746
+ const box = { left: Infinity, top: Infinity, right: -Infinity, bottom: -Infinity };
29747
+ for (const segment of segments) {
29748
+ const top = TOP_PADDING + segment.level * pitch;
29749
+ box.left = Math.min(box.left, coordinateFromTime(this._chart, this._series, segment.startTime) ?? 0);
29750
+ box.right = Math.max(box.right, coordinateFromTime(this._chart, this._series, segment.endTime) ?? 0);
29751
+ box.top = Math.min(box.top, top);
29752
+ box.bottom = Math.max(box.bottom, top + rowHeight);
29753
+ }
29754
+ return box;
29565
29755
  }
29566
29756
  _private__createPrimitive() {
29567
29757
  this._private__paneView = this._private__createPaneView();
@@ -29592,11 +29782,10 @@ class GanttTool extends DrawingToolBase {
29592
29782
  return new GanttPaneView(this);
29593
29783
  }
29594
29784
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29595
- if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
29785
+ if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
29596
29786
  return;
29597
29787
  // Position gantt at top of chart with padding
29598
- const topPadding = 50; // pixels from top
29599
- const baseY = topPadding * verticalPixelRatio;
29788
+ const baseY = TOP_PADDING * verticalPixelRatio;
29600
29789
  const rowHeight = this._private__ganttOptions.rowHeight * verticalPixelRatio;
29601
29790
  const rowSpacing = this._private__ganttOptions.rowSpacing * verticalPixelRatio;
29602
29791
  // Draw grid lines if enabled
@@ -29775,11 +29964,10 @@ class GanttTool extends DrawingToolBase {
29775
29964
  this._private__moveStart = null;
29776
29965
  }
29777
29966
  hitTest(x, y) {
29778
- if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
29967
+ if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
29779
29968
  return false;
29780
29969
  }
29781
- const topPadding = 50;
29782
- const baseY = topPadding;
29970
+ const baseY = TOP_PADDING;
29783
29971
  const rowHeight = this._private__ganttOptions.rowHeight;
29784
29972
  const rowSpacing = this._private__ganttOptions.rowSpacing;
29785
29973
  // Check each segment
@@ -29833,6 +30021,7 @@ class GanttTool extends DrawingToolBase {
29833
30021
  this._private__requestUpdate();
29834
30022
  }
29835
30023
  remove() {
30024
+ this._private__placement?._internal_stop();
29836
30025
  if (this._private__primitive) {
29837
30026
  this._series.detachPrimitive(this._private__primitive);
29838
30027
  this._private__primitive = null;
@@ -29968,6 +30157,7 @@ class DrawingToolsManager {
29968
30157
  }
29969
30158
  this._private__completionCheck = setTimeout(() => {
29970
30159
  this._private__completionCheck = null;
30160
+ const completed = [];
29971
30161
  for (const id of Array.from(this._private__pendingToolIds)) {
29972
30162
  const tool = this._private__tools.get(id);
29973
30163
  if (!tool) {
@@ -29976,9 +30166,15 @@ class DrawingToolsManager {
29976
30166
  }
29977
30167
  if (tool.isComplete()) {
29978
30168
  this._private__pendingToolIds.delete(id);
29979
- this._private__drawingCompleted._internal_fire(id);
30169
+ completed.push(id);
29980
30170
  }
29981
30171
  }
30172
+ if (completed.length > 0) {
30173
+ this.recordChange();
30174
+ }
30175
+ for (const id of completed) {
30176
+ this._private__drawingCompleted._internal_fire(id);
30177
+ }
29982
30178
  }, 0);
29983
30179
  }
29984
30180
  /**
@@ -30038,12 +30234,16 @@ class DrawingToolsManager {
30038
30234
  if (this._private__restoring) {
30039
30235
  return;
30040
30236
  }
30237
+ const next = this.serializeAll();
30238
+ if (JSON.stringify(next) === JSON.stringify(this._private__committed)) {
30239
+ return;
30240
+ }
30041
30241
  this._private__past.push(this._private__committed);
30042
30242
  if (this._private__past.length > UNDO_HISTORY_LIMIT) {
30043
30243
  this._private__past.shift();
30044
30244
  }
30045
30245
  this._private__future = [];
30046
- this._private__committed = this.serializeAll();
30246
+ this._private__committed = next;
30047
30247
  }
30048
30248
  canUndo() {
30049
30249
  return this._private__past.length > 0;
@@ -30099,11 +30299,25 @@ class DrawingToolsManager {
30099
30299
  const tool = this._private__tools.get(this._private__selectedToolId);
30100
30300
  return tool ? this._private__boundsOf(tool) : null;
30101
30301
  }
30302
+ /** Relative to the chart element; the tool's box is relative to its pane. */
30303
+ _private__boundsOf(tool) {
30304
+ const box = tool.selectionBox() ?? this._private__boxFromPoints(tool);
30305
+ if (box === null) {
30306
+ return null;
30307
+ }
30308
+ const offset = this._private__paneOffsetOf(tool);
30309
+ return {
30310
+ left: box.left + offset._internal_x,
30311
+ right: box.right + offset._internal_x,
30312
+ top: box.top + offset._internal_y,
30313
+ bottom: box.bottom + offset._internal_y,
30314
+ };
30315
+ }
30102
30316
  /**
30103
30317
  * Bounds come from the tool's own serialized points, so this works for every
30104
30318
  * tool without each one having to report a box.
30105
30319
  */
30106
- _private__boundsOf(tool) {
30320
+ _private__boxFromPoints(tool) {
30107
30321
  const series = tool.series();
30108
30322
  const points = tool.serialize().points;
30109
30323
  let left = Number.POSITIVE_INFINITY;
@@ -30126,13 +30340,7 @@ class DrawingToolsManager {
30126
30340
  if (!found) {
30127
30341
  return null;
30128
30342
  }
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
- };
30343
+ return { left, top, right, bottom };
30136
30344
  }
30137
30345
  /** Panes below the first one are offset inside the chart element. */
30138
30346
  _private__paneOffsetOf(tool) {
@@ -30216,7 +30424,10 @@ class DrawingToolsManager {
30216
30424
  serializeAll() {
30217
30425
  const result = [];
30218
30426
  for (const tool of this._private__tools.values()) {
30219
- result.push(tool.serialize());
30427
+ // Not drawn yet: undo would bring it back as a finished drawing
30428
+ if (tool.isComplete()) {
30429
+ result.push(tool.serialize());
30430
+ }
30220
30431
  }
30221
30432
  return result;
30222
30433
  }
@@ -30841,7 +31052,7 @@ const customSeriesDefaultOptions = {
30841
31052
  * Returns the current version as a string. For example `'1.0.0'`.
30842
31053
  */
30843
31054
  function version() {
30844
- return "2.3.13-dev+202609280001";
31055
+ return "2.3.15";
30845
31056
  }
30846
31057
 
30847
31058
  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 };