acttrader-charts 1.4.0-beta.4 → 1.4.0-beta.6
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 +17 -0
- package/dist/index.cjs +146 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.js +146 -33
- package/dist/index.js.map +1 -1
- package/dist/webview/chart.html +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2266,6 +2266,23 @@ Two details worth knowing:
|
|
|
2266
2266
|
in hand. It deletes via select-then-delete, so it shares the undo history with
|
|
2267
2267
|
every other delete.
|
|
2268
2268
|
|
|
2269
|
+
### Touch gestures (iOS / Android)
|
|
2270
|
+
|
|
2271
|
+
| Gesture | Effect |
|
|
2272
|
+
| --- | --- |
|
|
2273
|
+
| One-finger drag on the chart | Scrolls through time (with momentum) |
|
|
2274
|
+
| Pinch | Zooms the time axis |
|
|
2275
|
+
| One-finger drag along the time (x) axis | Zooms the time axis the way TradingView does: the right edge (normally the latest bar) stays put, drag right to zoom in and left to zoom out. Zooming out stops when the oldest bar reaches the left edge, so the candles always fill the full width. The mouse drag on the time axis behaves the same way |
|
|
2276
|
+
| One-finger drag on the price (y) axis | Zooms the price axis |
|
|
2277
|
+
| One-finger **double tap** on the chart | Zooms the price axis in one step around the tapped price |
|
|
2278
|
+
| One-finger **double tap** on the price (y) axis | Resets the view, the same as the Reset view button |
|
|
2279
|
+
| One-finger **double tap, then drag** up/down | Zooms the price axis continuously: up zooms in, down zooms out (the same direction as the price-axis drag) |
|
|
2280
|
+
| Long press | Shows the crosshair |
|
|
2281
|
+
|
|
2282
|
+
Once the view has moved away from its default, a round **Reset view** button
|
|
2283
|
+
appears just above the time axis. It is shown even when `showUI: false`, so a
|
|
2284
|
+
bare chart with no toolbar always has a way back. No configuration is needed.
|
|
2285
|
+
|
|
2269
2286
|
### Value tooltip on long press
|
|
2270
2287
|
|
|
2271
2288
|
```ts
|
package/dist/index.cjs
CHANGED
|
@@ -37080,6 +37080,14 @@ function initialVisibleBars(mobileBarDivisor = 2, forceApply = false, defaultBar
|
|
|
37080
37080
|
}
|
|
37081
37081
|
var LOOKAHEAD = 4;
|
|
37082
37082
|
var LONG_PRESS_CANCEL_ARM_MS = 300;
|
|
37083
|
+
var DOUBLE_TAP_MS = 300;
|
|
37084
|
+
var DOUBLE_TAP_SLOP_PX = 30;
|
|
37085
|
+
var TAP_MAX_MS = 250;
|
|
37086
|
+
var TAP_MAX_MOVE_PX = 10;
|
|
37087
|
+
var DOUBLE_TAP_ZOOM_STEP = 0.7;
|
|
37088
|
+
var TIME_AXIS_DRAG_SPEED = 2.5;
|
|
37089
|
+
var MIN_VISIBLE_BARS_AXIS = 10;
|
|
37090
|
+
var MAX_VISIBLE_BARS_AXIS = 500;
|
|
37083
37091
|
function lastIndexAtOrBefore(bars, time) {
|
|
37084
37092
|
let lo = 0, hi = bars.length - 1, ans = -1;
|
|
37085
37093
|
while (lo <= hi) {
|
|
@@ -37382,6 +37390,16 @@ var _ChartEngine = class _ChartEngine {
|
|
|
37382
37390
|
*/
|
|
37383
37391
|
this._viewportPersistTimer = null;
|
|
37384
37392
|
this.axisDrag = null;
|
|
37393
|
+
/**
|
|
37394
|
+
* One-finger double-tap zoom on the price scale (the TradingView app's
|
|
37395
|
+
* gesture). Second tap down arms it; dragging up/down zooms Y continuously,
|
|
37396
|
+
* and lifting without a drag steps one zoom-in around the tapped price.
|
|
37397
|
+
*/
|
|
37398
|
+
this.tapZoom = null;
|
|
37399
|
+
/** Where and when the last quick one-finger tap ended — the first half of a double tap. */
|
|
37400
|
+
this._lastTap = null;
|
|
37401
|
+
/** Down position/time of the current one-finger touch, to tell a tap from a drag. */
|
|
37402
|
+
this._touchDown = null;
|
|
37385
37403
|
/** Per-instance sub-pane Y-scale factors, keyed by ActiveIndicatorState.instanceId. */
|
|
37386
37404
|
this.subPaneScaleFactors = /* @__PURE__ */ new Map();
|
|
37387
37405
|
this._mobileCrosshairActive = false;
|
|
@@ -37738,19 +37756,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
37738
37756
|
);
|
|
37739
37757
|
this.subPaneScaleFactors.set(this.axisDrag.instanceId, newFactor);
|
|
37740
37758
|
} else {
|
|
37741
|
-
|
|
37742
|
-
const newVisible = Math.max(
|
|
37743
|
-
10,
|
|
37744
|
-
Math.min(500, Math.round(this.axisDrag.startSize + delta * 0.5))
|
|
37745
|
-
);
|
|
37746
|
-
const mid = (this.viewport.startIndex + this.viewport.endIndex) / 2;
|
|
37747
|
-
const newStart = Math.max(0, mid - newVisible / 2);
|
|
37748
|
-
this.viewport = {
|
|
37749
|
-
...this.viewport,
|
|
37750
|
-
startIndex: newStart,
|
|
37751
|
-
endIndex: newStart + newVisible
|
|
37752
|
-
};
|
|
37753
|
-
this._targetViewport = { ...this.viewport };
|
|
37759
|
+
this.applyTimeAxisDrag(x);
|
|
37754
37760
|
}
|
|
37755
37761
|
this.drawCanvas.style.cursor = this.axisDrag.axis === "time" ? "ew-resize" : "ns-resize";
|
|
37756
37762
|
this.scheduleRender();
|
|
@@ -37999,6 +38005,22 @@ var _ChartEngine = class _ChartEngine {
|
|
|
37999
38005
|
const y = touch.clientY - rect.top;
|
|
38000
38006
|
const { chartW, priceH } = this.getChartLayout();
|
|
38001
38007
|
const priceRange = this.getScaledPriceRange(this.viewport);
|
|
38008
|
+
const now = performance.now();
|
|
38009
|
+
const lastTap = this._lastTap;
|
|
38010
|
+
this._lastTap = null;
|
|
38011
|
+
this._touchDown = null;
|
|
38012
|
+
if (lastTap && now - lastTap.at < DOUBLE_TAP_MS && Math.hypot(x - lastTap.x, y - lastTap.y) < DOUBLE_TAP_SLOP_PX && y < priceH && !this._mobileCrosshairActive) {
|
|
38013
|
+
e.preventDefault();
|
|
38014
|
+
this.panHandler.cancel();
|
|
38015
|
+
this.axisDrag = null;
|
|
38016
|
+
if (x >= chartW) {
|
|
38017
|
+
this.resetView();
|
|
38018
|
+
this.scheduleViewportPersist();
|
|
38019
|
+
} else {
|
|
38020
|
+
this.startTapZoom(y, priceH);
|
|
38021
|
+
}
|
|
38022
|
+
return;
|
|
38023
|
+
}
|
|
38002
38024
|
const axisTapHit = hitTestAxisTagCluster(x, y, this.axisTagClusterAreas);
|
|
38003
38025
|
if (axisTapHit) {
|
|
38004
38026
|
e.preventDefault();
|
|
@@ -38015,6 +38037,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
38015
38037
|
e.preventDefault();
|
|
38016
38038
|
if (y <= priceH) {
|
|
38017
38039
|
this.axisDrag = { axis: "price", startPos: y, startSize: this.priceScaleFactor };
|
|
38040
|
+
this._touchDown = { x, y, at: now };
|
|
38018
38041
|
} else {
|
|
38019
38042
|
const subPaneStates = this.activeIndicators.filter(
|
|
38020
38043
|
(s) => s.indicator.pane === "sub"
|
|
@@ -38043,7 +38066,8 @@ var _ChartEngine = class _ChartEngine {
|
|
|
38043
38066
|
this.axisDrag = {
|
|
38044
38067
|
axis: "time",
|
|
38045
38068
|
startPos: x,
|
|
38046
|
-
startSize: this.viewport.endIndex - this.viewport.startIndex
|
|
38069
|
+
startSize: this.viewport.endIndex - this.viewport.startIndex,
|
|
38070
|
+
startEnd: this.viewport.endIndex
|
|
38047
38071
|
};
|
|
38048
38072
|
this.scheduleRender();
|
|
38049
38073
|
return;
|
|
@@ -38197,6 +38221,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
38197
38221
|
}
|
|
38198
38222
|
}
|
|
38199
38223
|
}
|
|
38224
|
+
this._touchDown = { x, y, at: now };
|
|
38200
38225
|
this.drawingManager.selectDrawing(null);
|
|
38201
38226
|
this.hideMobileDrawingFlyout();
|
|
38202
38227
|
if (this.expandedClusters.size > 0) {
|
|
@@ -38234,6 +38259,11 @@ var _ChartEngine = class _ChartEngine {
|
|
|
38234
38259
|
const point = this.touchToDrawingPoint(e.touches[0]);
|
|
38235
38260
|
const tp = this.touchToPixels(e.touches[0]);
|
|
38236
38261
|
this._lastDrawingTouchPoint = point;
|
|
38262
|
+
if (this.tapZoom) {
|
|
38263
|
+
e.preventDefault();
|
|
38264
|
+
this.moveTapZoom(tp.y);
|
|
38265
|
+
return;
|
|
38266
|
+
}
|
|
38237
38267
|
if (this.axisDrag) {
|
|
38238
38268
|
e.preventDefault();
|
|
38239
38269
|
const touch = e.touches[0];
|
|
@@ -38254,18 +38284,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
38254
38284
|
);
|
|
38255
38285
|
this.subPaneScaleFactors.set(this.axisDrag.instanceId, newFactor);
|
|
38256
38286
|
} else {
|
|
38257
|
-
|
|
38258
|
-
const newVisible = Math.max(
|
|
38259
|
-
10,
|
|
38260
|
-
Math.min(500, Math.round(this.axisDrag.startSize + delta * 0.5))
|
|
38261
|
-
);
|
|
38262
|
-
const mid = (this.viewport.startIndex + this.viewport.endIndex) / 2;
|
|
38263
|
-
const newStart = Math.max(0, mid - newVisible / 2);
|
|
38264
|
-
this.viewport = {
|
|
38265
|
-
...this.viewport,
|
|
38266
|
-
startIndex: newStart,
|
|
38267
|
-
endIndex: newStart + newVisible
|
|
38268
|
-
};
|
|
38287
|
+
this.applyTimeAxisDrag(tx);
|
|
38269
38288
|
}
|
|
38270
38289
|
this.scheduleRender();
|
|
38271
38290
|
return;
|
|
@@ -38409,6 +38428,9 @@ var _ChartEngine = class _ChartEngine {
|
|
|
38409
38428
|
* back to its start; an in-progress price drag is also cancelled defensively.
|
|
38410
38429
|
*/
|
|
38411
38430
|
this.onDrawingTouchCancel = (_e) => {
|
|
38431
|
+
this._touchDown = null;
|
|
38432
|
+
this._lastTap = null;
|
|
38433
|
+
if (this.tapZoom) this.endTapZoom();
|
|
38412
38434
|
if (this._longPressTimer !== null) {
|
|
38413
38435
|
clearTimeout(this._longPressTimer);
|
|
38414
38436
|
this._longPressTimer = null;
|
|
@@ -38442,6 +38464,22 @@ var _ChartEngine = class _ChartEngine {
|
|
|
38442
38464
|
this._longPressTimer = null;
|
|
38443
38465
|
this._longPressPoint = null;
|
|
38444
38466
|
}
|
|
38467
|
+
if (this.tapZoom) {
|
|
38468
|
+
this.endTapZoom();
|
|
38469
|
+
return;
|
|
38470
|
+
}
|
|
38471
|
+
const down = this._touchDown;
|
|
38472
|
+
this._touchDown = null;
|
|
38473
|
+
const lifted = _e.changedTouches[0];
|
|
38474
|
+
if (down && lifted && _e.touches.length === 0) {
|
|
38475
|
+
const rect = this._cachedDrawRect ?? this.drawCanvas.getBoundingClientRect();
|
|
38476
|
+
const ux = lifted.clientX - rect.left;
|
|
38477
|
+
const uy = lifted.clientY - rect.top;
|
|
38478
|
+
const now = performance.now();
|
|
38479
|
+
if (now - down.at < TAP_MAX_MS && Math.hypot(ux - down.x, uy - down.y) < TAP_MAX_MOVE_PX) {
|
|
38480
|
+
this._lastTap = { x: down.x, y: down.y, at: now };
|
|
38481
|
+
}
|
|
38482
|
+
}
|
|
38445
38483
|
if (this._pendingTradeDrag) {
|
|
38446
38484
|
const { level } = this._pendingTradeDrag;
|
|
38447
38485
|
this._pendingTradeDrag = null;
|
|
@@ -38664,7 +38702,8 @@ var _ChartEngine = class _ChartEngine {
|
|
|
38664
38702
|
this.axisDrag = {
|
|
38665
38703
|
axis: "time",
|
|
38666
38704
|
startPos: x,
|
|
38667
|
-
startSize: this.viewport.endIndex - this.viewport.startIndex
|
|
38705
|
+
startSize: this.viewport.endIndex - this.viewport.startIndex,
|
|
38706
|
+
startEnd: this.viewport.endIndex
|
|
38668
38707
|
};
|
|
38669
38708
|
this.scheduleRender();
|
|
38670
38709
|
return;
|
|
@@ -39696,12 +39735,12 @@ var _ChartEngine = class _ChartEngine {
|
|
|
39696
39735
|
this.applyScrollToEndBtnStyles();
|
|
39697
39736
|
this.scrollToEndBtn.addEventListener("click", () => this.scrollToEnd());
|
|
39698
39737
|
this.canvasWrap.appendChild(this.scrollToEndBtn);
|
|
39699
|
-
this.resetViewBtn = document.createElement("button");
|
|
39700
|
-
this.resetViewBtn.title = "Reset view";
|
|
39701
|
-
this.applyResetViewBtnStyles();
|
|
39702
|
-
this.resetViewBtn.addEventListener("click", () => this.resetView());
|
|
39703
|
-
this.canvasWrap.appendChild(this.resetViewBtn);
|
|
39704
39738
|
}
|
|
39739
|
+
this.resetViewBtn = document.createElement("button");
|
|
39740
|
+
this.resetViewBtn.title = "Reset view";
|
|
39741
|
+
this.applyResetViewBtnStyles();
|
|
39742
|
+
this.resetViewBtn.addEventListener("click", () => this.resetView());
|
|
39743
|
+
this.canvasWrap.appendChild(this.resetViewBtn);
|
|
39705
39744
|
this.tradeDragHandler = new TradeDragHandler();
|
|
39706
39745
|
this.orderTimeDragHandler = new OrderLineTimeDragHandler();
|
|
39707
39746
|
if (config.onLevelDragEnd) {
|
|
@@ -39975,7 +40014,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
39975
40014
|
},
|
|
39976
40015
|
() => this.dataStore.length,
|
|
39977
40016
|
() => this.scheduleRender(),
|
|
39978
|
-
() => this.drawingManager.getActiveTool() !== null || this.drawingManager.isDraggingDrawing() || this.drawingManager.isResizingDrawing() || this.separatorDrag !== null || this.axisDrag !== null || (this.tradeDragHandler?.active ?? false) || (this.orderTimeDragHandler?.active ?? false) || this._pendingTradeDrag !== null || this._mobileCrosshairActive,
|
|
40017
|
+
() => this.drawingManager.getActiveTool() !== null || this.drawingManager.isDraggingDrawing() || this.drawingManager.isResizingDrawing() || this.separatorDrag !== null || this.axisDrag !== null || this.tapZoom !== null || (this.tradeDragHandler?.active ?? false) || (this.orderTimeDragHandler?.active ?? false) || this._pendingTradeDrag !== null || this._mobileCrosshairActive,
|
|
39979
40018
|
(deltaY) => {
|
|
39980
40019
|
if (this.priceScaleFactor === 1) return;
|
|
39981
40020
|
const { priceH } = this.getChartLayout();
|
|
@@ -40254,6 +40293,7 @@ var _ChartEngine = class _ChartEngine {
|
|
|
40254
40293
|
endIndex: this.viewport.endIndex + n
|
|
40255
40294
|
};
|
|
40256
40295
|
this._targetViewport = { ...this.viewport };
|
|
40296
|
+
if (this.axisDrag?.startEnd !== void 0) this.axisDrag.startEnd += n;
|
|
40257
40297
|
this.drawingManager.shiftBarIndices(n);
|
|
40258
40298
|
this.reanchorDrawingsByTimestamp();
|
|
40259
40299
|
this.recomputeIndicators();
|
|
@@ -46886,6 +46926,79 @@ var _ChartEngine = class _ChartEngine {
|
|
|
46886
46926
|
const all = this.dataStore.raw;
|
|
46887
46927
|
return all[all.length - 1]?.close ?? 0;
|
|
46888
46928
|
}
|
|
46929
|
+
/**
|
|
46930
|
+
* Time-axis drag (mouse or touch), the way TradingView does it: the right
|
|
46931
|
+
* edge — normally the latest bar — stays where it was, dragging right
|
|
46932
|
+
* spreads the bars (zoom in) and left packs more in (zoom out). The scale is
|
|
46933
|
+
* proportional to the drag, and zooming out stops once the oldest bar
|
|
46934
|
+
* reaches the left edge, so the candles always fill the full width.
|
|
46935
|
+
*/
|
|
46936
|
+
applyTimeAxisDrag(x) {
|
|
46937
|
+
const drag = this.axisDrag;
|
|
46938
|
+
if (!drag || drag.axis !== "time") return;
|
|
46939
|
+
const { chartW } = this.getChartLayout();
|
|
46940
|
+
const end = drag.startEnd ?? this.viewport.endIndex;
|
|
46941
|
+
const delta = x - drag.startPos;
|
|
46942
|
+
const wanted = drag.startSize * Math.exp(-delta / Math.max(1, chartW) * TIME_AXIS_DRAG_SPEED);
|
|
46943
|
+
const maxVisible = Math.max(MIN_VISIBLE_BARS_AXIS, Math.min(MAX_VISIBLE_BARS_AXIS, end));
|
|
46944
|
+
const newVisible = Math.max(MIN_VISIBLE_BARS_AXIS, Math.min(maxVisible, wanted));
|
|
46945
|
+
this.viewport = {
|
|
46946
|
+
...this.viewport,
|
|
46947
|
+
startIndex: end - newVisible,
|
|
46948
|
+
endIndex: end
|
|
46949
|
+
};
|
|
46950
|
+
this._targetViewport = { ...this.viewport };
|
|
46951
|
+
this.checkNeedMoreData();
|
|
46952
|
+
}
|
|
46953
|
+
/**
|
|
46954
|
+
* Arms a one-finger double-tap zoom at pane-local `y`. Locks the current
|
|
46955
|
+
* auto-fit range as the manual base so the zoom is relative to what is on
|
|
46956
|
+
* screen, and anchors on the tapped price so it stays under the finger.
|
|
46957
|
+
*/
|
|
46958
|
+
startTapZoom(y, priceH) {
|
|
46959
|
+
if (this._longPressTimer !== null) {
|
|
46960
|
+
clearTimeout(this._longPressTimer);
|
|
46961
|
+
this._longPressTimer = null;
|
|
46962
|
+
this._longPressPoint = null;
|
|
46963
|
+
}
|
|
46964
|
+
const displayed = this._displayedPriceRange ?? this.getScaledPriceRange(this.viewport);
|
|
46965
|
+
if (!this._lockedAutoFitBase) this._lockedAutoFitBase = { ...displayed };
|
|
46966
|
+
const t = Math.max(0, Math.min(1, y / Math.max(1, priceH)));
|
|
46967
|
+
this.tapZoom = {
|
|
46968
|
+
startY: y,
|
|
46969
|
+
startFactor: this.priceScaleFactor,
|
|
46970
|
+
anchorPrice: displayed.max - t * (displayed.max - displayed.min),
|
|
46971
|
+
moved: false
|
|
46972
|
+
};
|
|
46973
|
+
}
|
|
46974
|
+
/** Sets the price-scale factor, keeping the tap-zoom anchor price at the same pixel. */
|
|
46975
|
+
applyTapZoomFactor(factor) {
|
|
46976
|
+
const tz = this.tapZoom;
|
|
46977
|
+
const base = this._lockedAutoFitBase;
|
|
46978
|
+
if (!tz || !base) return;
|
|
46979
|
+
const clamped = Math.max(0.1, Math.min(10, factor));
|
|
46980
|
+
const baseMid = (base.min + base.max) / 2;
|
|
46981
|
+
const mid = baseMid + this.pricePanOffset;
|
|
46982
|
+
const newMid = tz.anchorPrice + (mid - tz.anchorPrice) * (clamped / this.priceScaleFactor);
|
|
46983
|
+
this.priceScaleFactor = clamped;
|
|
46984
|
+
this.pricePanOffset = newMid - baseMid;
|
|
46985
|
+
this.scheduleRender();
|
|
46986
|
+
}
|
|
46987
|
+
moveTapZoom(y) {
|
|
46988
|
+
const tz = this.tapZoom;
|
|
46989
|
+
if (!tz) return;
|
|
46990
|
+
const dy = y - tz.startY;
|
|
46991
|
+
if (!tz.moved && Math.abs(dy) < TAP_MAX_MOVE_PX) return;
|
|
46992
|
+
tz.moved = true;
|
|
46993
|
+
this.applyTapZoomFactor(tz.startFactor * Math.pow(1.01, dy));
|
|
46994
|
+
}
|
|
46995
|
+
endTapZoom() {
|
|
46996
|
+
const tz = this.tapZoom;
|
|
46997
|
+
if (!tz) return;
|
|
46998
|
+
if (!tz.moved) this.applyTapZoomFactor(tz.startFactor * DOUBLE_TAP_ZOOM_STEP);
|
|
46999
|
+
this.tapZoom = null;
|
|
47000
|
+
this.scheduleViewportPersist();
|
|
47001
|
+
}
|
|
46889
47002
|
resetPriceAxis() {
|
|
46890
47003
|
this.priceScaleFactor = 1;
|
|
46891
47004
|
this.pricePanOffset = 0;
|