@forgecharts/sdk 1.5.83 → 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.
@@ -9379,6 +9379,16 @@ var PixiChart = class {
9379
9379
  }
9380
9380
  this._markViewportDirty();
9381
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
+ }
9382
9392
  /** Pans the viewport so the latest bar sits near the right edge. */
9383
9393
  _scrollToLatestBar() {
9384
9394
  let latestTime = -Infinity;
@@ -13080,7 +13090,7 @@ var ForgeCharts = class _ForgeCharts {
13080
13090
  // exists. Selection (the hover shadow of what a click would remove) is a
13081
13091
  // backend surface only Pixi implements; ForgeCharts refuses replay elsewhere.
13082
13092
  /** Playback speeds, in bars per second. */
13083
- static REPLAY_SPEEDS = [0.5, 1, 2, 5, 10];
13093
+ static REPLAY_SPEEDS = [0.5, 1, 2, 5, 10, 15, 20];
13084
13094
  _replay = {
13085
13095
  selecting: false,
13086
13096
  active: false,
@@ -13148,6 +13158,7 @@ var ForgeCharts = class _ForgeCharts {
13148
13158
  this._series.setCutoffTime(time);
13149
13159
  this._replay.active = true;
13150
13160
  this._replay.playing = false;
13161
+ this._core.centerOnTime?.(time);
13151
13162
  this._core.markDirty();
13152
13163
  this._emitReplayState();
13153
13164
  }
@@ -13193,6 +13204,33 @@ var ForgeCharts = class _ForgeCharts {
13193
13204
  this._core.markDirty();
13194
13205
  this._emitReplayState();
13195
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
+ }
13196
13234
  /** Cycle to the next playback speed; restarts the timer when playing. */
13197
13235
  replayCycleSpeed() {
13198
13236
  this._assertAlive();
@@ -18664,7 +18702,7 @@ var DEMONSTRATION_STROKE = "rgba(255, 200, 50, 0.7)";
18664
18702
  var DEMONSTRATION_COLOR = "var(--crosshair-overlay, rgba(255,255,255,0.75))";
18665
18703
 
18666
18704
  // src/version.ts
18667
- var FORGECHARTS_VERSION = "1.5.83" ;
18705
+ var FORGECHARTS_VERSION = "1.5.84" ;
18668
18706
  function applyRuntimeConfig(caps, config) {
18669
18707
  if (!config) return caps;
18670
18708
  const orderEntry = config.enableOrderEntry === false ? false : caps.orderEntry;
@@ -23506,6 +23544,8 @@ var ChartCanvas = forwardRef(
23506
23544
  onPointerToolChange,
23507
23545
  initialPointerTool,
23508
23546
  initialSeriesType,
23547
+ initialReplay,
23548
+ onReplayStateChange,
23509
23549
  initialMagnetMode,
23510
23550
  onMagnetModeChange,
23511
23551
  drawingFavorites: drawingFavoritesProp,
@@ -23696,6 +23736,7 @@ var ChartCanvas = forwardRef(
23696
23736
  }
23697
23737
  chart.on("replayStateChanged", (s) => {
23698
23738
  setReplayState(s.selecting || s.active ? s : null);
23739
+ onReplayStateChangeRef.current?.(s);
23699
23740
  });
23700
23741
  transformRef.current = chart.getTransform();
23701
23742
  const syncChartState = () => {
@@ -24657,6 +24698,36 @@ var ChartCanvas = forwardRef(
24657
24698
  const initialPointerAppliedRef = useRef(false);
24658
24699
  const initialSeriesAppliedRef = useRef(false);
24659
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
+ }, []);
24660
24731
  useEffect(() => {
24661
24732
  if (!initialSeriesAppliedRef.current && initialSeriesType && chartRef.current) {
24662
24733
  initialSeriesAppliedRef.current = true;
@@ -24763,15 +24834,15 @@ var ChartCanvas = forwardRef(
24763
24834
  children: [
24764
24835
  replayState && (() => {
24765
24836
  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)";
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)";
24769
24840
  const btn = {
24770
24841
  background: "transparent",
24771
24842
  border: "none",
24772
24843
  color: fg,
24773
24844
  cursor: "pointer",
24774
- padding: "5px 9px",
24845
+ padding: "6px 10px",
24775
24846
  borderRadius: 6,
24776
24847
  fontSize: 12,
24777
24848
  display: "inline-flex",
@@ -24779,27 +24850,44 @@ var ChartCanvas = forwardRef(
24779
24850
  gap: 5,
24780
24851
  lineHeight: 1
24781
24852
  };
24853
+ const sep = /* @__PURE__ */ jsx("span", { style: { width: 1, alignSelf: "stretch", background: line, margin: "6px 4px" } });
24782
24854
  const call = (fn) => () => {
24783
24855
  if (chartRef.current) fn(chartRef.current);
24784
24856
  };
24785
24857
  return /* @__PURE__ */ jsxs("div", { style: {
24786
24858
  position: "absolute",
24787
- bottom: 46,
24788
- left: "50%",
24789
- transform: "translateX(-50%)",
24859
+ left: 0,
24860
+ right: 0,
24861
+ bottom: replayBarOffset,
24790
24862
  zIndex: 8,
24791
24863
  display: "flex",
24792
24864
  alignItems: "center",
24793
24865
  gap: 2,
24866
+ height: 38,
24867
+ padding: "0 8px",
24868
+ boxSizing: "border-box",
24794
24869
  background: bg,
24795
24870
  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)",
24871
+ borderTop: `1px solid ${line}`,
24872
+ borderBottom: `1px solid ${line}`,
24800
24873
  userSelect: "none"
24801
24874
  }, 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: [
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: [
24803
24891
  /* @__PURE__ */ jsx(
24804
24892
  "button",
24805
24893
  {
@@ -24809,7 +24897,7 @@ var ChartCanvas = forwardRef(
24809
24897
  children: "\u2506\u2190 Select bar"
24810
24898
  }
24811
24899
  ),
24812
- /* @__PURE__ */ jsx("span", { style: { width: 1, alignSelf: "stretch", background: line, margin: "4px 2px" } }),
24900
+ sep,
24813
24901
  /* @__PURE__ */ jsx(
24814
24902
  "button",
24815
24903
  {
@@ -24840,8 +24928,8 @@ var ChartCanvas = forwardRef(
24840
24928
  /* @__PURE__ */ jsxs(
24841
24929
  "button",
24842
24930
  {
24843
- style: { ...btn, fontVariantNumeric: "tabular-nums" },
24844
- title: "Playback speed",
24931
+ style: { ...btn, fontVariantNumeric: "tabular-nums", minWidth: 44, justifyContent: "center" },
24932
+ title: "Playback speed (0.5\u201320 bars per second)",
24845
24933
  onClick: call((c) => c.replayCycleSpeed()),
24846
24934
  children: [
24847
24935
  replayState.speed,
@@ -24850,7 +24938,7 @@ var ChartCanvas = forwardRef(
24850
24938
  }
24851
24939
  )
24852
24940
  ] }),
24853
- /* @__PURE__ */ jsx("span", { style: { width: 1, alignSelf: "stretch", background: line, margin: "4px 2px" } }),
24941
+ /* @__PURE__ */ jsx("span", { style: { flex: 1 } }),
24854
24942
  /* @__PURE__ */ jsx(
24855
24943
  "button",
24856
24944
  {