acttrader-charts 1.2.0-beta.8 → 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/README.md +24 -0
- package/dist/{MACD-wDy-dPD_.d.cts → MACD-D1i-sEFR.d.cts} +9 -2
- package/dist/{MACD-wDy-dPD_.d.ts → MACD-D1i-sEFR.d.ts} +9 -2
- package/dist/index.cjs +331 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +331 -19
- package/dist/index.js.map +1 -1
- package/dist/indicators/index.d.cts +2 -2
- package/dist/indicators/index.d.ts +2 -2
- package/dist/webview/chart.html +11 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -892,6 +892,30 @@ Sub-pane heights are user-resizable by dragging the separator between panes.
|
|
|
892
892
|
|
|
893
893
|
`fibRetracement` · `fibExtension` · `fibChannel` · `fibTimezone` · `fibCircles` · `fibSpiral` · `fibFan` · `fibProjection` · `gannFan` · `gannSquare` · `gannBox`
|
|
894
894
|
|
|
895
|
+
**Editable Fib Retracement ratios.** Select a Fib Retracement and open **LEVELS → Configure ▾** in the style popover: each row's ratio is an editable field alongside its visibility checkbox and color swatch. Type a new ratio and press Enter (or click away) and the line moves to it, keeping that row's color and visibility. The number of levels never changes — a value that isn't a finite number, or that another row already uses, reverts to the previous one.
|
|
896
|
+
|
|
897
|
+
Defaults come from the theme (`0` · `0.236` · `0.382` · `0.5` · `0.618` · `0.786` · `1`) and can be changed per theme without touching a drawing:
|
|
898
|
+
|
|
899
|
+
```ts
|
|
900
|
+
new ChartEngine({
|
|
901
|
+
container,
|
|
902
|
+
themeOverrides: {
|
|
903
|
+
dark: {
|
|
904
|
+
drawing: {
|
|
905
|
+
fibRetracement: {
|
|
906
|
+
levels: {
|
|
907
|
+
'0.786': { visible: false, color: '#e8b84b' }, // hide a default ratio
|
|
908
|
+
'0.886': { visible: true, color: '#e8b84b' }, // and draw this one instead
|
|
909
|
+
},
|
|
910
|
+
},
|
|
911
|
+
},
|
|
912
|
+
},
|
|
913
|
+
},
|
|
914
|
+
});
|
|
915
|
+
```
|
|
916
|
+
|
|
917
|
+
In a **per-drawing** style (`SerializedDrawing.style.levels`), a level may also be set to `null` — that removes a theme-supplied ratio for that one drawing, which is how a retyped ratio replaces its original rather than adding to it. Ratios are map keys, so they are written in canonical number form (`'0.5'`, not `'0.50'`).
|
|
918
|
+
|
|
895
919
|
### Shapes
|
|
896
920
|
|
|
897
921
|
`rectangle` · `rotatedRectangle` · `ellipse` · `triangle` · `circle` · `arc` · `polyline` · `path` · `brush` · `highlighter`
|
|
@@ -1254,8 +1254,15 @@ interface DrawingLevelStyle {
|
|
|
1254
1254
|
}
|
|
1255
1255
|
/** Extends DrawingStyle for fib/multi-level drawings. */
|
|
1256
1256
|
interface DrawingFibStyle extends DrawingStyle {
|
|
1257
|
-
/**
|
|
1258
|
-
|
|
1257
|
+
/**
|
|
1258
|
+
* Map of level value (as string, e.g. '0', '0.236', '0.5', '1') → per-level style.
|
|
1259
|
+
*
|
|
1260
|
+
* In a **per-instance** style, `null` marks a level supplied by the theme as
|
|
1261
|
+
* removed for this drawing — used when the user retypes a level's ratio in
|
|
1262
|
+
* the LEVELS → Configure panel, so the old ratio does not come back through
|
|
1263
|
+
* the theme merge. Resolved styles (from `resolveStyle`) never contain null.
|
|
1264
|
+
*/
|
|
1265
|
+
levels?: Record<string, DrawingLevelStyle | null>;
|
|
1259
1266
|
}
|
|
1260
1267
|
/** Union of all drawing style shapes. */
|
|
1261
1268
|
type AnyDrawingStyle = DrawingStyle | DrawingTextStyle | DrawingFibStyle;
|
|
@@ -1254,8 +1254,15 @@ interface DrawingLevelStyle {
|
|
|
1254
1254
|
}
|
|
1255
1255
|
/** Extends DrawingStyle for fib/multi-level drawings. */
|
|
1256
1256
|
interface DrawingFibStyle extends DrawingStyle {
|
|
1257
|
-
/**
|
|
1258
|
-
|
|
1257
|
+
/**
|
|
1258
|
+
* Map of level value (as string, e.g. '0', '0.236', '0.5', '1') → per-level style.
|
|
1259
|
+
*
|
|
1260
|
+
* In a **per-instance** style, `null` marks a level supplied by the theme as
|
|
1261
|
+
* removed for this drawing — used when the user retypes a level's ratio in
|
|
1262
|
+
* the LEVELS → Configure panel, so the old ratio does not come back through
|
|
1263
|
+
* the theme merge. Resolved styles (from `resolveStyle`) never contain null.
|
|
1264
|
+
*/
|
|
1265
|
+
levels?: Record<string, DrawingLevelStyle | null>;
|
|
1259
1266
|
}
|
|
1260
1267
|
/** Union of all drawing style shapes. */
|
|
1261
1268
|
type AnyDrawingStyle = DrawingStyle | DrawingTextStyle | DrawingFibStyle;
|
package/dist/index.cjs
CHANGED
|
@@ -318,6 +318,7 @@ function resolveDrawingThemeVars(drawing, resolve) {
|
|
|
318
318
|
const levels = {};
|
|
319
319
|
for (const lk of Object.keys(resolved.levels)) {
|
|
320
320
|
const lv = resolved.levels[lk];
|
|
321
|
+
if (!lv) continue;
|
|
321
322
|
levels[lk] = { ...lv, color: resolve(lv.color) };
|
|
322
323
|
}
|
|
323
324
|
resolved.levels = levels;
|
|
@@ -6573,6 +6574,21 @@ var MobileToolsSheet = class {
|
|
|
6573
6574
|
};
|
|
6574
6575
|
|
|
6575
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
|
+
];
|
|
6576
6592
|
var TF_PILLS3 = ["1m", "5m", "15m", "1h", "4h", "1D", "1W"];
|
|
6577
6593
|
var TF_LABEL3 = {
|
|
6578
6594
|
"1m": "1m",
|
|
@@ -6617,14 +6633,21 @@ var IC_SETTINGS2 = (sz = 16) => lucide4(
|
|
|
6617
6633
|
1.75
|
|
6618
6634
|
);
|
|
6619
6635
|
var MobileHeader = class {
|
|
6620
|
-
constructor(onTimeframeChange, toolsController, onAskAiClick, onSettingsClick, currentTf, theme, fontFamily) {
|
|
6636
|
+
constructor(onTimeframeChange, toolsController, onAskAiClick, onSettingsClick, onSeriesChange, currentTf, currentSeries, theme, fontFamily) {
|
|
6621
6637
|
this.toolsBtn = null;
|
|
6622
6638
|
this.tfBtns = /* @__PURE__ */ new Map();
|
|
6623
6639
|
this.toolsActive = false;
|
|
6640
|
+
this.seriesBtn = null;
|
|
6641
|
+
this.seriesMenu = null;
|
|
6642
|
+
this.seriesMenuOpen = false;
|
|
6643
|
+
this.onDocPointerDown = null;
|
|
6644
|
+
this.onWinReposition = null;
|
|
6624
6645
|
this.onTimeframeChange = onTimeframeChange;
|
|
6625
6646
|
this.onAskAiClick = onAskAiClick;
|
|
6626
6647
|
this.onSettingsClick = onSettingsClick;
|
|
6648
|
+
this.onSeriesChange = onSeriesChange;
|
|
6627
6649
|
this.currentTf = currentTf;
|
|
6650
|
+
this.currentSeries = currentSeries;
|
|
6628
6651
|
this.theme = theme;
|
|
6629
6652
|
this.fontFamily = fontFamily ?? MONO_FONT2;
|
|
6630
6653
|
this.sheet = toolsController ? new MobileToolsSheet(theme, toolsController, this.fontFamily) : null;
|
|
@@ -6635,9 +6658,11 @@ var MobileHeader = class {
|
|
|
6635
6658
|
}
|
|
6636
6659
|
// ── Build ──────────────────────────────────────────────────────────────────
|
|
6637
6660
|
build() {
|
|
6661
|
+
this.closeSeriesMenu();
|
|
6638
6662
|
this.el.innerHTML = "";
|
|
6639
6663
|
this.tfBtns.clear();
|
|
6640
6664
|
this.toolsBtn = null;
|
|
6665
|
+
this.seriesBtn = null;
|
|
6641
6666
|
Object.assign(this.el.style, {
|
|
6642
6667
|
display: "flex",
|
|
6643
6668
|
alignItems: "center",
|
|
@@ -6691,6 +6716,41 @@ var MobileHeader = class {
|
|
|
6691
6716
|
});
|
|
6692
6717
|
this.el.appendChild(divider);
|
|
6693
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
|
+
}
|
|
6694
6754
|
for (const tf of TF_PILLS3) {
|
|
6695
6755
|
const btn = document.createElement("button");
|
|
6696
6756
|
btn.setAttribute("data-testid", `m-tf-${TF_LABEL3[tf] ?? tf}`);
|
|
@@ -6791,6 +6851,135 @@ var MobileHeader = class {
|
|
|
6791
6851
|
btn.style.color = active ? t.ui.accent : t.topBar.btnColor;
|
|
6792
6852
|
}
|
|
6793
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
|
+
}
|
|
6794
6983
|
// ── Public surface (drop-in parity with CompactToolbar) ──────────────────────
|
|
6795
6984
|
setTimeframe(tf) {
|
|
6796
6985
|
this.currentTf = tf;
|
|
@@ -6808,10 +6997,12 @@ var MobileHeader = class {
|
|
|
6808
6997
|
this.sheet?.setTheme(theme);
|
|
6809
6998
|
}
|
|
6810
6999
|
destroy() {
|
|
7000
|
+
this.closeSeriesMenu();
|
|
6811
7001
|
this.sheet?.destroy();
|
|
6812
7002
|
this.el.remove();
|
|
6813
7003
|
this.tfBtns.clear();
|
|
6814
7004
|
this.toolsBtn = null;
|
|
7005
|
+
this.seriesBtn = null;
|
|
6815
7006
|
}
|
|
6816
7007
|
// ── Forwarded to the "Draw & Indicators" sheet so its toggles stay in sync ───
|
|
6817
7008
|
/**
|
|
@@ -6827,15 +7018,24 @@ var MobileHeader = class {
|
|
|
6827
7018
|
this.sheet?.syncVolume(show);
|
|
6828
7019
|
}
|
|
6829
7020
|
// ── No-op stubs — controls this variant intentionally does not render ────────
|
|
6830
|
-
|
|
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
|
+
}
|
|
6831
7030
|
}
|
|
6832
7031
|
setIndicatorOptions(_options) {
|
|
6833
7032
|
}
|
|
6834
7033
|
closeDropdown() {
|
|
6835
7034
|
this.sheet?.close();
|
|
7035
|
+
this.closeSeriesMenu();
|
|
6836
7036
|
}
|
|
6837
7037
|
get hasOpenDropdown() {
|
|
6838
|
-
return this.sheet?.isOpen ?? false;
|
|
7038
|
+
return (this.sheet?.isOpen ?? false) || this.seriesMenuOpen;
|
|
6839
7039
|
}
|
|
6840
7040
|
setFullscreen(_active) {
|
|
6841
7041
|
}
|
|
@@ -8531,10 +8731,19 @@ function resolveStyle(type, instanceStyle, theme) {
|
|
|
8531
8731
|
const themeLevels = themeEntry.levels;
|
|
8532
8732
|
const instLevels = instanceStyle?.levels;
|
|
8533
8733
|
if (themeLevels || instLevels) {
|
|
8534
|
-
const out = {
|
|
8734
|
+
const out = {};
|
|
8735
|
+
for (const k of Object.keys(themeLevels ?? {})) {
|
|
8736
|
+
const lvl = themeLevels?.[k];
|
|
8737
|
+
if (lvl) out[k] = lvl;
|
|
8738
|
+
}
|
|
8535
8739
|
if (instLevels) {
|
|
8536
8740
|
for (const k of Object.keys(instLevels)) {
|
|
8537
|
-
|
|
8741
|
+
const inst = instLevels[k];
|
|
8742
|
+
if (inst === null) {
|
|
8743
|
+
delete out[k];
|
|
8744
|
+
continue;
|
|
8745
|
+
}
|
|
8746
|
+
out[k] = { ...out[k] ?? { visible: true, color: "#888888" }, ...inst };
|
|
8538
8747
|
}
|
|
8539
8748
|
}
|
|
8540
8749
|
merged.levels = out;
|
|
@@ -9155,6 +9364,12 @@ var DisjointChannel = class {
|
|
|
9155
9364
|
var FIB_LEVELS = [0, 0.236, 0.382, 0.5, 0.618, 0.786, 1];
|
|
9156
9365
|
var FIB_FALLBACK_COLORS = ["#ef5350", "#e8b84b", "#26a69a", "#2196f3", "#26a69a", "#e8b84b", "#ef5350"];
|
|
9157
9366
|
var HIT_TOLERANCE9 = 6;
|
|
9367
|
+
function fibLevelEntries(levels) {
|
|
9368
|
+
const fallback = () => FIB_LEVELS.map((value) => ({ key: String(value), value }));
|
|
9369
|
+
if (!levels) return fallback();
|
|
9370
|
+
const entries = Object.keys(levels).filter((key) => levels[key] !== null).map((key) => ({ key, value: Number(key) })).filter((e) => Number.isFinite(e.value));
|
|
9371
|
+
return entries.length ? entries.sort((a, b) => a.value - b.value) : fallback();
|
|
9372
|
+
}
|
|
9158
9373
|
var FibRetracement = class {
|
|
9159
9374
|
constructor(first) {
|
|
9160
9375
|
this.type = "fibRetracement";
|
|
@@ -9180,14 +9395,16 @@ var FibRetracement = class {
|
|
|
9180
9395
|
ctx.save();
|
|
9181
9396
|
ctx.font = "10px 'Inter', system-ui, sans-serif";
|
|
9182
9397
|
ctx.lineWidth = baseLineWidth;
|
|
9183
|
-
|
|
9184
|
-
|
|
9185
|
-
|
|
9398
|
+
const entries = fibLevelEntries(s.levels);
|
|
9399
|
+
const drawn = [];
|
|
9400
|
+
for (let i = 0; i < entries.length; i++) {
|
|
9401
|
+
const { key, value: level } = entries[i];
|
|
9186
9402
|
const lvl = s.levels?.[key];
|
|
9187
9403
|
if (lvl?.visible === false) continue;
|
|
9404
|
+
drawn.push(level);
|
|
9188
9405
|
const price = highPrice - range * level;
|
|
9189
9406
|
const y = scale.yToPixel(price, priceRange, chartH);
|
|
9190
|
-
const baseColor = lvl?.color ?? s.strokeColor ?? FIB_FALLBACK_COLORS[i];
|
|
9407
|
+
const baseColor = lvl?.color ?? s.strokeColor ?? FIB_FALLBACK_COLORS[i % FIB_FALLBACK_COLORS.length];
|
|
9191
9408
|
const color = hexWithAlpha(baseColor, isSelected ? 0.8 : 0.6);
|
|
9192
9409
|
ctx.strokeStyle = color;
|
|
9193
9410
|
applyLineStyle(ctx, isSelected ? "solid" : s.lineStyle, baseLineWidth);
|
|
@@ -9201,6 +9418,7 @@ var FibRetracement = class {
|
|
|
9201
9418
|
ctx.fillStyle = color;
|
|
9202
9419
|
ctx.fillText(lbl, xMin + 4, y - 3);
|
|
9203
9420
|
}
|
|
9421
|
+
this.visibleLevels = drawn;
|
|
9204
9422
|
for (const [ax, ay] of [[x1, y1], [x22, y22]]) {
|
|
9205
9423
|
ctx.beginPath();
|
|
9206
9424
|
ctx.arc(ax, ay, isSelected ? 4 : 3, 0, Math.PI * 2);
|
|
@@ -9214,7 +9432,7 @@ var FibRetracement = class {
|
|
|
9214
9432
|
const highPrice = Math.max(this.points[0].price, this.points[1].price);
|
|
9215
9433
|
const lowPrice = Math.min(this.points[0].price, this.points[1].price);
|
|
9216
9434
|
const range = highPrice - lowPrice;
|
|
9217
|
-
for (const level of FIB_LEVELS) {
|
|
9435
|
+
for (const level of this.visibleLevels ?? FIB_LEVELS) {
|
|
9218
9436
|
const price = highPrice - range * level;
|
|
9219
9437
|
const lineY = scale.yToPixel(price, priceRange, chartH);
|
|
9220
9438
|
if (Math.abs(y - lineY) <= HIT_TOLERANCE9) return true;
|
|
@@ -14800,9 +15018,9 @@ var DrawingManager = class {
|
|
|
14800
15018
|
updateDrawingStyle(id, patch) {
|
|
14801
15019
|
const drawing = this.getDrawingById(id);
|
|
14802
15020
|
if (!drawing) return null;
|
|
15021
|
+
const existingLevels = drawing.style?.levels;
|
|
14803
15022
|
drawing.style = { ...drawing.style ?? {}, ...patch };
|
|
14804
15023
|
const patchLevels = patch.levels;
|
|
14805
|
-
const existingLevels = drawing.style.levels;
|
|
14806
15024
|
if (patchLevels && existingLevels) {
|
|
14807
15025
|
drawing.style.levels = {
|
|
14808
15026
|
...existingLevels,
|
|
@@ -16854,6 +17072,23 @@ var POPOVER_CONTROLS = {
|
|
|
16854
17072
|
var LINE_WIDTHS = [1, 2, 3, 4];
|
|
16855
17073
|
var FONT_SIZES = [10, 11, 12, 13, 14, 16, 18, 20, 24];
|
|
16856
17074
|
var SWATCH_PX = 24;
|
|
17075
|
+
var EDITABLE_LEVEL_TYPES = /* @__PURE__ */ new Set(["fibRetracement"]);
|
|
17076
|
+
function mergeLevels(themeLevels, instLevels) {
|
|
17077
|
+
const out = {};
|
|
17078
|
+
for (const k of Object.keys(themeLevels ?? {})) {
|
|
17079
|
+
const lvl = themeLevels?.[k];
|
|
17080
|
+
if (lvl) out[k] = lvl;
|
|
17081
|
+
}
|
|
17082
|
+
for (const k of Object.keys(instLevels ?? {})) {
|
|
17083
|
+
const lvl = instLevels?.[k];
|
|
17084
|
+
if (lvl === null) delete out[k];
|
|
17085
|
+
else if (lvl) out[k] = { ...out[k] ?? { visible: true, color: "#888888" }, ...lvl };
|
|
17086
|
+
}
|
|
17087
|
+
return out;
|
|
17088
|
+
}
|
|
17089
|
+
function levelKey(value) {
|
|
17090
|
+
return String(value);
|
|
17091
|
+
}
|
|
16857
17092
|
var DrawingStylePopover = class {
|
|
16858
17093
|
constructor(container, theme, onChange) {
|
|
16859
17094
|
this.currentDrawing = null;
|
|
@@ -16998,7 +17233,9 @@ var DrawingStylePopover = class {
|
|
|
16998
17233
|
case "fibLevels":
|
|
16999
17234
|
groups.push(this.group(
|
|
17000
17235
|
"Levels",
|
|
17001
|
-
this.buildFibLevelsButton(
|
|
17236
|
+
this.buildFibLevelsButton(
|
|
17237
|
+
mergeLevels(themeEntry.levels, style.levels)
|
|
17238
|
+
)
|
|
17002
17239
|
));
|
|
17003
17240
|
break;
|
|
17004
17241
|
}
|
|
@@ -17198,6 +17435,65 @@ var DrawingStylePopover = class {
|
|
|
17198
17435
|
});
|
|
17199
17436
|
return wrap;
|
|
17200
17437
|
}
|
|
17438
|
+
/** Static ratio text — used for level sets whose ratios are fixed. */
|
|
17439
|
+
buildLevelRatioLabel(key) {
|
|
17440
|
+
const label = document.createElement("span");
|
|
17441
|
+
label.textContent = key;
|
|
17442
|
+
Object.assign(label.style, { fontSize: "11px", color: this.theme.tooltip.text });
|
|
17443
|
+
return label;
|
|
17444
|
+
}
|
|
17445
|
+
/**
|
|
17446
|
+
* Editable ratio field (TradingView-style): the user retypes a level's ratio
|
|
17447
|
+
* and the line moves, keeping its color and visibility. Commits on Enter or
|
|
17448
|
+
* blur; reverts when the value isn't a finite number or already belongs to
|
|
17449
|
+
* another row, so the level count never changes.
|
|
17450
|
+
*/
|
|
17451
|
+
buildLevelRatioInput(initialKey, taken, onCommit) {
|
|
17452
|
+
let current = initialKey;
|
|
17453
|
+
const input = document.createElement("input");
|
|
17454
|
+
input.type = "number";
|
|
17455
|
+
input.step = "0.001";
|
|
17456
|
+
input.value = current;
|
|
17457
|
+
input.title = "Level ratio \u2014 press Enter to apply";
|
|
17458
|
+
Object.assign(input.style, {
|
|
17459
|
+
width: "58px",
|
|
17460
|
+
padding: "2px 4px",
|
|
17461
|
+
fontSize: "11px",
|
|
17462
|
+
fontFamily: "inherit",
|
|
17463
|
+
color: this.theme.tooltip.text,
|
|
17464
|
+
background: this.theme.background,
|
|
17465
|
+
border: `1px solid ${this.theme.axisBorder}`,
|
|
17466
|
+
borderRadius: "3px",
|
|
17467
|
+
outline: "none"
|
|
17468
|
+
});
|
|
17469
|
+
input.addEventListener("focus", () => {
|
|
17470
|
+
input.style.borderColor = this.theme.ui.accent;
|
|
17471
|
+
});
|
|
17472
|
+
input.addEventListener("blur", () => {
|
|
17473
|
+
input.style.borderColor = this.theme.axisBorder;
|
|
17474
|
+
});
|
|
17475
|
+
input.addEventListener("keydown", (e) => {
|
|
17476
|
+
e.stopPropagation();
|
|
17477
|
+
if (e.key === "Enter") input.blur();
|
|
17478
|
+
});
|
|
17479
|
+
const commit = () => {
|
|
17480
|
+
const raw = Number(input.value);
|
|
17481
|
+
const next = Number.isFinite(raw) ? levelKey(raw) : null;
|
|
17482
|
+
if (next === null || next !== current && taken.has(next)) {
|
|
17483
|
+
input.value = current;
|
|
17484
|
+
return;
|
|
17485
|
+
}
|
|
17486
|
+
if (next === current) {
|
|
17487
|
+
input.value = current;
|
|
17488
|
+
return;
|
|
17489
|
+
}
|
|
17490
|
+
current = next;
|
|
17491
|
+
input.value = next;
|
|
17492
|
+
onCommit(next);
|
|
17493
|
+
};
|
|
17494
|
+
input.addEventListener("change", commit);
|
|
17495
|
+
return input;
|
|
17496
|
+
}
|
|
17201
17497
|
openFibPanel(anchor, levels) {
|
|
17202
17498
|
this.closeFibPanel();
|
|
17203
17499
|
const panel = document.createElement("div");
|
|
@@ -17219,23 +17515,37 @@ var DrawingStylePopover = class {
|
|
|
17219
17515
|
alignItems: "center"
|
|
17220
17516
|
});
|
|
17221
17517
|
const keys = Object.keys(levels).sort((a, b) => Number(a) - Number(b));
|
|
17518
|
+
const editable = this.currentDrawing !== null && EDITABLE_LEVEL_TYPES.has(this.currentDrawing.type);
|
|
17519
|
+
const taken = new Set(keys);
|
|
17222
17520
|
for (const k of keys) {
|
|
17223
17521
|
const level = levels[k];
|
|
17522
|
+
let key = k;
|
|
17523
|
+
let color = level.color;
|
|
17224
17524
|
const cb = document.createElement("input");
|
|
17225
17525
|
cb.type = "checkbox";
|
|
17226
17526
|
cb.checked = level.visible !== false;
|
|
17227
17527
|
cb.style.accentColor = this.theme.ui.accent;
|
|
17228
17528
|
cb.style.cursor = "pointer";
|
|
17229
|
-
const
|
|
17230
|
-
|
|
17231
|
-
|
|
17232
|
-
const swatch = this.makeColorSwatch(level.color, (hex) => {
|
|
17233
|
-
this.emit({ levels: { [k]: { visible: cb.checked, color: hex } } });
|
|
17529
|
+
const swatch = this.makeColorSwatch(color, (hex) => {
|
|
17530
|
+
color = hex;
|
|
17531
|
+
this.emit({ levels: { [key]: { visible: cb.checked, color: hex } } });
|
|
17234
17532
|
});
|
|
17235
17533
|
cb.addEventListener("change", () => {
|
|
17236
|
-
this.emit({ levels: { [
|
|
17534
|
+
this.emit({ levels: { [key]: { visible: cb.checked, color } } });
|
|
17237
17535
|
});
|
|
17238
|
-
panel.append(cb,
|
|
17536
|
+
panel.append(cb, editable ? this.buildLevelRatioInput(
|
|
17537
|
+
k,
|
|
17538
|
+
taken,
|
|
17539
|
+
(nextKey) => {
|
|
17540
|
+
this.emit({ levels: {
|
|
17541
|
+
[key]: null,
|
|
17542
|
+
[nextKey]: { visible: cb.checked, color }
|
|
17543
|
+
} });
|
|
17544
|
+
taken.delete(key);
|
|
17545
|
+
taken.add(nextKey);
|
|
17546
|
+
key = nextKey;
|
|
17547
|
+
}
|
|
17548
|
+
) : this.buildLevelRatioLabel(k), swatch);
|
|
17239
17549
|
}
|
|
17240
17550
|
const anchorRect = anchor.getBoundingClientRect();
|
|
17241
17551
|
const cRect = this.container.getBoundingClientRect();
|
|
@@ -23615,7 +23925,9 @@ var _ChartEngine = class _ChartEngine {
|
|
|
23615
23925
|
toolsController,
|
|
23616
23926
|
config.onAskAiClick ?? null,
|
|
23617
23927
|
this.settingsEnabled ? () => this.openChartSettings() : null,
|
|
23928
|
+
(s) => this.setSeries(s),
|
|
23618
23929
|
this.timeframe,
|
|
23930
|
+
this.series,
|
|
23619
23931
|
this.theme,
|
|
23620
23932
|
this.uiConfig.topBar?.dropBtnFontFamily
|
|
23621
23933
|
);
|