@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)
@@ -14687,6 +14687,9 @@
14687
14687
  function shapeMargin(barSpacing) {
14688
14688
  return Math.max(size(barSpacing, 0.1), 3 /* Constants.MinShapeMargin */);
14689
14689
  }
14690
+ function barGap(barSpacing) {
14691
+ return Math.max(shapeMargin(barSpacing), 10 /* Constants.MinBarGap */);
14692
+ }
14690
14693
  function calculateAdjustedMargin(margin, hasSide, hasInBar) {
14691
14694
  if (hasSide) {
14692
14695
  return margin;
@@ -15313,17 +15316,18 @@
15313
15316
  }
15314
15317
  let prevTimeIndex = NaN;
15315
15318
  const shapeMargin$1 = shapeMargin(timeScale.options().barSpacing);
15319
+ const barGap$1 = barGap(timeScale.options().barSpacing);
15316
15320
  const offsets = {
15317
- _internal_aboveBar: shapeMargin$1,
15318
- _internal_belowBar: shapeMargin$1,
15321
+ _internal_aboveBar: barGap$1,
15322
+ _internal_belowBar: barGap$1,
15319
15323
  };
15320
15324
  this._private__data._internal_visibleRange = visibleTimedValues(this._private__data._internal_items, visibleBarsRange, true);
15321
15325
  for (let index = this._private__data._internal_visibleRange.from; index < this._private__data._internal_visibleRange.to; index++) {
15322
15326
  const marker = seriesMarkers[index];
15323
15327
  if (marker.time !== prevTimeIndex) {
15324
15328
  // new bar, reset stack counter
15325
- offsets._internal_aboveBar = shapeMargin$1;
15326
- offsets._internal_belowBar = shapeMargin$1;
15329
+ offsets._internal_aboveBar = barGap$1;
15330
+ offsets._internal_belowBar = barGap$1;
15327
15331
  prevTimeIndex = marker.time;
15328
15332
  }
15329
15333
  const rendererItem = this._private__data._internal_items[index];
@@ -15440,7 +15444,7 @@
15440
15444
  this._private__cachedBarSpacing = barSpacing;
15441
15445
  if (this._private__markers.length > 0) {
15442
15446
  const shapeMargin$1 = shapeMargin(barSpacing);
15443
- const marginValue = calculateShapeHeight(barSpacing) * 1.5 + shapeMargin$1 * 2;
15447
+ const marginValue = calculateShapeHeight(barSpacing) * 1.5 + shapeMargin$1 + barGap(barSpacing);
15444
15448
  const positions = this._private__getMarkerPositions();
15445
15449
  this._private__autoScaleMargins = {
15446
15450
  above: calculateAdjustedMargin(marginValue, positions.aboveBar, positions.inBar),
@@ -17943,10 +17947,13 @@
17943
17947
  const blankSource = (blank) => {
17944
17948
  if (blank && restoreSourcePaint === null) {
17945
17949
  restoreSourcePaint = hideSeriesPaint(series);
17950
+ // Under the blank source, so what hangs off it draws over the candles.
17951
+ haSeries.setSeriesOrder(series.seriesOrder());
17946
17952
  }
17947
17953
  else if (!blank && restoreSourcePaint !== null) {
17948
17954
  restoreSourcePaint();
17949
17955
  restoreSourcePaint = null;
17956
+ haSeries.setSeriesOrder(series.seriesOrder() + 1);
17950
17957
  }
17951
17958
  };
17952
17959
  const runtime = bindIndicator({
@@ -17975,8 +17982,9 @@
17975
17982
  blankSource(current.hideSource === true);
17976
17983
  },
17977
17984
  });
17978
- const handle = {
17979
- _internal_series: haSeries,
17985
+ // Returned as a literal: a local annotation would let the property renamer mangle `series`.
17986
+ return {
17987
+ series: haSeries,
17980
17988
  options: runtime.options,
17981
17989
  applyOptions: runtime.applyOptions,
17982
17990
  recalculate: runtime.recalculate,
@@ -17992,7 +18000,6 @@
17992
18000
  }
17993
18001
  },
17994
18002
  };
17995
- return handle;
17996
18003
  }
17997
18004
 
17998
18005
  /**
@@ -21644,6 +21651,13 @@
21644
21651
  series() {
21645
21652
  return this._series;
21646
21653
  }
21654
+ /**
21655
+ * Box the selection toolbar anchors to, in pane pixels. Null falls back to
21656
+ * the serialized points, which cannot place a line across the whole pane.
21657
+ */
21658
+ selectionBox() {
21659
+ return null;
21660
+ }
21647
21661
  getState() {
21648
21662
  return this._state;
21649
21663
  }
@@ -22320,6 +22334,53 @@
22320
22334
  return new TrendLineTool(context, options);
22321
22335
  }
22322
22336
 
22337
+ const END_HANDLE_MARGIN = 10;
22338
+ /**
22339
+ * Hands the next `clicks` clicks on the chart to `onClick`, past the last bar
22340
+ * too. A press that grabbed another drawing does not count.
22341
+ */
22342
+ function placeByClicks(chart, series, clicks, onClick) {
22343
+ let placed = 0;
22344
+ let handler = null;
22345
+ const stop = () => {
22346
+ if (handler !== null) {
22347
+ chart.unsubscribeClick(handler);
22348
+ handler = null;
22349
+ }
22350
+ };
22351
+ handler = (param) => {
22352
+ if (!param.point || consumeInputClaim(chart)) {
22353
+ return;
22354
+ }
22355
+ const price = series.coordinateToPrice(param.point.y);
22356
+ const time = timeFromCoordinate(chart, series, param.point.x);
22357
+ if (price === null || time === null) {
22358
+ return;
22359
+ }
22360
+ placed++;
22361
+ if (placed >= clicks) {
22362
+ stop();
22363
+ }
22364
+ onClick({ _internal_time: time, _internal_price: price, _internal_x: param.point.x, _internal_y: param.point.y }, placed - 1);
22365
+ };
22366
+ chart.subscribeClick(handler);
22367
+ return {
22368
+ _internal_pending: () => handler !== null,
22369
+ _internal_stop: stop,
22370
+ };
22371
+ }
22372
+ /** A fifth of the pane, and never less than a bar. */
22373
+ function defaultWidth(chart) {
22374
+ const timeScale = chart.timeScale();
22375
+ return Math.max(timeScale.width() / 5, timeScale.options().barSpacing);
22376
+ }
22377
+ /** Kept inside the pane: past it, the handles on the end hide under the price axis. */
22378
+ function placedEndX(chart, x) {
22379
+ const timeScale = chart.timeScale();
22380
+ const onScreen = Math.min(x + defaultWidth(chart), timeScale.width() - END_HANDLE_MARGIN);
22381
+ return Math.max(onScreen, x + timeScale.options().barSpacing);
22382
+ }
22383
+
22323
22384
  class HorizontalLinePaneRenderer {
22324
22385
  constructor(tool) {
22325
22386
  this._private__tool = tool;
@@ -22360,6 +22421,7 @@
22360
22421
  this._private__isDragging = false;
22361
22422
  this._private__grab = null;
22362
22423
  this._private__moveStartPrice = 0;
22424
+ this._private__placement = null;
22363
22425
  this._private__hLineOptions = {
22364
22426
  lineColor: '#2962ff',
22365
22427
  lineWidth: 2,
@@ -22372,6 +22434,26 @@
22372
22434
  };
22373
22435
  this._registerOptions(this._private__hLineOptions);
22374
22436
  this._private__createPrimitive();
22437
+ if (this._private__hLineOptions.interactive) {
22438
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
22439
+ }
22440
+ }
22441
+ _private__placeAt(point) {
22442
+ this._private__hLineOptions.price = point._internal_price;
22443
+ if (this._private__hLineOptions.startTime !== undefined) {
22444
+ this._private__hLineOptions.startTime = point._internal_time;
22445
+ }
22446
+ this._private__requestUpdate();
22447
+ }
22448
+ isComplete() {
22449
+ return !this._private__placement?._internal_pending();
22450
+ }
22451
+ selectionBox() {
22452
+ const y = this._private__hLineOptions.price === undefined ? null : this._series.priceToCoordinate(this._private__hLineOptions.price);
22453
+ if (y === null) {
22454
+ return null;
22455
+ }
22456
+ return { left: this._private__startX(), right: this._chart.timeScale().width(), top: y, bottom: y };
22375
22457
  }
22376
22458
  _private__createPrimitive() {
22377
22459
  this._private__paneView = this._private__createPaneView();
@@ -22409,7 +22491,7 @@
22409
22491
  return coordinateFromTime(this._chart, this._series, this._private__hLineOptions.startTime) ?? 0;
22410
22492
  }
22411
22493
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
22412
- if (this._private__hLineOptions.price === undefined)
22494
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined)
22413
22495
  return;
22414
22496
  const y = (this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0) * verticalPixelRatio;
22415
22497
  const chartWidth = this._chart.chartElement().clientWidth * horizontalPixelRatio;
@@ -22503,7 +22585,7 @@
22503
22585
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
22504
22586
  }
22505
22587
  hitTest(x, y) {
22506
- if (this._private__hLineOptions.price === undefined) {
22588
+ if (!this.isComplete() || this._private__hLineOptions.price === undefined) {
22507
22589
  return false;
22508
22590
  }
22509
22591
  const lineY = this._series.priceToCoordinate(this._private__hLineOptions.price) ?? 0;
@@ -22578,6 +22660,7 @@
22578
22660
  this._private__requestUpdate();
22579
22661
  }
22580
22662
  remove() {
22663
+ this._private__placement?._internal_stop();
22581
22664
  if (this._private__primitive) {
22582
22665
  this._series.detachPrimitive(this._private__primitive);
22583
22666
  this._private__primitive = null;
@@ -22637,6 +22720,7 @@
22637
22720
  this._private__grab = null;
22638
22721
  this._private__moveStartTime = 0;
22639
22722
  this._private__moveStartPrice = 0;
22723
+ this._private__placement = null;
22640
22724
  this._private__crossOptions = {
22641
22725
  lineColor: '#787b86',
22642
22726
  lineWidth: 1,
@@ -22646,6 +22730,16 @@
22646
22730
  };
22647
22731
  this._registerOptions(this._private__crossOptions);
22648
22732
  this._private__createPrimitive();
22733
+ if (this._private__crossOptions.interactive) {
22734
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
22735
+ this._private__crossOptions.time = point._internal_time;
22736
+ this._private__crossOptions.price = point._internal_price;
22737
+ this._private__requestUpdate();
22738
+ });
22739
+ }
22740
+ }
22741
+ isComplete() {
22742
+ return !this._private__placement?._internal_pending();
22649
22743
  }
22650
22744
  _private__createPrimitive() {
22651
22745
  this._private__paneView = new CrossLinePaneView(this);
@@ -22673,7 +22767,7 @@
22673
22767
  this._private__requestUpdate();
22674
22768
  }
22675
22769
  _private__coordinates() {
22676
- if (this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
22770
+ if (!this.isComplete() || this._private__crossOptions.time === undefined || this._private__crossOptions.price === undefined) {
22677
22771
  return null;
22678
22772
  }
22679
22773
  const x = coordinateFromTime(this._chart, this._series, this._private__crossOptions.time);
@@ -22796,6 +22890,7 @@
22796
22890
  return this._private__isHovered;
22797
22891
  }
22798
22892
  remove() {
22893
+ this._private__placement?._internal_stop();
22799
22894
  if (this._private__primitive) {
22800
22895
  try {
22801
22896
  this._series.detachPrimitive(this._private__primitive);
@@ -24218,6 +24313,7 @@
24218
24313
  this._private__isDragging = false;
24219
24314
  this._private__grab = null;
24220
24315
  this._private__moveStartTime = 0;
24316
+ this._private__placement = null;
24221
24317
  this._private__vLineOptions = {
24222
24318
  lineColor: '#787b86',
24223
24319
  lineWidth: 1,
@@ -24229,6 +24325,29 @@
24229
24325
  };
24230
24326
  this._registerOptions(this._private__vLineOptions);
24231
24327
  this._private__createPrimitive();
24328
+ if (this._private__vLineOptions.interactive) {
24329
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
24330
+ this._private__vLineOptions.time = point._internal_time;
24331
+ this._private__requestUpdate();
24332
+ });
24333
+ }
24334
+ }
24335
+ isComplete() {
24336
+ return !this._private__placement?._internal_pending();
24337
+ }
24338
+ selectionBox() {
24339
+ const x = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time);
24340
+ if (x === null) {
24341
+ return null;
24342
+ }
24343
+ let middle;
24344
+ try {
24345
+ middle = this._series.getPane().getHeight() / 2;
24346
+ }
24347
+ catch (error) {
24348
+ return null;
24349
+ }
24350
+ return { left: x, right: x, top: middle, bottom: middle };
24232
24351
  }
24233
24352
  _private__createPrimitive() {
24234
24353
  this._private__paneView = this._private__createPaneView();
@@ -24259,7 +24378,7 @@
24259
24378
  return new VerticalLinePaneView(this);
24260
24379
  }
24261
24380
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
24262
- if (this._private__vLineOptions.time === undefined)
24381
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined)
24263
24382
  return;
24264
24383
  const x = (coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0) * horizontalPixelRatio;
24265
24384
  const chartHeight = this._chart.chartElement().clientHeight * verticalPixelRatio;
@@ -24346,7 +24465,7 @@
24346
24465
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
24347
24466
  }
24348
24467
  hitTest(x, y) {
24349
- if (this._private__vLineOptions.time === undefined) {
24468
+ if (!this.isComplete() || this._private__vLineOptions.time === undefined) {
24350
24469
  return false;
24351
24470
  }
24352
24471
  const lineX = coordinateFromTime(this._chart, this._series, this._private__vLineOptions.time) ?? 0;
@@ -24416,6 +24535,7 @@
24416
24535
  this._private__requestUpdate();
24417
24536
  }
24418
24537
  remove() {
24538
+ this._private__placement?._internal_stop();
24419
24539
  if (this._private__primitive) {
24420
24540
  this._series.detachPrimitive(this._private__primitive);
24421
24541
  this._private__primitive = null;
@@ -25137,6 +25257,7 @@
25137
25257
  this._private__isHovered = false;
25138
25258
  this._private__isDragging = false;
25139
25259
  this._private__dragTarget = null;
25260
+ this._private__placement = null;
25140
25261
  this._private__fibOptions = {
25141
25262
  levels: [
25142
25263
  { level: 0, color: '#787B86', label: '0%' },
@@ -25152,6 +25273,20 @@
25152
25273
  ...options,
25153
25274
  };
25154
25275
  this._private__createPrimitive();
25276
+ if (this._private__fibOptions.interactive) {
25277
+ this._private__placement = placeByClicks(this._chart, this._series, 3, (point, index) => {
25278
+ const placed = { time: point._internal_time, price: point._internal_price };
25279
+ if (index === 0) {
25280
+ this.setPoint1(placed);
25281
+ }
25282
+ else if (index === 1) {
25283
+ this.setPoint2(placed);
25284
+ }
25285
+ else {
25286
+ this.setPoint3(placed);
25287
+ }
25288
+ });
25289
+ }
25155
25290
  }
25156
25291
  _private__createPrimitive() {
25157
25292
  this._private__paneView = this._private__createPaneView();
@@ -25209,7 +25344,7 @@
25209
25344
  this._private__requestUpdate();
25210
25345
  }
25211
25346
  isComplete() {
25212
- return !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
25347
+ return !this._private__placement?._internal_pending() && !!(this._private__fibOptions.point1 && this._private__fibOptions.point2 && this._private__fibOptions.point3);
25213
25348
  }
25214
25349
  setColor(color) {
25215
25350
  this._private__fibOptions.lineColor = color;
@@ -25504,6 +25639,7 @@
25504
25639
  this._private__requestUpdate();
25505
25640
  }
25506
25641
  remove() {
25642
+ this._private__placement?._internal_stop();
25507
25643
  if (this._private__primitive) {
25508
25644
  this._series.detachPrimitive(this._private__primitive);
25509
25645
  this._private__primitive = null;
@@ -28069,6 +28205,7 @@
28069
28205
  this._private__moveStart = null;
28070
28206
  this._private__isEditing = false;
28071
28207
  this._private__clickOutsideHandler = null;
28208
+ this._private__placement = null;
28072
28209
  this._private__textOptions = {
28073
28210
  text: 'Double-click to edit',
28074
28211
  fontSize: 14,
@@ -28089,6 +28226,14 @@
28089
28226
  this._registerOptions(this._private__textOptions);
28090
28227
  this._private__createPrimitive();
28091
28228
  this._private__setupClickOutsideHandler();
28229
+ if (this._private__textOptions.interactive) {
28230
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => {
28231
+ this.setPosition(point._internal_time, point._internal_price);
28232
+ if (this._private__textOptions.onComplete) {
28233
+ this._private__textOptions.onComplete();
28234
+ }
28235
+ });
28236
+ }
28092
28237
  }
28093
28238
  _private__setupClickOutsideHandler() {
28094
28239
  // Set up click handler to finish editing when clicking outside
@@ -28174,7 +28319,7 @@
28174
28319
  this._private__requestUpdate();
28175
28320
  }
28176
28321
  isComplete() {
28177
- return !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
28322
+ return !this._private__placement?._internal_pending() && !!(this._private__textOptions.time && this._private__textOptions.price !== undefined);
28178
28323
  }
28179
28324
  setColor(color) {
28180
28325
  this._private__textOptions.textColor = color;
@@ -28457,6 +28602,7 @@
28457
28602
  };
28458
28603
  }
28459
28604
  remove() {
28605
+ this._private__placement?._internal_stop();
28460
28606
  // Clean up click handler
28461
28607
  if (this._private__clickOutsideHandler) {
28462
28608
  this._chart.unsubscribeClick(this._private__clickOutsideHandler);
@@ -29315,6 +29461,7 @@
29315
29461
  this._private__paneView = null;
29316
29462
  this._private__isSelected = false;
29317
29463
  this._private__isHovered = false;
29464
+ this._private__placement = null;
29318
29465
  this._private__isDragging = false;
29319
29466
  this._private__dragTarget = null;
29320
29467
  this._private__grab = null;
@@ -29337,6 +29484,32 @@
29337
29484
  // Create primitive and setup
29338
29485
  this._private__createPrimitive();
29339
29486
  this._private__updateMetrics();
29487
+ if (this._private__positionOptions.interactive) {
29488
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
29489
+ }
29490
+ }
29491
+ _private__placeAt(point) {
29492
+ const options = this._private__positionOptions;
29493
+ const shift = point._internal_price - options.entryPrice;
29494
+ options.entryPrice = point._internal_price;
29495
+ options.entryTime = point._internal_time;
29496
+ if (options.stopLoss !== undefined) {
29497
+ options.stopLoss += shift;
29498
+ }
29499
+ if (options.takeProfit !== undefined) {
29500
+ options.takeProfit += shift;
29501
+ }
29502
+ options.endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? undefined;
29503
+ this._private__updateMetrics();
29504
+ this._private__requestUpdate();
29505
+ }
29506
+ isComplete() {
29507
+ return !this._private__placement?._internal_pending();
29508
+ }
29509
+ _private__span() {
29510
+ const entryX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0;
29511
+ const endX = coordinateFromTime(this._chart, this._series, this._private__positionOptions.endTime) ?? entryX + defaultWidth(this._chart);
29512
+ return { _internal_entryX: entryX, _internal_endX: endX };
29340
29513
  }
29341
29514
  _private__createPrimitive() {
29342
29515
  // Create and cache the paneView once
@@ -29368,6 +29541,9 @@
29368
29541
  return new PositionPaneView(this);
29369
29542
  }
29370
29543
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29544
+ if (!this.isComplete()) {
29545
+ return;
29546
+ }
29371
29547
  // Draw profit rectangle (green)
29372
29548
  if (this._private__profitRect && this._private__positionOptions.takeProfit) {
29373
29549
  const timeScale = this._chart.timeScale();
@@ -29401,16 +29577,9 @@
29401
29577
  }
29402
29578
  }
29403
29579
  _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
- }
29580
+ const span = this._private__span();
29581
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29582
+ const endX = span._internal_endX * horizontalPixelRatio;
29414
29583
  const y1 = (this._series.priceToCoordinate(rect._internal_price1) ?? 0) * verticalPixelRatio;
29415
29584
  const y2 = (this._series.priceToCoordinate(rect._internal_price2) ?? 0) * verticalPixelRatio;
29416
29585
  // Draw filled rectangle with semi-transparent fill
@@ -29425,16 +29594,10 @@
29425
29594
  ctx.stroke();
29426
29595
  }
29427
29596
  _private__drawEntryLine(ctx, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29428
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29597
+ const span = this._private__span();
29598
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29599
+ const endX = span._internal_endX * horizontalPixelRatio;
29429
29600
  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
29601
  // Draw horizontal entry line (divider)
29439
29602
  const lineColor = this._private__positionOptions.entryLineColor;
29440
29603
  ctx.strokeStyle = lineColor;
@@ -29548,9 +29711,11 @@
29548
29711
  const padding = 6 * horizontalPixelRatio;
29549
29712
  const labelWidth = textMetrics.width + padding * 2;
29550
29713
  const labelHeight = 20 * horizontalPixelRatio;
29551
- // Position label at the start of the line
29714
+ // Outside the box, so a label wider than it does not cover the handles on its edge
29715
+ const entryY = (this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0) * verticalPixelRatio;
29716
+ const gap = 3 * verticalPixelRatio;
29552
29717
  const labelX = entryX + 8 * horizontalPixelRatio;
29553
- const labelY = y - labelHeight / 2;
29718
+ const labelY = y <= entryY ? y - labelHeight - gap : y + gap;
29554
29719
  // Draw label background
29555
29720
  ctx.fillStyle = rect._internal_color;
29556
29721
  ctx.fillRect(labelX, labelY, labelWidth, labelHeight);
@@ -29558,10 +29723,12 @@
29558
29723
  ctx.fillStyle = '#ffffff';
29559
29724
  ctx.textAlign = 'left';
29560
29725
  ctx.textBaseline = 'middle';
29561
- ctx.fillText(labelText, labelX + padding, y);
29726
+ ctx.fillText(labelText, labelX + padding, labelY + labelHeight / 2);
29562
29727
  }
29563
29728
  _private__drawEndpoint(ctx, price, color, horizontalPixelRatio, verticalPixelRatio, timeScale) {
29564
- const entryX = (coordinateFromTime(this._chart, this._series, this._private__positionOptions.entryTime) ?? 0) * horizontalPixelRatio;
29729
+ const span = this._private__span();
29730
+ const entryX = span._internal_entryX * horizontalPixelRatio;
29731
+ const endX = span._internal_endX * horizontalPixelRatio;
29565
29732
  const y = (this._series.priceToCoordinate(price) ?? 0) * verticalPixelRatio;
29566
29733
  // Draw circle endpoint at entry
29567
29734
  const circleColor = color;
@@ -29573,14 +29740,6 @@
29573
29740
  ctx.fill();
29574
29741
  ctx.stroke();
29575
29742
  // 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
29743
  const size = 8 * horizontalPixelRatio;
29585
29744
  const squareColor = color;
29586
29745
  ctx.fillStyle = squareColor;
@@ -29701,18 +29860,12 @@
29701
29860
  return this.hitTest(x, y) ? toolHoveredItem(this.id, 'body', false) : null;
29702
29861
  }
29703
29862
  hitTest(x, y) {
29704
- if (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit) {
29863
+ if (!this.isComplete() || (!this._private__positionOptions.stopLoss && !this._private__positionOptions.takeProfit)) {
29705
29864
  return false;
29706
29865
  }
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
- }
29866
+ const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
29867
+ const left = Math.min(entryX, endX);
29868
+ const right = Math.max(entryX, endX);
29716
29869
  // Check if inside the entire Position Tool area (rectangles)
29717
29870
  if (this._private__positionOptions.takeProfit && this._private__positionOptions.stopLoss) {
29718
29871
  const tpY = this._series.priceToCoordinate(this._private__positionOptions.takeProfit) ?? 0;
@@ -29720,7 +29873,7 @@
29720
29873
  const minY = Math.min(tpY, slY);
29721
29874
  const maxY = Math.max(tpY, slY);
29722
29875
  // Check if inside the rectangle bounds (entire tool area)
29723
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29876
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29724
29877
  return true;
29725
29878
  }
29726
29879
  // Also check entry line area
@@ -29732,15 +29885,12 @@
29732
29885
  return false;
29733
29886
  }
29734
29887
  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;
29888
+ if (!this.isComplete()) {
29889
+ return null;
29743
29890
  }
29891
+ const { _internal_entryX: entryX, _internal_endX: endX } = this._private__span();
29892
+ const left = Math.min(entryX, endX);
29893
+ const right = Math.max(entryX, endX);
29744
29894
  const tolerance = 10;
29745
29895
  // Check TP left endpoint (circle)
29746
29896
  if (this._private__positionOptions.takeProfit) {
@@ -29777,7 +29927,7 @@
29777
29927
  // Check entry line (middle divider line) - higher priority than 'move'
29778
29928
  const entryY = this._series.priceToCoordinate(this._private__positionOptions.entryPrice) ?? 0;
29779
29929
  const lineTolerance = 8;
29780
- if (x >= entryX && x <= endX && Math.abs(y - entryY) <= lineTolerance) {
29930
+ if (x >= left && x <= right && Math.abs(y - entryY) <= lineTolerance) {
29781
29931
  return 'entry';
29782
29932
  }
29783
29933
  // Check if clicking on the body (rectangles) for moving entire tool
@@ -29787,7 +29937,7 @@
29787
29937
  const minY = Math.min(tpY, slY);
29788
29938
  const maxY = Math.max(tpY, slY);
29789
29939
  // Check if inside the rectangle bounds
29790
- if (x >= entryX && x <= endX && y >= minY && y <= maxY) {
29940
+ if (x >= left && x <= right && y >= minY && y <= maxY) {
29791
29941
  return 'move';
29792
29942
  }
29793
29943
  }
@@ -29887,6 +30037,7 @@
29887
30037
  this._private__requestUpdate();
29888
30038
  }
29889
30039
  remove() {
30040
+ this._private__placement?._internal_stop();
29890
30041
  if (this._private__primitive) {
29891
30042
  this._series.detachPrimitive(this._private__primitive);
29892
30043
  this._private__primitive = null;
@@ -29933,6 +30084,7 @@
29933
30084
  }
29934
30085
  }
29935
30086
 
30087
+ const TOP_PADDING = 50;
29936
30088
  class GanttTool extends DrawingToolBase {
29937
30089
  constructor(context, options) {
29938
30090
  super(context, 'gantt', options);
@@ -29946,6 +30098,7 @@
29946
30098
  this._private__grab = null;
29947
30099
  this._private__moveStart = null;
29948
30100
  this._private__hoveredSegmentIndex = -1;
30101
+ this._private__placement = null;
29949
30102
  this._private__ganttOptions = {
29950
30103
  segments: [],
29951
30104
  rowHeight: 40,
@@ -29958,6 +30111,43 @@
29958
30111
  };
29959
30112
  this._registerOptions(this._private__ganttOptions);
29960
30113
  this._private__createPrimitive();
30114
+ if (this._private__ganttOptions.interactive) {
30115
+ this._private__placement = placeByClicks(this._chart, this._series, 1, (point) => this._private__placeAt(point));
30116
+ }
30117
+ }
30118
+ _private__placeAt(point) {
30119
+ const segments = this._private__ganttOptions.segments ?? [];
30120
+ const endTime = timeFromCoordinate(this._chart, this._series, placedEndX(this._chart, point._internal_x)) ?? point._internal_time;
30121
+ const rowPitch = this._private__ganttOptions.rowHeight + this._private__ganttOptions.rowSpacing;
30122
+ const row = Math.max(0, Math.floor((point._internal_y - TOP_PADDING) / rowPitch));
30123
+ const shift = segments.length > 0 ? row - segments[0].level : 0;
30124
+ for (const segment of segments) {
30125
+ segment.startTime = point._internal_time;
30126
+ segment.endTime = endTime;
30127
+ segment.level = Math.max(0, segment.level + shift);
30128
+ }
30129
+ this._private__requestUpdate();
30130
+ }
30131
+ isComplete() {
30132
+ return !this._private__placement?._internal_pending();
30133
+ }
30134
+ selectionBox() {
30135
+ const segments = this._private__ganttOptions.segments ?? [];
30136
+ if (segments.length === 0) {
30137
+ return null;
30138
+ }
30139
+ const rowHeight = this._private__ganttOptions.rowHeight;
30140
+ const pitch = rowHeight + this._private__ganttOptions.rowSpacing;
30141
+ // Typed as the public shape: an inferred one gets its keys renamed by the build
30142
+ const box = { left: Infinity, top: Infinity, right: -Infinity, bottom: -Infinity };
30143
+ for (const segment of segments) {
30144
+ const top = TOP_PADDING + segment.level * pitch;
30145
+ box.left = Math.min(box.left, coordinateFromTime(this._chart, this._series, segment.startTime) ?? 0);
30146
+ box.right = Math.max(box.right, coordinateFromTime(this._chart, this._series, segment.endTime) ?? 0);
30147
+ box.top = Math.min(box.top, top);
30148
+ box.bottom = Math.max(box.bottom, top + rowHeight);
30149
+ }
30150
+ return box;
29961
30151
  }
29962
30152
  _private__createPrimitive() {
29963
30153
  this._private__paneView = this._private__createPaneView();
@@ -29988,11 +30178,10 @@
29988
30178
  return new GanttPaneView(this);
29989
30179
  }
29990
30180
  drawToCanvas(ctx, horizontalPixelRatio, verticalPixelRatio) {
29991
- if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
30181
+ if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0)
29992
30182
  return;
29993
30183
  // Position gantt at top of chart with padding
29994
- const topPadding = 50; // pixels from top
29995
- const baseY = topPadding * verticalPixelRatio;
30184
+ const baseY = TOP_PADDING * verticalPixelRatio;
29996
30185
  const rowHeight = this._private__ganttOptions.rowHeight * verticalPixelRatio;
29997
30186
  const rowSpacing = this._private__ganttOptions.rowSpacing * verticalPixelRatio;
29998
30187
  // Draw grid lines if enabled
@@ -30171,11 +30360,10 @@
30171
30360
  this._private__moveStart = null;
30172
30361
  }
30173
30362
  hitTest(x, y) {
30174
- if (!this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
30363
+ if (!this.isComplete() || !this._private__ganttOptions.segments || this._private__ganttOptions.segments.length === 0) {
30175
30364
  return false;
30176
30365
  }
30177
- const topPadding = 50;
30178
- const baseY = topPadding;
30366
+ const baseY = TOP_PADDING;
30179
30367
  const rowHeight = this._private__ganttOptions.rowHeight;
30180
30368
  const rowSpacing = this._private__ganttOptions.rowSpacing;
30181
30369
  // Check each segment
@@ -30229,6 +30417,7 @@
30229
30417
  this._private__requestUpdate();
30230
30418
  }
30231
30419
  remove() {
30420
+ this._private__placement?._internal_stop();
30232
30421
  if (this._private__primitive) {
30233
30422
  this._series.detachPrimitive(this._private__primitive);
30234
30423
  this._private__primitive = null;
@@ -30364,6 +30553,7 @@
30364
30553
  }
30365
30554
  this._private__completionCheck = setTimeout(() => {
30366
30555
  this._private__completionCheck = null;
30556
+ const completed = [];
30367
30557
  for (const id of Array.from(this._private__pendingToolIds)) {
30368
30558
  const tool = this._private__tools.get(id);
30369
30559
  if (!tool) {
@@ -30372,9 +30562,15 @@
30372
30562
  }
30373
30563
  if (tool.isComplete()) {
30374
30564
  this._private__pendingToolIds.delete(id);
30375
- this._private__drawingCompleted._internal_fire(id);
30565
+ completed.push(id);
30376
30566
  }
30377
30567
  }
30568
+ if (completed.length > 0) {
30569
+ this.recordChange();
30570
+ }
30571
+ for (const id of completed) {
30572
+ this._private__drawingCompleted._internal_fire(id);
30573
+ }
30378
30574
  }, 0);
30379
30575
  }
30380
30576
  /**
@@ -30434,12 +30630,16 @@
30434
30630
  if (this._private__restoring) {
30435
30631
  return;
30436
30632
  }
30633
+ const next = this.serializeAll();
30634
+ if (JSON.stringify(next) === JSON.stringify(this._private__committed)) {
30635
+ return;
30636
+ }
30437
30637
  this._private__past.push(this._private__committed);
30438
30638
  if (this._private__past.length > UNDO_HISTORY_LIMIT) {
30439
30639
  this._private__past.shift();
30440
30640
  }
30441
30641
  this._private__future = [];
30442
- this._private__committed = this.serializeAll();
30642
+ this._private__committed = next;
30443
30643
  }
30444
30644
  canUndo() {
30445
30645
  return this._private__past.length > 0;
@@ -30495,11 +30695,25 @@
30495
30695
  const tool = this._private__tools.get(this._private__selectedToolId);
30496
30696
  return tool ? this._private__boundsOf(tool) : null;
30497
30697
  }
30698
+ /** Relative to the chart element; the tool's box is relative to its pane. */
30699
+ _private__boundsOf(tool) {
30700
+ const box = tool.selectionBox() ?? this._private__boxFromPoints(tool);
30701
+ if (box === null) {
30702
+ return null;
30703
+ }
30704
+ const offset = this._private__paneOffsetOf(tool);
30705
+ return {
30706
+ left: box.left + offset._internal_x,
30707
+ right: box.right + offset._internal_x,
30708
+ top: box.top + offset._internal_y,
30709
+ bottom: box.bottom + offset._internal_y,
30710
+ };
30711
+ }
30498
30712
  /**
30499
30713
  * Bounds come from the tool's own serialized points, so this works for every
30500
30714
  * tool without each one having to report a box.
30501
30715
  */
30502
- _private__boundsOf(tool) {
30716
+ _private__boxFromPoints(tool) {
30503
30717
  const series = tool.series();
30504
30718
  const points = tool.serialize().points;
30505
30719
  let left = Number.POSITIVE_INFINITY;
@@ -30522,13 +30736,7 @@
30522
30736
  if (!found) {
30523
30737
  return null;
30524
30738
  }
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
- };
30739
+ return { left, top, right, bottom };
30532
30740
  }
30533
30741
  /** Panes below the first one are offset inside the chart element. */
30534
30742
  _private__paneOffsetOf(tool) {
@@ -30612,7 +30820,10 @@
30612
30820
  serializeAll() {
30613
30821
  const result = [];
30614
30822
  for (const tool of this._private__tools.values()) {
30615
- result.push(tool.serialize());
30823
+ // Not drawn yet: undo would bring it back as a finished drawing
30824
+ if (tool.isComplete()) {
30825
+ result.push(tool.serialize());
30826
+ }
30616
30827
  }
30617
30828
  return result;
30618
30829
  }
@@ -31237,7 +31448,7 @@
31237
31448
  * Returns the current version as a string. For example `'1.0.0'`.
31238
31449
  */
31239
31450
  function version() {
31240
- return "2.3.13-dev+202609280001";
31451
+ return "2.3.15";
31241
31452
  }
31242
31453
 
31243
31454
  var PipsendChartsModule = /*#__PURE__*/Object.freeze({