acttrader-charts 1.0.0 → 1.0.2

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.
package/dist/index.cjs CHANGED
@@ -82,17 +82,17 @@ var DataStore = class {
82
82
  var _ScaleManager = class _ScaleManager {
83
83
  // ── X axis: bar index → canvas pixel ──────────────────────────────────────
84
84
  xToPixel(index, viewport, chartWidth) {
85
- const visibleBars = viewport.endIndex - viewport.startIndex;
85
+ const visibleBars = Math.max(1, viewport.endIndex - viewport.startIndex);
86
86
  const candleWidth = chartWidth / visibleBars;
87
87
  return (index - viewport.startIndex) * candleWidth + candleWidth / 2;
88
88
  }
89
89
  pixelToBarIndex(x, viewport, chartWidth) {
90
- const visibleBars = viewport.endIndex - viewport.startIndex;
90
+ const visibleBars = Math.max(1, viewport.endIndex - viewport.startIndex);
91
91
  const candleWidth = chartWidth / visibleBars;
92
92
  return viewport.startIndex + x / candleWidth - 0.5;
93
93
  }
94
94
  candleBodyWidth(viewport, chartWidth) {
95
- const visibleBars = viewport.endIndex - viewport.startIndex;
95
+ const visibleBars = Math.max(1, viewport.endIndex - viewport.startIndex);
96
96
  const raw = chartWidth / visibleBars * 0.7;
97
97
  return Math.max(1, Math.min(raw, 40));
98
98
  }
@@ -714,7 +714,7 @@ function resolveLabels(override) {
714
714
  // src/interaction/PanHandler.ts
715
715
  var _PanHandler = class _PanHandler {
716
716
  // px — below this = click, not drag
717
- constructor(canvas, getViewport, setViewport, totalBars, onUpdate, isLocked = () => false, panY) {
717
+ constructor(canvas, getViewport, setViewport, totalBars, onUpdate, isLocked = () => false, panY, onDragStart, onDragEnd) {
718
718
  this.canvas = canvas;
719
719
  this.getViewport = getViewport;
720
720
  this.setViewport = setViewport;
@@ -722,15 +722,20 @@ var _PanHandler = class _PanHandler {
722
722
  this.onUpdate = onUpdate;
723
723
  this.isLocked = isLocked;
724
724
  this.panY = panY;
725
+ this.onDragStart = onDragStart;
726
+ this.onDragEnd = onDragEnd;
725
727
  this.isDragging = false;
728
+ this.isTouchDrag = false;
726
729
  this.lastX = 0;
727
730
  this.lastY = 0;
728
731
  this.dragStartX = 0;
729
732
  this.dragStartY = 0;
730
733
  this.hasPanned = false;
734
+ this.dragStartFired = false;
731
735
  this.onMouseDown = (e) => {
732
736
  if (this.isLocked()) return;
733
737
  this.isDragging = true;
738
+ this.isTouchDrag = false;
734
739
  this.lastX = e.clientX;
735
740
  this.lastY = e.clientY;
736
741
  this.dragStartX = e.clientX;
@@ -741,8 +746,13 @@ var _PanHandler = class _PanHandler {
741
746
  if (this.isLocked()) return;
742
747
  if (e.touches.length !== 1) return;
743
748
  this.isDragging = true;
749
+ this.isTouchDrag = true;
744
750
  this.lastX = e.touches[0].clientX;
745
751
  this.lastY = e.touches[0].clientY;
752
+ this.dragStartX = e.touches[0].clientX;
753
+ this.dragStartY = e.touches[0].clientY;
754
+ this.dragStartFired = true;
755
+ this.onDragStart?.();
746
756
  };
747
757
  this.onMouseMove = (e) => {
748
758
  if (!this.isDragging) return;
@@ -759,6 +769,8 @@ var _PanHandler = class _PanHandler {
759
769
  const totalDeltaY = Math.abs(e.clientY - this.dragStartY);
760
770
  if (!this.hasPanned && (totalDeltaX > _PanHandler.DRAG_THRESHOLD || totalDeltaY > _PanHandler.DRAG_THRESHOLD)) {
761
771
  this.hasPanned = true;
772
+ this.dragStartFired = true;
773
+ this.onDragStart?.();
762
774
  this.canvas.style.cursor = "grabbing";
763
775
  }
764
776
  if (this.hasPanned) {
@@ -784,6 +796,10 @@ var _PanHandler = class _PanHandler {
784
796
  if (this.hasPanned) this.canvas.style.cursor = "crosshair";
785
797
  this.isDragging = false;
786
798
  this.hasPanned = false;
799
+ if (this.dragStartFired) {
800
+ this.dragStartFired = false;
801
+ this.onDragEnd?.();
802
+ }
787
803
  };
788
804
  this.attach();
789
805
  }
@@ -791,10 +807,15 @@ var _PanHandler = class _PanHandler {
791
807
  this.canvas.addEventListener("mousedown", this.onMouseDown);
792
808
  window.addEventListener("mousemove", this.onMouseMove);
793
809
  window.addEventListener("mouseup", this.onMouseUp);
794
- this.canvas.addEventListener("touchstart", this.onTouchStart, { passive: true });
810
+ this.canvas.addEventListener("touchstart", this.onTouchStart, { passive: false });
795
811
  window.addEventListener("touchmove", this.onTouchMove, { passive: false });
796
812
  window.addEventListener("touchend", this.onMouseUp);
797
813
  }
814
+ /** Cancel any in-progress pan gesture (e.g. when a drawing drag takes over). */
815
+ cancel() {
816
+ this.isDragging = false;
817
+ this.hasPanned = false;
818
+ }
798
819
  destroy() {
799
820
  this.canvas.removeEventListener("mousedown", this.onMouseDown);
800
821
  window.removeEventListener("mousemove", this.onMouseMove);
@@ -806,9 +827,10 @@ var _PanHandler = class _PanHandler {
806
827
  pan(deltaX) {
807
828
  const vp = this.getViewport();
808
829
  const visibleBars = vp.endIndex - vp.startIndex;
809
- const barsPerPixel = visibleBars / this.canvas.width;
830
+ const width = this.isTouchDrag ? this.canvas.clientWidth || this.canvas.width / (window.devicePixelRatio || 1) : this.canvas.width;
831
+ const barsPerPixel = visibleBars / width;
810
832
  const shift = -deltaX * barsPerPixel;
811
- const maxStart = this.totalBars();
833
+ const maxStart = Math.max(0, this.totalBars() - Math.floor(visibleBars / 2));
812
834
  const newStart = Math.max(0, Math.min(maxStart, vp.startIndex + shift));
813
835
  this.setViewport({ ...vp, startIndex: newStart, endIndex: newStart + visibleBars });
814
836
  this.onUpdate();
@@ -907,7 +929,7 @@ var ZoomHandler = class {
907
929
  }
908
930
  attach() {
909
931
  this.canvas.addEventListener("wheel", this.onWheel, { passive: false });
910
- this.canvas.addEventListener("touchstart", this.onTouchStart, { passive: true });
932
+ this.canvas.addEventListener("touchstart", this.onTouchStart, { passive: false });
911
933
  this.canvas.addEventListener("touchmove", this.onTouchMove, { passive: false });
912
934
  this.canvas.addEventListener("touchend", this.onTouchEnd);
913
935
  }
@@ -10522,6 +10544,19 @@ var DrawingManager = class {
10522
10544
  isResizingDrawing() {
10523
10545
  return this.resizeDrawing !== null;
10524
10546
  }
10547
+ /** Shift all drawings' barIndex values by `delta` (called when bars are prepended to DataStore). */
10548
+ shiftBarIndices(delta) {
10549
+ for (const drawing of this.drawings) {
10550
+ for (const pt of drawing.points) {
10551
+ pt.barIndex += delta;
10552
+ }
10553
+ }
10554
+ if (this.dragStartPoints) {
10555
+ for (const pt of this.dragStartPoints) {
10556
+ pt.barIndex += delta;
10557
+ }
10558
+ }
10559
+ }
10525
10560
  /** Returns the handle under (x,y) across all complete drawings, or null */
10526
10561
  getHandleAtPoint(x, y, scale, viewport, priceRange, chartW, chartH) {
10527
10562
  for (let i = this.drawings.length - 1; i >= 0; i--) {
@@ -12337,9 +12372,9 @@ var DraftOrderQtyInput = class _DraftOrderQtyInput {
12337
12372
  display: "none",
12338
12373
  alignItems: "center",
12339
12374
  gap: "5px",
12340
- right: `${priceAxisWidth + 70}px`,
12375
+ right: `${priceAxisWidth + 4}px`,
12341
12376
  top: "0",
12342
- transform: "translateY(-50%)",
12377
+ transform: "translateY(calc(-100% - 16px))",
12343
12378
  zIndex: "18",
12344
12379
  pointerEvents: "auto",
12345
12380
  userSelect: "none",
@@ -12389,7 +12424,7 @@ var DraftOrderQtyInput = class _DraftOrderQtyInput {
12389
12424
  this.input.step = String(minQty);
12390
12425
  this.input.value = String(this._pendingLots ?? minQty);
12391
12426
  this._pendingLots = null;
12392
- const clampedY = priceH !== void 0 ? Math.max(10, Math.min(priceH - 10, y)) : y;
12427
+ const clampedY = priceH !== void 0 ? Math.max(54, Math.min(priceH - 10, y)) : y;
12393
12428
  this.el.style.top = `${clampedY}px`;
12394
12429
  this.el.style.display = "flex";
12395
12430
  requestAnimationFrame(() => this.input.focus({ preventScroll: true }));
@@ -12411,12 +12446,12 @@ var DraftOrderQtyInput = class _DraftOrderQtyInput {
12411
12446
  return isNaN(v) || v <= 0 ? parseFloat(this.input.min || "1") : v;
12412
12447
  }
12413
12448
  setY(y, priceH) {
12414
- const clampedY = priceH !== void 0 ? Math.max(10, Math.min(priceH - 10, y)) : y;
12449
+ const clampedY = priceH !== void 0 ? Math.max(54, Math.min(priceH - 10, y)) : y;
12415
12450
  this.el.style.top = `${clampedY}px`;
12416
12451
  }
12417
12452
  setTheme(theme, priceAxisWidth) {
12418
12453
  this.priceAxisWidth = priceAxisWidth;
12419
- this.el.style.right = `${priceAxisWidth + 70}px`;
12454
+ this.el.style.right = `${priceAxisWidth + 4}px`;
12420
12455
  const bg = _DraftOrderQtyInput.boxBg(theme);
12421
12456
  const fg = _DraftOrderQtyInput.boxFg(theme);
12422
12457
  Object.assign(this.input.style, {
@@ -16304,13 +16339,20 @@ var VOLUME_PANEL_RATIO = 0.18;
16304
16339
  var SUB_PANE_HEIGHT = 80;
16305
16340
  var PANE_SEPARATOR = 1;
16306
16341
  var DEFAULT_VISIBLE_BARS = 80;
16342
+ var DEFAULT_CANDLE_SLOT_WIDTH = 10;
16307
16343
  var PRICE_RANGE_LERP = 0.25;
16344
+ var VIEWPORT_LERP = 0.2;
16308
16345
  function isMobileDevice() {
16309
16346
  return typeof window !== "undefined" && ("ontouchstart" in window || navigator.maxTouchPoints > 1);
16310
16347
  }
16311
- function initialVisibleBars(mobileBarDivisor = 2, forceApply = false) {
16312
- if (!forceApply && !isMobileDevice()) return DEFAULT_VISIBLE_BARS;
16313
- return Math.max(10, Math.round(DEFAULT_VISIBLE_BARS / mobileBarDivisor));
16348
+ function initialVisibleBars(mobileBarDivisor = 2, forceApply = false, defaultBars = DEFAULT_VISIBLE_BARS, defaultMobileBars, chartWidth, targetCandleSlotWidth) {
16349
+ if (chartWidth && chartWidth > 0 && targetCandleSlotWidth && targetCandleSlotWidth > 0) {
16350
+ const auto = Math.floor(chartWidth / targetCandleSlotWidth);
16351
+ return Math.max(10, Math.min(500, auto));
16352
+ }
16353
+ if (!forceApply && !isMobileDevice()) return defaultBars;
16354
+ if (defaultMobileBars !== void 0) return defaultMobileBars;
16355
+ return Math.max(10, Math.round(defaultBars / mobileBarDivisor));
16314
16356
  }
16315
16357
  var LOOKAHEAD = 4;
16316
16358
  function applySubPaneScale(range, factor) {
@@ -16326,7 +16368,7 @@ function subPaneAxisPrecision(range) {
16326
16368
  if (span >= 0.1) return 3;
16327
16369
  return 4;
16328
16370
  }
16329
- var ChartEngine = class {
16371
+ var _ChartEngine = class _ChartEngine {
16330
16372
  constructor(config) {
16331
16373
  // UI bars
16332
16374
  this.topBar = null;
@@ -16345,6 +16387,7 @@ var ChartEngine = class {
16345
16387
  this.siStreamDot = null;
16346
16388
  this.siSymbolSpan = null;
16347
16389
  this.siOHLCSpan = null;
16390
+ this.siOHLC2Span = null;
16348
16391
  this.tickActivityTimer = null;
16349
16392
  this.tickActivityMs = 3e4;
16350
16393
  this.streamStatus = "disconnected";
@@ -16379,11 +16422,24 @@ var ChartEngine = class {
16379
16422
  candleWidth: 8,
16380
16423
  candleGap: 2
16381
16424
  };
16425
+ // Logical target viewport — pan/zoom handlers write here; `viewport` lerps toward it each frame.
16426
+ this._targetViewport = {
16427
+ startIndex: 0,
16428
+ endIndex: initialVisibleBars(),
16429
+ candleWidth: 8,
16430
+ candleGap: 2
16431
+ };
16432
+ this._isPanning = false;
16382
16433
  this.crosshair = null;
16383
16434
  // Drawing
16384
16435
  this.drawingManager = new DrawingManager();
16385
16436
  this.textInputEl = null;
16386
16437
  this.textInputDrawing = null;
16438
+ /** Pending drag — committed only after mouse moves past DRAW_GESTURE_THRESHOLD. */
16439
+ this._pendingDrawDrag = null;
16440
+ /** Pending resize — committed only after mouse moves past DRAW_GESTURE_THRESHOLD. */
16441
+ this._pendingDrawResize = null;
16442
+ // px
16387
16443
  // Pane resize
16388
16444
  this.paneHeightOverrides = /* @__PURE__ */ new Map();
16389
16445
  this.separatorDrag = null;
@@ -16403,7 +16459,9 @@ var ChartEngine = class {
16403
16459
  this.subPaneScaleFactors = /* @__PURE__ */ new Map();
16404
16460
  this._mobileCrosshairActive = false;
16405
16461
  this._mobileTapStartXY = null;
16406
- this._isHoveringTradeButton = false;
16462
+ this._longPressTimer = null;
16463
+ this._longPressPoint = null;
16464
+ this._lastTouchTime = 0;
16407
16465
  this._tradeButtonClickData = null;
16408
16466
  this.rafId = null;
16409
16467
  this.countdownRafId = null;
@@ -16456,8 +16514,13 @@ var ChartEngine = class {
16456
16514
  this.isFetching = false;
16457
16515
  this.noMoreData = false;
16458
16516
  this.maxCandles = 200;
16517
+ this.defaultVisibleBars = DEFAULT_VISIBLE_BARS;
16518
+ this.defaultMobileVisibleBars = void 0;
16459
16519
  this.mobileBarDivisor = 2;
16460
16520
  this.explicitMobileDivisor = false;
16521
+ this._autoVisibleBars = true;
16522
+ // false when defaultVisibleBars was explicitly set
16523
+ this.targetCandleWidth = DEFAULT_CANDLE_SLOT_WIDTH;
16461
16524
  this.maxSubPanes = 3;
16462
16525
  this.showCandleCountdown = true;
16463
16526
  this.loadingOverlayEl = null;
@@ -16467,6 +16530,7 @@ var ChartEngine = class {
16467
16530
  this.isRestoringState = false;
16468
16531
  this.onMouseMove = (e) => {
16469
16532
  if (this._mobileCrosshairActive) return;
16533
+ if (Date.now() - this._lastTouchTime < 500) return;
16470
16534
  const rect = this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect();
16471
16535
  const x = e.clientX - rect.left;
16472
16536
  const y = e.clientY - rect.top;
@@ -16481,7 +16545,7 @@ var ChartEngine = class {
16481
16545
  Math.min(Math.ceil(this.viewport.endIndex), Math.round(rawBarIndex))
16482
16546
  );
16483
16547
  const bar = this.dataStore.all[clamped] ?? null;
16484
- this.crosshair = isInChartArea && !this._isHoveringTradeButton ? { x, y, barIndex: clamped, price, bar } : null;
16548
+ this.crosshair = isInChartArea && this.tradeButton === null ? { x, y, barIndex: clamped, price, bar } : null;
16485
16549
  if (this.crosshair) this.emitter.emit("crosshair", this.crosshair);
16486
16550
  const hoveringDotLevel = this.positionRenderStyle === "dot" && this.hoveredTradeLabel !== null;
16487
16551
  if (this.tradeButton) {
@@ -16497,6 +16561,28 @@ var ChartEngine = class {
16497
16561
  this.tradeButton.hide();
16498
16562
  }
16499
16563
  }
16564
+ if (this._pendingDrawResize) {
16565
+ const dx = x - this._pendingDrawResize.startX;
16566
+ const dy = y - this._pendingDrawResize.startY;
16567
+ if (Math.sqrt(dx * dx + dy * dy) > _ChartEngine.DRAW_GESTURE_THRESHOLD) {
16568
+ this.drawingManager.startResize(
16569
+ this._pendingDrawResize.drawing,
16570
+ this._pendingDrawResize.pointIndex
16571
+ );
16572
+ this._pendingDrawResize = null;
16573
+ }
16574
+ }
16575
+ if (this._pendingDrawDrag) {
16576
+ const dx = x - this._pendingDrawDrag.startX;
16577
+ const dy = y - this._pendingDrawDrag.startY;
16578
+ if (Math.sqrt(dx * dx + dy * dy) > _ChartEngine.DRAW_GESTURE_THRESHOLD) {
16579
+ this.drawingManager.startDrag(this._pendingDrawDrag.drawing, {
16580
+ barIndex: rawBarIndex,
16581
+ price
16582
+ });
16583
+ this._pendingDrawDrag = null;
16584
+ }
16585
+ }
16500
16586
  this.drawingManager.onMouseMove({ barIndex: rawBarIndex, price });
16501
16587
  if (this.axisDrag) {
16502
16588
  if (this.axisDrag.axis === "price") {
@@ -16525,6 +16611,7 @@ var ChartEngine = class {
16525
16611
  startIndex: newStart,
16526
16612
  endIndex: newStart + newVisible
16527
16613
  };
16614
+ this._targetViewport = { ...this.viewport };
16528
16615
  }
16529
16616
  this.drawCanvas.style.cursor = this.axisDrag.axis === "time" ? "ew-resize" : "ns-resize";
16530
16617
  this.scheduleRender();
@@ -16668,6 +16755,8 @@ var ChartEngine = class {
16668
16755
  this.drawCanvas.style.cursor = "default";
16669
16756
  this.scheduleRender();
16670
16757
  }
16758
+ this._pendingDrawDrag = null;
16759
+ this._pendingDrawResize = null;
16671
16760
  if (this.drawingManager.isDraggingDrawing()) {
16672
16761
  this.drawingManager.endDrag();
16673
16762
  this.drawCanvas.style.cursor = "default";
@@ -16749,6 +16838,7 @@ var ChartEngine = class {
16749
16838
  );
16750
16839
  if (handleHit) {
16751
16840
  e.preventDefault();
16841
+ this.panHandler.cancel();
16752
16842
  this.drawingManager.selectDrawing(handleHit.drawing);
16753
16843
  this.drawingManager.startResize(handleHit.drawing, handleHit.handle.pointIndex);
16754
16844
  this.hideMobileDrawingFlyout();
@@ -16766,18 +16856,66 @@ var ChartEngine = class {
16766
16856
  );
16767
16857
  if (bodyHit) {
16768
16858
  e.preventDefault();
16859
+ this.panHandler.cancel();
16769
16860
  this.drawingManager.selectDrawing(bodyHit);
16770
16861
  this.drawingManager.startDrag(bodyHit, point);
16771
16862
  this._drawTouchStartXY = { x: touch.clientX, y: touch.clientY };
16772
16863
  this.hideMobileDrawingFlyout();
16773
16864
  this.scheduleRender();
16774
- } else {
16775
- this.drawingManager.selectDrawing(null);
16776
- this.hideMobileDrawingFlyout();
16777
- const timeAxisY2 = rect.height - this.timeAxisHeight;
16778
- const isInChartArea = x < chartW && y < timeAxisY2;
16779
- if (isInChartArea) {
16865
+ return;
16866
+ }
16867
+ const confirmHit = hitTestTradeLevelConfirm(x, y, this.tradeLevelConfirmAreas);
16868
+ if (confirmHit) {
16869
+ e.preventDefault();
16870
+ if (confirmHit.action === "confirm") this.applyPendingChanges(confirmHit.label);
16871
+ else this.revertPendingChanges(confirmHit.label);
16872
+ return;
16873
+ }
16874
+ const closeHit = hitTestTradeLevelClose(x, y, this.tradeLevelHitAreas);
16875
+ if (closeHit) {
16876
+ e.preventDefault();
16877
+ this._handleTradeLevelClose(closeHit);
16878
+ return;
16879
+ }
16880
+ if (this.tradeDragHandler && !this.drawingManager.getActiveTool()) {
16881
+ const dragHit = hitTestTradeLevelDrag(x, y, this.tradeLevelDragAreas);
16882
+ if (dragHit) {
16883
+ e.preventDefault();
16884
+ const level = this.levels.find((l) => l.label === dragHit.label);
16885
+ if (level) {
16886
+ this._startTradeDragFromHit(dragHit, level, y);
16887
+ this.scheduleRender();
16888
+ return;
16889
+ }
16890
+ }
16891
+ const addHit = hitTestTradeLevelAddBracket(x, y, this.tradeAddBracketAreas);
16892
+ if (addHit) {
16893
+ e.preventDefault();
16894
+ this._startAddBracketFromHit(addHit, y);
16895
+ this.scheduleRender();
16896
+ return;
16897
+ }
16898
+ }
16899
+ this.drawingManager.selectDrawing(null);
16900
+ this.hideMobileDrawingFlyout();
16901
+ const isInChartArea = x < chartW && y < timeAxisY;
16902
+ if (isInChartArea) {
16903
+ if (this._mobileCrosshairActive) {
16904
+ if (this._longPressTimer !== null) {
16905
+ clearTimeout(this._longPressTimer);
16906
+ this._longPressTimer = null;
16907
+ }
16780
16908
  this._mobileTapStartXY = { x, y };
16909
+ } else {
16910
+ if (this._longPressTimer !== null) clearTimeout(this._longPressTimer);
16911
+ this._longPressPoint = { x, y };
16912
+ this._longPressTimer = setTimeout(() => {
16913
+ this._longPressTimer = null;
16914
+ if (this._longPressPoint) {
16915
+ this._activateMobileCrosshair(this._longPressPoint.x, this._longPressPoint.y);
16916
+ this._longPressPoint = null;
16917
+ }
16918
+ }, 500);
16781
16919
  }
16782
16920
  }
16783
16921
  };
@@ -16812,7 +16950,11 @@ var ChartEngine = class {
16812
16950
  );
16813
16951
  const mid = (this.viewport.startIndex + this.viewport.endIndex) / 2;
16814
16952
  const newStart = Math.max(0, mid - newVisible / 2);
16815
- this.viewport = { ...this.viewport, startIndex: newStart, endIndex: newStart + newVisible };
16953
+ this.viewport = {
16954
+ ...this.viewport,
16955
+ startIndex: newStart,
16956
+ endIndex: newStart + newVisible
16957
+ };
16816
16958
  }
16817
16959
  this.scheduleRender();
16818
16960
  return;
@@ -16825,16 +16967,52 @@ var ChartEngine = class {
16825
16967
  }
16826
16968
  if (this.drawingManager.isDraggingDrawing() || this.drawingManager.isResizingDrawing()) {
16827
16969
  e.preventDefault();
16970
+ e.stopPropagation();
16828
16971
  this.drawingManager.onMouseMove(point);
16829
16972
  this.scheduleRender();
16973
+ return;
16974
+ }
16975
+ if (this.tradeDragHandler?.active) {
16976
+ e.preventDefault();
16977
+ const touch = e.touches[0];
16978
+ const rect = this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect();
16979
+ const ty = touch.clientY - rect.top;
16980
+ const { priceH } = this.getChartLayout();
16981
+ const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(this.viewport);
16982
+ const moveResult = this.tradeDragHandler.onMouseMove(ty, this.scaleManager, priceRange, priceH);
16983
+ this.tradeDragNewPrice = moveResult?.newPrice ?? null;
16984
+ this.renderTradeLayer();
16985
+ if (this.tradeDragNewPrice !== null && moveResult) {
16986
+ const colonIdx = moveResult.label.indexOf(":");
16987
+ const baseLabel = colonIdx !== -1 ? moveResult.label.slice(0, colonIdx) : moveResult.label;
16988
+ const bracketType = colonIdx !== -1 ? moveResult.label.slice(colonIdx + 1) : void 0;
16989
+ const draggingLevel = this.levels.find((l) => l.label === baseLabel);
16990
+ if (draggingLevel) {
16991
+ this.emitter.emit("tradeLevelDrag", {
16992
+ label: draggingLevel.label,
16993
+ newPrice: this.tradeDragNewPrice,
16994
+ data: draggingLevel.data,
16995
+ bracketType
16996
+ });
16997
+ }
16998
+ }
16999
+ return;
16830
17000
  }
16831
- if (!this._mobileCrosshairActive && this._mobileTapStartXY) {
17001
+ if (this._mobileTapStartXY || this._longPressTimer !== null) {
16832
17002
  const touch = e.touches[0];
16833
17003
  const rect = this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect();
16834
- const dx = touch.clientX - rect.left - this._mobileTapStartXY.x;
16835
- const dy = touch.clientY - rect.top - this._mobileTapStartXY.y;
16836
- if (Math.sqrt(dx * dx + dy * dy) > 6) {
16837
- this._mobileTapStartXY = null;
17004
+ const tapOrigin = this._mobileTapStartXY ?? this._longPressPoint;
17005
+ if (tapOrigin) {
17006
+ const dx = touch.clientX - rect.left - tapOrigin.x;
17007
+ const dy = touch.clientY - rect.top - tapOrigin.y;
17008
+ if (Math.sqrt(dx * dx + dy * dy) > 6) {
17009
+ this._mobileTapStartXY = null;
17010
+ if (this._longPressTimer !== null) {
17011
+ clearTimeout(this._longPressTimer);
17012
+ this._longPressTimer = null;
17013
+ this._longPressPoint = null;
17014
+ }
17015
+ }
16838
17016
  }
16839
17017
  }
16840
17018
  if (this._mobileCrosshairActive) {
@@ -16864,6 +17042,23 @@ var ChartEngine = class {
16864
17042
  }
16865
17043
  };
16866
17044
  this.onDrawingTouchEnd = (_e) => {
17045
+ this._lastTouchTime = Date.now();
17046
+ if (this._longPressTimer !== null) {
17047
+ clearTimeout(this._longPressTimer);
17048
+ this._longPressTimer = null;
17049
+ this._longPressPoint = null;
17050
+ }
17051
+ if (this.tradeDragHandler?.active) {
17052
+ const result = this.tradeDragHandler.endDrag();
17053
+ if (result) {
17054
+ this.storePendingChange(result, result.startPrice, this._pendingBracketOrderLabel ?? void 0);
17055
+ } else {
17056
+ this.tradeDragNewPrice = null;
17057
+ }
17058
+ this._pendingBracketOrderLabel = null;
17059
+ this.renderTradeLayer();
17060
+ return;
17061
+ }
16867
17062
  if (this.axisDrag) {
16868
17063
  this.axisDrag = null;
16869
17064
  this.scheduleRender();
@@ -16896,35 +17091,11 @@ var ChartEngine = class {
16896
17091
  this.scheduleRender();
16897
17092
  }
16898
17093
  if (this._mobileTapStartXY) {
16899
- const { x, y } = this._mobileTapStartXY;
16900
17094
  this._mobileTapStartXY = null;
16901
- if (this._mobileCrosshairActive) {
16902
- this._mobileCrosshairActive = false;
16903
- this.crosshair = null;
16904
- this.tradeButton?.hide();
16905
- this.scheduleRender();
16906
- } else {
16907
- const rect = this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect();
16908
- const { chartW, priceH } = this.getChartLayout();
16909
- const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(this.viewport);
16910
- const timeAxisY = rect.height - this.timeAxisHeight;
16911
- if (x < chartW && y < timeAxisY) {
16912
- this._mobileCrosshairActive = true;
16913
- const rawBarIndex = this.scaleManager.pixelToBarIndex(x, this.viewport, chartW);
16914
- const price = this.scaleManager.pixelToPrice(y, priceRange, priceH);
16915
- const clamped = Math.max(
16916
- Math.floor(this.viewport.startIndex),
16917
- Math.min(Math.ceil(this.viewport.endIndex), Math.round(rawBarIndex))
16918
- );
16919
- const bar = this.dataStore.all[clamped] ?? null;
16920
- this.crosshair = { x, y, barIndex: clamped, price, bar };
16921
- if (this.tradeButton) {
16922
- this.tradeButton.setY(y);
16923
- this.tradeButton.show();
16924
- }
16925
- this.scheduleRender();
16926
- }
16927
- }
17095
+ this._mobileCrosshairActive = false;
17096
+ this.crosshair = null;
17097
+ this.tradeButton?.hide();
17098
+ this.scheduleRender();
16928
17099
  }
16929
17100
  };
16930
17101
  this.onCanvasClick = (e) => {
@@ -16932,7 +17103,7 @@ var ChartEngine = class {
16932
17103
  const x = e.clientX - rect.left;
16933
17104
  const y = e.clientY - rect.top;
16934
17105
  const { chartW, priceH } = this.getChartLayout();
16935
- const priceRange = this.getScaledPriceRange(this.viewport);
17106
+ const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(this.viewport);
16936
17107
  const rawBarIndex = this.scaleManager.pixelToBarIndex(x, this.viewport, chartW);
16937
17108
  const price = this.scaleManager.pixelToPrice(y, priceRange, priceH);
16938
17109
  const barIndex = Math.round(rawBarIndex);
@@ -16944,54 +17115,7 @@ var ChartEngine = class {
16944
17115
  }
16945
17116
  const closedLevel = hitTestTradeLevelClose(x, y, this.tradeLevelHitAreas);
16946
17117
  if (closedLevel) {
16947
- if (closedLevel.bracketType) {
16948
- const parentLevel = this.levels.find((l) => l.label === closedLevel.label);
16949
- const isMarketDraftBracket = parentLevel?.orderType === "market" && parentLevel?.data?._isDraft === true;
16950
- if (isMarketDraftBracket) {
16951
- const bracketKey = closedLevel.bracketType === "stopLoss" ? "sl" : "tp";
16952
- this.updateDraftOrderBracket(bracketKey, null);
16953
- this.emitter.emit("tradeLevelEdit", {
16954
- label: closedLevel.label,
16955
- data: parentLevel?.data,
16956
- type: "pending",
16957
- changes: [
16958
- {
16959
- field: closedLevel.bracketType === "stopLoss" ? "REMOVE_SL" : "REMOVE_TP",
16960
- newPrice: 0,
16961
- data: closedLevel.data
16962
- }
16963
- ]
16964
- });
16965
- return;
16966
- }
16967
- const field = closedLevel.bracketType;
16968
- const existing = this.pendingLevelDrags.get(closedLevel.label) ?? [];
16969
- const filtered = existing.filter((c) => c.field !== field);
16970
- filtered.push({
16971
- field,
16972
- newPrice: 0,
16973
- oldPrice: 0,
16974
- isRemoval: true,
16975
- bracketOrderLabel: closedLevel.bracketOrderLabel
16976
- });
16977
- this.pendingLevelDrags.set(closedLevel.label, filtered);
16978
- this.selectedTradeLabel = closedLevel.label;
16979
- this.renderTradeLayer();
16980
- return;
16981
- }
16982
- const isDraftOrder = closedLevel.label === this.draftOrderLabel;
16983
- this.revertPendingChanges(closedLevel.label);
16984
- this.pendingLevelDrags.delete(closedLevel.label);
16985
- if (isDraftOrder) return;
16986
- const originalLevel = this.levels.find((l) => l.label === closedLevel.label);
16987
- const isEntryOrder = originalLevel?.type === "position" && !!originalLevel.entryPriceEditable;
16988
- const closeAction = closedLevel.type === "position" && !isEntryOrder ? "CLOSED" : "CANCELLED";
16989
- this.emitter.emit("tradeLevelClose", {
16990
- label: closedLevel.label,
16991
- type: closedLevel.type,
16992
- action: closeAction,
16993
- data: closedLevel.data
16994
- });
17118
+ this._handleTradeLevelClose(closedLevel);
16995
17119
  return;
16996
17120
  }
16997
17121
  if (this.tradeDragHandler && !this.drawingManager.getActiveTool()) {
@@ -16999,54 +17123,7 @@ var ChartEngine = class {
16999
17123
  if (dragHit) {
17000
17124
  const level = this.levels.find((l) => l.label === dragHit.label);
17001
17125
  if (level) {
17002
- if (dragHit.isBracket) {
17003
- this._pendingBracketOrderLabel = dragHit.bracketOrderLabel ?? null;
17004
- this._isBracketNew = false;
17005
- const isEntryOrder = level.entryPriceEditable === true;
17006
- const isDraft = level.data?._isDraft === true;
17007
- const all = this.dataStore.all;
17008
- const marketPrice = isEntryOrder || isDraft ? 0 : all[all.length - 1]?.close ?? 0;
17009
- this.tradeDragHandler.startBracketDrag(
17010
- level,
17011
- dragHit.bracketType,
17012
- dragHit.price,
17013
- marketPrice,
17014
- this._effectiveEntryPrice(level.label, level.price)
17015
- );
17016
- } else {
17017
- this._pendingBracketOrderLabel = null;
17018
- const isEntryOrder = level.entryPriceEditable === true;
17019
- if (isEntryOrder) {
17020
- this.selectedTradeLabel = dragHit.label;
17021
- const posLevel = level;
17022
- const allPos = this.dataStore.all;
17023
- const mktPricePos = allPos[allPos.length - 1]?.close ?? 0;
17024
- this.tradeDragHandler.startDrag(
17025
- level,
17026
- y,
17027
- dragHit.price,
17028
- posLevel.stopLossPrice,
17029
- posLevel.takeProfitPrice,
17030
- posLevel.side,
17031
- mktPricePos,
17032
- posLevel.orderType
17033
- );
17034
- } else {
17035
- const pendingLevel = level;
17036
- const allBars = this.dataStore.all;
17037
- const mktPrice = allBars[allBars.length - 1]?.close ?? 0;
17038
- this.tradeDragHandler.startDrag(
17039
- level,
17040
- y,
17041
- dragHit.price,
17042
- void 0,
17043
- void 0,
17044
- pendingLevel.side,
17045
- mktPrice,
17046
- pendingLevel.orderType
17047
- );
17048
- }
17049
- }
17126
+ this._startTradeDragFromHit(dragHit, level, y);
17050
17127
  this.drawCanvas.style.cursor = "ns-resize";
17051
17128
  return;
17052
17129
  }
@@ -17055,43 +17132,9 @@ var ChartEngine = class {
17055
17132
  if (this.tradeDragHandler && !this.drawingManager.getActiveTool()) {
17056
17133
  const addHit = hitTestTradeLevelAddBracket(x, y, this.tradeAddBracketAreas);
17057
17134
  if (addHit) {
17058
- const level = this.levels.find((l) => l.label === addHit.label);
17059
- if (level) {
17060
- const { priceH: priceH2 } = this.getChartLayout();
17061
- const priceRange2 = this.getScaledPriceRange(this.viewport);
17062
- const btnCenterY = addHit.y + addHit.h / 2;
17063
- const initialPrice = this.scaleManager.pixelToPrice(
17064
- btnCenterY,
17065
- priceRange2,
17066
- priceH2
17067
- );
17068
- const isEntryOrder = level.entryPriceEditable === true;
17069
- const isDraft = level.data?._isDraft === true;
17070
- const all = this.dataStore.all;
17071
- const marketPrice = isEntryOrder || isDraft ? 0 : all[all.length - 1]?.close ?? 0;
17072
- if (!isDraft) {
17073
- this.emitter.emit("tradeLevelEditOpen", {
17074
- label: level.label,
17075
- type: level.type,
17076
- data: level.data,
17077
- price: level.price,
17078
- side: level.side,
17079
- stopLossPrice: level.stopLossPrice,
17080
- takeProfitPrice: level.takeProfitPrice
17081
- });
17082
- }
17083
- this._pendingBracketOrderLabel = null;
17084
- this._isBracketNew = true;
17085
- this.tradeDragHandler.startBracketDrag(
17086
- level,
17087
- addHit.bracketType,
17088
- initialPrice,
17089
- marketPrice,
17090
- this._effectiveEntryPrice(level.label, level.price)
17091
- );
17092
- this.drawCanvas.style.cursor = "ns-resize";
17093
- return;
17094
- }
17135
+ this._startAddBracketFromHit(addHit, y);
17136
+ this.drawCanvas.style.cursor = "ns-resize";
17137
+ return;
17095
17138
  }
17096
17139
  }
17097
17140
  if (!this.drawingManager.getActiveTool()) {
@@ -17192,7 +17235,12 @@ var ChartEngine = class {
17192
17235
  );
17193
17236
  if (handleHit) {
17194
17237
  this.drawingManager.selectDrawing(handleHit.drawing);
17195
- this.drawingManager.startResize(handleHit.drawing, handleHit.handle.pointIndex);
17238
+ this._pendingDrawResize = {
17239
+ drawing: handleHit.drawing,
17240
+ pointIndex: handleHit.handle.pointIndex,
17241
+ startX: x,
17242
+ startY: y
17243
+ };
17196
17244
  } else {
17197
17245
  const bodyHit = this.drawingManager.hitTest(
17198
17246
  x,
@@ -17205,10 +17253,7 @@ var ChartEngine = class {
17205
17253
  );
17206
17254
  this.drawingManager.selectDrawing(bodyHit);
17207
17255
  if (bodyHit) {
17208
- this.drawingManager.startDrag(bodyHit, {
17209
- barIndex: rawBarIndex,
17210
- price
17211
- });
17256
+ this._pendingDrawDrag = { drawing: bodyHit, startX: x, startY: y };
17212
17257
  }
17213
17258
  }
17214
17259
  const bar = this.dataStore.all[barIndex] ?? null;
@@ -17293,11 +17338,11 @@ var ChartEngine = class {
17293
17338
  );
17294
17339
  }
17295
17340
  const openingBar = all[all.length - 1];
17296
- if (this.viewport.endIndex >= all.length) {
17297
- this.viewport = {
17298
- ...this.viewport,
17299
- startIndex: this.viewport.startIndex + 1,
17300
- endIndex: this.viewport.endIndex + 1
17341
+ if (this._targetViewport.endIndex >= all.length) {
17342
+ this._targetViewport = {
17343
+ ...this._targetViewport,
17344
+ startIndex: this._targetViewport.startIndex + 1,
17345
+ endIndex: this._targetViewport.endIndex + 1
17301
17346
  };
17302
17347
  }
17303
17348
  this.emitter.emit("newBar", { completedBar, openingBar, intervalMs });
@@ -17310,7 +17355,7 @@ var ChartEngine = class {
17310
17355
  this.scheduleRender();
17311
17356
  };
17312
17357
  if (typeof window === "undefined" || typeof document === "undefined") {
17313
- throw new Error("stockchart requires a browser environment");
17358
+ throw new Error("acttrader-charts requires a browser environment");
17314
17359
  }
17315
17360
  const {
17316
17361
  container,
@@ -17336,16 +17381,26 @@ var ChartEngine = class {
17336
17381
  this.labels = resolveLabels(labels);
17337
17382
  this.tickActivityMs = tickActivityMs;
17338
17383
  this.maxCandles = config.maxCandles ?? 200;
17384
+ this.defaultVisibleBars = config.defaultVisibleBars ?? DEFAULT_VISIBLE_BARS;
17385
+ this.defaultMobileVisibleBars = config.defaultMobileVisibleBars;
17339
17386
  this.explicitMobileDivisor = config.mobileBarDivisor !== void 0;
17340
17387
  this.mobileBarDivisor = config.mobileBarDivisor ?? 2;
17341
- this.viewport = { ...this.viewport, endIndex: initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor) };
17388
+ this._autoVisibleBars = config.defaultVisibleBars == null && config.defaultMobileVisibleBars == null;
17389
+ this.targetCandleWidth = config.targetCandleWidth ?? DEFAULT_CANDLE_SLOT_WIDTH;
17390
+ this.viewport = {
17391
+ ...this.viewport,
17392
+ endIndex: initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor, this.defaultVisibleBars, this.defaultMobileVisibleBars)
17393
+ };
17394
+ this._targetViewport = { ...this.viewport };
17342
17395
  this.maxSubPanes = config.maxSubPanes ?? 3;
17343
17396
  this.tickClosePriceSource = config.tickClosePriceSource ?? "bid";
17344
17397
  this.tradesThresholdForHorizontalLine = config.tradesThresholdForHorizontalLine ?? 2;
17345
17398
  if (config.positionRenderStyle === "line" || config.positionRenderStyle === "dot") {
17346
17399
  this.positionRenderStyleOverride = config.positionRenderStyle;
17347
17400
  } else {
17348
- const _storedStyle = localStorage.getItem(this.RENDER_STYLE_LS_KEY);
17401
+ const _storedStyle = localStorage.getItem(
17402
+ this.RENDER_STYLE_LS_KEY
17403
+ );
17349
17404
  if (_storedStyle === "line" || _storedStyle === "dot") {
17350
17405
  this.positionRenderStyleOverride = _storedStyle;
17351
17406
  }
@@ -17483,7 +17538,8 @@ var ChartEngine = class {
17483
17538
  Object.assign(this.canvasWrap.style, {
17484
17539
  flex: "1",
17485
17540
  position: "relative",
17486
- overflow: "hidden"
17541
+ overflow: "hidden",
17542
+ touchAction: "none"
17487
17543
  });
17488
17544
  this.canvas = document.createElement("canvas");
17489
17545
  Object.assign(this.canvas.style, {
@@ -17513,8 +17569,11 @@ var ChartEngine = class {
17513
17569
  width: "100%",
17514
17570
  height: "100%",
17515
17571
  cursor: "crosshair",
17516
- touchAction: "none"
17572
+ touchAction: "none",
17573
+ userSelect: "none"
17517
17574
  });
17575
+ this.drawCanvas.style["webkitUserSelect"] = "none";
17576
+ this.drawCanvas.style["webkitTouchCallout"] = "none";
17518
17577
  this.canvasWrap.appendChild(this.drawCanvas);
17519
17578
  this.textInputEl = document.createElement("input");
17520
17579
  this.textInputEl.type = "text";
@@ -17596,6 +17655,12 @@ var ChartEngine = class {
17596
17655
  fontFamily: "'Inter', system-ui, sans-serif",
17597
17656
  whiteSpace: "nowrap"
17598
17657
  });
17658
+ this.siOHLC2Span = document.createElement("span");
17659
+ Object.assign(this.siOHLC2Span.style, {
17660
+ fontSize: "14px",
17661
+ fontFamily: "'Inter', system-ui, sans-serif",
17662
+ whiteSpace: "nowrap"
17663
+ });
17599
17664
  this.siStreamDot = document.createElement("span");
17600
17665
  Object.assign(this.siStreamDot.style, {
17601
17666
  width: "7px",
@@ -17605,7 +17670,14 @@ var ChartEngine = class {
17605
17670
  display: "inline-block",
17606
17671
  flexShrink: "0"
17607
17672
  });
17608
- this.siEl.append(this.siStreamDot, this.siSymbolSpan, this.siOHLCSpan);
17673
+ const siRow1 = document.createElement("div");
17674
+ Object.assign(siRow1.style, {
17675
+ display: "flex",
17676
+ alignItems: "center",
17677
+ gap: "8px"
17678
+ });
17679
+ siRow1.append(this.siStreamDot, this.siSymbolSpan, this.siOHLCSpan);
17680
+ this.siEl.append(siRow1, this.siOHLC2Span);
17609
17681
  this.canvasWrap.appendChild(this.siEl);
17610
17682
  }
17611
17683
  if (showUI) {
@@ -17673,7 +17745,6 @@ var ChartEngine = class {
17673
17745
  );
17674
17746
  this.canvasWrap.appendChild(this.tradeButton.el);
17675
17747
  this.tradeButton.el.addEventListener("mouseenter", () => {
17676
- this._isHoveringTradeButton = true;
17677
17748
  this.crosshair = null;
17678
17749
  this.scheduleRender();
17679
17750
  });
@@ -17689,9 +17760,6 @@ var ChartEngine = class {
17689
17760
  this._tradeButtonClickData = { price, y };
17690
17761
  }
17691
17762
  });
17692
- this.tradeButton.el.addEventListener("mouseleave", () => {
17693
- this._isHoveringTradeButton = false;
17694
- });
17695
17763
  }
17696
17764
  middleRow.appendChild(this.canvasWrap);
17697
17765
  this.wrapper.appendChild(middleRow);
@@ -17748,45 +17816,58 @@ var ChartEngine = class {
17748
17816
  this.drawingManager.onStartTextEdit = (drawing) => this.showTextInput(drawing);
17749
17817
  this.panHandler = new PanHandler(
17750
17818
  this.drawCanvas,
17751
- () => this.viewport,
17819
+ () => this._targetViewport,
17752
17820
  (v) => {
17753
- this.viewport = v;
17821
+ this._targetViewport = v;
17754
17822
  this.clearDuration();
17755
17823
  this.checkNeedMoreData();
17824
+ this.scheduleRender();
17756
17825
  },
17757
17826
  () => this.dataStore.length,
17758
17827
  () => this.scheduleRender(),
17759
17828
  () => this.drawingManager.getActiveTool() !== null || this.drawingManager.isDraggingDrawing() || this.drawingManager.isResizingDrawing() || this.separatorDrag !== null || this.axisDrag !== null || (this.tradeDragHandler?.active ?? false) || this._mobileCrosshairActive,
17760
17829
  (deltaY) => {
17761
17830
  const { priceH } = this.getChartLayout();
17762
- const range = this.getScaledPriceRange(this.viewport);
17763
- const pricePerPixel = (range.max - range.min) / priceH;
17831
+ const currentRange = this._displayedPriceRange ?? this.getScaledPriceRange(this._targetViewport);
17832
+ const pricePerPixel = (currentRange.max - currentRange.min) / priceH;
17764
17833
  this.pricePanOffset += deltaY * pricePerPixel;
17765
17834
  this.scheduleRender();
17835
+ },
17836
+ () => {
17837
+ this._isPanning = true;
17838
+ },
17839
+ () => {
17840
+ this._isPanning = false;
17766
17841
  }
17767
17842
  );
17768
17843
  this.zoomHandler = new ZoomHandler(
17769
17844
  this.drawCanvas,
17770
- () => this.viewport,
17845
+ () => this._targetViewport,
17771
17846
  (v) => {
17772
- this.viewport = v;
17847
+ this._targetViewport = v;
17773
17848
  this.clearDuration();
17774
17849
  this.checkNeedMoreData();
17850
+ this.scheduleRender();
17775
17851
  },
17776
17852
  () => this.dataStore.length,
17777
17853
  () => this.scheduleRender(),
17778
17854
  () => this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect(),
17779
17855
  () => this.getChartLayout().chartW,
17780
17856
  (dx) => {
17781
- const vp = this.viewport;
17857
+ const vp = this._targetViewport;
17782
17858
  const visibleBars = vp.endIndex - vp.startIndex;
17783
17859
  const barsPerPixel = visibleBars / this.drawCanvas.width;
17784
17860
  const shift = dx * barsPerPixel;
17785
- const maxStart = this.dataStore.length;
17861
+ const maxStart = Math.max(0, this.dataStore.length - Math.floor(visibleBars / 2));
17786
17862
  const newStart = Math.max(0, Math.min(maxStart, vp.startIndex + shift));
17787
- this.viewport = { ...vp, startIndex: newStart, endIndex: newStart + visibleBars };
17863
+ this._targetViewport = {
17864
+ ...vp,
17865
+ startIndex: newStart,
17866
+ endIndex: newStart + visibleBars
17867
+ };
17788
17868
  this.clearDuration();
17789
17869
  this.checkNeedMoreData();
17870
+ this.scheduleRender();
17790
17871
  }
17791
17872
  );
17792
17873
  this.drawCanvas.addEventListener("mousemove", this.onMouseMove);
@@ -17799,6 +17880,7 @@ var ChartEngine = class {
17799
17880
  });
17800
17881
  this.drawCanvas.addEventListener("touchmove", this.onDrawingTouchMove, { passive: false });
17801
17882
  this.drawCanvas.addEventListener("touchend", this.onDrawingTouchEnd, { passive: false });
17883
+ this.drawCanvas.addEventListener("contextmenu", (e) => e.preventDefault());
17802
17884
  document.addEventListener("keydown", this.onKeyDown);
17803
17885
  const onFullscreenChange = () => {
17804
17886
  const active = !!document.fullscreenElement;
@@ -17832,11 +17914,17 @@ var ChartEngine = class {
17832
17914
  this._displayedPriceRange = null;
17833
17915
  this._lockedAutoFitBase = null;
17834
17916
  this.dataStore.set(bars);
17917
+ const rawW = (this._cachedWrapRect?.width ?? this.canvasWrap.getBoundingClientRect().width) - this.priceAxisWidth;
17918
+ const autoChartW = this._autoVisibleBars && rawW > 0 ? rawW : void 0;
17835
17919
  this.viewport = {
17836
17920
  ...this.viewport,
17837
- startIndex: fitAll ? 0 : Math.max(0, bars.length - initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor) + LOOKAHEAD),
17921
+ startIndex: fitAll ? 0 : Math.max(
17922
+ 0,
17923
+ bars.length - initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor, this.defaultVisibleBars, this.defaultMobileVisibleBars, autoChartW, this.targetCandleWidth) + LOOKAHEAD
17924
+ ),
17838
17925
  endIndex: bars.length + LOOKAHEAD
17839
17926
  };
17927
+ this._targetViewport = { ...this.viewport };
17840
17928
  const hasVol = this.dataStore.hasVolumeData;
17841
17929
  if (!hasVol) this.showVolume = false;
17842
17930
  this.topBar?.setVolumeControl(this.showVolume, hasVol);
@@ -17861,6 +17949,7 @@ var ChartEngine = class {
17861
17949
  );
17862
17950
  }
17863
17951
  this.recomputeIndicators();
17952
+ this.reanchorDrawingsByTimestamp();
17864
17953
  this.updateCountdownState();
17865
17954
  this.scheduleRender();
17866
17955
  return this;
@@ -17879,6 +17968,8 @@ var ChartEngine = class {
17879
17968
  startIndex: this.viewport.startIndex + n,
17880
17969
  endIndex: this.viewport.endIndex + n
17881
17970
  };
17971
+ this._targetViewport = { ...this.viewport };
17972
+ this.drawingManager.shiftBarIndices(n);
17882
17973
  this.recomputeIndicators();
17883
17974
  this.scheduleRender();
17884
17975
  }
@@ -17981,8 +18072,11 @@ var ChartEngine = class {
17981
18072
  }
17982
18073
  checkNeedMoreData() {
17983
18074
  if (!this.dataLoader || this.isFetching || this.noMoreData) return;
17984
- const threshold = Math.max(20, Math.round(initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor) * 0.25));
17985
- if (this.viewport.startIndex > threshold) return;
18075
+ const threshold = Math.max(
18076
+ 20,
18077
+ Math.round(initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor, this.defaultVisibleBars, this.defaultMobileVisibleBars) * 0.25)
18078
+ );
18079
+ if (this._targetViewport.startIndex > threshold) return;
17986
18080
  if (this.dataStore.length === 0) return;
17987
18081
  this.isFetching = true;
17988
18082
  const before = this.dataStore.all[0].time;
@@ -18195,7 +18289,10 @@ var ChartEngine = class {
18195
18289
  }
18196
18290
  }
18197
18291
  } : void 0;
18198
- const merged = deepMergeTheme(deepMergeTheme(base, this.themeOverrides?.[mode]), userOverride);
18292
+ const merged = deepMergeTheme(
18293
+ deepMergeTheme(base, this.themeOverrides?.[mode]),
18294
+ userOverride
18295
+ );
18199
18296
  return resolveCssVarsInTheme(merged, this.wrapper);
18200
18297
  }
18201
18298
  setTheme(theme) {
@@ -18259,7 +18356,13 @@ var ChartEngine = class {
18259
18356
  params: { ...s.currentParams },
18260
18357
  visible: s.visible
18261
18358
  })),
18262
- drawings: this.drawingManager.getDrawingsSnapshot(),
18359
+ drawings: this.drawingManager.getDrawingsSnapshot().map((d) => ({
18360
+ ...d,
18361
+ points: d.points.map((p) => ({
18362
+ ...p,
18363
+ timestamp: this.dataStore.all[Math.floor(p.barIndex)]?.time
18364
+ }))
18365
+ })),
18263
18366
  positionRenderStyle: this.positionRenderStyle,
18264
18367
  tradeDisplayFilter: this.tradeDisplayFilter,
18265
18368
  canvasColors: Object.keys(this.settingsCanvasColors).length > 0 ? { ...this.settingsCanvasColors } : void 0
@@ -18309,7 +18412,21 @@ var ChartEngine = class {
18309
18412
  }
18310
18413
  }
18311
18414
  }
18312
- this.drawingManager.loadDrawings(state.drawings);
18415
+ const bars = this.dataStore.all;
18416
+ const resolvedDrawings = state.drawings.map((d) => ({
18417
+ ...d,
18418
+ points: d.points.map((p) => {
18419
+ if (!p.timestamp || bars.length === 0) return p;
18420
+ let lo = 0, hi = bars.length - 1;
18421
+ while (lo < hi) {
18422
+ const mid = lo + hi >> 1;
18423
+ if ((bars[mid]?.time ?? 0) < p.timestamp) lo = mid + 1;
18424
+ else hi = mid;
18425
+ }
18426
+ return { ...p, barIndex: lo + (p.barIndex - Math.floor(p.barIndex)) };
18427
+ })
18428
+ }));
18429
+ this.drawingManager.loadDrawings(resolvedDrawings);
18313
18430
  if (state.positionRenderStyle) {
18314
18431
  this.positionRenderStyleOverride = state.positionRenderStyle;
18315
18432
  this.positionRenderStyle = state.positionRenderStyle;
@@ -18326,6 +18443,27 @@ var ChartEngine = class {
18326
18443
  this.scheduleRender();
18327
18444
  return this;
18328
18445
  }
18446
+ /**
18447
+ * Re-anchors all loaded drawings to the current dataset using their stored timestamps.
18448
+ * Called after loadData() to fix drawings restored via setState() before data was
18449
+ * available (bars.length === 0 at restore time causes the raw barIndex fallback).
18450
+ */
18451
+ reanchorDrawingsByTimestamp() {
18452
+ const bars = this.dataStore.all;
18453
+ if (bars.length === 0) return;
18454
+ for (const drawing of this.drawingManager.getDrawings()) {
18455
+ for (const pt of drawing.points) {
18456
+ if (!pt.timestamp) continue;
18457
+ let lo = 0, hi = bars.length - 1;
18458
+ while (lo < hi) {
18459
+ const mid = lo + hi >> 1;
18460
+ if ((bars[mid]?.time ?? 0) < pt.timestamp) lo = mid + 1;
18461
+ else hi = mid;
18462
+ }
18463
+ pt.barIndex = lo + (pt.barIndex - Math.floor(pt.barIndex));
18464
+ }
18465
+ }
18466
+ }
18329
18467
  /**
18330
18468
  * Resets all chart settings that are captured by `getState()` back to their
18331
18469
  * defaults (candlestick / dark / 1D / 1Y / volume on / no indicators / no drawings)
@@ -18378,6 +18516,7 @@ var ChartEngine = class {
18378
18516
  if (!this.siOHLCSpan) return;
18379
18517
  if (!bar) {
18380
18518
  this.siOHLCSpan.innerHTML = "";
18519
+ if (this.siOHLC2Span) this.siOHLC2Span.innerHTML = "";
18381
18520
  return;
18382
18521
  }
18383
18522
  const up = bar.close >= bar.open;
@@ -18385,26 +18524,32 @@ var ChartEngine = class {
18385
18524
  const fmt = (n) => n.toFixed(this.dataStore.getPricePrecision());
18386
18525
  const fmtVol = (v) => v >= 1e6 ? (v / 1e6).toFixed(2) + "M" : v >= 1e3 ? (v / 1e3).toFixed(1) + "K" : v.toFixed(0);
18387
18526
  const ol = this.labels.ohlc;
18388
- const fields = [
18527
+ const renderFields = (el, fields) => {
18528
+ el.innerHTML = "";
18529
+ for (const [lbl, val, clr] of fields) {
18530
+ const span = document.createElement("span");
18531
+ span.style.marginRight = "8px";
18532
+ const key = document.createElement("span");
18533
+ key.textContent = lbl + "\xA0";
18534
+ key.style.color = this.theme.axisText;
18535
+ key.style.opacity = "0.6";
18536
+ const num = document.createElement("span");
18537
+ num.textContent = val;
18538
+ num.style.color = clr;
18539
+ span.append(key, num);
18540
+ el.appendChild(span);
18541
+ }
18542
+ };
18543
+ renderFields(this.siOHLCSpan, [
18389
18544
  [ol.open, fmt(bar.open), this.theme.axisText],
18390
- [ol.high, fmt(bar.high), this.theme.axisText],
18391
- [ol.low, fmt(bar.low), this.theme.axisText],
18392
- [ol.close, fmt(bar.close), color],
18393
- ...bar.volume != null && bar.volume !== 0 ? [[ol.volume, fmtVol(bar.volume), this.theme.axisText]] : []
18394
- ];
18395
- this.siOHLCSpan.innerHTML = "";
18396
- for (const [lbl, val, clr] of fields) {
18397
- const span = document.createElement("span");
18398
- span.style.marginRight = "8px";
18399
- const key = document.createElement("span");
18400
- key.textContent = lbl + "\xA0";
18401
- key.style.color = this.theme.axisText;
18402
- key.style.opacity = "0.6";
18403
- const num = document.createElement("span");
18404
- num.textContent = val;
18405
- num.style.color = clr;
18406
- span.append(key, num);
18407
- this.siOHLCSpan.appendChild(span);
18545
+ [ol.high, fmt(bar.high), this.theme.axisText]
18546
+ ]);
18547
+ if (this.siOHLC2Span) {
18548
+ renderFields(this.siOHLC2Span, [
18549
+ [ol.low, fmt(bar.low), this.theme.axisText],
18550
+ [ol.close, fmt(bar.close), color],
18551
+ ...bar.volume != null && bar.volume !== 0 ? [[ol.volume, fmtVol(bar.volume), this.theme.axisText]] : []
18552
+ ]);
18408
18553
  }
18409
18554
  }
18410
18555
  setMinLots(lots) {
@@ -18873,7 +19018,9 @@ var ChartEngine = class {
18873
19018
  }
18874
19019
  updateCountdownState() {
18875
19020
  const INTRADAY = ["1m", "5m", "15m", "30m", "1h", "4h"];
18876
- const allowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY).includes(this.timeframe);
19021
+ const allowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY).includes(
19022
+ this.timeframe
19023
+ );
18877
19024
  const should = this.showCandleCountdown && allowed && this.dataStore.length > 0;
18878
19025
  if (should && !this.countdownActive) {
18879
19026
  this.countdownActive = true;
@@ -19179,6 +19326,7 @@ var ChartEngine = class {
19179
19326
  this.getScaledPriceRange(this.viewport),
19180
19327
  priceH
19181
19328
  );
19329
+ this.crosshair = null;
19182
19330
  this.draftOrderQtyInput?.show(yPx, this._minLots, this.theme, priceH);
19183
19331
  this.renderTradeLayer();
19184
19332
  }
@@ -19277,6 +19425,28 @@ var ChartEngine = class {
19277
19425
  ctx.fillRect(0, 0, totalW, totalH);
19278
19426
  const bars = this.dataStore.getVisible(viewport);
19279
19427
  if (bars.length === 0) return;
19428
+ {
19429
+ if (this._isPanning) {
19430
+ this.viewport = { ...this._targetViewport };
19431
+ } else {
19432
+ const dStart = this._targetViewport.startIndex - this.viewport.startIndex;
19433
+ const dEnd = this._targetViewport.endIndex - this.viewport.endIndex;
19434
+ const visibleBars = this._targetViewport.endIndex - this._targetViewport.startIndex;
19435
+ const epsilon = visibleBars * 2e-4;
19436
+ if (Math.abs(dStart) < epsilon && Math.abs(dEnd) < epsilon) {
19437
+ this.viewport = { ...this._targetViewport };
19438
+ } else {
19439
+ this.viewport = {
19440
+ ...this.viewport,
19441
+ startIndex: this.viewport.startIndex + dStart * VIEWPORT_LERP,
19442
+ endIndex: this.viewport.endIndex + dEnd * VIEWPORT_LERP,
19443
+ candleWidth: this._targetViewport.candleWidth,
19444
+ candleGap: this._targetViewport.candleGap
19445
+ };
19446
+ this.scheduleRender();
19447
+ }
19448
+ }
19449
+ }
19280
19450
  const isManualMode = this.priceScaleFactor !== 1 || this.pricePanOffset !== 0;
19281
19451
  if (!isManualMode) {
19282
19452
  this._lockedAutoFitBase = null;
@@ -19612,7 +19782,9 @@ var ChartEngine = class {
19612
19782
  }
19613
19783
  {
19614
19784
  const INTRADAY_TF = ["1m", "5m", "15m", "30m", "1h", "4h"];
19615
- const countdownAllowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY_TF).includes(this.timeframe);
19785
+ const countdownAllowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY_TF).includes(
19786
+ this.timeframe
19787
+ );
19616
19788
  const showCountdown = this.showCandleCountdown && countdownAllowed && bars.length > 0 && viewport.endIndex >= this.dataStore.length - 1;
19617
19789
  const countdownBarIndex = showCountdown ? this.dataStore.length - 1 : void 0;
19618
19790
  if (this._debug) {
@@ -19710,7 +19882,7 @@ var ChartEngine = class {
19710
19882
  this.dataStore.getPricePrecision()
19711
19883
  );
19712
19884
  if (this.scrollToEndBtn) {
19713
- const atEnd = this.dataStore.length === 0 || this.viewport.endIndex >= this.dataStore.length - 0.5;
19885
+ const atEnd = this.dataStore.length === 0 || this._targetViewport.endIndex >= this.dataStore.length - 0.5;
19714
19886
  if (atEnd !== !this.scrollToEndVisible) {
19715
19887
  this.scrollToEndVisible = !atEnd;
19716
19888
  this.scrollToEndBtn.style.opacity = atEnd ? "0" : "0.85";
@@ -19740,6 +19912,11 @@ var ChartEngine = class {
19740
19912
  this._mobileLayout = isMobile;
19741
19913
  this.leftBar?.setMobileMode(isMobile);
19742
19914
  this.topBar?.setMobileLayout(isMobile);
19915
+ if (this.siEl) {
19916
+ this.siEl.style.flexDirection = isMobile ? "column" : "row";
19917
+ this.siEl.style.alignItems = isMobile ? "flex-start" : "center";
19918
+ this.siEl.style.gap = isMobile ? "2px" : "8px";
19919
+ }
19743
19920
  if (isMobile) {
19744
19921
  this.leftBarVisible = false;
19745
19922
  this.topBar?.setDrawingsBtnActive(false);
@@ -19830,6 +20007,164 @@ var ChartEngine = class {
19830
20007
  price: this.scaleManager.pixelToPrice(y, priceRange, priceH)
19831
20008
  };
19832
20009
  }
20010
+ /** Shared close-button handler used by both mouse click and touch tap. */
20011
+ _handleTradeLevelClose(closedLevel) {
20012
+ if (closedLevel.bracketType) {
20013
+ const parentLevel = this.levels.find((l) => l.label === closedLevel.label);
20014
+ const isMarketDraftBracket = parentLevel?.orderType === "market" && parentLevel?.data?._isDraft === true;
20015
+ if (isMarketDraftBracket) {
20016
+ const bracketKey = closedLevel.bracketType === "stopLoss" ? "sl" : "tp";
20017
+ this.updateDraftOrderBracket(bracketKey, null);
20018
+ this.emitter.emit("tradeLevelEdit", {
20019
+ label: closedLevel.label,
20020
+ data: parentLevel?.data,
20021
+ type: "pending",
20022
+ changes: [
20023
+ {
20024
+ field: closedLevel.bracketType === "stopLoss" ? "REMOVE_SL" : "REMOVE_TP",
20025
+ newPrice: 0,
20026
+ data: closedLevel.data
20027
+ }
20028
+ ]
20029
+ });
20030
+ return;
20031
+ }
20032
+ const field = closedLevel.bracketType;
20033
+ const existing = this.pendingLevelDrags.get(closedLevel.label) ?? [];
20034
+ const filtered = existing.filter((c) => c.field !== field);
20035
+ filtered.push({
20036
+ field,
20037
+ newPrice: 0,
20038
+ oldPrice: 0,
20039
+ isRemoval: true,
20040
+ bracketOrderLabel: closedLevel.bracketOrderLabel
20041
+ });
20042
+ this.pendingLevelDrags.set(closedLevel.label, filtered);
20043
+ this.selectedTradeLabel = closedLevel.label;
20044
+ this.renderTradeLayer();
20045
+ return;
20046
+ }
20047
+ const isDraftOrder = closedLevel.label === this.draftOrderLabel;
20048
+ this.revertPendingChanges(closedLevel.label);
20049
+ this.pendingLevelDrags.delete(closedLevel.label);
20050
+ if (isDraftOrder) return;
20051
+ const originalLevel = this.levels.find((l) => l.label === closedLevel.label);
20052
+ const isEntryOrder = originalLevel?.type === "position" && !!originalLevel.entryPriceEditable;
20053
+ const closeAction = closedLevel.type === "position" && !isEntryOrder ? "CLOSED" : "CANCELLED";
20054
+ this.emitter.emit("tradeLevelClose", {
20055
+ label: closedLevel.label,
20056
+ type: closedLevel.type,
20057
+ action: closeAction,
20058
+ data: closedLevel.data
20059
+ });
20060
+ }
20061
+ /** Activates the mobile crosshair at canvas coords (x, y). */
20062
+ _activateMobileCrosshair(x, y) {
20063
+ const rect = this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect();
20064
+ const { chartW, priceH } = this.getChartLayout();
20065
+ const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(this.viewport);
20066
+ const timeAxisY = rect.height - this.timeAxisHeight;
20067
+ if (x >= chartW || y >= timeAxisY) return;
20068
+ this._mobileCrosshairActive = true;
20069
+ const rawBarIndex = this.scaleManager.pixelToBarIndex(x, this.viewport, chartW);
20070
+ const price = this.scaleManager.pixelToPrice(y, priceRange, priceH);
20071
+ const clamped = Math.max(
20072
+ Math.floor(this.viewport.startIndex),
20073
+ Math.min(Math.ceil(this.viewport.endIndex), Math.round(rawBarIndex))
20074
+ );
20075
+ const bar = this.dataStore.all[clamped] ?? null;
20076
+ this.crosshair = { x, y, barIndex: clamped, price, bar };
20077
+ if (this.tradeButton) {
20078
+ this.tradeButton.setY(y);
20079
+ this.tradeButton.show();
20080
+ }
20081
+ this.scheduleRender();
20082
+ }
20083
+ /** Shared helper: initiate a trade level main-line drag from a hit area. */
20084
+ _startTradeDragFromHit(dragHit, level, hitY) {
20085
+ if (!this.tradeDragHandler) return;
20086
+ if (dragHit.isBracket) {
20087
+ this._pendingBracketOrderLabel = dragHit.bracketOrderLabel ?? null;
20088
+ this._isBracketNew = false;
20089
+ const isEntryOrder = level.entryPriceEditable === true;
20090
+ const isDraft = level.data?._isDraft === true;
20091
+ const all = this.dataStore.all;
20092
+ const marketPrice = isEntryOrder || isDraft ? 0 : all[all.length - 1]?.close ?? 0;
20093
+ this.tradeDragHandler.startBracketDrag(
20094
+ level,
20095
+ dragHit.bracketType,
20096
+ dragHit.price,
20097
+ marketPrice,
20098
+ this._effectiveEntryPrice(level.label, level.price)
20099
+ );
20100
+ } else {
20101
+ this._pendingBracketOrderLabel = null;
20102
+ const isEntryOrder = level.entryPriceEditable === true;
20103
+ if (isEntryOrder) {
20104
+ this.selectedTradeLabel = dragHit.label;
20105
+ const posLevel = level;
20106
+ const allPos = this.dataStore.all;
20107
+ const mktPricePos = allPos[allPos.length - 1]?.close ?? 0;
20108
+ this.tradeDragHandler.startDrag(
20109
+ level,
20110
+ hitY,
20111
+ dragHit.price,
20112
+ posLevel.stopLossPrice,
20113
+ posLevel.takeProfitPrice,
20114
+ posLevel.side,
20115
+ mktPricePos,
20116
+ posLevel.orderType
20117
+ );
20118
+ } else {
20119
+ const pendingLevel = level;
20120
+ const allBars = this.dataStore.all;
20121
+ const mktPrice = allBars[allBars.length - 1]?.close ?? 0;
20122
+ this.tradeDragHandler.startDrag(
20123
+ level,
20124
+ hitY,
20125
+ dragHit.price,
20126
+ void 0,
20127
+ void 0,
20128
+ pendingLevel.side,
20129
+ mktPrice,
20130
+ pendingLevel.orderType
20131
+ );
20132
+ }
20133
+ }
20134
+ }
20135
+ /** Shared helper: initiate a +SL/+TP add-bracket drag from a hit area. */
20136
+ _startAddBracketFromHit(addHit, hitY) {
20137
+ const level = this.levels.find((l) => l.label === addHit.label);
20138
+ if (!level || !this.tradeDragHandler) return;
20139
+ const { priceH } = this.getChartLayout();
20140
+ const priceRange = this.getScaledPriceRange(this.viewport);
20141
+ const btnCenterY = addHit.y + addHit.h / 2;
20142
+ const initialPrice = this.scaleManager.pixelToPrice(btnCenterY, priceRange, priceH);
20143
+ const isEntryOrder = level.entryPriceEditable === true;
20144
+ const isDraft = level.data?._isDraft === true;
20145
+ const all = this.dataStore.all;
20146
+ const marketPrice = isEntryOrder || isDraft ? 0 : all[all.length - 1]?.close ?? 0;
20147
+ if (!isDraft) {
20148
+ this.emitter.emit("tradeLevelEditOpen", {
20149
+ label: level.label,
20150
+ type: level.type,
20151
+ data: level.data,
20152
+ price: level.price,
20153
+ side: level.side,
20154
+ stopLossPrice: level.stopLossPrice,
20155
+ takeProfitPrice: level.takeProfitPrice
20156
+ });
20157
+ }
20158
+ this._pendingBracketOrderLabel = null;
20159
+ this._isBracketNew = true;
20160
+ this.tradeDragHandler.startBracketDrag(
20161
+ level,
20162
+ addHit.bracketType,
20163
+ initialPrice,
20164
+ marketPrice,
20165
+ this._effectiveEntryPrice(level.label, level.price)
20166
+ );
20167
+ }
19833
20168
  // ── Text drawing input ────────────────────────────────────────────────────
19834
20169
  showTextInput(drawing) {
19835
20170
  if (!this.textInputEl) return;
@@ -20000,6 +20335,7 @@ var ChartEngine = class {
20000
20335
  startIndex: newEnd - visible,
20001
20336
  endIndex: newEnd
20002
20337
  };
20338
+ this._targetViewport = { ...this.viewport };
20003
20339
  this.pricePanOffset = startPan * (1 - prog);
20004
20340
  this.render();
20005
20341
  if (raw < 1) {
@@ -20019,8 +20355,12 @@ var ChartEngine = class {
20019
20355
  resetTimeAxis() {
20020
20356
  const total = this.dataStore.length;
20021
20357
  if (total === 0) return;
20022
- const newStart = Math.max(0, total - initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor) + LOOKAHEAD);
20358
+ const newStart = Math.max(
20359
+ 0,
20360
+ total - initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor, this.defaultVisibleBars, this.defaultMobileVisibleBars) + LOOKAHEAD
20361
+ );
20023
20362
  this.viewport = { ...this.viewport, startIndex: newStart, endIndex: total + LOOKAHEAD };
20363
+ this._targetViewport = { ...this.viewport };
20024
20364
  this.scheduleRender();
20025
20365
  }
20026
20366
  /** Reset both price and time axes to their default state. */
@@ -20079,6 +20419,8 @@ var ChartEngine = class {
20079
20419
  });
20080
20420
  }
20081
20421
  };
20422
+ _ChartEngine.DRAW_GESTURE_THRESHOLD = 4;
20423
+ var ChartEngine = _ChartEngine;
20082
20424
 
20083
20425
  // src/data/WebSocketAdapter.ts
20084
20426
  var WebSocketAdapter = class {