acttrader-charts 1.2.0-beta.9 → 1.3.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -6574,6 +6574,21 @@ var MobileToolsSheet = class {
6574
6574
  };
6575
6575
 
6576
6576
  // src/ui/MobileHeader.ts
6577
+ var SERIES_OPTIONS = [
6578
+ { value: "candlestick", label: "Candlestick" },
6579
+ { value: "hollow", label: "Hollow Candles" },
6580
+ { value: "ohlc", label: "OHLC Bars" },
6581
+ { value: "line", label: "Line" },
6582
+ { value: "area", label: "Area" },
6583
+ { value: "heikinashi", label: "Heikin Ashi" },
6584
+ { value: "volumecandles", label: "Volume Candles" },
6585
+ { value: "linemarkers", label: "Line with Markers" },
6586
+ { value: "step", label: "Step Line" },
6587
+ { value: "hlcarea", label: "HLC Area" },
6588
+ { value: "baseline", label: "Baseline" },
6589
+ { value: "columns", label: "Columns" },
6590
+ { value: "highlow", label: "High Low" }
6591
+ ];
6577
6592
  var TF_PILLS3 = ["1m", "5m", "15m", "1h", "4h", "1D", "1W"];
6578
6593
  var TF_LABEL3 = {
6579
6594
  "1m": "1m",
@@ -6618,14 +6633,21 @@ var IC_SETTINGS2 = (sz = 16) => lucide4(
6618
6633
  1.75
6619
6634
  );
6620
6635
  var MobileHeader = class {
6621
- constructor(onTimeframeChange, toolsController, onAskAiClick, onSettingsClick, currentTf, theme, fontFamily) {
6636
+ constructor(onTimeframeChange, toolsController, onAskAiClick, onSettingsClick, onSeriesChange, currentTf, currentSeries, theme, fontFamily) {
6622
6637
  this.toolsBtn = null;
6623
6638
  this.tfBtns = /* @__PURE__ */ new Map();
6624
6639
  this.toolsActive = false;
6640
+ this.seriesBtn = null;
6641
+ this.seriesMenu = null;
6642
+ this.seriesMenuOpen = false;
6643
+ this.onDocPointerDown = null;
6644
+ this.onWinReposition = null;
6625
6645
  this.onTimeframeChange = onTimeframeChange;
6626
6646
  this.onAskAiClick = onAskAiClick;
6627
6647
  this.onSettingsClick = onSettingsClick;
6648
+ this.onSeriesChange = onSeriesChange;
6628
6649
  this.currentTf = currentTf;
6650
+ this.currentSeries = currentSeries;
6629
6651
  this.theme = theme;
6630
6652
  this.fontFamily = fontFamily ?? MONO_FONT2;
6631
6653
  this.sheet = toolsController ? new MobileToolsSheet(theme, toolsController, this.fontFamily) : null;
@@ -6636,9 +6658,11 @@ var MobileHeader = class {
6636
6658
  }
6637
6659
  // ── Build ──────────────────────────────────────────────────────────────────
6638
6660
  build() {
6661
+ this.closeSeriesMenu();
6639
6662
  this.el.innerHTML = "";
6640
6663
  this.tfBtns.clear();
6641
6664
  this.toolsBtn = null;
6665
+ this.seriesBtn = null;
6642
6666
  Object.assign(this.el.style, {
6643
6667
  display: "flex",
6644
6668
  alignItems: "center",
@@ -6692,6 +6716,41 @@ var MobileHeader = class {
6692
6716
  });
6693
6717
  this.el.appendChild(divider);
6694
6718
  }
6719
+ if (this.onSeriesChange) {
6720
+ const btn = document.createElement("button");
6721
+ btn.setAttribute("data-testid", "m-chart-type-btn");
6722
+ btn.title = "Chart type";
6723
+ btn.innerHTML = this.seriesIconHtml(this.currentSeries);
6724
+ Object.assign(btn.style, {
6725
+ display: "flex",
6726
+ alignItems: "center",
6727
+ justifyContent: "center",
6728
+ padding: "6px",
6729
+ borderRadius: "4px",
6730
+ border: "none",
6731
+ whiteSpace: "nowrap",
6732
+ flexShrink: "0",
6733
+ cursor: "pointer",
6734
+ outline: "none",
6735
+ background: "transparent",
6736
+ color: this.theme.axisText,
6737
+ transition: "color 0.15s, background 0.15s"
6738
+ });
6739
+ btn.addEventListener("click", (e) => {
6740
+ e.stopPropagation();
6741
+ this.toggleSeriesMenu();
6742
+ });
6743
+ this.seriesBtn = btn;
6744
+ this.el.appendChild(btn);
6745
+ const d2 = document.createElement("div");
6746
+ Object.assign(d2.style, {
6747
+ width: "1px",
6748
+ height: "20px",
6749
+ background: this.theme.axisBorder,
6750
+ flexShrink: "0"
6751
+ });
6752
+ this.el.appendChild(d2);
6753
+ }
6695
6754
  for (const tf of TF_PILLS3) {
6696
6755
  const btn = document.createElement("button");
6697
6756
  btn.setAttribute("data-testid", `m-tf-${TF_LABEL3[tf] ?? tf}`);
@@ -6792,6 +6851,135 @@ var MobileHeader = class {
6792
6851
  btn.style.color = active ? t.ui.accent : t.topBar.btnColor;
6793
6852
  }
6794
6853
  }
6854
+ // ── Chart-type (series) picker ───────────────────────────────────────────────
6855
+ /** Current-series icon, sized for the header button (mirrors AdvancedToolbar). */
6856
+ seriesIconHtml(series) {
6857
+ const raw = ICON_SERIES[series] ?? ICON_SERIES.candlestick;
6858
+ return raw.replace(/width="\d+" height="\d+"/, 'width="18" height="18"');
6859
+ }
6860
+ toggleSeriesMenu() {
6861
+ if (this.seriesMenuOpen) this.closeSeriesMenu();
6862
+ else this.openSeriesMenu();
6863
+ }
6864
+ openSeriesMenu() {
6865
+ if (this.seriesMenuOpen || !this.seriesBtn || typeof document === "undefined") return;
6866
+ this.seriesMenu = this.buildSeriesMenu();
6867
+ document.body.appendChild(this.seriesMenu);
6868
+ this.seriesMenuOpen = true;
6869
+ this.positionSeriesMenu();
6870
+ this.styleSeriesBtnActive(true);
6871
+ this.onDocPointerDown = (e) => {
6872
+ const t = e.target;
6873
+ if (this.seriesMenu?.contains(t) || this.seriesBtn?.contains(t)) return;
6874
+ this.closeSeriesMenu();
6875
+ };
6876
+ this.onWinReposition = () => this.closeSeriesMenu();
6877
+ setTimeout(() => {
6878
+ if (!this.seriesMenuOpen) return;
6879
+ document.addEventListener("pointerdown", this.onDocPointerDown, true);
6880
+ window.addEventListener("resize", this.onWinReposition, true);
6881
+ window.addEventListener("scroll", this.onWinReposition, true);
6882
+ this.el.addEventListener("scroll", this.onWinReposition, true);
6883
+ }, 0);
6884
+ }
6885
+ closeSeriesMenu() {
6886
+ if (this.onDocPointerDown) {
6887
+ document.removeEventListener("pointerdown", this.onDocPointerDown, true);
6888
+ this.onDocPointerDown = null;
6889
+ }
6890
+ if (this.onWinReposition) {
6891
+ window.removeEventListener("resize", this.onWinReposition, true);
6892
+ window.removeEventListener("scroll", this.onWinReposition, true);
6893
+ this.el.removeEventListener("scroll", this.onWinReposition, true);
6894
+ this.onWinReposition = null;
6895
+ }
6896
+ this.seriesMenu?.remove();
6897
+ this.seriesMenu = null;
6898
+ this.seriesMenuOpen = false;
6899
+ this.styleSeriesBtnActive(false);
6900
+ }
6901
+ buildSeriesMenu() {
6902
+ const t = this.theme;
6903
+ const menu = document.createElement("div");
6904
+ menu.setAttribute("data-testid", "m-chart-type-menu");
6905
+ Object.assign(menu.style, {
6906
+ position: "fixed",
6907
+ zIndex: "2147483000",
6908
+ minWidth: "184px",
6909
+ maxHeight: "60vh",
6910
+ overflowY: "auto",
6911
+ padding: "4px",
6912
+ borderRadius: "8px",
6913
+ border: `1px solid ${t.axisBorder}`,
6914
+ background: t.background,
6915
+ boxShadow: "0 8px 28px rgba(0,0,0,0.35)",
6916
+ fontFamily: this.fontFamily,
6917
+ WebkitOverflowScrolling: "touch"
6918
+ });
6919
+ for (const opt of SERIES_OPTIONS) {
6920
+ const active = opt.value === this.currentSeries;
6921
+ const row = document.createElement("button");
6922
+ row.type = "button";
6923
+ row.setAttribute("data-testid", `m-chart-type-${opt.value}`);
6924
+ Object.assign(row.style, {
6925
+ display: "flex",
6926
+ alignItems: "center",
6927
+ gap: "10px",
6928
+ width: "100%",
6929
+ padding: "9px 10px",
6930
+ borderRadius: "6px",
6931
+ border: "none",
6932
+ cursor: "pointer",
6933
+ outline: "none",
6934
+ textAlign: "left",
6935
+ fontSize: "13px",
6936
+ fontFamily: this.fontFamily,
6937
+ background: active ? t.ui.accentBg : "transparent",
6938
+ color: active ? t.ui.accent : t.axisText
6939
+ });
6940
+ const icon = (ICON_SERIES[opt.value] ?? "").replace(/width="\d+" height="\d+"/, 'width="16" height="16"');
6941
+ const check = active ? ICON_CHECKMARK.replace(/width="\d+" height="\d+"/, 'width="16" height="16"') : "";
6942
+ row.innerHTML = `<span style="display:flex;flex-shrink:0;">${icon}</span><span style="flex:1;">${opt.label}</span><span style="display:flex;flex-shrink:0;width:16px;">${check}</span>`;
6943
+ row.addEventListener("mouseenter", () => {
6944
+ if (opt.value !== this.currentSeries) row.style.background = t.ui.accentBg;
6945
+ });
6946
+ row.addEventListener("mouseleave", () => {
6947
+ if (opt.value !== this.currentSeries) row.style.background = "transparent";
6948
+ });
6949
+ row.addEventListener("click", (e) => {
6950
+ e.stopPropagation();
6951
+ this.selectSeries(opt.value);
6952
+ });
6953
+ menu.appendChild(row);
6954
+ }
6955
+ return menu;
6956
+ }
6957
+ positionSeriesMenu() {
6958
+ if (!this.seriesMenu || !this.seriesBtn) return;
6959
+ const r = this.seriesBtn.getBoundingClientRect();
6960
+ const m = this.seriesMenu;
6961
+ m.style.top = `${r.bottom + 6}px`;
6962
+ m.style.left = `${r.left}px`;
6963
+ const mr = m.getBoundingClientRect();
6964
+ if (mr.right > window.innerWidth - 8) {
6965
+ m.style.left = `${Math.max(8, window.innerWidth - 8 - mr.width)}px`;
6966
+ }
6967
+ if (mr.bottom > window.innerHeight - 8) {
6968
+ m.style.top = `${Math.max(8, r.top - 6 - mr.height)}px`;
6969
+ }
6970
+ }
6971
+ selectSeries(series) {
6972
+ this.closeSeriesMenu();
6973
+ if (series === this.currentSeries) return;
6974
+ this.currentSeries = series;
6975
+ if (this.seriesBtn) this.seriesBtn.innerHTML = this.seriesIconHtml(series);
6976
+ this.onSeriesChange?.(series);
6977
+ }
6978
+ styleSeriesBtnActive(active) {
6979
+ if (!this.seriesBtn) return;
6980
+ this.seriesBtn.style.color = active ? this.theme.ui.accent : this.theme.axisText;
6981
+ this.seriesBtn.style.background = active ? this.theme.ui.accentBg : "transparent";
6982
+ }
6795
6983
  // ── Public surface (drop-in parity with CompactToolbar) ──────────────────────
6796
6984
  setTimeframe(tf) {
6797
6985
  this.currentTf = tf;
@@ -6809,10 +6997,12 @@ var MobileHeader = class {
6809
6997
  this.sheet?.setTheme(theme);
6810
6998
  }
6811
6999
  destroy() {
7000
+ this.closeSeriesMenu();
6812
7001
  this.sheet?.destroy();
6813
7002
  this.el.remove();
6814
7003
  this.tfBtns.clear();
6815
7004
  this.toolsBtn = null;
7005
+ this.seriesBtn = null;
6816
7006
  }
6817
7007
  // ── Forwarded to the "Draw & Indicators" sheet so its toggles stay in sync ───
6818
7008
  /**
@@ -6828,15 +7018,24 @@ var MobileHeader = class {
6828
7018
  this.sheet?.syncVolume(show);
6829
7019
  }
6830
7020
  // ── No-op stubs — controls this variant intentionally does not render ────────
6831
- setSeries(_series) {
7021
+ /** Engine → header sync: reflect an external series change (restored layout
7022
+ * state, keyboard shortcut, etc.) on the chart-type button. */
7023
+ setSeries(series) {
7024
+ this.currentSeries = series;
7025
+ if (this.seriesBtn) this.seriesBtn.innerHTML = this.seriesIconHtml(series);
7026
+ if (this.seriesMenuOpen) {
7027
+ this.closeSeriesMenu();
7028
+ this.openSeriesMenu();
7029
+ }
6832
7030
  }
6833
7031
  setIndicatorOptions(_options) {
6834
7032
  }
6835
7033
  closeDropdown() {
6836
7034
  this.sheet?.close();
7035
+ this.closeSeriesMenu();
6837
7036
  }
6838
7037
  get hasOpenDropdown() {
6839
- return this.sheet?.isOpen ?? false;
7038
+ return (this.sheet?.isOpen ?? false) || this.seriesMenuOpen;
6840
7039
  }
6841
7040
  setFullscreen(_active) {
6842
7041
  }
@@ -23726,7 +23925,9 @@ var _ChartEngine = class _ChartEngine {
23726
23925
  toolsController,
23727
23926
  config.onAskAiClick ?? null,
23728
23927
  this.settingsEnabled ? () => this.openChartSettings() : null,
23928
+ (s) => this.setSeries(s),
23729
23929
  this.timeframe,
23930
+ this.series,
23730
23931
  this.theme,
23731
23932
  this.uiConfig.topBar?.dropBtnFontFamily
23732
23933
  );