@forgecharts/sdk 1.5.82 → 1.5.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/EventBus.d.ts +1 -1
- package/dist/api/{TChart.d.ts → ForgeCharts.d.ts} +47 -4
- package/dist/api/ForgeCharts.d.ts.map +1 -0
- package/dist/api/createChart.d.ts +4 -4
- package/dist/api/createChart.d.ts.map +1 -1
- package/dist/core/Chart.d.ts +5 -5
- package/dist/core/Chart.d.ts.map +1 -1
- package/dist/core/Series.d.ts +14 -0
- package/dist/core/Series.d.ts.map +1 -1
- package/dist/core/__tests__/seriesCutoff.test.d.ts +2 -0
- package/dist/core/__tests__/seriesCutoff.test.d.ts.map +1 -0
- package/dist/datafeed/DatafeedConnector.d.ts +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +520 -27
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.js +520 -27
- package/dist/internal.js.map +1 -1
- package/dist/licensing/packageFeatures.d.ts.map +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/node.d.ts.map +1 -1
- package/dist/pixi/PixiCandlestickRenderer.d.ts +4 -0
- package/dist/pixi/PixiCandlestickRenderer.d.ts.map +1 -1
- package/dist/pixi/PixiChart.d.ts +18 -2
- package/dist/pixi/PixiChart.d.ts.map +1 -1
- package/dist/pixi/PixiHistogramRenderer.d.ts +2 -0
- package/dist/pixi/PixiHistogramRenderer.d.ts.map +1 -1
- package/dist/pixi/PixiLineRenderer.d.ts +2 -0
- package/dist/pixi/PixiLineRenderer.d.ts.map +1 -1
- package/dist/react/canvas/ChartCanvas.d.ts +20 -2
- package/dist/react/canvas/ChartCanvas.d.ts.map +1 -1
- package/dist/react/hooks/useUserPackage.d.ts.map +1 -1
- package/dist/react/index.js +807 -63
- package/dist/react/index.js.map +1 -1
- package/dist/react/internal.js +814 -83
- package/dist/react/internal.js.map +1 -1
- package/dist/react/shell/AlertsDrawer.d.ts +11 -0
- package/dist/react/shell/AlertsDrawer.d.ts.map +1 -0
- package/dist/react/shell/ManagedAppShell.d.ts +1 -1
- package/dist/react/shell/ManagedAppShell.d.ts.map +1 -1
- package/dist/react/workspace/ChartWorkspace.d.ts +10 -1
- package/dist/react/workspace/ChartWorkspace.d.ts.map +1 -1
- package/dist/react/workspace/LayoutMenu.d.ts +3 -1
- package/dist/react/workspace/LayoutMenu.d.ts.map +1 -1
- package/dist/react/workspace/toolbars/TopToolbar.d.ts +7 -1
- package/dist/react/workspace/toolbars/TopToolbar.d.ts.map +1 -1
- package/dist/types/ChartBackend.d.ts +17 -4
- package/dist/types/ChartBackend.d.ts.map +1 -1
- package/dist/types/ISeries.d.ts +5 -0
- package/dist/types/ISeries.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/api/TChart.d.ts.map +0 -1
package/dist/react/internal.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React11, { createContext, forwardRef, useRef, useState, useEffect, useImperativeHandle, useCallback, useMemo, useContext, useReducer } from 'react';
|
|
2
|
-
import { TextStyle, Application,
|
|
2
|
+
import { TextStyle, Application, Graphics, Container, Text, FillGradient, CanvasTextMetrics } from 'pixi.js';
|
|
3
3
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import ReactDOM, { createPortal } from 'react-dom';
|
|
5
5
|
|
|
@@ -1410,6 +1410,7 @@ var Series = class _Series {
|
|
|
1410
1410
|
// ─── ISeries implementation ──────────────────────────────────────────────────
|
|
1411
1411
|
setData(data) {
|
|
1412
1412
|
this._data = [...data].sort((a, b) => a.time - b.time);
|
|
1413
|
+
this._cutoffView = null;
|
|
1413
1414
|
this.onDataChanged?.();
|
|
1414
1415
|
}
|
|
1415
1416
|
/**
|
|
@@ -1449,11 +1450,47 @@ var Series = class _Series {
|
|
|
1449
1450
|
this._data = this._data.slice(this._data.length - 1e5);
|
|
1450
1451
|
}
|
|
1451
1452
|
}
|
|
1453
|
+
this._cutoffView = null;
|
|
1452
1454
|
this.onDataChanged?.();
|
|
1453
1455
|
}
|
|
1454
|
-
|
|
1456
|
+
/**
|
|
1457
|
+
* Bar-replay display cutoff. When set, data() returns only bars at or
|
|
1458
|
+
* before this time while the FULL tape keeps flowing underneath — the
|
|
1459
|
+
* datafeed keeps writing live bars via setData/update, and everything
|
|
1460
|
+
* downstream (renderers, indicators, crosshair, last-price) naturally sees
|
|
1461
|
+
* the truncated past. Replay is not a mode the pipeline knows about; it is
|
|
1462
|
+
* this clamp plus a controller that moves it.
|
|
1463
|
+
*/
|
|
1464
|
+
_cutoffTime = null;
|
|
1465
|
+
_cutoffView = null;
|
|
1466
|
+
setCutoffTime(time) {
|
|
1467
|
+
this._cutoffTime = time;
|
|
1468
|
+
this._cutoffView = null;
|
|
1469
|
+
this.onDataChanged?.();
|
|
1470
|
+
}
|
|
1471
|
+
cutoffTime() {
|
|
1472
|
+
return this._cutoffTime;
|
|
1473
|
+
}
|
|
1474
|
+
/** The complete tape, ignoring any replay cutoff. Datafeed-side reads only. */
|
|
1475
|
+
fullData() {
|
|
1455
1476
|
return this._data;
|
|
1456
1477
|
}
|
|
1478
|
+
data() {
|
|
1479
|
+
if (this._cutoffTime === null) return this._data;
|
|
1480
|
+
const len = this._data.length;
|
|
1481
|
+
const cached = this._cutoffView;
|
|
1482
|
+
if (cached && cached.len === len && cached.cutoff === this._cutoffTime) return cached.view;
|
|
1483
|
+
let lo = 0;
|
|
1484
|
+
let hi = len;
|
|
1485
|
+
while (lo < hi) {
|
|
1486
|
+
const mid = lo + hi >>> 1;
|
|
1487
|
+
if (this._data[mid].time <= this._cutoffTime) lo = mid + 1;
|
|
1488
|
+
else hi = mid;
|
|
1489
|
+
}
|
|
1490
|
+
const view = this._data.slice(0, lo);
|
|
1491
|
+
this._cutoffView = { len, cutoff: this._cutoffTime, view };
|
|
1492
|
+
return view;
|
|
1493
|
+
}
|
|
1457
1494
|
applyOptions(options) {
|
|
1458
1495
|
this._options = { ...this._options, ...options };
|
|
1459
1496
|
}
|
|
@@ -4730,14 +4767,14 @@ var Chart = class {
|
|
|
4730
4767
|
_crosshairEnabled = true;
|
|
4731
4768
|
_magnetMode = "snap-xy";
|
|
4732
4769
|
// ── Trading overlay ───────────────────────────────────────────────────────────
|
|
4733
|
-
/** Current set of order lines supplied by
|
|
4770
|
+
/** Current set of order lines supplied by ForgeCharts for canvas rendering. */
|
|
4734
4771
|
_tradingOrders = [];
|
|
4735
4772
|
/** Called whenever a drawing is committed (created or updated). */
|
|
4736
4773
|
onDrawingCommitted;
|
|
4737
4774
|
/** Fired when an EXISTING drawing is modified via drag (handle or body move). */
|
|
4738
4775
|
onDrawingEdited;
|
|
4739
4776
|
_drawingEditDirty = false;
|
|
4740
|
-
/** Ctrl+Z / Ctrl+Y — drawing history is owned by
|
|
4777
|
+
/** Ctrl+Z / Ctrl+Y — drawing history is owned by ForgeCharts. */
|
|
4741
4778
|
onUndoRequest;
|
|
4742
4779
|
onRedoRequest;
|
|
4743
4780
|
/** Called whenever a drawing is deleted. */
|
|
@@ -4753,7 +4790,7 @@ var Chart = class {
|
|
|
4753
4790
|
/** Called when the user drags a TP/SL pill off an entry line to create a bracket leg. */
|
|
4754
4791
|
onTpSlCreate;
|
|
4755
4792
|
/**
|
|
4756
|
-
* Called after every pan or zoom interaction so external code (e.g.
|
|
4793
|
+
* Called after every pan or zoom interaction so external code (e.g. ForgeCharts)
|
|
4757
4794
|
* can check whether the visible time range now extends past the oldest
|
|
4758
4795
|
* loaded bar and trigger a lazy history fetch.
|
|
4759
4796
|
*/
|
|
@@ -5256,11 +5293,11 @@ var Chart = class {
|
|
|
5256
5293
|
}
|
|
5257
5294
|
ctx.restore();
|
|
5258
5295
|
}
|
|
5259
|
-
/** Provides the overlay renderer with the latest order list from
|
|
5296
|
+
/** Provides the overlay renderer with the latest order list from ForgeCharts. */
|
|
5260
5297
|
setTradingOrders(orders) {
|
|
5261
5298
|
this._tradingOrders = orders;
|
|
5262
5299
|
}
|
|
5263
|
-
/** Provides the overlay renderer with the latest position list from
|
|
5300
|
+
/** Provides the overlay renderer with the latest position list from ForgeCharts. */
|
|
5264
5301
|
setTradingPositions(_positions) {
|
|
5265
5302
|
}
|
|
5266
5303
|
/** Sets contract switch markers for continuous futures roll dates. */
|
|
@@ -6077,9 +6114,17 @@ var PixiCandlestickRenderer = class {
|
|
|
6077
6114
|
this._gfx = new Graphics();
|
|
6078
6115
|
layer.addChild(this._gfx);
|
|
6079
6116
|
}
|
|
6117
|
+
/** Clear this renderer's graphics — used when another style takes over. */
|
|
6118
|
+
clear() {
|
|
6119
|
+
this._gfx.clear();
|
|
6120
|
+
}
|
|
6080
6121
|
draw(bars, transform, opts) {
|
|
6081
6122
|
const gfx = this._gfx;
|
|
6082
6123
|
gfx.clear();
|
|
6124
|
+
if (opts?.type === "bar") {
|
|
6125
|
+
this._drawBars(bars, transform, opts);
|
|
6126
|
+
return;
|
|
6127
|
+
}
|
|
6083
6128
|
if (bars.length === 0) return;
|
|
6084
6129
|
const { plotWidth: pw, plotHeight: ph } = transform;
|
|
6085
6130
|
const upColor = hexToNum(opts && "upColor" in opts ? opts.upColor : void 0, UP_COLOR);
|
|
@@ -6210,6 +6255,36 @@ var PixiCandlestickRenderer = class {
|
|
|
6210
6255
|
/**
|
|
6211
6256
|
* Converts OHLCV bars to Heikin-Ashi values before drawing.
|
|
6212
6257
|
*/
|
|
6258
|
+
/** Classic OHLC bars: high-low spine, open tick left, close tick right. */
|
|
6259
|
+
_drawBars(bars, transform, opts) {
|
|
6260
|
+
const gfx = this._gfx;
|
|
6261
|
+
if (bars.length === 0) return;
|
|
6262
|
+
const upColor = hexToNum(opts && "upColor" in opts ? opts.upColor : void 0, UP_COLOR);
|
|
6263
|
+
const downColor = hexToNum(opts && "downColor" in opts ? opts.downColor : void 0, DOWN_COLOR);
|
|
6264
|
+
const { plotWidth: pw } = transform;
|
|
6265
|
+
let barPitch = pw / Math.max(1, bars.length);
|
|
6266
|
+
if (bars.length >= 2) {
|
|
6267
|
+
const dx = Math.abs(transform.timeToX(bars[1].time) - transform.timeToX(bars[0].time));
|
|
6268
|
+
if (dx > 0) barPitch = dx;
|
|
6269
|
+
}
|
|
6270
|
+
const tick = Math.max(1.5, Math.min(barPitch * 0.35, 6));
|
|
6271
|
+
const lineW = barPitch < 3 ? 1 : 1.5;
|
|
6272
|
+
for (const bar of bars) {
|
|
6273
|
+
const x = transform.timeToX(bar.time);
|
|
6274
|
+
if (x < -barPitch || x > pw + barPitch) continue;
|
|
6275
|
+
const yHigh = transform.priceToY(bar.high);
|
|
6276
|
+
const yLow = transform.priceToY(bar.low);
|
|
6277
|
+
const yOpen = transform.priceToY(bar.open);
|
|
6278
|
+
const yClose = transform.priceToY(bar.close);
|
|
6279
|
+
const color = bar.close >= bar.open ? upColor : downColor;
|
|
6280
|
+
gfx.moveTo(x, yHigh).lineTo(x, yLow);
|
|
6281
|
+
if (barPitch >= 3) {
|
|
6282
|
+
gfx.moveTo(x - tick, yOpen).lineTo(x, yOpen);
|
|
6283
|
+
gfx.moveTo(x, yClose).lineTo(x + tick, yClose);
|
|
6284
|
+
}
|
|
6285
|
+
gfx.stroke({ color, width: lineW });
|
|
6286
|
+
}
|
|
6287
|
+
}
|
|
6213
6288
|
drawHeikinAshi(bars, transform, opts) {
|
|
6214
6289
|
const first = bars[0];
|
|
6215
6290
|
const last = bars[bars.length - 1];
|
|
@@ -7532,6 +7607,10 @@ var PixiLineRenderer = class {
|
|
|
7532
7607
|
this._gradientCache = null;
|
|
7533
7608
|
this._gfx.destroy(true);
|
|
7534
7609
|
}
|
|
7610
|
+
/** Clear this renderer's graphics — used when another style takes over. */
|
|
7611
|
+
clear() {
|
|
7612
|
+
this._gfx.clear();
|
|
7613
|
+
}
|
|
7535
7614
|
draw(bars, transform, options) {
|
|
7536
7615
|
const gfx = this._gfx;
|
|
7537
7616
|
gfx.clear();
|
|
@@ -7626,6 +7705,10 @@ var PixiHistogramRenderer = class {
|
|
|
7626
7705
|
destroy() {
|
|
7627
7706
|
this._gfx.destroy(true);
|
|
7628
7707
|
}
|
|
7708
|
+
/** Clear this renderer's graphics — used when another style takes over. */
|
|
7709
|
+
clear() {
|
|
7710
|
+
this._gfx.clear();
|
|
7711
|
+
}
|
|
7629
7712
|
draw(bars, transform) {
|
|
7630
7713
|
const gfx = this._gfx;
|
|
7631
7714
|
gfx.clear();
|
|
@@ -8605,7 +8688,7 @@ var PixiChart = class {
|
|
|
8605
8688
|
/** Fired when an EXISTING drawing is modified via drag (handle or body move). */
|
|
8606
8689
|
onDrawingEdited;
|
|
8607
8690
|
_drawingEditDirty = false;
|
|
8608
|
-
/** Ctrl+Z / Ctrl+Y — drawing history is owned by
|
|
8691
|
+
/** Ctrl+Z / Ctrl+Y — drawing history is owned by ForgeCharts. */
|
|
8609
8692
|
onUndoRequest;
|
|
8610
8693
|
onRedoRequest;
|
|
8611
8694
|
/** Called whenever a drawing is deleted. */
|
|
@@ -8620,7 +8703,7 @@ var PixiChart = class {
|
|
|
8620
8703
|
onDrawingContextMenu;
|
|
8621
8704
|
/** Called when the user double-clicks a drawing. */
|
|
8622
8705
|
onDrawingDoubleClick;
|
|
8623
|
-
/** Called after every pan/zoom so
|
|
8706
|
+
/** Called after every pan/zoom so ForgeCharts can trigger lazy history loading. */
|
|
8624
8707
|
onViewportChanged;
|
|
8625
8708
|
constructor(container, options = {}) {
|
|
8626
8709
|
this._container = container;
|
|
@@ -8727,6 +8810,12 @@ var PixiChart = class {
|
|
|
8727
8810
|
this.onViewportChanged?.();
|
|
8728
8811
|
},
|
|
8729
8812
|
onCrosshairMove: (x, y) => {
|
|
8813
|
+
if (this._replaySelect) {
|
|
8814
|
+
const t = this._cursorBucketTime(x);
|
|
8815
|
+
this._replayHoverTime = t;
|
|
8816
|
+
this._replaySelect.onHover(t);
|
|
8817
|
+
this._drawReplayDim(x);
|
|
8818
|
+
}
|
|
8730
8819
|
if (!this._crosshairEnabled) return;
|
|
8731
8820
|
if (this._magnetMode === "off") {
|
|
8732
8821
|
this._crosshairRenderer.update(x, y, this._cursorBucketTime(x));
|
|
@@ -9016,7 +9105,7 @@ var PixiChart = class {
|
|
|
9016
9105
|
}).catch(() => {
|
|
9017
9106
|
});
|
|
9018
9107
|
}
|
|
9019
|
-
// ─── Drawing objects (used by PixiChart directly;
|
|
9108
|
+
// ─── Drawing objects (used by PixiChart directly; ForgeCharts uses DrawingManager) ─
|
|
9020
9109
|
/** @internal Used by PixiChart when DrawingManager pushes an update. */
|
|
9021
9110
|
setDrawings(drawings) {
|
|
9022
9111
|
this._drawings = [...drawings];
|
|
@@ -9025,6 +9114,47 @@ var PixiChart = class {
|
|
|
9025
9114
|
// ─── Rendering ───────────────────────────────────────────────────────────────
|
|
9026
9115
|
/** Styles whose renderer has already logged a failure this session. */
|
|
9027
9116
|
_renderErrorLogged = /* @__PURE__ */ new Set();
|
|
9117
|
+
// ── Bar-replay anchor selection ─────────────────────────────────────────
|
|
9118
|
+
_replaySelect = null;
|
|
9119
|
+
_replayHoverTime = null;
|
|
9120
|
+
_replayDimGfx = null;
|
|
9121
|
+
_replayClickHandler = null;
|
|
9122
|
+
setReplaySelect(cb) {
|
|
9123
|
+
this._replaySelect = cb;
|
|
9124
|
+
this._replayHoverTime = null;
|
|
9125
|
+
if (cb) {
|
|
9126
|
+
this._replayClickHandler = () => {
|
|
9127
|
+
if (this._replayHoverTime !== null) this._replaySelect?.onCommit(this._replayHoverTime);
|
|
9128
|
+
};
|
|
9129
|
+
this._app.canvas.addEventListener("click", this._replayClickHandler);
|
|
9130
|
+
this._app.canvas.style.cursor = "crosshair";
|
|
9131
|
+
} else {
|
|
9132
|
+
if (this._replayClickHandler) {
|
|
9133
|
+
this._app.canvas.removeEventListener("click", this._replayClickHandler);
|
|
9134
|
+
this._replayClickHandler = null;
|
|
9135
|
+
}
|
|
9136
|
+
this._app.canvas.style.cursor = "";
|
|
9137
|
+
if (this._replayDimGfx) {
|
|
9138
|
+
this._replayDimGfx.clear();
|
|
9139
|
+
this._replayDimGfx.destroy();
|
|
9140
|
+
this._replayDimGfx = null;
|
|
9141
|
+
}
|
|
9142
|
+
}
|
|
9143
|
+
}
|
|
9144
|
+
/** Translucent veil over the bars a replay-anchor click would remove. */
|
|
9145
|
+
_drawReplayDim(x) {
|
|
9146
|
+
if (!this._replayDimGfx) {
|
|
9147
|
+
this._replayDimGfx = new Graphics();
|
|
9148
|
+
this._layers.get(5 /* Interaction */).addChildAt(this._replayDimGfx, 0);
|
|
9149
|
+
}
|
|
9150
|
+
const gfx = this._replayDimGfx;
|
|
9151
|
+
const { plotWidth, plotHeight } = this._transform;
|
|
9152
|
+
gfx.clear();
|
|
9153
|
+
if (x >= 0 && x < plotWidth) {
|
|
9154
|
+
gfx.rect(x, 0, plotWidth - x, plotHeight).fill({ color: 8421520, alpha: 0.35 });
|
|
9155
|
+
}
|
|
9156
|
+
this._layers.dirty.mark(5 /* Interaction */);
|
|
9157
|
+
}
|
|
9028
9158
|
_renderFrame() {
|
|
9029
9159
|
if (!this._ready || this._destroyed) return;
|
|
9030
9160
|
const t = this._transform;
|
|
@@ -9034,6 +9164,9 @@ var PixiChart = class {
|
|
|
9034
9164
|
if (dirty.isDirty(1 /* PriceSeries */)) {
|
|
9035
9165
|
this._autoFitPriceRange();
|
|
9036
9166
|
dirty.mark(0 /* Background */);
|
|
9167
|
+
this._candleRenderer.clear();
|
|
9168
|
+
this._lineRenderer.clear();
|
|
9169
|
+
this._histogramRenderer.clear();
|
|
9037
9170
|
for (const series of this._series) {
|
|
9038
9171
|
const opts = series.options();
|
|
9039
9172
|
if (opts.visible === false) continue;
|
|
@@ -9246,6 +9379,16 @@ var PixiChart = class {
|
|
|
9246
9379
|
}
|
|
9247
9380
|
this._markViewportDirty();
|
|
9248
9381
|
}
|
|
9382
|
+
/**
|
|
9383
|
+
* Pans the viewport so the bar at `time` sits at the horizontal center.
|
|
9384
|
+
* Used by bar replay: the anchor candle the trader picked becomes the
|
|
9385
|
+
* chart's center, with the (empty, dimmed-away) future to its right.
|
|
9386
|
+
*/
|
|
9387
|
+
centerOnTime(time) {
|
|
9388
|
+
const px = this._transform.timeToX(time);
|
|
9389
|
+
this._transform.pan(-(px - this._transform.plotWidth / 2), 0);
|
|
9390
|
+
this._markViewportDirty();
|
|
9391
|
+
}
|
|
9249
9392
|
/** Pans the viewport so the latest bar sits near the right edge. */
|
|
9250
9393
|
_scrollToLatestBar() {
|
|
9251
9394
|
let latestTime = -Infinity;
|
|
@@ -11596,6 +11739,8 @@ var resolver = () => ChartRuntimeResolver.getInstance();
|
|
|
11596
11739
|
var canUseChartTypes = () => resolver().canUseChartTypes();
|
|
11597
11740
|
var canUseExtendedDrawings = () => resolver().canUseExtendedDrawings();
|
|
11598
11741
|
var canUseMultiPane = () => resolver().canUseMultiPane();
|
|
11742
|
+
var canUseBarReplay = () => resolver().canUseBarReplay();
|
|
11743
|
+
var canRenderOverlays = () => resolver().canRenderOverlays();
|
|
11599
11744
|
var canUseDraggableOrders = () => resolver().canUseDraggableOrders();
|
|
11600
11745
|
var canUseBracketOrders = () => resolver().canUseBracketOrders();
|
|
11601
11746
|
var canUsePositionsOverlay = () => resolver().canUsePositionsOverlay();
|
|
@@ -11912,8 +12057,8 @@ var ManagedTradingController = class {
|
|
|
11912
12057
|
}
|
|
11913
12058
|
};
|
|
11914
12059
|
|
|
11915
|
-
// src/api/
|
|
11916
|
-
var
|
|
12060
|
+
// src/api/ForgeCharts.ts
|
|
12061
|
+
var ForgeCharts = class _ForgeCharts {
|
|
11917
12062
|
_core;
|
|
11918
12063
|
_bus;
|
|
11919
12064
|
_indicators;
|
|
@@ -12304,7 +12449,7 @@ var TChart = class _TChart {
|
|
|
12304
12449
|
"[ForgeCharts] ForgeScript indicators are not enabled on the active license. Contact your license provider to enable the forgeScript feature."
|
|
12305
12450
|
);
|
|
12306
12451
|
}
|
|
12307
|
-
let finalConfig =
|
|
12452
|
+
let finalConfig = _ForgeCharts.OVERLAY_ONLY.has(config.type) && config.overlay !== true ? { ...config, overlay: true } : config;
|
|
12308
12453
|
if (finalConfig.overlay !== true && !canUseMultiPane()) {
|
|
12309
12454
|
console.warn("[ForgeCharts] multiPane is not enabled on the active license \u2014 indicator added as a price-pane overlay instead.");
|
|
12310
12455
|
finalConfig = { ...finalConfig, overlay: true };
|
|
@@ -12482,7 +12627,7 @@ var TChart = class _TChart {
|
|
|
12482
12627
|
*/
|
|
12483
12628
|
startDrawingTool(type) {
|
|
12484
12629
|
this._assertAlive();
|
|
12485
|
-
if (
|
|
12630
|
+
if (_ForgeCharts.EXTENDED_DRAWING_TOOLS.has(type) && !canUseExtendedDrawings()) {
|
|
12486
12631
|
console.warn(`[ForgeCharts] Drawing tool '${type}' requires the extendedDrawings feature, which is not enabled on the active license.`);
|
|
12487
12632
|
return;
|
|
12488
12633
|
}
|
|
@@ -12520,6 +12665,14 @@ var TChart = class _TChart {
|
|
|
12520
12665
|
/** Set contract switch roll markers for continuous futures. */
|
|
12521
12666
|
setContractSwitches(switches) {
|
|
12522
12667
|
this._assertAlive();
|
|
12668
|
+
if (!canRenderOverlays()) {
|
|
12669
|
+
if (switches.length > 0) {
|
|
12670
|
+
console.warn("[ForgeCharts] Overlay rendering (overlays) is not enabled on the active plan \u2014 contract switch markers suppressed.");
|
|
12671
|
+
}
|
|
12672
|
+
this._core.setContractSwitches([]);
|
|
12673
|
+
this._core.markDirty();
|
|
12674
|
+
return;
|
|
12675
|
+
}
|
|
12523
12676
|
this._core.setContractSwitches(switches);
|
|
12524
12677
|
this._core.markDirty();
|
|
12525
12678
|
}
|
|
@@ -12563,7 +12716,7 @@ var TChart = class _TChart {
|
|
|
12563
12716
|
const snap = this._drawings.all().map((d) => ({ ...d, points: [...d.points] }));
|
|
12564
12717
|
this._drawHistory.splice(this._drawHistIdx + 1);
|
|
12565
12718
|
this._drawHistory.push(snap);
|
|
12566
|
-
if (this._drawHistory.length >
|
|
12719
|
+
if (this._drawHistory.length > _ForgeCharts._HIST_MAX) this._drawHistory.shift();
|
|
12567
12720
|
this._drawHistIdx = this._drawHistory.length - 1;
|
|
12568
12721
|
}
|
|
12569
12722
|
/** Steps drawing state one edit back (Ctrl+Z). */
|
|
@@ -12928,6 +13081,186 @@ var TChart = class _TChart {
|
|
|
12928
13081
|
}
|
|
12929
13082
|
return this._unmanagedIngestion;
|
|
12930
13083
|
}
|
|
13084
|
+
// ─── Bar replay ───────────────────────────────────────────────────────────────
|
|
13085
|
+
//
|
|
13086
|
+
// Replay is a display cutoff on the primary series (Series.setCutoffTime)
|
|
13087
|
+
// plus this controller moving it. The full tape keeps flowing underneath —
|
|
13088
|
+
// live bars land in fullData() and stay hidden until stepped over or the
|
|
13089
|
+
// replay exits — so no datafeed, indicator, or renderer knows replay
|
|
13090
|
+
// exists. Selection (the hover shadow of what a click would remove) is a
|
|
13091
|
+
// backend surface only Pixi implements; ForgeCharts refuses replay elsewhere.
|
|
13092
|
+
/** Playback speeds, in bars per second. */
|
|
13093
|
+
static REPLAY_SPEEDS = [0.5, 1, 2, 5, 10, 15, 20];
|
|
13094
|
+
_replay = {
|
|
13095
|
+
selecting: false,
|
|
13096
|
+
active: false,
|
|
13097
|
+
playing: false,
|
|
13098
|
+
speedIdx: 1,
|
|
13099
|
+
timer: null
|
|
13100
|
+
};
|
|
13101
|
+
_emitReplayState() {
|
|
13102
|
+
this._bus.emit("replayStateChanged", {
|
|
13103
|
+
selecting: this._replay.selecting,
|
|
13104
|
+
active: this._replay.active,
|
|
13105
|
+
playing: this._replay.playing,
|
|
13106
|
+
speed: _ForgeCharts.REPLAY_SPEEDS[this._replay.speedIdx],
|
|
13107
|
+
cutoffTime: this._series.cutoffTime()
|
|
13108
|
+
});
|
|
13109
|
+
}
|
|
13110
|
+
/** Current replay state — the control strip's render source. */
|
|
13111
|
+
replayState() {
|
|
13112
|
+
return {
|
|
13113
|
+
selecting: this._replay.selecting,
|
|
13114
|
+
active: this._replay.active,
|
|
13115
|
+
playing: this._replay.playing,
|
|
13116
|
+
speed: _ForgeCharts.REPLAY_SPEEDS[this._replay.speedIdx],
|
|
13117
|
+
cutoffTime: this._series.cutoffTime()
|
|
13118
|
+
};
|
|
13119
|
+
}
|
|
13120
|
+
/**
|
|
13121
|
+
* Enter replay-anchor selection: the cursor shadows everything a click
|
|
13122
|
+
* would remove; clicking commits that bucket as the replay head. Returns
|
|
13123
|
+
* false (with a warning) when the plan lacks bar_replay or the backend
|
|
13124
|
+
* has no selection surface.
|
|
13125
|
+
*/
|
|
13126
|
+
replayEnterSelection() {
|
|
13127
|
+
this._assertAlive();
|
|
13128
|
+
if (!canUseBarReplay()) {
|
|
13129
|
+
console.warn("[ForgeCharts] Bar replay (bar_replay) is not enabled on the active plan.");
|
|
13130
|
+
return false;
|
|
13131
|
+
}
|
|
13132
|
+
if (!this._core.setReplaySelect) {
|
|
13133
|
+
console.warn("[ForgeCharts] Bar replay requires the Pixi render backend.");
|
|
13134
|
+
return false;
|
|
13135
|
+
}
|
|
13136
|
+
this._replayStopTimer();
|
|
13137
|
+
this._replay.selecting = true;
|
|
13138
|
+
this._replay.playing = false;
|
|
13139
|
+
this._core.setReplaySelect({
|
|
13140
|
+
onHover: () => {
|
|
13141
|
+
},
|
|
13142
|
+
onCommit: (time) => this._replayCommit(time)
|
|
13143
|
+
});
|
|
13144
|
+
this._emitReplayState();
|
|
13145
|
+
return true;
|
|
13146
|
+
}
|
|
13147
|
+
/** Leave selection without committing (existing replay, if any, survives). */
|
|
13148
|
+
replayCancelSelection() {
|
|
13149
|
+
this._assertAlive();
|
|
13150
|
+
if (!this._replay.selecting) return;
|
|
13151
|
+
this._replay.selecting = false;
|
|
13152
|
+
this._core.setReplaySelect?.(null);
|
|
13153
|
+
this._emitReplayState();
|
|
13154
|
+
}
|
|
13155
|
+
_replayCommit(time) {
|
|
13156
|
+
this._replay.selecting = false;
|
|
13157
|
+
this._core.setReplaySelect?.(null);
|
|
13158
|
+
this._series.setCutoffTime(time);
|
|
13159
|
+
this._replay.active = true;
|
|
13160
|
+
this._replay.playing = false;
|
|
13161
|
+
this._core.centerOnTime?.(time);
|
|
13162
|
+
this._core.markDirty();
|
|
13163
|
+
this._emitReplayState();
|
|
13164
|
+
}
|
|
13165
|
+
replayPlay() {
|
|
13166
|
+
this._assertAlive();
|
|
13167
|
+
if (!this._replay.active || this._replay.playing) return;
|
|
13168
|
+
this._replay.playing = true;
|
|
13169
|
+
this._replayStartTimer();
|
|
13170
|
+
this._emitReplayState();
|
|
13171
|
+
}
|
|
13172
|
+
replayPause() {
|
|
13173
|
+
this._assertAlive();
|
|
13174
|
+
if (!this._replay.playing) return;
|
|
13175
|
+
this._replay.playing = false;
|
|
13176
|
+
this._replayStopTimer();
|
|
13177
|
+
this._emitReplayState();
|
|
13178
|
+
}
|
|
13179
|
+
/** Advance the replay head n bars. Pauses at the end of the tape. */
|
|
13180
|
+
replayStepForward(n = 1) {
|
|
13181
|
+
this._assertAlive();
|
|
13182
|
+
if (!this._replay.active) return;
|
|
13183
|
+
const full = this._series.fullData();
|
|
13184
|
+
const shown = this._series.data().length;
|
|
13185
|
+
const nextIdx = Math.min(shown + n, full.length) - 1;
|
|
13186
|
+
if (nextIdx < shown) {
|
|
13187
|
+
this.replayPause();
|
|
13188
|
+
return;
|
|
13189
|
+
}
|
|
13190
|
+
this._series.setCutoffTime(full[nextIdx].time);
|
|
13191
|
+
this._core.markDirty();
|
|
13192
|
+
if (nextIdx >= full.length - 1) this.replayPause();
|
|
13193
|
+
this._emitReplayState();
|
|
13194
|
+
}
|
|
13195
|
+
/** Rewind the replay head n bars back in time (default 10). */
|
|
13196
|
+
replayRewind(n = 10) {
|
|
13197
|
+
this._assertAlive();
|
|
13198
|
+
if (!this._replay.active) return;
|
|
13199
|
+
const full = this._series.fullData();
|
|
13200
|
+
const shown = this._series.data().length;
|
|
13201
|
+
const idx = Math.max(shown - 1 - n, 0);
|
|
13202
|
+
if (full.length === 0) return;
|
|
13203
|
+
this._series.setCutoffTime(full[idx].time);
|
|
13204
|
+
this._core.markDirty();
|
|
13205
|
+
this._emitReplayState();
|
|
13206
|
+
}
|
|
13207
|
+
/** Set an exact playback speed (nearest catalogued value). */
|
|
13208
|
+
replaySetSpeed(speed) {
|
|
13209
|
+
this._assertAlive();
|
|
13210
|
+
let best = 0;
|
|
13211
|
+
for (let i = 0; i < _ForgeCharts.REPLAY_SPEEDS.length; i++) {
|
|
13212
|
+
if (Math.abs(_ForgeCharts.REPLAY_SPEEDS[i] - speed) < Math.abs(_ForgeCharts.REPLAY_SPEEDS[best] - speed)) best = i;
|
|
13213
|
+
}
|
|
13214
|
+
this._replay.speedIdx = best;
|
|
13215
|
+
if (this._replay.playing) this._replayStartTimer();
|
|
13216
|
+
this._emitReplayState();
|
|
13217
|
+
}
|
|
13218
|
+
/**
|
|
13219
|
+
* Restore a persisted replay session (browser refresh mid-replay). Applies
|
|
13220
|
+
* the same gate as entering selection; the cutoff simply lands where it
|
|
13221
|
+
* was, paused, with the anchor centered. A cutoff at or beyond the tape's
|
|
13222
|
+
* head means the replay had effectively finished — nothing is restored.
|
|
13223
|
+
*/
|
|
13224
|
+
replayRestore(cutoffTime, speed) {
|
|
13225
|
+
this._assertAlive();
|
|
13226
|
+
if (!canUseBarReplay()) return false;
|
|
13227
|
+
const full = this._series.fullData();
|
|
13228
|
+
const last = full[full.length - 1];
|
|
13229
|
+
if (!last || cutoffTime >= last.time) return false;
|
|
13230
|
+
if (speed !== void 0) this.replaySetSpeed(speed);
|
|
13231
|
+
this._replayCommit(cutoffTime);
|
|
13232
|
+
return true;
|
|
13233
|
+
}
|
|
13234
|
+
/** Cycle to the next playback speed; restarts the timer when playing. */
|
|
13235
|
+
replayCycleSpeed() {
|
|
13236
|
+
this._assertAlive();
|
|
13237
|
+
this._replay.speedIdx = (this._replay.speedIdx + 1) % _ForgeCharts.REPLAY_SPEEDS.length;
|
|
13238
|
+
if (this._replay.playing) this._replayStartTimer();
|
|
13239
|
+
this._emitReplayState();
|
|
13240
|
+
return _ForgeCharts.REPLAY_SPEEDS[this._replay.speedIdx];
|
|
13241
|
+
}
|
|
13242
|
+
/** Exit replay entirely: the full tape (live head included) returns. */
|
|
13243
|
+
replayExit() {
|
|
13244
|
+
this._assertAlive();
|
|
13245
|
+
this._replayStopTimer();
|
|
13246
|
+
this._replay.selecting = false;
|
|
13247
|
+
this._replay.active = false;
|
|
13248
|
+
this._replay.playing = false;
|
|
13249
|
+
this._core.setReplaySelect?.(null);
|
|
13250
|
+
this._series.setCutoffTime(null);
|
|
13251
|
+
this._core.markDirty();
|
|
13252
|
+
this._core.scrollToEnd();
|
|
13253
|
+
this._emitReplayState();
|
|
13254
|
+
}
|
|
13255
|
+
_replayStartTimer() {
|
|
13256
|
+
this._replayStopTimer();
|
|
13257
|
+
const speed = _ForgeCharts.REPLAY_SPEEDS[this._replay.speedIdx];
|
|
13258
|
+
this._replay.timer = setInterval(() => this.replayStepForward(1), Math.max(1e3 / speed, 16));
|
|
13259
|
+
}
|
|
13260
|
+
_replayStopTimer() {
|
|
13261
|
+
if (this._replay.timer) clearInterval(this._replay.timer);
|
|
13262
|
+
this._replay.timer = null;
|
|
13263
|
+
}
|
|
12931
13264
|
// ─── Datafeed ─────────────────────────────────────────────────────────────────
|
|
12932
13265
|
_buildConnector(datafeed) {
|
|
12933
13266
|
return new DatafeedConnector(datafeed, this._series, {
|
|
@@ -18369,7 +18702,7 @@ var DEMONSTRATION_STROKE = "rgba(255, 200, 50, 0.7)";
|
|
|
18369
18702
|
var DEMONSTRATION_COLOR = "var(--crosshair-overlay, rgba(255,255,255,0.75))";
|
|
18370
18703
|
|
|
18371
18704
|
// src/version.ts
|
|
18372
|
-
var FORGECHARTS_VERSION = "1.5.
|
|
18705
|
+
var FORGECHARTS_VERSION = "1.5.84" ;
|
|
18373
18706
|
function applyRuntimeConfig(caps, config) {
|
|
18374
18707
|
if (!config) return caps;
|
|
18375
18708
|
const orderEntry = config.enableOrderEntry === false ? false : caps.orderEntry;
|
|
@@ -18471,10 +18804,7 @@ function useHostLicense(apiUrl, getAuthToken) {
|
|
|
18471
18804
|
function useUserPackage(apiUrl, getAuthToken) {
|
|
18472
18805
|
useEffect(() => {
|
|
18473
18806
|
const resolver3 = ChartRuntimeResolver.getInstance();
|
|
18474
|
-
if (!apiUrl || !getAuthToken)
|
|
18475
|
-
resolver3.setUserPackageFeatures(null);
|
|
18476
|
-
return;
|
|
18477
|
-
}
|
|
18807
|
+
if (!apiUrl || !getAuthToken) return;
|
|
18478
18808
|
let cancelled = false;
|
|
18479
18809
|
void (async () => {
|
|
18480
18810
|
try {
|
|
@@ -23214,6 +23544,8 @@ var ChartCanvas = forwardRef(
|
|
|
23214
23544
|
onPointerToolChange,
|
|
23215
23545
|
initialPointerTool,
|
|
23216
23546
|
initialSeriesType,
|
|
23547
|
+
initialReplay,
|
|
23548
|
+
onReplayStateChange,
|
|
23217
23549
|
initialMagnetMode,
|
|
23218
23550
|
onMagnetModeChange,
|
|
23219
23551
|
drawingFavorites: drawingFavoritesProp,
|
|
@@ -23333,6 +23665,14 @@ var ChartCanvas = forwardRef(
|
|
|
23333
23665
|
},
|
|
23334
23666
|
addIndicator: (config) => chartRef.current?.addIndicator(config) ?? null,
|
|
23335
23667
|
setSeriesType: (type) => chartRef.current?.setSeriesType(type),
|
|
23668
|
+
toggleReplay: () => {
|
|
23669
|
+
const chart = chartRef.current;
|
|
23670
|
+
if (!chart) return;
|
|
23671
|
+
const st = chart.replayState();
|
|
23672
|
+
if (st.selecting) chart.replayCancelSelection();
|
|
23673
|
+
else if (st.active) chart.replayExit();
|
|
23674
|
+
else chart.replayEnterSelection();
|
|
23675
|
+
},
|
|
23336
23676
|
removeIndicator: (id) => chartRef.current?.removeIndicator(id),
|
|
23337
23677
|
moveIndicatorToOverlay: (id) => chartRef.current?.moveIndicatorToOverlay(id),
|
|
23338
23678
|
moveIndicatorToPane: (id, targetPaneId) => chartRef.current?.moveIndicatorToPane(id, targetPaneId),
|
|
@@ -23380,7 +23720,7 @@ var ChartCanvas = forwardRef(
|
|
|
23380
23720
|
useEffect(() => {
|
|
23381
23721
|
const container = priceRef.current;
|
|
23382
23722
|
if (!container) return;
|
|
23383
|
-
const chart = new
|
|
23723
|
+
const chart = new ForgeCharts({
|
|
23384
23724
|
container,
|
|
23385
23725
|
symbol,
|
|
23386
23726
|
interval: timeframe,
|
|
@@ -23394,6 +23734,10 @@ var ChartCanvas = forwardRef(
|
|
|
23394
23734
|
for (const config of initialIndicators) {
|
|
23395
23735
|
chart.addIndicator(config);
|
|
23396
23736
|
}
|
|
23737
|
+
chart.on("replayStateChanged", (s) => {
|
|
23738
|
+
setReplayState(s.selecting || s.active ? s : null);
|
|
23739
|
+
onReplayStateChangeRef.current?.(s);
|
|
23740
|
+
});
|
|
23397
23741
|
transformRef.current = chart.getTransform();
|
|
23398
23742
|
const syncChartState = () => {
|
|
23399
23743
|
setBars([...chart.getBars()]);
|
|
@@ -24353,6 +24697,37 @@ var ChartCanvas = forwardRef(
|
|
|
24353
24697
|
selectPointerToolRef.current = handleToolSelect;
|
|
24354
24698
|
const initialPointerAppliedRef = useRef(false);
|
|
24355
24699
|
const initialSeriesAppliedRef = useRef(false);
|
|
24700
|
+
const [replayState, setReplayState] = useState(null);
|
|
24701
|
+
const [replayBarOffset, setReplayBarOffset] = useState(0);
|
|
24702
|
+
const replayDragRef = useRef(null);
|
|
24703
|
+
const onReplayStateChangeRef = useRef(onReplayStateChange);
|
|
24704
|
+
useEffect(() => {
|
|
24705
|
+
onReplayStateChangeRef.current = onReplayStateChange;
|
|
24706
|
+
}, [onReplayStateChange]);
|
|
24707
|
+
const initialReplayAppliedRef = useRef(false);
|
|
24708
|
+
useEffect(() => {
|
|
24709
|
+
if (initialReplayAppliedRef.current || !initialReplay || bars.length === 0) return;
|
|
24710
|
+
initialReplayAppliedRef.current = true;
|
|
24711
|
+
chartRef.current?.replayRestore(initialReplay.cutoffTime, initialReplay.speed);
|
|
24712
|
+
}, [initialReplay, bars]);
|
|
24713
|
+
useEffect(() => {
|
|
24714
|
+
const move = (e) => {
|
|
24715
|
+
const d = replayDragRef.current;
|
|
24716
|
+
if (!d) return;
|
|
24717
|
+
const host = outerRef.current;
|
|
24718
|
+
const max = host ? Math.max(0, host.clientHeight - 80) : 400;
|
|
24719
|
+
setReplayBarOffset(Math.min(max, Math.max(0, d.startOffset + (d.startY - e.clientY))));
|
|
24720
|
+
};
|
|
24721
|
+
const up = () => {
|
|
24722
|
+
replayDragRef.current = null;
|
|
24723
|
+
};
|
|
24724
|
+
window.addEventListener("pointermove", move);
|
|
24725
|
+
window.addEventListener("pointerup", up);
|
|
24726
|
+
return () => {
|
|
24727
|
+
window.removeEventListener("pointermove", move);
|
|
24728
|
+
window.removeEventListener("pointerup", up);
|
|
24729
|
+
};
|
|
24730
|
+
}, []);
|
|
24356
24731
|
useEffect(() => {
|
|
24357
24732
|
if (!initialSeriesAppliedRef.current && initialSeriesType && chartRef.current) {
|
|
24358
24733
|
initialSeriesAppliedRef.current = true;
|
|
@@ -24361,7 +24736,7 @@ var ChartCanvas = forwardRef(
|
|
|
24361
24736
|
if (initialPointerAppliedRef.current || !initialPointerTool) return;
|
|
24362
24737
|
initialPointerAppliedRef.current = true;
|
|
24363
24738
|
selectPointerToolRef.current(initialPointerTool);
|
|
24364
|
-
}, [initialPointerTool]);
|
|
24739
|
+
}, [initialPointerTool, initialSeriesType]);
|
|
24365
24740
|
const handleMagnetModeChange = useCallback((mode) => {
|
|
24366
24741
|
setMagnetMode(mode);
|
|
24367
24742
|
chartRef.current?.setMagnetMode(mode);
|
|
@@ -24457,6 +24832,124 @@ var ChartCanvas = forwardRef(
|
|
|
24457
24832
|
crosshairXRef.current = null;
|
|
24458
24833
|
},
|
|
24459
24834
|
children: [
|
|
24835
|
+
replayState && (() => {
|
|
24836
|
+
const dark = effectiveTheme !== "light";
|
|
24837
|
+
const bg = dark ? "#000000" : "#ffffff";
|
|
24838
|
+
const fg = dark ? "#ffffff" : "#000000";
|
|
24839
|
+
const line = dark ? "rgba(255,255,255,0.22)" : "rgba(0,0,0,0.22)";
|
|
24840
|
+
const btn = {
|
|
24841
|
+
background: "transparent",
|
|
24842
|
+
border: "none",
|
|
24843
|
+
color: fg,
|
|
24844
|
+
cursor: "pointer",
|
|
24845
|
+
padding: "6px 10px",
|
|
24846
|
+
borderRadius: 6,
|
|
24847
|
+
fontSize: 12,
|
|
24848
|
+
display: "inline-flex",
|
|
24849
|
+
alignItems: "center",
|
|
24850
|
+
gap: 5,
|
|
24851
|
+
lineHeight: 1
|
|
24852
|
+
};
|
|
24853
|
+
const sep = /* @__PURE__ */ jsx("span", { style: { width: 1, alignSelf: "stretch", background: line, margin: "6px 4px" } });
|
|
24854
|
+
const call = (fn) => () => {
|
|
24855
|
+
if (chartRef.current) fn(chartRef.current);
|
|
24856
|
+
};
|
|
24857
|
+
return /* @__PURE__ */ jsxs("div", { style: {
|
|
24858
|
+
position: "absolute",
|
|
24859
|
+
left: 0,
|
|
24860
|
+
right: 0,
|
|
24861
|
+
bottom: replayBarOffset,
|
|
24862
|
+
zIndex: 8,
|
|
24863
|
+
display: "flex",
|
|
24864
|
+
alignItems: "center",
|
|
24865
|
+
gap: 2,
|
|
24866
|
+
height: 38,
|
|
24867
|
+
padding: "0 8px",
|
|
24868
|
+
boxSizing: "border-box",
|
|
24869
|
+
background: bg,
|
|
24870
|
+
color: fg,
|
|
24871
|
+
borderTop: `1px solid ${line}`,
|
|
24872
|
+
borderBottom: `1px solid ${line}`,
|
|
24873
|
+
userSelect: "none"
|
|
24874
|
+
}, children: [
|
|
24875
|
+
/* @__PURE__ */ jsx(
|
|
24876
|
+
"span",
|
|
24877
|
+
{
|
|
24878
|
+
title: "Drag to move",
|
|
24879
|
+
onPointerDown: (e) => {
|
|
24880
|
+
e.preventDefault();
|
|
24881
|
+
replayDragRef.current = { startY: e.clientY, startOffset: replayBarOffset };
|
|
24882
|
+
},
|
|
24883
|
+
style: { cursor: "ns-resize", padding: "6px 6px", color: fg, opacity: 0.55, fontSize: 12, letterSpacing: 1 },
|
|
24884
|
+
children: "\u2059\u2059"
|
|
24885
|
+
}
|
|
24886
|
+
),
|
|
24887
|
+
sep,
|
|
24888
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: 12, fontWeight: 600, letterSpacing: 0.3, padding: "0 6px" }, children: "Replay" }),
|
|
24889
|
+
sep,
|
|
24890
|
+
replayState.selecting ? /* @__PURE__ */ jsx("span", { style: { fontSize: 12, padding: "0 8px", opacity: 0.85 }, children: "Click a bar to start replay from there" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
24891
|
+
/* @__PURE__ */ jsx(
|
|
24892
|
+
"button",
|
|
24893
|
+
{
|
|
24894
|
+
style: btn,
|
|
24895
|
+
title: "Pick a new starting bar",
|
|
24896
|
+
onClick: call((c) => c.replayEnterSelection()),
|
|
24897
|
+
children: "\u2506\u2190 Select bar"
|
|
24898
|
+
}
|
|
24899
|
+
),
|
|
24900
|
+
sep,
|
|
24901
|
+
/* @__PURE__ */ jsx(
|
|
24902
|
+
"button",
|
|
24903
|
+
{
|
|
24904
|
+
style: btn,
|
|
24905
|
+
title: "Rewind 10 bars",
|
|
24906
|
+
onClick: call((c) => c.replayRewind(10)),
|
|
24907
|
+
children: "\u23EA"
|
|
24908
|
+
}
|
|
24909
|
+
),
|
|
24910
|
+
/* @__PURE__ */ jsx(
|
|
24911
|
+
"button",
|
|
24912
|
+
{
|
|
24913
|
+
style: btn,
|
|
24914
|
+
title: replayState.playing ? "Pause" : "Play",
|
|
24915
|
+
onClick: call((c) => replayState.playing ? c.replayPause() : c.replayPlay()),
|
|
24916
|
+
children: replayState.playing ? "\u23F8" : "\u25B6"
|
|
24917
|
+
}
|
|
24918
|
+
),
|
|
24919
|
+
/* @__PURE__ */ jsx(
|
|
24920
|
+
"button",
|
|
24921
|
+
{
|
|
24922
|
+
style: btn,
|
|
24923
|
+
title: "Forward one bar",
|
|
24924
|
+
onClick: call((c) => c.replayStepForward(1)),
|
|
24925
|
+
children: "\u23E9"
|
|
24926
|
+
}
|
|
24927
|
+
),
|
|
24928
|
+
/* @__PURE__ */ jsxs(
|
|
24929
|
+
"button",
|
|
24930
|
+
{
|
|
24931
|
+
style: { ...btn, fontVariantNumeric: "tabular-nums", minWidth: 44, justifyContent: "center" },
|
|
24932
|
+
title: "Playback speed (0.5\u201320 bars per second)",
|
|
24933
|
+
onClick: call((c) => c.replayCycleSpeed()),
|
|
24934
|
+
children: [
|
|
24935
|
+
replayState.speed,
|
|
24936
|
+
"x"
|
|
24937
|
+
]
|
|
24938
|
+
}
|
|
24939
|
+
)
|
|
24940
|
+
] }),
|
|
24941
|
+
/* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
|
|
24942
|
+
/* @__PURE__ */ jsx(
|
|
24943
|
+
"button",
|
|
24944
|
+
{
|
|
24945
|
+
style: btn,
|
|
24946
|
+
title: replayState.selecting ? "Cancel selection" : "Exit replay",
|
|
24947
|
+
onClick: call((c) => replayState.selecting ? c.replayCancelSelection() : c.replayExit()),
|
|
24948
|
+
children: "\u2715"
|
|
24949
|
+
}
|
|
24950
|
+
)
|
|
24951
|
+
] });
|
|
24952
|
+
})(),
|
|
24460
24953
|
/* @__PURE__ */ jsx(
|
|
24461
24954
|
"div",
|
|
24462
24955
|
{
|
|
@@ -24544,7 +25037,7 @@ var ChartCanvas = forwardRef(
|
|
|
24544
25037
|
symbol,
|
|
24545
25038
|
timeframe,
|
|
24546
25039
|
bars,
|
|
24547
|
-
theme,
|
|
25040
|
+
theme: effectiveTheme,
|
|
24548
25041
|
indicators,
|
|
24549
25042
|
overlayColors: OVERLAY_COLORS,
|
|
24550
25043
|
onRemove: handleRemoveIndicator,
|
|
@@ -24986,6 +25479,7 @@ function LayoutMenu({
|
|
|
24986
25479
|
currentName,
|
|
24987
25480
|
currentLayoutId,
|
|
24988
25481
|
autoSave,
|
|
25482
|
+
saveState,
|
|
24989
25483
|
onFetchLayouts,
|
|
24990
25484
|
onSave,
|
|
24991
25485
|
onLoad,
|
|
@@ -25127,7 +25621,9 @@ function LayoutMenu({
|
|
|
25127
25621
|
] }),
|
|
25128
25622
|
currentName ?? "Layout",
|
|
25129
25623
|
" \u25BE",
|
|
25130
|
-
|
|
25624
|
+
saveState === "saving" && /* @__PURE__ */ jsx("span", { style: { fontSize: 10.5, opacity: 0.75, fontStyle: "italic" }, children: "Saving\u2026" }),
|
|
25625
|
+
saveState === "saved" && /* @__PURE__ */ jsx("span", { style: { fontSize: 10.5, color: "var(--up)" }, children: "Saved \u2713" }),
|
|
25626
|
+
saveState !== "saving" && saveState !== "saved" && autoSave && isSaved && /* @__PURE__ */ jsx("span", { className: "layout-autosave-dot", title: "Auto-save enabled" })
|
|
25131
25627
|
]
|
|
25132
25628
|
}
|
|
25133
25629
|
),
|
|
@@ -26500,28 +26996,32 @@ function RollRuleDropdown({
|
|
|
26500
26996
|
))
|
|
26501
26997
|
] });
|
|
26502
26998
|
}
|
|
26503
|
-
var
|
|
26504
|
-
{
|
|
26505
|
-
/* @__PURE__ */
|
|
26506
|
-
|
|
26507
|
-
|
|
26508
|
-
|
|
26509
|
-
|
|
26510
|
-
|
|
26511
|
-
|
|
26512
|
-
|
|
26513
|
-
|
|
26514
|
-
|
|
26515
|
-
|
|
26516
|
-
|
|
26517
|
-
|
|
26518
|
-
|
|
26519
|
-
|
|
26520
|
-
|
|
26521
|
-
/* @__PURE__ */ jsx("
|
|
26522
|
-
/* @__PURE__ */
|
|
26523
|
-
|
|
26999
|
+
var SERIES_STYLE_GROUPS = [
|
|
27000
|
+
{ label: "Bars", items: [
|
|
27001
|
+
{ id: "bar", label: "Bars", icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", stroke: "currentColor", strokeWidth: "1.4", fill: "none", children: [
|
|
27002
|
+
/* @__PURE__ */ jsx("line", { x1: "5", y1: "2", x2: "5", y2: "13" }),
|
|
27003
|
+
/* @__PURE__ */ jsx("line", { x1: "3", y1: "5", x2: "5", y2: "5" }),
|
|
27004
|
+
/* @__PURE__ */ jsx("line", { x1: "5", y1: "10", x2: "7", y2: "10" }),
|
|
27005
|
+
/* @__PURE__ */ jsx("line", { x1: "11", y1: "4", x2: "11", y2: "14" }),
|
|
27006
|
+
/* @__PURE__ */ jsx("line", { x1: "9", y1: "7", x2: "11", y2: "7" }),
|
|
27007
|
+
/* @__PURE__ */ jsx("line", { x1: "11", y1: "12", x2: "13", y2: "12" })
|
|
27008
|
+
] }) },
|
|
27009
|
+
{ id: "candlestick", label: "Candles", icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "currentColor", children: [
|
|
27010
|
+
/* @__PURE__ */ jsx("rect", { x: "3", y: "5", width: "3", height: "6", rx: "0.5" }),
|
|
27011
|
+
/* @__PURE__ */ jsx("line", { x1: "4.5", y1: "2", x2: "4.5", y2: "14", stroke: "currentColor", strokeWidth: "1" }),
|
|
27012
|
+
/* @__PURE__ */ jsx("rect", { x: "10", y: "3", width: "3", height: "7", rx: "0.5" }),
|
|
27013
|
+
/* @__PURE__ */ jsx("line", { x1: "11.5", y1: "1", x2: "11.5", y2: "13", stroke: "currentColor", strokeWidth: "1" })
|
|
27014
|
+
] }) }
|
|
27015
|
+
] },
|
|
27016
|
+
{ label: "Lines", items: [
|
|
27017
|
+
{ id: "line", label: "Line", icon: /* @__PURE__ */ jsx("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", children: /* @__PURE__ */ jsx("polyline", { points: "2,12 6,7 10,10 14,4" }) }) },
|
|
27018
|
+
{ id: "area", label: "Area", icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", children: [
|
|
27019
|
+
/* @__PURE__ */ jsx("polyline", { points: "2,12 6,7 10,10 14,4", fill: "none", stroke: "currentColor", strokeWidth: "1.4" }),
|
|
27020
|
+
/* @__PURE__ */ jsx("polygon", { points: "2,12 6,7 10,10 14,4 14,14 2,14", fill: "currentColor", opacity: "0.35" })
|
|
27021
|
+
] }) }
|
|
27022
|
+
] }
|
|
26524
27023
|
];
|
|
27024
|
+
var ALL_SERIES_STYLES = SERIES_STYLE_GROUPS.flatMap((g) => g.items);
|
|
26525
27025
|
function TopToolbar({
|
|
26526
27026
|
symbol,
|
|
26527
27027
|
timeframe,
|
|
@@ -26535,6 +27035,9 @@ function TopToolbar({
|
|
|
26535
27035
|
onFavoritesChange,
|
|
26536
27036
|
seriesType,
|
|
26537
27037
|
onSeriesTypeChange,
|
|
27038
|
+
onToggleReplay,
|
|
27039
|
+
onToggleAlerts,
|
|
27040
|
+
alertsOpen,
|
|
26538
27041
|
onAddIndicator,
|
|
26539
27042
|
maxIndicators,
|
|
26540
27043
|
indicatorsOpen,
|
|
@@ -26572,7 +27075,8 @@ function TopToolbar({
|
|
|
26572
27075
|
showSavedLayouts,
|
|
26573
27076
|
showCustomTimeframes,
|
|
26574
27077
|
showDataExport,
|
|
26575
|
-
showMultiChartLayout
|
|
27078
|
+
showMultiChartLayout,
|
|
27079
|
+
layoutSaveState
|
|
26576
27080
|
}) {
|
|
26577
27081
|
const [tfOpen, setTfOpen] = useState(false);
|
|
26578
27082
|
const [styleOpen, setStyleOpen] = useState(false);
|
|
@@ -26730,27 +27234,60 @@ function TopToolbar({
|
|
|
26730
27234
|
onClick: () => setStyleOpen((o) => !o),
|
|
26731
27235
|
title: "Chart style",
|
|
26732
27236
|
children: [
|
|
26733
|
-
(
|
|
26734
|
-
(
|
|
27237
|
+
(ALL_SERIES_STYLES.find((s) => s.id === seriesType) ?? ALL_SERIES_STYLES[1]).icon,
|
|
27238
|
+
(ALL_SERIES_STYLES.find((s) => s.id === seriesType) ?? ALL_SERIES_STYLES[1]).label
|
|
26735
27239
|
]
|
|
26736
27240
|
}
|
|
26737
27241
|
),
|
|
26738
|
-
styleOpen && /* @__PURE__ */ jsx("div", { className: "tf-dropdown", style: { minWidth:
|
|
26739
|
-
"
|
|
26740
|
-
|
|
26741
|
-
|
|
26742
|
-
|
|
26743
|
-
|
|
26744
|
-
|
|
26745
|
-
|
|
27242
|
+
styleOpen && /* @__PURE__ */ jsx("div", { className: "tf-dropdown", style: { minWidth: 165 }, children: SERIES_STYLE_GROUPS.map((group) => /* @__PURE__ */ jsxs("div", { children: [
|
|
27243
|
+
/* @__PURE__ */ jsx("div", { style: {
|
|
27244
|
+
padding: "6px 12px 3px",
|
|
27245
|
+
fontSize: 10.5,
|
|
27246
|
+
fontWeight: 600,
|
|
27247
|
+
letterSpacing: 1,
|
|
27248
|
+
textTransform: "uppercase",
|
|
27249
|
+
opacity: 0.55
|
|
27250
|
+
}, children: group.label }),
|
|
27251
|
+
group.items.map((style) => /* @__PURE__ */ jsxs(
|
|
27252
|
+
"button",
|
|
27253
|
+
{
|
|
27254
|
+
className: "tf-dropdown-row",
|
|
27255
|
+
style: style.id === seriesType ? { fontWeight: 600 } : void 0,
|
|
27256
|
+
onClick: () => {
|
|
27257
|
+
onSeriesTypeChange(style.id);
|
|
27258
|
+
setStyleOpen(false);
|
|
27259
|
+
},
|
|
27260
|
+
children: [
|
|
27261
|
+
/* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 8 }, children: [
|
|
27262
|
+
style.icon,
|
|
27263
|
+
style.label
|
|
27264
|
+
] }),
|
|
27265
|
+
style.id === seriesType && /* @__PURE__ */ jsx("span", { "aria-hidden": true, style: { opacity: 0.9 }, children: "\u2713" })
|
|
27266
|
+
]
|
|
26746
27267
|
},
|
|
26747
|
-
|
|
26748
|
-
|
|
26749
|
-
|
|
26750
|
-
|
|
26751
|
-
|
|
26752
|
-
|
|
26753
|
-
|
|
27268
|
+
style.id
|
|
27269
|
+
))
|
|
27270
|
+
] }, group.label)) })
|
|
27271
|
+
] })
|
|
27272
|
+
] }),
|
|
27273
|
+
onToggleAlerts !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
27274
|
+
/* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
|
|
27275
|
+
/* @__PURE__ */ jsxs("button", { className: `ind-trigger${alertsOpen ? " active" : ""}`, onClick: onToggleAlerts, title: "Price alerts", children: [
|
|
27276
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
27277
|
+
/* @__PURE__ */ jsx("path", { d: "M8 2.5a4 4 0 0 0-4 4v2.5l-1.4 2.3a.5.5 0 0 0 .43.75h9.94a.5.5 0 0 0 .43-.75L12 9V6.5a4 4 0 0 0-4-4z" }),
|
|
27278
|
+
/* @__PURE__ */ jsx("path", { d: "M6.6 12.5a1.5 1.5 0 0 0 2.8 0" })
|
|
27279
|
+
] }),
|
|
27280
|
+
"Alerts"
|
|
27281
|
+
] })
|
|
27282
|
+
] }),
|
|
27283
|
+
onToggleReplay !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
27284
|
+
/* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
|
|
27285
|
+
/* @__PURE__ */ jsxs("button", { className: "ind-trigger", onClick: onToggleReplay, title: "Bar replay", children: [
|
|
27286
|
+
/* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
27287
|
+
/* @__PURE__ */ jsx("polygon", { points: "6,4 12,8 6,12", fill: "currentColor", stroke: "none" }),
|
|
27288
|
+
/* @__PURE__ */ jsx("line", { x1: "3", y1: "3", x2: "3", y2: "13" })
|
|
27289
|
+
] }),
|
|
27290
|
+
"Replay"
|
|
26754
27291
|
] })
|
|
26755
27292
|
] }),
|
|
26756
27293
|
/* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
|
|
@@ -26819,6 +27356,7 @@ function TopToolbar({
|
|
|
26819
27356
|
currentName: currentLayoutName,
|
|
26820
27357
|
currentLayoutId,
|
|
26821
27358
|
autoSave,
|
|
27359
|
+
...layoutSaveState !== void 0 ? { saveState: layoutSaveState } : {},
|
|
26822
27360
|
onFetchLayouts,
|
|
26823
27361
|
onSave: onSaveLayout,
|
|
26824
27362
|
onLoad: onLoadLayout,
|
|
@@ -31571,6 +32109,194 @@ function WatchlistDrawer({ onClose, onSelectSymbol, symbolResolver, apiUrl, getA
|
|
|
31571
32109
|
}
|
|
31572
32110
|
);
|
|
31573
32111
|
}
|
|
32112
|
+
function AlertsDrawer({ apiUrl, getAuthToken, symbol, onClose }) {
|
|
32113
|
+
const [alerts, setAlerts] = useState(null);
|
|
32114
|
+
const [error, setError] = useState("");
|
|
32115
|
+
const [busy, setBusy] = useState(false);
|
|
32116
|
+
const [formSymbol, setFormSymbol] = useState(symbol);
|
|
32117
|
+
const [condition, setCondition] = useState("above");
|
|
32118
|
+
const [priceStr, setPriceStr] = useState("");
|
|
32119
|
+
useEffect(() => {
|
|
32120
|
+
setFormSymbol(symbol);
|
|
32121
|
+
}, [symbol]);
|
|
32122
|
+
const call = useCallback(async (path, init) => {
|
|
32123
|
+
const token = getAuthToken ? await getAuthToken() : "";
|
|
32124
|
+
return fetch(`${apiUrl.replace(/\/$/, "")}/api/alerts${path}`, {
|
|
32125
|
+
...init,
|
|
32126
|
+
headers: {
|
|
32127
|
+
"Content-Type": "application/json",
|
|
32128
|
+
...token ? { Authorization: `Bearer ${token}` } : {},
|
|
32129
|
+
...init?.headers ?? {}
|
|
32130
|
+
}
|
|
32131
|
+
});
|
|
32132
|
+
}, [apiUrl, getAuthToken]);
|
|
32133
|
+
const load = useCallback(() => {
|
|
32134
|
+
call("").then(async (r) => {
|
|
32135
|
+
if (!r.ok) throw new Error(`HTTP ${r.status}`);
|
|
32136
|
+
setAlerts(await r.json());
|
|
32137
|
+
}).catch((e) => setError(`Failed to load alerts: ${e instanceof Error ? e.message : e}`));
|
|
32138
|
+
}, [call]);
|
|
32139
|
+
useEffect(() => {
|
|
32140
|
+
load();
|
|
32141
|
+
const t = setInterval(load, 15e3);
|
|
32142
|
+
return () => clearInterval(t);
|
|
32143
|
+
}, [load]);
|
|
32144
|
+
async function create() {
|
|
32145
|
+
const price = parseFloat(priceStr);
|
|
32146
|
+
if (!(price > 0) || !formSymbol.trim()) return;
|
|
32147
|
+
setBusy(true);
|
|
32148
|
+
setError("");
|
|
32149
|
+
try {
|
|
32150
|
+
const r = await call("", {
|
|
32151
|
+
method: "POST",
|
|
32152
|
+
body: JSON.stringify({ symbol: formSymbol.trim(), condition, price })
|
|
32153
|
+
});
|
|
32154
|
+
const d = await r.json().catch(() => ({}));
|
|
32155
|
+
if (!r.ok) throw new Error(d.error ?? `HTTP ${r.status}`);
|
|
32156
|
+
setPriceStr("");
|
|
32157
|
+
load();
|
|
32158
|
+
} catch (e) {
|
|
32159
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
32160
|
+
} finally {
|
|
32161
|
+
setBusy(false);
|
|
32162
|
+
}
|
|
32163
|
+
}
|
|
32164
|
+
async function remove(id) {
|
|
32165
|
+
try {
|
|
32166
|
+
await call(`/${encodeURIComponent(id)}`, { method: "DELETE" });
|
|
32167
|
+
setAlerts((prev) => prev?.filter((a) => a.id !== id) ?? null);
|
|
32168
|
+
} catch {
|
|
32169
|
+
}
|
|
32170
|
+
}
|
|
32171
|
+
return /* @__PURE__ */ jsxs("div", { style: {
|
|
32172
|
+
width: 280,
|
|
32173
|
+
flexShrink: 0,
|
|
32174
|
+
display: "flex",
|
|
32175
|
+
flexDirection: "column",
|
|
32176
|
+
background: "var(--surface)",
|
|
32177
|
+
border: "1px solid var(--border)",
|
|
32178
|
+
borderRadius: 8,
|
|
32179
|
+
overflow: "hidden",
|
|
32180
|
+
color: "var(--text)"
|
|
32181
|
+
}, children: [
|
|
32182
|
+
/* @__PURE__ */ jsxs("div", { style: {
|
|
32183
|
+
display: "flex",
|
|
32184
|
+
alignItems: "center",
|
|
32185
|
+
justifyContent: "space-between",
|
|
32186
|
+
padding: "10px 14px",
|
|
32187
|
+
borderBottom: "1px solid var(--border)"
|
|
32188
|
+
}, children: [
|
|
32189
|
+
/* @__PURE__ */ jsx("strong", { style: { fontSize: 13 }, children: "Alerts" }),
|
|
32190
|
+
/* @__PURE__ */ jsx(
|
|
32191
|
+
"button",
|
|
32192
|
+
{
|
|
32193
|
+
onClick: onClose,
|
|
32194
|
+
title: "Close",
|
|
32195
|
+
style: { background: "none", border: "none", color: "var(--text-muted)", cursor: "pointer", fontSize: 14 },
|
|
32196
|
+
children: "\u2715"
|
|
32197
|
+
}
|
|
32198
|
+
)
|
|
32199
|
+
] }),
|
|
32200
|
+
/* @__PURE__ */ jsxs("div", { style: { padding: "10px 14px", borderBottom: "1px solid var(--border)", display: "flex", flexDirection: "column", gap: 8 }, children: [
|
|
32201
|
+
/* @__PURE__ */ jsx(
|
|
32202
|
+
"input",
|
|
32203
|
+
{
|
|
32204
|
+
value: formSymbol,
|
|
32205
|
+
onChange: (e) => setFormSymbol(e.target.value),
|
|
32206
|
+
placeholder: "Symbol",
|
|
32207
|
+
style: { fontSize: 12.5, padding: "5px 8px", background: "var(--surface-2)", color: "var(--text)", border: "1px solid var(--border)", borderRadius: 6 }
|
|
32208
|
+
}
|
|
32209
|
+
),
|
|
32210
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
|
|
32211
|
+
/* @__PURE__ */ jsxs(
|
|
32212
|
+
"select",
|
|
32213
|
+
{
|
|
32214
|
+
value: condition,
|
|
32215
|
+
onChange: (e) => setCondition(e.target.value),
|
|
32216
|
+
style: { fontSize: 12.5, padding: "5px 6px", background: "var(--surface-2)", color: "var(--text)", border: "1px solid var(--border)", borderRadius: 6 },
|
|
32217
|
+
children: [
|
|
32218
|
+
/* @__PURE__ */ jsx("option", { value: "above", children: "Crosses above" }),
|
|
32219
|
+
/* @__PURE__ */ jsx("option", { value: "below", children: "Crosses below" })
|
|
32220
|
+
]
|
|
32221
|
+
}
|
|
32222
|
+
),
|
|
32223
|
+
/* @__PURE__ */ jsx(
|
|
32224
|
+
"input",
|
|
32225
|
+
{
|
|
32226
|
+
value: priceStr,
|
|
32227
|
+
onChange: (e) => setPriceStr(e.target.value.replace(/[^0-9.]/g, "")),
|
|
32228
|
+
placeholder: "Price",
|
|
32229
|
+
inputMode: "decimal",
|
|
32230
|
+
style: { flex: 1, minWidth: 0, fontSize: 12.5, padding: "5px 8px", background: "var(--surface-2)", color: "var(--text)", border: "1px solid var(--border)", borderRadius: 6, fontVariantNumeric: "tabular-nums" },
|
|
32231
|
+
onKeyDown: (e) => e.key === "Enter" && !busy && create()
|
|
32232
|
+
}
|
|
32233
|
+
)
|
|
32234
|
+
] }),
|
|
32235
|
+
/* @__PURE__ */ jsx(
|
|
32236
|
+
"button",
|
|
32237
|
+
{
|
|
32238
|
+
onClick: create,
|
|
32239
|
+
disabled: busy || !(parseFloat(priceStr) > 0),
|
|
32240
|
+
style: {
|
|
32241
|
+
fontSize: 12.5,
|
|
32242
|
+
padding: "6px 0",
|
|
32243
|
+
borderRadius: 6,
|
|
32244
|
+
border: "none",
|
|
32245
|
+
cursor: "pointer",
|
|
32246
|
+
background: "var(--primary, #5b5bd6)",
|
|
32247
|
+
color: "#fff",
|
|
32248
|
+
opacity: busy || !(parseFloat(priceStr) > 0) ? 0.55 : 1
|
|
32249
|
+
},
|
|
32250
|
+
children: busy ? "Creating\u2026" : "Create alert"
|
|
32251
|
+
}
|
|
32252
|
+
),
|
|
32253
|
+
error && /* @__PURE__ */ jsx("div", { style: { fontSize: 11.5, color: "var(--down, #e5484d)" }, children: error })
|
|
32254
|
+
] }),
|
|
32255
|
+
/* @__PURE__ */ jsxs("div", { style: { flex: 1, overflowY: "auto" }, children: [
|
|
32256
|
+
alerts === null && !error && /* @__PURE__ */ jsx("div", { style: { padding: 14, fontSize: 12.5, color: "var(--text-muted)" }, children: "Loading\u2026" }),
|
|
32257
|
+
alerts !== null && alerts.length === 0 && /* @__PURE__ */ jsx("div", { style: { padding: 14, fontSize: 12.5, color: "var(--text-muted)" }, children: "No alerts yet. They fire server-side and email you \u2014 the chart doesn't need to stay open." }),
|
|
32258
|
+
alerts?.map((a) => /* @__PURE__ */ jsxs("div", { style: {
|
|
32259
|
+
display: "flex",
|
|
32260
|
+
alignItems: "center",
|
|
32261
|
+
gap: 8,
|
|
32262
|
+
padding: "8px 14px",
|
|
32263
|
+
borderBottom: "1px solid var(--border)",
|
|
32264
|
+
fontSize: 12.5,
|
|
32265
|
+
opacity: a.status === "triggered" ? 0.75 : 1
|
|
32266
|
+
}, children: [
|
|
32267
|
+
/* @__PURE__ */ jsx("span", { style: {
|
|
32268
|
+
width: 7,
|
|
32269
|
+
height: 7,
|
|
32270
|
+
borderRadius: "50%",
|
|
32271
|
+
flexShrink: 0,
|
|
32272
|
+
background: a.status === "active" ? "var(--up, #22a06b)" : "var(--text-muted)"
|
|
32273
|
+
}, title: a.status }),
|
|
32274
|
+
/* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
|
|
32275
|
+
/* @__PURE__ */ jsxs("div", { style: { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: [
|
|
32276
|
+
/* @__PURE__ */ jsx("strong", { children: a.symbol }),
|
|
32277
|
+
" ",
|
|
32278
|
+
a.condition === "above" ? "\u2265" : "\u2264",
|
|
32279
|
+
" ",
|
|
32280
|
+
/* @__PURE__ */ jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: a.price })
|
|
32281
|
+
] }),
|
|
32282
|
+
a.status === "triggered" && /* @__PURE__ */ jsxs("div", { style: { fontSize: 11, color: "var(--text-muted)" }, children: [
|
|
32283
|
+
"fired at ",
|
|
32284
|
+
a.trigger_price
|
|
32285
|
+
] })
|
|
32286
|
+
] }),
|
|
32287
|
+
/* @__PURE__ */ jsx(
|
|
32288
|
+
"button",
|
|
32289
|
+
{
|
|
32290
|
+
onClick: () => remove(a.id),
|
|
32291
|
+
title: "Remove alert",
|
|
32292
|
+
style: { background: "none", border: "none", color: "var(--text-muted)", cursor: "pointer", fontSize: 13 },
|
|
32293
|
+
children: "\u2715"
|
|
32294
|
+
}
|
|
32295
|
+
)
|
|
32296
|
+
] }, a.id))
|
|
32297
|
+
] })
|
|
32298
|
+
] });
|
|
32299
|
+
}
|
|
31574
32300
|
function ChartWorkspace({
|
|
31575
32301
|
// TabBar
|
|
31576
32302
|
tabs,
|
|
@@ -31621,6 +32347,9 @@ function ChartWorkspace({
|
|
|
31621
32347
|
seriesType,
|
|
31622
32348
|
onSeriesTypeChange,
|
|
31623
32349
|
showSeriesTypes,
|
|
32350
|
+
onToggleReplay,
|
|
32351
|
+
showReplay,
|
|
32352
|
+
layoutSaveState,
|
|
31624
32353
|
extraTfGroups,
|
|
31625
32354
|
// RightToolbar
|
|
31626
32355
|
watchlistOpen,
|
|
@@ -31731,6 +32460,9 @@ function ChartWorkspace({
|
|
|
31731
32460
|
const effShowMultiChartLayout = gate(showMultiChartLayout, capabilities.multiChartLayout);
|
|
31732
32461
|
const effShowMultipleTabs = gate(showMultipleTabs, capabilities.multipleWorkspaces);
|
|
31733
32462
|
const effShowSeriesTypes = gate(showSeriesTypes, capabilities.chartTypes) && onSeriesTypeChange !== void 0;
|
|
32463
|
+
const effShowReplay = gate(showReplay, capabilities.barReplay) && onToggleReplay !== void 0;
|
|
32464
|
+
const effShowAlerts = gate(void 0, capabilities.alerts) && hostApiUrl !== void 0;
|
|
32465
|
+
const [alertsOpen, setAlertsOpen] = React11.useState(false);
|
|
31734
32466
|
const tabItems = tabs.map((t) => ({
|
|
31735
32467
|
id: t.id,
|
|
31736
32468
|
label: t.label,
|
|
@@ -31809,6 +32541,8 @@ function ChartWorkspace({
|
|
|
31809
32541
|
onSymbolChange,
|
|
31810
32542
|
onTimeframeChange,
|
|
31811
32543
|
...effShowSeriesTypes ? { seriesType: seriesType ?? "candlestick", onSeriesTypeChange } : {},
|
|
32544
|
+
...effShowReplay ? { onToggleReplay } : {},
|
|
32545
|
+
...effShowAlerts ? { onToggleAlerts: () => setAlertsOpen((o) => !o), alertsOpen } : {},
|
|
31812
32546
|
onAddCustomTimeframe,
|
|
31813
32547
|
onRemoveCustomTimeframe,
|
|
31814
32548
|
onFavoritesChange: onFavoriteTfsChange,
|
|
@@ -31824,6 +32558,7 @@ function ChartWorkspace({
|
|
|
31824
32558
|
currentLayoutName,
|
|
31825
32559
|
currentLayoutId,
|
|
31826
32560
|
autoSave,
|
|
32561
|
+
...layoutSaveState !== void 0 ? { layoutSaveState } : {},
|
|
31827
32562
|
onFetchLayouts,
|
|
31828
32563
|
onSaveLayout,
|
|
31829
32564
|
onLoadLayout,
|
|
@@ -31855,6 +32590,15 @@ function ChartWorkspace({
|
|
|
31855
32590
|
/* @__PURE__ */ jsx("div", { style: { flex: 1, display: "flex", gap: 4, minHeight: 0, overflow: "hidden" }, children: /* @__PURE__ */ jsxs("div", { style: { position: "relative", flex: 1, display: "flex", gap: 4, minWidth: 0, minHeight: 0 }, children: [
|
|
31856
32591
|
(dataBlocked || isLicensed === false) && /* @__PURE__ */ jsx(LicenseRequiredOverlay, {}),
|
|
31857
32592
|
/* @__PURE__ */ jsx("div", { style: { position: "relative", flex: 1, minWidth: 0, minHeight: 0 }, children: chartSlots }),
|
|
32593
|
+
effShowAlerts && alertsOpen && /* @__PURE__ */ jsx(
|
|
32594
|
+
AlertsDrawer,
|
|
32595
|
+
{
|
|
32596
|
+
apiUrl: hostApiUrl,
|
|
32597
|
+
getAuthToken,
|
|
32598
|
+
symbol: activeSymbol ?? "",
|
|
32599
|
+
onClose: () => setAlertsOpen(false)
|
|
32600
|
+
}
|
|
32601
|
+
),
|
|
31858
32602
|
autoTrading && !onToggleOrderEntry && effOrderEntryOpen && capabilities.orderEntry && tradingBridge && /* @__PURE__ */ jsx(
|
|
31859
32603
|
OrderTicket,
|
|
31860
32604
|
{
|
|
@@ -35258,22 +36002,6 @@ var ManagedAppShell = forwardRef(
|
|
|
35258
36002
|
const maxBars = data?.package?.maxHistoricalBars ?? null;
|
|
35259
36003
|
const df = datafeed;
|
|
35260
36004
|
df.setMaxHistoricalBars?.(maxBars);
|
|
35261
|
-
if (data?.features && typeof data.features === "object") {
|
|
35262
|
-
const lm = LicenseManager.getInstance();
|
|
35263
|
-
const current = lm.getFeatures();
|
|
35264
|
-
const merged = { ...current };
|
|
35265
|
-
for (const [key, val] of Object.entries(data.features)) {
|
|
35266
|
-
if (val === true) {
|
|
35267
|
-
merged[key] = true;
|
|
35268
|
-
}
|
|
35269
|
-
}
|
|
35270
|
-
const payload = lm.getLicense();
|
|
35271
|
-
if (payload) {
|
|
35272
|
-
lm.loadLicense({ ...payload, features: merged });
|
|
35273
|
-
} else {
|
|
35274
|
-
lm.loadLicense({ licenseKey: "", mode: "unmanaged", features: merged });
|
|
35275
|
-
}
|
|
35276
|
-
}
|
|
35277
36005
|
}
|
|
35278
36006
|
}
|
|
35279
36007
|
if (plansRes.ok) {
|
|
@@ -36623,13 +37351,14 @@ var ManagedAppShell = forwardRef(
|
|
|
36623
37351
|
}
|
|
36624
37352
|
),
|
|
36625
37353
|
scriptDrawerOpen && capabilities.forgeScript && (renderScriptDrawer ? renderScriptDrawer({ onClose: () => setScriptDrawerOpen(false), onAddIndicator: handleAddIndicator }) : /* @__PURE__ */ jsx(ScriptDrawer, { onClose: () => setScriptDrawerOpen(false), onAddIndicator: handleAddIndicator, ...apiUrl !== void 0 ? { apiUrl } : {}, ...getAuthToken ? { getAuthToken } : {} })),
|
|
36626
|
-
(orderPanelOpen || connectedSession?.disconnected) && connectedSession && capabilities.renderManagedTradingControls && /* @__PURE__ */ jsx(
|
|
37354
|
+
(orderPanelOpen || connectedSession?.disconnected) && connectedSession && capabilities.renderManagedTradingControls && capabilities.orderEntry && /* @__PURE__ */ jsx(
|
|
36627
37355
|
OrderEntryPanel,
|
|
36628
37356
|
{
|
|
36629
37357
|
sessionId: connectedSession.sessionId,
|
|
36630
37358
|
brokerSlug: connectedSession.brokerSlug,
|
|
36631
37359
|
accounts: connectedSession.accounts,
|
|
36632
37360
|
selectedAccountId,
|
|
37361
|
+
showTrailingStop: capabilities.trailingStops,
|
|
36633
37362
|
defaultSymbol: activeTab.symbol,
|
|
36634
37363
|
apiUrl,
|
|
36635
37364
|
getAuthToken,
|
|
@@ -36817,7 +37546,7 @@ var ManagedAppShell = forwardRef(
|
|
|
36817
37546
|
) : null;
|
|
36818
37547
|
const drawers = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
36819
37548
|
watchlistOpen && capabilities.watchlists && (renderWatchlistDrawer ? renderWatchlistDrawer({ onClose: () => setWatchlistOpen(false), onSelectSymbol: handleSymbolChange }) : /* @__PURE__ */ jsx(WatchlistDrawer, { onClose: () => setWatchlistOpen(false), onSelectSymbol: handleSymbolChange, symbolResolver })),
|
|
36820
|
-
tradeDrawerOpen && capabilities.renderManagedTradingControls && (renderTradeDrawer ? renderTradeDrawer({ onClose: () => setTradeDrawerOpen(false) }) : /* @__PURE__ */ jsx(
|
|
37549
|
+
tradeDrawerOpen && capabilities.renderManagedTradingControls && capabilities.orderEntry && (renderTradeDrawer ? renderTradeDrawer({ onClose: () => setTradeDrawerOpen(false) }) : /* @__PURE__ */ jsx(
|
|
36821
37550
|
TradeDrawer,
|
|
36822
37551
|
{
|
|
36823
37552
|
onClose: () => setTradeDrawerOpen(false),
|
|
@@ -36981,6 +37710,8 @@ var ManagedAppShell = forwardRef(
|
|
|
36981
37710
|
onTimeframeChange: handleTimeframeChange,
|
|
36982
37711
|
seriesType,
|
|
36983
37712
|
onSeriesTypeChange: handleSeriesTypeChange,
|
|
37713
|
+
onToggleReplay: () => chartHandleRef.current?.toggleReplay(),
|
|
37714
|
+
...apiUrl !== void 0 ? { hostApiUrl: apiUrl } : {},
|
|
36984
37715
|
onAddCustomTimeframe: handleAddCustomTimeframe,
|
|
36985
37716
|
onRemoveCustomTimeframe: handleRemoveCustomTimeframe,
|
|
36986
37717
|
onFavoriteTfsChange: setFavoriteTfs,
|
|
@@ -37004,7 +37735,7 @@ var ManagedAppShell = forwardRef(
|
|
|
37004
37735
|
onDeleteLayout: handleDeleteLayout,
|
|
37005
37736
|
onOpenLayoutInNewTab: handleOpenLayoutInNewTab,
|
|
37006
37737
|
symbolResolver,
|
|
37007
|
-
showTradeButton: capabilities.renderManagedTradingControls,
|
|
37738
|
+
showTradeButton: capabilities.renderManagedTradingControls && capabilities.orderEntry,
|
|
37008
37739
|
tradeDrawerOpen: tradeDrawerOpen || orderPanelOpen,
|
|
37009
37740
|
onToggleTradeDrawer: () => {
|
|
37010
37741
|
if (orderPanelOpen && !tradeDrawerOpen) {
|
|
@@ -37019,7 +37750,7 @@ var ManagedAppShell = forwardRef(
|
|
|
37019
37750
|
onToggleWatchlist: () => setWatchlistOpen((o) => !o),
|
|
37020
37751
|
orderEntryOpen: orderPanelOpen,
|
|
37021
37752
|
onToggleOrderEntry: handleToggleOrderEntry,
|
|
37022
|
-
showOrderEntry: capabilities.renderManagedTradingControls,
|
|
37753
|
+
showOrderEntry: capabilities.renderManagedTradingControls && capabilities.orderEntry,
|
|
37023
37754
|
showAgent: agentEnabled,
|
|
37024
37755
|
agentOpen: agentPanelOpen,
|
|
37025
37756
|
onToggleAgent: () => setAgentPanelOpen((o) => !o),
|