acttrader-charts 1.1.0 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -287,6 +287,7 @@ When `enableTrading` is on and live BID/ASK data is streaming, hovering / activa
287
287
  | `tradeDisplayFilter` | | `"all"` | Which TFC levels are visible: `"all"` · `"positions"` · `"orders"` · `"none"` |
288
288
  | `positionRenderStyle` | | auto | Force position render style: `"line"` or `"dot"` |
289
289
  | `hideLevelConfirmCancel` | | `false` | Hide on-canvas ✓/✗ confirm-cancel buttons for TFC level edits |
290
+ | `deselectActiveOnOutsideClick` | | `false` | When `true`, clicking/tapping anywhere outside a selected trade level dismisses it (reverting any pending edits, mirroring ✗ Cancel). Default `false` keeps the level active so incidental clicks — price-axis resize, taps outside the QTY input — don't drop an in-progress edit. The level can still be dismissed via ✓/✗, tapping the level again, or `setLevels()` removing it |
290
291
  | `showTradeLevelsAlways` | | `false` | Always render SL/TP bracket lines + price pills, even when the parent level is not hovered or selected. The close (×) button stays hover-only so the chart isn't cluttered. Toggleable from the Settings dialog (Trading tab). Persisted in `localStorage`. |
291
292
  | `tradeLevelButtonScale` | | `1` | Multiplier for trade-level Confirm/Cancel/Edit/Close button radii and gaps. Scales visuals **and** hit/drag areas together — raise it on touch devices for larger tap targets. Clamped to `[1, 3]`. Also settable at runtime via `chart.setTradeLevelButtonScale(scale)` |
292
293
  | `tfcEnabled` | | `true` | Enable the TFC toggle button in the top bar. When `false`, TFC is completely disabled — the toggle button is hidden and all trade levels, draft orders, and the floating trade button are suppressed |
@@ -879,6 +879,17 @@ interface ChartConfig {
879
879
  * Default: `false`.
880
880
  */
881
881
  hideLevelConfirmCancel?: boolean;
882
+ /**
883
+ * When `true`, clicking or tapping anywhere outside a selected/active trade
884
+ * level dismisses it (reverting any pending edits, mirroring ✗ Cancel).
885
+ * When `false` (default), the active level is preserved across outside
886
+ * clicks — only explicit ✓ / ✗ buttons, tapping the level itself, or
887
+ * `setLevels()` removing it will dismiss it. Recommended `false` so
888
+ * incidental clicks (e.g. price-axis resize, tapping outside the QTY input)
889
+ * don't drop the user's in-progress edit.
890
+ * Default: `false`.
891
+ */
892
+ deselectActiveOnOutsideClick?: boolean;
882
893
  /**
883
894
  * Always render SL/TP bracket lines + price pills, even when the parent
884
895
  * trade level is not hovered or selected. Close (×) buttons remain
@@ -879,6 +879,17 @@ interface ChartConfig {
879
879
  * Default: `false`.
880
880
  */
881
881
  hideLevelConfirmCancel?: boolean;
882
+ /**
883
+ * When `true`, clicking or tapping anywhere outside a selected/active trade
884
+ * level dismisses it (reverting any pending edits, mirroring ✗ Cancel).
885
+ * When `false` (default), the active level is preserved across outside
886
+ * clicks — only explicit ✓ / ✗ buttons, tapping the level itself, or
887
+ * `setLevels()` removing it will dismiss it. Recommended `false` so
888
+ * incidental clicks (e.g. price-axis resize, tapping outside the QTY input)
889
+ * don't drop the user's in-progress edit.
890
+ * Default: `false`.
891
+ */
892
+ deselectActiveOnOutsideClick?: boolean;
882
893
  /**
883
894
  * Always render SL/TP bracket lines + price pills, even when the parent
884
895
  * trade level is not hovered or selected. Close (×) buttons remain
package/dist/index.cjs CHANGED
@@ -3371,6 +3371,7 @@ function formatTimestamp(ts, visibleBars, timeframe, spansMultipleDays, timezone
3371
3371
  return d.toLocaleDateString("en-US", { month: "short", year: "2-digit", timeZone: timezone });
3372
3372
  }
3373
3373
  function renderTimeAxis(ctx, bars, viewport, scale, theme, chartWidth, axisY, maxLabels = 8, cfg = DEFAULT_TIME_AXIS_CONFIG, timeframe, countdownBarIndex, timezone = "UTC") {
3374
+ if (bars.length === 0) return;
3374
3375
  const visibleBars = viewport.endIndex - viewport.startIndex;
3375
3376
  const firstBar = bars[0];
3376
3377
  const lastBar = bars[bars.length - 1];
@@ -3379,7 +3380,8 @@ function renderTimeAxis(ctx, bars, viewport, scale, theme, chartWidth, axisY, ma
3379
3380
  ctx.font = `${cfg.fontSize} ${cfg.fontFamily}`;
3380
3381
  ctx.textAlign = "center";
3381
3382
  const MIN_GAP = 12;
3382
- const sampleLabel = formatTimestamp(bars[Math.floor(bars.length / 2)]?.time ?? bars[0].time, visibleBars, timeframe, spansMultipleDays, timezone);
3383
+ const sampleBar = bars[Math.floor(bars.length / 2)] ?? bars[0];
3384
+ const sampleLabel = formatTimestamp(sampleBar.time, visibleBars, timeframe, spansMultipleDays, timezone);
3383
3385
  const labelW = ctx.measureText(sampleLabel).width;
3384
3386
  const minStep = Math.max(1, Math.floor(bars.length / maxLabels));
3385
3387
  let step = minStep;
@@ -18661,6 +18663,9 @@ var _ChartEngine = class _ChartEngine {
18661
18663
  this.tickActivityTimer = null;
18662
18664
  this.tickActivityMs = 3e4;
18663
18665
  this.streamStatus = "disconnected";
18666
+ // Set by destroy() so late-firing callbacks (ResizeObserver entries queued
18667
+ // before disconnect, in-flight promises, sockets) bail before touching state.
18668
+ this._isDestroyed = false;
18664
18669
  this.indicatorSettingsDialog = null;
18665
18670
  this.chartSettingsDialog = null;
18666
18671
  this.tradeButton = null;
@@ -18693,6 +18698,7 @@ var _ChartEngine = class _ChartEngine {
18693
18698
  this.showBidAskLines = false;
18694
18699
  this.showActLogo = false;
18695
18700
  this.hideLevelConfirmCancel = false;
18701
+ this.deselectActiveOnOutsideClick = false;
18696
18702
  this.tradeLevelButtonScale = 1;
18697
18703
  this.levelClusteringEnabled = true;
18698
18704
  this.clusterThresholdDistance = 20;
@@ -19662,7 +19668,7 @@ var _ChartEngine = class _ChartEngine {
19662
19668
  }
19663
19669
  return;
19664
19670
  } else if (this.selectedTradeLabel !== null) {
19665
- if (!this.hideLevelConfirmCancel) {
19671
+ if (!this.hideLevelConfirmCancel && this.deselectActiveOnOutsideClick) {
19666
19672
  const lbl = this.selectedTradeLabel;
19667
19673
  const pending = this.pendingLevelDrags.get(lbl);
19668
19674
  if (!pending || pending.length === 0) {
@@ -19813,6 +19819,7 @@ var _ChartEngine = class _ChartEngine {
19813
19819
  }
19814
19820
  };
19815
19821
  this.onTick = (tick) => {
19822
+ if (this._isDestroyed) return;
19816
19823
  const all = this.dataStore.all;
19817
19824
  if (all.length === 0) return;
19818
19825
  if (this._suppressTicks) return;
@@ -19952,6 +19959,7 @@ var _ChartEngine = class _ChartEngine {
19952
19959
  this.showBidAskLines = config.showBidAskLines ?? false;
19953
19960
  this.showActLogo = config.showActLogo ?? false;
19954
19961
  this.hideLevelConfirmCancel = config.hideLevelConfirmCancel ?? false;
19962
+ this.deselectActiveOnOutsideClick = config.deselectActiveOnOutsideClick ?? false;
19955
19963
  this.tradeLevelButtonScale = Math.min(Math.max(config.tradeLevelButtonScale ?? 1, 1), 3);
19956
19964
  this.levelClusteringEnabled = config.levelClusteringEnabled ?? true;
19957
19965
  this.clusterThresholdDistance = config.clusterThresholdDistance ?? 20;
@@ -22221,6 +22229,7 @@ var _ChartEngine = class _ChartEngine {
22221
22229
  return this;
22222
22230
  }
22223
22231
  destroy() {
22232
+ this._isDestroyed = true;
22224
22233
  this.panHandler.destroy();
22225
22234
  this.zoomHandler.destroy();
22226
22235
  this.resizeObserver.disconnect();
@@ -22353,6 +22362,7 @@ var _ChartEngine = class _ChartEngine {
22353
22362
  }
22354
22363
  // ── Rendering ─────────────────────────────────────────────────────────────
22355
22364
  scheduleRender() {
22365
+ if (this._isDestroyed) return;
22356
22366
  if (this.rafId !== null) return;
22357
22367
  this.rafId = requestAnimationFrame(() => {
22358
22368
  this.rafId = null;
@@ -22542,6 +22552,7 @@ var _ChartEngine = class _ChartEngine {
22542
22552
  this.positionRenderStyle = activeCount > this.tradesThresholdForHorizontalLine ? "dot" : "line";
22543
22553
  }
22544
22554
  renderTradeLayer() {
22555
+ if (this._isDestroyed) return;
22545
22556
  const dpr = window.devicePixelRatio || 1;
22546
22557
  const { tradeCtx: ctx, tradeCanvas: canvas, theme, viewport } = this;
22547
22558
  ctx.clearRect(0, 0, canvas.width, canvas.height);
@@ -23087,6 +23098,7 @@ var _ChartEngine = class _ChartEngine {
23087
23098
  });
23088
23099
  }
23089
23100
  render() {
23101
+ if (this._isDestroyed) return;
23090
23102
  const { ctx, canvas, theme, viewport } = this;
23091
23103
  const dpr = window.devicePixelRatio || 1;
23092
23104
  const totalW = canvas.width;
@@ -23534,19 +23546,21 @@ var _ChartEngine = class _ChartEngine {
23534
23546
  );
23535
23547
  if (showCountdown) {
23536
23548
  const lastBar2 = this.dataStore.all[this.dataStore.length - 1];
23537
- const intervalMs = this.timeframeToMs(this.timeframe);
23538
- renderCandleCountdown(
23539
- ctx,
23540
- lastBar2.time,
23541
- intervalMs,
23542
- this.dataStore.length - 1,
23543
- viewport,
23544
- this.scaleManager,
23545
- theme,
23546
- chartW,
23547
- subPaneY,
23548
- this.uiConfig.timeAxis
23549
- );
23549
+ if (lastBar2) {
23550
+ const intervalMs = this.timeframeToMs(this.timeframe);
23551
+ renderCandleCountdown(
23552
+ ctx,
23553
+ lastBar2.time,
23554
+ intervalMs,
23555
+ this.dataStore.length - 1,
23556
+ viewport,
23557
+ this.scaleManager,
23558
+ theme,
23559
+ chartW,
23560
+ subPaneY,
23561
+ this.uiConfig.timeAxis
23562
+ );
23563
+ }
23550
23564
  }
23551
23565
  }
23552
23566
  if (this.crosshair) {
@@ -23635,8 +23649,10 @@ var _ChartEngine = class _ChartEngine {
23635
23649
  }
23636
23650
  }
23637
23651
  onResize() {
23652
+ if (this._isDestroyed) return;
23638
23653
  const dpr = window.devicePixelRatio || 1;
23639
23654
  const rect = this.canvasWrap.getBoundingClientRect();
23655
+ if (rect.width === 0 || rect.height === 0) return;
23640
23656
  this._cachedWrapRect = rect;
23641
23657
  this._cachedDrawRect = this.drawCanvas.getBoundingClientRect();
23642
23658
  for (const c of [this.canvas, this.tradeCanvas, this.drawCanvas]) {