@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)
@@ -14684,6 +14684,9 @@ function calculateShapeHeight(barSpacing) {
14684
14684
  function shapeMargin(barSpacing) {
14685
14685
  return Math.max(size(barSpacing, 0.1), 3 /* Constants.MinShapeMargin */);
14686
14686
  }
14687
+ function barGap(barSpacing) {
14688
+ return Math.max(shapeMargin(barSpacing), 10 /* Constants.MinBarGap */);
14689
+ }
14687
14690
  function calculateAdjustedMargin(margin, hasSide, hasInBar) {
14688
14691
  if (hasSide) {
14689
14692
  return margin;
@@ -15310,17 +15313,18 @@ class SeriesMarkersPaneView {
15310
15313
  }
15311
15314
  let prevTimeIndex = NaN;
15312
15315
  const shapeMargin$1 = shapeMargin(timeScale.options().barSpacing);
15316
+ const barGap$1 = barGap(timeScale.options().barSpacing);
15313
15317
  const offsets = {
15314
- _internal_aboveBar: shapeMargin$1,
15315
- _internal_belowBar: shapeMargin$1,
15318
+ _internal_aboveBar: barGap$1,
15319
+ _internal_belowBar: barGap$1,
15316
15320
  };
15317
15321
  this._private__data._internal_visibleRange = visibleTimedValues(this._private__data._internal_items, visibleBarsRange, true);
15318
15322
  for (let index = this._private__data._internal_visibleRange.from; index < this._private__data._internal_visibleRange.to; index++) {
15319
15323
  const marker = seriesMarkers[index];
15320
15324
  if (marker.time !== prevTimeIndex) {
15321
15325
  // new bar, reset stack counter
15322
- offsets._internal_aboveBar = shapeMargin$1;
15323
- offsets._internal_belowBar = shapeMargin$1;
15326
+ offsets._internal_aboveBar = barGap$1;
15327
+ offsets._internal_belowBar = barGap$1;
15324
15328
  prevTimeIndex = marker.time;
15325
15329
  }
15326
15330
  const rendererItem = this._private__data._internal_items[index];
@@ -15437,7 +15441,7 @@ class SeriesMarkersPrimitive {
15437
15441
  this._private__cachedBarSpacing = barSpacing;
15438
15442
  if (this._private__markers.length > 0) {
15439
15443
  const shapeMargin$1 = shapeMargin(barSpacing);
15440
- const marginValue = calculateShapeHeight(barSpacing) * 1.5 + shapeMargin$1 * 2;
15444
+ const marginValue = calculateShapeHeight(barSpacing) * 1.5 + shapeMargin$1 + barGap(barSpacing);
15441
15445
  const positions = this._private__getMarkerPositions();
15442
15446
  this._private__autoScaleMargins = {
15443
15447
  above: calculateAdjustedMargin(marginValue, positions.aboveBar, positions.inBar),
@@ -17940,10 +17944,13 @@ function applyHeikinAshi(series, chart, options = {}) {
17940
17944
  const blankSource = (blank) => {
17941
17945
  if (blank && restoreSourcePaint === null) {
17942
17946
  restoreSourcePaint = hideSeriesPaint(series);
17947
+ // Under the blank source, so what hangs off it draws over the candles.
17948
+ haSeries.setSeriesOrder(series.seriesOrder());
17943
17949
  }
17944
17950
  else if (!blank && restoreSourcePaint !== null) {
17945
17951
  restoreSourcePaint();
17946
17952
  restoreSourcePaint = null;
17953
+ haSeries.setSeriesOrder(series.seriesOrder() + 1);
17947
17954
  }
17948
17955
  };
17949
17956
  const runtime = bindIndicator({
@@ -17972,8 +17979,9 @@ function applyHeikinAshi(series, chart, options = {}) {
17972
17979
  blankSource(current.hideSource === true);
17973
17980
  },
17974
17981
  });
17975
- const handle = {
17976
- _internal_series: haSeries,
17982
+ // Returned as a literal: a local annotation would let the property renamer mangle `series`.
17983
+ return {
17984
+ series: haSeries,
17977
17985
  options: runtime.options,
17978
17986
  applyOptions: runtime.applyOptions,
17979
17987
  recalculate: runtime.recalculate,
@@ -17989,7 +17997,6 @@ function applyHeikinAshi(series, chart, options = {}) {
17989
17997
  }
17990
17998
  },
17991
17999
  };
17992
- return handle;
17993
18000
  }
17994
18001
 
17995
18002
  /**
@@ -21641,6 +21648,13 @@ class DrawingToolBase {
21641
21648
  series() {
21642
21649
  return this._series;
21643
21650
  }
21651
+ /**
21652
+ * Box the selection toolbar anchors to, in pane pixels. Null falls back to
21653
+ * the serialized points, which cannot place a line across the whole pane.
21654
+ */
21655
+ selectionBox() {
21656
+ return null;
21657
+ }
21644
21658
  getState() {
21645
21659
  return this._state;
21646
21660
  }
@@ -22317,6 +22331,53 @@ function createTrendLineTool(context, options) {
22317
22331
  return new TrendLineTool(context, options);
22318
22332
  }
22319
22333
 
22334
+ const END_HANDLE_MARGIN = 10;
22335
+ /**
22336
+ * Hands the next `clicks` clicks on the chart to `onClick`, past the last bar
22337
+ * too. A press that grabbed another drawing does not count.
22338
+ */
22339
+ function placeByClicks(chart, series, clicks, onClick) {
22340
+ let placed = 0;
22341
+ let handler = null;
22342
+ const stop = () => {
22343
+ if (handler !== null) {
22344
+ chart.unsubscribeClick(handler);
22345
+ handler = null;
22346
+ }
22347
+ };
22348
+ handler = (param) => {
22349
+ if (!param.point || consumeInputClaim(chart)) {
22350
+ return;
22351
+ }
22352
+ const price = series.coordinateToPrice(param.point.y);
22353
+ const time = timeFromCoordinate(chart, series, param.point.x);
22354
+ if (price === null || time === null) {
22355
+ return;
22356
+ }
22357
+ placed++;
22358
+ if (placed >= clicks) {
22359
+ stop();
22360
+ }
22361
+ onClick({ _internal_time: time, _internal_price: price, _internal_x: param.point.x, _internal_y: param.point.y }, placed - 1);
22362
+ };
22363
+ chart.subscribeClick(handler);
22364
+ return {
22365
+ _internal_pending: () => handler !== null,
22366
+ _internal_stop: stop,
22367
+ };
22368
+ }
22369
+ /** A fifth of the pane, and never less than a bar. */
22370
+ function defaultWidth(chart) {
22371
+ const timeScale = chart.timeScale();
22372
+ return Math.max(timeScale.width() / 5, timeScale.options().barSpacing);
22373
+ }
22374
+ /** Kept inside the pane: past it, the handles on the end hide under the price axis. */
22375
+ function placedEndX(chart, x) {
22376
+ const timeScale = chart.timeScale();
22377
+ const onScreen = Math.min(x + defaultWidth(chart), timeScale.width() - END_HANDLE_MARGIN);
22378
+ return Math.max(onScreen, x + timeScale.options().barSpacing);
22379
+ }
22380
+
22320
22381
  class HorizontalLinePaneRenderer {
22321
22382
  constructor(tool) {
22322
22383
  this._private__tool = tool;
@@ -22357,6 +22418,7 @@ class HorizontalLineTool extends DrawingToolBase {
22357
22418
  this._private__isDragging = false;
22358
22419
  this._private__grab = null;
22359
22420
  this._private__moveStartPrice = 0;
22421
+ this._private__placement = null;
22360
22422
  this._private__hLineOptions = {
22361
22423
  lineColor: '#2962ff',
22362
22424
  lineWidth: 2,
@@ -22369,6 +22431,26 @@ class HorizontalLineTool extends DrawingToolBase {
22369
22431
  };
22370
22432
  this._registerOptions(this._private__hLineOptions);
22371
22433
  this._private__createPrimitive();
22434
+ if (this._private__hLineOptions.interactive) {
22435
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
22436
+ }
22437
+ }
22438
+ _private__placeAt(point) {
22439
+ this._private__hLineOptions.price = point._internal_price;
22440
+ if (this._private__hLineOptions.startTime !== undefined) {
22441
+ this._private__hLineOptions.startTime = point._internal_time;
22442
+ }
22443
+ this._private__requestUpdate();
22444
+ }
22445
+ isComplete() {
22446
+ return !this._private__placement?._internal_pending();
22447
+ }
22448
+ selectionBox() {
22449
+ const y = this._private__hLineOptions.price === undefined ? null : this._series.priceToCoordinate(this._private__hLineOptions.price);
22450
+ if (y === null) {
22451
+ return null;
22452
+ }
22453
+ return { left: this._private__startX(), right: this._chart.timeScale().width(), top: y, bottom: y };
22372
22454
  }
22373
22455
  _private__createPrimitive() {
22374
22456
  this._private__paneView = this._private__createPaneView();
@@ -22406,7 +22488,7 @@ class HorizontalLineTool extends DrawingToolBase {
22406
22488
  return coordinateFromTime(this._chart, this._series, this._private__hLineOptions.startTime) ?? 0;
22407
22489
  }
22408
22490
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
22409
- if (this._private__hLineOptions.price === undefined)
22491
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined)
22410
22492
  return;
22411
22493
  const y = (this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0) * verticalPixelRatio;
22412
22494
  const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
@@ -22500,7 +22582,7 @@ class HorizontalLineTool extends DrawingToolBase {
22500
22582
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
22501
22583
  }
22502
22584
  hitTest(x, y) {
22503
- if (this._private__hLineOptions.price === undefined) {
22585
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined) {
22504
22586
  return false;
22505
22587
  }
22506
22588
  const lineY = this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0;
@@ -22575,6 +22657,7 @@ class HorizontalLineTool extends DrawingToolBase {
22575
22657
  this._private__requestUpdate();
22576
22658
  }
22577
22659
  remove() {
22660
+ this._private__placement?._internal_stop();
22578
22661
  if (this._private__primitive) {
22579
22662
  this._series.detachPrimitive(this._private__primitive);
22580
22663
  this._private__primitive = null;
@@ -22634,6 +22717,7 @@ class CrossLineTool extends DrawingToolBase {
22634
22717
  this._private__grab = null;
22635
22718
  this._private__moveStartTime = 0;
22636
22719
  this._private__moveStartPrice = 0;
22720
+ this._private__placement = null;
22637
22721
  this._private__crossOptions = {
22638
22722
  lineColor: '#787b86',
22639
22723
  lineWidth: 1,
@@ -22643,6 +22727,16 @@ class CrossLineTool extends DrawingToolBase {
22643
22727
  };
22644
22728
  this._registerOptions(this._private__crossOptions);
22645
22729
  this._private__createPrimitive();
22730
+ if (this._private__crossOptions.interactive) {
22731
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
22732
+ this._private__crossOptions.time = point._internal_time;
22733
+ this._private__crossOptions.price = point._internal_price;
22734
+ this._private__requestUpdate();
22735
+ });
22736
+ }
22737
+ }
22738
+ isComplete() {
22739
+ return !this._private__placement?._internal_pending();
22646
22740
  }
22647
22741
  _private__createPrimitive() {
22648
22742
  this._private__paneView = new CrossLinePaneView(this);
@@ -22670,7 +22764,7 @@ class CrossLineTool extends DrawingToolBase {
22670
22764
  this._private__requestUpdate();
22671
22765
  }
22672
22766
  _private__coordinates() {
22673
- if (this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
22767
+ if (!this.isComplete() || this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
22674
22768
  return null;
22675
22769
  }
22676
22770
  const x = coordinateFromTime(this._chart, this._series, this._private__crossOptions.time);
@@ -22793,6 +22887,7 @@ class CrossLineTool extends DrawingToolBase {
22793
22887
  return this._private__isHovered;
22794
22888
  }
22795
22889
  remove() {
22890
+ this._private__placement?._internal_stop();
22796
22891
  if (this._private__primitive) {
22797
22892
  try {
22798
22893
  this._series.detachPrimitive(this._private__primitive);
@@ -24215,6 +24310,7 @@ class VerticalLineTool extends DrawingToolBase {
24215
24310
  this._private__isDragging = false;
24216
24311
  this._private__grab = null;
24217
24312
  this._private__moveStartTime = 0;
24313
+ this._private__placement = null;
24218
24314
  this._private__vLineOptions = {
24219
24315
  lineColor: '#787b86',
24220
24316
  lineWidth: 1,
@@ -24226,6 +24322,29 @@ class VerticalLineTool extends DrawingToolBase {
24226
24322
  };
24227
24323
  this._registerOptions(this._private__vLineOptions);
24228
24324
  this._private__createPrimitive();
24325
+ if (this._private__vLineOptions.interactive) {
24326
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
24327
+ this._private__vLineOptions.time = point._internal_time;
24328
+ this._private__requestUpdate();
24329
+ });
24330
+ }
24331
+ }
24332
+ isComplete() {
24333
+ return !this._private__placement?._internal_pending();
24334
+ }
24335
+ selectionBox() {
24336
+ const x = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time);
24337
+ if (x === null) {
24338
+ return null;
24339
+ }
24340
+ let middle;
24341
+ try {
24342
+ middle = this._series.getPane().getHeight() / 2;
24343
+ }
24344
+ catch (error) {
24345
+ return null;
24346
+ }
24347
+ return { left: x, right: x, top: middle, bottom: middle };
24229
24348
  }
24230
24349
  _private__createPrimitive() {
24231
24350
  this._private__paneView = this._private__createPaneView();
@@ -24256,7 +24375,7 @@ class VerticalLineTool extends DrawingToolBase {
24256
24375
  return new VerticalLinePaneView(this);
24257
24376
  }
24258
24377
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
24259
- if (this._private__vLineOptions.time === undefined)
24378
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined)
24260
24379
  return;
24261
24380
  const x = (coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0) * horizontalPixelRatio;
24262
24381
  const chartHeight = this._chart.chartElement().clientHeight * verticalPixelRatio;
@@ -24343,7 +24462,7 @@ class VerticalLineTool extends DrawingToolBase {
24343
24462
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
24344
24463
  }
24345
24464
  hitTest(x, y) {
24346
- if (this._private__vLineOptions.time === undefined) {
24465
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined) {
24347
24466
  return false;
24348
24467
  }
24349
24468
  const lineX = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0;
@@ -24413,6 +24532,7 @@ class VerticalLineTool extends DrawingToolBase {
24413
24532
  this._private__requestUpdate();
24414
24533
  }
24415
24534
  remove() {
24535
+ this._private__placement?._internal_stop();
24416
24536
  if (this._private__primitive) {
24417
24537
  this._series.detachPrimitive(this._private__primitive);
24418
24538
  this._private__primitive = null;
@@ -25134,6 +25254,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
25134
25254
  this._private__isHovered = false;
25135
25255
  this._private__isDragging = false;
25136
25256
  this._private__dragTarget = null;
25257
+ this._private__placement = null;
25137
25258
  this._private__fibOptions = {
25138
25259
  levels: [
25139
25260
  { level: 0, color: '#787B86', label: '0%' },
@@ -25149,6 +25270,20 @@ class FibonacciExtensionTool extends DrawingToolBase {
25149
25270
  ...options,
25150
25271
  };
25151
25272
  this._private__createPrimitive();
25273
+ if (this._private__fibOptions.interactive) {
25274
+ this._private__placement = placeByClicks(this._chart, this._series, 3, (point, index) => {
25275
+ const placed = { time: point._internal_time, price: point._internal_price };
25276
+ if (index === 0) {
25277
+ this.setPoint1(placed);
25278
+ }
25279
+ else if (index === 1) {
25280
+ this.setPoint2(placed);
25281
+ }
25282
+ else {
25283
+ this.setPoint3(placed);
25284
+ }
25285
+ });
25286
+ }
25152
25287
  }
25153
25288
  _private__createPrimitive() {
25154
25289
  this._private__paneView = this._private__createPaneView();
@@ -25206,7 +25341,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
25206
25341
  this._private__requestUpdate();
25207
25342
  }
25208
25343
  isComplete() {
25209
- return !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
25344
+ return !this._private__placement?._internal_pending() && !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
25210
25345
  }
25211
25346
  setColor(color) {
25212
25347
  this._private__fibOptions.lineColor = color;
@@ -25501,6 +25636,7 @@ class FibonacciExtensionTool extends DrawingToolBase {
25501
25636
  this._private__requestUpdate();
25502
25637
  }
25503
25638
  remove() {
25639
+ this._private__placement?._internal_stop();
25504
25640
  if (this._private__primitive) {
25505
25641
  this._series.detachPrimitive(this._private__primitive);
25506
25642
  this._private__primitive = null;
@@ -28066,6 +28202,7 @@ class TextTool extends DrawingToolBase {
28066
28202
  this._private__moveStart = null;
28067
28203
  this._private__isEditing = false;
28068
28204
  this._private__clickOutsideHandler = null;
28205
+ this._private__placement = null;
28069
28206
  this._private__textOptions = {
28070
28207
  text: 'Double-click to edit',
28071
28208
  fontSize: 14,
@@ -28086,6 +28223,14 @@ class TextTool extends DrawingToolBase {
28086
28223
  this._registerOptions(this._private__textOptions);
28087
28224
  this._private__createPrimitive();
28088
28225
  this._private__setupClickOutsideHandler();
28226
+ if (this._private__textOptions.interactive) {
28227
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
28228
+ this.setPosition(point._internal_time, point._internal_price);
28229
+ if (this._private__textOptions.onComplete) {
28230
+ this._private__textOptions.onComplete();
28231
+ }
28232
+ });
28233
+ }
28089
28234
  }
28090
28235
  _private__setupClickOutsideHandler() {
28091
28236
  // Set up click handler to finish editing when clicking outside
@@ -28171,7 +28316,7 @@ class TextTool extends DrawingToolBase {
28171
28316
  this._private__requestUpdate();
28172
28317
  }
28173
28318
  isComplete() {
28174
- return !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
28319
+ return !this._private__placement?._internal_pending() && !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
28175
28320
  }
28176
28321
  setColor(color) {
28177
28322
  this._private__textOptions.textColor = color;
@@ -28454,6 +28599,7 @@ class TextTool extends DrawingToolBase {
28454
28599
  };
28455
28600
  }
28456
28601
  remove() {
28602
+ this._private__placement?._internal_stop();
28457
28603
  // Clean up click handler
28458
28604
  if (this._private__clickOutsideHandler) {
28459
28605
  this._chart.unsubscribeClick(this._private__clickOutsideHandler);
@@ -29312,6 +29458,7 @@ class PositionTool extends DrawingToolBase {
29312
29458
  this._private__paneView = null;
29313
29459
  this._private__isSelected = false;
29314
29460
  this._private__isHovered = false;
29461
+ this._private__placement = null;
29315
29462
  this._private__isDragging = false;
29316
29463
  this._private__dragTarget = null;
29317
29464
  this._private__grab = null;
@@ -29334,6 +29481,32 @@ class PositionTool extends DrawingToolBase {
29334
29481
  // Create primitive and setup
29335
29482
  this._private__createPrimitive();
29336
29483
  this._private__updateMetrics();
29484
+ if (this._private__positionOptions.interactive) {
29485
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
29486
+ }
29487
+ }
29488
+ _private__placeAt(point) {
29489
+ const options = this._private__positionOptions;
29490
+ const shift = point._internal_price - options.entryPrice;
29491
+ options.entryPrice = point._internal_price;
29492
+ options.entryTime = point._internal_time;
29493
+ if (options.stopLoss !== undefined) {
29494
+ options.stopLoss += shift;
29495
+ }
29496
+ if (options.takeProfit !== undefined) {
29497
+ options.takeProfit += shift;
29498
+ }
29499
+ options.endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? undefined;
29500
+ this._private__updateMetrics();
29501
+ this._private__requestUpdate();
29502
+ }
29503
+ isComplete() {
29504
+ return !this._private__placement?._internal_pending();
29505
+ }
29506
+ _private__span() {
29507
+ const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29508
+ const endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? entryX + defaultWidth(this._chart);
29509
+ return { _internal_entryX: entryX, _internal_endX: endX };
29337
29510
  }
29338
29511
  _private__createPrimitive() {
29339
29512
  // Create and cache the paneView once
@@ -29365,6 +29538,9 @@ class PositionTool extends DrawingToolBase {
29365
29538
  return new PositionPaneView(this);
29366
29539
  }
29367
29540
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29541
+ if (!this.isComplete()) {
29542
+ return;
29543
+ }
29368
29544
  // Draw profit rectangle (green)
29369
29545
  if (this._private__profitRect && this._private__positionOptions.takeProfit) {
29370
29546
  const timeScale = this._chart.timeScale();
@@ -29398,16 +29574,9 @@ class PositionTool extends DrawingToolBase {
29398
29574
  }
29399
29575
  }
29400
29576
  _private__drawRectangle(ctx, rect, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29401
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29402
- // Use endTime if specified, otherwise extend to chart edge
29403
- let endX;
29404
- if (this._private__positionOptions.endTime) {
29405
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29406
- }
29407
- else {
29408
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29409
- endX = chartWidth;
29410
- }
29577
+ const span = this._private__span();
29578
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29579
+ const endX = span._internal_endX * horizontalPixelRatio;
29411
29580
  const y1 = (this._series.priceToCoordinate(rect._internal_price1) ?? 0) * verticalPixelRatio;
29412
29581
  const y2 = (this._series.priceToCoordinate(rect._internal_price2) ?? 0) * verticalPixelRatio;
29413
29582
  // Draw filled rectangle with semi-transparent fill
@@ -29422,16 +29591,10 @@ class PositionTool extends DrawingToolBase {
29422
29591
  ctx.stroke();
29423
29592
  }
29424
29593
  _private__drawEntryLine(ctx, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29425
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29594
+ const span = this._private__span();
29595
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29596
+ const endX = span._internal_endX * horizontalPixelRatio;
29426
29597
  const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
29427
- let endX;
29428
- if (this._private__positionOptions.endTime) {
29429
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29430
- }
29431
- else {
29432
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29433
- endX = chartWidth;
29434
- }
29435
29598
  // Draw horizontal entry line (divider)
29436
29599
  const lineColor = this._private__positionOptions.entryLineColor;
29437
29600
  ctx.strokeStyle = lineColor;
@@ -29545,9 +29708,11 @@ class PositionTool extends DrawingToolBase {
29545
29708
  const padding = 6 * horizontalPixelRatio;
29546
29709
  const labelWidth = textMetrics.width + padding * 2;
29547
29710
  const labelHeight = 20 * horizontalPixelRatio;
29548
- // Position label at the start of the line
29711
+ // Outside the box, so a label wider than it does not cover the handles on its edge
29712
+ const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
29713
+ const gap = 3 * verticalPixelRatio;
29549
29714
  const labelX = entryX + 8 * horizontalPixelRatio;
29550
- const labelY = y - labelHeight / 2;
29715
+ const labelY = y <= entryY ? y - labelHeight - gap : y + gap;
29551
29716
  // Draw label background
29552
29717
  ctx.fillStyle = rect._internal_color;
29553
29718
  ctx.fillRect(labelX, labelY, labelWidth, labelHeight);
@@ -29555,10 +29720,12 @@ class PositionTool extends DrawingToolBase {
29555
29720
  ctx.fillStyle = '#ffffff';
29556
29721
  ctx.textAlign = 'left';
29557
29722
  ctx.textBaseline = 'middle';
29558
- ctx.fillText(labelText, labelX + padding, y);
29723
+ ctx.fillText(labelText, labelX + padding, labelY + labelHeight / 2);
29559
29724
  }
29560
29725
  _private__drawEndpoint(ctx, price, color, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29561
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29726
+ const span = this._private__span();
29727
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29728
+ const endX = span._internal_endX * horizontalPixelRatio;
29562
29729
  const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
29563
29730
  // Draw circle endpoint at entry
29564
29731
  const circleColor = color;
@@ -29570,14 +29737,6 @@ class PositionTool extends DrawingToolBase {
29570
29737
  ctx.fill();
29571
29738
  ctx.stroke();
29572
29739
  // Draw square endpoint at the right edge
29573
- let endX;
29574
- if (this._private__positionOptions.endTime) {
29575
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29576
- }
29577
- else {
29578
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29579
- endX = chartWidth - 10 * horizontalPixelRatio;
29580
- }
29581
29740
  const size = 8 * horizontalPixelRatio;
29582
29741
  const squareColor = color;
29583
29742
  ctx.fillStyle = squareColor;
@@ -29698,18 +29857,12 @@ class PositionTool extends DrawingToolBase {
29698
29857
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
29699
29858
  }
29700
29859
  hitTest(x, y) {
29701
- if (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit) {
29860
+ if (!this.isComplete() || (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit)) {
29702
29861
  return false;
29703
29862
  }
29704
- const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29705
- let endX;
29706
- if (this._private__positionOptions.endTime) {
29707
- endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
29708
- }
29709
- else {
29710
- const chartWidth = this._chart.chartElement().clientWidth;
29711
- endX = chartWidth;
29712
- }
29863
+ const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
29864
+ const left = Math.min(entryX, endX);
29865
+ const right = Math.max(entryX, endX);
29713
29866
  // Check if inside the entire Position Tool area (rectangles)
29714
29867
  if (this._private__positionOptions.takeProfit && this._private__positionOptions.stopLoss) {
29715
29868
  const tpY = this._series.priceToCoordinate(this._private__positionOptions.takeProfit) ?? 0;
@@ -29717,7 +29870,7 @@ class PositionTool extends DrawingToolBase {
29717
29870
  const minY = Math.min(tpY, slY);
29718
29871
  const maxY = Math.max(tpY, slY);
29719
29872
  // Check if inside the rectangle bounds (entire tool area)
29720
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29873
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29721
29874
  return true;
29722
29875
  }
29723
29876
  // Also check entry line area
@@ -29729,15 +29882,12 @@ class PositionTool extends DrawingToolBase {
29729
29882
  return false;
29730
29883
  }
29731
29884
  hitTestEndpoint(x, y) {
29732
- const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29733
- let endX;
29734
- if (this._private__positionOptions.endTime) {
29735
- endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
29736
- }
29737
- else {
29738
- const chartWidth = this._chart.chartElement().clientWidth;
29739
- endX = chartWidth;
29885
+ if (!this.isComplete()) {
29886
+ return null;
29740
29887
  }
29888
+ const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
29889
+ const left = Math.min(entryX, endX);
29890
+ const right = Math.max(entryX, endX);
29741
29891
  const tolerance = 10;
29742
29892
  // Check TP left endpoint (circle)
29743
29893
  if (this._private__positionOptions.takeProfit) {
@@ -29774,7 +29924,7 @@ class PositionTool extends DrawingToolBase {
29774
29924
  // Check entry line (middle divider line) - higher priority than 'move'
29775
29925
  const entryY = this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0;
29776
29926
  const lineTolerance = 8;
29777
- if (x >= entryX && x <= endX && Math.abs(y - entryY) <= lineTolerance) {
29927
+ if (x >= left && x <= right && Math.abs(y - entryY) <= lineTolerance) {
29778
29928
  return 'entry';
29779
29929
  }
29780
29930
  // Check if clicking on the body (rectangles) for moving entire tool
@@ -29784,7 +29934,7 @@ class PositionTool extends DrawingToolBase {
29784
29934
  const minY = Math.min(tpY, slY);
29785
29935
  const maxY = Math.max(tpY, slY);
29786
29936
  // Check if inside the rectangle bounds
29787
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29937
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29788
29938
  return 'move';
29789
29939
  }
29790
29940
  }
@@ -29884,6 +30034,7 @@ class PositionTool extends DrawingToolBase {
29884
30034
  this._private__requestUpdate();
29885
30035
  }
29886
30036
  remove() {
30037
+ this._private__placement?._internal_stop();
29887
30038
  if (this._private__primitive) {
29888
30039
  this._series.detachPrimitive(this._private__primitive);
29889
30040
  this._private__primitive = null;
@@ -29930,6 +30081,7 @@ class GanttPaneView {
29930
30081
  }
29931
30082
  }
29932
30083
 
30084
+ const TOP_PADDING = 50;
29933
30085
  class GanttTool extends DrawingToolBase {
29934
30086
  constructor(context, options) {
29935
30087
  super(context, 'gantt', options);
@@ -29943,6 +30095,7 @@ class GanttTool extends DrawingToolBase {
29943
30095
  this._private__grab = null;
29944
30096
  this._private__moveStart = null;
29945
30097
  this._private__hoveredSegmentIndex = -1;
30098
+ this._private__placement = null;
29946
30099
  this._private__ganttOptions = {
29947
30100
  segments: [],
29948
30101
  rowHeight: 40,
@@ -29955,6 +30108,43 @@ class GanttTool extends DrawingToolBase {
29955
30108
  };
29956
30109
  this._registerOptions(this._private__ganttOptions);
29957
30110
  this._private__createPrimitive();
30111
+ if (this._private__ganttOptions.interactive) {
30112
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
30113
+ }
30114
+ }
30115
+ _private__placeAt(point) {
30116
+ const segments = this._private__ganttOptions.segments ?? [];
30117
+ const endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? point._internal_time;
30118
+ const rowPitch = this._private__ganttOptions.rowHeight + this._private__ganttOptions.rowSpacing;
30119
+ const row = Math.max(0, Math.floor((point._internal_y - TOP_PADDING) / rowPitch));
30120
+ const shift = segments.length > 0 ? row - segments[0].level : 0;
30121
+ for (const segment of segments) {
30122
+ segment.startTime = point._internal_time;
30123
+ segment.endTime = endTime;
30124
+ segment.level = Math.max(0, segment.level + shift);
30125
+ }
30126
+ this._private__requestUpdate();
30127
+ }
30128
+ isComplete() {
30129
+ return !this._private__placement?._internal_pending();
30130
+ }
30131
+ selectionBox() {
30132
+ const segments = this._private__ganttOptions.segments ?? [];
30133
+ if (segments.length === 0) {
30134
+ return null;
30135
+ }
30136
+ const rowHeight = this._private__ganttOptions.rowHeight;
30137
+ const pitch = rowHeight + this._private__ganttOptions.rowSpacing;
30138
+ // Typed as the public shape: an inferred one gets its keys renamed by the build
30139
+ const box = { left: Infinity, top: Infinity, right: -Infinity, bottom: -Infinity };
30140
+ for (const segment of segments) {
30141
+ const top = TOP_PADDING + segment.level * pitch;
30142
+ box.left = Math.min(box.left, coordinateFromTime(this._chart, this._series, segment.startTime) ?? 0);
30143
+ box.right = Math.max(box.right, coordinateFromTime(this._chart, this._series, segment.endTime) ?? 0);
30144
+ box.top = Math.min(box.top, top);
30145
+ box.bottom = Math.max(box.bottom, top + rowHeight);
30146
+ }
30147
+ return box;
29958
30148
  }
29959
30149
  _private__createPrimitive() {
29960
30150
  this._private__paneView = this._private__createPaneView();
@@ -29985,11 +30175,10 @@ class GanttTool extends DrawingToolBase {
29985
30175
  return new GanttPaneView(this);
29986
30176
  }
29987
30177
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29988
- if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
30178
+ if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
29989
30179
  return;
29990
30180
  // Position gantt at top of chart with padding
29991
- const topPadding = 50; // pixels from top
29992
- const baseY = topPadding * verticalPixelRatio;
30181
+ const baseY = TOP_PADDING * verticalPixelRatio;
29993
30182
  const rowHeight = this._private__ganttOptions.rowHeight * verticalPixelRatio;
29994
30183
  const rowSpacing = this._private__ganttOptions.rowSpacing * verticalPixelRatio;
29995
30184
  // Draw grid lines if enabled
@@ -30168,11 +30357,10 @@ class GanttTool extends DrawingToolBase {
30168
30357
  this._private__moveStart = null;
30169
30358
  }
30170
30359
  hitTest(x, y) {
30171
- if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
30360
+ if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
30172
30361
  return false;
30173
30362
  }
30174
- const topPadding = 50;
30175
- const baseY = topPadding;
30363
+ const baseY = TOP_PADDING;
30176
30364
  const rowHeight = this._private__ganttOptions.rowHeight;
30177
30365
  const rowSpacing = this._private__ganttOptions.rowSpacing;
30178
30366
  // Check each segment
@@ -30226,6 +30414,7 @@ class GanttTool extends DrawingToolBase {
30226
30414
  this._private__requestUpdate();
30227
30415
  }
30228
30416
  remove() {
30417
+ this._private__placement?._internal_stop();
30229
30418
  if (this._private__primitive) {
30230
30419
  this._series.detachPrimitive(this._private__primitive);
30231
30420
  this._private__primitive = null;
@@ -30361,6 +30550,7 @@ class DrawingToolsManager {
30361
30550
  }
30362
30551
  this._private__completionCheck = setTimeout(() => {
30363
30552
  this._private__completionCheck = null;
30553
+ const completed = [];
30364
30554
  for (const id of Array.from(this._private__pendingToolIds)) {
30365
30555
  const tool = this._private__tools.get(id);
30366
30556
  if (!tool) {
@@ -30369,9 +30559,15 @@ class DrawingToolsManager {
30369
30559
  }
30370
30560
  if (tool.isComplete()) {
30371
30561
  this._private__pendingToolIds.delete(id);
30372
- this._private__drawingCompleted._internal_fire(id);
30562
+ completed.push(id);
30373
30563
  }
30374
30564
  }
30565
+ if (completed.length > 0) {
30566
+ this.recordChange();
30567
+ }
30568
+ for (const id of completed) {
30569
+ this._private__drawingCompleted._internal_fire(id);
30570
+ }
30375
30571
  }, 0);
30376
30572
  }
30377
30573
  /**
@@ -30431,12 +30627,16 @@ class DrawingToolsManager {
30431
30627
  if (this._private__restoring) {
30432
30628
  return;
30433
30629
  }
30630
+ const next = this.serializeAll();
30631
+ if (JSON.stringify(next) === JSON.stringify(this._private__committed)) {
30632
+ return;
30633
+ }
30434
30634
  this._private__past.push(this._private__committed);
30435
30635
  if (this._private__past.length > UNDO_HISTORY_LIMIT) {
30436
30636
  this._private__past.shift();
30437
30637
  }
30438
30638
  this._private__future = [];
30439
- this._private__committed = this.serializeAll();
30639
+ this._private__committed = next;
30440
30640
  }
30441
30641
  canUndo() {
30442
30642
  return this._private__past.length > 0;
@@ -30492,11 +30692,25 @@ class DrawingToolsManager {
30492
30692
  const tool = this._private__tools.get(this._private__selectedToolId);
30493
30693
  return tool ? this._private__boundsOf(tool) : null;
30494
30694
  }
30695
+ /** Relative to the chart element; the tool's box is relative to its pane. */
30696
+ _private__boundsOf(tool) {
30697
+ const box = tool.selectionBox() ?? this._private__boxFromPoints(tool);
30698
+ if (box === null) {
30699
+ return null;
30700
+ }
30701
+ const offset = this._private__paneOffsetOf(tool);
30702
+ return {
30703
+ left: box.left + offset._internal_x,
30704
+ right: box.right + offset._internal_x,
30705
+ top: box.top + offset._internal_y,
30706
+ bottom: box.bottom + offset._internal_y,
30707
+ };
30708
+ }
30495
30709
  /**
30496
30710
  * Bounds come from the tool's own serialized points, so this works for every
30497
30711
  * tool without each one having to report a box.
30498
30712
  */
30499
- _private__boundsOf(tool) {
30713
+ _private__boxFromPoints(tool) {
30500
30714
  const series = tool.series();
30501
30715
  const points = tool.serialize().points;
30502
30716
  let left = Number.POSITIVE_INFINITY;
@@ -30519,13 +30733,7 @@ class DrawingToolsManager {
30519
30733
  if (!found) {
30520
30734
  return null;
30521
30735
  }
30522
- const offset = this._private__paneOffsetOf(tool);
30523
- return {
30524
- left: left + offset._internal_x,
30525
- right: right + offset._internal_x,
30526
- top: top + offset._internal_y,
30527
- bottom: bottom + offset._internal_y,
30528
- };
30736
+ return { left, top, right, bottom };
30529
30737
  }
30530
30738
  /** Panes below the first one are offset inside the chart element. */
30531
30739
  _private__paneOffsetOf(tool) {
@@ -30609,7 +30817,10 @@ class DrawingToolsManager {
30609
30817
  serializeAll() {
30610
30818
  const result = [];
30611
30819
  for (const tool of this._private__tools.values()) {
30612
- result.push(tool.serialize());
30820
+ // Not drawn yet: undo would bring it back as a finished drawing
30821
+ if (tool.isComplete()) {
30822
+ result.push(tool.serialize());
30823
+ }
30613
30824
  }
30614
30825
  return result;
30615
30826
  }
@@ -31234,7 +31445,7 @@ const customSeriesDefaultOptions = {
31234
31445
  * Returns the current version as a string. For example `'1.0.0'`.
31235
31446
  */
31236
31447
  function version() {
31237
- return "2.3.13-dev+202609280001";
31448
+ return "2.3.15";
31238
31449
  }
31239
31450
 
31240
31451
  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 };