@forgecharts/sdk 1.5.82 → 1.5.83

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.
Files changed (53) hide show
  1. package/dist/api/EventBus.d.ts +1 -1
  2. package/dist/api/{TChart.d.ts → ForgeCharts.d.ts} +38 -4
  3. package/dist/api/ForgeCharts.d.ts.map +1 -0
  4. package/dist/api/createChart.d.ts +4 -4
  5. package/dist/api/createChart.d.ts.map +1 -1
  6. package/dist/core/Chart.d.ts +5 -5
  7. package/dist/core/Chart.d.ts.map +1 -1
  8. package/dist/core/Series.d.ts +14 -0
  9. package/dist/core/Series.d.ts.map +1 -1
  10. package/dist/core/__tests__/seriesCutoff.test.d.ts +2 -0
  11. package/dist/core/__tests__/seriesCutoff.test.d.ts.map +1 -0
  12. package/dist/datafeed/DatafeedConnector.d.ts +1 -1
  13. package/dist/index.d.ts +3 -1
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +432 -27
  16. package/dist/index.js.map +1 -1
  17. package/dist/internal.d.ts +1 -1
  18. package/dist/internal.js +432 -27
  19. package/dist/internal.js.map +1 -1
  20. package/dist/licensing/packageFeatures.d.ts.map +1 -1
  21. package/dist/node.d.ts +1 -1
  22. package/dist/node.d.ts.map +1 -1
  23. package/dist/pixi/PixiCandlestickRenderer.d.ts +4 -0
  24. package/dist/pixi/PixiCandlestickRenderer.d.ts.map +1 -1
  25. package/dist/pixi/PixiChart.d.ts +12 -2
  26. package/dist/pixi/PixiChart.d.ts.map +1 -1
  27. package/dist/pixi/PixiHistogramRenderer.d.ts +2 -0
  28. package/dist/pixi/PixiHistogramRenderer.d.ts.map +1 -1
  29. package/dist/pixi/PixiLineRenderer.d.ts +2 -0
  30. package/dist/pixi/PixiLineRenderer.d.ts.map +1 -1
  31. package/dist/react/canvas/ChartCanvas.d.ts +7 -2
  32. package/dist/react/canvas/ChartCanvas.d.ts.map +1 -1
  33. package/dist/react/hooks/useUserPackage.d.ts.map +1 -1
  34. package/dist/react/index.js +719 -63
  35. package/dist/react/index.js.map +1 -1
  36. package/dist/react/internal.js +726 -83
  37. package/dist/react/internal.js.map +1 -1
  38. package/dist/react/shell/AlertsDrawer.d.ts +11 -0
  39. package/dist/react/shell/AlertsDrawer.d.ts.map +1 -0
  40. package/dist/react/shell/ManagedAppShell.d.ts +1 -1
  41. package/dist/react/shell/ManagedAppShell.d.ts.map +1 -1
  42. package/dist/react/workspace/ChartWorkspace.d.ts +10 -1
  43. package/dist/react/workspace/ChartWorkspace.d.ts.map +1 -1
  44. package/dist/react/workspace/LayoutMenu.d.ts +3 -1
  45. package/dist/react/workspace/LayoutMenu.d.ts.map +1 -1
  46. package/dist/react/workspace/toolbars/TopToolbar.d.ts +7 -1
  47. package/dist/react/workspace/toolbars/TopToolbar.d.ts.map +1 -1
  48. package/dist/types/ChartBackend.d.ts +15 -4
  49. package/dist/types/ChartBackend.d.ts.map +1 -1
  50. package/dist/types/ISeries.d.ts +5 -0
  51. package/dist/types/ISeries.d.ts.map +1 -1
  52. package/package.json +1 -1
  53. package/dist/api/TChart.d.ts.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import React11, { createContext, forwardRef, useRef, useState, useEffect, useImperativeHandle, useCallback, useMemo, useContext, useReducer } from 'react';
2
- import { TextStyle, Application, Container, Graphics, Text, FillGradient, CanvasTextMetrics } from 'pixi.js';
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
- data() {
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 TChart for canvas rendering. */
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 TChart. */
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. TChart)
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 TChart. */
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 TChart. */
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 TChart. */
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 TChart can trigger lazy history loading. */
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; TChart uses DrawingManager) ─
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;
@@ -11596,6 +11729,8 @@ var resolver = () => ChartRuntimeResolver.getInstance();
11596
11729
  var canUseChartTypes = () => resolver().canUseChartTypes();
11597
11730
  var canUseExtendedDrawings = () => resolver().canUseExtendedDrawings();
11598
11731
  var canUseMultiPane = () => resolver().canUseMultiPane();
11732
+ var canUseBarReplay = () => resolver().canUseBarReplay();
11733
+ var canRenderOverlays = () => resolver().canRenderOverlays();
11599
11734
  var canUseDraggableOrders = () => resolver().canUseDraggableOrders();
11600
11735
  var canUseBracketOrders = () => resolver().canUseBracketOrders();
11601
11736
  var canUsePositionsOverlay = () => resolver().canUsePositionsOverlay();
@@ -11912,8 +12047,8 @@ var ManagedTradingController = class {
11912
12047
  }
11913
12048
  };
11914
12049
 
11915
- // src/api/TChart.ts
11916
- var TChart = class _TChart {
12050
+ // src/api/ForgeCharts.ts
12051
+ var ForgeCharts = class _ForgeCharts {
11917
12052
  _core;
11918
12053
  _bus;
11919
12054
  _indicators;
@@ -12304,7 +12439,7 @@ var TChart = class _TChart {
12304
12439
  "[ForgeCharts] ForgeScript indicators are not enabled on the active license. Contact your license provider to enable the forgeScript feature."
12305
12440
  );
12306
12441
  }
12307
- let finalConfig = _TChart.OVERLAY_ONLY.has(config.type) && config.overlay !== true ? { ...config, overlay: true } : config;
12442
+ let finalConfig = _ForgeCharts.OVERLAY_ONLY.has(config.type) && config.overlay !== true ? { ...config, overlay: true } : config;
12308
12443
  if (finalConfig.overlay !== true && !canUseMultiPane()) {
12309
12444
  console.warn("[ForgeCharts] multiPane is not enabled on the active license \u2014 indicator added as a price-pane overlay instead.");
12310
12445
  finalConfig = { ...finalConfig, overlay: true };
@@ -12482,7 +12617,7 @@ var TChart = class _TChart {
12482
12617
  */
12483
12618
  startDrawingTool(type) {
12484
12619
  this._assertAlive();
12485
- if (_TChart.EXTENDED_DRAWING_TOOLS.has(type) && !canUseExtendedDrawings()) {
12620
+ if (_ForgeCharts.EXTENDED_DRAWING_TOOLS.has(type) && !canUseExtendedDrawings()) {
12486
12621
  console.warn(`[ForgeCharts] Drawing tool '${type}' requires the extendedDrawings feature, which is not enabled on the active license.`);
12487
12622
  return;
12488
12623
  }
@@ -12520,6 +12655,14 @@ var TChart = class _TChart {
12520
12655
  /** Set contract switch roll markers for continuous futures. */
12521
12656
  setContractSwitches(switches) {
12522
12657
  this._assertAlive();
12658
+ if (!canRenderOverlays()) {
12659
+ if (switches.length > 0) {
12660
+ console.warn("[ForgeCharts] Overlay rendering (overlays) is not enabled on the active plan \u2014 contract switch markers suppressed.");
12661
+ }
12662
+ this._core.setContractSwitches([]);
12663
+ this._core.markDirty();
12664
+ return;
12665
+ }
12523
12666
  this._core.setContractSwitches(switches);
12524
12667
  this._core.markDirty();
12525
12668
  }
@@ -12563,7 +12706,7 @@ var TChart = class _TChart {
12563
12706
  const snap = this._drawings.all().map((d) => ({ ...d, points: [...d.points] }));
12564
12707
  this._drawHistory.splice(this._drawHistIdx + 1);
12565
12708
  this._drawHistory.push(snap);
12566
- if (this._drawHistory.length > _TChart._HIST_MAX) this._drawHistory.shift();
12709
+ if (this._drawHistory.length > _ForgeCharts._HIST_MAX) this._drawHistory.shift();
12567
12710
  this._drawHistIdx = this._drawHistory.length - 1;
12568
12711
  }
12569
12712
  /** Steps drawing state one edit back (Ctrl+Z). */
@@ -12928,6 +13071,158 @@ var TChart = class _TChart {
12928
13071
  }
12929
13072
  return this._unmanagedIngestion;
12930
13073
  }
13074
+ // ─── Bar replay ───────────────────────────────────────────────────────────────
13075
+ //
13076
+ // Replay is a display cutoff on the primary series (Series.setCutoffTime)
13077
+ // plus this controller moving it. The full tape keeps flowing underneath —
13078
+ // live bars land in fullData() and stay hidden until stepped over or the
13079
+ // replay exits — so no datafeed, indicator, or renderer knows replay
13080
+ // exists. Selection (the hover shadow of what a click would remove) is a
13081
+ // backend surface only Pixi implements; ForgeCharts refuses replay elsewhere.
13082
+ /** Playback speeds, in bars per second. */
13083
+ static REPLAY_SPEEDS = [0.5, 1, 2, 5, 10];
13084
+ _replay = {
13085
+ selecting: false,
13086
+ active: false,
13087
+ playing: false,
13088
+ speedIdx: 1,
13089
+ timer: null
13090
+ };
13091
+ _emitReplayState() {
13092
+ this._bus.emit("replayStateChanged", {
13093
+ selecting: this._replay.selecting,
13094
+ active: this._replay.active,
13095
+ playing: this._replay.playing,
13096
+ speed: _ForgeCharts.REPLAY_SPEEDS[this._replay.speedIdx],
13097
+ cutoffTime: this._series.cutoffTime()
13098
+ });
13099
+ }
13100
+ /** Current replay state — the control strip's render source. */
13101
+ replayState() {
13102
+ return {
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
+ /**
13111
+ * Enter replay-anchor selection: the cursor shadows everything a click
13112
+ * would remove; clicking commits that bucket as the replay head. Returns
13113
+ * false (with a warning) when the plan lacks bar_replay or the backend
13114
+ * has no selection surface.
13115
+ */
13116
+ replayEnterSelection() {
13117
+ this._assertAlive();
13118
+ if (!canUseBarReplay()) {
13119
+ console.warn("[ForgeCharts] Bar replay (bar_replay) is not enabled on the active plan.");
13120
+ return false;
13121
+ }
13122
+ if (!this._core.setReplaySelect) {
13123
+ console.warn("[ForgeCharts] Bar replay requires the Pixi render backend.");
13124
+ return false;
13125
+ }
13126
+ this._replayStopTimer();
13127
+ this._replay.selecting = true;
13128
+ this._replay.playing = false;
13129
+ this._core.setReplaySelect({
13130
+ onHover: () => {
13131
+ },
13132
+ onCommit: (time) => this._replayCommit(time)
13133
+ });
13134
+ this._emitReplayState();
13135
+ return true;
13136
+ }
13137
+ /** Leave selection without committing (existing replay, if any, survives). */
13138
+ replayCancelSelection() {
13139
+ this._assertAlive();
13140
+ if (!this._replay.selecting) return;
13141
+ this._replay.selecting = false;
13142
+ this._core.setReplaySelect?.(null);
13143
+ this._emitReplayState();
13144
+ }
13145
+ _replayCommit(time) {
13146
+ this._replay.selecting = false;
13147
+ this._core.setReplaySelect?.(null);
13148
+ this._series.setCutoffTime(time);
13149
+ this._replay.active = true;
13150
+ this._replay.playing = false;
13151
+ this._core.markDirty();
13152
+ this._emitReplayState();
13153
+ }
13154
+ replayPlay() {
13155
+ this._assertAlive();
13156
+ if (!this._replay.active || this._replay.playing) return;
13157
+ this._replay.playing = true;
13158
+ this._replayStartTimer();
13159
+ this._emitReplayState();
13160
+ }
13161
+ replayPause() {
13162
+ this._assertAlive();
13163
+ if (!this._replay.playing) return;
13164
+ this._replay.playing = false;
13165
+ this._replayStopTimer();
13166
+ this._emitReplayState();
13167
+ }
13168
+ /** Advance the replay head n bars. Pauses at the end of the tape. */
13169
+ replayStepForward(n = 1) {
13170
+ this._assertAlive();
13171
+ if (!this._replay.active) return;
13172
+ const full = this._series.fullData();
13173
+ const shown = this._series.data().length;
13174
+ const nextIdx = Math.min(shown + n, full.length) - 1;
13175
+ if (nextIdx < shown) {
13176
+ this.replayPause();
13177
+ return;
13178
+ }
13179
+ this._series.setCutoffTime(full[nextIdx].time);
13180
+ this._core.markDirty();
13181
+ if (nextIdx >= full.length - 1) this.replayPause();
13182
+ this._emitReplayState();
13183
+ }
13184
+ /** Rewind the replay head n bars back in time (default 10). */
13185
+ replayRewind(n = 10) {
13186
+ this._assertAlive();
13187
+ if (!this._replay.active) return;
13188
+ const full = this._series.fullData();
13189
+ const shown = this._series.data().length;
13190
+ const idx = Math.max(shown - 1 - n, 0);
13191
+ if (full.length === 0) return;
13192
+ this._series.setCutoffTime(full[idx].time);
13193
+ this._core.markDirty();
13194
+ this._emitReplayState();
13195
+ }
13196
+ /** Cycle to the next playback speed; restarts the timer when playing. */
13197
+ replayCycleSpeed() {
13198
+ this._assertAlive();
13199
+ this._replay.speedIdx = (this._replay.speedIdx + 1) % _ForgeCharts.REPLAY_SPEEDS.length;
13200
+ if (this._replay.playing) this._replayStartTimer();
13201
+ this._emitReplayState();
13202
+ return _ForgeCharts.REPLAY_SPEEDS[this._replay.speedIdx];
13203
+ }
13204
+ /** Exit replay entirely: the full tape (live head included) returns. */
13205
+ replayExit() {
13206
+ this._assertAlive();
13207
+ this._replayStopTimer();
13208
+ this._replay.selecting = false;
13209
+ this._replay.active = false;
13210
+ this._replay.playing = false;
13211
+ this._core.setReplaySelect?.(null);
13212
+ this._series.setCutoffTime(null);
13213
+ this._core.markDirty();
13214
+ this._core.scrollToEnd();
13215
+ this._emitReplayState();
13216
+ }
13217
+ _replayStartTimer() {
13218
+ this._replayStopTimer();
13219
+ const speed = _ForgeCharts.REPLAY_SPEEDS[this._replay.speedIdx];
13220
+ this._replay.timer = setInterval(() => this.replayStepForward(1), Math.max(1e3 / speed, 16));
13221
+ }
13222
+ _replayStopTimer() {
13223
+ if (this._replay.timer) clearInterval(this._replay.timer);
13224
+ this._replay.timer = null;
13225
+ }
12931
13226
  // ─── Datafeed ─────────────────────────────────────────────────────────────────
12932
13227
  _buildConnector(datafeed) {
12933
13228
  return new DatafeedConnector(datafeed, this._series, {
@@ -18369,7 +18664,7 @@ var DEMONSTRATION_STROKE = "rgba(255, 200, 50, 0.7)";
18369
18664
  var DEMONSTRATION_COLOR = "var(--crosshair-overlay, rgba(255,255,255,0.75))";
18370
18665
 
18371
18666
  // src/version.ts
18372
- var FORGECHARTS_VERSION = "1.5.82" ;
18667
+ var FORGECHARTS_VERSION = "1.5.83" ;
18373
18668
  function applyRuntimeConfig(caps, config) {
18374
18669
  if (!config) return caps;
18375
18670
  const orderEntry = config.enableOrderEntry === false ? false : caps.orderEntry;
@@ -18471,10 +18766,7 @@ function useHostLicense(apiUrl, getAuthToken) {
18471
18766
  function useUserPackage(apiUrl, getAuthToken) {
18472
18767
  useEffect(() => {
18473
18768
  const resolver3 = ChartRuntimeResolver.getInstance();
18474
- if (!apiUrl || !getAuthToken) {
18475
- resolver3.setUserPackageFeatures(null);
18476
- return;
18477
- }
18769
+ if (!apiUrl || !getAuthToken) return;
18478
18770
  let cancelled = false;
18479
18771
  void (async () => {
18480
18772
  try {
@@ -23333,6 +23625,14 @@ var ChartCanvas = forwardRef(
23333
23625
  },
23334
23626
  addIndicator: (config) => chartRef.current?.addIndicator(config) ?? null,
23335
23627
  setSeriesType: (type) => chartRef.current?.setSeriesType(type),
23628
+ toggleReplay: () => {
23629
+ const chart = chartRef.current;
23630
+ if (!chart) return;
23631
+ const st = chart.replayState();
23632
+ if (st.selecting) chart.replayCancelSelection();
23633
+ else if (st.active) chart.replayExit();
23634
+ else chart.replayEnterSelection();
23635
+ },
23336
23636
  removeIndicator: (id) => chartRef.current?.removeIndicator(id),
23337
23637
  moveIndicatorToOverlay: (id) => chartRef.current?.moveIndicatorToOverlay(id),
23338
23638
  moveIndicatorToPane: (id, targetPaneId) => chartRef.current?.moveIndicatorToPane(id, targetPaneId),
@@ -23380,7 +23680,7 @@ var ChartCanvas = forwardRef(
23380
23680
  useEffect(() => {
23381
23681
  const container = priceRef.current;
23382
23682
  if (!container) return;
23383
- const chart = new TChart({
23683
+ const chart = new ForgeCharts({
23384
23684
  container,
23385
23685
  symbol,
23386
23686
  interval: timeframe,
@@ -23394,6 +23694,9 @@ var ChartCanvas = forwardRef(
23394
23694
  for (const config of initialIndicators) {
23395
23695
  chart.addIndicator(config);
23396
23696
  }
23697
+ chart.on("replayStateChanged", (s) => {
23698
+ setReplayState(s.selecting || s.active ? s : null);
23699
+ });
23397
23700
  transformRef.current = chart.getTransform();
23398
23701
  const syncChartState = () => {
23399
23702
  setBars([...chart.getBars()]);
@@ -24353,6 +24656,7 @@ var ChartCanvas = forwardRef(
24353
24656
  selectPointerToolRef.current = handleToolSelect;
24354
24657
  const initialPointerAppliedRef = useRef(false);
24355
24658
  const initialSeriesAppliedRef = useRef(false);
24659
+ const [replayState, setReplayState] = useState(null);
24356
24660
  useEffect(() => {
24357
24661
  if (!initialSeriesAppliedRef.current && initialSeriesType && chartRef.current) {
24358
24662
  initialSeriesAppliedRef.current = true;
@@ -24361,7 +24665,7 @@ var ChartCanvas = forwardRef(
24361
24665
  if (initialPointerAppliedRef.current || !initialPointerTool) return;
24362
24666
  initialPointerAppliedRef.current = true;
24363
24667
  selectPointerToolRef.current(initialPointerTool);
24364
- }, [initialPointerTool]);
24668
+ }, [initialPointerTool, initialSeriesType]);
24365
24669
  const handleMagnetModeChange = useCallback((mode) => {
24366
24670
  setMagnetMode(mode);
24367
24671
  chartRef.current?.setMagnetMode(mode);
@@ -24457,6 +24761,107 @@ var ChartCanvas = forwardRef(
24457
24761
  crosshairXRef.current = null;
24458
24762
  },
24459
24763
  children: [
24764
+ replayState && (() => {
24765
+ const dark = effectiveTheme !== "light";
24766
+ const bg = dark ? "rgba(24,26,34,0.94)" : "rgba(255,255,255,0.96)";
24767
+ const fg = dark ? "#dfe3ee" : "#20242f";
24768
+ const line = dark ? "rgba(255,255,255,0.12)" : "rgba(0,0,0,0.12)";
24769
+ const btn = {
24770
+ background: "transparent",
24771
+ border: "none",
24772
+ color: fg,
24773
+ cursor: "pointer",
24774
+ padding: "5px 9px",
24775
+ borderRadius: 6,
24776
+ fontSize: 12,
24777
+ display: "inline-flex",
24778
+ alignItems: "center",
24779
+ gap: 5,
24780
+ lineHeight: 1
24781
+ };
24782
+ const call = (fn) => () => {
24783
+ if (chartRef.current) fn(chartRef.current);
24784
+ };
24785
+ return /* @__PURE__ */ jsxs("div", { style: {
24786
+ position: "absolute",
24787
+ bottom: 46,
24788
+ left: "50%",
24789
+ transform: "translateX(-50%)",
24790
+ zIndex: 8,
24791
+ display: "flex",
24792
+ alignItems: "center",
24793
+ gap: 2,
24794
+ background: bg,
24795
+ color: fg,
24796
+ border: `1px solid ${line}`,
24797
+ borderRadius: 8,
24798
+ padding: "2px 6px",
24799
+ boxShadow: "0 4px 16px rgba(0,0,0,0.25)",
24800
+ userSelect: "none"
24801
+ }, children: [
24802
+ replayState.selecting ? /* @__PURE__ */ jsx("span", { style: { fontSize: 12, padding: "5px 9px" }, children: "Click a bar to start replay from there" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
24803
+ /* @__PURE__ */ jsx(
24804
+ "button",
24805
+ {
24806
+ style: btn,
24807
+ title: "Pick a new starting bar",
24808
+ onClick: call((c) => c.replayEnterSelection()),
24809
+ children: "\u2506\u2190 Select bar"
24810
+ }
24811
+ ),
24812
+ /* @__PURE__ */ jsx("span", { style: { width: 1, alignSelf: "stretch", background: line, margin: "4px 2px" } }),
24813
+ /* @__PURE__ */ jsx(
24814
+ "button",
24815
+ {
24816
+ style: btn,
24817
+ title: "Rewind 10 bars",
24818
+ onClick: call((c) => c.replayRewind(10)),
24819
+ children: "\u23EA"
24820
+ }
24821
+ ),
24822
+ /* @__PURE__ */ jsx(
24823
+ "button",
24824
+ {
24825
+ style: btn,
24826
+ title: replayState.playing ? "Pause" : "Play",
24827
+ onClick: call((c) => replayState.playing ? c.replayPause() : c.replayPlay()),
24828
+ children: replayState.playing ? "\u23F8" : "\u25B6"
24829
+ }
24830
+ ),
24831
+ /* @__PURE__ */ jsx(
24832
+ "button",
24833
+ {
24834
+ style: btn,
24835
+ title: "Forward one bar",
24836
+ onClick: call((c) => c.replayStepForward(1)),
24837
+ children: "\u23E9"
24838
+ }
24839
+ ),
24840
+ /* @__PURE__ */ jsxs(
24841
+ "button",
24842
+ {
24843
+ style: { ...btn, fontVariantNumeric: "tabular-nums" },
24844
+ title: "Playback speed",
24845
+ onClick: call((c) => c.replayCycleSpeed()),
24846
+ children: [
24847
+ replayState.speed,
24848
+ "x"
24849
+ ]
24850
+ }
24851
+ )
24852
+ ] }),
24853
+ /* @__PURE__ */ jsx("span", { style: { width: 1, alignSelf: "stretch", background: line, margin: "4px 2px" } }),
24854
+ /* @__PURE__ */ jsx(
24855
+ "button",
24856
+ {
24857
+ style: btn,
24858
+ title: replayState.selecting ? "Cancel selection" : "Exit replay",
24859
+ onClick: call((c) => replayState.selecting ? c.replayCancelSelection() : c.replayExit()),
24860
+ children: "\u2715"
24861
+ }
24862
+ )
24863
+ ] });
24864
+ })(),
24460
24865
  /* @__PURE__ */ jsx(
24461
24866
  "div",
24462
24867
  {
@@ -24544,7 +24949,7 @@ var ChartCanvas = forwardRef(
24544
24949
  symbol,
24545
24950
  timeframe,
24546
24951
  bars,
24547
- theme,
24952
+ theme: effectiveTheme,
24548
24953
  indicators,
24549
24954
  overlayColors: OVERLAY_COLORS,
24550
24955
  onRemove: handleRemoveIndicator,
@@ -24986,6 +25391,7 @@ function LayoutMenu({
24986
25391
  currentName,
24987
25392
  currentLayoutId,
24988
25393
  autoSave,
25394
+ saveState,
24989
25395
  onFetchLayouts,
24990
25396
  onSave,
24991
25397
  onLoad,
@@ -25127,7 +25533,9 @@ function LayoutMenu({
25127
25533
  ] }),
25128
25534
  currentName ?? "Layout",
25129
25535
  " \u25BE",
25130
- autoSave && isSaved && /* @__PURE__ */ jsx("span", { className: "layout-autosave-dot", title: "Auto-save enabled" })
25536
+ saveState === "saving" && /* @__PURE__ */ jsx("span", { style: { fontSize: 10.5, opacity: 0.75, fontStyle: "italic" }, children: "Saving\u2026" }),
25537
+ saveState === "saved" && /* @__PURE__ */ jsx("span", { style: { fontSize: 10.5, color: "var(--up)" }, children: "Saved \u2713" }),
25538
+ saveState !== "saving" && saveState !== "saved" && autoSave && isSaved && /* @__PURE__ */ jsx("span", { className: "layout-autosave-dot", title: "Auto-save enabled" })
25131
25539
  ]
25132
25540
  }
25133
25541
  ),
@@ -26500,28 +26908,32 @@ function RollRuleDropdown({
26500
26908
  ))
26501
26909
  ] });
26502
26910
  }
26503
- var SERIES_STYLES = [
26504
- { id: "candlestick", label: "Candles", icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "currentColor", children: [
26505
- /* @__PURE__ */ jsx("rect", { x: "3", y: "5", width: "3", height: "6", rx: "0.5" }),
26506
- /* @__PURE__ */ jsx("line", { x1: "4.5", y1: "2", x2: "4.5", y2: "14", stroke: "currentColor", strokeWidth: "1" }),
26507
- /* @__PURE__ */ jsx("rect", { x: "10", y: "3", width: "3", height: "7", rx: "0.5" }),
26508
- /* @__PURE__ */ jsx("line", { x1: "11.5", y1: "1", x2: "11.5", y2: "13", stroke: "currentColor", strokeWidth: "1" })
26509
- ] }) },
26510
- { id: "heikinashi", label: "Heikin Ashi", icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "currentColor", children: [
26511
- /* @__PURE__ */ jsx("rect", { x: "2.5", y: "6", width: "3.4", height: "6", rx: "1.6" }),
26512
- /* @__PURE__ */ jsx("rect", { x: "9.5", y: "3", width: "3.4", height: "6", rx: "1.6" })
26513
- ] }) },
26514
- { 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" }) }) },
26515
- { id: "area", label: "Area", icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", children: [
26516
- /* @__PURE__ */ jsx("polyline", { points: "2,12 6,7 10,10 14,4", fill: "none", stroke: "currentColor", strokeWidth: "1.4" }),
26517
- /* @__PURE__ */ jsx("polygon", { points: "2,12 6,7 10,10 14,4 14,14 2,14", fill: "currentColor", opacity: "0.35" })
26518
- ] }) },
26519
- { id: "histogram", label: "Histogram", icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "currentColor", children: [
26520
- /* @__PURE__ */ jsx("rect", { x: "2", y: "9", width: "2.6", height: "5" }),
26521
- /* @__PURE__ */ jsx("rect", { x: "6.5", y: "5", width: "2.6", height: "9" }),
26522
- /* @__PURE__ */ jsx("rect", { x: "11", y: "7", width: "2.6", height: "7" })
26523
- ] }) }
26911
+ var SERIES_STYLE_GROUPS = [
26912
+ { label: "Bars", items: [
26913
+ { 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: [
26914
+ /* @__PURE__ */ jsx("line", { x1: "5", y1: "2", x2: "5", y2: "13" }),
26915
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "5", x2: "5", y2: "5" }),
26916
+ /* @__PURE__ */ jsx("line", { x1: "5", y1: "10", x2: "7", y2: "10" }),
26917
+ /* @__PURE__ */ jsx("line", { x1: "11", y1: "4", x2: "11", y2: "14" }),
26918
+ /* @__PURE__ */ jsx("line", { x1: "9", y1: "7", x2: "11", y2: "7" }),
26919
+ /* @__PURE__ */ jsx("line", { x1: "11", y1: "12", x2: "13", y2: "12" })
26920
+ ] }) },
26921
+ { id: "candlestick", label: "Candles", icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "currentColor", children: [
26922
+ /* @__PURE__ */ jsx("rect", { x: "3", y: "5", width: "3", height: "6", rx: "0.5" }),
26923
+ /* @__PURE__ */ jsx("line", { x1: "4.5", y1: "2", x2: "4.5", y2: "14", stroke: "currentColor", strokeWidth: "1" }),
26924
+ /* @__PURE__ */ jsx("rect", { x: "10", y: "3", width: "3", height: "7", rx: "0.5" }),
26925
+ /* @__PURE__ */ jsx("line", { x1: "11.5", y1: "1", x2: "11.5", y2: "13", stroke: "currentColor", strokeWidth: "1" })
26926
+ ] }) }
26927
+ ] },
26928
+ { label: "Lines", items: [
26929
+ { 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" }) }) },
26930
+ { id: "area", label: "Area", icon: /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", children: [
26931
+ /* @__PURE__ */ jsx("polyline", { points: "2,12 6,7 10,10 14,4", fill: "none", stroke: "currentColor", strokeWidth: "1.4" }),
26932
+ /* @__PURE__ */ jsx("polygon", { points: "2,12 6,7 10,10 14,4 14,14 2,14", fill: "currentColor", opacity: "0.35" })
26933
+ ] }) }
26934
+ ] }
26524
26935
  ];
26936
+ var ALL_SERIES_STYLES = SERIES_STYLE_GROUPS.flatMap((g) => g.items);
26525
26937
  function TopToolbar({
26526
26938
  symbol,
26527
26939
  timeframe,
@@ -26535,6 +26947,9 @@ function TopToolbar({
26535
26947
  onFavoritesChange,
26536
26948
  seriesType,
26537
26949
  onSeriesTypeChange,
26950
+ onToggleReplay,
26951
+ onToggleAlerts,
26952
+ alertsOpen,
26538
26953
  onAddIndicator,
26539
26954
  maxIndicators,
26540
26955
  indicatorsOpen,
@@ -26572,7 +26987,8 @@ function TopToolbar({
26572
26987
  showSavedLayouts,
26573
26988
  showCustomTimeframes,
26574
26989
  showDataExport,
26575
- showMultiChartLayout
26990
+ showMultiChartLayout,
26991
+ layoutSaveState
26576
26992
  }) {
26577
26993
  const [tfOpen, setTfOpen] = useState(false);
26578
26994
  const [styleOpen, setStyleOpen] = useState(false);
@@ -26730,27 +27146,60 @@ function TopToolbar({
26730
27146
  onClick: () => setStyleOpen((o) => !o),
26731
27147
  title: "Chart style",
26732
27148
  children: [
26733
- (SERIES_STYLES.find((s) => s.id === seriesType) ?? SERIES_STYLES[0]).icon,
26734
- (SERIES_STYLES.find((s) => s.id === seriesType) ?? SERIES_STYLES[0]).label
27149
+ (ALL_SERIES_STYLES.find((s) => s.id === seriesType) ?? ALL_SERIES_STYLES[1]).icon,
27150
+ (ALL_SERIES_STYLES.find((s) => s.id === seriesType) ?? ALL_SERIES_STYLES[1]).label
26735
27151
  ]
26736
27152
  }
26737
27153
  ),
26738
- styleOpen && /* @__PURE__ */ jsx("div", { className: "tf-dropdown", style: { minWidth: 150 }, children: SERIES_STYLES.map((style) => /* @__PURE__ */ jsx(
26739
- "button",
26740
- {
26741
- className: "tf-dropdown-row",
26742
- style: style.id === seriesType ? { fontWeight: 600 } : void 0,
26743
- onClick: () => {
26744
- onSeriesTypeChange(style.id);
26745
- setStyleOpen(false);
27154
+ styleOpen && /* @__PURE__ */ jsx("div", { className: "tf-dropdown", style: { minWidth: 165 }, children: SERIES_STYLE_GROUPS.map((group) => /* @__PURE__ */ jsxs("div", { children: [
27155
+ /* @__PURE__ */ jsx("div", { style: {
27156
+ padding: "6px 12px 3px",
27157
+ fontSize: 10.5,
27158
+ fontWeight: 600,
27159
+ letterSpacing: 1,
27160
+ textTransform: "uppercase",
27161
+ opacity: 0.55
27162
+ }, children: group.label }),
27163
+ group.items.map((style) => /* @__PURE__ */ jsxs(
27164
+ "button",
27165
+ {
27166
+ className: "tf-dropdown-row",
27167
+ style: style.id === seriesType ? { fontWeight: 600 } : void 0,
27168
+ onClick: () => {
27169
+ onSeriesTypeChange(style.id);
27170
+ setStyleOpen(false);
27171
+ },
27172
+ children: [
27173
+ /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 8 }, children: [
27174
+ style.icon,
27175
+ style.label
27176
+ ] }),
27177
+ style.id === seriesType && /* @__PURE__ */ jsx("span", { "aria-hidden": true, style: { opacity: 0.9 }, children: "\u2713" })
27178
+ ]
26746
27179
  },
26747
- children: /* @__PURE__ */ jsxs("span", { style: { display: "inline-flex", alignItems: "center", gap: 8 }, children: [
26748
- style.icon,
26749
- style.label
26750
- ] })
26751
- },
26752
- style.id
26753
- )) })
27180
+ style.id
27181
+ ))
27182
+ ] }, group.label)) })
27183
+ ] })
27184
+ ] }),
27185
+ onToggleAlerts !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
27186
+ /* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
27187
+ /* @__PURE__ */ jsxs("button", { className: `ind-trigger${alertsOpen ? " active" : ""}`, onClick: onToggleAlerts, title: "Price alerts", children: [
27188
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
27189
+ /* @__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" }),
27190
+ /* @__PURE__ */ jsx("path", { d: "M6.6 12.5a1.5 1.5 0 0 0 2.8 0" })
27191
+ ] }),
27192
+ "Alerts"
27193
+ ] })
27194
+ ] }),
27195
+ onToggleReplay !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
27196
+ /* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
27197
+ /* @__PURE__ */ jsxs("button", { className: "ind-trigger", onClick: onToggleReplay, title: "Bar replay", children: [
27198
+ /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 16 16", width: "13", height: "13", fill: "none", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round", children: [
27199
+ /* @__PURE__ */ jsx("polygon", { points: "6,4 12,8 6,12", fill: "currentColor", stroke: "none" }),
27200
+ /* @__PURE__ */ jsx("line", { x1: "3", y1: "3", x2: "3", y2: "13" })
27201
+ ] }),
27202
+ "Replay"
26754
27203
  ] })
26755
27204
  ] }),
26756
27205
  /* @__PURE__ */ jsx("div", { className: "toolbar-sep" }),
@@ -26819,6 +27268,7 @@ function TopToolbar({
26819
27268
  currentName: currentLayoutName,
26820
27269
  currentLayoutId,
26821
27270
  autoSave,
27271
+ ...layoutSaveState !== void 0 ? { saveState: layoutSaveState } : {},
26822
27272
  onFetchLayouts,
26823
27273
  onSave: onSaveLayout,
26824
27274
  onLoad: onLoadLayout,
@@ -31571,6 +32021,194 @@ function WatchlistDrawer({ onClose, onSelectSymbol, symbolResolver, apiUrl, getA
31571
32021
  }
31572
32022
  );
31573
32023
  }
32024
+ function AlertsDrawer({ apiUrl, getAuthToken, symbol, onClose }) {
32025
+ const [alerts, setAlerts] = useState(null);
32026
+ const [error, setError] = useState("");
32027
+ const [busy, setBusy] = useState(false);
32028
+ const [formSymbol, setFormSymbol] = useState(symbol);
32029
+ const [condition, setCondition] = useState("above");
32030
+ const [priceStr, setPriceStr] = useState("");
32031
+ useEffect(() => {
32032
+ setFormSymbol(symbol);
32033
+ }, [symbol]);
32034
+ const call = useCallback(async (path, init) => {
32035
+ const token = getAuthToken ? await getAuthToken() : "";
32036
+ return fetch(`${apiUrl.replace(/\/$/, "")}/api/alerts${path}`, {
32037
+ ...init,
32038
+ headers: {
32039
+ "Content-Type": "application/json",
32040
+ ...token ? { Authorization: `Bearer ${token}` } : {},
32041
+ ...init?.headers ?? {}
32042
+ }
32043
+ });
32044
+ }, [apiUrl, getAuthToken]);
32045
+ const load = useCallback(() => {
32046
+ call("").then(async (r) => {
32047
+ if (!r.ok) throw new Error(`HTTP ${r.status}`);
32048
+ setAlerts(await r.json());
32049
+ }).catch((e) => setError(`Failed to load alerts: ${e instanceof Error ? e.message : e}`));
32050
+ }, [call]);
32051
+ useEffect(() => {
32052
+ load();
32053
+ const t = setInterval(load, 15e3);
32054
+ return () => clearInterval(t);
32055
+ }, [load]);
32056
+ async function create() {
32057
+ const price = parseFloat(priceStr);
32058
+ if (!(price > 0) || !formSymbol.trim()) return;
32059
+ setBusy(true);
32060
+ setError("");
32061
+ try {
32062
+ const r = await call("", {
32063
+ method: "POST",
32064
+ body: JSON.stringify({ symbol: formSymbol.trim(), condition, price })
32065
+ });
32066
+ const d = await r.json().catch(() => ({}));
32067
+ if (!r.ok) throw new Error(d.error ?? `HTTP ${r.status}`);
32068
+ setPriceStr("");
32069
+ load();
32070
+ } catch (e) {
32071
+ setError(e instanceof Error ? e.message : String(e));
32072
+ } finally {
32073
+ setBusy(false);
32074
+ }
32075
+ }
32076
+ async function remove(id) {
32077
+ try {
32078
+ await call(`/${encodeURIComponent(id)}`, { method: "DELETE" });
32079
+ setAlerts((prev) => prev?.filter((a) => a.id !== id) ?? null);
32080
+ } catch {
32081
+ }
32082
+ }
32083
+ return /* @__PURE__ */ jsxs("div", { style: {
32084
+ width: 280,
32085
+ flexShrink: 0,
32086
+ display: "flex",
32087
+ flexDirection: "column",
32088
+ background: "var(--surface)",
32089
+ border: "1px solid var(--border)",
32090
+ borderRadius: 8,
32091
+ overflow: "hidden",
32092
+ color: "var(--text)"
32093
+ }, children: [
32094
+ /* @__PURE__ */ jsxs("div", { style: {
32095
+ display: "flex",
32096
+ alignItems: "center",
32097
+ justifyContent: "space-between",
32098
+ padding: "10px 14px",
32099
+ borderBottom: "1px solid var(--border)"
32100
+ }, children: [
32101
+ /* @__PURE__ */ jsx("strong", { style: { fontSize: 13 }, children: "Alerts" }),
32102
+ /* @__PURE__ */ jsx(
32103
+ "button",
32104
+ {
32105
+ onClick: onClose,
32106
+ title: "Close",
32107
+ style: { background: "none", border: "none", color: "var(--text-muted)", cursor: "pointer", fontSize: 14 },
32108
+ children: "\u2715"
32109
+ }
32110
+ )
32111
+ ] }),
32112
+ /* @__PURE__ */ jsxs("div", { style: { padding: "10px 14px", borderBottom: "1px solid var(--border)", display: "flex", flexDirection: "column", gap: 8 }, children: [
32113
+ /* @__PURE__ */ jsx(
32114
+ "input",
32115
+ {
32116
+ value: formSymbol,
32117
+ onChange: (e) => setFormSymbol(e.target.value),
32118
+ placeholder: "Symbol",
32119
+ style: { fontSize: 12.5, padding: "5px 8px", background: "var(--surface-2)", color: "var(--text)", border: "1px solid var(--border)", borderRadius: 6 }
32120
+ }
32121
+ ),
32122
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
32123
+ /* @__PURE__ */ jsxs(
32124
+ "select",
32125
+ {
32126
+ value: condition,
32127
+ onChange: (e) => setCondition(e.target.value),
32128
+ style: { fontSize: 12.5, padding: "5px 6px", background: "var(--surface-2)", color: "var(--text)", border: "1px solid var(--border)", borderRadius: 6 },
32129
+ children: [
32130
+ /* @__PURE__ */ jsx("option", { value: "above", children: "Crosses above" }),
32131
+ /* @__PURE__ */ jsx("option", { value: "below", children: "Crosses below" })
32132
+ ]
32133
+ }
32134
+ ),
32135
+ /* @__PURE__ */ jsx(
32136
+ "input",
32137
+ {
32138
+ value: priceStr,
32139
+ onChange: (e) => setPriceStr(e.target.value.replace(/[^0-9.]/g, "")),
32140
+ placeholder: "Price",
32141
+ inputMode: "decimal",
32142
+ 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" },
32143
+ onKeyDown: (e) => e.key === "Enter" && !busy && create()
32144
+ }
32145
+ )
32146
+ ] }),
32147
+ /* @__PURE__ */ jsx(
32148
+ "button",
32149
+ {
32150
+ onClick: create,
32151
+ disabled: busy || !(parseFloat(priceStr) > 0),
32152
+ style: {
32153
+ fontSize: 12.5,
32154
+ padding: "6px 0",
32155
+ borderRadius: 6,
32156
+ border: "none",
32157
+ cursor: "pointer",
32158
+ background: "var(--primary, #5b5bd6)",
32159
+ color: "#fff",
32160
+ opacity: busy || !(parseFloat(priceStr) > 0) ? 0.55 : 1
32161
+ },
32162
+ children: busy ? "Creating\u2026" : "Create alert"
32163
+ }
32164
+ ),
32165
+ error && /* @__PURE__ */ jsx("div", { style: { fontSize: 11.5, color: "var(--down, #e5484d)" }, children: error })
32166
+ ] }),
32167
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, overflowY: "auto" }, children: [
32168
+ alerts === null && !error && /* @__PURE__ */ jsx("div", { style: { padding: 14, fontSize: 12.5, color: "var(--text-muted)" }, children: "Loading\u2026" }),
32169
+ 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." }),
32170
+ alerts?.map((a) => /* @__PURE__ */ jsxs("div", { style: {
32171
+ display: "flex",
32172
+ alignItems: "center",
32173
+ gap: 8,
32174
+ padding: "8px 14px",
32175
+ borderBottom: "1px solid var(--border)",
32176
+ fontSize: 12.5,
32177
+ opacity: a.status === "triggered" ? 0.75 : 1
32178
+ }, children: [
32179
+ /* @__PURE__ */ jsx("span", { style: {
32180
+ width: 7,
32181
+ height: 7,
32182
+ borderRadius: "50%",
32183
+ flexShrink: 0,
32184
+ background: a.status === "active" ? "var(--up, #22a06b)" : "var(--text-muted)"
32185
+ }, title: a.status }),
32186
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, minWidth: 0 }, children: [
32187
+ /* @__PURE__ */ jsxs("div", { style: { whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: [
32188
+ /* @__PURE__ */ jsx("strong", { children: a.symbol }),
32189
+ " ",
32190
+ a.condition === "above" ? "\u2265" : "\u2264",
32191
+ " ",
32192
+ /* @__PURE__ */ jsx("span", { style: { fontVariantNumeric: "tabular-nums" }, children: a.price })
32193
+ ] }),
32194
+ a.status === "triggered" && /* @__PURE__ */ jsxs("div", { style: { fontSize: 11, color: "var(--text-muted)" }, children: [
32195
+ "fired at ",
32196
+ a.trigger_price
32197
+ ] })
32198
+ ] }),
32199
+ /* @__PURE__ */ jsx(
32200
+ "button",
32201
+ {
32202
+ onClick: () => remove(a.id),
32203
+ title: "Remove alert",
32204
+ style: { background: "none", border: "none", color: "var(--text-muted)", cursor: "pointer", fontSize: 13 },
32205
+ children: "\u2715"
32206
+ }
32207
+ )
32208
+ ] }, a.id))
32209
+ ] })
32210
+ ] });
32211
+ }
31574
32212
  function ChartWorkspace({
31575
32213
  // TabBar
31576
32214
  tabs,
@@ -31621,6 +32259,9 @@ function ChartWorkspace({
31621
32259
  seriesType,
31622
32260
  onSeriesTypeChange,
31623
32261
  showSeriesTypes,
32262
+ onToggleReplay,
32263
+ showReplay,
32264
+ layoutSaveState,
31624
32265
  extraTfGroups,
31625
32266
  // RightToolbar
31626
32267
  watchlistOpen,
@@ -31731,6 +32372,9 @@ function ChartWorkspace({
31731
32372
  const effShowMultiChartLayout = gate(showMultiChartLayout, capabilities.multiChartLayout);
31732
32373
  const effShowMultipleTabs = gate(showMultipleTabs, capabilities.multipleWorkspaces);
31733
32374
  const effShowSeriesTypes = gate(showSeriesTypes, capabilities.chartTypes) && onSeriesTypeChange !== void 0;
32375
+ const effShowReplay = gate(showReplay, capabilities.barReplay) && onToggleReplay !== void 0;
32376
+ const effShowAlerts = gate(void 0, capabilities.alerts) && hostApiUrl !== void 0;
32377
+ const [alertsOpen, setAlertsOpen] = React11.useState(false);
31734
32378
  const tabItems = tabs.map((t) => ({
31735
32379
  id: t.id,
31736
32380
  label: t.label,
@@ -31809,6 +32453,8 @@ function ChartWorkspace({
31809
32453
  onSymbolChange,
31810
32454
  onTimeframeChange,
31811
32455
  ...effShowSeriesTypes ? { seriesType: seriesType ?? "candlestick", onSeriesTypeChange } : {},
32456
+ ...effShowReplay ? { onToggleReplay } : {},
32457
+ ...effShowAlerts ? { onToggleAlerts: () => setAlertsOpen((o) => !o), alertsOpen } : {},
31812
32458
  onAddCustomTimeframe,
31813
32459
  onRemoveCustomTimeframe,
31814
32460
  onFavoritesChange: onFavoriteTfsChange,
@@ -31824,6 +32470,7 @@ function ChartWorkspace({
31824
32470
  currentLayoutName,
31825
32471
  currentLayoutId,
31826
32472
  autoSave,
32473
+ ...layoutSaveState !== void 0 ? { layoutSaveState } : {},
31827
32474
  onFetchLayouts,
31828
32475
  onSaveLayout,
31829
32476
  onLoadLayout,
@@ -31855,6 +32502,15 @@ function ChartWorkspace({
31855
32502
  /* @__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
32503
  (dataBlocked || isLicensed === false) && /* @__PURE__ */ jsx(LicenseRequiredOverlay, {}),
31857
32504
  /* @__PURE__ */ jsx("div", { style: { position: "relative", flex: 1, minWidth: 0, minHeight: 0 }, children: chartSlots }),
32505
+ effShowAlerts && alertsOpen && /* @__PURE__ */ jsx(
32506
+ AlertsDrawer,
32507
+ {
32508
+ apiUrl: hostApiUrl,
32509
+ getAuthToken,
32510
+ symbol: activeSymbol ?? "",
32511
+ onClose: () => setAlertsOpen(false)
32512
+ }
32513
+ ),
31858
32514
  autoTrading && !onToggleOrderEntry && effOrderEntryOpen && capabilities.orderEntry && tradingBridge && /* @__PURE__ */ jsx(
31859
32515
  OrderTicket,
31860
32516
  {
@@ -35258,22 +35914,6 @@ var ManagedAppShell = forwardRef(
35258
35914
  const maxBars = data?.package?.maxHistoricalBars ?? null;
35259
35915
  const df = datafeed;
35260
35916
  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
35917
  }
35278
35918
  }
35279
35919
  if (plansRes.ok) {
@@ -36623,13 +37263,14 @@ var ManagedAppShell = forwardRef(
36623
37263
  }
36624
37264
  ),
36625
37265
  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(
37266
+ (orderPanelOpen || connectedSession?.disconnected) && connectedSession && capabilities.renderManagedTradingControls && capabilities.orderEntry && /* @__PURE__ */ jsx(
36627
37267
  OrderEntryPanel,
36628
37268
  {
36629
37269
  sessionId: connectedSession.sessionId,
36630
37270
  brokerSlug: connectedSession.brokerSlug,
36631
37271
  accounts: connectedSession.accounts,
36632
37272
  selectedAccountId,
37273
+ showTrailingStop: capabilities.trailingStops,
36633
37274
  defaultSymbol: activeTab.symbol,
36634
37275
  apiUrl,
36635
37276
  getAuthToken,
@@ -36817,7 +37458,7 @@ var ManagedAppShell = forwardRef(
36817
37458
  ) : null;
36818
37459
  const drawers = /* @__PURE__ */ jsxs(Fragment, { children: [
36819
37460
  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(
37461
+ tradeDrawerOpen && capabilities.renderManagedTradingControls && capabilities.orderEntry && (renderTradeDrawer ? renderTradeDrawer({ onClose: () => setTradeDrawerOpen(false) }) : /* @__PURE__ */ jsx(
36821
37462
  TradeDrawer,
36822
37463
  {
36823
37464
  onClose: () => setTradeDrawerOpen(false),
@@ -36981,6 +37622,8 @@ var ManagedAppShell = forwardRef(
36981
37622
  onTimeframeChange: handleTimeframeChange,
36982
37623
  seriesType,
36983
37624
  onSeriesTypeChange: handleSeriesTypeChange,
37625
+ onToggleReplay: () => chartHandleRef.current?.toggleReplay(),
37626
+ ...apiUrl !== void 0 ? { hostApiUrl: apiUrl } : {},
36984
37627
  onAddCustomTimeframe: handleAddCustomTimeframe,
36985
37628
  onRemoveCustomTimeframe: handleRemoveCustomTimeframe,
36986
37629
  onFavoriteTfsChange: setFavoriteTfs,
@@ -37004,7 +37647,7 @@ var ManagedAppShell = forwardRef(
37004
37647
  onDeleteLayout: handleDeleteLayout,
37005
37648
  onOpenLayoutInNewTab: handleOpenLayoutInNewTab,
37006
37649
  symbolResolver,
37007
- showTradeButton: capabilities.renderManagedTradingControls,
37650
+ showTradeButton: capabilities.renderManagedTradingControls && capabilities.orderEntry,
37008
37651
  tradeDrawerOpen: tradeDrawerOpen || orderPanelOpen,
37009
37652
  onToggleTradeDrawer: () => {
37010
37653
  if (orderPanelOpen && !tradeDrawerOpen) {
@@ -37019,7 +37662,7 @@ var ManagedAppShell = forwardRef(
37019
37662
  onToggleWatchlist: () => setWatchlistOpen((o) => !o),
37020
37663
  orderEntryOpen: orderPanelOpen,
37021
37664
  onToggleOrderEntry: handleToggleOrderEntry,
37022
- showOrderEntry: capabilities.renderManagedTradingControls,
37665
+ showOrderEntry: capabilities.renderManagedTradingControls && capabilities.orderEntry,
37023
37666
  showAgent: agentEnabled,
37024
37667
  agentOpen: agentPanelOpen,
37025
37668
  onToggleAgent: () => setAgentPanelOpen((o) => !o),