@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)
@@ -21644,6 +21644,13 @@
21644
21644
  series() {
21645
21645
  return this._series;
21646
21646
  }
21647
+ /**
21648
+ * Box the selection toolbar anchors to, in pane pixels. Null falls back to
21649
+ * the serialized points, which cannot place a line across the whole pane.
21650
+ */
21651
+ selectionBox() {
21652
+ return null;
21653
+ }
21647
21654
  getState() {
21648
21655
  return this._state;
21649
21656
  }
@@ -22320,6 +22327,53 @@
22320
22327
  return new TrendLineTool(context, options);
22321
22328
  }
22322
22329
 
22330
+ const END_HANDLE_MARGIN = 10;
22331
+ /**
22332
+ * Hands the next `clicks` clicks on the chart to `onClick`, past the last bar
22333
+ * too. A press that grabbed another drawing does not count.
22334
+ */
22335
+ function placeByClicks(chart, series, clicks, onClick) {
22336
+ let placed = 0;
22337
+ let handler = null;
22338
+ const stop = () => {
22339
+ if (handler !== null) {
22340
+ chart.unsubscribeClick(handler);
22341
+ handler = null;
22342
+ }
22343
+ };
22344
+ handler = (param) => {
22345
+ if (!param.point || consumeInputClaim(chart)) {
22346
+ return;
22347
+ }
22348
+ const price = series.coordinateToPrice(param.point.y);
22349
+ const time = timeFromCoordinate(chart, series, param.point.x);
22350
+ if (price === null || time === null) {
22351
+ return;
22352
+ }
22353
+ placed++;
22354
+ if (placed >= clicks) {
22355
+ stop();
22356
+ }
22357
+ onClick({ _internal_time: time, _internal_price: price, _internal_x: param.point.x, _internal_y: param.point.y }, placed - 1);
22358
+ };
22359
+ chart.subscribeClick(handler);
22360
+ return {
22361
+ _internal_pending: () => handler !== null,
22362
+ _internal_stop: stop,
22363
+ };
22364
+ }
22365
+ /** A fifth of the pane, and never less than a bar. */
22366
+ function defaultWidth(chart) {
22367
+ const timeScale = chart.timeScale();
22368
+ return Math.max(timeScale.width() / 5, timeScale.options().barSpacing);
22369
+ }
22370
+ /** Kept inside the pane: past it, the handles on the end hide under the price axis. */
22371
+ function placedEndX(chart, x) {
22372
+ const timeScale = chart.timeScale();
22373
+ const onScreen = Math.min(x + defaultWidth(chart), timeScale.width() - END_HANDLE_MARGIN);
22374
+ return Math.max(onScreen, x + timeScale.options().barSpacing);
22375
+ }
22376
+
22323
22377
  class HorizontalLinePaneRenderer {
22324
22378
  constructor(tool) {
22325
22379
  this._private__tool = tool;
@@ -22360,6 +22414,7 @@
22360
22414
  this._private__isDragging = false;
22361
22415
  this._private__grab = null;
22362
22416
  this._private__moveStartPrice = 0;
22417
+ this._private__placement = null;
22363
22418
  this._private__hLineOptions = {
22364
22419
  lineColor: '#2962ff',
22365
22420
  lineWidth: 2,
@@ -22372,6 +22427,26 @@
22372
22427
  };
22373
22428
  this._registerOptions(this._private__hLineOptions);
22374
22429
  this._private__createPrimitive();
22430
+ if (this._private__hLineOptions.interactive) {
22431
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
22432
+ }
22433
+ }
22434
+ _private__placeAt(point) {
22435
+ this._private__hLineOptions.price = point._internal_price;
22436
+ if (this._private__hLineOptions.startTime !== undefined) {
22437
+ this._private__hLineOptions.startTime = point._internal_time;
22438
+ }
22439
+ this._private__requestUpdate();
22440
+ }
22441
+ isComplete() {
22442
+ return !this._private__placement?._internal_pending();
22443
+ }
22444
+ selectionBox() {
22445
+ const y = this._private__hLineOptions.price === undefined ? null : this._series.priceToCoordinate(this._private__hLineOptions.price);
22446
+ if (y === null) {
22447
+ return null;
22448
+ }
22449
+ return { left: this._private__startX(), right: this._chart.timeScale().width(), top: y, bottom: y };
22375
22450
  }
22376
22451
  _private__createPrimitive() {
22377
22452
  this._private__paneView = this._private__createPaneView();
@@ -22409,7 +22484,7 @@
22409
22484
  return coordinateFromTime(this._chart, this._series, this._private__hLineOptions.startTime) ?? 0;
22410
22485
  }
22411
22486
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
22412
- if (this._private__hLineOptions.price === undefined)
22487
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined)
22413
22488
  return;
22414
22489
  const y = (this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0) * verticalPixelRatio;
22415
22490
  const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
@@ -22503,7 +22578,7 @@
22503
22578
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
22504
22579
  }
22505
22580
  hitTest(x, y) {
22506
- if (this._private__hLineOptions.price === undefined) {
22581
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined) {
22507
22582
  return false;
22508
22583
  }
22509
22584
  const lineY = this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0;
@@ -22578,6 +22653,7 @@
22578
22653
  this._private__requestUpdate();
22579
22654
  }
22580
22655
  remove() {
22656
+ this._private__placement?._internal_stop();
22581
22657
  if (this._private__primitive) {
22582
22658
  this._series.detachPrimitive(this._private__primitive);
22583
22659
  this._private__primitive = null;
@@ -22637,6 +22713,7 @@
22637
22713
  this._private__grab = null;
22638
22714
  this._private__moveStartTime = 0;
22639
22715
  this._private__moveStartPrice = 0;
22716
+ this._private__placement = null;
22640
22717
  this._private__crossOptions = {
22641
22718
  lineColor: '#787b86',
22642
22719
  lineWidth: 1,
@@ -22646,6 +22723,16 @@
22646
22723
  };
22647
22724
  this._registerOptions(this._private__crossOptions);
22648
22725
  this._private__createPrimitive();
22726
+ if (this._private__crossOptions.interactive) {
22727
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
22728
+ this._private__crossOptions.time = point._internal_time;
22729
+ this._private__crossOptions.price = point._internal_price;
22730
+ this._private__requestUpdate();
22731
+ });
22732
+ }
22733
+ }
22734
+ isComplete() {
22735
+ return !this._private__placement?._internal_pending();
22649
22736
  }
22650
22737
  _private__createPrimitive() {
22651
22738
  this._private__paneView = new CrossLinePaneView(this);
@@ -22673,7 +22760,7 @@
22673
22760
  this._private__requestUpdate();
22674
22761
  }
22675
22762
  _private__coordinates() {
22676
- if (this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
22763
+ if (!this.isComplete() || this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
22677
22764
  return null;
22678
22765
  }
22679
22766
  const x = coordinateFromTime(this._chart, this._series, this._private__crossOptions.time);
@@ -22796,6 +22883,7 @@
22796
22883
  return this._private__isHovered;
22797
22884
  }
22798
22885
  remove() {
22886
+ this._private__placement?._internal_stop();
22799
22887
  if (this._private__primitive) {
22800
22888
  try {
22801
22889
  this._series.detachPrimitive(this._private__primitive);
@@ -24218,6 +24306,7 @@
24218
24306
  this._private__isDragging = false;
24219
24307
  this._private__grab = null;
24220
24308
  this._private__moveStartTime = 0;
24309
+ this._private__placement = null;
24221
24310
  this._private__vLineOptions = {
24222
24311
  lineColor: '#787b86',
24223
24312
  lineWidth: 1,
@@ -24229,6 +24318,29 @@
24229
24318
  };
24230
24319
  this._registerOptions(this._private__vLineOptions);
24231
24320
  this._private__createPrimitive();
24321
+ if (this._private__vLineOptions.interactive) {
24322
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
24323
+ this._private__vLineOptions.time = point._internal_time;
24324
+ this._private__requestUpdate();
24325
+ });
24326
+ }
24327
+ }
24328
+ isComplete() {
24329
+ return !this._private__placement?._internal_pending();
24330
+ }
24331
+ selectionBox() {
24332
+ const x = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time);
24333
+ if (x === null) {
24334
+ return null;
24335
+ }
24336
+ let middle;
24337
+ try {
24338
+ middle = this._series.getPane().getHeight() / 2;
24339
+ }
24340
+ catch (error) {
24341
+ return null;
24342
+ }
24343
+ return { left: x, right: x, top: middle, bottom: middle };
24232
24344
  }
24233
24345
  _private__createPrimitive() {
24234
24346
  this._private__paneView = this._private__createPaneView();
@@ -24259,7 +24371,7 @@
24259
24371
  return new VerticalLinePaneView(this);
24260
24372
  }
24261
24373
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
24262
- if (this._private__vLineOptions.time === undefined)
24374
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined)
24263
24375
  return;
24264
24376
  const x = (coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0) * horizontalPixelRatio;
24265
24377
  const chartHeight = this._chart.chartElement().clientHeight * verticalPixelRatio;
@@ -24346,7 +24458,7 @@
24346
24458
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
24347
24459
  }
24348
24460
  hitTest(x, y) {
24349
- if (this._private__vLineOptions.time === undefined) {
24461
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined) {
24350
24462
  return false;
24351
24463
  }
24352
24464
  const lineX = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0;
@@ -24416,6 +24528,7 @@
24416
24528
  this._private__requestUpdate();
24417
24529
  }
24418
24530
  remove() {
24531
+ this._private__placement?._internal_stop();
24419
24532
  if (this._private__primitive) {
24420
24533
  this._series.detachPrimitive(this._private__primitive);
24421
24534
  this._private__primitive = null;
@@ -25137,6 +25250,7 @@
25137
25250
  this._private__isHovered = false;
25138
25251
  this._private__isDragging = false;
25139
25252
  this._private__dragTarget = null;
25253
+ this._private__placement = null;
25140
25254
  this._private__fibOptions = {
25141
25255
  levels: [
25142
25256
  { level: 0, color: '#787B86', label: '0%' },
@@ -25152,6 +25266,20 @@
25152
25266
  ...options,
25153
25267
  };
25154
25268
  this._private__createPrimitive();
25269
+ if (this._private__fibOptions.interactive) {
25270
+ this._private__placement = placeByClicks(this._chart, this._series, 3, (point, index) => {
25271
+ const placed = { time: point._internal_time, price: point._internal_price };
25272
+ if (index === 0) {
25273
+ this.setPoint1(placed);
25274
+ }
25275
+ else if (index === 1) {
25276
+ this.setPoint2(placed);
25277
+ }
25278
+ else {
25279
+ this.setPoint3(placed);
25280
+ }
25281
+ });
25282
+ }
25155
25283
  }
25156
25284
  _private__createPrimitive() {
25157
25285
  this._private__paneView = this._private__createPaneView();
@@ -25209,7 +25337,7 @@
25209
25337
  this._private__requestUpdate();
25210
25338
  }
25211
25339
  isComplete() {
25212
- return !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
25340
+ return !this._private__placement?._internal_pending() && !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
25213
25341
  }
25214
25342
  setColor(color) {
25215
25343
  this._private__fibOptions.lineColor = color;
@@ -25504,6 +25632,7 @@
25504
25632
  this._private__requestUpdate();
25505
25633
  }
25506
25634
  remove() {
25635
+ this._private__placement?._internal_stop();
25507
25636
  if (this._private__primitive) {
25508
25637
  this._series.detachPrimitive(this._private__primitive);
25509
25638
  this._private__primitive = null;
@@ -28069,6 +28198,7 @@
28069
28198
  this._private__moveStart = null;
28070
28199
  this._private__isEditing = false;
28071
28200
  this._private__clickOutsideHandler = null;
28201
+ this._private__placement = null;
28072
28202
  this._private__textOptions = {
28073
28203
  text: 'Double-click to edit',
28074
28204
  fontSize: 14,
@@ -28089,6 +28219,14 @@
28089
28219
  this._registerOptions(this._private__textOptions);
28090
28220
  this._private__createPrimitive();
28091
28221
  this._private__setupClickOutsideHandler();
28222
+ if (this._private__textOptions.interactive) {
28223
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
28224
+ this.setPosition(point._internal_time, point._internal_price);
28225
+ if (this._private__textOptions.onComplete) {
28226
+ this._private__textOptions.onComplete();
28227
+ }
28228
+ });
28229
+ }
28092
28230
  }
28093
28231
  _private__setupClickOutsideHandler() {
28094
28232
  // Set up click handler to finish editing when clicking outside
@@ -28174,7 +28312,7 @@
28174
28312
  this._private__requestUpdate();
28175
28313
  }
28176
28314
  isComplete() {
28177
- return !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
28315
+ return !this._private__placement?._internal_pending() && !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
28178
28316
  }
28179
28317
  setColor(color) {
28180
28318
  this._private__textOptions.textColor = color;
@@ -28457,6 +28595,7 @@
28457
28595
  };
28458
28596
  }
28459
28597
  remove() {
28598
+ this._private__placement?._internal_stop();
28460
28599
  // Clean up click handler
28461
28600
  if (this._private__clickOutsideHandler) {
28462
28601
  this._chart.unsubscribeClick(this._private__clickOutsideHandler);
@@ -29315,6 +29454,7 @@
29315
29454
  this._private__paneView = null;
29316
29455
  this._private__isSelected = false;
29317
29456
  this._private__isHovered = false;
29457
+ this._private__placement = null;
29318
29458
  this._private__isDragging = false;
29319
29459
  this._private__dragTarget = null;
29320
29460
  this._private__grab = null;
@@ -29337,6 +29477,32 @@
29337
29477
  // Create primitive and setup
29338
29478
  this._private__createPrimitive();
29339
29479
  this._private__updateMetrics();
29480
+ if (this._private__positionOptions.interactive) {
29481
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
29482
+ }
29483
+ }
29484
+ _private__placeAt(point) {
29485
+ const options = this._private__positionOptions;
29486
+ const shift = point._internal_price - options.entryPrice;
29487
+ options.entryPrice = point._internal_price;
29488
+ options.entryTime = point._internal_time;
29489
+ if (options.stopLoss !== undefined) {
29490
+ options.stopLoss += shift;
29491
+ }
29492
+ if (options.takeProfit !== undefined) {
29493
+ options.takeProfit += shift;
29494
+ }
29495
+ options.endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? undefined;
29496
+ this._private__updateMetrics();
29497
+ this._private__requestUpdate();
29498
+ }
29499
+ isComplete() {
29500
+ return !this._private__placement?._internal_pending();
29501
+ }
29502
+ _private__span() {
29503
+ const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29504
+ const endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? entryX + defaultWidth(this._chart);
29505
+ return { _internal_entryX: entryX, _internal_endX: endX };
29340
29506
  }
29341
29507
  _private__createPrimitive() {
29342
29508
  // Create and cache the paneView once
@@ -29368,6 +29534,9 @@
29368
29534
  return new PositionPaneView(this);
29369
29535
  }
29370
29536
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29537
+ if (!this.isComplete()) {
29538
+ return;
29539
+ }
29371
29540
  // Draw profit rectangle (green)
29372
29541
  if (this._private__profitRect && this._private__positionOptions.takeProfit) {
29373
29542
  const timeScale = this._chart.timeScale();
@@ -29401,16 +29570,9 @@
29401
29570
  }
29402
29571
  }
29403
29572
  _private__drawRectangle(ctx, rect, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29404
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29405
- // Use endTime if specified, otherwise extend to chart edge
29406
- let endX;
29407
- if (this._private__positionOptions.endTime) {
29408
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29409
- }
29410
- else {
29411
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29412
- endX = chartWidth;
29413
- }
29573
+ const span = this._private__span();
29574
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29575
+ const endX = span._internal_endX * horizontalPixelRatio;
29414
29576
  const y1 = (this._series.priceToCoordinate(rect._internal_price1) ?? 0) * verticalPixelRatio;
29415
29577
  const y2 = (this._series.priceToCoordinate(rect._internal_price2) ?? 0) * verticalPixelRatio;
29416
29578
  // Draw filled rectangle with semi-transparent fill
@@ -29425,16 +29587,10 @@
29425
29587
  ctx.stroke();
29426
29588
  }
29427
29589
  _private__drawEntryLine(ctx, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29428
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29590
+ const span = this._private__span();
29591
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29592
+ const endX = span._internal_endX * horizontalPixelRatio;
29429
29593
  const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
29430
- let endX;
29431
- if (this._private__positionOptions.endTime) {
29432
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29433
- }
29434
- else {
29435
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29436
- endX = chartWidth;
29437
- }
29438
29594
  // Draw horizontal entry line (divider)
29439
29595
  const lineColor = this._private__positionOptions.entryLineColor;
29440
29596
  ctx.strokeStyle = lineColor;
@@ -29548,9 +29704,11 @@
29548
29704
  const padding = 6 * horizontalPixelRatio;
29549
29705
  const labelWidth = textMetrics.width + padding * 2;
29550
29706
  const labelHeight = 20 * horizontalPixelRatio;
29551
- // Position label at the start of the line
29707
+ // Outside the box, so a label wider than it does not cover the handles on its edge
29708
+ const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
29709
+ const gap = 3 * verticalPixelRatio;
29552
29710
  const labelX = entryX + 8 * horizontalPixelRatio;
29553
- const labelY = y - labelHeight / 2;
29711
+ const labelY = y <= entryY ? y - labelHeight - gap : y + gap;
29554
29712
  // Draw label background
29555
29713
  ctx.fillStyle = rect._internal_color;
29556
29714
  ctx.fillRect(labelX, labelY, labelWidth, labelHeight);
@@ -29558,10 +29716,12 @@
29558
29716
  ctx.fillStyle = '#ffffff';
29559
29717
  ctx.textAlign = 'left';
29560
29718
  ctx.textBaseline = 'middle';
29561
- ctx.fillText(labelText, labelX + padding, y);
29719
+ ctx.fillText(labelText, labelX + padding, labelY + labelHeight / 2);
29562
29720
  }
29563
29721
  _private__drawEndpoint(ctx, price, color, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29564
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29722
+ const span = this._private__span();
29723
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29724
+ const endX = span._internal_endX * horizontalPixelRatio;
29565
29725
  const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
29566
29726
  // Draw circle endpoint at entry
29567
29727
  const circleColor = color;
@@ -29573,14 +29733,6 @@
29573
29733
  ctx.fill();
29574
29734
  ctx.stroke();
29575
29735
  // Draw square endpoint at the right edge
29576
- let endX;
29577
- if (this._private__positionOptions.endTime) {
29578
- endX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0) * horizontalPixelRatio;
29579
- }
29580
- else {
29581
- const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
29582
- endX = chartWidth - 10 * horizontalPixelRatio;
29583
- }
29584
29736
  const size = 8 * horizontalPixelRatio;
29585
29737
  const squareColor = color;
29586
29738
  ctx.fillStyle = squareColor;
@@ -29701,18 +29853,12 @@
29701
29853
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
29702
29854
  }
29703
29855
  hitTest(x, y) {
29704
- if (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit) {
29856
+ if (!this.isComplete() || (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit)) {
29705
29857
  return false;
29706
29858
  }
29707
- const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29708
- let endX;
29709
- if (this._private__positionOptions.endTime) {
29710
- endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
29711
- }
29712
- else {
29713
- const chartWidth = this._chart.chartElement().clientWidth;
29714
- endX = chartWidth;
29715
- }
29859
+ const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
29860
+ const left = Math.min(entryX, endX);
29861
+ const right = Math.max(entryX, endX);
29716
29862
  // Check if inside the entire Position Tool area (rectangles)
29717
29863
  if (this._private__positionOptions.takeProfit && this._private__positionOptions.stopLoss) {
29718
29864
  const tpY = this._series.priceToCoordinate(this._private__positionOptions.takeProfit) ?? 0;
@@ -29720,7 +29866,7 @@
29720
29866
  const minY = Math.min(tpY, slY);
29721
29867
  const maxY = Math.max(tpY, slY);
29722
29868
  // Check if inside the rectangle bounds (entire tool area)
29723
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29869
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29724
29870
  return true;
29725
29871
  }
29726
29872
  // Also check entry line area
@@ -29732,15 +29878,12 @@
29732
29878
  return false;
29733
29879
  }
29734
29880
  hitTestEndpoint(x, y) {
29735
- const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29736
- let endX;
29737
- if (this._private__positionOptions.endTime) {
29738
- endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? 0;
29739
- }
29740
- else {
29741
- const chartWidth = this._chart.chartElement().clientWidth;
29742
- endX = chartWidth;
29881
+ if (!this.isComplete()) {
29882
+ return null;
29743
29883
  }
29884
+ const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
29885
+ const left = Math.min(entryX, endX);
29886
+ const right = Math.max(entryX, endX);
29744
29887
  const tolerance = 10;
29745
29888
  // Check TP left endpoint (circle)
29746
29889
  if (this._private__positionOptions.takeProfit) {
@@ -29777,7 +29920,7 @@
29777
29920
  // Check entry line (middle divider line) - higher priority than 'move'
29778
29921
  const entryY = this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0;
29779
29922
  const lineTolerance = 8;
29780
- if (x >= entryX && x <= endX && Math.abs(y - entryY) <= lineTolerance) {
29923
+ if (x >= left && x <= right && Math.abs(y - entryY) <= lineTolerance) {
29781
29924
  return 'entry';
29782
29925
  }
29783
29926
  // Check if clicking on the body (rectangles) for moving entire tool
@@ -29787,7 +29930,7 @@
29787
29930
  const minY = Math.min(tpY, slY);
29788
29931
  const maxY = Math.max(tpY, slY);
29789
29932
  // Check if inside the rectangle bounds
29790
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29933
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29791
29934
  return 'move';
29792
29935
  }
29793
29936
  }
@@ -29887,6 +30030,7 @@
29887
30030
  this._private__requestUpdate();
29888
30031
  }
29889
30032
  remove() {
30033
+ this._private__placement?._internal_stop();
29890
30034
  if (this._private__primitive) {
29891
30035
  this._series.detachPrimitive(this._private__primitive);
29892
30036
  this._private__primitive = null;
@@ -29933,6 +30077,7 @@
29933
30077
  }
29934
30078
  }
29935
30079
 
30080
+ const TOP_PADDING = 50;
29936
30081
  class GanttTool extends DrawingToolBase {
29937
30082
  constructor(context, options) {
29938
30083
  super(context, 'gantt', options);
@@ -29946,6 +30091,7 @@
29946
30091
  this._private__grab = null;
29947
30092
  this._private__moveStart = null;
29948
30093
  this._private__hoveredSegmentIndex = -1;
30094
+ this._private__placement = null;
29949
30095
  this._private__ganttOptions = {
29950
30096
  segments: [],
29951
30097
  rowHeight: 40,
@@ -29958,6 +30104,43 @@
29958
30104
  };
29959
30105
  this._registerOptions(this._private__ganttOptions);
29960
30106
  this._private__createPrimitive();
30107
+ if (this._private__ganttOptions.interactive) {
30108
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
30109
+ }
30110
+ }
30111
+ _private__placeAt(point) {
30112
+ const segments = this._private__ganttOptions.segments ?? [];
30113
+ const endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? point._internal_time;
30114
+ const rowPitch = this._private__ganttOptions.rowHeight + this._private__ganttOptions.rowSpacing;
30115
+ const row = Math.max(0, Math.floor((point._internal_y - TOP_PADDING) / rowPitch));
30116
+ const shift = segments.length > 0 ? row - segments[0].level : 0;
30117
+ for (const segment of segments) {
30118
+ segment.startTime = point._internal_time;
30119
+ segment.endTime = endTime;
30120
+ segment.level = Math.max(0, segment.level + shift);
30121
+ }
30122
+ this._private__requestUpdate();
30123
+ }
30124
+ isComplete() {
30125
+ return !this._private__placement?._internal_pending();
30126
+ }
30127
+ selectionBox() {
30128
+ const segments = this._private__ganttOptions.segments ?? [];
30129
+ if (segments.length === 0) {
30130
+ return null;
30131
+ }
30132
+ const rowHeight = this._private__ganttOptions.rowHeight;
30133
+ const pitch = rowHeight + this._private__ganttOptions.rowSpacing;
30134
+ // Typed as the public shape: an inferred one gets its keys renamed by the build
30135
+ const box = { left: Infinity, top: Infinity, right: -Infinity, bottom: -Infinity };
30136
+ for (const segment of segments) {
30137
+ const top = TOP_PADDING + segment.level * pitch;
30138
+ box.left = Math.min(box.left, coordinateFromTime(this._chart, this._series, segment.startTime) ?? 0);
30139
+ box.right = Math.max(box.right, coordinateFromTime(this._chart, this._series, segment.endTime) ?? 0);
30140
+ box.top = Math.min(box.top, top);
30141
+ box.bottom = Math.max(box.bottom, top + rowHeight);
30142
+ }
30143
+ return box;
29961
30144
  }
29962
30145
  _private__createPrimitive() {
29963
30146
  this._private__paneView = this._private__createPaneView();
@@ -29988,11 +30171,10 @@
29988
30171
  return new GanttPaneView(this);
29989
30172
  }
29990
30173
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29991
- if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
30174
+ if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
29992
30175
  return;
29993
30176
  // Position gantt at top of chart with padding
29994
- const topPadding = 50; // pixels from top
29995
- const baseY = topPadding * verticalPixelRatio;
30177
+ const baseY = TOP_PADDING * verticalPixelRatio;
29996
30178
  const rowHeight = this._private__ganttOptions.rowHeight * verticalPixelRatio;
29997
30179
  const rowSpacing = this._private__ganttOptions.rowSpacing * verticalPixelRatio;
29998
30180
  // Draw grid lines if enabled
@@ -30171,11 +30353,10 @@
30171
30353
  this._private__moveStart = null;
30172
30354
  }
30173
30355
  hitTest(x, y) {
30174
- if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
30356
+ if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
30175
30357
  return false;
30176
30358
  }
30177
- const topPadding = 50;
30178
- const baseY = topPadding;
30359
+ const baseY = TOP_PADDING;
30179
30360
  const rowHeight = this._private__ganttOptions.rowHeight;
30180
30361
  const rowSpacing = this._private__ganttOptions.rowSpacing;
30181
30362
  // Check each segment
@@ -30229,6 +30410,7 @@
30229
30410
  this._private__requestUpdate();
30230
30411
  }
30231
30412
  remove() {
30413
+ this._private__placement?._internal_stop();
30232
30414
  if (this._private__primitive) {
30233
30415
  this._series.detachPrimitive(this._private__primitive);
30234
30416
  this._private__primitive = null;
@@ -30364,6 +30546,7 @@
30364
30546
  }
30365
30547
  this._private__completionCheck = setTimeout(() => {
30366
30548
  this._private__completionCheck = null;
30549
+ const completed = [];
30367
30550
  for (const id of Array.from(this._private__pendingToolIds)) {
30368
30551
  const tool = this._private__tools.get(id);
30369
30552
  if (!tool) {
@@ -30372,9 +30555,15 @@
30372
30555
  }
30373
30556
  if (tool.isComplete()) {
30374
30557
  this._private__pendingToolIds.delete(id);
30375
- this._private__drawingCompleted._internal_fire(id);
30558
+ completed.push(id);
30376
30559
  }
30377
30560
  }
30561
+ if (completed.length > 0) {
30562
+ this.recordChange();
30563
+ }
30564
+ for (const id of completed) {
30565
+ this._private__drawingCompleted._internal_fire(id);
30566
+ }
30378
30567
  }, 0);
30379
30568
  }
30380
30569
  /**
@@ -30434,12 +30623,16 @@
30434
30623
  if (this._private__restoring) {
30435
30624
  return;
30436
30625
  }
30626
+ const next = this.serializeAll();
30627
+ if (JSON.stringify(next) === JSON.stringify(this._private__committed)) {
30628
+ return;
30629
+ }
30437
30630
  this._private__past.push(this._private__committed);
30438
30631
  if (this._private__past.length > UNDO_HISTORY_LIMIT) {
30439
30632
  this._private__past.shift();
30440
30633
  }
30441
30634
  this._private__future = [];
30442
- this._private__committed = this.serializeAll();
30635
+ this._private__committed = next;
30443
30636
  }
30444
30637
  canUndo() {
30445
30638
  return this._private__past.length > 0;
@@ -30495,11 +30688,25 @@
30495
30688
  const tool = this._private__tools.get(this._private__selectedToolId);
30496
30689
  return tool ? this._private__boundsOf(tool) : null;
30497
30690
  }
30691
+ /** Relative to the chart element; the tool's box is relative to its pane. */
30692
+ _private__boundsOf(tool) {
30693
+ const box = tool.selectionBox() ?? this._private__boxFromPoints(tool);
30694
+ if (box === null) {
30695
+ return null;
30696
+ }
30697
+ const offset = this._private__paneOffsetOf(tool);
30698
+ return {
30699
+ left: box.left + offset._internal_x,
30700
+ right: box.right + offset._internal_x,
30701
+ top: box.top + offset._internal_y,
30702
+ bottom: box.bottom + offset._internal_y,
30703
+ };
30704
+ }
30498
30705
  /**
30499
30706
  * Bounds come from the tool's own serialized points, so this works for every
30500
30707
  * tool without each one having to report a box.
30501
30708
  */
30502
- _private__boundsOf(tool) {
30709
+ _private__boxFromPoints(tool) {
30503
30710
  const series = tool.series();
30504
30711
  const points = tool.serialize().points;
30505
30712
  let left = Number.POSITIVE_INFINITY;
@@ -30522,13 +30729,7 @@
30522
30729
  if (!found) {
30523
30730
  return null;
30524
30731
  }
30525
- const offset = this._private__paneOffsetOf(tool);
30526
- return {
30527
- left: left + offset._internal_x,
30528
- right: right + offset._internal_x,
30529
- top: top + offset._internal_y,
30530
- bottom: bottom + offset._internal_y,
30531
- };
30732
+ return { left, top, right, bottom };
30532
30733
  }
30533
30734
  /** Panes below the first one are offset inside the chart element. */
30534
30735
  _private__paneOffsetOf(tool) {
@@ -30612,7 +30813,10 @@
30612
30813
  serializeAll() {
30613
30814
  const result = [];
30614
30815
  for (const tool of this._private__tools.values()) {
30615
- result.push(tool.serialize());
30816
+ // Not drawn yet: undo would bring it back as a finished drawing
30817
+ if (tool.isComplete()) {
30818
+ result.push(tool.serialize());
30819
+ }
30616
30820
  }
30617
30821
  return result;
30618
30822
  }
@@ -31237,7 +31441,7 @@
31237
31441
  * Returns the current version as a string. For example `'1.0.0'`.
31238
31442
  */
31239
31443
  function version() {
31240
- return "2.3.13-dev+202609280001";
31444
+ return "2.3.14";
31241
31445
  }
31242
31446
 
31243
31447
  var PipsendChartsModule = /*#__PURE__*/Object.freeze({