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.js CHANGED
@@ -83,17 +83,17 @@ var DataStore = class {
83
83
  var _ScaleManager = class _ScaleManager {
84
84
  // ── X axis: bar index → canvas pixel ──────────────────────────────────────
85
85
  xToPixel(index, viewport, chartWidth) {
86
- const visibleBars = viewport.endIndex - viewport.startIndex;
86
+ const visibleBars = Math.max(1, viewport.endIndex - viewport.startIndex);
87
87
  const candleWidth = chartWidth / visibleBars;
88
88
  return (index - viewport.startIndex) * candleWidth + candleWidth / 2;
89
89
  }
90
90
  pixelToBarIndex(x, viewport, chartWidth) {
91
- const visibleBars = viewport.endIndex - viewport.startIndex;
91
+ const visibleBars = Math.max(1, viewport.endIndex - viewport.startIndex);
92
92
  const candleWidth = chartWidth / visibleBars;
93
93
  return viewport.startIndex + x / candleWidth - 0.5;
94
94
  }
95
95
  candleBodyWidth(viewport, chartWidth) {
96
- const visibleBars = viewport.endIndex - viewport.startIndex;
96
+ const visibleBars = Math.max(1, viewport.endIndex - viewport.startIndex);
97
97
  const raw = chartWidth / visibleBars * 0.7;
98
98
  return Math.max(1, Math.min(raw, 40));
99
99
  }
@@ -715,7 +715,7 @@ function resolveLabels(override) {
715
715
  // src/interaction/PanHandler.ts
716
716
  var _PanHandler = class _PanHandler {
717
717
  // px — below this = click, not drag
718
- constructor(canvas, getViewport, setViewport, totalBars, onUpdate, isLocked = () => false, panY) {
718
+ constructor(canvas, getViewport, setViewport, totalBars, onUpdate, isLocked = () => false, panY, onDragStart, onDragEnd) {
719
719
  this.canvas = canvas;
720
720
  this.getViewport = getViewport;
721
721
  this.setViewport = setViewport;
@@ -723,15 +723,20 @@ var _PanHandler = class _PanHandler {
723
723
  this.onUpdate = onUpdate;
724
724
  this.isLocked = isLocked;
725
725
  this.panY = panY;
726
+ this.onDragStart = onDragStart;
727
+ this.onDragEnd = onDragEnd;
726
728
  this.isDragging = false;
729
+ this.isTouchDrag = false;
727
730
  this.lastX = 0;
728
731
  this.lastY = 0;
729
732
  this.dragStartX = 0;
730
733
  this.dragStartY = 0;
731
734
  this.hasPanned = false;
735
+ this.dragStartFired = false;
732
736
  this.onMouseDown = (e) => {
733
737
  if (this.isLocked()) return;
734
738
  this.isDragging = true;
739
+ this.isTouchDrag = false;
735
740
  this.lastX = e.clientX;
736
741
  this.lastY = e.clientY;
737
742
  this.dragStartX = e.clientX;
@@ -742,8 +747,13 @@ var _PanHandler = class _PanHandler {
742
747
  if (this.isLocked()) return;
743
748
  if (e.touches.length !== 1) return;
744
749
  this.isDragging = true;
750
+ this.isTouchDrag = true;
745
751
  this.lastX = e.touches[0].clientX;
746
752
  this.lastY = e.touches[0].clientY;
753
+ this.dragStartX = e.touches[0].clientX;
754
+ this.dragStartY = e.touches[0].clientY;
755
+ this.dragStartFired = true;
756
+ this.onDragStart?.();
747
757
  };
748
758
  this.onMouseMove = (e) => {
749
759
  if (!this.isDragging) return;
@@ -760,6 +770,8 @@ var _PanHandler = class _PanHandler {
760
770
  const totalDeltaY = Math.abs(e.clientY - this.dragStartY);
761
771
  if (!this.hasPanned && (totalDeltaX > _PanHandler.DRAG_THRESHOLD || totalDeltaY > _PanHandler.DRAG_THRESHOLD)) {
762
772
  this.hasPanned = true;
773
+ this.dragStartFired = true;
774
+ this.onDragStart?.();
763
775
  this.canvas.style.cursor = "grabbing";
764
776
  }
765
777
  if (this.hasPanned) {
@@ -785,6 +797,10 @@ var _PanHandler = class _PanHandler {
785
797
  if (this.hasPanned) this.canvas.style.cursor = "crosshair";
786
798
  this.isDragging = false;
787
799
  this.hasPanned = false;
800
+ if (this.dragStartFired) {
801
+ this.dragStartFired = false;
802
+ this.onDragEnd?.();
803
+ }
788
804
  };
789
805
  this.attach();
790
806
  }
@@ -792,10 +808,15 @@ var _PanHandler = class _PanHandler {
792
808
  this.canvas.addEventListener("mousedown", this.onMouseDown);
793
809
  window.addEventListener("mousemove", this.onMouseMove);
794
810
  window.addEventListener("mouseup", this.onMouseUp);
795
- this.canvas.addEventListener("touchstart", this.onTouchStart, { passive: true });
811
+ this.canvas.addEventListener("touchstart", this.onTouchStart, { passive: false });
796
812
  window.addEventListener("touchmove", this.onTouchMove, { passive: false });
797
813
  window.addEventListener("touchend", this.onMouseUp);
798
814
  }
815
+ /** Cancel any in-progress pan gesture (e.g. when a drawing drag takes over). */
816
+ cancel() {
817
+ this.isDragging = false;
818
+ this.hasPanned = false;
819
+ }
799
820
  destroy() {
800
821
  this.canvas.removeEventListener("mousedown", this.onMouseDown);
801
822
  window.removeEventListener("mousemove", this.onMouseMove);
@@ -807,9 +828,10 @@ var _PanHandler = class _PanHandler {
807
828
  pan(deltaX) {
808
829
  const vp = this.getViewport();
809
830
  const visibleBars = vp.endIndex - vp.startIndex;
810
- const barsPerPixel = visibleBars / this.canvas.width;
831
+ const width = this.isTouchDrag ? this.canvas.clientWidth || this.canvas.width / (window.devicePixelRatio || 1) : this.canvas.width;
832
+ const barsPerPixel = visibleBars / width;
811
833
  const shift = -deltaX * barsPerPixel;
812
- const maxStart = this.totalBars();
834
+ const maxStart = Math.max(0, this.totalBars() - Math.floor(visibleBars / 2));
813
835
  const newStart = Math.max(0, Math.min(maxStart, vp.startIndex + shift));
814
836
  this.setViewport({ ...vp, startIndex: newStart, endIndex: newStart + visibleBars });
815
837
  this.onUpdate();
@@ -908,7 +930,7 @@ var ZoomHandler = class {
908
930
  }
909
931
  attach() {
910
932
  this.canvas.addEventListener("wheel", this.onWheel, { passive: false });
911
- this.canvas.addEventListener("touchstart", this.onTouchStart, { passive: true });
933
+ this.canvas.addEventListener("touchstart", this.onTouchStart, { passive: false });
912
934
  this.canvas.addEventListener("touchmove", this.onTouchMove, { passive: false });
913
935
  this.canvas.addEventListener("touchend", this.onTouchEnd);
914
936
  }
@@ -10523,6 +10545,19 @@ var DrawingManager = class {
10523
10545
  isResizingDrawing() {
10524
10546
  return this.resizeDrawing !== null;
10525
10547
  }
10548
+ /** Shift all drawings' barIndex values by `delta` (called when bars are prepended to DataStore). */
10549
+ shiftBarIndices(delta) {
10550
+ for (const drawing of this.drawings) {
10551
+ for (const pt of drawing.points) {
10552
+ pt.barIndex += delta;
10553
+ }
10554
+ }
10555
+ if (this.dragStartPoints) {
10556
+ for (const pt of this.dragStartPoints) {
10557
+ pt.barIndex += delta;
10558
+ }
10559
+ }
10560
+ }
10526
10561
  /** Returns the handle under (x,y) across all complete drawings, or null */
10527
10562
  getHandleAtPoint(x, y, scale, viewport, priceRange, chartW, chartH) {
10528
10563
  for (let i = this.drawings.length - 1; i >= 0; i--) {
@@ -12338,9 +12373,9 @@ var DraftOrderQtyInput = class _DraftOrderQtyInput {
12338
12373
  display: "none",
12339
12374
  alignItems: "center",
12340
12375
  gap: "5px",
12341
- right: `${priceAxisWidth + 70}px`,
12376
+ right: `${priceAxisWidth + 4}px`,
12342
12377
  top: "0",
12343
- transform: "translateY(-50%)",
12378
+ transform: "translateY(calc(-100% - 16px))",
12344
12379
  zIndex: "18",
12345
12380
  pointerEvents: "auto",
12346
12381
  userSelect: "none",
@@ -12390,7 +12425,7 @@ var DraftOrderQtyInput = class _DraftOrderQtyInput {
12390
12425
  this.input.step = String(minQty);
12391
12426
  this.input.value = String(this._pendingLots ?? minQty);
12392
12427
  this._pendingLots = null;
12393
- const clampedY = priceH !== void 0 ? Math.max(10, Math.min(priceH - 10, y)) : y;
12428
+ const clampedY = priceH !== void 0 ? Math.max(54, Math.min(priceH - 10, y)) : y;
12394
12429
  this.el.style.top = `${clampedY}px`;
12395
12430
  this.el.style.display = "flex";
12396
12431
  requestAnimationFrame(() => this.input.focus({ preventScroll: true }));
@@ -12412,12 +12447,12 @@ var DraftOrderQtyInput = class _DraftOrderQtyInput {
12412
12447
  return isNaN(v) || v <= 0 ? parseFloat(this.input.min || "1") : v;
12413
12448
  }
12414
12449
  setY(y, priceH) {
12415
- const clampedY = priceH !== void 0 ? Math.max(10, Math.min(priceH - 10, y)) : y;
12450
+ const clampedY = priceH !== void 0 ? Math.max(54, Math.min(priceH - 10, y)) : y;
12416
12451
  this.el.style.top = `${clampedY}px`;
12417
12452
  }
12418
12453
  setTheme(theme, priceAxisWidth) {
12419
12454
  this.priceAxisWidth = priceAxisWidth;
12420
- this.el.style.right = `${priceAxisWidth + 70}px`;
12455
+ this.el.style.right = `${priceAxisWidth + 4}px`;
12421
12456
  const bg = _DraftOrderQtyInput.boxBg(theme);
12422
12457
  const fg = _DraftOrderQtyInput.boxFg(theme);
12423
12458
  Object.assign(this.input.style, {
@@ -13785,13 +13820,20 @@ var VOLUME_PANEL_RATIO = 0.18;
13785
13820
  var SUB_PANE_HEIGHT = 80;
13786
13821
  var PANE_SEPARATOR = 1;
13787
13822
  var DEFAULT_VISIBLE_BARS = 80;
13823
+ var DEFAULT_CANDLE_SLOT_WIDTH = 10;
13788
13824
  var PRICE_RANGE_LERP = 0.25;
13825
+ var VIEWPORT_LERP = 0.2;
13789
13826
  function isMobileDevice() {
13790
13827
  return typeof window !== "undefined" && ("ontouchstart" in window || navigator.maxTouchPoints > 1);
13791
13828
  }
13792
- function initialVisibleBars(mobileBarDivisor = 2, forceApply = false) {
13793
- if (!forceApply && !isMobileDevice()) return DEFAULT_VISIBLE_BARS;
13794
- return Math.max(10, Math.round(DEFAULT_VISIBLE_BARS / mobileBarDivisor));
13829
+ function initialVisibleBars(mobileBarDivisor = 2, forceApply = false, defaultBars = DEFAULT_VISIBLE_BARS, defaultMobileBars, chartWidth, targetCandleSlotWidth) {
13830
+ if (chartWidth && chartWidth > 0 && targetCandleSlotWidth && targetCandleSlotWidth > 0) {
13831
+ const auto = Math.floor(chartWidth / targetCandleSlotWidth);
13832
+ return Math.max(10, Math.min(500, auto));
13833
+ }
13834
+ if (!forceApply && !isMobileDevice()) return defaultBars;
13835
+ if (defaultMobileBars !== void 0) return defaultMobileBars;
13836
+ return Math.max(10, Math.round(defaultBars / mobileBarDivisor));
13795
13837
  }
13796
13838
  var LOOKAHEAD = 4;
13797
13839
  function applySubPaneScale(range, factor) {
@@ -13807,7 +13849,7 @@ function subPaneAxisPrecision(range) {
13807
13849
  if (span >= 0.1) return 3;
13808
13850
  return 4;
13809
13851
  }
13810
- var ChartEngine = class {
13852
+ var _ChartEngine = class _ChartEngine {
13811
13853
  constructor(config) {
13812
13854
  // UI bars
13813
13855
  this.topBar = null;
@@ -13826,6 +13868,7 @@ var ChartEngine = class {
13826
13868
  this.siStreamDot = null;
13827
13869
  this.siSymbolSpan = null;
13828
13870
  this.siOHLCSpan = null;
13871
+ this.siOHLC2Span = null;
13829
13872
  this.tickActivityTimer = null;
13830
13873
  this.tickActivityMs = 3e4;
13831
13874
  this.streamStatus = "disconnected";
@@ -13860,11 +13903,24 @@ var ChartEngine = class {
13860
13903
  candleWidth: 8,
13861
13904
  candleGap: 2
13862
13905
  };
13906
+ // Logical target viewport — pan/zoom handlers write here; `viewport` lerps toward it each frame.
13907
+ this._targetViewport = {
13908
+ startIndex: 0,
13909
+ endIndex: initialVisibleBars(),
13910
+ candleWidth: 8,
13911
+ candleGap: 2
13912
+ };
13913
+ this._isPanning = false;
13863
13914
  this.crosshair = null;
13864
13915
  // Drawing
13865
13916
  this.drawingManager = new DrawingManager();
13866
13917
  this.textInputEl = null;
13867
13918
  this.textInputDrawing = null;
13919
+ /** Pending drag — committed only after mouse moves past DRAW_GESTURE_THRESHOLD. */
13920
+ this._pendingDrawDrag = null;
13921
+ /** Pending resize — committed only after mouse moves past DRAW_GESTURE_THRESHOLD. */
13922
+ this._pendingDrawResize = null;
13923
+ // px
13868
13924
  // Pane resize
13869
13925
  this.paneHeightOverrides = /* @__PURE__ */ new Map();
13870
13926
  this.separatorDrag = null;
@@ -13884,7 +13940,9 @@ var ChartEngine = class {
13884
13940
  this.subPaneScaleFactors = /* @__PURE__ */ new Map();
13885
13941
  this._mobileCrosshairActive = false;
13886
13942
  this._mobileTapStartXY = null;
13887
- this._isHoveringTradeButton = false;
13943
+ this._longPressTimer = null;
13944
+ this._longPressPoint = null;
13945
+ this._lastTouchTime = 0;
13888
13946
  this._tradeButtonClickData = null;
13889
13947
  this.rafId = null;
13890
13948
  this.countdownRafId = null;
@@ -13937,8 +13995,13 @@ var ChartEngine = class {
13937
13995
  this.isFetching = false;
13938
13996
  this.noMoreData = false;
13939
13997
  this.maxCandles = 200;
13998
+ this.defaultVisibleBars = DEFAULT_VISIBLE_BARS;
13999
+ this.defaultMobileVisibleBars = void 0;
13940
14000
  this.mobileBarDivisor = 2;
13941
14001
  this.explicitMobileDivisor = false;
14002
+ this._autoVisibleBars = true;
14003
+ // false when defaultVisibleBars was explicitly set
14004
+ this.targetCandleWidth = DEFAULT_CANDLE_SLOT_WIDTH;
13942
14005
  this.maxSubPanes = 3;
13943
14006
  this.showCandleCountdown = true;
13944
14007
  this.loadingOverlayEl = null;
@@ -13948,6 +14011,7 @@ var ChartEngine = class {
13948
14011
  this.isRestoringState = false;
13949
14012
  this.onMouseMove = (e) => {
13950
14013
  if (this._mobileCrosshairActive) return;
14014
+ if (Date.now() - this._lastTouchTime < 500) return;
13951
14015
  const rect = this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect();
13952
14016
  const x = e.clientX - rect.left;
13953
14017
  const y = e.clientY - rect.top;
@@ -13962,7 +14026,7 @@ var ChartEngine = class {
13962
14026
  Math.min(Math.ceil(this.viewport.endIndex), Math.round(rawBarIndex))
13963
14027
  );
13964
14028
  const bar = this.dataStore.all[clamped] ?? null;
13965
- this.crosshair = isInChartArea && !this._isHoveringTradeButton ? { x, y, barIndex: clamped, price, bar } : null;
14029
+ this.crosshair = isInChartArea && this.tradeButton === null ? { x, y, barIndex: clamped, price, bar } : null;
13966
14030
  if (this.crosshair) this.emitter.emit("crosshair", this.crosshair);
13967
14031
  const hoveringDotLevel = this.positionRenderStyle === "dot" && this.hoveredTradeLabel !== null;
13968
14032
  if (this.tradeButton) {
@@ -13978,6 +14042,28 @@ var ChartEngine = class {
13978
14042
  this.tradeButton.hide();
13979
14043
  }
13980
14044
  }
14045
+ if (this._pendingDrawResize) {
14046
+ const dx = x - this._pendingDrawResize.startX;
14047
+ const dy = y - this._pendingDrawResize.startY;
14048
+ if (Math.sqrt(dx * dx + dy * dy) > _ChartEngine.DRAW_GESTURE_THRESHOLD) {
14049
+ this.drawingManager.startResize(
14050
+ this._pendingDrawResize.drawing,
14051
+ this._pendingDrawResize.pointIndex
14052
+ );
14053
+ this._pendingDrawResize = null;
14054
+ }
14055
+ }
14056
+ if (this._pendingDrawDrag) {
14057
+ const dx = x - this._pendingDrawDrag.startX;
14058
+ const dy = y - this._pendingDrawDrag.startY;
14059
+ if (Math.sqrt(dx * dx + dy * dy) > _ChartEngine.DRAW_GESTURE_THRESHOLD) {
14060
+ this.drawingManager.startDrag(this._pendingDrawDrag.drawing, {
14061
+ barIndex: rawBarIndex,
14062
+ price
14063
+ });
14064
+ this._pendingDrawDrag = null;
14065
+ }
14066
+ }
13981
14067
  this.drawingManager.onMouseMove({ barIndex: rawBarIndex, price });
13982
14068
  if (this.axisDrag) {
13983
14069
  if (this.axisDrag.axis === "price") {
@@ -14006,6 +14092,7 @@ var ChartEngine = class {
14006
14092
  startIndex: newStart,
14007
14093
  endIndex: newStart + newVisible
14008
14094
  };
14095
+ this._targetViewport = { ...this.viewport };
14009
14096
  }
14010
14097
  this.drawCanvas.style.cursor = this.axisDrag.axis === "time" ? "ew-resize" : "ns-resize";
14011
14098
  this.scheduleRender();
@@ -14149,6 +14236,8 @@ var ChartEngine = class {
14149
14236
  this.drawCanvas.style.cursor = "default";
14150
14237
  this.scheduleRender();
14151
14238
  }
14239
+ this._pendingDrawDrag = null;
14240
+ this._pendingDrawResize = null;
14152
14241
  if (this.drawingManager.isDraggingDrawing()) {
14153
14242
  this.drawingManager.endDrag();
14154
14243
  this.drawCanvas.style.cursor = "default";
@@ -14230,6 +14319,7 @@ var ChartEngine = class {
14230
14319
  );
14231
14320
  if (handleHit) {
14232
14321
  e.preventDefault();
14322
+ this.panHandler.cancel();
14233
14323
  this.drawingManager.selectDrawing(handleHit.drawing);
14234
14324
  this.drawingManager.startResize(handleHit.drawing, handleHit.handle.pointIndex);
14235
14325
  this.hideMobileDrawingFlyout();
@@ -14247,18 +14337,66 @@ var ChartEngine = class {
14247
14337
  );
14248
14338
  if (bodyHit) {
14249
14339
  e.preventDefault();
14340
+ this.panHandler.cancel();
14250
14341
  this.drawingManager.selectDrawing(bodyHit);
14251
14342
  this.drawingManager.startDrag(bodyHit, point);
14252
14343
  this._drawTouchStartXY = { x: touch.clientX, y: touch.clientY };
14253
14344
  this.hideMobileDrawingFlyout();
14254
14345
  this.scheduleRender();
14255
- } else {
14256
- this.drawingManager.selectDrawing(null);
14257
- this.hideMobileDrawingFlyout();
14258
- const timeAxisY2 = rect.height - this.timeAxisHeight;
14259
- const isInChartArea = x < chartW && y < timeAxisY2;
14260
- if (isInChartArea) {
14346
+ return;
14347
+ }
14348
+ const confirmHit = hitTestTradeLevelConfirm(x, y, this.tradeLevelConfirmAreas);
14349
+ if (confirmHit) {
14350
+ e.preventDefault();
14351
+ if (confirmHit.action === "confirm") this.applyPendingChanges(confirmHit.label);
14352
+ else this.revertPendingChanges(confirmHit.label);
14353
+ return;
14354
+ }
14355
+ const closeHit = hitTestTradeLevelClose(x, y, this.tradeLevelHitAreas);
14356
+ if (closeHit) {
14357
+ e.preventDefault();
14358
+ this._handleTradeLevelClose(closeHit);
14359
+ return;
14360
+ }
14361
+ if (this.tradeDragHandler && !this.drawingManager.getActiveTool()) {
14362
+ const dragHit = hitTestTradeLevelDrag(x, y, this.tradeLevelDragAreas);
14363
+ if (dragHit) {
14364
+ e.preventDefault();
14365
+ const level = this.levels.find((l) => l.label === dragHit.label);
14366
+ if (level) {
14367
+ this._startTradeDragFromHit(dragHit, level, y);
14368
+ this.scheduleRender();
14369
+ return;
14370
+ }
14371
+ }
14372
+ const addHit = hitTestTradeLevelAddBracket(x, y, this.tradeAddBracketAreas);
14373
+ if (addHit) {
14374
+ e.preventDefault();
14375
+ this._startAddBracketFromHit(addHit, y);
14376
+ this.scheduleRender();
14377
+ return;
14378
+ }
14379
+ }
14380
+ this.drawingManager.selectDrawing(null);
14381
+ this.hideMobileDrawingFlyout();
14382
+ const isInChartArea = x < chartW && y < timeAxisY;
14383
+ if (isInChartArea) {
14384
+ if (this._mobileCrosshairActive) {
14385
+ if (this._longPressTimer !== null) {
14386
+ clearTimeout(this._longPressTimer);
14387
+ this._longPressTimer = null;
14388
+ }
14261
14389
  this._mobileTapStartXY = { x, y };
14390
+ } else {
14391
+ if (this._longPressTimer !== null) clearTimeout(this._longPressTimer);
14392
+ this._longPressPoint = { x, y };
14393
+ this._longPressTimer = setTimeout(() => {
14394
+ this._longPressTimer = null;
14395
+ if (this._longPressPoint) {
14396
+ this._activateMobileCrosshair(this._longPressPoint.x, this._longPressPoint.y);
14397
+ this._longPressPoint = null;
14398
+ }
14399
+ }, 500);
14262
14400
  }
14263
14401
  }
14264
14402
  };
@@ -14293,7 +14431,11 @@ var ChartEngine = class {
14293
14431
  );
14294
14432
  const mid = (this.viewport.startIndex + this.viewport.endIndex) / 2;
14295
14433
  const newStart = Math.max(0, mid - newVisible / 2);
14296
- this.viewport = { ...this.viewport, startIndex: newStart, endIndex: newStart + newVisible };
14434
+ this.viewport = {
14435
+ ...this.viewport,
14436
+ startIndex: newStart,
14437
+ endIndex: newStart + newVisible
14438
+ };
14297
14439
  }
14298
14440
  this.scheduleRender();
14299
14441
  return;
@@ -14306,16 +14448,52 @@ var ChartEngine = class {
14306
14448
  }
14307
14449
  if (this.drawingManager.isDraggingDrawing() || this.drawingManager.isResizingDrawing()) {
14308
14450
  e.preventDefault();
14451
+ e.stopPropagation();
14309
14452
  this.drawingManager.onMouseMove(point);
14310
14453
  this.scheduleRender();
14454
+ return;
14455
+ }
14456
+ if (this.tradeDragHandler?.active) {
14457
+ e.preventDefault();
14458
+ const touch = e.touches[0];
14459
+ const rect = this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect();
14460
+ const ty = touch.clientY - rect.top;
14461
+ const { priceH } = this.getChartLayout();
14462
+ const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(this.viewport);
14463
+ const moveResult = this.tradeDragHandler.onMouseMove(ty, this.scaleManager, priceRange, priceH);
14464
+ this.tradeDragNewPrice = moveResult?.newPrice ?? null;
14465
+ this.renderTradeLayer();
14466
+ if (this.tradeDragNewPrice !== null && moveResult) {
14467
+ const colonIdx = moveResult.label.indexOf(":");
14468
+ const baseLabel = colonIdx !== -1 ? moveResult.label.slice(0, colonIdx) : moveResult.label;
14469
+ const bracketType = colonIdx !== -1 ? moveResult.label.slice(colonIdx + 1) : void 0;
14470
+ const draggingLevel = this.levels.find((l) => l.label === baseLabel);
14471
+ if (draggingLevel) {
14472
+ this.emitter.emit("tradeLevelDrag", {
14473
+ label: draggingLevel.label,
14474
+ newPrice: this.tradeDragNewPrice,
14475
+ data: draggingLevel.data,
14476
+ bracketType
14477
+ });
14478
+ }
14479
+ }
14480
+ return;
14311
14481
  }
14312
- if (!this._mobileCrosshairActive && this._mobileTapStartXY) {
14482
+ if (this._mobileTapStartXY || this._longPressTimer !== null) {
14313
14483
  const touch = e.touches[0];
14314
14484
  const rect = this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect();
14315
- const dx = touch.clientX - rect.left - this._mobileTapStartXY.x;
14316
- const dy = touch.clientY - rect.top - this._mobileTapStartXY.y;
14317
- if (Math.sqrt(dx * dx + dy * dy) > 6) {
14318
- this._mobileTapStartXY = null;
14485
+ const tapOrigin = this._mobileTapStartXY ?? this._longPressPoint;
14486
+ if (tapOrigin) {
14487
+ const dx = touch.clientX - rect.left - tapOrigin.x;
14488
+ const dy = touch.clientY - rect.top - tapOrigin.y;
14489
+ if (Math.sqrt(dx * dx + dy * dy) > 6) {
14490
+ this._mobileTapStartXY = null;
14491
+ if (this._longPressTimer !== null) {
14492
+ clearTimeout(this._longPressTimer);
14493
+ this._longPressTimer = null;
14494
+ this._longPressPoint = null;
14495
+ }
14496
+ }
14319
14497
  }
14320
14498
  }
14321
14499
  if (this._mobileCrosshairActive) {
@@ -14345,6 +14523,23 @@ var ChartEngine = class {
14345
14523
  }
14346
14524
  };
14347
14525
  this.onDrawingTouchEnd = (_e) => {
14526
+ this._lastTouchTime = Date.now();
14527
+ if (this._longPressTimer !== null) {
14528
+ clearTimeout(this._longPressTimer);
14529
+ this._longPressTimer = null;
14530
+ this._longPressPoint = null;
14531
+ }
14532
+ if (this.tradeDragHandler?.active) {
14533
+ const result = this.tradeDragHandler.endDrag();
14534
+ if (result) {
14535
+ this.storePendingChange(result, result.startPrice, this._pendingBracketOrderLabel ?? void 0);
14536
+ } else {
14537
+ this.tradeDragNewPrice = null;
14538
+ }
14539
+ this._pendingBracketOrderLabel = null;
14540
+ this.renderTradeLayer();
14541
+ return;
14542
+ }
14348
14543
  if (this.axisDrag) {
14349
14544
  this.axisDrag = null;
14350
14545
  this.scheduleRender();
@@ -14377,35 +14572,11 @@ var ChartEngine = class {
14377
14572
  this.scheduleRender();
14378
14573
  }
14379
14574
  if (this._mobileTapStartXY) {
14380
- const { x, y } = this._mobileTapStartXY;
14381
14575
  this._mobileTapStartXY = null;
14382
- if (this._mobileCrosshairActive) {
14383
- this._mobileCrosshairActive = false;
14384
- this.crosshair = null;
14385
- this.tradeButton?.hide();
14386
- this.scheduleRender();
14387
- } else {
14388
- const rect = this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect();
14389
- const { chartW, priceH } = this.getChartLayout();
14390
- const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(this.viewport);
14391
- const timeAxisY = rect.height - this.timeAxisHeight;
14392
- if (x < chartW && y < timeAxisY) {
14393
- this._mobileCrosshairActive = true;
14394
- const rawBarIndex = this.scaleManager.pixelToBarIndex(x, this.viewport, chartW);
14395
- const price = this.scaleManager.pixelToPrice(y, priceRange, priceH);
14396
- const clamped = Math.max(
14397
- Math.floor(this.viewport.startIndex),
14398
- Math.min(Math.ceil(this.viewport.endIndex), Math.round(rawBarIndex))
14399
- );
14400
- const bar = this.dataStore.all[clamped] ?? null;
14401
- this.crosshair = { x, y, barIndex: clamped, price, bar };
14402
- if (this.tradeButton) {
14403
- this.tradeButton.setY(y);
14404
- this.tradeButton.show();
14405
- }
14406
- this.scheduleRender();
14407
- }
14408
- }
14576
+ this._mobileCrosshairActive = false;
14577
+ this.crosshair = null;
14578
+ this.tradeButton?.hide();
14579
+ this.scheduleRender();
14409
14580
  }
14410
14581
  };
14411
14582
  this.onCanvasClick = (e) => {
@@ -14413,7 +14584,7 @@ var ChartEngine = class {
14413
14584
  const x = e.clientX - rect.left;
14414
14585
  const y = e.clientY - rect.top;
14415
14586
  const { chartW, priceH } = this.getChartLayout();
14416
- const priceRange = this.getScaledPriceRange(this.viewport);
14587
+ const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(this.viewport);
14417
14588
  const rawBarIndex = this.scaleManager.pixelToBarIndex(x, this.viewport, chartW);
14418
14589
  const price = this.scaleManager.pixelToPrice(y, priceRange, priceH);
14419
14590
  const barIndex = Math.round(rawBarIndex);
@@ -14425,54 +14596,7 @@ var ChartEngine = class {
14425
14596
  }
14426
14597
  const closedLevel = hitTestTradeLevelClose(x, y, this.tradeLevelHitAreas);
14427
14598
  if (closedLevel) {
14428
- if (closedLevel.bracketType) {
14429
- const parentLevel = this.levels.find((l) => l.label === closedLevel.label);
14430
- const isMarketDraftBracket = parentLevel?.orderType === "market" && parentLevel?.data?._isDraft === true;
14431
- if (isMarketDraftBracket) {
14432
- const bracketKey = closedLevel.bracketType === "stopLoss" ? "sl" : "tp";
14433
- this.updateDraftOrderBracket(bracketKey, null);
14434
- this.emitter.emit("tradeLevelEdit", {
14435
- label: closedLevel.label,
14436
- data: parentLevel?.data,
14437
- type: "pending",
14438
- changes: [
14439
- {
14440
- field: closedLevel.bracketType === "stopLoss" ? "REMOVE_SL" : "REMOVE_TP",
14441
- newPrice: 0,
14442
- data: closedLevel.data
14443
- }
14444
- ]
14445
- });
14446
- return;
14447
- }
14448
- const field = closedLevel.bracketType;
14449
- const existing = this.pendingLevelDrags.get(closedLevel.label) ?? [];
14450
- const filtered = existing.filter((c) => c.field !== field);
14451
- filtered.push({
14452
- field,
14453
- newPrice: 0,
14454
- oldPrice: 0,
14455
- isRemoval: true,
14456
- bracketOrderLabel: closedLevel.bracketOrderLabel
14457
- });
14458
- this.pendingLevelDrags.set(closedLevel.label, filtered);
14459
- this.selectedTradeLabel = closedLevel.label;
14460
- this.renderTradeLayer();
14461
- return;
14462
- }
14463
- const isDraftOrder = closedLevel.label === this.draftOrderLabel;
14464
- this.revertPendingChanges(closedLevel.label);
14465
- this.pendingLevelDrags.delete(closedLevel.label);
14466
- if (isDraftOrder) return;
14467
- const originalLevel = this.levels.find((l) => l.label === closedLevel.label);
14468
- const isEntryOrder = originalLevel?.type === "position" && !!originalLevel.entryPriceEditable;
14469
- const closeAction = closedLevel.type === "position" && !isEntryOrder ? "CLOSED" : "CANCELLED";
14470
- this.emitter.emit("tradeLevelClose", {
14471
- label: closedLevel.label,
14472
- type: closedLevel.type,
14473
- action: closeAction,
14474
- data: closedLevel.data
14475
- });
14599
+ this._handleTradeLevelClose(closedLevel);
14476
14600
  return;
14477
14601
  }
14478
14602
  if (this.tradeDragHandler && !this.drawingManager.getActiveTool()) {
@@ -14480,54 +14604,7 @@ var ChartEngine = class {
14480
14604
  if (dragHit) {
14481
14605
  const level = this.levels.find((l) => l.label === dragHit.label);
14482
14606
  if (level) {
14483
- if (dragHit.isBracket) {
14484
- this._pendingBracketOrderLabel = dragHit.bracketOrderLabel ?? null;
14485
- this._isBracketNew = false;
14486
- const isEntryOrder = level.entryPriceEditable === true;
14487
- const isDraft = level.data?._isDraft === true;
14488
- const all = this.dataStore.all;
14489
- const marketPrice = isEntryOrder || isDraft ? 0 : all[all.length - 1]?.close ?? 0;
14490
- this.tradeDragHandler.startBracketDrag(
14491
- level,
14492
- dragHit.bracketType,
14493
- dragHit.price,
14494
- marketPrice,
14495
- this._effectiveEntryPrice(level.label, level.price)
14496
- );
14497
- } else {
14498
- this._pendingBracketOrderLabel = null;
14499
- const isEntryOrder = level.entryPriceEditable === true;
14500
- if (isEntryOrder) {
14501
- this.selectedTradeLabel = dragHit.label;
14502
- const posLevel = level;
14503
- const allPos = this.dataStore.all;
14504
- const mktPricePos = allPos[allPos.length - 1]?.close ?? 0;
14505
- this.tradeDragHandler.startDrag(
14506
- level,
14507
- y,
14508
- dragHit.price,
14509
- posLevel.stopLossPrice,
14510
- posLevel.takeProfitPrice,
14511
- posLevel.side,
14512
- mktPricePos,
14513
- posLevel.orderType
14514
- );
14515
- } else {
14516
- const pendingLevel = level;
14517
- const allBars = this.dataStore.all;
14518
- const mktPrice = allBars[allBars.length - 1]?.close ?? 0;
14519
- this.tradeDragHandler.startDrag(
14520
- level,
14521
- y,
14522
- dragHit.price,
14523
- void 0,
14524
- void 0,
14525
- pendingLevel.side,
14526
- mktPrice,
14527
- pendingLevel.orderType
14528
- );
14529
- }
14530
- }
14607
+ this._startTradeDragFromHit(dragHit, level, y);
14531
14608
  this.drawCanvas.style.cursor = "ns-resize";
14532
14609
  return;
14533
14610
  }
@@ -14536,43 +14613,9 @@ var ChartEngine = class {
14536
14613
  if (this.tradeDragHandler && !this.drawingManager.getActiveTool()) {
14537
14614
  const addHit = hitTestTradeLevelAddBracket(x, y, this.tradeAddBracketAreas);
14538
14615
  if (addHit) {
14539
- const level = this.levels.find((l) => l.label === addHit.label);
14540
- if (level) {
14541
- const { priceH: priceH2 } = this.getChartLayout();
14542
- const priceRange2 = this.getScaledPriceRange(this.viewport);
14543
- const btnCenterY = addHit.y + addHit.h / 2;
14544
- const initialPrice = this.scaleManager.pixelToPrice(
14545
- btnCenterY,
14546
- priceRange2,
14547
- priceH2
14548
- );
14549
- const isEntryOrder = level.entryPriceEditable === true;
14550
- const isDraft = level.data?._isDraft === true;
14551
- const all = this.dataStore.all;
14552
- const marketPrice = isEntryOrder || isDraft ? 0 : all[all.length - 1]?.close ?? 0;
14553
- if (!isDraft) {
14554
- this.emitter.emit("tradeLevelEditOpen", {
14555
- label: level.label,
14556
- type: level.type,
14557
- data: level.data,
14558
- price: level.price,
14559
- side: level.side,
14560
- stopLossPrice: level.stopLossPrice,
14561
- takeProfitPrice: level.takeProfitPrice
14562
- });
14563
- }
14564
- this._pendingBracketOrderLabel = null;
14565
- this._isBracketNew = true;
14566
- this.tradeDragHandler.startBracketDrag(
14567
- level,
14568
- addHit.bracketType,
14569
- initialPrice,
14570
- marketPrice,
14571
- this._effectiveEntryPrice(level.label, level.price)
14572
- );
14573
- this.drawCanvas.style.cursor = "ns-resize";
14574
- return;
14575
- }
14616
+ this._startAddBracketFromHit(addHit, y);
14617
+ this.drawCanvas.style.cursor = "ns-resize";
14618
+ return;
14576
14619
  }
14577
14620
  }
14578
14621
  if (!this.drawingManager.getActiveTool()) {
@@ -14673,7 +14716,12 @@ var ChartEngine = class {
14673
14716
  );
14674
14717
  if (handleHit) {
14675
14718
  this.drawingManager.selectDrawing(handleHit.drawing);
14676
- this.drawingManager.startResize(handleHit.drawing, handleHit.handle.pointIndex);
14719
+ this._pendingDrawResize = {
14720
+ drawing: handleHit.drawing,
14721
+ pointIndex: handleHit.handle.pointIndex,
14722
+ startX: x,
14723
+ startY: y
14724
+ };
14677
14725
  } else {
14678
14726
  const bodyHit = this.drawingManager.hitTest(
14679
14727
  x,
@@ -14686,10 +14734,7 @@ var ChartEngine = class {
14686
14734
  );
14687
14735
  this.drawingManager.selectDrawing(bodyHit);
14688
14736
  if (bodyHit) {
14689
- this.drawingManager.startDrag(bodyHit, {
14690
- barIndex: rawBarIndex,
14691
- price
14692
- });
14737
+ this._pendingDrawDrag = { drawing: bodyHit, startX: x, startY: y };
14693
14738
  }
14694
14739
  }
14695
14740
  const bar = this.dataStore.all[barIndex] ?? null;
@@ -14774,11 +14819,11 @@ var ChartEngine = class {
14774
14819
  );
14775
14820
  }
14776
14821
  const openingBar = all[all.length - 1];
14777
- if (this.viewport.endIndex >= all.length) {
14778
- this.viewport = {
14779
- ...this.viewport,
14780
- startIndex: this.viewport.startIndex + 1,
14781
- endIndex: this.viewport.endIndex + 1
14822
+ if (this._targetViewport.endIndex >= all.length) {
14823
+ this._targetViewport = {
14824
+ ...this._targetViewport,
14825
+ startIndex: this._targetViewport.startIndex + 1,
14826
+ endIndex: this._targetViewport.endIndex + 1
14782
14827
  };
14783
14828
  }
14784
14829
  this.emitter.emit("newBar", { completedBar, openingBar, intervalMs });
@@ -14791,7 +14836,7 @@ var ChartEngine = class {
14791
14836
  this.scheduleRender();
14792
14837
  };
14793
14838
  if (typeof window === "undefined" || typeof document === "undefined") {
14794
- throw new Error("stockchart requires a browser environment");
14839
+ throw new Error("acttrader-charts requires a browser environment");
14795
14840
  }
14796
14841
  const {
14797
14842
  container,
@@ -14817,16 +14862,26 @@ var ChartEngine = class {
14817
14862
  this.labels = resolveLabels(labels);
14818
14863
  this.tickActivityMs = tickActivityMs;
14819
14864
  this.maxCandles = config.maxCandles ?? 200;
14865
+ this.defaultVisibleBars = config.defaultVisibleBars ?? DEFAULT_VISIBLE_BARS;
14866
+ this.defaultMobileVisibleBars = config.defaultMobileVisibleBars;
14820
14867
  this.explicitMobileDivisor = config.mobileBarDivisor !== void 0;
14821
14868
  this.mobileBarDivisor = config.mobileBarDivisor ?? 2;
14822
- this.viewport = { ...this.viewport, endIndex: initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor) };
14869
+ this._autoVisibleBars = config.defaultVisibleBars == null && config.defaultMobileVisibleBars == null;
14870
+ this.targetCandleWidth = config.targetCandleWidth ?? DEFAULT_CANDLE_SLOT_WIDTH;
14871
+ this.viewport = {
14872
+ ...this.viewport,
14873
+ endIndex: initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor, this.defaultVisibleBars, this.defaultMobileVisibleBars)
14874
+ };
14875
+ this._targetViewport = { ...this.viewport };
14823
14876
  this.maxSubPanes = config.maxSubPanes ?? 3;
14824
14877
  this.tickClosePriceSource = config.tickClosePriceSource ?? "bid";
14825
14878
  this.tradesThresholdForHorizontalLine = config.tradesThresholdForHorizontalLine ?? 2;
14826
14879
  if (config.positionRenderStyle === "line" || config.positionRenderStyle === "dot") {
14827
14880
  this.positionRenderStyleOverride = config.positionRenderStyle;
14828
14881
  } else {
14829
- const _storedStyle = localStorage.getItem(this.RENDER_STYLE_LS_KEY);
14882
+ const _storedStyle = localStorage.getItem(
14883
+ this.RENDER_STYLE_LS_KEY
14884
+ );
14830
14885
  if (_storedStyle === "line" || _storedStyle === "dot") {
14831
14886
  this.positionRenderStyleOverride = _storedStyle;
14832
14887
  }
@@ -14964,7 +15019,8 @@ var ChartEngine = class {
14964
15019
  Object.assign(this.canvasWrap.style, {
14965
15020
  flex: "1",
14966
15021
  position: "relative",
14967
- overflow: "hidden"
15022
+ overflow: "hidden",
15023
+ touchAction: "none"
14968
15024
  });
14969
15025
  this.canvas = document.createElement("canvas");
14970
15026
  Object.assign(this.canvas.style, {
@@ -14994,8 +15050,11 @@ var ChartEngine = class {
14994
15050
  width: "100%",
14995
15051
  height: "100%",
14996
15052
  cursor: "crosshair",
14997
- touchAction: "none"
15053
+ touchAction: "none",
15054
+ userSelect: "none"
14998
15055
  });
15056
+ this.drawCanvas.style["webkitUserSelect"] = "none";
15057
+ this.drawCanvas.style["webkitTouchCallout"] = "none";
14999
15058
  this.canvasWrap.appendChild(this.drawCanvas);
15000
15059
  this.textInputEl = document.createElement("input");
15001
15060
  this.textInputEl.type = "text";
@@ -15077,6 +15136,12 @@ var ChartEngine = class {
15077
15136
  fontFamily: "'Inter', system-ui, sans-serif",
15078
15137
  whiteSpace: "nowrap"
15079
15138
  });
15139
+ this.siOHLC2Span = document.createElement("span");
15140
+ Object.assign(this.siOHLC2Span.style, {
15141
+ fontSize: "14px",
15142
+ fontFamily: "'Inter', system-ui, sans-serif",
15143
+ whiteSpace: "nowrap"
15144
+ });
15080
15145
  this.siStreamDot = document.createElement("span");
15081
15146
  Object.assign(this.siStreamDot.style, {
15082
15147
  width: "7px",
@@ -15086,7 +15151,14 @@ var ChartEngine = class {
15086
15151
  display: "inline-block",
15087
15152
  flexShrink: "0"
15088
15153
  });
15089
- this.siEl.append(this.siStreamDot, this.siSymbolSpan, this.siOHLCSpan);
15154
+ const siRow1 = document.createElement("div");
15155
+ Object.assign(siRow1.style, {
15156
+ display: "flex",
15157
+ alignItems: "center",
15158
+ gap: "8px"
15159
+ });
15160
+ siRow1.append(this.siStreamDot, this.siSymbolSpan, this.siOHLCSpan);
15161
+ this.siEl.append(siRow1, this.siOHLC2Span);
15090
15162
  this.canvasWrap.appendChild(this.siEl);
15091
15163
  }
15092
15164
  if (showUI) {
@@ -15154,7 +15226,6 @@ var ChartEngine = class {
15154
15226
  );
15155
15227
  this.canvasWrap.appendChild(this.tradeButton.el);
15156
15228
  this.tradeButton.el.addEventListener("mouseenter", () => {
15157
- this._isHoveringTradeButton = true;
15158
15229
  this.crosshair = null;
15159
15230
  this.scheduleRender();
15160
15231
  });
@@ -15170,9 +15241,6 @@ var ChartEngine = class {
15170
15241
  this._tradeButtonClickData = { price, y };
15171
15242
  }
15172
15243
  });
15173
- this.tradeButton.el.addEventListener("mouseleave", () => {
15174
- this._isHoveringTradeButton = false;
15175
- });
15176
15244
  }
15177
15245
  middleRow.appendChild(this.canvasWrap);
15178
15246
  this.wrapper.appendChild(middleRow);
@@ -15229,45 +15297,58 @@ var ChartEngine = class {
15229
15297
  this.drawingManager.onStartTextEdit = (drawing) => this.showTextInput(drawing);
15230
15298
  this.panHandler = new PanHandler(
15231
15299
  this.drawCanvas,
15232
- () => this.viewport,
15300
+ () => this._targetViewport,
15233
15301
  (v) => {
15234
- this.viewport = v;
15302
+ this._targetViewport = v;
15235
15303
  this.clearDuration();
15236
15304
  this.checkNeedMoreData();
15305
+ this.scheduleRender();
15237
15306
  },
15238
15307
  () => this.dataStore.length,
15239
15308
  () => this.scheduleRender(),
15240
15309
  () => this.drawingManager.getActiveTool() !== null || this.drawingManager.isDraggingDrawing() || this.drawingManager.isResizingDrawing() || this.separatorDrag !== null || this.axisDrag !== null || (this.tradeDragHandler?.active ?? false) || this._mobileCrosshairActive,
15241
15310
  (deltaY) => {
15242
15311
  const { priceH } = this.getChartLayout();
15243
- const range = this.getScaledPriceRange(this.viewport);
15244
- const pricePerPixel = (range.max - range.min) / priceH;
15312
+ const currentRange = this._displayedPriceRange ?? this.getScaledPriceRange(this._targetViewport);
15313
+ const pricePerPixel = (currentRange.max - currentRange.min) / priceH;
15245
15314
  this.pricePanOffset += deltaY * pricePerPixel;
15246
15315
  this.scheduleRender();
15316
+ },
15317
+ () => {
15318
+ this._isPanning = true;
15319
+ },
15320
+ () => {
15321
+ this._isPanning = false;
15247
15322
  }
15248
15323
  );
15249
15324
  this.zoomHandler = new ZoomHandler(
15250
15325
  this.drawCanvas,
15251
- () => this.viewport,
15326
+ () => this._targetViewport,
15252
15327
  (v) => {
15253
- this.viewport = v;
15328
+ this._targetViewport = v;
15254
15329
  this.clearDuration();
15255
15330
  this.checkNeedMoreData();
15331
+ this.scheduleRender();
15256
15332
  },
15257
15333
  () => this.dataStore.length,
15258
15334
  () => this.scheduleRender(),
15259
15335
  () => this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect(),
15260
15336
  () => this.getChartLayout().chartW,
15261
15337
  (dx) => {
15262
- const vp = this.viewport;
15338
+ const vp = this._targetViewport;
15263
15339
  const visibleBars = vp.endIndex - vp.startIndex;
15264
15340
  const barsPerPixel = visibleBars / this.drawCanvas.width;
15265
15341
  const shift = dx * barsPerPixel;
15266
- const maxStart = this.dataStore.length;
15342
+ const maxStart = Math.max(0, this.dataStore.length - Math.floor(visibleBars / 2));
15267
15343
  const newStart = Math.max(0, Math.min(maxStart, vp.startIndex + shift));
15268
- this.viewport = { ...vp, startIndex: newStart, endIndex: newStart + visibleBars };
15344
+ this._targetViewport = {
15345
+ ...vp,
15346
+ startIndex: newStart,
15347
+ endIndex: newStart + visibleBars
15348
+ };
15269
15349
  this.clearDuration();
15270
15350
  this.checkNeedMoreData();
15351
+ this.scheduleRender();
15271
15352
  }
15272
15353
  );
15273
15354
  this.drawCanvas.addEventListener("mousemove", this.onMouseMove);
@@ -15280,6 +15361,7 @@ var ChartEngine = class {
15280
15361
  });
15281
15362
  this.drawCanvas.addEventListener("touchmove", this.onDrawingTouchMove, { passive: false });
15282
15363
  this.drawCanvas.addEventListener("touchend", this.onDrawingTouchEnd, { passive: false });
15364
+ this.drawCanvas.addEventListener("contextmenu", (e) => e.preventDefault());
15283
15365
  document.addEventListener("keydown", this.onKeyDown);
15284
15366
  const onFullscreenChange = () => {
15285
15367
  const active = !!document.fullscreenElement;
@@ -15313,11 +15395,17 @@ var ChartEngine = class {
15313
15395
  this._displayedPriceRange = null;
15314
15396
  this._lockedAutoFitBase = null;
15315
15397
  this.dataStore.set(bars);
15398
+ const rawW = (this._cachedWrapRect?.width ?? this.canvasWrap.getBoundingClientRect().width) - this.priceAxisWidth;
15399
+ const autoChartW = this._autoVisibleBars && rawW > 0 ? rawW : void 0;
15316
15400
  this.viewport = {
15317
15401
  ...this.viewport,
15318
- startIndex: fitAll ? 0 : Math.max(0, bars.length - initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor) + LOOKAHEAD),
15402
+ startIndex: fitAll ? 0 : Math.max(
15403
+ 0,
15404
+ bars.length - initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor, this.defaultVisibleBars, this.defaultMobileVisibleBars, autoChartW, this.targetCandleWidth) + LOOKAHEAD
15405
+ ),
15319
15406
  endIndex: bars.length + LOOKAHEAD
15320
15407
  };
15408
+ this._targetViewport = { ...this.viewport };
15321
15409
  const hasVol = this.dataStore.hasVolumeData;
15322
15410
  if (!hasVol) this.showVolume = false;
15323
15411
  this.topBar?.setVolumeControl(this.showVolume, hasVol);
@@ -15342,6 +15430,7 @@ var ChartEngine = class {
15342
15430
  );
15343
15431
  }
15344
15432
  this.recomputeIndicators();
15433
+ this.reanchorDrawingsByTimestamp();
15345
15434
  this.updateCountdownState();
15346
15435
  this.scheduleRender();
15347
15436
  return this;
@@ -15360,6 +15449,8 @@ var ChartEngine = class {
15360
15449
  startIndex: this.viewport.startIndex + n,
15361
15450
  endIndex: this.viewport.endIndex + n
15362
15451
  };
15452
+ this._targetViewport = { ...this.viewport };
15453
+ this.drawingManager.shiftBarIndices(n);
15363
15454
  this.recomputeIndicators();
15364
15455
  this.scheduleRender();
15365
15456
  }
@@ -15462,8 +15553,11 @@ var ChartEngine = class {
15462
15553
  }
15463
15554
  checkNeedMoreData() {
15464
15555
  if (!this.dataLoader || this.isFetching || this.noMoreData) return;
15465
- const threshold = Math.max(20, Math.round(initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor) * 0.25));
15466
- if (this.viewport.startIndex > threshold) return;
15556
+ const threshold = Math.max(
15557
+ 20,
15558
+ Math.round(initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor, this.defaultVisibleBars, this.defaultMobileVisibleBars) * 0.25)
15559
+ );
15560
+ if (this._targetViewport.startIndex > threshold) return;
15467
15561
  if (this.dataStore.length === 0) return;
15468
15562
  this.isFetching = true;
15469
15563
  const before = this.dataStore.all[0].time;
@@ -15676,7 +15770,10 @@ var ChartEngine = class {
15676
15770
  }
15677
15771
  }
15678
15772
  } : void 0;
15679
- const merged = deepMergeTheme(deepMergeTheme(base, this.themeOverrides?.[mode]), userOverride);
15773
+ const merged = deepMergeTheme(
15774
+ deepMergeTheme(base, this.themeOverrides?.[mode]),
15775
+ userOverride
15776
+ );
15680
15777
  return resolveCssVarsInTheme(merged, this.wrapper);
15681
15778
  }
15682
15779
  setTheme(theme) {
@@ -15740,7 +15837,13 @@ var ChartEngine = class {
15740
15837
  params: { ...s.currentParams },
15741
15838
  visible: s.visible
15742
15839
  })),
15743
- drawings: this.drawingManager.getDrawingsSnapshot(),
15840
+ drawings: this.drawingManager.getDrawingsSnapshot().map((d) => ({
15841
+ ...d,
15842
+ points: d.points.map((p) => ({
15843
+ ...p,
15844
+ timestamp: this.dataStore.all[Math.floor(p.barIndex)]?.time
15845
+ }))
15846
+ })),
15744
15847
  positionRenderStyle: this.positionRenderStyle,
15745
15848
  tradeDisplayFilter: this.tradeDisplayFilter,
15746
15849
  canvasColors: Object.keys(this.settingsCanvasColors).length > 0 ? { ...this.settingsCanvasColors } : void 0
@@ -15790,7 +15893,21 @@ var ChartEngine = class {
15790
15893
  }
15791
15894
  }
15792
15895
  }
15793
- this.drawingManager.loadDrawings(state.drawings);
15896
+ const bars = this.dataStore.all;
15897
+ const resolvedDrawings = state.drawings.map((d) => ({
15898
+ ...d,
15899
+ points: d.points.map((p) => {
15900
+ if (!p.timestamp || bars.length === 0) return p;
15901
+ let lo = 0, hi = bars.length - 1;
15902
+ while (lo < hi) {
15903
+ const mid = lo + hi >> 1;
15904
+ if ((bars[mid]?.time ?? 0) < p.timestamp) lo = mid + 1;
15905
+ else hi = mid;
15906
+ }
15907
+ return { ...p, barIndex: lo + (p.barIndex - Math.floor(p.barIndex)) };
15908
+ })
15909
+ }));
15910
+ this.drawingManager.loadDrawings(resolvedDrawings);
15794
15911
  if (state.positionRenderStyle) {
15795
15912
  this.positionRenderStyleOverride = state.positionRenderStyle;
15796
15913
  this.positionRenderStyle = state.positionRenderStyle;
@@ -15807,6 +15924,27 @@ var ChartEngine = class {
15807
15924
  this.scheduleRender();
15808
15925
  return this;
15809
15926
  }
15927
+ /**
15928
+ * Re-anchors all loaded drawings to the current dataset using their stored timestamps.
15929
+ * Called after loadData() to fix drawings restored via setState() before data was
15930
+ * available (bars.length === 0 at restore time causes the raw barIndex fallback).
15931
+ */
15932
+ reanchorDrawingsByTimestamp() {
15933
+ const bars = this.dataStore.all;
15934
+ if (bars.length === 0) return;
15935
+ for (const drawing of this.drawingManager.getDrawings()) {
15936
+ for (const pt of drawing.points) {
15937
+ if (!pt.timestamp) continue;
15938
+ let lo = 0, hi = bars.length - 1;
15939
+ while (lo < hi) {
15940
+ const mid = lo + hi >> 1;
15941
+ if ((bars[mid]?.time ?? 0) < pt.timestamp) lo = mid + 1;
15942
+ else hi = mid;
15943
+ }
15944
+ pt.barIndex = lo + (pt.barIndex - Math.floor(pt.barIndex));
15945
+ }
15946
+ }
15947
+ }
15810
15948
  /**
15811
15949
  * Resets all chart settings that are captured by `getState()` back to their
15812
15950
  * defaults (candlestick / dark / 1D / 1Y / volume on / no indicators / no drawings)
@@ -15859,6 +15997,7 @@ var ChartEngine = class {
15859
15997
  if (!this.siOHLCSpan) return;
15860
15998
  if (!bar) {
15861
15999
  this.siOHLCSpan.innerHTML = "";
16000
+ if (this.siOHLC2Span) this.siOHLC2Span.innerHTML = "";
15862
16001
  return;
15863
16002
  }
15864
16003
  const up = bar.close >= bar.open;
@@ -15866,26 +16005,32 @@ var ChartEngine = class {
15866
16005
  const fmt = (n) => n.toFixed(this.dataStore.getPricePrecision());
15867
16006
  const fmtVol = (v) => v >= 1e6 ? (v / 1e6).toFixed(2) + "M" : v >= 1e3 ? (v / 1e3).toFixed(1) + "K" : v.toFixed(0);
15868
16007
  const ol = this.labels.ohlc;
15869
- const fields = [
16008
+ const renderFields = (el, fields) => {
16009
+ el.innerHTML = "";
16010
+ for (const [lbl, val, clr] of fields) {
16011
+ const span = document.createElement("span");
16012
+ span.style.marginRight = "8px";
16013
+ const key = document.createElement("span");
16014
+ key.textContent = lbl + "\xA0";
16015
+ key.style.color = this.theme.axisText;
16016
+ key.style.opacity = "0.6";
16017
+ const num = document.createElement("span");
16018
+ num.textContent = val;
16019
+ num.style.color = clr;
16020
+ span.append(key, num);
16021
+ el.appendChild(span);
16022
+ }
16023
+ };
16024
+ renderFields(this.siOHLCSpan, [
15870
16025
  [ol.open, fmt(bar.open), this.theme.axisText],
15871
- [ol.high, fmt(bar.high), this.theme.axisText],
15872
- [ol.low, fmt(bar.low), this.theme.axisText],
15873
- [ol.close, fmt(bar.close), color],
15874
- ...bar.volume != null && bar.volume !== 0 ? [[ol.volume, fmtVol(bar.volume), this.theme.axisText]] : []
15875
- ];
15876
- this.siOHLCSpan.innerHTML = "";
15877
- for (const [lbl, val, clr] of fields) {
15878
- const span = document.createElement("span");
15879
- span.style.marginRight = "8px";
15880
- const key = document.createElement("span");
15881
- key.textContent = lbl + "\xA0";
15882
- key.style.color = this.theme.axisText;
15883
- key.style.opacity = "0.6";
15884
- const num = document.createElement("span");
15885
- num.textContent = val;
15886
- num.style.color = clr;
15887
- span.append(key, num);
15888
- this.siOHLCSpan.appendChild(span);
16026
+ [ol.high, fmt(bar.high), this.theme.axisText]
16027
+ ]);
16028
+ if (this.siOHLC2Span) {
16029
+ renderFields(this.siOHLC2Span, [
16030
+ [ol.low, fmt(bar.low), this.theme.axisText],
16031
+ [ol.close, fmt(bar.close), color],
16032
+ ...bar.volume != null && bar.volume !== 0 ? [[ol.volume, fmtVol(bar.volume), this.theme.axisText]] : []
16033
+ ]);
15889
16034
  }
15890
16035
  }
15891
16036
  setMinLots(lots) {
@@ -16354,7 +16499,9 @@ var ChartEngine = class {
16354
16499
  }
16355
16500
  updateCountdownState() {
16356
16501
  const INTRADAY = ["1m", "5m", "15m", "30m", "1h", "4h"];
16357
- const allowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY).includes(this.timeframe);
16502
+ const allowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY).includes(
16503
+ this.timeframe
16504
+ );
16358
16505
  const should = this.showCandleCountdown && allowed && this.dataStore.length > 0;
16359
16506
  if (should && !this.countdownActive) {
16360
16507
  this.countdownActive = true;
@@ -16660,6 +16807,7 @@ var ChartEngine = class {
16660
16807
  this.getScaledPriceRange(this.viewport),
16661
16808
  priceH
16662
16809
  );
16810
+ this.crosshair = null;
16663
16811
  this.draftOrderQtyInput?.show(yPx, this._minLots, this.theme, priceH);
16664
16812
  this.renderTradeLayer();
16665
16813
  }
@@ -16758,6 +16906,28 @@ var ChartEngine = class {
16758
16906
  ctx.fillRect(0, 0, totalW, totalH);
16759
16907
  const bars = this.dataStore.getVisible(viewport);
16760
16908
  if (bars.length === 0) return;
16909
+ {
16910
+ if (this._isPanning) {
16911
+ this.viewport = { ...this._targetViewport };
16912
+ } else {
16913
+ const dStart = this._targetViewport.startIndex - this.viewport.startIndex;
16914
+ const dEnd = this._targetViewport.endIndex - this.viewport.endIndex;
16915
+ const visibleBars = this._targetViewport.endIndex - this._targetViewport.startIndex;
16916
+ const epsilon = visibleBars * 2e-4;
16917
+ if (Math.abs(dStart) < epsilon && Math.abs(dEnd) < epsilon) {
16918
+ this.viewport = { ...this._targetViewport };
16919
+ } else {
16920
+ this.viewport = {
16921
+ ...this.viewport,
16922
+ startIndex: this.viewport.startIndex + dStart * VIEWPORT_LERP,
16923
+ endIndex: this.viewport.endIndex + dEnd * VIEWPORT_LERP,
16924
+ candleWidth: this._targetViewport.candleWidth,
16925
+ candleGap: this._targetViewport.candleGap
16926
+ };
16927
+ this.scheduleRender();
16928
+ }
16929
+ }
16930
+ }
16761
16931
  const isManualMode = this.priceScaleFactor !== 1 || this.pricePanOffset !== 0;
16762
16932
  if (!isManualMode) {
16763
16933
  this._lockedAutoFitBase = null;
@@ -17093,7 +17263,9 @@ var ChartEngine = class {
17093
17263
  }
17094
17264
  {
17095
17265
  const INTRADAY_TF = ["1m", "5m", "15m", "30m", "1h", "4h"];
17096
- const countdownAllowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY_TF).includes(this.timeframe);
17266
+ const countdownAllowed = this.candleCountdownTimeframes === "all" ? true : (this.candleCountdownTimeframes ?? INTRADAY_TF).includes(
17267
+ this.timeframe
17268
+ );
17097
17269
  const showCountdown = this.showCandleCountdown && countdownAllowed && bars.length > 0 && viewport.endIndex >= this.dataStore.length - 1;
17098
17270
  const countdownBarIndex = showCountdown ? this.dataStore.length - 1 : void 0;
17099
17271
  if (this._debug) {
@@ -17191,7 +17363,7 @@ var ChartEngine = class {
17191
17363
  this.dataStore.getPricePrecision()
17192
17364
  );
17193
17365
  if (this.scrollToEndBtn) {
17194
- const atEnd = this.dataStore.length === 0 || this.viewport.endIndex >= this.dataStore.length - 0.5;
17366
+ const atEnd = this.dataStore.length === 0 || this._targetViewport.endIndex >= this.dataStore.length - 0.5;
17195
17367
  if (atEnd !== !this.scrollToEndVisible) {
17196
17368
  this.scrollToEndVisible = !atEnd;
17197
17369
  this.scrollToEndBtn.style.opacity = atEnd ? "0" : "0.85";
@@ -17221,6 +17393,11 @@ var ChartEngine = class {
17221
17393
  this._mobileLayout = isMobile;
17222
17394
  this.leftBar?.setMobileMode(isMobile);
17223
17395
  this.topBar?.setMobileLayout(isMobile);
17396
+ if (this.siEl) {
17397
+ this.siEl.style.flexDirection = isMobile ? "column" : "row";
17398
+ this.siEl.style.alignItems = isMobile ? "flex-start" : "center";
17399
+ this.siEl.style.gap = isMobile ? "2px" : "8px";
17400
+ }
17224
17401
  if (isMobile) {
17225
17402
  this.leftBarVisible = false;
17226
17403
  this.topBar?.setDrawingsBtnActive(false);
@@ -17311,6 +17488,164 @@ var ChartEngine = class {
17311
17488
  price: this.scaleManager.pixelToPrice(y, priceRange, priceH)
17312
17489
  };
17313
17490
  }
17491
+ /** Shared close-button handler used by both mouse click and touch tap. */
17492
+ _handleTradeLevelClose(closedLevel) {
17493
+ if (closedLevel.bracketType) {
17494
+ const parentLevel = this.levels.find((l) => l.label === closedLevel.label);
17495
+ const isMarketDraftBracket = parentLevel?.orderType === "market" && parentLevel?.data?._isDraft === true;
17496
+ if (isMarketDraftBracket) {
17497
+ const bracketKey = closedLevel.bracketType === "stopLoss" ? "sl" : "tp";
17498
+ this.updateDraftOrderBracket(bracketKey, null);
17499
+ this.emitter.emit("tradeLevelEdit", {
17500
+ label: closedLevel.label,
17501
+ data: parentLevel?.data,
17502
+ type: "pending",
17503
+ changes: [
17504
+ {
17505
+ field: closedLevel.bracketType === "stopLoss" ? "REMOVE_SL" : "REMOVE_TP",
17506
+ newPrice: 0,
17507
+ data: closedLevel.data
17508
+ }
17509
+ ]
17510
+ });
17511
+ return;
17512
+ }
17513
+ const field = closedLevel.bracketType;
17514
+ const existing = this.pendingLevelDrags.get(closedLevel.label) ?? [];
17515
+ const filtered = existing.filter((c) => c.field !== field);
17516
+ filtered.push({
17517
+ field,
17518
+ newPrice: 0,
17519
+ oldPrice: 0,
17520
+ isRemoval: true,
17521
+ bracketOrderLabel: closedLevel.bracketOrderLabel
17522
+ });
17523
+ this.pendingLevelDrags.set(closedLevel.label, filtered);
17524
+ this.selectedTradeLabel = closedLevel.label;
17525
+ this.renderTradeLayer();
17526
+ return;
17527
+ }
17528
+ const isDraftOrder = closedLevel.label === this.draftOrderLabel;
17529
+ this.revertPendingChanges(closedLevel.label);
17530
+ this.pendingLevelDrags.delete(closedLevel.label);
17531
+ if (isDraftOrder) return;
17532
+ const originalLevel = this.levels.find((l) => l.label === closedLevel.label);
17533
+ const isEntryOrder = originalLevel?.type === "position" && !!originalLevel.entryPriceEditable;
17534
+ const closeAction = closedLevel.type === "position" && !isEntryOrder ? "CLOSED" : "CANCELLED";
17535
+ this.emitter.emit("tradeLevelClose", {
17536
+ label: closedLevel.label,
17537
+ type: closedLevel.type,
17538
+ action: closeAction,
17539
+ data: closedLevel.data
17540
+ });
17541
+ }
17542
+ /** Activates the mobile crosshair at canvas coords (x, y). */
17543
+ _activateMobileCrosshair(x, y) {
17544
+ const rect = this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect();
17545
+ const { chartW, priceH } = this.getChartLayout();
17546
+ const priceRange = this._displayedPriceRange ?? this.getScaledPriceRange(this.viewport);
17547
+ const timeAxisY = rect.height - this.timeAxisHeight;
17548
+ if (x >= chartW || y >= timeAxisY) return;
17549
+ this._mobileCrosshairActive = true;
17550
+ const rawBarIndex = this.scaleManager.pixelToBarIndex(x, this.viewport, chartW);
17551
+ const price = this.scaleManager.pixelToPrice(y, priceRange, priceH);
17552
+ const clamped = Math.max(
17553
+ Math.floor(this.viewport.startIndex),
17554
+ Math.min(Math.ceil(this.viewport.endIndex), Math.round(rawBarIndex))
17555
+ );
17556
+ const bar = this.dataStore.all[clamped] ?? null;
17557
+ this.crosshair = { x, y, barIndex: clamped, price, bar };
17558
+ if (this.tradeButton) {
17559
+ this.tradeButton.setY(y);
17560
+ this.tradeButton.show();
17561
+ }
17562
+ this.scheduleRender();
17563
+ }
17564
+ /** Shared helper: initiate a trade level main-line drag from a hit area. */
17565
+ _startTradeDragFromHit(dragHit, level, hitY) {
17566
+ if (!this.tradeDragHandler) return;
17567
+ if (dragHit.isBracket) {
17568
+ this._pendingBracketOrderLabel = dragHit.bracketOrderLabel ?? null;
17569
+ this._isBracketNew = false;
17570
+ const isEntryOrder = level.entryPriceEditable === true;
17571
+ const isDraft = level.data?._isDraft === true;
17572
+ const all = this.dataStore.all;
17573
+ const marketPrice = isEntryOrder || isDraft ? 0 : all[all.length - 1]?.close ?? 0;
17574
+ this.tradeDragHandler.startBracketDrag(
17575
+ level,
17576
+ dragHit.bracketType,
17577
+ dragHit.price,
17578
+ marketPrice,
17579
+ this._effectiveEntryPrice(level.label, level.price)
17580
+ );
17581
+ } else {
17582
+ this._pendingBracketOrderLabel = null;
17583
+ const isEntryOrder = level.entryPriceEditable === true;
17584
+ if (isEntryOrder) {
17585
+ this.selectedTradeLabel = dragHit.label;
17586
+ const posLevel = level;
17587
+ const allPos = this.dataStore.all;
17588
+ const mktPricePos = allPos[allPos.length - 1]?.close ?? 0;
17589
+ this.tradeDragHandler.startDrag(
17590
+ level,
17591
+ hitY,
17592
+ dragHit.price,
17593
+ posLevel.stopLossPrice,
17594
+ posLevel.takeProfitPrice,
17595
+ posLevel.side,
17596
+ mktPricePos,
17597
+ posLevel.orderType
17598
+ );
17599
+ } else {
17600
+ const pendingLevel = level;
17601
+ const allBars = this.dataStore.all;
17602
+ const mktPrice = allBars[allBars.length - 1]?.close ?? 0;
17603
+ this.tradeDragHandler.startDrag(
17604
+ level,
17605
+ hitY,
17606
+ dragHit.price,
17607
+ void 0,
17608
+ void 0,
17609
+ pendingLevel.side,
17610
+ mktPrice,
17611
+ pendingLevel.orderType
17612
+ );
17613
+ }
17614
+ }
17615
+ }
17616
+ /** Shared helper: initiate a +SL/+TP add-bracket drag from a hit area. */
17617
+ _startAddBracketFromHit(addHit, hitY) {
17618
+ const level = this.levels.find((l) => l.label === addHit.label);
17619
+ if (!level || !this.tradeDragHandler) return;
17620
+ const { priceH } = this.getChartLayout();
17621
+ const priceRange = this.getScaledPriceRange(this.viewport);
17622
+ const btnCenterY = addHit.y + addHit.h / 2;
17623
+ const initialPrice = this.scaleManager.pixelToPrice(btnCenterY, priceRange, priceH);
17624
+ const isEntryOrder = level.entryPriceEditable === true;
17625
+ const isDraft = level.data?._isDraft === true;
17626
+ const all = this.dataStore.all;
17627
+ const marketPrice = isEntryOrder || isDraft ? 0 : all[all.length - 1]?.close ?? 0;
17628
+ if (!isDraft) {
17629
+ this.emitter.emit("tradeLevelEditOpen", {
17630
+ label: level.label,
17631
+ type: level.type,
17632
+ data: level.data,
17633
+ price: level.price,
17634
+ side: level.side,
17635
+ stopLossPrice: level.stopLossPrice,
17636
+ takeProfitPrice: level.takeProfitPrice
17637
+ });
17638
+ }
17639
+ this._pendingBracketOrderLabel = null;
17640
+ this._isBracketNew = true;
17641
+ this.tradeDragHandler.startBracketDrag(
17642
+ level,
17643
+ addHit.bracketType,
17644
+ initialPrice,
17645
+ marketPrice,
17646
+ this._effectiveEntryPrice(level.label, level.price)
17647
+ );
17648
+ }
17314
17649
  // ── Text drawing input ────────────────────────────────────────────────────
17315
17650
  showTextInput(drawing) {
17316
17651
  if (!this.textInputEl) return;
@@ -17481,6 +17816,7 @@ var ChartEngine = class {
17481
17816
  startIndex: newEnd - visible,
17482
17817
  endIndex: newEnd
17483
17818
  };
17819
+ this._targetViewport = { ...this.viewport };
17484
17820
  this.pricePanOffset = startPan * (1 - prog);
17485
17821
  this.render();
17486
17822
  if (raw < 1) {
@@ -17500,8 +17836,12 @@ var ChartEngine = class {
17500
17836
  resetTimeAxis() {
17501
17837
  const total = this.dataStore.length;
17502
17838
  if (total === 0) return;
17503
- const newStart = Math.max(0, total - initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor) + LOOKAHEAD);
17839
+ const newStart = Math.max(
17840
+ 0,
17841
+ total - initialVisibleBars(this.mobileBarDivisor, this.explicitMobileDivisor, this.defaultVisibleBars, this.defaultMobileVisibleBars) + LOOKAHEAD
17842
+ );
17504
17843
  this.viewport = { ...this.viewport, startIndex: newStart, endIndex: total + LOOKAHEAD };
17844
+ this._targetViewport = { ...this.viewport };
17505
17845
  this.scheduleRender();
17506
17846
  }
17507
17847
  /** Reset both price and time axes to their default state. */
@@ -17560,6 +17900,8 @@ var ChartEngine = class {
17560
17900
  });
17561
17901
  }
17562
17902
  };
17903
+ _ChartEngine.DRAW_GESTURE_THRESHOLD = 4;
17904
+ var ChartEngine = _ChartEngine;
17563
17905
 
17564
17906
  // src/data/WebSocketAdapter.ts
17565
17907
  var WebSocketAdapter = class {