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