@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.
Files changed (53) hide show
  1. package/dist/api/EventBus.d.ts +1 -1
  2. package/dist/api/{TChart.d.ts → ForgeCharts.d.ts} +47 -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 +520 -27
  16. package/dist/index.js.map +1 -1
  17. package/dist/internal.d.ts +1 -1
  18. package/dist/internal.js +520 -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 +18 -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 +20 -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 +807 -63
  35. package/dist/react/index.js.map +1 -1
  36. package/dist/react/internal.js +814 -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 +17 -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;
@@ -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/TChart.ts
11916
- var TChart = class _TChart {
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 = _TChart.OVERLAY_ONLY.has(config.type) && config.overlay !== true ? { ...config, overlay: true } : config;
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 (_TChart.EXTENDED_DRAWING_TOOLS.has(type) && !canUseExtendedDrawings()) {
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 > _TChart._HIST_MAX) this._drawHistory.shift();
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.82" ;
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 TChart({
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
- autoSave && isSaved && /* @__PURE__ */ jsx("span", { className: "layout-autosave-dot", title: "Auto-save enabled" })
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 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
- ] }) }
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
- (SERIES_STYLES.find((s) => s.id === seriesType) ?? SERIES_STYLES[0]).icon,
26734
- (SERIES_STYLES.find((s) => s.id === seriesType) ?? SERIES_STYLES[0]).label
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: 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);
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
- 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
- )) })
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),