@luxalgo/vela 0.5.0 → 0.5.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/{DataProvider-DKNDHpNv.d.cts → DataProvider-BBQ3Hr21.d.cts} +1 -1
- package/dist/{DataProvider-Dzd-Erlk.d.ts → DataProvider-DGesjOJT.d.ts} +1 -1
- package/dist/{chunk-OVQKKXLZ.js → chunk-5SE4PL3H.js} +402 -60
- package/dist/{chunk-RHIDOUFL.js → chunk-AIW3EJUE.js} +2 -2
- package/dist/{chunk-GYD2THPV.js → chunk-EP6NDJYK.js} +5 -0
- package/dist/{chunk-KCCZNKH7.js → chunk-FNOMKHEZ.js} +668 -78
- package/dist/{chunk-7UFX5ZIG.js → chunk-S6F6SQ2L.js} +7 -2
- package/dist/{chunk-KM6LHB3Y.js → chunk-ULIX6AE2.js} +6 -1
- package/dist/{contributions-hX3EUyjG.d.ts → contributions-6N0rbrPC.d.ts} +46 -31
- package/dist/{contributions-o1GRKPI_.d.cts → contributions-BlV3kb0O.d.cts} +46 -31
- package/dist/{history-Dzxz-MQj.d.ts → history-BTd52YMA.d.ts} +10 -5
- package/dist/{history-LJkz4-sS.d.cts → history-DX3Zne3a.d.cts} +10 -5
- package/dist/index.cjs +680 -75
- package/dist/index.d.cts +37 -12
- package/dist/index.d.ts +37 -12
- package/dist/index.js +4 -4
- package/dist/{options-Q-576hIi.d.cts → options-DbQholFV.d.cts} +14 -0
- package/dist/{options-Q-576hIi.d.ts → options-DbQholFV.d.ts} +14 -0
- package/dist/{plugin-D94muTV-.d.cts → plugin-O34MbXf-.d.cts} +157 -7
- package/dist/{plugin-aGUD1epn.d.ts → plugin-TN3n-9YN.d.ts} +157 -7
- package/dist/plugin.cjs +5 -0
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/ui.cjs +10 -0
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +680 -75
- package/dist/vela.global.min.js +59 -28
- package/dist/widget.cjs +1081 -134
- package/dist/widget.d.cts +30 -16
- package/dist/widget.d.ts +30 -16
- package/dist/widget.js +9 -9
- package/dist/workspace.cjs +1372 -183
- package/dist/workspace.d.cts +66 -8
- package/dist/workspace.d.ts +66 -8
- package/dist/workspace.js +275 -37
- package/package.json +1 -1
package/dist/workspace.cjs
CHANGED
|
@@ -881,6 +881,11 @@ function themeTokens(t) {
|
|
|
881
881
|
"--vela-hover-strong": wash(0.16),
|
|
882
882
|
"--vela-focus": withAlpha(t.textColor, 0.5),
|
|
883
883
|
"--vela-focus-soft": withAlpha(t.textColor, 0.12),
|
|
884
|
+
// Separator hover — the SAME wash the chart's pane separators paint on hover
|
|
885
|
+
// (soft full-thickness band + solid 2px center line), so DOM-drawn dividers
|
|
886
|
+
// (the workspace grid) and canvas-drawn ones read as one family.
|
|
887
|
+
"--vela-separator-hover-band": withAlpha(t.textColor, 0.1),
|
|
888
|
+
"--vela-separator-hover-line": withAlpha(t.textColor, 0.55),
|
|
884
889
|
"--vela-scroll": withAlpha(t.textColor, 0.3),
|
|
885
890
|
"--vela-accent": ACCENT,
|
|
886
891
|
"--vela-accent-bright": ACCENT_BRIGHT,
|
|
@@ -1058,6 +1063,11 @@ registerIcon(
|
|
|
1058
1063
|
"pen-lock",
|
|
1059
1064
|
svg24('<path d="M13.8 4.2a2.1 2.1 0 0 1 3 3L8.5 15.5l-3.5 1 1-3.5Z"/><rect x="13" y="14.5" width="8" height="6" rx="1.2"/><path d="M15 14.5v-1.6a2 2 0 0 1 4 0v1.6"/>')
|
|
1060
1065
|
);
|
|
1066
|
+
registerIcon(
|
|
1067
|
+
"pen-sync",
|
|
1068
|
+
// Pen + two stacked panes — drawing onto every linked chart at once.
|
|
1069
|
+
svg24('<path d="M13.8 4.2a2.1 2.1 0 0 1 3 3L8.5 15.5l-3.5 1 1-3.5Z"/><rect x="12.5" y="13.5" width="6" height="4.8" rx="1"/><path d="M15.5 18.3v1a1 1 0 0 0 1 1h4a1 1 0 0 0 1-1v-2.8a1 1 0 0 0-1-1h-1"/>')
|
|
1070
|
+
);
|
|
1061
1071
|
registerIcon("brush", svg24('<path d="M9.5 12 17 4.5a2.12 2.12 0 0 1 3 3L12.5 15"/><path d="M7 14a3 3 0 0 0-3 3c0 1.3-1.2 1.5-1.5 2 .8.9 2 1.5 3.5 1.5a3.5 3.5 0 0 0 3.5-3.5 3 3 0 0 0-2.5-3Z"/>'));
|
|
1062
1072
|
registerIcon(
|
|
1063
1073
|
"bucket",
|
|
@@ -1750,7 +1760,332 @@ var Dialog = class {
|
|
|
1750
1760
|
}
|
|
1751
1761
|
};
|
|
1752
1762
|
|
|
1763
|
+
// src/widget/layout-picker.ts
|
|
1764
|
+
var STYLE_ID = "vela-widget-layout-picker-v14";
|
|
1765
|
+
var CSS2 = `
|
|
1766
|
+
.vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
|
|
1767
|
+
.vela-lp {
|
|
1768
|
+
background: var(--vela-surface-elev);
|
|
1769
|
+
color: var(--vela-fg);
|
|
1770
|
+
border: 1px solid var(--vela-border-strong);
|
|
1771
|
+
border-radius: 8px;
|
|
1772
|
+
box-shadow: var(--vela-shadow);
|
|
1773
|
+
padding: 10px 12px 10px;
|
|
1774
|
+
font-size: 13px;
|
|
1775
|
+
user-select: none;
|
|
1776
|
+
transform-origin: top;
|
|
1777
|
+
animation: vela-lp-in var(--vela-dur-fast) var(--vela-ease);
|
|
1778
|
+
}
|
|
1779
|
+
@keyframes vela-lp-in {
|
|
1780
|
+
from { opacity: 0; transform: translateY(-4px) scale(0.98); }
|
|
1781
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
1782
|
+
}
|
|
1783
|
+
.vela-lp-cols { display: flex; align-items: stretch; gap: 14px; }
|
|
1784
|
+
.vela-lp-heading {
|
|
1785
|
+
display: flex;
|
|
1786
|
+
align-items: center;
|
|
1787
|
+
gap: 8px;
|
|
1788
|
+
font-size: 10px;
|
|
1789
|
+
font-weight: 600;
|
|
1790
|
+
letter-spacing: 1.2px;
|
|
1791
|
+
text-transform: uppercase;
|
|
1792
|
+
color: var(--vela-fg-faint);
|
|
1793
|
+
margin-bottom: 10px;
|
|
1794
|
+
}
|
|
1795
|
+
/* "?" help badge: hover it for the canvas explainer. */
|
|
1796
|
+
.vela-lp-badge {
|
|
1797
|
+
display: inline-flex;
|
|
1798
|
+
align-items: center;
|
|
1799
|
+
justify-content: center;
|
|
1800
|
+
width: 14px;
|
|
1801
|
+
height: 14px;
|
|
1802
|
+
border-radius: 50%;
|
|
1803
|
+
border: 1px solid var(--vela-border-strong);
|
|
1804
|
+
color: var(--vela-fg-muted);
|
|
1805
|
+
font-size: 9px;
|
|
1806
|
+
font-weight: 600;
|
|
1807
|
+
letter-spacing: 0;
|
|
1808
|
+
cursor: default;
|
|
1809
|
+
transition: color var(--vela-dur-fast) var(--vela-ease), border-color var(--vela-dur-fast) var(--vela-ease);
|
|
1810
|
+
}
|
|
1811
|
+
.vela-lp-badge:hover { color: var(--vela-fg-bright); border-color: var(--vela-fg-muted); }
|
|
1812
|
+
.vela-lp-tip { display: flex; flex-direction: column; gap: 4px; max-width: 230px; white-space: normal; }
|
|
1813
|
+
.vela-lp-vsep { width: 1px; flex: none; align-self: stretch; background: var(--vela-border-faint); }
|
|
1814
|
+
.vela-lp-layout { width: 132px; display: flex; flex-direction: column; align-items: center; }
|
|
1815
|
+
.vela-lp-layout > .vela-lp-heading,
|
|
1816
|
+
.vela-lp-layout > .vela-lp-presets { align-self: stretch; }
|
|
1817
|
+
.vela-lp-grid { display: grid; grid-template-columns: repeat(4, 24px); grid-auto-rows: 24px; gap: 4px; }
|
|
1818
|
+
.vela-lp-sq {
|
|
1819
|
+
all: unset;
|
|
1820
|
+
box-sizing: border-box;
|
|
1821
|
+
border-radius: 4px;
|
|
1822
|
+
background: var(--vela-hover);
|
|
1823
|
+
border: 1px solid var(--vela-border-faint);
|
|
1824
|
+
cursor: pointer;
|
|
1825
|
+
transition: background var(--vela-dur-fast) var(--vela-ease), border-color var(--vela-dur-fast) var(--vela-ease);
|
|
1826
|
+
}
|
|
1827
|
+
.vela-lp-sq:hover { background: var(--vela-hover-strong); border-color: var(--vela-border-strong); }
|
|
1828
|
+
/* Selected/previewed squares \u2014 the shared monochrome selection chip. */
|
|
1829
|
+
.vela-lp-sq[data-on='1'] {
|
|
1830
|
+
background: var(--vela-selected-bg);
|
|
1831
|
+
border-color: var(--vela-selected-bg);
|
|
1832
|
+
}
|
|
1833
|
+
.vela-lp-presets { display: flex; flex-direction: column; gap: 2px; margin-top: 8px; }
|
|
1834
|
+
.vela-lp-preset { all: unset; padding: 5px 8px; border-radius: 4px; cursor: pointer; color: var(--vela-fg-muted); font-size: 12px; white-space: nowrap; transition: transform 120ms var(--vela-ease); }
|
|
1835
|
+
.vela-lp-preset:hover { background: var(--vela-hover); }
|
|
1836
|
+
.vela-lp-preset:active { transform: scale(0.98); }
|
|
1837
|
+
.vela-lp-preset[data-checked='1'] { background: var(--vela-hover-strong); color: var(--vela-fg-bright); }
|
|
1838
|
+
.vela-lp-sync { display: flex; flex-direction: column; gap: 4px; min-width: 118px; }
|
|
1839
|
+
.vela-lp-sync-row { all: unset; display: flex; align-items: center; gap: 14px; padding: 6px 8px; border-radius: 5px; cursor: pointer; }
|
|
1840
|
+
.vela-lp-sync-row:hover { background: var(--vela-hover); }
|
|
1841
|
+
.vela-lp-sync-row .vela-lp-label { flex: 1 1 auto; }
|
|
1842
|
+
/* Toggle pill \u2014 same control language as the menu's switch rows, monochrome ON state. */
|
|
1843
|
+
.vela-lp-switch {
|
|
1844
|
+
position: relative;
|
|
1845
|
+
flex: none;
|
|
1846
|
+
width: 34px;
|
|
1847
|
+
height: 18px;
|
|
1848
|
+
border-radius: 9px;
|
|
1849
|
+
background: var(--vela-hover);
|
|
1850
|
+
border: 1px solid var(--vela-border-soft);
|
|
1851
|
+
transition: background 0.16s ease, border-color 0.16s ease;
|
|
1852
|
+
}
|
|
1853
|
+
.vela-lp-switch::after {
|
|
1854
|
+
content: '';
|
|
1855
|
+
position: absolute;
|
|
1856
|
+
top: 2px;
|
|
1857
|
+
left: 2px;
|
|
1858
|
+
width: 12px;
|
|
1859
|
+
height: 12px;
|
|
1860
|
+
border-radius: 50%;
|
|
1861
|
+
background: var(--vela-fg-muted);
|
|
1862
|
+
transition: transform 0.16s ease, background 0.16s ease;
|
|
1863
|
+
}
|
|
1864
|
+
.vela-lp-switch.on { background: var(--vela-selected-bg); border-color: var(--vela-selected-bg); }
|
|
1865
|
+
.vela-lp-switch.on::after { transform: translateX(16px); background: var(--vela-selected-fg); }
|
|
1866
|
+
`;
|
|
1867
|
+
var GRID = 4;
|
|
1868
|
+
var LayoutPicker = class {
|
|
1869
|
+
constructor(opts) {
|
|
1870
|
+
this.squares = [];
|
|
1871
|
+
this.isOpen = false;
|
|
1872
|
+
/** Hover preview (1-based rows/cols), null = show current shape. */
|
|
1873
|
+
this.hover = null;
|
|
1874
|
+
this.onDocPointerDown = (e) => {
|
|
1875
|
+
const t = e.target;
|
|
1876
|
+
if (t && (this.layer.contains(t) || this.opts.trigger.contains(t))) return;
|
|
1877
|
+
this.close();
|
|
1878
|
+
};
|
|
1879
|
+
this.onDocKeydown = (e) => {
|
|
1880
|
+
if (e.key === "Escape") this.close();
|
|
1881
|
+
};
|
|
1882
|
+
this.opts = opts;
|
|
1883
|
+
this.doc = opts.host.ownerDocument;
|
|
1884
|
+
injectStyles(STYLE_ID, CSS2, this.doc);
|
|
1885
|
+
const doc = this.doc;
|
|
1886
|
+
this.layer = doc.createElement("div");
|
|
1887
|
+
this.layer.className = "vela-ui-layer vela-lp-layer";
|
|
1888
|
+
this.layer.style.display = "none";
|
|
1889
|
+
const panel = doc.createElement("div");
|
|
1890
|
+
panel.className = "vela-lp";
|
|
1891
|
+
this.layer.appendChild(panel);
|
|
1892
|
+
const cols = doc.createElement("div");
|
|
1893
|
+
cols.className = "vela-lp-cols";
|
|
1894
|
+
panel.appendChild(cols);
|
|
1895
|
+
const layoutCol = doc.createElement("div");
|
|
1896
|
+
layoutCol.className = "vela-lp-layout";
|
|
1897
|
+
const layoutHeading = doc.createElement("div");
|
|
1898
|
+
layoutHeading.className = "vela-lp-heading";
|
|
1899
|
+
const headingText = doc.createElement("span");
|
|
1900
|
+
headingText.textContent = "Layout";
|
|
1901
|
+
const badge = doc.createElement("span");
|
|
1902
|
+
badge.className = "vela-lp-badge";
|
|
1903
|
+
badge.textContent = "?";
|
|
1904
|
+
layoutHeading.append(headingText, badge);
|
|
1905
|
+
layoutCol.appendChild(layoutHeading);
|
|
1906
|
+
this.infoTip = new Tooltip(badge, {
|
|
1907
|
+
host: opts.host,
|
|
1908
|
+
placement: "bottom",
|
|
1909
|
+
content: () => this.tipNode()
|
|
1910
|
+
});
|
|
1911
|
+
const grid = doc.createElement("div");
|
|
1912
|
+
grid.className = "vela-lp-grid";
|
|
1913
|
+
for (let r = 0; r < GRID; r += 1) {
|
|
1914
|
+
for (let c = 0; c < GRID; c += 1) {
|
|
1915
|
+
const sq = doc.createElement("button");
|
|
1916
|
+
sq.className = "vela-lp-sq";
|
|
1917
|
+
sq.dataset.r = String(r);
|
|
1918
|
+
sq.dataset.c = String(c);
|
|
1919
|
+
sq.setAttribute("aria-label", `${c + 1} \xD7 ${r + 1}`);
|
|
1920
|
+
this.squares.push(sq);
|
|
1921
|
+
grid.appendChild(sq);
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
layoutCol.appendChild(grid);
|
|
1925
|
+
this.presetsEl = doc.createElement("div");
|
|
1926
|
+
this.presetsEl.className = "vela-lp-presets";
|
|
1927
|
+
layoutCol.appendChild(this.presetsEl);
|
|
1928
|
+
cols.appendChild(layoutCol);
|
|
1929
|
+
const vsep = doc.createElement("div");
|
|
1930
|
+
vsep.className = "vela-lp-vsep";
|
|
1931
|
+
cols.appendChild(vsep);
|
|
1932
|
+
const syncCol = doc.createElement("div");
|
|
1933
|
+
const syncHeading = doc.createElement("div");
|
|
1934
|
+
syncHeading.className = "vela-lp-heading";
|
|
1935
|
+
syncHeading.textContent = "Sync";
|
|
1936
|
+
syncCol.appendChild(syncHeading);
|
|
1937
|
+
this.syncEl = doc.createElement("div");
|
|
1938
|
+
this.syncEl.className = "vela-lp-sync";
|
|
1939
|
+
syncCol.appendChild(this.syncEl);
|
|
1940
|
+
cols.appendChild(syncCol);
|
|
1941
|
+
grid.addEventListener("pointerdown", (e) => {
|
|
1942
|
+
const sq = this.squareAt(e);
|
|
1943
|
+
if (!sq) return;
|
|
1944
|
+
e.preventDefault();
|
|
1945
|
+
const { r, c } = this.squarePos(sq);
|
|
1946
|
+
this.close();
|
|
1947
|
+
this.opts.onSelectGrid(r + 1, c + 1);
|
|
1948
|
+
});
|
|
1949
|
+
grid.addEventListener("pointermove", (e) => {
|
|
1950
|
+
const sq = this.squareAt(e);
|
|
1951
|
+
if (!sq) return;
|
|
1952
|
+
const { r, c } = this.squarePos(sq);
|
|
1953
|
+
if (this.hover?.rows !== r + 1 || this.hover?.cols !== c + 1) {
|
|
1954
|
+
this.hover = { rows: r + 1, cols: c + 1 };
|
|
1955
|
+
this.render();
|
|
1956
|
+
}
|
|
1957
|
+
});
|
|
1958
|
+
grid.addEventListener("pointerleave", () => {
|
|
1959
|
+
if (this.hover) {
|
|
1960
|
+
this.hover = null;
|
|
1961
|
+
this.render();
|
|
1962
|
+
}
|
|
1963
|
+
});
|
|
1964
|
+
opts.trigger.addEventListener("click", () => this.toggle());
|
|
1965
|
+
opts.trigger.setAttribute("aria-haspopup", "true");
|
|
1966
|
+
opts.trigger.setAttribute("aria-expanded", "false");
|
|
1967
|
+
opts.host.appendChild(this.layer);
|
|
1968
|
+
}
|
|
1969
|
+
toggle() {
|
|
1970
|
+
if (this.isOpen) this.close();
|
|
1971
|
+
else this.open();
|
|
1972
|
+
}
|
|
1973
|
+
open() {
|
|
1974
|
+
if (this.isOpen) return;
|
|
1975
|
+
this.isOpen = true;
|
|
1976
|
+
this.hover = null;
|
|
1977
|
+
this.refresh();
|
|
1978
|
+
this.layer.style.display = "";
|
|
1979
|
+
this.position();
|
|
1980
|
+
this.opts.trigger.setAttribute("aria-expanded", "true");
|
|
1981
|
+
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
1982
|
+
this.doc.addEventListener("keydown", this.onDocKeydown, true);
|
|
1983
|
+
this.opts.onOpenChange?.(true);
|
|
1984
|
+
}
|
|
1985
|
+
close() {
|
|
1986
|
+
if (!this.isOpen) return;
|
|
1987
|
+
this.isOpen = false;
|
|
1988
|
+
this.layer.style.display = "none";
|
|
1989
|
+
this.opts.trigger.setAttribute("aria-expanded", "false");
|
|
1990
|
+
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
1991
|
+
this.doc.removeEventListener("keydown", this.onDocKeydown, true);
|
|
1992
|
+
this.opts.onOpenChange?.(false);
|
|
1993
|
+
}
|
|
1994
|
+
/** Re-read shape/presets/syncs and re-render (no-op while closed — `open` re-reads). */
|
|
1995
|
+
refresh() {
|
|
1996
|
+
if (!this.isOpen) return;
|
|
1997
|
+
this.renderPresets();
|
|
1998
|
+
this.renderSyncs();
|
|
1999
|
+
this.render();
|
|
2000
|
+
}
|
|
2001
|
+
destroy() {
|
|
2002
|
+
this.close();
|
|
2003
|
+
this.infoTip.destroy();
|
|
2004
|
+
this.layer.remove();
|
|
2005
|
+
}
|
|
2006
|
+
// ── internals ──
|
|
2007
|
+
squareAt(e) {
|
|
2008
|
+
const sq = e.target?.closest?.(".vela-lp-sq");
|
|
2009
|
+
return sq instanceof HTMLButtonElement ? sq : null;
|
|
2010
|
+
}
|
|
2011
|
+
squarePos(sq) {
|
|
2012
|
+
return { r: Number(sq.dataset.r), c: Number(sq.dataset.c) };
|
|
2013
|
+
}
|
|
2014
|
+
/** Help copy for the LAYOUT "?" badge. */
|
|
2015
|
+
tipNode() {
|
|
2016
|
+
const tip = this.doc.createElement("div");
|
|
2017
|
+
tip.className = "vela-lp-tip";
|
|
2018
|
+
tip.textContent = "Click a square to apply that columns \xD7 rows layout.";
|
|
2019
|
+
return tip;
|
|
2020
|
+
}
|
|
2021
|
+
/** Project the interaction state onto the DOM (the hover/current rectangle). */
|
|
2022
|
+
render() {
|
|
2023
|
+
const preview = this.hover ?? this.opts.shape();
|
|
2024
|
+
for (const sq of this.squares) {
|
|
2025
|
+
const { r, c } = this.squarePos(sq);
|
|
2026
|
+
const on = preview !== null && r < preview.rows && c < preview.cols;
|
|
2027
|
+
if (on) sq.dataset.on = "1";
|
|
2028
|
+
else delete sq.dataset.on;
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
renderPresets() {
|
|
2032
|
+
const doc = this.doc;
|
|
2033
|
+
this.presetsEl.replaceChildren();
|
|
2034
|
+
const presets = this.opts.presets();
|
|
2035
|
+
this.presetsEl.style.display = presets.length > 0 ? "" : "none";
|
|
2036
|
+
for (const p of presets) {
|
|
2037
|
+
const b = doc.createElement("button");
|
|
2038
|
+
b.className = "vela-lp-preset";
|
|
2039
|
+
b.textContent = p.label;
|
|
2040
|
+
if (p.checked) b.dataset.checked = "1";
|
|
2041
|
+
b.addEventListener("click", () => {
|
|
2042
|
+
this.close();
|
|
2043
|
+
this.opts.onSelectPreset(p.id);
|
|
2044
|
+
});
|
|
2045
|
+
this.presetsEl.appendChild(b);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
renderSyncs() {
|
|
2049
|
+
const doc = this.doc;
|
|
2050
|
+
this.syncEl.replaceChildren();
|
|
2051
|
+
for (const s of this.opts.syncs()) {
|
|
2052
|
+
const row = doc.createElement("button");
|
|
2053
|
+
row.className = "vela-lp-sync-row";
|
|
2054
|
+
const label = doc.createElement("span");
|
|
2055
|
+
label.className = "vela-lp-label";
|
|
2056
|
+
label.textContent = s.label;
|
|
2057
|
+
const pill = doc.createElement("span");
|
|
2058
|
+
pill.className = "vela-lp-switch" + (s.checked ? " on" : "");
|
|
2059
|
+
pill.setAttribute("aria-hidden", "true");
|
|
2060
|
+
row.setAttribute("role", "switch");
|
|
2061
|
+
row.setAttribute("aria-checked", String(s.checked));
|
|
2062
|
+
row.append(label, pill);
|
|
2063
|
+
row.addEventListener("click", () => {
|
|
2064
|
+
this.opts.onToggleSync(s.id);
|
|
2065
|
+
this.renderSyncs();
|
|
2066
|
+
});
|
|
2067
|
+
this.syncEl.appendChild(row);
|
|
2068
|
+
}
|
|
2069
|
+
}
|
|
2070
|
+
/** Anchor under the trigger, clamped to the host's right edge. */
|
|
2071
|
+
position() {
|
|
2072
|
+
const hostRect = this.opts.host.getBoundingClientRect();
|
|
2073
|
+
const trigRect = this.opts.trigger.getBoundingClientRect();
|
|
2074
|
+
let left = trigRect.left - hostRect.left;
|
|
2075
|
+
const top = trigRect.bottom - hostRect.top + 4;
|
|
2076
|
+
const width = this.layer.offsetWidth;
|
|
2077
|
+
if (left + width > hostRect.width - 8) left = Math.max(8, hostRect.width - 8 - width);
|
|
2078
|
+
this.layer.style.left = `${left}px`;
|
|
2079
|
+
this.layer.style.top = `${top}px`;
|
|
2080
|
+
}
|
|
2081
|
+
};
|
|
2082
|
+
|
|
1753
2083
|
// src/chart-types/registry.ts
|
|
2084
|
+
function settingsRowVisible(when, bag) {
|
|
2085
|
+
if (!when) return true;
|
|
2086
|
+
const conds = Array.isArray(when) ? when : [when];
|
|
2087
|
+
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === bag[c.key]) : bag[c.key] === c.equals);
|
|
2088
|
+
}
|
|
1754
2089
|
var registry2 = /* @__PURE__ */ new Map();
|
|
1755
2090
|
function registerChartType(def) {
|
|
1756
2091
|
registry2.set(def.id, def);
|
|
@@ -1844,6 +2179,44 @@ function basePaintingOf(style) {
|
|
|
1844
2179
|
for (const t of chartTypes()) if (t.id === style) return t.basePainting ?? "candles";
|
|
1845
2180
|
return "candles";
|
|
1846
2181
|
}
|
|
2182
|
+
function hasOwnCandlePaint(style) {
|
|
2183
|
+
if (BUILTIN_PRICE_STYLES.includes(style)) return false;
|
|
2184
|
+
for (const t of chartTypes()) if (t.id === style) return (t.basePainting ?? "candles") === "candles";
|
|
2185
|
+
return false;
|
|
2186
|
+
}
|
|
2187
|
+
function candleOverrideFor(style, bags) {
|
|
2188
|
+
if (!hasOwnCandlePaint(style)) return null;
|
|
2189
|
+
const bag = bags[style] ?? {};
|
|
2190
|
+
const color = (v) => isColor(v) ? v : null;
|
|
2191
|
+
const bool = (v) => isBool(v) ? v : null;
|
|
2192
|
+
return {
|
|
2193
|
+
upColor: color(bag.candleUpColor),
|
|
2194
|
+
downColor: color(bag.candleDownColor),
|
|
2195
|
+
bodyVisible: bool(bag.candleBodyVisible),
|
|
2196
|
+
borderVisible: bool(bag.candleBorderVisible),
|
|
2197
|
+
borderUpColor: color(bag.candleBorderUpColor),
|
|
2198
|
+
borderDownColor: color(bag.candleBorderDownColor),
|
|
2199
|
+
wickVisible: bool(bag.candleWickVisible),
|
|
2200
|
+
wickUpColor: color(bag.candleWickUpColor),
|
|
2201
|
+
wickDownColor: color(bag.candleWickDownColor)
|
|
2202
|
+
};
|
|
2203
|
+
}
|
|
2204
|
+
function effectiveCandlePaint(base, override, themeUp, themeDown) {
|
|
2205
|
+
if (!override) return { up: themeUp, down: themeDown, candle: base };
|
|
2206
|
+
return {
|
|
2207
|
+
up: override.upColor ?? themeUp,
|
|
2208
|
+
down: override.downColor ?? themeDown,
|
|
2209
|
+
candle: {
|
|
2210
|
+
bodyVisible: override.bodyVisible ?? base.bodyVisible,
|
|
2211
|
+
borderVisible: override.borderVisible ?? base.borderVisible,
|
|
2212
|
+
borderUpColor: override.borderUpColor ?? base.borderUpColor,
|
|
2213
|
+
borderDownColor: override.borderDownColor ?? base.borderDownColor,
|
|
2214
|
+
wickVisible: override.wickVisible ?? base.wickVisible,
|
|
2215
|
+
wickUpColor: override.wickUpColor ?? base.wickUpColor,
|
|
2216
|
+
wickDownColor: override.wickDownColor ?? base.wickDownColor
|
|
2217
|
+
}
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
1847
2220
|
function priceStyleIds() {
|
|
1848
2221
|
const out = [...BUILTIN_PRICE_STYLES];
|
|
1849
2222
|
for (const t of chartTypes()) if (!out.includes(t.id)) out.push(t.id);
|
|
@@ -1882,6 +2255,40 @@ function clampLevel(v) {
|
|
|
1882
2255
|
function clampSpacing(v) {
|
|
1883
2256
|
return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
|
|
1884
2257
|
}
|
|
2258
|
+
function factoryResetConfig(factory) {
|
|
2259
|
+
const bag = {};
|
|
2260
|
+
for (const t of chartTypes()) {
|
|
2261
|
+
const section = t.settings;
|
|
2262
|
+
if (!section) continue;
|
|
2263
|
+
const defaults2 = {};
|
|
2264
|
+
const addRows = (rows) => {
|
|
2265
|
+
for (const r of rows ?? []) {
|
|
2266
|
+
if (r.kind === "heading" || r.kind === "header") continue;
|
|
2267
|
+
if (r.kind === "range") {
|
|
2268
|
+
defaults2[r.minKey] = r.defval;
|
|
2269
|
+
defaults2[r.maxKey] = r.defval;
|
|
2270
|
+
continue;
|
|
2271
|
+
}
|
|
2272
|
+
defaults2[r.key] = r.defval;
|
|
2273
|
+
if (r.kind === "toggle") for (const c of r.colors ?? []) defaults2[c.key] = c.defval;
|
|
2274
|
+
}
|
|
2275
|
+
};
|
|
2276
|
+
for (const inst of section.instances ?? []) {
|
|
2277
|
+
if (inst.enableKey) defaults2[inst.enableKey] = false;
|
|
2278
|
+
addRows(inst.rows);
|
|
2279
|
+
}
|
|
2280
|
+
for (const sub of section.subsections ?? []) {
|
|
2281
|
+
if (sub.enableKey) defaults2[sub.enableKey] = false;
|
|
2282
|
+
addRows(sub.rows);
|
|
2283
|
+
}
|
|
2284
|
+
if (!section.instances) addRows(section.rows);
|
|
2285
|
+
bag[t.id] = defaults2;
|
|
2286
|
+
}
|
|
2287
|
+
for (const [typeId, vals] of Object.entries(factory.chartTypes)) {
|
|
2288
|
+
bag[typeId] = { ...bag[typeId] ?? {}, ...vals };
|
|
2289
|
+
}
|
|
2290
|
+
return { ...factory, chartTypes: bag };
|
|
2291
|
+
}
|
|
1885
2292
|
function mergeConfig(base, patch) {
|
|
1886
2293
|
const p = asObject(patch);
|
|
1887
2294
|
const ctPatch = asObject(p.chartTypes);
|
|
@@ -2081,8 +2488,8 @@ function timeframeLabel(value) {
|
|
|
2081
2488
|
}
|
|
2082
2489
|
|
|
2083
2490
|
// src/widget/topbar.ts
|
|
2084
|
-
var
|
|
2085
|
-
var
|
|
2491
|
+
var STYLE_ID2 = "vela-topbar";
|
|
2492
|
+
var CSS3 = `
|
|
2086
2493
|
.vela-widget-topbar {
|
|
2087
2494
|
display: flex;
|
|
2088
2495
|
align-items: center;
|
|
@@ -2194,7 +2601,7 @@ function priceStyleIcon(id) {
|
|
|
2194
2601
|
var Topbar = class {
|
|
2195
2602
|
constructor(host, opts) {
|
|
2196
2603
|
this.layoutButton = null;
|
|
2197
|
-
this.
|
|
2604
|
+
this.layoutPicker = null;
|
|
2198
2605
|
this.layoutId = null;
|
|
2199
2606
|
this.tooltips = [];
|
|
2200
2607
|
this.panelBtns = /* @__PURE__ */ new Map();
|
|
@@ -2214,7 +2621,7 @@ var Topbar = class {
|
|
|
2214
2621
|
this.timeframe = opts.timeframe;
|
|
2215
2622
|
this.priceStyle = opts.priceStyle;
|
|
2216
2623
|
const doc = host.ownerDocument;
|
|
2217
|
-
injectStyles(
|
|
2624
|
+
injectStyles(STYLE_ID2, CSS3, doc);
|
|
2218
2625
|
this.el = doc.createElement("div");
|
|
2219
2626
|
this.el.className = "vela-widget-topbar";
|
|
2220
2627
|
this.symbolEl = doc.createElement("button");
|
|
@@ -2271,19 +2678,16 @@ var Topbar = class {
|
|
|
2271
2678
|
this.tooltips.push(new Tooltip(this.styleButton, { content: "Chart style", triggerId: "vela-topbar-style", host }));
|
|
2272
2679
|
if (this.layoutButton && opts.layout) {
|
|
2273
2680
|
this.tooltips.push(new Tooltip(this.layoutButton, { content: "Layout", triggerId: "vela-topbar-layout", host }));
|
|
2274
|
-
|
|
2681
|
+
const layout = opts.layout;
|
|
2682
|
+
this.layoutPicker = new LayoutPicker({
|
|
2275
2683
|
trigger: this.layoutButton,
|
|
2276
|
-
triggerId: "vela-topbar-layout",
|
|
2277
2684
|
host,
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
}
|
|
2285
|
-
opts.layout.onSelect(id);
|
|
2286
|
-
}
|
|
2685
|
+
shape: () => layout.shape(),
|
|
2686
|
+
presets: () => layout.presets().map((p) => ({ ...p, checked: p.id === this.layoutId })),
|
|
2687
|
+
onSelectGrid: (rows, cols) => layout.onSelectGrid(rows, cols),
|
|
2688
|
+
onSelectPreset: (id) => layout.onSelectPreset(id),
|
|
2689
|
+
syncs: () => layout.syncs(),
|
|
2690
|
+
onToggleSync: (id) => layout.onToggleSync(id)
|
|
2287
2691
|
});
|
|
2288
2692
|
}
|
|
2289
2693
|
this.tfMenu = new Menu({
|
|
@@ -2322,7 +2726,7 @@ var Topbar = class {
|
|
|
2322
2726
|
if (!this.layoutButton) return;
|
|
2323
2727
|
this.layoutId = id;
|
|
2324
2728
|
this.renderLayoutButton(this.layoutButton.ownerDocument);
|
|
2325
|
-
this.
|
|
2729
|
+
this.layoutPicker?.refresh();
|
|
2326
2730
|
}
|
|
2327
2731
|
renderLayoutButton(doc) {
|
|
2328
2732
|
if (!this.layoutButton) return;
|
|
@@ -2331,11 +2735,6 @@ var Topbar = class {
|
|
|
2331
2735
|
else this.layoutButton.appendChild(doc.createTextNode(this.layoutId ?? ""));
|
|
2332
2736
|
this.layoutButton.setAttribute("aria-label", `Layout \u2014 ${this.layoutId ?? ""}`);
|
|
2333
2737
|
}
|
|
2334
|
-
layoutItems() {
|
|
2335
|
-
const presets = (this.opts.layout?.options() ?? []).map((l) => ({ id: l.id, label: l.label, checked: l.id === this.layoutId }));
|
|
2336
|
-
const toggles = (this.opts.layout?.toggles?.() ?? []).map((t, i) => ({ id: `toggle:${t.id}`, label: t.label, checked: t.checked, toggle: true, separatorBefore: i === 0 }));
|
|
2337
|
-
return [...presets, ...toggles];
|
|
2338
|
-
}
|
|
2339
2738
|
renderStyleButton(doc) {
|
|
2340
2739
|
this.styleButton.replaceChildren();
|
|
2341
2740
|
const icon2 = priceStyleIcon(this.priceStyle);
|
|
@@ -2400,7 +2799,7 @@ var Topbar = class {
|
|
|
2400
2799
|
if (this.hairlineRaf) win?.cancelAnimationFrame(this.hairlineRaf);
|
|
2401
2800
|
this.tfMenu.destroy();
|
|
2402
2801
|
this.styleMenu.destroy();
|
|
2403
|
-
this.
|
|
2802
|
+
this.layoutPicker?.destroy();
|
|
2404
2803
|
for (const t of [...this.tooltips, ...this.panelTooltips]) t.destroy();
|
|
2405
2804
|
this.el.remove();
|
|
2406
2805
|
}
|
|
@@ -2547,8 +2946,8 @@ var RANGE_PRESETS = [
|
|
|
2547
2946
|
{ id: "ALL", tf: "W", preset: "ALL", bars: 5e3 }
|
|
2548
2947
|
// everything the provider serves
|
|
2549
2948
|
];
|
|
2550
|
-
var
|
|
2551
|
-
var
|
|
2949
|
+
var STYLE_ID3 = "vela-widget-bottombar";
|
|
2950
|
+
var CSS4 = `
|
|
2552
2951
|
.vela-widget-bottombar {
|
|
2553
2952
|
display: flex;
|
|
2554
2953
|
align-items: center;
|
|
@@ -2623,7 +3022,7 @@ var Bottombar = class {
|
|
|
2623
3022
|
this.timer = null;
|
|
2624
3023
|
this.timezone = opts.timezone;
|
|
2625
3024
|
const doc = host.ownerDocument;
|
|
2626
|
-
injectStyles(
|
|
3025
|
+
injectStyles(STYLE_ID3, CSS4, doc);
|
|
2627
3026
|
this.el = doc.createElement("div");
|
|
2628
3027
|
this.el.className = "vela-widget-bottombar";
|
|
2629
3028
|
for (const preset of RANGE_PRESETS) {
|
|
@@ -8579,11 +8978,11 @@ function treeIsEmpty(panes) {
|
|
|
8579
8978
|
}
|
|
8580
8979
|
|
|
8581
8980
|
// src/widget/side-panel.ts
|
|
8582
|
-
var
|
|
8981
|
+
var STYLE_ID4 = "vela-widget-sidepanel";
|
|
8583
8982
|
var DEFAULT_PANEL_WIDTH = 280;
|
|
8584
8983
|
var DEFAULT_PANEL_MIN_WIDTH = 200;
|
|
8585
8984
|
var DEFAULT_PANEL_MAX_WIDTH = 640;
|
|
8586
|
-
var
|
|
8985
|
+
var CSS5 = `
|
|
8587
8986
|
.vela-panel[hidden] { display: none !important; }
|
|
8588
8987
|
.vela-panel {
|
|
8589
8988
|
position: relative;
|
|
@@ -8672,7 +9071,7 @@ var SidePanel = class {
|
|
|
8672
9071
|
* a programmatic {@link setWidth}, so restoring a persisted width raises no change. */
|
|
8673
9072
|
this.onWidthChange = null;
|
|
8674
9073
|
const doc = host.ownerDocument;
|
|
8675
|
-
injectStyles(
|
|
9074
|
+
injectStyles(STYLE_ID4, CSS5, doc);
|
|
8676
9075
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
8677
9076
|
this.maxWidth = opts.resizable ? opts.maxWidth ?? DEFAULT_PANEL_MAX_WIDTH : Number.POSITIVE_INFINITY;
|
|
8678
9077
|
this.declaredWidth = clampPanelWidth(opts.width ?? DEFAULT_PANEL_WIDTH, this.minWidth, this.maxWidth);
|
|
@@ -8781,8 +9180,8 @@ var SidePanel = class {
|
|
|
8781
9180
|
};
|
|
8782
9181
|
|
|
8783
9182
|
// src/widget/object-tree.ts
|
|
8784
|
-
var
|
|
8785
|
-
var
|
|
9183
|
+
var STYLE_ID5 = "vela-widget-objtree";
|
|
9184
|
+
var CSS6 = `
|
|
8786
9185
|
/* One pane's block. The transparent border reserves the drop-target outline. */
|
|
8787
9186
|
.vela-ot-pane {
|
|
8788
9187
|
border: 1px solid transparent;
|
|
@@ -9020,7 +9419,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
9020
9419
|
this.menu = null;
|
|
9021
9420
|
this.menuActions = /* @__PURE__ */ new Map();
|
|
9022
9421
|
this.drag = null;
|
|
9023
|
-
injectStyles(
|
|
9422
|
+
injectStyles(STYLE_ID5, CSS6, host.ownerDocument);
|
|
9024
9423
|
this.body.addEventListener("contextmenu", (e) => this.onContextMenu(e));
|
|
9025
9424
|
this.body.addEventListener("pointerdown", (e) => this.onPointerDown(e));
|
|
9026
9425
|
}
|
|
@@ -9999,8 +10398,8 @@ var ObjectTree = class extends SidePanel {
|
|
|
9999
10398
|
};
|
|
10000
10399
|
|
|
10001
10400
|
// src/widget/data-window.ts
|
|
10002
|
-
var
|
|
10003
|
-
var
|
|
10401
|
+
var STYLE_ID6 = "vela-widget-datawindow";
|
|
10402
|
+
var CSS7 = `
|
|
10004
10403
|
.vela-dw-group {
|
|
10005
10404
|
padding: 10px 8px 4px;
|
|
10006
10405
|
margin-top: 4px;
|
|
@@ -10059,7 +10458,7 @@ var DataWindow = class extends SidePanel {
|
|
|
10059
10458
|
super(host, "Data window", "vela-dw");
|
|
10060
10459
|
this.chart = null;
|
|
10061
10460
|
this.unsubs = [];
|
|
10062
|
-
injectStyles(
|
|
10461
|
+
injectStyles(STYLE_ID6, CSS7, host.ownerDocument);
|
|
10063
10462
|
}
|
|
10064
10463
|
toggle(open = this.el.hidden) {
|
|
10065
10464
|
super.toggle(open);
|
|
@@ -10296,8 +10695,8 @@ function filterSymbols(list, query, limit = 100) {
|
|
|
10296
10695
|
}
|
|
10297
10696
|
return [...prefix, ...substr, ...desc, ...venue].slice(0, limit);
|
|
10298
10697
|
}
|
|
10299
|
-
var
|
|
10300
|
-
var
|
|
10698
|
+
var STYLE_ID7 = "vela-widget-symbolpicker";
|
|
10699
|
+
var CSS8 = `
|
|
10301
10700
|
.vela-sp-searchrow {
|
|
10302
10701
|
display: flex;
|
|
10303
10702
|
align-items: center;
|
|
@@ -10387,7 +10786,7 @@ var SymbolPicker = class {
|
|
|
10387
10786
|
this.seed = "";
|
|
10388
10787
|
this.activeTab = "All";
|
|
10389
10788
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
10390
|
-
injectStyles(
|
|
10789
|
+
injectStyles(STYLE_ID7, CSS8, doc);
|
|
10391
10790
|
this.input = doc.createElement("input");
|
|
10392
10791
|
this.input.className = "vela-sp-input";
|
|
10393
10792
|
this.input.placeholder = "Search symbol\u2026";
|
|
@@ -10521,8 +10920,8 @@ var SymbolPicker = class {
|
|
|
10521
10920
|
};
|
|
10522
10921
|
|
|
10523
10922
|
// src/widget/indicator-picker.ts
|
|
10524
|
-
var
|
|
10525
|
-
var
|
|
10923
|
+
var STYLE_ID8 = "vela-widget-indpicker";
|
|
10924
|
+
var CSS9 = `
|
|
10526
10925
|
.vela-ip-searchrow {
|
|
10527
10926
|
display: flex;
|
|
10528
10927
|
align-items: center;
|
|
@@ -10596,7 +10995,7 @@ var IndicatorPicker = class {
|
|
|
10596
10995
|
this.isOpen = false;
|
|
10597
10996
|
this.opts = opts;
|
|
10598
10997
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
10599
|
-
injectStyles(
|
|
10998
|
+
injectStyles(STYLE_ID8, CSS9, doc);
|
|
10600
10999
|
this.search = doc.createElement("input");
|
|
10601
11000
|
this.search.className = "vela-ip-search";
|
|
10602
11001
|
this.search.placeholder = "Search\u2026";
|
|
@@ -10724,8 +11123,8 @@ var IndicatorPicker = class {
|
|
|
10724
11123
|
};
|
|
10725
11124
|
|
|
10726
11125
|
// src/widget/timeframe-quick.ts
|
|
10727
|
-
var
|
|
10728
|
-
var
|
|
11126
|
+
var STYLE_ID9 = "vela-widget-tfquick";
|
|
11127
|
+
var CSS10 = `
|
|
10729
11128
|
.vela-tq-input {
|
|
10730
11129
|
display: block;
|
|
10731
11130
|
margin: 0 auto;
|
|
@@ -10748,7 +11147,7 @@ var CSS9 = `
|
|
|
10748
11147
|
var TimeframeQuick = class {
|
|
10749
11148
|
constructor(opts) {
|
|
10750
11149
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
10751
|
-
injectStyles(
|
|
11150
|
+
injectStyles(STYLE_ID9, CSS10, doc);
|
|
10752
11151
|
this.input = doc.createElement("input");
|
|
10753
11152
|
this.input.className = "vela-tq-input";
|
|
10754
11153
|
this.input.setAttribute("spellcheck", "false");
|
|
@@ -10804,8 +11203,8 @@ var TimeframeQuick = class {
|
|
|
10804
11203
|
};
|
|
10805
11204
|
|
|
10806
11205
|
// src/widget/shortcuts-help.ts
|
|
10807
|
-
var
|
|
10808
|
-
var
|
|
11206
|
+
var STYLE_ID10 = "vela-widget-shortcuts";
|
|
11207
|
+
var CSS11 = `
|
|
10809
11208
|
.vela-sh-cat {
|
|
10810
11209
|
color: var(--vela-fg-muted);
|
|
10811
11210
|
font-size: var(--vela-font-size-sm);
|
|
@@ -10830,7 +11229,7 @@ var ShortcutsHelp = class {
|
|
|
10830
11229
|
constructor(keymap, host, onOpenChange) {
|
|
10831
11230
|
this.keymap = keymap;
|
|
10832
11231
|
const doc = (host ?? document.body).ownerDocument;
|
|
10833
|
-
injectStyles(
|
|
11232
|
+
injectStyles(STYLE_ID10, CSS11, doc);
|
|
10834
11233
|
this.list = doc.createElement("div");
|
|
10835
11234
|
this.dialog = new Dialog({
|
|
10836
11235
|
title: "Keyboard shortcuts",
|
|
@@ -10900,8 +11299,8 @@ var ShortcutsHelp = class {
|
|
|
10900
11299
|
};
|
|
10901
11300
|
|
|
10902
11301
|
// src/widget/toast.ts
|
|
10903
|
-
var
|
|
10904
|
-
var
|
|
11302
|
+
var STYLE_ID11 = "vela-widget-toast";
|
|
11303
|
+
var CSS12 = `
|
|
10905
11304
|
.vela-toast {
|
|
10906
11305
|
position: absolute;
|
|
10907
11306
|
left: 50%;
|
|
@@ -10931,7 +11330,7 @@ var CSS11 = `
|
|
|
10931
11330
|
var Toast = class {
|
|
10932
11331
|
constructor(host) {
|
|
10933
11332
|
this.timer = null;
|
|
10934
|
-
injectStyles(
|
|
11333
|
+
injectStyles(STYLE_ID11, CSS12, host.ownerDocument);
|
|
10935
11334
|
this.el = host.ownerDocument.createElement("div");
|
|
10936
11335
|
this.el.className = "vela-toast";
|
|
10937
11336
|
host.appendChild(this.el);
|
|
@@ -11188,6 +11587,8 @@ var DrawingToolbar = class {
|
|
|
11188
11587
|
this.eraserActive = false;
|
|
11189
11588
|
this.stayBtn = null;
|
|
11190
11589
|
this.stayActive = false;
|
|
11590
|
+
this.syncBtn = null;
|
|
11591
|
+
this.syncActive = false;
|
|
11191
11592
|
this.collapseBtn = null;
|
|
11192
11593
|
this.collapsed = false;
|
|
11193
11594
|
this.visible = false;
|
|
@@ -11204,6 +11605,7 @@ var DrawingToolbar = class {
|
|
|
11204
11605
|
this.dock = options.dock ?? "absolute";
|
|
11205
11606
|
this.onCollapse = options.onCollapse ?? (() => {
|
|
11206
11607
|
});
|
|
11608
|
+
this.onDrawingsSync = options.onDrawingsSync ?? null;
|
|
11207
11609
|
ensureStyles();
|
|
11208
11610
|
this.root = document.createElement("div");
|
|
11209
11611
|
this.root.className = "vela-dtb";
|
|
@@ -11302,6 +11704,10 @@ var DrawingToolbar = class {
|
|
|
11302
11704
|
this.root.appendChild(this.makeMagnetCell());
|
|
11303
11705
|
this.stayBtn = this.makeButton(STAY_ICON, "Stay in drawing mode", () => this.toggleStay());
|
|
11304
11706
|
this.root.appendChild(this.stayBtn);
|
|
11707
|
+
if (this.onDrawingsSync) {
|
|
11708
|
+
this.syncBtn = this.makeButton(SYNC_ICON, "Sync drawings on all charts", () => this.toggleDrawingsSync());
|
|
11709
|
+
this.root.appendChild(this.syncBtn);
|
|
11710
|
+
}
|
|
11305
11711
|
this.collapseBtn = this.makeButton(COLLAPSE_ICON, "Collapse toolbar", () => this.toggleCollapsed());
|
|
11306
11712
|
this.collapseBtn.classList.add("vela-dtb-collapse");
|
|
11307
11713
|
this.root.appendChild(this.collapseBtn);
|
|
@@ -11309,6 +11715,7 @@ var DrawingToolbar = class {
|
|
|
11309
11715
|
this.paintEraser();
|
|
11310
11716
|
this.paintMagnet();
|
|
11311
11717
|
this.paintStay();
|
|
11718
|
+
this.paintDrawingsSync();
|
|
11312
11719
|
this.paintCollapse();
|
|
11313
11720
|
this.highlight();
|
|
11314
11721
|
}
|
|
@@ -11359,7 +11766,7 @@ var DrawingToolbar = class {
|
|
|
11359
11766
|
cell.appendChild(arrow);
|
|
11360
11767
|
}
|
|
11361
11768
|
this.tipDisposers.push(
|
|
11362
|
-
attachChromeTooltip(cell, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(cell) ?? label, placement: "right"
|
|
11769
|
+
attachChromeTooltip(cell, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(cell) ?? label, placement: "right" })
|
|
11363
11770
|
);
|
|
11364
11771
|
return { cell, icon: icon2, arrow };
|
|
11365
11772
|
}
|
|
@@ -11469,6 +11876,23 @@ var DrawingToolbar = class {
|
|
|
11469
11876
|
b.dataset.active = this.stayActive ? "1" : "";
|
|
11470
11877
|
b.style.opacity = this.stayActive ? "1" : "0.5";
|
|
11471
11878
|
}
|
|
11879
|
+
/** Toggle drawings sync on/off and notify the host. */
|
|
11880
|
+
toggleDrawingsSync() {
|
|
11881
|
+
this.syncActive = !this.syncActive;
|
|
11882
|
+
this.onDrawingsSync?.(this.syncActive);
|
|
11883
|
+
this.paintDrawingsSync();
|
|
11884
|
+
}
|
|
11885
|
+
/** Reflect drawings sync externally (e.g. set through the API) without notifying back. */
|
|
11886
|
+
setDrawingsSyncMode(on) {
|
|
11887
|
+
this.syncActive = on;
|
|
11888
|
+
this.paintDrawingsSync();
|
|
11889
|
+
}
|
|
11890
|
+
paintDrawingsSync() {
|
|
11891
|
+
const b = this.syncBtn;
|
|
11892
|
+
if (!b) return;
|
|
11893
|
+
b.dataset.active = this.syncActive ? "1" : "";
|
|
11894
|
+
b.style.opacity = this.syncActive ? "1" : "0.5";
|
|
11895
|
+
}
|
|
11472
11896
|
// ── flyout ──
|
|
11473
11897
|
/** Create the positioned flyout panel anchored to a cell, register it as open, and start the
|
|
11474
11898
|
* outside-dismiss. Callers fill it with items. */
|
|
@@ -11628,6 +12052,8 @@ var DrawingToolbar = class {
|
|
|
11628
12052
|
const type = this.lastUsed.get(group.id) ?? group.tools[0]?.type;
|
|
11629
12053
|
const tool = group.tools.find((t) => t.type === type) ?? group.tools[0];
|
|
11630
12054
|
iconBtn.innerHTML = hitHtml(tool?.icon ?? "");
|
|
12055
|
+
const cell = this.groupCells.get(group.id);
|
|
12056
|
+
if (cell) this.tipText.set(cell, tool?.label ?? group.label);
|
|
11631
12057
|
}
|
|
11632
12058
|
/** Reconcile active tints from state (armed group / open flyout / cursor / modes). Active is a
|
|
11633
12059
|
* `data-active` attribute the stylesheet paints — no inline background, so hover never sticks. */
|
|
@@ -11651,7 +12077,7 @@ var DrawingToolbar = class {
|
|
|
11651
12077
|
this.tipText.set(btn, title);
|
|
11652
12078
|
if (icon2) btn.innerHTML = hitHtml(icon2);
|
|
11653
12079
|
this.tipDisposers.push(
|
|
11654
|
-
attachChromeTooltip(btn, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(btn) ?? title, placement: "right"
|
|
12080
|
+
attachChromeTooltip(btn, { host: this.host, theme: () => this.theme, text: () => this.tipText.get(btn) ?? title, placement: "right" })
|
|
11655
12081
|
);
|
|
11656
12082
|
btn.addEventListener("click", onClick);
|
|
11657
12083
|
return btn;
|
|
@@ -11663,13 +12089,13 @@ var DrawingToolbar = class {
|
|
|
11663
12089
|
}
|
|
11664
12090
|
};
|
|
11665
12091
|
var MAGNET_ID = "__magnet__";
|
|
11666
|
-
var
|
|
12092
|
+
var STYLE_ID12 = "vela-drawing-toolbar-styles";
|
|
11667
12093
|
function ensureStyles() {
|
|
11668
12094
|
if (typeof document === "undefined") return;
|
|
11669
|
-
let s = document.getElementById(
|
|
12095
|
+
let s = document.getElementById(STYLE_ID12);
|
|
11670
12096
|
if (!s) {
|
|
11671
12097
|
s = document.createElement("style");
|
|
11672
|
-
s.id =
|
|
12098
|
+
s.id = STYLE_ID12;
|
|
11673
12099
|
document.head.appendChild(s);
|
|
11674
12100
|
}
|
|
11675
12101
|
s.textContent = `
|
|
@@ -11717,6 +12143,7 @@ var COLLAPSE_ICON = icon("chevrons-left");
|
|
|
11717
12143
|
var EXPAND_ICON = icon("chevrons-right");
|
|
11718
12144
|
var MAGNET_ICON = icon("magnet");
|
|
11719
12145
|
var STAY_ICON = icon("pen-lock");
|
|
12146
|
+
var SYNC_ICON = icon("pen-sync");
|
|
11720
12147
|
var ERASER_ICON = icon("eraser");
|
|
11721
12148
|
var ARROW_ICON2 = icon("chevron-right");
|
|
11722
12149
|
var CHECK_ICON = icon("check");
|
|
@@ -11742,8 +12169,8 @@ var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path
|
|
|
11742
12169
|
|
|
11743
12170
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
11744
12171
|
var ATTRIBUTION_URL = "https://luxalgo.com/vela";
|
|
11745
|
-
var
|
|
11746
|
-
var
|
|
12172
|
+
var STYLE_ID13 = "vela-attribution-styles";
|
|
12173
|
+
var CSS13 = `
|
|
11747
12174
|
.vela-attribution {
|
|
11748
12175
|
text-decoration: none;
|
|
11749
12176
|
display: flex;
|
|
@@ -11790,13 +12217,13 @@ var CSS12 = `
|
|
|
11790
12217
|
transform: translateX(0);
|
|
11791
12218
|
}`;
|
|
11792
12219
|
function ensureStyles2(doc) {
|
|
11793
|
-
let s = doc.getElementById(
|
|
12220
|
+
let s = doc.getElementById(STYLE_ID13);
|
|
11794
12221
|
if (!s) {
|
|
11795
12222
|
s = doc.createElement("style");
|
|
11796
|
-
s.id =
|
|
12223
|
+
s.id = STYLE_ID13;
|
|
11797
12224
|
doc.head.appendChild(s);
|
|
11798
12225
|
}
|
|
11799
|
-
s.textContent =
|
|
12226
|
+
s.textContent = CSS13;
|
|
11800
12227
|
}
|
|
11801
12228
|
function attributionMarkColor(background) {
|
|
11802
12229
|
return isDarkColor(background) ? "#ffffff" : "#000000";
|
|
@@ -12361,6 +12788,11 @@ var DrawingController = class {
|
|
|
12361
12788
|
setToolbar(option) {
|
|
12362
12789
|
this.port?.setToolbar(buildToolbar(option).definition);
|
|
12363
12790
|
}
|
|
12791
|
+
/** Display (or clear) another chart's in-progress placement as a ghost — silently
|
|
12792
|
+
* inert on a renderer without the optional `setExternalGhost` seam. */
|
|
12793
|
+
setExternalGhost(doc) {
|
|
12794
|
+
this.port?.setExternalGhost?.(doc);
|
|
12795
|
+
}
|
|
12364
12796
|
/** Push per-tool shortcut hints (pre-formatted display strings) to the toolbar flyouts. */
|
|
12365
12797
|
setToolShortcuts(map) {
|
|
12366
12798
|
this.port?.setToolShortcuts?.(map);
|
|
@@ -12591,6 +13023,9 @@ var DrawingController = class {
|
|
|
12591
13023
|
case "arm":
|
|
12592
13024
|
this.setTool(i.type);
|
|
12593
13025
|
break;
|
|
13026
|
+
case "draft":
|
|
13027
|
+
this.events.emit("drawing:draft", { doc: i.doc });
|
|
13028
|
+
break;
|
|
12594
13029
|
case "create": {
|
|
12595
13030
|
const before = this.store.serialize();
|
|
12596
13031
|
const last = this.lastStyle.get(i.doc.type);
|
|
@@ -12762,7 +13197,7 @@ function sanitizeSync(raw) {
|
|
|
12762
13197
|
if (raw == null || typeof raw !== "object") return null;
|
|
12763
13198
|
const s = raw;
|
|
12764
13199
|
const out = {};
|
|
12765
|
-
for (const kind of ["viewport", "symbol", "timeframe", "crosshair"]) {
|
|
13200
|
+
for (const kind of ["viewport", "symbol", "timeframe", "crosshair", "drawings"]) {
|
|
12766
13201
|
const v = s[kind];
|
|
12767
13202
|
if (v === true) out[kind] = true;
|
|
12768
13203
|
else if (v != null && typeof v === "object") {
|
|
@@ -14856,6 +15291,14 @@ var DrawingsControl = class {
|
|
|
14856
15291
|
if (this.ok("setToolShortcuts")) this.ctrl.setToolShortcuts(map);
|
|
14857
15292
|
return this;
|
|
14858
15293
|
}
|
|
15294
|
+
/** Display another chart's in-progress placement as a GHOST at reduced opacity
|
|
15295
|
+
* (`null` clears it) — how a multi-chart host mirrors `drawing:draft` onto linked
|
|
15296
|
+
* charts. Never a real drawing: no store entry, no selection, no persistence.
|
|
15297
|
+
* Silently inert on a renderer without the optional seam. */
|
|
15298
|
+
setExternalGhost(doc) {
|
|
15299
|
+
this.ctrl.setExternalGhost(doc);
|
|
15300
|
+
return this;
|
|
15301
|
+
}
|
|
14859
15302
|
/** Create a drawing programmatically (no clicking). Returns it, or null if unsupported. */
|
|
14860
15303
|
add(type, init = {}) {
|
|
14861
15304
|
if (!this.ok("add")) return null;
|
|
@@ -16201,13 +16644,13 @@ var ICONS = {
|
|
|
16201
16644
|
maximize: icon("maximize"),
|
|
16202
16645
|
restore: icon("restore")
|
|
16203
16646
|
};
|
|
16204
|
-
var
|
|
16647
|
+
var STYLE_ID14 = "vela-pane-controls";
|
|
16205
16648
|
var ICON_PX = 12;
|
|
16206
16649
|
var CLUSTER_PILL = "rgba(0,0,0,0.28)";
|
|
16207
16650
|
function ensureStyles3() {
|
|
16208
|
-
if (typeof document === "undefined" || document.getElementById(
|
|
16651
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID14)) return;
|
|
16209
16652
|
const st = document.createElement("style");
|
|
16210
|
-
st.id =
|
|
16653
|
+
st.id = STYLE_ID14;
|
|
16211
16654
|
st.textContent = `
|
|
16212
16655
|
.vela-pc-btn{display:inline-flex;align-items:center;justify-content:center;padding:0;border:none;border-radius:var(--vela-radius-sm);background:transparent;line-height:0;font-size:${ICON_PX}px;color:var(--vela-fg-muted);}
|
|
16213
16656
|
.vela-pc-btn svg{display:block;}
|
|
@@ -16899,6 +17342,9 @@ var SceneGraph = class {
|
|
|
16899
17342
|
this.priceStyle = "candles";
|
|
16900
17343
|
/** Price-series base painting for the ACTIVE style (see ChartTypeDefinition.basePainting). */
|
|
16901
17344
|
this.basePainting = "candles";
|
|
17345
|
+
/** The ACTIVE style's own candle cosmetics (`chartTypes.<id>.candle*`) when it is a
|
|
17346
|
+
* candle-based plugin type; null ⇒ paint with the shared `style.candle` block. */
|
|
17347
|
+
this.candleOverride = null;
|
|
16902
17348
|
/** Explicit baseline reference price for `priceStyle:'baseline'`; when null the
|
|
16903
17349
|
* baseline follows `style.baseline.baselineLevel` as a percent of the visible pane
|
|
16904
17350
|
* range (resolved per frame via `baselinePriceFor`). */
|
|
@@ -18128,14 +18574,15 @@ var WebGL2Backend = class {
|
|
|
18128
18574
|
emitCandles(b, scene, bars, i0, i1, coords, pane, theme, barColors) {
|
|
18129
18575
|
const spacing = coords.bodySpacing();
|
|
18130
18576
|
const tier = candleTier(spacing);
|
|
18577
|
+
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
18131
18578
|
if (tier === "aggregate") {
|
|
18132
|
-
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane,
|
|
18579
|
+
this.emitCandlesAggregated(b, bars, i0, i1, coords, pane, paint.up, paint.down, barColors);
|
|
18133
18580
|
return;
|
|
18134
18581
|
}
|
|
18135
18582
|
const drawBody = tier === "full";
|
|
18136
|
-
const up =
|
|
18137
|
-
const down =
|
|
18138
|
-
const cs =
|
|
18583
|
+
const up = paint.up;
|
|
18584
|
+
const down = paint.down;
|
|
18585
|
+
const cs = paint.candle;
|
|
18139
18586
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
18140
18587
|
for (let i = i0; i <= i1; i += 1) {
|
|
18141
18588
|
const bar = bars[i];
|
|
@@ -18727,7 +19174,6 @@ function effectiveSnapMode(momentaryOverride, sticky) {
|
|
|
18727
19174
|
return momentaryOverride ? "strong" : sticky;
|
|
18728
19175
|
}
|
|
18729
19176
|
var InputController = class {
|
|
18730
|
-
// the last middle press deleted a drawing (suppress autoscroll/paste)
|
|
18731
19177
|
constructor(deps) {
|
|
18732
19178
|
this.deps = deps;
|
|
18733
19179
|
/** When true (the default), wheel-zoom pins the right edge instead of the bar
|
|
@@ -18755,9 +19201,23 @@ var InputController = class {
|
|
|
18755
19201
|
this.vx = 0;
|
|
18756
19202
|
// smoothed pointer velocity, px/ms
|
|
18757
19203
|
this.middleDeleted = false;
|
|
19204
|
+
// the last middle press deleted a drawing (suppress autoscroll/paste)
|
|
19205
|
+
// Last cursor position over the element (NaN once it leaves) — lets a modifier
|
|
19206
|
+
// press/release re-shape the drawings preview with the pointer stationary.
|
|
19207
|
+
this.cursorX = NaN;
|
|
19208
|
+
this.cursorY = NaN;
|
|
18758
19209
|
this.onMiddleGuard = (e) => {
|
|
18759
19210
|
if (e.button === 1 && this.middleDeleted) e.preventDefault();
|
|
18760
19211
|
};
|
|
19212
|
+
/** A modifier press/release with the pointer stationary: re-issue the last cursor
|
|
19213
|
+
* position so the placing ghost / drag preview reflects the new state immediately
|
|
19214
|
+
* (Shift's 45° angle lock, Ctrl/Cmd's momentary strong magnet). */
|
|
19215
|
+
this.onModifier = (e) => {
|
|
19216
|
+
if (e.repeat || e.key !== "Shift" && e.key !== "Control" && e.key !== "Meta") return;
|
|
19217
|
+
if (Number.isNaN(this.cursorX)) return;
|
|
19218
|
+
if (this.dragging && this.region !== "drawing") return;
|
|
19219
|
+
this.deps.drawingsPointerMove?.(this.cursorX, this.cursorY, this.snapMode(e), e.shiftKey);
|
|
19220
|
+
};
|
|
18761
19221
|
this.onDown = (e) => {
|
|
18762
19222
|
if (e.button === 1) {
|
|
18763
19223
|
const { x: x2, y: y2 } = this.local(e);
|
|
@@ -18799,6 +19259,8 @@ var InputController = class {
|
|
|
18799
19259
|
};
|
|
18800
19260
|
this.onMove = (e) => {
|
|
18801
19261
|
const { x, y } = this.local(e);
|
|
19262
|
+
this.cursorX = x;
|
|
19263
|
+
this.cursorY = y;
|
|
18802
19264
|
if (this.dragging) {
|
|
18803
19265
|
if (this.region === "price") {
|
|
18804
19266
|
if (Math.abs(y - this.startY) > DRAG_SLOP2) this.moved = true;
|
|
@@ -18812,7 +19274,7 @@ var InputController = class {
|
|
|
18812
19274
|
this.deps.apply({ barSpacing, rightOffset: this.startRightOffset });
|
|
18813
19275
|
} else if (this.region === "drawing") {
|
|
18814
19276
|
if (Math.abs(x - this.startX) > DRAG_SLOP2 || Math.abs(y - this.startY) > DRAG_SLOP2) this.moved = true;
|
|
18815
|
-
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e));
|
|
19277
|
+
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey);
|
|
18816
19278
|
} else {
|
|
18817
19279
|
const coords = this.deps.getCoords();
|
|
18818
19280
|
const vp = coords.getViewport();
|
|
@@ -18835,7 +19297,7 @@ var InputController = class {
|
|
|
18835
19297
|
const drawCursor = this.deps.drawingsCursor?.(x, y);
|
|
18836
19298
|
const r = this.regionAt(x, y);
|
|
18837
19299
|
this.el.style.cursor = drawCursor ?? (r === "price" ? "ns-resize" : r === "time" ? "ew-resize" : r === "separator" ? "row-resize" : "");
|
|
18838
|
-
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e));
|
|
19300
|
+
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey);
|
|
18839
19301
|
}
|
|
18840
19302
|
this.deps.onPointerMove(x, y);
|
|
18841
19303
|
};
|
|
@@ -18860,6 +19322,8 @@ var InputController = class {
|
|
|
18860
19322
|
}
|
|
18861
19323
|
};
|
|
18862
19324
|
this.onLeave = () => {
|
|
19325
|
+
this.cursorX = NaN;
|
|
19326
|
+
this.cursorY = NaN;
|
|
18863
19327
|
this.deps.onPointerMove(null, null);
|
|
18864
19328
|
};
|
|
18865
19329
|
this.onDblClick = (e) => {
|
|
@@ -18898,10 +19362,16 @@ var InputController = class {
|
|
|
18898
19362
|
el.addEventListener("wheel", this.onWheel, { passive: false });
|
|
18899
19363
|
el.addEventListener("mousedown", this.onMiddleGuard);
|
|
18900
19364
|
el.addEventListener("auxclick", this.onMiddleGuard);
|
|
19365
|
+
const win = el.ownerDocument?.defaultView;
|
|
19366
|
+
win?.addEventListener("keydown", this.onModifier);
|
|
19367
|
+
win?.addEventListener("keyup", this.onModifier);
|
|
18901
19368
|
}
|
|
18902
19369
|
detach() {
|
|
18903
19370
|
const el = this.el;
|
|
18904
19371
|
if (!el) return;
|
|
19372
|
+
const win = el.ownerDocument?.defaultView;
|
|
19373
|
+
win?.removeEventListener("keydown", this.onModifier);
|
|
19374
|
+
win?.removeEventListener("keyup", this.onModifier);
|
|
18905
19375
|
el.removeEventListener("pointerdown", this.onDown);
|
|
18906
19376
|
el.removeEventListener("pointermove", this.onMove);
|
|
18907
19377
|
el.removeEventListener("pointerup", this.onUp);
|
|
@@ -19319,12 +19789,13 @@ var Canvas2dBackend = class {
|
|
|
19319
19789
|
drawCandles(ctx, scene, bars, i0, i1, coords, pane, theme, barColors) {
|
|
19320
19790
|
const spacing = coords.bodySpacing();
|
|
19321
19791
|
const tier = candleTier(spacing);
|
|
19792
|
+
const paint = effectiveCandlePaint(scene.style.candle, scene.candleOverride, theme.upColor, theme.downColor);
|
|
19322
19793
|
if (tier === "aggregate") {
|
|
19323
|
-
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane,
|
|
19794
|
+
this.drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, paint.up, paint.down, barColors);
|
|
19324
19795
|
return;
|
|
19325
19796
|
}
|
|
19326
19797
|
const drawBody = tier === "full";
|
|
19327
|
-
const cs =
|
|
19798
|
+
const cs = paint.candle;
|
|
19328
19799
|
const fading = this.candleStructureAlpha > this.candleBodyAlpha + 1e-3;
|
|
19329
19800
|
for (let i = i0; i <= i1; i += 1) {
|
|
19330
19801
|
const b = bars[i];
|
|
@@ -19332,7 +19803,7 @@ var Canvas2dBackend = class {
|
|
|
19332
19803
|
const g = candleGeometry(coords.logicalToX(i), spacing, coords.dpr, this.candleBodyScale);
|
|
19333
19804
|
const x = g.center;
|
|
19334
19805
|
const up = b.close >= b.open;
|
|
19335
|
-
const dir = up ?
|
|
19806
|
+
const dir = up ? paint.up : paint.down;
|
|
19336
19807
|
const bc = barColors.get(b.time);
|
|
19337
19808
|
const color = bc ?? dir;
|
|
19338
19809
|
let top = 0;
|
|
@@ -20443,7 +20914,7 @@ var ChromeRenderer = class {
|
|
|
20443
20914
|
constructor() {
|
|
20444
20915
|
this.canvas = null;
|
|
20445
20916
|
this.ctx = null;
|
|
20446
|
-
// The color for axis tick labels — the
|
|
20917
|
+
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
20447
20918
|
this.axisTextColor = DARK_THEME.textColor;
|
|
20448
20919
|
// Shared Pine-drawing renderer (line/box/label/polyline/linefill); widthCache persists.
|
|
20449
20920
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
@@ -20475,8 +20946,8 @@ var ChromeRenderer = class {
|
|
|
20475
20946
|
return dr;
|
|
20476
20947
|
}
|
|
20477
20948
|
/** Clear the chrome canvas and draw drawings + axes + current-price line.
|
|
20478
|
-
* `surface` (background + text) paints the axis-scale gutters
|
|
20479
|
-
*
|
|
20949
|
+
* `surface` (background + text) paints the axis-scale gutters — the host passes the live
|
|
20950
|
+
* chart background so the scales read as part of the plot, with contrast-corrected text.
|
|
20480
20951
|
* Falls back to the theme's own colors when no surface is supplied. */
|
|
20481
20952
|
render(scene, coords, theme, surface) {
|
|
20482
20953
|
const ctx = this.ctx;
|
|
@@ -21110,11 +21581,11 @@ function buildColorPicker(color, theme, onChange) {
|
|
|
21110
21581
|
|
|
21111
21582
|
// src/renderers/native/chrome/ColorField.ts
|
|
21112
21583
|
var CHECKER2 = transparencyChecker(8);
|
|
21113
|
-
var
|
|
21584
|
+
var STYLE_ID15 = "vela-color-field";
|
|
21114
21585
|
function ensureStyles4() {
|
|
21115
|
-
if (typeof document === "undefined" || document.getElementById(
|
|
21586
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID15)) return;
|
|
21116
21587
|
const st = document.createElement("style");
|
|
21117
|
-
st.id =
|
|
21588
|
+
st.id = STYLE_ID15;
|
|
21118
21589
|
st.textContent = `
|
|
21119
21590
|
.vela-color-field{width:24px;height:24px;padding:2px;border:1px solid var(--vela-border);border-radius:0;background:var(--vela-surface-sunken);cursor:pointer;display:inline-flex;flex:none;}
|
|
21120
21591
|
.vela-color-field:hover{border-color:var(--vela-fg-muted);}
|
|
@@ -21243,6 +21714,37 @@ function ensureControlStyles() {
|
|
|
21243
21714
|
.vela-sd-btn:hover{background:var(--vela-hover);border-color:var(--vela-border-strong);color:var(--vela-fg-bright);}
|
|
21244
21715
|
.vela-sd-close{cursor:pointer;display:inline-flex;align-items:center;justify-content:center;background:transparent;border:none;color:var(--vela-fg-muted);line-height:0;width:30px;height:30px;border-radius:var(--vela-radius-sm);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
21245
21716
|
.vela-sd-close:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
21717
|
+
/* Rows/blocks gated away by chart-type conditions, TOC filters, or the instance strip.
|
|
21718
|
+
!important: the pane grid rewrites inline display ('contents') AFTER the initial
|
|
21719
|
+
visibility pass, so a class must win. */
|
|
21720
|
+
.vela-sd-hide{display:none !important;}
|
|
21721
|
+
/* Indented rail sub-entry (a chart-type section's subsection tab). */
|
|
21722
|
+
.vela-sd-tab-sub{padding-left:28px;font-weight:600;font-size:12px;}
|
|
21723
|
+
/* Instance strip: a tab per present instance (label, \u2715 on the active removable one)
|
|
21724
|
+
and a dashed + that turns on the next absent instance. The rule under it separates
|
|
21725
|
+
the strip from the instance's TOC + rows area. */
|
|
21726
|
+
.vela-sd-itabs{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin:14px 0 0;padding-bottom:12px;border-bottom:1px solid var(--vela-border);}
|
|
21727
|
+
.vela-sd-itab{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 11px;background:transparent;border:1px solid var(--vela-border);border-radius:var(--vela-radius-md);color:var(--vela-fg-muted);font-family:inherit;font-size:var(--vela-font-size-md);font-weight:600;cursor:pointer;transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
|
|
21728
|
+
.vela-sd-itab:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
21729
|
+
.vela-sd-itab.on{background:var(--vela-active);color:var(--vela-fg-bright);border-color:var(--vela-border-strong);}
|
|
21730
|
+
.vela-sd-ix{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;margin-right:-4px;border-radius:var(--vela-radius-sm);color:var(--vela-fg-muted);font-size:10px;line-height:1;}
|
|
21731
|
+
.vela-sd-ix:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
21732
|
+
.vela-sd-itab-add{border-style:dashed;min-width:30px;justify-content:center;padding:0;}
|
|
21733
|
+
/* Structured pane: group TOC column + rows column, TOP-ALIGNED (the shared padding-top
|
|
21734
|
+
lives on the wrap, never on one column). The TOC sticks while the pane scrolls; the
|
|
21735
|
+
vertical rule sits on the rows column so it spans the full content height. When every
|
|
21736
|
+
group gates out the TOC hides and .no-toc drops the rule with it. */
|
|
21737
|
+
.vela-sd-struct{display:flex;align-items:flex-start;padding-top:14px;}
|
|
21738
|
+
.vela-sd-toc{position:sticky;top:0;flex:0 0 auto;min-width:104px;display:flex;flex-direction:column;gap:2px;padding:2px 14px 0 0;}
|
|
21739
|
+
.vela-sd-struct>[data-sd-rows-host]{border-left:1px solid var(--vela-border);padding-left:18px;}
|
|
21740
|
+
.vela-sd-struct.no-toc>[data-sd-rows-host]{border-left:none;padding-left:0;}
|
|
21741
|
+
.vela-sd-toc-btn{text-align:left;padding:6px 10px;background:transparent;border:none;border-radius:var(--vela-radius-sm);color:var(--vela-fg-muted);font-family:inherit;font-size:12px;font-weight:600;cursor:pointer;transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
21742
|
+
.vela-sd-toc-btn:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
|
|
21743
|
+
.vela-sd-toc-btn.on{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
21744
|
+
/* Soft-disable: a subsection's enableKey is off \u2014 rows stay visible (browseable) but
|
|
21745
|
+
muted and non-interactive. Applied to each row's children so it survives display:contents;
|
|
21746
|
+
!important beats the inline opacity on labels. */
|
|
21747
|
+
.vela-sd-soft>*{opacity:0.4 !important;pointer-events:none !important;}
|
|
21246
21748
|
`;
|
|
21247
21749
|
document.head.appendChild(st);
|
|
21248
21750
|
}
|
|
@@ -21263,6 +21765,10 @@ var SettingsDialog = class {
|
|
|
21263
21765
|
this.themeControl = null;
|
|
21264
21766
|
/** The built tabs, by title — how `showSection` reaches a pane while the dialog is open. */
|
|
21265
21767
|
this.tabs = [];
|
|
21768
|
+
/** Active instance-strip tab per chart type — remembered across dialog rebuilds. */
|
|
21769
|
+
this.typeActiveInstance = /* @__PURE__ */ new Map();
|
|
21770
|
+
/** Active TOC group per structured pane (`<typeId>/<pane>` → group label). */
|
|
21771
|
+
this.typeActiveGroup = /* @__PURE__ */ new Map();
|
|
21266
21772
|
/** The tab currently shown, so a theme change (which rebuilds) lands back on it. */
|
|
21267
21773
|
this.activeSection = null;
|
|
21268
21774
|
if (getComputedStyle(container).position === "static") container.style.position = "relative";
|
|
@@ -21424,6 +21930,29 @@ var SettingsDialog = class {
|
|
|
21424
21930
|
baseline.append(this.numberRow("Width", config.baseline.width, 1, 10, 1, (v) => this.emit({ baseline: { width: v } })));
|
|
21425
21931
|
groups.baseline = baseline;
|
|
21426
21932
|
body.append(baseline);
|
|
21933
|
+
for (const def of chartTypes()) {
|
|
21934
|
+
if (!hasOwnCandlePaint(def.id)) continue;
|
|
21935
|
+
const bag = config.chartTypes[def.id] ?? {};
|
|
21936
|
+
const colorOf2 = (key, shared) => typeof bag[key] === "string" && bag[key] !== "" ? bag[key] : shared;
|
|
21937
|
+
const boolOf = (key, shared) => typeof bag[key] === "boolean" ? bag[key] : shared;
|
|
21938
|
+
const g = this.group();
|
|
21939
|
+
g.append(this.sectionTitle("Candles"));
|
|
21940
|
+
g.append(this.toggleRow("Body", boolOf("candleBodyVisible", config.candles.bodyVisible), (v) => this.emitType(def.id, "candleBodyVisible", v), [
|
|
21941
|
+
this.swatch(colorOf2("candleUpColor", config.candles.upColor), (v) => this.emitType(def.id, "candleUpColor", v)),
|
|
21942
|
+
this.swatch(colorOf2("candleDownColor", config.candles.downColor), (v) => this.emitType(def.id, "candleDownColor", v))
|
|
21943
|
+
]));
|
|
21944
|
+
g.append(this.toggleRow("Borders", boolOf("candleBorderVisible", config.candles.borderVisible), (v) => this.emitType(def.id, "candleBorderVisible", v), [
|
|
21945
|
+
this.swatch(colorOf2("candleBorderUpColor", config.candles.borderUpColor), (v) => this.emitType(def.id, "candleBorderUpColor", v)),
|
|
21946
|
+
this.swatch(colorOf2("candleBorderDownColor", config.candles.borderDownColor), (v) => this.emitType(def.id, "candleBorderDownColor", v))
|
|
21947
|
+
]));
|
|
21948
|
+
g.append(this.toggleRow("Wick", boolOf("candleWickVisible", config.candles.wickVisible), (v) => this.emitType(def.id, "candleWickVisible", v), [
|
|
21949
|
+
this.swatch(colorOf2("candleWickUpColor", config.candles.wickUpColor), (v) => this.emitType(def.id, "candleWickUpColor", v)),
|
|
21950
|
+
this.swatch(colorOf2("candleWickDownColor", config.candles.wickDownColor), (v) => this.emitType(def.id, "candleWickDownColor", v))
|
|
21951
|
+
]));
|
|
21952
|
+
g.append(this.numberRow("Spacing", config.series.spacing, 0.1, 10, 0.1, (v) => this.emit({ series: { spacing: v } })));
|
|
21953
|
+
groups[def.id] = g;
|
|
21954
|
+
body.append(g);
|
|
21955
|
+
}
|
|
21427
21956
|
showActive(config.series.style);
|
|
21428
21957
|
body.append(this.sectionTitle("Time zone"));
|
|
21429
21958
|
body.append(this.selectRowLabeled("Time zone", normalizeTimezone(config.timeScale.timezone), timezoneOptions(config.timeScale.timezone), (v) => this.emit({ timeScale: { timezone: v } })));
|
|
@@ -21438,7 +21967,16 @@ var SettingsDialog = class {
|
|
|
21438
21967
|
}
|
|
21439
21968
|
}
|
|
21440
21969
|
};
|
|
21970
|
+
const renderChartTypeSections = (placement) => {
|
|
21971
|
+
for (const def of chartTypes()) {
|
|
21972
|
+
const typeSettings = def.settings;
|
|
21973
|
+
if (!typeSettings) continue;
|
|
21974
|
+
if ((typeSettings.placement ?? "end") !== placement) continue;
|
|
21975
|
+
this.chartTypeSection(def.id, typeSettings, config, body);
|
|
21976
|
+
}
|
|
21977
|
+
};
|
|
21441
21978
|
renderHostSections("symbol");
|
|
21979
|
+
renderChartTypeSections("after-symbol");
|
|
21442
21980
|
renderHostSections("after-symbol");
|
|
21443
21981
|
body.append(this.section("Scales and lines"));
|
|
21444
21982
|
body.append(this.sectionTitle("Price scale"));
|
|
@@ -21479,27 +22017,7 @@ var SettingsDialog = class {
|
|
|
21479
22017
|
body.append(this.sectionTitle("Theme"));
|
|
21480
22018
|
body.append(this.selectRow("Color theme", tc.current === "dark" ? "Dark" : "Light", ["Dark", "Light"], (v) => tc.onSelect(v === "Dark" ? "dark" : "light")));
|
|
21481
22019
|
}
|
|
21482
|
-
|
|
21483
|
-
const typeSettings = def.settings;
|
|
21484
|
-
if (!typeSettings) continue;
|
|
21485
|
-
const marker = this.section(typeSettings.title);
|
|
21486
|
-
marker.dataset.sdStyle = def.id;
|
|
21487
|
-
marker.dataset.sdVisibility = typeSettings.visibility ?? "active";
|
|
21488
|
-
body.append(marker);
|
|
21489
|
-
const values = config.chartTypes[def.id] ?? {};
|
|
21490
|
-
for (const r of typeSettings.rows) {
|
|
21491
|
-
const current2 = values[r.key];
|
|
21492
|
-
if (r.kind === "toggle") {
|
|
21493
|
-
body.append(this.boolRow(r.label, typeof current2 === "boolean" ? current2 : r.defval, (v) => this.emitType(def.id, r.key, v)));
|
|
21494
|
-
} else if (r.kind === "number") {
|
|
21495
|
-
body.append(this.numberRow(r.label, typeof current2 === "number" ? current2 : r.defval, r.min ?? 0, r.max ?? 1e6, r.step ?? 1, (v) => this.emitType(def.id, r.key, v)));
|
|
21496
|
-
} else if (r.kind === "color") {
|
|
21497
|
-
body.append(this.colorRow(r.label, typeof current2 === "string" ? current2 : r.defval, (v) => this.emitType(def.id, r.key, v)));
|
|
21498
|
-
} else {
|
|
21499
|
-
body.append(this.selectRow(r.label, typeof current2 === "string" ? current2 : r.defval, [...r.options], (v) => this.emitType(def.id, r.key, v)));
|
|
21500
|
-
}
|
|
21501
|
-
}
|
|
21502
|
-
}
|
|
22020
|
+
renderChartTypeSections("end");
|
|
21503
22021
|
renderHostSections("end");
|
|
21504
22022
|
const shell = document.createElement("div");
|
|
21505
22023
|
shell.style.cssText = "display:flex;min-height:360px;max-height:calc(70vh - 100px);flex:1 1 auto;";
|
|
@@ -21518,7 +22036,7 @@ var SettingsDialog = class {
|
|
|
21518
22036
|
const tab = document.createElement("button");
|
|
21519
22037
|
tab.type = "button";
|
|
21520
22038
|
tab.textContent = title;
|
|
21521
|
-
tab.className = "vela-sd-tab";
|
|
22039
|
+
tab.className = "vela-sd-tab" + (child.dataset.sdSub !== void 0 ? " vela-sd-tab-sub" : "");
|
|
21522
22040
|
panes.push({ title, el, tab, style: child.dataset.sdStyle, visibility: child.dataset.sdVisibility });
|
|
21523
22041
|
current = el;
|
|
21524
22042
|
child.remove();
|
|
@@ -21558,7 +22076,14 @@ var SettingsDialog = class {
|
|
|
21558
22076
|
scrim.appendChild(dlg);
|
|
21559
22077
|
this.container.appendChild(scrim);
|
|
21560
22078
|
this.root = scrim;
|
|
21561
|
-
for (const p of panes)
|
|
22079
|
+
for (const p of panes) {
|
|
22080
|
+
const hosts = [...p.el.querySelectorAll("[data-sd-rows-host]")];
|
|
22081
|
+
if (hosts.length === 0) {
|
|
22082
|
+
this.layoutSettingsGrids(p.el, dlg);
|
|
22083
|
+
continue;
|
|
22084
|
+
}
|
|
22085
|
+
for (const h of hosts) this.layoutSettingsGrids(h, dlg);
|
|
22086
|
+
}
|
|
21562
22087
|
}
|
|
21563
22088
|
close() {
|
|
21564
22089
|
closeColorPopover();
|
|
@@ -21588,6 +22113,291 @@ var SettingsDialog = class {
|
|
|
21588
22113
|
el.textContent = text;
|
|
21589
22114
|
return el;
|
|
21590
22115
|
}
|
|
22116
|
+
/**
|
|
22117
|
+
* One chart type's settings tab (and its subsection tabs), appended to the linear
|
|
22118
|
+
* `body` for the pane splitter to file. One live values BAG serves the whole section
|
|
22119
|
+
* — instances and subsections share the per-type store, so a `when` gate anywhere
|
|
22120
|
+
* can reference a key edited anywhere else; every edit runs every registered
|
|
22121
|
+
* refresher.
|
|
22122
|
+
*/
|
|
22123
|
+
chartTypeSection(typeId, section, config, body) {
|
|
22124
|
+
const marker = this.section(section.title);
|
|
22125
|
+
marker.dataset.sdStyle = typeId;
|
|
22126
|
+
marker.dataset.sdVisibility = section.visibility ?? "active";
|
|
22127
|
+
body.append(marker);
|
|
22128
|
+
const values = config.chartTypes[typeId] ?? {};
|
|
22129
|
+
const bag = {};
|
|
22130
|
+
const seedKey = (key, want, defval) => {
|
|
22131
|
+
const v = values[key];
|
|
22132
|
+
bag[key] = typeof v === want ? v : defval;
|
|
22133
|
+
};
|
|
22134
|
+
const seed = (rows) => {
|
|
22135
|
+
for (const r of rows) {
|
|
22136
|
+
if (r.kind === "heading" || r.kind === "header") continue;
|
|
22137
|
+
if (r.kind === "range") {
|
|
22138
|
+
seedKey(r.minKey, "number", r.defval);
|
|
22139
|
+
seedKey(r.maxKey, "number", r.defval);
|
|
22140
|
+
continue;
|
|
22141
|
+
}
|
|
22142
|
+
seedKey(r.key, r.kind === "toggle" ? "boolean" : r.kind === "number" ? "number" : "string", r.defval);
|
|
22143
|
+
if (r.kind === "toggle") for (const c of r.colors ?? []) seedKey(c.key, "string", c.defval);
|
|
22144
|
+
}
|
|
22145
|
+
};
|
|
22146
|
+
if (section.rows) seed(section.rows);
|
|
22147
|
+
for (const inst of section.instances ?? []) seed(inst.rows);
|
|
22148
|
+
for (const sub of section.subsections ?? []) seed(sub.rows);
|
|
22149
|
+
for (const inst of section.instances ?? []) {
|
|
22150
|
+
if (!inst.enableKey || inst.enableKey in bag) continue;
|
|
22151
|
+
bag[inst.enableKey] = values[inst.enableKey] === true;
|
|
22152
|
+
}
|
|
22153
|
+
for (const sub of section.subsections ?? []) {
|
|
22154
|
+
if (!sub.enableKey || sub.enableKey in bag) continue;
|
|
22155
|
+
bag[sub.enableKey] = values[sub.enableKey] === true;
|
|
22156
|
+
}
|
|
22157
|
+
const refreshers = [];
|
|
22158
|
+
const put = (key, v) => {
|
|
22159
|
+
bag[key] = v;
|
|
22160
|
+
this.emitType(typeId, key, v);
|
|
22161
|
+
for (const r of refreshers) r();
|
|
22162
|
+
};
|
|
22163
|
+
if (section.instances && section.instances.length > 0) {
|
|
22164
|
+
body.append(this.instancesBlock(typeId, section.instances, bag, put, refreshers));
|
|
22165
|
+
} else if (section.rows) {
|
|
22166
|
+
this.flatTypeRows(section.rows, bag, put, refreshers, body);
|
|
22167
|
+
}
|
|
22168
|
+
for (const sub of section.subsections ?? []) {
|
|
22169
|
+
const subMarker = this.section(sub.title);
|
|
22170
|
+
subMarker.dataset.sdStyle = typeId;
|
|
22171
|
+
subMarker.dataset.sdVisibility = section.visibility ?? "active";
|
|
22172
|
+
subMarker.dataset.sdSub = "1";
|
|
22173
|
+
body.append(subMarker);
|
|
22174
|
+
body.append(this.groupedRows(`${typeId}/${sub.title}`, sub.rows, bag, put, refreshers, sub.enableKey));
|
|
22175
|
+
}
|
|
22176
|
+
for (const r of refreshers) r();
|
|
22177
|
+
}
|
|
22178
|
+
/** The FLAT chart-type form: rows appended straight to the body (the pane grid wraps
|
|
22179
|
+
* them), headings/headers as inline group titles, `when` gates refreshed live. */
|
|
22180
|
+
flatTypeRows(rows, bag, put, refreshers, body) {
|
|
22181
|
+
const entries = [];
|
|
22182
|
+
for (const r of rows) {
|
|
22183
|
+
const el = r.kind === "heading" || r.kind === "header" ? this.sectionTitle(r.label) : this.typeRow(r, bag, put);
|
|
22184
|
+
entries.push({ el, when: r.when });
|
|
22185
|
+
body.append(el);
|
|
22186
|
+
}
|
|
22187
|
+
refreshers.push(() => {
|
|
22188
|
+
for (const e of entries) e.el.classList.toggle("vela-sd-hide", !settingsRowVisible(e.when, bag));
|
|
22189
|
+
});
|
|
22190
|
+
}
|
|
22191
|
+
/** One value row for a chart-type descriptor, writing through `put`. */
|
|
22192
|
+
typeRow(r, bag, put) {
|
|
22193
|
+
if (r.kind === "toggle") {
|
|
22194
|
+
const swatches = (r.colors ?? []).map((c) => {
|
|
22195
|
+
const sw = this.swatch(bag[c.key], (v) => put(c.key, v));
|
|
22196
|
+
sw.title = c.label;
|
|
22197
|
+
return sw;
|
|
22198
|
+
});
|
|
22199
|
+
return this.toggleRow(r.label, bag[r.key], (v) => put(r.key, v), swatches);
|
|
22200
|
+
}
|
|
22201
|
+
if (r.kind === "number") return this.numberRow(r.label, bag[r.key], r.min ?? 0, r.max ?? 1e6, r.step ?? 1, (v) => put(r.key, v));
|
|
22202
|
+
if (r.kind === "color") return this.colorRow(r.label, bag[r.key], (v) => put(r.key, v));
|
|
22203
|
+
if (r.kind === "range") return this.rangeRow(r, bag, put);
|
|
22204
|
+
return this.selectRowLabeled(r.label, bag[r.key], normalizeSelectOptions(r.options), (v) => put(r.key, v));
|
|
22205
|
+
}
|
|
22206
|
+
/**
|
|
22207
|
+
* A MIN–MAX row: two number inputs on one row (stored under the descriptor's
|
|
22208
|
+
* `minKey`/`maxKey`). With a `placeholder`, an input at the DEFAULT value renders
|
|
22209
|
+
* empty showing it, and clearing an input stores the default back — the
|
|
22210
|
+
* placeholder names the unset state ('Off' for 0-disables bounds).
|
|
22211
|
+
*/
|
|
22212
|
+
rangeRow(r, bag, put) {
|
|
22213
|
+
const { wrap } = this.row(r.label);
|
|
22214
|
+
const input = (key, title) => {
|
|
22215
|
+
const ni = document.createElement("input");
|
|
22216
|
+
ni.type = "number";
|
|
22217
|
+
ni.className = "vela-sd-number";
|
|
22218
|
+
ni.min = String(r.min ?? 0);
|
|
22219
|
+
ni.max = String(r.max ?? 1e6);
|
|
22220
|
+
ni.step = String(r.step ?? 1);
|
|
22221
|
+
ni.title = title;
|
|
22222
|
+
const current = bag[key];
|
|
22223
|
+
if (r.placeholder !== void 0) {
|
|
22224
|
+
ni.placeholder = r.placeholder;
|
|
22225
|
+
if (current !== r.defval) ni.value = String(current);
|
|
22226
|
+
} else {
|
|
22227
|
+
ni.value = String(current);
|
|
22228
|
+
}
|
|
22229
|
+
ni.addEventListener("change", () => {
|
|
22230
|
+
const raw = ni.value.trim() === "" ? r.defval : Number(ni.value);
|
|
22231
|
+
const v = Number.isFinite(raw) ? Math.min(r.max ?? Infinity, Math.max(r.min ?? -Infinity, raw)) : r.defval;
|
|
22232
|
+
ni.value = r.placeholder !== void 0 && v === r.defval ? "" : String(v);
|
|
22233
|
+
put(key, v);
|
|
22234
|
+
});
|
|
22235
|
+
return ni;
|
|
22236
|
+
};
|
|
22237
|
+
const box = document.createElement("div");
|
|
22238
|
+
box.style.cssText = "display:flex;align-items:center;gap:6px;flex:0 0 auto;";
|
|
22239
|
+
const dash = document.createElement("span");
|
|
22240
|
+
dash.textContent = "\u2013";
|
|
22241
|
+
dash.style.cssText = "color:var(--vela-fg-muted);";
|
|
22242
|
+
box.append(input(r.minKey, `${r.label} \u2014 min`), dash, input(r.maxKey, `${r.label} \u2014 max`));
|
|
22243
|
+
wrap.appendChild(box);
|
|
22244
|
+
return wrap;
|
|
22245
|
+
}
|
|
22246
|
+
/**
|
|
22247
|
+
* The INSTANCE STRIP block: a tab per present instance (label, `×` on the active
|
|
22248
|
+
* removable one), a dashed `+` while an instance is still absent, and one
|
|
22249
|
+
* grouped-rows content per instance below — only the active tab's content shows.
|
|
22250
|
+
* Presence is the boolean at each instance's `enableKey`; the strip rebuilds on
|
|
22251
|
+
* every section edit, so gates elsewhere stay coherent.
|
|
22252
|
+
*/
|
|
22253
|
+
instancesBlock(typeId, instances, bag, put, refreshers) {
|
|
22254
|
+
const wrap = document.createElement("div");
|
|
22255
|
+
const strip = document.createElement("div");
|
|
22256
|
+
strip.className = "vela-sd-itabs";
|
|
22257
|
+
wrap.append(strip);
|
|
22258
|
+
const contents = instances.map((inst, i) => {
|
|
22259
|
+
const content = this.groupedRows(`${typeId}/#${i}`, inst.rows, bag, put, refreshers);
|
|
22260
|
+
wrap.append(content);
|
|
22261
|
+
return content;
|
|
22262
|
+
});
|
|
22263
|
+
const present = (inst) => !inst.enableKey || bag[inst.enableKey] === true;
|
|
22264
|
+
const refresh = () => {
|
|
22265
|
+
let active = this.typeActiveInstance.get(typeId) ?? 0;
|
|
22266
|
+
if (!present(instances[active] ?? instances[0])) active = 0;
|
|
22267
|
+
this.typeActiveInstance.set(typeId, active);
|
|
22268
|
+
strip.replaceChildren();
|
|
22269
|
+
instances.forEach((inst, i) => {
|
|
22270
|
+
if (!present(inst)) return;
|
|
22271
|
+
const tab = document.createElement("button");
|
|
22272
|
+
tab.type = "button";
|
|
22273
|
+
tab.className = "vela-sd-itab" + (i === active ? " on" : "");
|
|
22274
|
+
const lbl = document.createElement("span");
|
|
22275
|
+
lbl.textContent = inst.label;
|
|
22276
|
+
tab.append(lbl);
|
|
22277
|
+
if (i === active && inst.enableKey) {
|
|
22278
|
+
const x = document.createElement("span");
|
|
22279
|
+
x.className = "vela-sd-ix";
|
|
22280
|
+
x.textContent = "\u2715";
|
|
22281
|
+
x.title = `Remove ${inst.label}`;
|
|
22282
|
+
x.addEventListener("click", (e) => {
|
|
22283
|
+
e.stopPropagation();
|
|
22284
|
+
this.typeActiveInstance.set(typeId, 0);
|
|
22285
|
+
put(inst.enableKey, false);
|
|
22286
|
+
});
|
|
22287
|
+
tab.append(x);
|
|
22288
|
+
}
|
|
22289
|
+
tab.addEventListener("click", () => {
|
|
22290
|
+
this.typeActiveInstance.set(typeId, i);
|
|
22291
|
+
refresh();
|
|
22292
|
+
});
|
|
22293
|
+
strip.append(tab);
|
|
22294
|
+
});
|
|
22295
|
+
const absent = instances.findIndex((inst) => !present(inst));
|
|
22296
|
+
if (absent >= 0) {
|
|
22297
|
+
const add = document.createElement("button");
|
|
22298
|
+
add.type = "button";
|
|
22299
|
+
add.className = "vela-sd-itab vela-sd-itab-add";
|
|
22300
|
+
add.textContent = "+";
|
|
22301
|
+
add.title = `Add ${instances[absent].label}`;
|
|
22302
|
+
add.addEventListener("click", () => {
|
|
22303
|
+
this.typeActiveInstance.set(typeId, absent);
|
|
22304
|
+
put(instances[absent].enableKey, true);
|
|
22305
|
+
});
|
|
22306
|
+
strip.append(add);
|
|
22307
|
+
}
|
|
22308
|
+
contents.forEach((c, i) => c.classList.toggle("vela-sd-hide", i !== active));
|
|
22309
|
+
};
|
|
22310
|
+
refreshers.push(refresh);
|
|
22311
|
+
return wrap;
|
|
22312
|
+
}
|
|
22313
|
+
/**
|
|
22314
|
+
* A GROUPED rows pane: a TOC column of the group labels (from `heading` rows) and
|
|
22315
|
+
* ONE rows host holding every row — the active group's rows show, the rest hide.
|
|
22316
|
+
* Rows BEFORE the first heading are the always block, visible above every group.
|
|
22317
|
+
* `header` rows stay in the rows column as in-group subgroup titles (not TOC
|
|
22318
|
+
* entries). A group whose value rows are all gated out (or whose heading's own
|
|
22319
|
+
* `when` fails) leaves the TOC; the whole TOC hides when no group is live. The
|
|
22320
|
+
* rows host is tagged for `layoutSettingsGrids`, keeping one shared label column
|
|
22321
|
+
* across all groups.
|
|
22322
|
+
*
|
|
22323
|
+
* `enableKey` (optional): while that bag boolean is false, every row except the one
|
|
22324
|
+
* whose key matches is soft-disabled (visible but grayed / non-interactive) so the
|
|
22325
|
+
* pane stays browseable with the feature off.
|
|
22326
|
+
*/
|
|
22327
|
+
groupedRows(paneKey, rows, bag, put, refreshers, enableKey) {
|
|
22328
|
+
const wrap = document.createElement("div");
|
|
22329
|
+
wrap.className = "vela-sd-struct";
|
|
22330
|
+
const toc = document.createElement("div");
|
|
22331
|
+
toc.className = "vela-sd-toc";
|
|
22332
|
+
const rowsHost = document.createElement("div");
|
|
22333
|
+
rowsHost.dataset.sdRowsHost = "1";
|
|
22334
|
+
rowsHost.style.cssText = "flex:1 1 auto;min-width:0;";
|
|
22335
|
+
wrap.append(toc, rowsHost);
|
|
22336
|
+
const entries = [];
|
|
22337
|
+
const groups = [];
|
|
22338
|
+
const groupWhens = [];
|
|
22339
|
+
let g = -1;
|
|
22340
|
+
for (const r of rows) {
|
|
22341
|
+
if (r.kind === "heading") {
|
|
22342
|
+
g = groups.length;
|
|
22343
|
+
groups.push(r.label);
|
|
22344
|
+
groupWhens.push(r.when);
|
|
22345
|
+
continue;
|
|
22346
|
+
}
|
|
22347
|
+
if (r.kind === "header") {
|
|
22348
|
+
entries.push({ el: this.sectionTitle(r.label), when: r.when, group: g, header: true });
|
|
22349
|
+
rowsHost.append(entries[entries.length - 1].el);
|
|
22350
|
+
continue;
|
|
22351
|
+
}
|
|
22352
|
+
const el = this.typeRow(r, bag, put);
|
|
22353
|
+
const key = r.kind === "range" ? void 0 : r.key;
|
|
22354
|
+
entries.push({ el, when: r.when, group: g, key });
|
|
22355
|
+
rowsHost.append(el);
|
|
22356
|
+
}
|
|
22357
|
+
const refresh = () => {
|
|
22358
|
+
const live = groups.map((_, gi) => settingsRowVisible(groupWhens[gi], bag) && entries.some((e) => e.group === gi && !e.header && settingsRowVisible(e.when, bag)));
|
|
22359
|
+
let activeIdx = groups.indexOf(this.typeActiveGroup.get(paneKey) ?? "");
|
|
22360
|
+
if (activeIdx < 0 || !live[activeIdx]) activeIdx = live.findIndex(Boolean);
|
|
22361
|
+
if (activeIdx >= 0) this.typeActiveGroup.set(paneKey, groups[activeIdx]);
|
|
22362
|
+
toc.replaceChildren();
|
|
22363
|
+
groups.forEach((label, gi) => {
|
|
22364
|
+
if (!live[gi]) return;
|
|
22365
|
+
const b = document.createElement("button");
|
|
22366
|
+
b.type = "button";
|
|
22367
|
+
b.className = "vela-sd-toc-btn" + (gi === activeIdx ? " on" : "");
|
|
22368
|
+
b.textContent = label;
|
|
22369
|
+
b.addEventListener("click", () => {
|
|
22370
|
+
this.typeActiveGroup.set(paneKey, label);
|
|
22371
|
+
refresh();
|
|
22372
|
+
});
|
|
22373
|
+
toc.append(b);
|
|
22374
|
+
});
|
|
22375
|
+
const anyGroup = live.some(Boolean);
|
|
22376
|
+
toc.classList.toggle("vela-sd-hide", !anyGroup);
|
|
22377
|
+
wrap.classList.toggle("no-toc", !anyGroup);
|
|
22378
|
+
const enabled = !enableKey || bag[enableKey] === true;
|
|
22379
|
+
for (let i = 0; i < entries.length; i++) {
|
|
22380
|
+
const e = entries[i];
|
|
22381
|
+
let visible = (e.group === -1 || e.group === activeIdx) && settingsRowVisible(e.when, bag);
|
|
22382
|
+
if (visible && e.header) {
|
|
22383
|
+
let hasContent = false;
|
|
22384
|
+
for (let j = i + 1; j < entries.length; j++) {
|
|
22385
|
+
const n = entries[j];
|
|
22386
|
+
if (n.group !== e.group || n.header) break;
|
|
22387
|
+
if (settingsRowVisible(n.when, bag)) {
|
|
22388
|
+
hasContent = true;
|
|
22389
|
+
break;
|
|
22390
|
+
}
|
|
22391
|
+
}
|
|
22392
|
+
visible = hasContent;
|
|
22393
|
+
}
|
|
22394
|
+
e.el.classList.toggle("vela-sd-hide", !visible);
|
|
22395
|
+
e.el.classList.toggle("vela-sd-soft", visible && !enabled && e.key !== enableKey);
|
|
22396
|
+
}
|
|
22397
|
+
};
|
|
22398
|
+
refreshers.push(refresh);
|
|
22399
|
+
return wrap;
|
|
22400
|
+
}
|
|
21591
22401
|
/** Vertical breathing space between row clusters — grouping reads from whitespace,
|
|
21592
22402
|
* so no drawn rule. */
|
|
21593
22403
|
separator() {
|
|
@@ -21850,7 +22660,7 @@ var SettingsDialog = class {
|
|
|
21850
22660
|
const { wrap } = this.row(label);
|
|
21851
22661
|
const sel = document.createElement("select");
|
|
21852
22662
|
sel.className = "vela-sd-select";
|
|
21853
|
-
sel.style.cssText = "max-width:
|
|
22663
|
+
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
21854
22664
|
for (const [val, lbl] of options) {
|
|
21855
22665
|
const o = document.createElement("option");
|
|
21856
22666
|
o.value = val;
|
|
@@ -21866,7 +22676,7 @@ var SettingsDialog = class {
|
|
|
21866
22676
|
const { wrap } = this.row(label);
|
|
21867
22677
|
const sel = document.createElement("select");
|
|
21868
22678
|
sel.className = "vela-sd-select";
|
|
21869
|
-
sel.style.cssText = "max-width:
|
|
22679
|
+
sel.style.cssText = "max-width:200px;flex:0 0 auto;";
|
|
21870
22680
|
for (const opt of options) {
|
|
21871
22681
|
const o = document.createElement("option");
|
|
21872
22682
|
o.value = opt;
|
|
@@ -21903,6 +22713,9 @@ var SettingsDialog = class {
|
|
|
21903
22713
|
}
|
|
21904
22714
|
};
|
|
21905
22715
|
var AUTO_MANUAL_OPTS = [["auto", "Auto"], ["manual", "Manual"]];
|
|
22716
|
+
function normalizeSelectOptions(options) {
|
|
22717
|
+
return options.map((o) => typeof o === "string" ? [o, o] : o);
|
|
22718
|
+
}
|
|
21906
22719
|
function timezoneOptions(current) {
|
|
21907
22720
|
const options = TIMEZONES.map((t) => [t.value, tzMenuLabel(t.value, t.label)]);
|
|
21908
22721
|
const normalized = normalizeTimezone(current);
|
|
@@ -23261,6 +24074,8 @@ function topDrawingAt(drawings, x, y, proj, tol = HIT_TOLERANCE) {
|
|
|
23261
24074
|
// src/renderers/native/drawings/DrawingInteraction.ts
|
|
23262
24075
|
var DRAG_SLOP3 = 3;
|
|
23263
24076
|
var FREEHAND_SAMPLE_PX = 4;
|
|
24077
|
+
var ANGLE_SNAP_STEP = Math.PI / 4;
|
|
24078
|
+
var ANGLE_SNAP_TYPES = /* @__PURE__ */ new Set(["trendline", "ray", "extendedline", "infoline", "trendangle", "arrow"]);
|
|
23264
24079
|
var DrawingInteraction = class {
|
|
23265
24080
|
// magnet target for the ring
|
|
23266
24081
|
constructor(deps) {
|
|
@@ -23301,6 +24116,27 @@ var DrawingInteraction = class {
|
|
|
23301
24116
|
this.snapAt = changed ? { point: snapped, paneId } : null;
|
|
23302
24117
|
return snapped;
|
|
23303
24118
|
}
|
|
24119
|
+
/** Resolve a pixel to a data point with the segment angle locked to 45° steps around
|
|
24120
|
+
* `pivot` (Shift held on a line tool). Works in PIXEL space — the user reasons about
|
|
24121
|
+
* the angle they see, not about time/price units. The magnet is bypassed: snapping
|
|
24122
|
+
* the result to a candle afterwards would break the exact angle just enforced. */
|
|
24123
|
+
resolveAngleSnapped(pivot, paneId, x, y) {
|
|
24124
|
+
const proj = this.deps.projector();
|
|
24125
|
+
const px = proj.xOf(pivot.time);
|
|
24126
|
+
const py = proj.yOf(pivot.price, paneId);
|
|
24127
|
+
this.snapAt = null;
|
|
24128
|
+
if (py == null) return proj.pxToPoint(x, y, paneId);
|
|
24129
|
+
const r = Math.hypot(x - px, y - py);
|
|
24130
|
+
if (r < 1) return proj.pxToPoint(x, y, paneId);
|
|
24131
|
+
const angle = Math.round(Math.atan2(y - py, x - px) / ANGLE_SNAP_STEP) * ANGLE_SNAP_STEP;
|
|
24132
|
+
return proj.pxToPoint(px + r * Math.cos(angle), py + r * Math.sin(angle), paneId);
|
|
24133
|
+
}
|
|
24134
|
+
/** The pivot the free endpoint angle-snaps around while placing `draft`, or null when
|
|
24135
|
+
* the tool is not a two-point segment (or no anchor is committed yet). */
|
|
24136
|
+
placingPivot(draft) {
|
|
24137
|
+
if (!ANGLE_SNAP_TYPES.has(draft.type)) return null;
|
|
24138
|
+
return draft.anchors[draft.anchors.length - 1] ?? null;
|
|
24139
|
+
}
|
|
23304
24140
|
/** Should the drawing layer win this press (vs pan)? Armed/placing/pressed, or over a drawing/handle. */
|
|
23305
24141
|
claim(x, y) {
|
|
23306
24142
|
if (this.deps.activeTool() != null || this.state.kind !== "idle") return true;
|
|
@@ -23322,12 +24158,15 @@ var DrawingInteraction = class {
|
|
|
23322
24158
|
return this.state.kind === "pressed" && this.state.moved ? this.state.id : null;
|
|
23323
24159
|
}
|
|
23324
24160
|
/** A press: place the next anchor, or grab a drawing/handle (resolved as click or drag on release).
|
|
23325
|
-
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging
|
|
24161
|
+
* `shift` over a drawing toggles it in/out of the selection (multi-select) instead of dragging;
|
|
24162
|
+
* while placing a line tool it locks the segment angle to 45° steps. */
|
|
23326
24163
|
down(x, y, mode = "off", shift = false) {
|
|
23327
24164
|
const proj = this.deps.projector();
|
|
23328
24165
|
if (this.state.kind === "placing") {
|
|
23329
24166
|
if (this.state.draft.placementMode() !== "click") return;
|
|
23330
|
-
this.
|
|
24167
|
+
const pivot = shift ? this.placingPivot(this.state.draft) : null;
|
|
24168
|
+
const point = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
24169
|
+
this.state.draft.anchors.push(this.placementAnchor(this.state.draft, point));
|
|
23331
24170
|
if (this.state.draft.anchors.length >= this.state.need) this.finalize();
|
|
23332
24171
|
else this.deps.changed();
|
|
23333
24172
|
return;
|
|
@@ -23352,8 +24191,9 @@ var DrawingInteraction = class {
|
|
|
23352
24191
|
this.state = { kind: "pressed", id: hit.id, handle, grab: proj.pxToPoint(x, y, hit.paneId), orig: cloneAnchors(hit), px: x, py: y, moved: false };
|
|
23353
24192
|
}
|
|
23354
24193
|
}
|
|
23355
|
-
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
23356
|
-
|
|
24194
|
+
/** Pointer move: advance the placing ghost, or live-preview a drag once past the slop.
|
|
24195
|
+
* `shift` on a line tool locks the segment angle to 45° steps (placing + handle drags). */
|
|
24196
|
+
move(x, y, mode = "off", shift = false) {
|
|
23357
24197
|
if (this.state.kind === "idle") {
|
|
23358
24198
|
const tool = this.deps.activeTool();
|
|
23359
24199
|
const had = this.snapAt != null;
|
|
@@ -23367,7 +24207,8 @@ var DrawingInteraction = class {
|
|
|
23367
24207
|
this.captureFreehand(x, y);
|
|
23368
24208
|
return;
|
|
23369
24209
|
}
|
|
23370
|
-
|
|
24210
|
+
const pivot = shift ? this.placingPivot(this.state.draft) : null;
|
|
24211
|
+
this.state.cursor = pivot ? this.resolveAngleSnapped(pivot, this.state.draft.paneId, x, y) : this.resolve(x, y, this.state.draft.paneId, mode);
|
|
23371
24212
|
this.deps.changed();
|
|
23372
24213
|
return;
|
|
23373
24214
|
}
|
|
@@ -23378,7 +24219,11 @@ var DrawingInteraction = class {
|
|
|
23378
24219
|
this.state.moved = true;
|
|
23379
24220
|
if (this.state.handle >= 0) {
|
|
23380
24221
|
const a = d.anchors[this.state.handle];
|
|
23381
|
-
if (a)
|
|
24222
|
+
if (a) {
|
|
24223
|
+
const pivot = shift && ANGLE_SNAP_TYPES.has(d.type) && d.anchors.length === 2 ? d.anchors[1 - this.state.handle] : void 0;
|
|
24224
|
+
const point = pivot ? this.resolveAngleSnapped(pivot, d.paneId, x, y) : this.resolve(x, y, d.paneId, mode);
|
|
24225
|
+
applyFree(a, point, d.anchorSchema().slots[this.state.handle]?.free ?? "both");
|
|
24226
|
+
}
|
|
23382
24227
|
d.constrainHandleDrag(this.state.handle);
|
|
23383
24228
|
} else {
|
|
23384
24229
|
const pt = this.deps.projector().pxToPoint(x, y, d.paneId);
|
|
@@ -23547,11 +24392,11 @@ function applyFree(anchor, pt, free) {
|
|
|
23547
24392
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
23548
24393
|
var BTN = 30;
|
|
23549
24394
|
var ICON = 21;
|
|
23550
|
-
var
|
|
24395
|
+
var STYLE_ID16 = "vela-drawing-popup-styles";
|
|
23551
24396
|
function ensureStyles5() {
|
|
23552
|
-
if (typeof document === "undefined" || document.getElementById(
|
|
24397
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID16)) return;
|
|
23553
24398
|
const s = document.createElement("style");
|
|
23554
|
-
s.id =
|
|
24399
|
+
s.id = STYLE_ID16;
|
|
23555
24400
|
s.textContent = `
|
|
23556
24401
|
.vela-dpop input[type=color]{-webkit-appearance:none;appearance:none;border:none;padding:0;background:none;cursor:pointer;border-radius:0;}
|
|
23557
24402
|
.vela-dpop input[type=color]::-webkit-color-swatch-wrapper{padding:0;}
|
|
@@ -24887,6 +25732,10 @@ var UserDrawingController = class {
|
|
|
24887
25732
|
this.activeTool = null;
|
|
24888
25733
|
// last-used style for the armed tool (seeds the placement ghost)
|
|
24889
25734
|
this.intentCb = null;
|
|
25735
|
+
/** Another chart's in-progress placement, mirrored here as a ghost (drawings sync). */
|
|
25736
|
+
this.externalGhost = null;
|
|
25737
|
+
/** Last draft fingerprint reported upstream — gates the per-render emission to actual changes. */
|
|
25738
|
+
this.lastDraftKey = null;
|
|
24890
25739
|
this.measure = new MeasureOverlay();
|
|
24891
25740
|
// transient ruler — not a persistent drawing
|
|
24892
25741
|
this.toolbarVisible = false;
|
|
@@ -24958,6 +25807,11 @@ var UserDrawingController = class {
|
|
|
24958
25807
|
syncToolbarGutter() {
|
|
24959
25808
|
this.deps.setToolbarGutter(this.toolbarVisible ? this.toolbarCollapsed ? TOOLBAR_COLLAPSED_WIDTH : TOOLBAR_WIDTH : 0);
|
|
24960
25809
|
}
|
|
25810
|
+
/** Core push: mirror (or clear) another chart's in-progress placement as a ghost. */
|
|
25811
|
+
setExternalGhost(doc) {
|
|
25812
|
+
this.externalGhost = doc ? deserializeDrawing(doc) : null;
|
|
25813
|
+
this.render();
|
|
25814
|
+
}
|
|
24961
25815
|
syncDrawings(docs) {
|
|
24962
25816
|
this.drawings = docs.map((d) => deserializeDrawing(d)).filter((d) => d != null);
|
|
24963
25817
|
if (this.textEditor && !this.editedDrawing(this.textEditor.id)) this.closeTextEditor();
|
|
@@ -25116,7 +25970,7 @@ var UserDrawingController = class {
|
|
|
25116
25970
|
}
|
|
25117
25971
|
this.interaction.down(x, y, snap, shift);
|
|
25118
25972
|
}
|
|
25119
|
-
pointerMove(x, y, snap = "off") {
|
|
25973
|
+
pointerMove(x, y, snap = "off", shift = false) {
|
|
25120
25974
|
if (this.eraserMode) {
|
|
25121
25975
|
if (this.erasing) this.deleteAt(x, y);
|
|
25122
25976
|
return;
|
|
@@ -25126,7 +25980,7 @@ var UserDrawingController = class {
|
|
|
25126
25980
|
this.render();
|
|
25127
25981
|
return;
|
|
25128
25982
|
}
|
|
25129
|
-
this.interaction.move(x, y, snap);
|
|
25983
|
+
this.interaction.move(x, y, snap, shift);
|
|
25130
25984
|
this.updateHover(x, y);
|
|
25131
25985
|
}
|
|
25132
25986
|
/** Track which drawing is hovered so its handles appear on hover (and only then). */
|
|
@@ -25512,6 +26366,8 @@ var UserDrawingController = class {
|
|
|
25512
26366
|
this.layoutTextEditor();
|
|
25513
26367
|
const ghost = this.interaction.ghost();
|
|
25514
26368
|
if (ghost) this.painter.paintGhost(ctx, ghost, proj, this.deps.theme());
|
|
26369
|
+
if (this.externalGhost) this.painter.paintGhost(ctx, this.externalGhost, proj, this.deps.theme());
|
|
26370
|
+
this.emitDraft(ghost);
|
|
25515
26371
|
const markers = this.interaction.placingMarkers(proj);
|
|
25516
26372
|
if (markers) this.painter.paintHandles(ctx, markers);
|
|
25517
26373
|
const m = this.interaction.snapMarker();
|
|
@@ -25573,6 +26429,15 @@ var UserDrawingController = class {
|
|
|
25573
26429
|
}
|
|
25574
26430
|
}, () => this.clearSelection());
|
|
25575
26431
|
}
|
|
26432
|
+
/** Report placement progress upstream (`draft` intent) so the drawings sync can
|
|
26433
|
+
* mirror the ghost live on linked charts. Called from every render; only actual
|
|
26434
|
+
* shape changes emit, and the end of a placement emits one `null`. */
|
|
26435
|
+
emitDraft(ghost) {
|
|
26436
|
+
const key = ghost ? JSON.stringify({ t: ghost.type, p: ghost.paneId, a: ghost.anchors, s: ghost.style, x: ghost.text }) : null;
|
|
26437
|
+
if (key === this.lastDraftKey) return;
|
|
26438
|
+
this.lastDraftKey = key;
|
|
26439
|
+
this.intentCb?.({ kind: "draft", doc: ghost ? ghost.serialize() : null });
|
|
26440
|
+
}
|
|
25576
26441
|
emit(i) {
|
|
25577
26442
|
if (i.kind === "create") {
|
|
25578
26443
|
if (i.doc.text && i.doc.text.color === void 0) {
|
|
@@ -25998,10 +26863,10 @@ var SEPARATOR_HIT_PX = 4;
|
|
|
25998
26863
|
var NativeRenderer = class {
|
|
25999
26864
|
constructor(opts) {
|
|
26000
26865
|
this.capabilities = NATIVE_CAPABILITIES;
|
|
26001
|
-
// The app "chrome" surface (drawing toolbar +
|
|
26866
|
+
// The app "chrome" surface (drawing toolbar + in-chart dialogs): background + text.
|
|
26002
26867
|
// Owned by the app theme alone (mount/setTheme) — config-level background/text edits
|
|
26003
|
-
// (settings dialog, persisted configs) recolor
|
|
26004
|
-
//
|
|
26868
|
+
// (settings dialog, persisted configs) recolor the plot AND the axis scales (see
|
|
26869
|
+
// axisSurface), but never the toolbar/dialog chrome.
|
|
26005
26870
|
this.surfaceBackground = DARK_THEME.background;
|
|
26006
26871
|
this.surfaceTextColor = DARK_THEME.textColor;
|
|
26007
26872
|
// the plot area (canvases + DOM overlays), inset to the right of the toolbar gutter
|
|
@@ -26201,6 +27066,7 @@ var NativeRenderer = class {
|
|
|
26201
27066
|
this.candleDown = opts.downColor;
|
|
26202
27067
|
this.scene.priceStyle = opts.priceStyle;
|
|
26203
27068
|
this.scene.basePainting = basePaintingOf(opts.priceStyle);
|
|
27069
|
+
this.scene.candleOverride = candleOverrideFor(opts.priceStyle, this.scene.style.chartTypes);
|
|
26204
27070
|
}
|
|
26205
27071
|
this.theme = this.deriveTheme(DARK_THEME);
|
|
26206
27072
|
}
|
|
@@ -26678,6 +27544,7 @@ var NativeRenderer = class {
|
|
|
26678
27544
|
baselineLevel: next.baseline.baselineLevel
|
|
26679
27545
|
};
|
|
26680
27546
|
this.setPriceStyle(next.series.style);
|
|
27547
|
+
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
26681
27548
|
this.scene.baselineValue = next.series.baseline;
|
|
26682
27549
|
this.coords.setPitchScale(next.series.spacing);
|
|
26683
27550
|
this.coords.setViewport(this.clampViewport(this.coords.getViewport().barSpacing, this.coords.getViewport().rightOffset));
|
|
@@ -26783,7 +27650,7 @@ var NativeRenderer = class {
|
|
|
26783
27650
|
(patch) => this.applyConfig(patch),
|
|
26784
27651
|
(json) => this.applyConfig(json),
|
|
26785
27652
|
() => {
|
|
26786
|
-
if (this.factoryConfig) this.applyConfig(this.factoryConfig);
|
|
27653
|
+
if (this.factoryConfig) this.applyConfig(factoryResetConfig(this.factoryConfig));
|
|
26787
27654
|
this.settingsDialog?.close();
|
|
26788
27655
|
this.openSettingsDialog();
|
|
26789
27656
|
},
|
|
@@ -26984,10 +27851,16 @@ var NativeRenderer = class {
|
|
|
26984
27851
|
borderColor: isDarkColor(this.surfaceBackground) ? DARK_THEME.borderColor : LIGHT_THEME.borderColor
|
|
26985
27852
|
};
|
|
26986
27853
|
}
|
|
27854
|
+
/** The colors the axis-scale gutters (price + time) paint with: the LIVE chart background
|
|
27855
|
+
* (`layout.background`) and its contrast-corrected text, so the scales read as part of the
|
|
27856
|
+
* plot. Only the toolbar/dialog chrome stays on the stable app-theme surface. */
|
|
27857
|
+
axisSurface() {
|
|
27858
|
+
return { background: this.theme.background, textColor: this.theme.textColor };
|
|
27859
|
+
}
|
|
26987
27860
|
// ── lifecycle ──
|
|
26988
27861
|
mount(container, theme) {
|
|
26989
27862
|
this.mountContainer = container;
|
|
26990
|
-
this.
|
|
27863
|
+
this.publishGutters();
|
|
26991
27864
|
this.theme = this.deriveTheme(theme);
|
|
26992
27865
|
this.surfaceBackground = theme.background;
|
|
26993
27866
|
this.surfaceTextColor = theme.textColor;
|
|
@@ -27059,7 +27932,7 @@ var NativeRenderer = class {
|
|
|
27059
27932
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
27060
27933
|
drawingsSnapMode: () => this.snapMode,
|
|
27061
27934
|
drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
|
|
27062
|
-
drawingsPointerMove: (x, y, snap) => this.userDrawings?.pointerMove(x, y, snap),
|
|
27935
|
+
drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
|
|
27063
27936
|
drawingsPointerUp: (x, y) => this.userDrawings?.pointerUp(x, y),
|
|
27064
27937
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
27065
27938
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
@@ -27296,6 +28169,7 @@ var NativeRenderer = class {
|
|
|
27296
28169
|
this.attributionEl?.remove();
|
|
27297
28170
|
this.attributionEl = null;
|
|
27298
28171
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
28172
|
+
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
27299
28173
|
this.mountContainer = null;
|
|
27300
28174
|
this.wrapper?.remove();
|
|
27301
28175
|
}
|
|
@@ -27464,6 +28338,7 @@ var NativeRenderer = class {
|
|
|
27464
28338
|
const w = RIGHT_AXIS_W + AXIS_COL_W * this.maxOwnScaleColumns();
|
|
27465
28339
|
if (w === this.rightAxisW) return false;
|
|
27466
28340
|
this.rightAxisW = w;
|
|
28341
|
+
this.publishGutters();
|
|
27467
28342
|
if (this.coords.width > 0) this.syncSize();
|
|
27468
28343
|
return true;
|
|
27469
28344
|
}
|
|
@@ -27623,6 +28498,7 @@ var NativeRenderer = class {
|
|
|
27623
28498
|
if (style === this.scene.priceStyle) return;
|
|
27624
28499
|
this.scene.priceStyle = style;
|
|
27625
28500
|
this.scene.basePainting = basePaintingOf(style);
|
|
28501
|
+
this.scene.candleOverride = candleOverrideFor(style, this.scene.style.chartTypes);
|
|
27626
28502
|
for (const cb of this.priceStyleCbs) cb(style);
|
|
27627
28503
|
}
|
|
27628
28504
|
onRemoveIndicator(cb) {
|
|
@@ -27784,7 +28660,10 @@ var NativeRenderer = class {
|
|
|
27784
28660
|
active = true;
|
|
27785
28661
|
}
|
|
27786
28662
|
}
|
|
27787
|
-
this.
|
|
28663
|
+
const clamped = this.clampViewport(barSpacing, rightOffset);
|
|
28664
|
+
if (clamped.barSpacing !== barSpacing) this.targetBarSpacing = clamped.barSpacing;
|
|
28665
|
+
if (this.scrollTargetRO != null && clamped.rightOffset !== rightOffset) this.scrollTargetRO = clamped.rightOffset;
|
|
28666
|
+
this.coords.setViewport(clamped);
|
|
27788
28667
|
this.computeScales();
|
|
27789
28668
|
if (this.easeScales(dtMs)) active = true;
|
|
27790
28669
|
if (this.easeLiveBar(dtMs)) active = true;
|
|
@@ -28236,9 +29115,37 @@ var NativeRenderer = class {
|
|
|
28236
29115
|
this.paintData();
|
|
28237
29116
|
} else if (repaintsChrome(level) && this.paintedData) {
|
|
28238
29117
|
this.chrome.prepare(this.scene, this.coords, this.theme);
|
|
28239
|
-
this.chrome.render(this.scene, this.coords, this.theme,
|
|
29118
|
+
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
28240
29119
|
}
|
|
28241
29120
|
this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
|
|
29121
|
+
if (!repaintsData(level) && this.paintedData) this.repaintCursorLayers();
|
|
29122
|
+
}
|
|
29123
|
+
/** Repaint the SDK layers that opted into cursor tracking (their own canvas only). */
|
|
29124
|
+
repaintCursorLayers() {
|
|
29125
|
+
const pane = this.scene.panes.get(PRICE_PANE_ID2);
|
|
29126
|
+
if (!pane) return;
|
|
29127
|
+
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
29128
|
+
for (const l of this.extLayers) {
|
|
29129
|
+
if (!l.def.repaintOnCursor) continue;
|
|
29130
|
+
l.instance.render(this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs));
|
|
29131
|
+
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
29132
|
+
}
|
|
29133
|
+
}
|
|
29134
|
+
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths). */
|
|
29135
|
+
extLayerArgs(id, scale, bounds, nowMs) {
|
|
29136
|
+
return {
|
|
29137
|
+
bars: this.scene.bars,
|
|
29138
|
+
data: this.scene.nativeData.get(id),
|
|
29139
|
+
settings: this.scene.nativeData.get(`${id}-settings`) ?? {},
|
|
29140
|
+
pending: this.scene.nativePending.get(id) ?? [],
|
|
29141
|
+
coords: this.coords,
|
|
29142
|
+
scale,
|
|
29143
|
+
bounds,
|
|
29144
|
+
theme: this.theme,
|
|
29145
|
+
priceStyle: this.scene.priceStyle,
|
|
29146
|
+
nowMs,
|
|
29147
|
+
cursor: this.lastPointer
|
|
29148
|
+
};
|
|
28242
29149
|
}
|
|
28243
29150
|
/** Paint the below-data (L-1) + geometry (L0) + chrome (L1) layers from the current scene/coords. */
|
|
28244
29151
|
paintData() {
|
|
@@ -28271,18 +29178,16 @@ var NativeRenderer = class {
|
|
|
28271
29178
|
});
|
|
28272
29179
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
28273
29180
|
for (const l of this.extLayers) {
|
|
28274
|
-
l.
|
|
28275
|
-
|
|
28276
|
-
|
|
28277
|
-
|
|
28278
|
-
|
|
28279
|
-
|
|
28280
|
-
|
|
28281
|
-
|
|
28282
|
-
|
|
28283
|
-
|
|
28284
|
-
nowMs
|
|
28285
|
-
});
|
|
29181
|
+
const args = this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs);
|
|
29182
|
+
l.instance.render(args);
|
|
29183
|
+
if (l.def.id === this.scene.priceStyle && l.instance.modulateBase) {
|
|
29184
|
+
const mod = l.instance.modulateBase(args);
|
|
29185
|
+
if (mod) {
|
|
29186
|
+
if (mod.candleBodyScale != null) this.backend.candleBodyScale = clamp012(mod.candleBodyScale) || 0.01;
|
|
29187
|
+
if (mod.candleBodyAlpha != null) this.backend.candleBodyAlpha = clamp012(mod.candleBodyAlpha) * this.candleBodyAlpha;
|
|
29188
|
+
if (mod.gridAlpha != null) this.backend.gridAlpha = clamp012(mod.gridAlpha);
|
|
29189
|
+
}
|
|
29190
|
+
}
|
|
28286
29191
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
28287
29192
|
}
|
|
28288
29193
|
}
|
|
@@ -28292,7 +29197,7 @@ var NativeRenderer = class {
|
|
|
28292
29197
|
if (easeLive && liveActual) this.bars[li] = { ...liveActual, high: this.liveEaseHigh, low: this.liveEaseLow, close: this.liveEaseClose };
|
|
28293
29198
|
this.scene.drawingSlices = this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map();
|
|
28294
29199
|
this.backend.render(this.scene, this.coords, this.theme);
|
|
28295
|
-
this.chrome.render(this.scene, this.coords, this.theme,
|
|
29200
|
+
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
28296
29201
|
this.userDrawings?.render();
|
|
28297
29202
|
if (easeLive && liveActual) this.bars[li] = liveActual;
|
|
28298
29203
|
this.paintedData = true;
|
|
@@ -28726,15 +29631,18 @@ var NativeRenderer = class {
|
|
|
28726
29631
|
setToolbarGutter(px) {
|
|
28727
29632
|
if (px === this.toolbarGutter) return;
|
|
28728
29633
|
this.toolbarGutter = px;
|
|
28729
|
-
this.
|
|
29634
|
+
this.publishGutters();
|
|
28730
29635
|
this.positionAttribution();
|
|
28731
29636
|
this.syncSize();
|
|
28732
29637
|
}
|
|
28733
|
-
/** Publish the
|
|
28734
|
-
*
|
|
28735
|
-
*
|
|
28736
|
-
|
|
29638
|
+
/** Publish the gutters on the mount container as `--vela-toolbar-gutter` (left,
|
|
29639
|
+
* drawings toolbar) and `--vela-scale-gutter` (right, the full price-scale width
|
|
29640
|
+
* incl. merged own-scale columns), so host overlays sharing that container (a
|
|
29641
|
+
* status line, a watermark, a custom legend) can anchor to the plot's edges
|
|
29642
|
+
* without reaching into the renderer's DOM. */
|
|
29643
|
+
publishGutters() {
|
|
28737
29644
|
this.mountContainer?.style.setProperty("--vela-toolbar-gutter", `${this.toolbarGutter}px`);
|
|
29645
|
+
this.mountContainer?.style.setProperty("--vela-scale-gutter", `${this.rightAxisW}px`);
|
|
28738
29646
|
}
|
|
28739
29647
|
/** The built-in mark, or the host's own when one is set. */
|
|
28740
29648
|
buildAttributionEl() {
|
|
@@ -28804,7 +29712,13 @@ var NativeRenderer = class {
|
|
|
28804
29712
|
this.fitContent();
|
|
28805
29713
|
this.didInitialFit = true;
|
|
28806
29714
|
}
|
|
28807
|
-
this.
|
|
29715
|
+
if (this.animator.active) {
|
|
29716
|
+
this.computeScales();
|
|
29717
|
+
this.paintData();
|
|
29718
|
+
this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
|
|
29719
|
+
} else {
|
|
29720
|
+
this.scheduler.flushNow(4 /* Full */);
|
|
29721
|
+
}
|
|
28808
29722
|
}
|
|
28809
29723
|
applyBackground() {
|
|
28810
29724
|
this.wrapper.style.background = this.theme.background;
|
|
@@ -28873,6 +29787,9 @@ function sanitizeHighlights(value) {
|
|
|
28873
29787
|
}
|
|
28874
29788
|
return out.sort((a, b) => a.from - b.from);
|
|
28875
29789
|
}
|
|
29790
|
+
function clamp012(v) {
|
|
29791
|
+
return Math.max(0, Math.min(1, v));
|
|
29792
|
+
}
|
|
28876
29793
|
function isPriceStyle2(v) {
|
|
28877
29794
|
return typeof v === "string" && priceStyleIds().includes(v);
|
|
28878
29795
|
}
|
|
@@ -29418,8 +30335,8 @@ function fmtChange(open, close) {
|
|
|
29418
30335
|
}
|
|
29419
30336
|
|
|
29420
30337
|
// src/widget/statusline.ts
|
|
29421
|
-
var
|
|
29422
|
-
var
|
|
30338
|
+
var STYLE_ID17 = "vela-widget-statusline";
|
|
30339
|
+
var CSS14 = `
|
|
29423
30340
|
.vela-statusline {
|
|
29424
30341
|
position: absolute;
|
|
29425
30342
|
top: var(--vela-space-2);
|
|
@@ -29554,7 +30471,7 @@ var Statusline = class {
|
|
|
29554
30471
|
/** 'ohlc' for bar-shaped styles; 'value' (the single plotted close) for line styles. */
|
|
29555
30472
|
this.readout = "ohlc";
|
|
29556
30473
|
const doc = host.ownerDocument;
|
|
29557
|
-
injectStyles(
|
|
30474
|
+
injectStyles(STYLE_ID17, CSS14, doc);
|
|
29558
30475
|
host.classList.add("vela-has-statusline");
|
|
29559
30476
|
this.el = doc.createElement("div");
|
|
29560
30477
|
this.el.className = "vela-statusline";
|
|
@@ -29677,11 +30594,21 @@ var Statusline = class {
|
|
|
29677
30594
|
};
|
|
29678
30595
|
|
|
29679
30596
|
// src/widget/watermark.ts
|
|
29680
|
-
var
|
|
29681
|
-
var
|
|
30597
|
+
var MAX_FONT_PX = 72;
|
|
30598
|
+
var MIN_FONT_PX = 12;
|
|
30599
|
+
var FILL = 0.9;
|
|
30600
|
+
var STYLE_ID18 = "vela-widget-watermark";
|
|
30601
|
+
var CSS15 = `
|
|
29682
30602
|
.vela-watermark {
|
|
29683
30603
|
position: absolute;
|
|
29684
|
-
|
|
30604
|
+
/* Insets follow the renderer-published gutters (mount container), so the mark
|
|
30605
|
+
* centers and fits within the PLOT \u2014 never bleeding into the drawings toolbar
|
|
30606
|
+
* on the left or the price scale on the right (visible in small multi-chart
|
|
30607
|
+
* cells, where the scale is a large share of the width). */
|
|
30608
|
+
top: 0;
|
|
30609
|
+
bottom: 0;
|
|
30610
|
+
left: var(--vela-toolbar-gutter, 0px);
|
|
30611
|
+
right: var(--vela-scale-gutter, 0px);
|
|
29685
30612
|
display: flex;
|
|
29686
30613
|
align-items: center;
|
|
29687
30614
|
justify-content: center;
|
|
@@ -29689,28 +30616,48 @@ var CSS14 = `
|
|
|
29689
30616
|
z-index: 1;
|
|
29690
30617
|
color: var(--vela-fg);
|
|
29691
30618
|
opacity: 0.05;
|
|
29692
|
-
font-size:
|
|
30619
|
+
font-size: ${MAX_FONT_PX}px;
|
|
29693
30620
|
font-weight: 700;
|
|
29694
30621
|
letter-spacing: 0.04em;
|
|
29695
30622
|
user-select: none;
|
|
29696
30623
|
white-space: nowrap;
|
|
29697
30624
|
}
|
|
29698
30625
|
`;
|
|
30626
|
+
function watermarkFontPx(availPx, textPxAtMax) {
|
|
30627
|
+
if (textPxAtMax <= 0) return MAX_FONT_PX;
|
|
30628
|
+
return Math.max(MIN_FONT_PX, Math.min(MAX_FONT_PX, Math.floor(MAX_FONT_PX * availPx * FILL / textPxAtMax)));
|
|
30629
|
+
}
|
|
29699
30630
|
var Watermark = class {
|
|
29700
30631
|
constructor(host, symbol, timeframe) {
|
|
29701
|
-
|
|
30632
|
+
this.resizeObserver = null;
|
|
30633
|
+
injectStyles(STYLE_ID18, CSS15, host.ownerDocument);
|
|
29702
30634
|
this.el = host.ownerDocument.createElement("div");
|
|
29703
30635
|
this.el.className = "vela-watermark";
|
|
30636
|
+
this.text = host.ownerDocument.createElement("span");
|
|
30637
|
+
this.el.appendChild(this.text);
|
|
29704
30638
|
host.appendChild(this.el);
|
|
30639
|
+
if (typeof ResizeObserver !== "undefined") {
|
|
30640
|
+
this.resizeObserver = new ResizeObserver(() => this.fit());
|
|
30641
|
+
this.resizeObserver.observe(this.el);
|
|
30642
|
+
}
|
|
29705
30643
|
this.update(symbol, timeframe);
|
|
29706
30644
|
}
|
|
29707
30645
|
setVisible(visible) {
|
|
29708
30646
|
this.el.style.display = visible ? "" : "none";
|
|
29709
30647
|
}
|
|
29710
30648
|
update(symbol, timeframe) {
|
|
29711
|
-
this.
|
|
30649
|
+
this.text.textContent = symbol ? `${parseSymbol(symbol).ticker} \xB7 ${timeframeLabel(timeframe)}` : "";
|
|
30650
|
+
this.fit();
|
|
30651
|
+
}
|
|
30652
|
+
/** Measure the text at the cap and shrink it to the chart's own width. */
|
|
30653
|
+
fit() {
|
|
30654
|
+
if (this.el.clientWidth <= 0 || !this.text.textContent) return;
|
|
30655
|
+
this.el.style.fontSize = `${MAX_FONT_PX}px`;
|
|
30656
|
+
const px = watermarkFontPx(this.el.clientWidth, this.text.getBoundingClientRect().width);
|
|
30657
|
+
if (px !== MAX_FONT_PX) this.el.style.fontSize = `${px}px`;
|
|
29712
30658
|
}
|
|
29713
30659
|
destroy() {
|
|
30660
|
+
this.resizeObserver?.disconnect();
|
|
29714
30661
|
this.el.remove();
|
|
29715
30662
|
}
|
|
29716
30663
|
};
|
|
@@ -30579,6 +31526,49 @@ function registerBuiltinLayouts() {
|
|
|
30579
31526
|
registerLayout({ id: "4", label: "4 grid", cols: [1, 1], rows: [1, 1], cells: slots(4) });
|
|
30580
31527
|
registerLayout({ id: "8", label: "8 grid", cols: [1, 1, 1, 1], rows: [1, 1], cells: slots(8) });
|
|
30581
31528
|
}
|
|
31529
|
+
var GRID_PICKER_MAX = 4;
|
|
31530
|
+
var GRID_BUILTIN_IDS = {
|
|
31531
|
+
"1x1": "1",
|
|
31532
|
+
"1x2": "2h",
|
|
31533
|
+
"2x1": "2v",
|
|
31534
|
+
"2x2": "4",
|
|
31535
|
+
"2x4": "8"
|
|
31536
|
+
};
|
|
31537
|
+
var clampTrack = (n) => Math.max(1, Math.min(GRID_PICKER_MAX, Math.round(n)));
|
|
31538
|
+
function layoutForGrid(rows, cols) {
|
|
31539
|
+
const r = clampTrack(rows);
|
|
31540
|
+
const c = clampTrack(cols);
|
|
31541
|
+
const builtin = GRID_BUILTIN_IDS[`${r}x${c}`];
|
|
31542
|
+
const registered = builtin ? registry5.get(builtin) : void 0;
|
|
31543
|
+
if (registered) return registered;
|
|
31544
|
+
return {
|
|
31545
|
+
id: `g${r}x${c}`,
|
|
31546
|
+
// Width-first label — matches the picker's caption ("3 × 2" = 3 wide, 2 tall).
|
|
31547
|
+
label: `${c} \xD7 ${r} grid`,
|
|
31548
|
+
cols: Array.from({ length: c }, () => 1),
|
|
31549
|
+
rows: Array.from({ length: r }, () => 1),
|
|
31550
|
+
cells: slots(r * c)
|
|
31551
|
+
};
|
|
31552
|
+
}
|
|
31553
|
+
var GRID_ID_RE = /^g([1-4])x([1-4])$/;
|
|
31554
|
+
function ensureLayout(id) {
|
|
31555
|
+
const registered = registry5.get(id);
|
|
31556
|
+
if (registered) return registered;
|
|
31557
|
+
const g = GRID_ID_RE.exec(id);
|
|
31558
|
+
if (g) return layoutForGrid(Number(g[1]), Number(g[2]));
|
|
31559
|
+
return void 0;
|
|
31560
|
+
}
|
|
31561
|
+
function layoutShape(def) {
|
|
31562
|
+
if (!def.areas && def.rows.length <= GRID_PICKER_MAX && def.cols.length <= GRID_PICKER_MAX && def.cells.length === def.rows.length * def.cols.length) {
|
|
31563
|
+
return { rows: def.rows.length, cols: def.cols.length };
|
|
31564
|
+
}
|
|
31565
|
+
return null;
|
|
31566
|
+
}
|
|
31567
|
+
function occupancyGrid(def) {
|
|
31568
|
+
if (def.areas) return def.areas.map((row) => row.trim().split(/\s+/));
|
|
31569
|
+
const cols = def.cols.length;
|
|
31570
|
+
return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ?? `\xB7${r}x${c}`));
|
|
31571
|
+
}
|
|
30582
31572
|
function gridStyles(def, trackSizes) {
|
|
30583
31573
|
const cols = trackSizes?.cols?.length === def.cols.length ? trackSizes.cols : def.cols;
|
|
30584
31574
|
const rows = trackSizes?.rows?.length === def.rows.length ? trackSizes.rows : def.rows;
|
|
@@ -30598,6 +31588,15 @@ function activeAfterLayout(current, cellIds) {
|
|
|
30598
31588
|
if (current != null && cellIds.includes(current)) return current;
|
|
30599
31589
|
return cellIds[0] ?? null;
|
|
30600
31590
|
}
|
|
31591
|
+
function orderAfterLayout(order, slots2, active) {
|
|
31592
|
+
const next = [...order];
|
|
31593
|
+
if (active == null || slots2 <= 0) return next;
|
|
31594
|
+
const idx = next.indexOf(active);
|
|
31595
|
+
if (idx < 0 || idx < slots2) return next;
|
|
31596
|
+
next.splice(idx, 1);
|
|
31597
|
+
next.splice(slots2 - 1, 0, active);
|
|
31598
|
+
return next;
|
|
31599
|
+
}
|
|
30601
31600
|
|
|
30602
31601
|
// src/workspace/splitters.ts
|
|
30603
31602
|
var MIN_TRACK_FRAC = 0.1;
|
|
@@ -30630,6 +31629,27 @@ function trackOffsets(weights, sizePx, gapPx) {
|
|
|
30630
31629
|
}
|
|
30631
31630
|
return out;
|
|
30632
31631
|
}
|
|
31632
|
+
function seamSegments(grid, axis, index) {
|
|
31633
|
+
const count = axis === "cols" ? grid.length : grid[0]?.length ?? 0;
|
|
31634
|
+
const isSeam = (k) => axis === "cols" ? grid[k]?.[index] !== grid[k]?.[index + 1] : grid[index]?.[k] !== grid[index + 1]?.[k];
|
|
31635
|
+
const out = [];
|
|
31636
|
+
for (let k = 0; k < count; k += 1) {
|
|
31637
|
+
if (!isSeam(k)) continue;
|
|
31638
|
+
const last = out[out.length - 1];
|
|
31639
|
+
if (last && last[1] === k - 1) last[1] = k;
|
|
31640
|
+
else out.push([k, k]);
|
|
31641
|
+
}
|
|
31642
|
+
return out;
|
|
31643
|
+
}
|
|
31644
|
+
function segmentSpanPx(weights, sizePx, gapPx, from, to) {
|
|
31645
|
+
const total = weights.reduce((s, w) => s + w, 0);
|
|
31646
|
+
if (total <= 0) return { start: 0, end: 0 };
|
|
31647
|
+
const content = sizePx - gapPx * (weights.length - 1);
|
|
31648
|
+
const startOf = (k) => weights.slice(0, k).reduce((s, w) => s + w / total * content, 0) + gapPx * k;
|
|
31649
|
+
const start = from === 0 ? 0 : startOf(from) - gapPx / 2;
|
|
31650
|
+
const end = to === weights.length - 1 ? sizePx : startOf(to + 1) - gapPx / 2;
|
|
31651
|
+
return { start, end };
|
|
31652
|
+
}
|
|
30633
31653
|
var HIT_PX = 8;
|
|
30634
31654
|
var SplitterLayer = class {
|
|
30635
31655
|
constructor(container, deps) {
|
|
@@ -30638,23 +31658,36 @@ var SplitterLayer = class {
|
|
|
30638
31658
|
this.strips = [];
|
|
30639
31659
|
this.drag = null;
|
|
30640
31660
|
}
|
|
30641
|
-
/** Rebuild + reposition the strips for the current tracks/size. */
|
|
31661
|
+
/** Rebuild + reposition the strips for the current tracks/size/occupancy. */
|
|
30642
31662
|
layout() {
|
|
30643
31663
|
const { cols, rows } = this.deps.tracks();
|
|
31664
|
+
const grid = this.deps.grid();
|
|
30644
31665
|
const rect = this.container.getBoundingClientRect();
|
|
30645
31666
|
const gap = this.deps.gapPx();
|
|
30646
|
-
const
|
|
30647
|
-
while (this.strips.length > wanted) this.strips.pop().remove();
|
|
30648
|
-
while (this.strips.length < wanted) this.strips.push(this.makeStrip());
|
|
30649
|
-
let k = 0;
|
|
31667
|
+
const placements = [];
|
|
30650
31668
|
for (const [i, x] of trackOffsets(cols, rect.width, gap).entries()) {
|
|
30651
|
-
|
|
30652
|
-
|
|
31669
|
+
for (const [from, to] of seamSegments(grid, "cols", i)) {
|
|
31670
|
+
const { start, end } = segmentSpanPx(rows, rect.height, gap, from, to);
|
|
31671
|
+
placements.push({
|
|
31672
|
+
axis: "cols",
|
|
31673
|
+
index: i,
|
|
31674
|
+
css: `left:${Math.round(x - HIT_PX / 2)}px;top:${Math.round(start)}px;width:${HIT_PX}px;height:${Math.round(end - start)}px;cursor:col-resize;`
|
|
31675
|
+
});
|
|
31676
|
+
}
|
|
30653
31677
|
}
|
|
30654
31678
|
for (const [i, y] of trackOffsets(rows, rect.height, gap).entries()) {
|
|
30655
|
-
|
|
30656
|
-
|
|
31679
|
+
for (const [from, to] of seamSegments(grid, "rows", i)) {
|
|
31680
|
+
const { start, end } = segmentSpanPx(cols, rect.width, gap, from, to);
|
|
31681
|
+
placements.push({
|
|
31682
|
+
axis: "rows",
|
|
31683
|
+
index: i,
|
|
31684
|
+
css: `left:${Math.round(start)}px;top:${Math.round(y - HIT_PX / 2)}px;width:${Math.round(end - start)}px;height:${HIT_PX}px;cursor:row-resize;`
|
|
31685
|
+
});
|
|
31686
|
+
}
|
|
30657
31687
|
}
|
|
31688
|
+
while (this.strips.length > placements.length) this.strips.pop().remove();
|
|
31689
|
+
while (this.strips.length < placements.length) this.strips.push(this.makeStrip());
|
|
31690
|
+
for (const [k, p] of placements.entries()) this.place(this.strips[k], p.axis, p.index, p.css);
|
|
30658
31691
|
}
|
|
30659
31692
|
destroy() {
|
|
30660
31693
|
for (const s of this.strips.splice(0)) s.remove();
|
|
@@ -30720,16 +31753,18 @@ var GAP_PX = 2;
|
|
|
30720
31753
|
var POOL_CAP = 16;
|
|
30721
31754
|
var TIME_AXIS_H3 = 22;
|
|
30722
31755
|
var ALERT_CAP = 50;
|
|
30723
|
-
var
|
|
30724
|
-
var
|
|
31756
|
+
var STYLE_ID19 = "vela-workspace";
|
|
31757
|
+
var CSS16 = `
|
|
30725
31758
|
.vela-workspace { position: relative; width: 100%; height: 100%; display: flex; flex-direction: column; background: var(--vela-bg); }
|
|
30726
31759
|
.vela-ws-main { position: relative; display: flex; flex-direction: row; flex: 1 1 auto; min-height: 0; }
|
|
30727
31760
|
.vela-ws-toolbar { position: relative; flex: none; }
|
|
30728
31761
|
.vela-ws-grid { position: relative; flex: 1 1 auto; min-width: 0; display: grid; gap: ${GAP_PX}px; background: var(--vela-border-soft); }
|
|
30729
31762
|
.vela-cell { background: var(--vela-bg); position: relative; }
|
|
30730
31763
|
/* Active-cell highlight: an overlay ring ABOVE the chart's own canvas stack (a plain
|
|
30731
|
-
outline on the cell is painted under them) \u2014 inert to the pointer.
|
|
30732
|
-
|
|
31764
|
+
outline on the cell is painted under them) \u2014 inert to the pointer. Scoped to
|
|
31765
|
+
multi-cell grids ([data-multi]): a single-cell layout always has an active cell,
|
|
31766
|
+
and ringing the only chart would just be noise. */
|
|
31767
|
+
.vela-ws-grid[data-multi='1'] .vela-cell[data-active='1']::after {
|
|
30733
31768
|
content: '';
|
|
30734
31769
|
position: absolute;
|
|
30735
31770
|
inset: 0;
|
|
@@ -30737,7 +31772,12 @@ var CSS15 = `
|
|
|
30737
31772
|
pointer-events: none;
|
|
30738
31773
|
z-index: 10;
|
|
30739
31774
|
}
|
|
30740
|
-
|
|
31775
|
+
/* Splitter hover mirrors the in-chart pane separator hover (CrosshairRenderer):
|
|
31776
|
+
a soft band over the whole grab target + a solid 2px line on the seam center. */
|
|
31777
|
+
.vela-ws-splitter:hover { background: var(--vela-separator-hover-band); }
|
|
31778
|
+
.vela-ws-splitter:hover::after { content: ''; position: absolute; background: var(--vela-separator-hover-line); }
|
|
31779
|
+
.vela-ws-splitter[data-axis='cols']:hover::after { left: calc(50% - 1px); top: 0; width: 2px; height: 100%; }
|
|
31780
|
+
.vela-ws-splitter[data-axis='rows']:hover::after { top: calc(50% - 1px); left: 0; height: 2px; width: 100%; }
|
|
30741
31781
|
`;
|
|
30742
31782
|
registerIcon("layout", svg16('<rect x="1.5" y="1.5" width="13" height="13" rx="1.5"/><path d="M8 1.5v13M1.5 8h13"/>'));
|
|
30743
31783
|
function declaredOrder(cells) {
|
|
@@ -30788,6 +31828,15 @@ var VelaWorkspace = class {
|
|
|
30788
31828
|
/** Re-entrance guard around one propagation tick: followers' synchronous echoes
|
|
30789
31829
|
* (their setVisibleRange re-emits viewport:changed) must not re-propagate. */
|
|
30790
31830
|
this.syncBusy = false;
|
|
31831
|
+
/** Same guard for the drawings link: the propagated mutations' own `drawing:*`
|
|
31832
|
+
* events fire synchronously inside the propagation loop and must not fan out again. */
|
|
31833
|
+
this.drawingSyncBusy = false;
|
|
31834
|
+
/** LINKED drawings (the drawings sync): one map per synced set (cellId → that
|
|
31835
|
+
* cell's drawing id), reachable from every member under its `cellId\0drawingId`
|
|
31836
|
+
* key — any member finds its peers to push edits/removals onto. Survives a
|
|
31837
|
+
* toggle-off (propagation freezes while the setting is off; re-enabling resumes
|
|
31838
|
+
* edit/delete for these pairs). Cleared on reload / `applyState`. */
|
|
31839
|
+
this.drawingLinks = /* @__PURE__ */ new Map();
|
|
30791
31840
|
this.manifest = [];
|
|
30792
31841
|
/** The shared manifest can no longer change instance sets — resolved, or no
|
|
30793
31842
|
* `indicators` option so nothing ever will. Gates the cells' ledger fallback. */
|
|
@@ -30829,16 +31878,16 @@ var VelaWorkspace = class {
|
|
|
30829
31878
|
this.timezone = boot?.timezone ?? opts.timezone ?? "Etc/UTC";
|
|
30830
31879
|
if (boot?.favorites) this.favs = [...boot.favorites];
|
|
30831
31880
|
const sync = boot?.sync ?? opts.sync;
|
|
30832
|
-
for (const kind of ["viewport", "symbol", "timeframe", "crosshair"]) {
|
|
31881
|
+
for (const kind of ["viewport", "symbol", "timeframe", "crosshair", "drawings"]) {
|
|
30833
31882
|
this.applySyncSetting(kind, sync?.[kind]);
|
|
30834
31883
|
}
|
|
30835
|
-
this.def = this.resolveLayout(boot?.layout &&
|
|
31884
|
+
this.def = this.resolveLayout(boot?.layout && ensureLayout(boot.layout) ? boot.layout : opts.layout ?? "4");
|
|
30836
31885
|
if (boot?.trackSizes) for (const [id, ts] of Object.entries(boot.trackSizes)) this.trackSizes.set(id, ts);
|
|
30837
31886
|
if (boot?.charts) for (const { id, ...cs } of boot.charts) this.pool.set(id, cs);
|
|
30838
31887
|
this.order = boot?.charts ? boot.charts.map((c) => c.id) : declaredOrder(opts.cells);
|
|
30839
31888
|
const bootActive = boot?.activeCellId ?? null;
|
|
30840
31889
|
const doc = hostEl.ownerDocument;
|
|
30841
|
-
injectStyles(
|
|
31890
|
+
injectStyles(STYLE_ID19, CSS16, doc);
|
|
30842
31891
|
this.root = doc.createElement("div");
|
|
30843
31892
|
this.root.className = "vela-workspace";
|
|
30844
31893
|
ensureUIHost(this.root, resolveTheme(opts.theme));
|
|
@@ -30883,14 +31932,23 @@ var VelaWorkspace = class {
|
|
|
30883
31932
|
onPriceStyle: (style) => this.active.setPriceStyle(style),
|
|
30884
31933
|
layout: {
|
|
30885
31934
|
current: this.def.id,
|
|
30886
|
-
|
|
30887
|
-
|
|
30888
|
-
|
|
30889
|
-
|
|
30890
|
-
|
|
30891
|
-
|
|
30892
|
-
|
|
30893
|
-
|
|
31935
|
+
// The picker composes dynamic layouts on its grid canvas; registered
|
|
31936
|
+
// presets the canvas cannot express (bespoke plugin areas) list as rows.
|
|
31937
|
+
shape: () => layoutShape(this.def),
|
|
31938
|
+
presets: () => layouts().filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label })),
|
|
31939
|
+
onSelectGrid: (rows, cols) => this.setLayout(layoutForGrid(rows, cols)),
|
|
31940
|
+
onSelectPreset: (id) => this.setLayout(id),
|
|
31941
|
+
// The SYNC switches reflect the simple all-cells form; flipping one
|
|
31942
|
+
// OVERRIDES a host-set group record with plain on/off (groups stay an
|
|
31943
|
+
// API-only shape).
|
|
31944
|
+
syncs: () => [
|
|
31945
|
+
{ id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
|
|
31946
|
+
{ id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
|
|
31947
|
+
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true }
|
|
31948
|
+
],
|
|
31949
|
+
onToggleSync: (id) => {
|
|
31950
|
+
const kind = id;
|
|
31951
|
+
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
30894
31952
|
}
|
|
30895
31953
|
},
|
|
30896
31954
|
getContext: () => this.context()
|
|
@@ -30953,10 +32011,19 @@ var VelaWorkspace = class {
|
|
|
30953
32011
|
this.active.chart.drawings.setStayMode(on);
|
|
30954
32012
|
this.refocusActive();
|
|
30955
32013
|
},
|
|
30956
|
-
{
|
|
32014
|
+
{
|
|
32015
|
+
dock: "static",
|
|
32016
|
+
// Drawings sync is a WORKSPACE link (same model as the layout
|
|
32017
|
+
// dropdown's switches) — the bar only hosts its toggle.
|
|
32018
|
+
onDrawingsSync: (on) => {
|
|
32019
|
+
this.sync.set("drawings", on);
|
|
32020
|
+
this.refocusActive();
|
|
32021
|
+
}
|
|
32022
|
+
}
|
|
30957
32023
|
) : null;
|
|
30958
32024
|
this.drawToolbar?.setDefinition(defaultToolbar());
|
|
30959
32025
|
this.drawToolbar?.setVisible(true);
|
|
32026
|
+
this.drawToolbar?.setDrawingsSyncMode(!!this.syncOpts.drawings);
|
|
30960
32027
|
this.bottombar = opts.bottombar !== false ? new Bottombar(this.root, {
|
|
30961
32028
|
timezone: this.timezone,
|
|
30962
32029
|
onRange: (preset) => {
|
|
@@ -30969,6 +32036,7 @@ var VelaWorkspace = class {
|
|
|
30969
32036
|
hostEl.appendChild(this.root);
|
|
30970
32037
|
this.splitters = new SplitterLayer(this.gridEl, {
|
|
30971
32038
|
tracks: () => this.currentTracks(),
|
|
32039
|
+
grid: () => occupancyGrid(this.def),
|
|
30972
32040
|
apply: (axis, weights) => this.applyTracks(axis, weights),
|
|
30973
32041
|
reset: (axis) => this.applyTracks(axis, evenTracks(this.currentTracks()[axis].length)),
|
|
30974
32042
|
gapPx: () => GAP_PX
|
|
@@ -31116,7 +32184,7 @@ var VelaWorkspace = class {
|
|
|
31116
32184
|
}
|
|
31117
32185
|
if (st.favorites) this.favs = [...st.favorites];
|
|
31118
32186
|
this.dock.applyState(st.panels);
|
|
31119
|
-
for (const kind of ["viewport", "symbol", "timeframe", "crosshair"]) this.applySyncSetting(kind, st.sync?.[kind]);
|
|
32187
|
+
for (const kind of ["viewport", "symbol", "timeframe", "crosshair", "drawings"]) this.applySyncSetting(kind, st.sync?.[kind]);
|
|
31120
32188
|
this.trackSizes.clear();
|
|
31121
32189
|
if (st.trackSizes) for (const [id, ts] of Object.entries(st.trackSizes)) this.trackSizes.set(id, ts);
|
|
31122
32190
|
for (const [id, cell] of [...this.cellsById]) {
|
|
@@ -31125,9 +32193,10 @@ var VelaWorkspace = class {
|
|
|
31125
32193
|
this.events.emit("cell:destroyed", { id });
|
|
31126
32194
|
}
|
|
31127
32195
|
this.pool.clear();
|
|
32196
|
+
this.drawingLinks.clear();
|
|
31128
32197
|
for (const { id, ...cs } of st.charts) this.pool.set(id, cs);
|
|
31129
32198
|
this.order = st.charts.map((c) => c.id);
|
|
31130
|
-
const def =
|
|
32199
|
+
const def = ensureLayout(st.layout);
|
|
31131
32200
|
if (def) this.def = def;
|
|
31132
32201
|
this.cellBackend = this.backendFor(this.def);
|
|
31133
32202
|
this.applyGrid();
|
|
@@ -31178,6 +32247,7 @@ var VelaWorkspace = class {
|
|
|
31178
32247
|
const next = this.resolveLayout(layout);
|
|
31179
32248
|
const nextBackend = this.backendFor(next);
|
|
31180
32249
|
const rebuildAll = nextBackend !== this.cellBackend;
|
|
32250
|
+
this.order = orderAfterLayout(this.order, next.cells.length, this.activeId);
|
|
31181
32251
|
const keep = new Set(this.order.slice(0, next.cells.length));
|
|
31182
32252
|
for (const [id, cell] of [...this.cellsById]) {
|
|
31183
32253
|
if (!keep.has(id) || rebuildAll) {
|
|
@@ -31301,7 +32371,7 @@ var VelaWorkspace = class {
|
|
|
31301
32371
|
// ── internals ───────────────────────────────────────────────
|
|
31302
32372
|
resolveLayout(layout) {
|
|
31303
32373
|
if (typeof layout !== "string") return layout;
|
|
31304
|
-
const def =
|
|
32374
|
+
const def = ensureLayout(layout);
|
|
31305
32375
|
if (!def) throw new Error(`[vela] unknown workspace layout "${layout}" \u2014 register it with registerLayout().`);
|
|
31306
32376
|
return def;
|
|
31307
32377
|
}
|
|
@@ -31329,6 +32399,8 @@ var VelaWorkspace = class {
|
|
|
31329
32399
|
* `order[i]` — the identity/position decoupling in one line. */
|
|
31330
32400
|
applyGrid() {
|
|
31331
32401
|
const { container, perCell } = gridStyles(this.def, this.trackSizes.get(this.def.id));
|
|
32402
|
+
if (this.def.cells.length > 1) this.gridEl.dataset.multi = "1";
|
|
32403
|
+
else delete this.gridEl.dataset.multi;
|
|
31332
32404
|
this.gridEl.style.gridTemplateColumns = container.gridTemplateColumns ?? "";
|
|
31333
32405
|
this.gridEl.style.gridTemplateRows = container.gridTemplateRows ?? "";
|
|
31334
32406
|
this.gridEl.style.gridTemplateAreas = container.gridTemplateAreas ?? "";
|
|
@@ -31376,6 +32448,7 @@ var VelaWorkspace = class {
|
|
|
31376
32448
|
});
|
|
31377
32449
|
cell.host.style.gridArea = perCell[slot.id]?.gridArea ?? "";
|
|
31378
32450
|
this.cellsById.set(id, cell);
|
|
32451
|
+
if (id === this.activeId) cell.host.dataset.active = "1";
|
|
31379
32452
|
this.wireCell(cell);
|
|
31380
32453
|
if (this.favs.length > 0) cell.chart.drawings.setFavorites(this.favs);
|
|
31381
32454
|
cell.setManifest(this.manifest, pooled?.indicators == null);
|
|
@@ -31407,9 +32480,19 @@ var VelaWorkspace = class {
|
|
|
31407
32480
|
this.markStateDirty();
|
|
31408
32481
|
});
|
|
31409
32482
|
chart.renderer.onCrosshairMove((e) => this.propagateCrosshair(cell.id, e.time));
|
|
31410
|
-
chart.on("drawing:created", () =>
|
|
31411
|
-
|
|
31412
|
-
|
|
32483
|
+
chart.on("drawing:created", ({ id }) => {
|
|
32484
|
+
this.propagateDrawing(cell.id, id);
|
|
32485
|
+
this.markStateDirty();
|
|
32486
|
+
});
|
|
32487
|
+
chart.on("drawing:draft", ({ doc }) => this.propagateDraft(cell.id, doc));
|
|
32488
|
+
chart.on("drawing:edited", ({ id }) => {
|
|
32489
|
+
this.propagateDrawingEdit(cell.id, id);
|
|
32490
|
+
this.markStateDirty();
|
|
32491
|
+
});
|
|
32492
|
+
chart.on("drawing:removed", ({ id }) => {
|
|
32493
|
+
this.propagateDrawingRemoval(cell.id, id);
|
|
32494
|
+
this.markStateDirty();
|
|
32495
|
+
});
|
|
31413
32496
|
chart.on("drawing:tool", ({ type }) => {
|
|
31414
32497
|
if (cell.id !== this.activeId) return;
|
|
31415
32498
|
this.globalTool = type;
|
|
@@ -31437,6 +32520,12 @@ var VelaWorkspace = class {
|
|
|
31437
32520
|
applySyncSetting(kind, setting, align = false) {
|
|
31438
32521
|
if (setting == null || setting === false) delete this.syncOpts[kind];
|
|
31439
32522
|
else this.syncOpts[kind] = setting;
|
|
32523
|
+
if (kind === "drawings") {
|
|
32524
|
+
for (const cell of this.cellsById.values()) cell.chart.drawings.setExternalGhost(null);
|
|
32525
|
+
this.drawToolbar?.setDrawingsSyncMode(!!setting);
|
|
32526
|
+
this.markStateDirty();
|
|
32527
|
+
return;
|
|
32528
|
+
}
|
|
31440
32529
|
if (kind === "crosshair") {
|
|
31441
32530
|
for (const cell of this.cellsById.values()) cell.chart.renderer.setExternalCrosshair(null);
|
|
31442
32531
|
if (setting && ![...this.cellsById.values()].some((c) => c.chart.renderer.supportsExternalCrosshair)) {
|
|
@@ -31446,6 +32535,7 @@ var VelaWorkspace = class {
|
|
|
31446
32535
|
this.markStateDirty();
|
|
31447
32536
|
return;
|
|
31448
32537
|
}
|
|
32538
|
+
if (this.topbar && this.def) this.topbar.setLayout(this.def.id);
|
|
31449
32539
|
this.markStateDirty();
|
|
31450
32540
|
if (align && setting && this.activeId) {
|
|
31451
32541
|
if (kind === "viewport") {
|
|
@@ -31497,6 +32587,101 @@ var VelaWorkspace = class {
|
|
|
31497
32587
|
this.syncBusy = false;
|
|
31498
32588
|
}
|
|
31499
32589
|
}
|
|
32590
|
+
/**
|
|
32591
|
+
* Copy a freshly created drawing onto the origin's same-group followers (fresh ids
|
|
32592
|
+
* through `drawings.add` — anchors are time+price, so the copy lands at the same
|
|
32593
|
+
* spot whatever the follower shows) and register the whole set as LINKED: edits
|
|
32594
|
+
* and removals of any member follow while the link is on ({@link drawingLinks}).
|
|
32595
|
+
* The busy guard stops the copies' own `drawing:created` events from fanning out.
|
|
32596
|
+
*/
|
|
32597
|
+
propagateDrawing(originId, drawingId) {
|
|
32598
|
+
if (this.drawingSyncBusy || this.destroyed) return;
|
|
32599
|
+
const setting = this.syncOpts.drawings;
|
|
32600
|
+
if (!setting) return;
|
|
32601
|
+
const doc = this.cellsById.get(originId)?.chart.drawings.all().find((d) => d.id === drawingId);
|
|
32602
|
+
if (!doc) return;
|
|
32603
|
+
this.drawingSyncBusy = true;
|
|
32604
|
+
try {
|
|
32605
|
+
const group = /* @__PURE__ */ new Map([[originId, drawingId]]);
|
|
32606
|
+
for (const id of syncTargets(originId, setting, [...this.cellsById.keys()])) {
|
|
32607
|
+
const copy = this.cellsById.get(id)?.chart.drawings.add(doc.type, {
|
|
32608
|
+
paneId: doc.paneId,
|
|
32609
|
+
anchors: doc.anchors,
|
|
32610
|
+
style: doc.style,
|
|
32611
|
+
text: doc.text,
|
|
32612
|
+
props: doc.props
|
|
32613
|
+
});
|
|
32614
|
+
if (copy) group.set(id, copy.id);
|
|
32615
|
+
}
|
|
32616
|
+
if (group.size > 1) {
|
|
32617
|
+
for (const [cellId, dId] of group) this.drawingLinks.set(`${cellId}\0${dId}`, group);
|
|
32618
|
+
}
|
|
32619
|
+
} finally {
|
|
32620
|
+
this.drawingSyncBusy = false;
|
|
32621
|
+
}
|
|
32622
|
+
}
|
|
32623
|
+
/** Mirror an in-progress placement (its current ghost, `null` = placement ended)
|
|
32624
|
+
* onto the origin's same-group followers — the live half of the drawings link;
|
|
32625
|
+
* the created copy replaces the ghosts when the placement completes. One-way by
|
|
32626
|
+
* contract (an external ghost never re-emits drafts), so no busy guard needed. */
|
|
32627
|
+
propagateDraft(originId, doc) {
|
|
32628
|
+
if (this.destroyed) return;
|
|
32629
|
+
const setting = this.syncOpts.drawings;
|
|
32630
|
+
if (!setting) return;
|
|
32631
|
+
for (const id of syncTargets(originId, setting, [...this.cellsById.keys()])) {
|
|
32632
|
+
this.cellsById.get(id)?.chart.drawings.setExternalGhost(doc);
|
|
32633
|
+
}
|
|
32634
|
+
}
|
|
32635
|
+
/** Push a linked drawing's edited CONTENT (anchors, style, text, per-type props)
|
|
32636
|
+
* onto its same-group peers — any member propagates, not just the original. */
|
|
32637
|
+
propagateDrawingEdit(originId, drawingId) {
|
|
32638
|
+
if (this.drawingSyncBusy || this.destroyed) return;
|
|
32639
|
+
const setting = this.syncOpts.drawings;
|
|
32640
|
+
if (!setting) return;
|
|
32641
|
+
const group = this.drawingLinks.get(`${originId}\0${drawingId}`);
|
|
32642
|
+
if (!group) return;
|
|
32643
|
+
const doc = this.cellsById.get(originId)?.chart.drawings.all().find((d) => d.id === drawingId);
|
|
32644
|
+
if (!doc) return;
|
|
32645
|
+
this.drawingSyncBusy = true;
|
|
32646
|
+
try {
|
|
32647
|
+
for (const id of syncTargets(originId, setting, [...this.cellsById.keys()])) {
|
|
32648
|
+
const peerId = group.get(id);
|
|
32649
|
+
if (peerId == null) continue;
|
|
32650
|
+
this.cellsById.get(id)?.chart.drawings.update(peerId, {
|
|
32651
|
+
anchors: doc.anchors,
|
|
32652
|
+
style: doc.style,
|
|
32653
|
+
text: doc.text,
|
|
32654
|
+
props: doc.props
|
|
32655
|
+
});
|
|
32656
|
+
}
|
|
32657
|
+
} finally {
|
|
32658
|
+
this.drawingSyncBusy = false;
|
|
32659
|
+
}
|
|
32660
|
+
}
|
|
32661
|
+
/** Remove a linked drawing's peers with it. The removed member always leaves its
|
|
32662
|
+
* link group (whatever the setting); peers are only deleted while the link is on.
|
|
32663
|
+
* A propagated peer removal re-enters here under the busy guard and just cleans
|
|
32664
|
+
* its own link key. */
|
|
32665
|
+
propagateDrawingRemoval(originId, drawingId) {
|
|
32666
|
+
if (this.destroyed) return;
|
|
32667
|
+
const key = `${originId}\0${drawingId}`;
|
|
32668
|
+
const group = this.drawingLinks.get(key);
|
|
32669
|
+
if (!group) return;
|
|
32670
|
+
this.drawingLinks.delete(key);
|
|
32671
|
+
group.delete(originId);
|
|
32672
|
+
if (this.drawingSyncBusy) return;
|
|
32673
|
+
const setting = this.syncOpts.drawings;
|
|
32674
|
+
if (!setting) return;
|
|
32675
|
+
this.drawingSyncBusy = true;
|
|
32676
|
+
try {
|
|
32677
|
+
for (const id of syncTargets(originId, setting, [...this.cellsById.keys()])) {
|
|
32678
|
+
const peerId = group.get(id);
|
|
32679
|
+
if (peerId != null) this.cellsById.get(id)?.chart.drawings.remove(peerId);
|
|
32680
|
+
}
|
|
32681
|
+
} finally {
|
|
32682
|
+
this.drawingSyncBusy = false;
|
|
32683
|
+
}
|
|
32684
|
+
}
|
|
31500
32685
|
/**
|
|
31501
32686
|
* Push an origin cell's symbol/timeframe onto its same-group followers (fired from
|
|
31502
32687
|
* `market:changed`). Convergence comes from IDEMPOTENCE, not the guard: a follower's
|
|
@@ -31673,13 +32858,17 @@ var VelaWorkspace = class {
|
|
|
31673
32858
|
};
|
|
31674
32859
|
|
|
31675
32860
|
exports.ChartCell = ChartCell;
|
|
32861
|
+
exports.GRID_PICKER_MAX = GRID_PICKER_MAX;
|
|
31676
32862
|
exports.VelaWorkspace = VelaWorkspace;
|
|
31677
32863
|
exports.activeAfterLayout = activeAfterLayout;
|
|
31678
32864
|
exports.decodeState = decodeState;
|
|
31679
32865
|
exports.encodeState = encodeState;
|
|
32866
|
+
exports.ensureLayout = ensureLayout;
|
|
31680
32867
|
exports.evenTracks = evenTracks;
|
|
31681
32868
|
exports.gridStyles = gridStyles;
|
|
31682
32869
|
exports.layoutDefinition = layoutDefinition;
|
|
32870
|
+
exports.layoutForGrid = layoutForGrid;
|
|
32871
|
+
exports.layoutShape = layoutShape;
|
|
31683
32872
|
exports.layouts = layouts;
|
|
31684
32873
|
exports.memoryStorageAdapter = memoryStorageAdapter;
|
|
31685
32874
|
exports.rangesWithin = rangesWithin;
|