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/README.md +13 -13
- package/dist/{MACD-BmvUIYwI.d.cts → MACD-K2nBudgk.d.cts} +30 -0
- package/dist/{MACD-BmvUIYwI.d.ts → MACD-K2nBudgk.d.ts} +30 -0
- package/dist/index.cjs +593 -251
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +33 -3
- package/dist/index.d.ts +33 -3
- package/dist/index.js +593 -251
- package/dist/index.js.map +1 -1
- package/dist/indicators/index.d.cts +2 -2
- package/dist/indicators/index.d.ts +2 -2
- package/dist/webview/chart.html +237 -0
- package/package.json +8 -2
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:
|
|
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
|
|
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:
|
|
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 +
|
|
12376
|
+
right: `${priceAxisWidth + 4}px`,
|
|
12342
12377
|
top: "0",
|
|
12343
|
-
transform: "translateY(-
|
|
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(
|
|
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(
|
|
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 +
|
|
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 (
|
|
13794
|
-
|
|
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
|
|
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.
|
|
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 &&
|
|
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
|
-
|
|
14256
|
-
|
|
14257
|
-
|
|
14258
|
-
|
|
14259
|
-
|
|
14260
|
-
if (
|
|
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 = {
|
|
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 (
|
|
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
|
|
14316
|
-
|
|
14317
|
-
|
|
14318
|
-
|
|
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
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14385
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14540
|
-
|
|
14541
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
14778
|
-
this.
|
|
14779
|
-
...this.
|
|
14780
|
-
startIndex: this.
|
|
14781
|
-
endIndex: this.
|
|
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("
|
|
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.
|
|
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(
|
|
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
|
-
|
|
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.
|
|
15300
|
+
() => this._targetViewport,
|
|
15233
15301
|
(v) => {
|
|
15234
|
-
this.
|
|
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
|
|
15244
|
-
const pricePerPixel = (
|
|
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.
|
|
15326
|
+
() => this._targetViewport,
|
|
15252
15327
|
(v) => {
|
|
15253
|
-
this.
|
|
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.
|
|
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.
|
|
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(
|
|
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(
|
|
15466
|
-
|
|
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(
|
|
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.
|
|
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
|
|
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
|
-
|
|
15873
|
-
|
|
15874
|
-
|
|
15875
|
-
|
|
15876
|
-
|
|
15877
|
-
|
|
15878
|
-
|
|
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(
|
|
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(
|
|
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.
|
|
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(
|
|
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 {
|