@luxalgo/vela 0.8.1 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{DataProvider-DwFIoV4r.d.ts → DataProvider-oH78tYXL.d.ts} +1 -1
- package/dist/{DataProvider-BrlxntPN.d.cts → DataProvider-wbtJ3-Sa.d.cts} +1 -1
- package/dist/{chunk-DBDXKZUF.js → chunk-2IPXYM4B.js} +306 -177
- package/dist/{chunk-NELQJCGK.js → chunk-G7B7ZCBF.js} +167 -10
- package/dist/{chunk-ATGTCWHJ.js → chunk-HMHA733L.js} +473 -196
- package/dist/{chunk-EZ5FWVLA.js → chunk-KWI3YDBW.js} +343 -17
- package/dist/{chunk-LMBYEGUR.js → chunk-TO3GIK7Z.js} +71 -6
- package/dist/{chunk-BZQM2XO7.js → chunk-VHGACEHO.js} +62 -1
- package/dist/{contributions-tjNExo1o.d.ts → contributions-BhOBRenr.d.cts} +79 -12
- package/dist/{contributions-CtlptxR8.d.cts → contributions-HGqnNb_P.d.ts} +79 -12
- package/dist/index.cjs +733 -113
- package/dist/index.d.cts +34 -14
- package/dist/index.d.ts +34 -14
- package/dist/index.js +5 -5
- package/dist/{options-BnEPUuVV.d.ts → options-lMUwmRwh.d.cts} +69 -4
- package/dist/{options-BnEPUuVV.d.cts → options-lMUwmRwh.d.ts} +69 -4
- package/dist/{plugin-e2WUEtiI.d.ts → plugin-Cr2Pj8q8.d.ts} +4 -4
- package/dist/{plugin-BWxJRKzS.d.cts → plugin-Do5J5xuF.d.cts} +4 -4
- package/dist/plugin.cjs +38 -6
- package/dist/plugin.d.cts +5 -5
- package/dist/plugin.d.ts +5 -5
- package/dist/plugin.js +3 -3
- 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/{side-panel-HF0IAzwf.d.cts → side-panel-_2UK1gSm.d.cts} +58 -3
- package/dist/{side-panel-HF0IAzwf.d.ts → side-panel-_2UK1gSm.d.ts} +58 -3
- package/dist/{statusline-model-DFY7tuHj.d.cts → statusline-model-B-2xGlB_.d.cts} +3 -3
- package/dist/{statusline-model-zKXtGIjj.d.ts → statusline-model-CY1wzxHg.d.ts} +3 -3
- package/dist/ui.cjs +298 -13
- package/dist/ui.d.cts +132 -74
- package/dist/ui.d.ts +132 -74
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +734 -114
- package/dist/vela.global.min.js +86 -52
- package/dist/widget.cjs +1227 -211
- package/dist/widget.d.cts +36 -12
- package/dist/widget.d.ts +36 -12
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +1226 -211
- package/dist/workspace.d.cts +47 -20
- package/dist/workspace.d.ts +47 -20
- package/dist/workspace.js +6 -6
- package/package.json +2 -2
- /package/dist/{chunk-JU4CWEBF.js → chunk-W5YPYZ2P.js} +0 -0
package/dist/widget.cjs
CHANGED
|
@@ -1256,6 +1256,7 @@ registerIcon("expand", S('<rect x="2.2" y="2.2" width="11.6" height="11.6" rx="1
|
|
|
1256
1256
|
registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
|
|
1257
1257
|
registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
|
|
1258
1258
|
registerIcon("maximize", S('<path d="M2.5 6V3a.5.5 0 0 1 .5-.5h3M10 2.5h3a.5.5 0 0 1 .5.5v3M13.5 10v3a.5.5 0 0 1-.5.5h-3M6 13.5H3a.5.5 0 0 1-.5-.5v-3"/>'));
|
|
1259
|
+
registerIcon("restore", S('<path d="M6 2.5v3a.5.5 0 0 1-.5.5h-3M13.5 6h-3a.5.5 0 0 1-.5-.5v-3M10 13.5v-3a.5.5 0 0 1 .5-.5h3M2.5 10h3a.5.5 0 0 1 .5.5v3"/>'));
|
|
1259
1260
|
registerIcon("restore", S('<path d="M6.2 2.5v3.7H2.5M9.8 13.5V9.8h3.7M13.5 6.2H9.8V2.5M2.5 9.8h3.7v3.7"/>'));
|
|
1260
1261
|
registerIcon("star", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>'));
|
|
1261
1262
|
registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>', 'fill="currentColor"'));
|
|
@@ -1337,6 +1338,17 @@ function nextUid(prefix) {
|
|
|
1337
1338
|
return `${prefix}-${++uid}`;
|
|
1338
1339
|
}
|
|
1339
1340
|
|
|
1341
|
+
// src/ui/surface-events.ts
|
|
1342
|
+
var SURFACE_OPEN_EVENT = "vela:surface-open";
|
|
1343
|
+
var SURFACE_CLOSE_EVENT = "vela:surface-close";
|
|
1344
|
+
function announceSurface(el, open2, kind, trigger = null) {
|
|
1345
|
+
const View = el.ownerDocument.defaultView;
|
|
1346
|
+
if (!View) return;
|
|
1347
|
+
el.dispatchEvent(
|
|
1348
|
+
new View.CustomEvent(open2 ? SURFACE_OPEN_EVENT : SURFACE_CLOSE_EVENT, { bubbles: true, composed: true, detail: { kind, trigger } })
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1340
1352
|
// src/ui/keymap.ts
|
|
1341
1353
|
var KEY_ALIASES = {
|
|
1342
1354
|
esc: "escape",
|
|
@@ -1553,6 +1565,55 @@ var Tooltip = class {
|
|
|
1553
1565
|
this.trigger.removeAttribute("data-scope");
|
|
1554
1566
|
}
|
|
1555
1567
|
};
|
|
1568
|
+
|
|
1569
|
+
// src/ui/surface-exit.ts
|
|
1570
|
+
var CLOSING_ATTR = "data-closing";
|
|
1571
|
+
var SURFACE_EXIT_CAP_MS = 1e3;
|
|
1572
|
+
function animationsOf(els) {
|
|
1573
|
+
return els.flatMap((el) => el.getAnimations({ subtree: true }));
|
|
1574
|
+
}
|
|
1575
|
+
function ends(a) {
|
|
1576
|
+
return Number.isFinite(a.effect?.getComputedTiming().endTime ?? Number.POSITIVE_INFINITY);
|
|
1577
|
+
}
|
|
1578
|
+
function holdForExit(els, done, opts = {}) {
|
|
1579
|
+
const list = Array.isArray(els) ? els : [els];
|
|
1580
|
+
if (list.length === 0 || !list.every((el) => typeof el.getAnimations === "function")) {
|
|
1581
|
+
done();
|
|
1582
|
+
return null;
|
|
1583
|
+
}
|
|
1584
|
+
const before = new Set(animationsOf(list));
|
|
1585
|
+
for (const el of list) el.setAttribute(CLOSING_ATTR, "");
|
|
1586
|
+
const exit = animationsOf(list).filter((a) => !before.has(a) && ends(a));
|
|
1587
|
+
if (exit.length === 0) {
|
|
1588
|
+
for (const el of list) el.removeAttribute(CLOSING_ATTR);
|
|
1589
|
+
done();
|
|
1590
|
+
return null;
|
|
1591
|
+
}
|
|
1592
|
+
const frozen = [...list, ...opts.inert ?? []];
|
|
1593
|
+
const wasInert = frozen.map((el) => el.inert);
|
|
1594
|
+
for (const el of frozen) el.inert = true;
|
|
1595
|
+
let settled = false;
|
|
1596
|
+
const settle = (complete) => {
|
|
1597
|
+
if (settled) return;
|
|
1598
|
+
settled = true;
|
|
1599
|
+
clearTimeout(cap);
|
|
1600
|
+
for (const el of list) el.removeAttribute(CLOSING_ATTR);
|
|
1601
|
+
frozen.forEach((el, i) => {
|
|
1602
|
+
el.inert = wasInert[i];
|
|
1603
|
+
});
|
|
1604
|
+
if (complete) done();
|
|
1605
|
+
};
|
|
1606
|
+
const cap = setTimeout(() => settle(true), SURFACE_EXIT_CAP_MS);
|
|
1607
|
+
let pending = exit.length;
|
|
1608
|
+
const one = () => {
|
|
1609
|
+
if (--pending === 0) settle(true);
|
|
1610
|
+
};
|
|
1611
|
+
for (const a of exit) a.finished.then(one, one);
|
|
1612
|
+
return {
|
|
1613
|
+
cancel: () => settle(false),
|
|
1614
|
+
finish: () => settle(true)
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1556
1617
|
function menuController(opts) {
|
|
1557
1618
|
return {
|
|
1558
1619
|
machine: menu__namespace.machine,
|
|
@@ -1634,6 +1695,35 @@ var MENU_CSS = `
|
|
|
1634
1695
|
color: var(--vela-fg-bright);
|
|
1635
1696
|
}
|
|
1636
1697
|
.vela-menu-item[data-checkmark] { color: var(--vela-fg-bright); }
|
|
1698
|
+
/* Badge mode (iconBadges): the row icon sits in a rounded tile and the row grows to fit
|
|
1699
|
+
it. An icon-less row of the same level keeps an invisible slot, so labels align. */
|
|
1700
|
+
.vela-menu[data-badges] > .vela-menu-item { padding: 5px 10px 5px 6px; }
|
|
1701
|
+
.vela-menu-item .vela-menu-badge {
|
|
1702
|
+
width: 24px;
|
|
1703
|
+
height: 24px;
|
|
1704
|
+
padding: 3px;
|
|
1705
|
+
flex: none;
|
|
1706
|
+
box-sizing: border-box;
|
|
1707
|
+
display: inline-flex;
|
|
1708
|
+
align-items: center;
|
|
1709
|
+
justify-content: center;
|
|
1710
|
+
border-radius: var(--vela-radius-md);
|
|
1711
|
+
background: var(--vela-hover);
|
|
1712
|
+
border: 1px solid var(--vela-border);
|
|
1713
|
+
color: var(--vela-fg-muted);
|
|
1714
|
+
transition: background var(--vela-dur-fast) var(--vela-ease), border-color var(--vela-dur-fast) var(--vela-ease), color var(--vela-dur-fast) var(--vela-ease);
|
|
1715
|
+
}
|
|
1716
|
+
.vela-menu-item .vela-menu-badge:empty { background: transparent; border-color: transparent; }
|
|
1717
|
+
.vela-menu-item .vela-menu-badge .vela-icon { color: inherit; }
|
|
1718
|
+
.vela-menu-item[data-highlighted] .vela-menu-badge:not(:empty) { color: var(--vela-fg-bright); }
|
|
1719
|
+
/* The selected tile inverts: a bright fill under the surface-colored glyph. After the
|
|
1720
|
+
hover rule, so a selected row keeps its tile while highlighted. */
|
|
1721
|
+
.vela-menu-item[data-checked] .vela-menu-badge:not(:empty),
|
|
1722
|
+
.vela-menu-item[data-checkmark] .vela-menu-badge:not(:empty) {
|
|
1723
|
+
background: color-mix(in srgb, var(--vela-fg-bright) 83%, var(--vela-surface));
|
|
1724
|
+
border-color: color-mix(in srgb, var(--vela-fg-bright) 40%, transparent);
|
|
1725
|
+
color: var(--vela-surface);
|
|
1726
|
+
}
|
|
1637
1727
|
/* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
|
|
1638
1728
|
same control language as the settings dialog's toggles. */
|
|
1639
1729
|
.vela-menu-switch {
|
|
@@ -1698,11 +1788,16 @@ var Surface = class _Surface {
|
|
|
1698
1788
|
/** Trigger rect captured when this level opened — the list stays put if the
|
|
1699
1789
|
* trigger then moves (favorite chips shifting the caret). */
|
|
1700
1790
|
this.pinnedAnchor = null;
|
|
1791
|
+
/** The open state last announced — see {@link announce}. */
|
|
1792
|
+
this.shown = false;
|
|
1793
|
+
/** The list's exit animation after a close; a reopen cancels it. */
|
|
1794
|
+
this.exit = null;
|
|
1701
1795
|
this.doc = doc;
|
|
1702
1796
|
this.host = opts.host;
|
|
1703
1797
|
this.onSelect = opts.onSelect;
|
|
1704
1798
|
this.onFavorite = opts.onFavorite;
|
|
1705
1799
|
this.checkmarks = opts.checkmarks === true;
|
|
1800
|
+
this.iconBadges = opts.iconBadges === true;
|
|
1706
1801
|
this.positioner = doc.createElement("div");
|
|
1707
1802
|
this.positioner.className = "vela-ui-layer";
|
|
1708
1803
|
this.list = doc.createElement("ul");
|
|
@@ -1711,6 +1806,7 @@ var Surface = class _Surface {
|
|
|
1711
1806
|
this.positioner.appendChild(this.list);
|
|
1712
1807
|
this.host.appendChild(this.positioner);
|
|
1713
1808
|
const trigger = opts.trigger;
|
|
1809
|
+
this.opener = trigger ?? opts.opener ?? null;
|
|
1714
1810
|
this.ctrl = menuController({
|
|
1715
1811
|
items: [],
|
|
1716
1812
|
id: opts.id,
|
|
@@ -1732,15 +1828,37 @@ var Surface = class _Surface {
|
|
|
1732
1828
|
if (opts.triggerId && trigger) trigger.id = opts.triggerId;
|
|
1733
1829
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
1734
1830
|
const api = this.ctrl.connect(service);
|
|
1831
|
+
const closing = this.shown && !api.open;
|
|
1832
|
+
if (closing) this.announce(false);
|
|
1833
|
+
if (api.open && this.exit) {
|
|
1834
|
+
this.exit.cancel();
|
|
1835
|
+
this.exit = null;
|
|
1836
|
+
}
|
|
1735
1837
|
if (trigger) vanilla.spreadProps(trigger, api.getTriggerProps(), this.mid);
|
|
1736
1838
|
vanilla.spreadProps(this.positioner, api.getPositionerProps(), this.mid);
|
|
1737
1839
|
vanilla.spreadProps(this.list, api.getContentProps(), this.mid);
|
|
1738
1840
|
this.project(api);
|
|
1841
|
+
if (closing) this.holdExit();
|
|
1842
|
+
if (api.open && !this.shown) this.announce(true);
|
|
1843
|
+
});
|
|
1844
|
+
}
|
|
1845
|
+
/** Keep the just-closed list up through its exit animation. `spreadProps` re-applies
|
|
1846
|
+
* `hidden` only when the machine's value for it changes, so the list stays shown until
|
|
1847
|
+
* the exit ends — or until a reopen, which projects `hidden: false` itself. */
|
|
1848
|
+
holdExit() {
|
|
1849
|
+
this.list.hidden = false;
|
|
1850
|
+
this.exit = holdForExit(this.list, () => {
|
|
1851
|
+
this.exit = null;
|
|
1852
|
+
if (!this.shown) this.list.hidden = true;
|
|
1739
1853
|
});
|
|
1740
1854
|
}
|
|
1741
1855
|
get api() {
|
|
1742
1856
|
return this.ctrl.connect(this.handle.service);
|
|
1743
1857
|
}
|
|
1858
|
+
announce(open2) {
|
|
1859
|
+
this.shown = open2;
|
|
1860
|
+
announceSurface(this.list, open2, "menu", this.opener);
|
|
1861
|
+
}
|
|
1744
1862
|
setItems(items) {
|
|
1745
1863
|
this.items = items;
|
|
1746
1864
|
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
@@ -1757,7 +1875,9 @@ var Surface = class _Surface {
|
|
|
1757
1875
|
destroy() {
|
|
1758
1876
|
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
1759
1877
|
this.subs.clear();
|
|
1878
|
+
this.exit?.finish();
|
|
1760
1879
|
this.handle.stop();
|
|
1880
|
+
if (this.shown) this.announce(false);
|
|
1761
1881
|
this.positioner.remove();
|
|
1762
1882
|
}
|
|
1763
1883
|
byId(id) {
|
|
@@ -1782,6 +1902,10 @@ var Surface = class _Surface {
|
|
|
1782
1902
|
const doc = this.doc;
|
|
1783
1903
|
this.list.replaceChildren();
|
|
1784
1904
|
const markable = this.checkmarks && this.items.some((i) => !(i.submenu && i.submenu.length > 0) && !i.toggle && i.checked !== void 0);
|
|
1905
|
+
const iconic = this.items.some((i) => !!i.icon);
|
|
1906
|
+
const badged = this.iconBadges && iconic;
|
|
1907
|
+
if (badged) this.list.dataset.badges = "1";
|
|
1908
|
+
else delete this.list.dataset.badges;
|
|
1785
1909
|
for (const item of this.items) {
|
|
1786
1910
|
if (item.separatorBefore) {
|
|
1787
1911
|
const sep = doc.createElement("li");
|
|
@@ -1807,7 +1931,16 @@ var Surface = class _Surface {
|
|
|
1807
1931
|
} else if (!branch && !item.toggle && item.checked) {
|
|
1808
1932
|
li.dataset.checked = "1";
|
|
1809
1933
|
}
|
|
1810
|
-
if (
|
|
1934
|
+
if (badged) {
|
|
1935
|
+
const badge = doc.createElement("span");
|
|
1936
|
+
badge.className = "vela-menu-badge";
|
|
1937
|
+
if (item.icon) badge.appendChild(iconEl(item.icon, doc));
|
|
1938
|
+
li.appendChild(badge);
|
|
1939
|
+
} else if (item.icon) {
|
|
1940
|
+
li.appendChild(iconEl(item.icon, doc));
|
|
1941
|
+
} else if (iconic) {
|
|
1942
|
+
li.appendChild(iconEl("", doc));
|
|
1943
|
+
}
|
|
1811
1944
|
const label = doc.createElement("span");
|
|
1812
1945
|
label.className = "vela-menu-label";
|
|
1813
1946
|
label.textContent = item.label;
|
|
@@ -1851,9 +1984,11 @@ var Surface = class _Surface {
|
|
|
1851
1984
|
const sub2 = new _Surface(doc, {
|
|
1852
1985
|
host: this.host,
|
|
1853
1986
|
placement: "right-start",
|
|
1987
|
+
opener: li,
|
|
1854
1988
|
onSelect: this.onSelect,
|
|
1855
1989
|
onFavorite: this.onFavorite,
|
|
1856
1990
|
checkmarks: this.checkmarks,
|
|
1991
|
+
iconBadges: this.iconBadges,
|
|
1857
1992
|
id: `${this.mid}--${item.id}`
|
|
1858
1993
|
});
|
|
1859
1994
|
sub2.setItems(item.submenu ?? []);
|
|
@@ -1879,7 +2014,8 @@ var Menu = class {
|
|
|
1879
2014
|
id: opts.id,
|
|
1880
2015
|
minWidth: opts.minWidth,
|
|
1881
2016
|
onFavorite: opts.onFavorite,
|
|
1882
|
-
checkmarks: opts.checkmarks
|
|
2017
|
+
checkmarks: opts.checkmarks,
|
|
2018
|
+
iconBadges: opts.iconBadges
|
|
1883
2019
|
});
|
|
1884
2020
|
this.root.setItems(opts.items);
|
|
1885
2021
|
}
|
|
@@ -1983,6 +2119,7 @@ var POPOVER_CSS = `
|
|
|
1983
2119
|
|
|
1984
2120
|
// src/ui/components/popover/view.ts
|
|
1985
2121
|
var open = null;
|
|
2122
|
+
var leaving = /* @__PURE__ */ new Set();
|
|
1986
2123
|
function closeOpenPopovers() {
|
|
1987
2124
|
open?.hide();
|
|
1988
2125
|
}
|
|
@@ -2010,6 +2147,8 @@ var Popover = class {
|
|
|
2010
2147
|
this.shown = false;
|
|
2011
2148
|
/** The pending removal of a fading-out shell; a show() that reuses the shell cancels it. */
|
|
2012
2149
|
this.leaveTimer = null;
|
|
2150
|
+
/** The shell's exit animation before it leaves the DOM; a show() that reuses the shell cancels it. */
|
|
2151
|
+
this.exit = null;
|
|
2013
2152
|
const doc = opts.trigger.ownerDocument;
|
|
2014
2153
|
injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
|
|
2015
2154
|
this.trigger = opts.trigger;
|
|
@@ -2046,6 +2185,10 @@ var Popover = class {
|
|
|
2046
2185
|
clearTimeout(this.leaveTimer);
|
|
2047
2186
|
this.leaveTimer = null;
|
|
2048
2187
|
}
|
|
2188
|
+
this.exit?.cancel();
|
|
2189
|
+
this.exit = null;
|
|
2190
|
+
leaving.delete(this);
|
|
2191
|
+
for (const p of [...leaving]) if (p.trigger === this.trigger) p.exit?.finish();
|
|
2049
2192
|
ensureUIHost(this.el, this.inheritsTokens ? void 0 : this.theme);
|
|
2050
2193
|
if (this.fadeMs > 0) {
|
|
2051
2194
|
this.el.style.transition = `opacity ${this.fadeMs}ms ease`;
|
|
@@ -2070,16 +2213,21 @@ var Popover = class {
|
|
|
2070
2213
|
this.hide();
|
|
2071
2214
|
};
|
|
2072
2215
|
const onReflow = () => this.place();
|
|
2073
|
-
setTimeout(() =>
|
|
2216
|
+
setTimeout(() => {
|
|
2217
|
+
if (this.onOutside === onOutside) document.addEventListener("pointerdown", onOutside, true);
|
|
2218
|
+
}, 0);
|
|
2074
2219
|
document.addEventListener("keydown", onKey, true);
|
|
2075
2220
|
window.addEventListener("resize", onReflow, true);
|
|
2076
2221
|
document.addEventListener("scroll", onReflow, true);
|
|
2077
2222
|
this.onOutside = onOutside;
|
|
2078
2223
|
this.onKey = onKey;
|
|
2079
2224
|
this.onReflow = onReflow;
|
|
2225
|
+
announceSurface(this.el, true, "popover", this.trigger);
|
|
2080
2226
|
}
|
|
2081
2227
|
hide() {
|
|
2082
2228
|
if (!this.shown) return;
|
|
2229
|
+
this.shown = false;
|
|
2230
|
+
announceSurface(this.el, false, "popover", this.trigger);
|
|
2083
2231
|
if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
|
|
2084
2232
|
if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
|
|
2085
2233
|
if (this.onReflow) {
|
|
@@ -2097,9 +2245,13 @@ var Popover = class {
|
|
|
2097
2245
|
this.el.remove();
|
|
2098
2246
|
}, this.fadeMs);
|
|
2099
2247
|
} else {
|
|
2100
|
-
this.el
|
|
2248
|
+
this.exit = holdForExit(this.el, () => {
|
|
2249
|
+
this.exit = null;
|
|
2250
|
+
leaving.delete(this);
|
|
2251
|
+
this.el.remove();
|
|
2252
|
+
});
|
|
2253
|
+
if (this.exit) leaving.add(this);
|
|
2101
2254
|
}
|
|
2102
|
-
this.shown = false;
|
|
2103
2255
|
if (open === this) open = null;
|
|
2104
2256
|
this.ctrl.onClose?.();
|
|
2105
2257
|
}
|
|
@@ -2110,6 +2262,7 @@ var Popover = class {
|
|
|
2110
2262
|
}
|
|
2111
2263
|
destroy() {
|
|
2112
2264
|
this.hide();
|
|
2265
|
+
this.exit?.finish();
|
|
2113
2266
|
}
|
|
2114
2267
|
reposition() {
|
|
2115
2268
|
if (this.shown) this.place();
|
|
@@ -2305,6 +2458,11 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
2305
2458
|
`;
|
|
2306
2459
|
var Dialog = class {
|
|
2307
2460
|
constructor(opts = {}) {
|
|
2461
|
+
this.opener = null;
|
|
2462
|
+
this.wasOpen = false;
|
|
2463
|
+
/** The panel and scrim's exit animation after a close; a reopen cancels it. */
|
|
2464
|
+
this.exit = null;
|
|
2465
|
+
this.destroyed = false;
|
|
2308
2466
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
2309
2467
|
injectStyles(DIALOG_STYLE_ID, DIALOG_CSS, doc);
|
|
2310
2468
|
const host = opts.host ?? doc.body;
|
|
@@ -2401,19 +2559,73 @@ var Dialog = class {
|
|
|
2401
2559
|
const mid = String(this.ctrl.props.id);
|
|
2402
2560
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
2403
2561
|
const api = this.ctrl.connect(service);
|
|
2562
|
+
const closing = this.wasOpen && !api.open;
|
|
2563
|
+
if (closing) this.announce(false);
|
|
2404
2564
|
vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
|
|
2405
2565
|
vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
|
|
2406
2566
|
vanilla.spreadProps(this.panel, api.getContentProps(), mid);
|
|
2407
2567
|
vanilla.spreadProps(title, api.getTitleProps(), mid);
|
|
2408
2568
|
vanilla.spreadProps(close, api.getCloseTriggerProps(), mid);
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2569
|
+
if (api.open) {
|
|
2570
|
+
this.exit?.cancel();
|
|
2571
|
+
this.exit = null;
|
|
2572
|
+
this.setShown(true);
|
|
2573
|
+
} else if (closing) {
|
|
2574
|
+
this.holdExit();
|
|
2575
|
+
} else if (!this.exit) {
|
|
2576
|
+
this.setShown(false);
|
|
2577
|
+
}
|
|
2578
|
+
if (api.open && !this.wasOpen) this.announce(true);
|
|
2579
|
+
if (this.wasOpen && !api.open) this.restoreOpener();
|
|
2580
|
+
this.wasOpen = api.open;
|
|
2581
|
+
});
|
|
2582
|
+
}
|
|
2583
|
+
setShown(shown) {
|
|
2584
|
+
this.backdrop.style.display = shown ? "" : "none";
|
|
2585
|
+
this.positioner.style.display = shown ? "" : "none";
|
|
2586
|
+
}
|
|
2587
|
+
/** Keep the just-closed dialog up through its exit animation, then hide it — or
|
|
2588
|
+
* remove it, when it was destroyed meanwhile. `spreadProps` re-applies the close
|
|
2589
|
+
* props' `hidden` only when its value changes, so lifting it keeps the panel and
|
|
2590
|
+
* scrim up until the exit ends. The positioner spans the viewport: inert, it lets
|
|
2591
|
+
* clicks through to the page while the panel animates out. */
|
|
2592
|
+
holdExit() {
|
|
2593
|
+
const held = [this.panel, this.backdrop];
|
|
2594
|
+
for (const el of held) el.hidden = false;
|
|
2595
|
+
this.exit = holdForExit(
|
|
2596
|
+
held,
|
|
2597
|
+
() => {
|
|
2598
|
+
this.exit = null;
|
|
2599
|
+
if (this.destroyed) {
|
|
2600
|
+
this.detach();
|
|
2601
|
+
} else if (!this.open) {
|
|
2602
|
+
for (const el of held) el.hidden = true;
|
|
2603
|
+
this.setShown(false);
|
|
2604
|
+
}
|
|
2605
|
+
},
|
|
2606
|
+
{ inert: [this.positioner] }
|
|
2607
|
+
);
|
|
2608
|
+
}
|
|
2609
|
+
detach() {
|
|
2610
|
+
this.backdrop.remove();
|
|
2611
|
+
this.positioner.remove();
|
|
2612
|
+
}
|
|
2613
|
+
/** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
|
|
2614
|
+
* teardown that outruns the machine's close notification). */
|
|
2615
|
+
restoreOpener() {
|
|
2616
|
+
const opener = this.opener;
|
|
2617
|
+
this.opener = null;
|
|
2618
|
+
if (opener?.isConnected) opener.focus({ preventScroll: true });
|
|
2619
|
+
}
|
|
2620
|
+
announce(open2) {
|
|
2621
|
+
const opener = this.opener === this.panel.ownerDocument.body ? null : this.opener;
|
|
2622
|
+
announceSurface(this.panel, open2, "dialog", opener);
|
|
2412
2623
|
}
|
|
2413
2624
|
get open() {
|
|
2414
2625
|
return this.ctrl.connect(this.handle.service).open;
|
|
2415
2626
|
}
|
|
2416
2627
|
show() {
|
|
2628
|
+
this.opener = this.panel.ownerDocument.activeElement;
|
|
2417
2629
|
this.ctrl.connect(this.handle.service).setOpen(true);
|
|
2418
2630
|
}
|
|
2419
2631
|
hide() {
|
|
@@ -2426,10 +2638,16 @@ var Dialog = class {
|
|
|
2426
2638
|
return el?.closest(".vela-popover, .vela-menu") != null;
|
|
2427
2639
|
}
|
|
2428
2640
|
destroy() {
|
|
2641
|
+
if (this.wasOpen && !this.open) this.handle.flush();
|
|
2429
2642
|
if (this.open) this.hide();
|
|
2430
2643
|
this.handle.stop();
|
|
2431
|
-
this.
|
|
2432
|
-
this.
|
|
2644
|
+
this.destroyed = true;
|
|
2645
|
+
if (this.wasOpen) {
|
|
2646
|
+
this.wasOpen = false;
|
|
2647
|
+
this.announce(false);
|
|
2648
|
+
}
|
|
2649
|
+
this.restoreOpener();
|
|
2650
|
+
if (!this.exit) this.detach();
|
|
2433
2651
|
}
|
|
2434
2652
|
};
|
|
2435
2653
|
function drawerController(opts = {}) {
|
|
@@ -2556,6 +2774,11 @@ function swipeDirection(dx, dy) {
|
|
|
2556
2774
|
}
|
|
2557
2775
|
var Drawer = class {
|
|
2558
2776
|
constructor(opts = {}) {
|
|
2777
|
+
this.opener = null;
|
|
2778
|
+
this.wasOpen = false;
|
|
2779
|
+
/** The sheet and scrim's exit animation after a close; a reopen cancels it. */
|
|
2780
|
+
this.exit = null;
|
|
2781
|
+
this.destroyed = false;
|
|
2559
2782
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
2560
2783
|
injectStyles(DRAWER_STYLE_ID, DRAWER_CSS, doc);
|
|
2561
2784
|
const host = opts.host ?? doc.body;
|
|
@@ -2583,12 +2806,24 @@ var Drawer = class {
|
|
|
2583
2806
|
const mid = String(this.ctrl.props.id);
|
|
2584
2807
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
2585
2808
|
const api = this.ctrl.connect(service);
|
|
2809
|
+
const closing = this.wasOpen && !api.open;
|
|
2810
|
+
if (closing) this.announce(false);
|
|
2586
2811
|
vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
|
|
2587
2812
|
vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
|
|
2588
2813
|
vanilla.spreadProps(this.panel, api.getContentProps(), mid);
|
|
2589
2814
|
vanilla.spreadProps(this.titleEl, api.getTitleProps(), mid);
|
|
2590
|
-
|
|
2591
|
-
|
|
2815
|
+
if (api.open) {
|
|
2816
|
+
this.exit?.cancel();
|
|
2817
|
+
this.exit = null;
|
|
2818
|
+
this.setShown(true);
|
|
2819
|
+
} else if (closing) {
|
|
2820
|
+
this.holdExit();
|
|
2821
|
+
} else if (!this.exit) {
|
|
2822
|
+
this.setShown(false);
|
|
2823
|
+
}
|
|
2824
|
+
if (api.open && !this.wasOpen) this.announce(true);
|
|
2825
|
+
if (this.wasOpen && !api.open) this.restoreOpener();
|
|
2826
|
+
this.wasOpen = api.open;
|
|
2592
2827
|
});
|
|
2593
2828
|
}
|
|
2594
2829
|
/** Any element between `from` and the panel that has already been scrolled down —
|
|
@@ -2697,19 +2932,67 @@ var Drawer = class {
|
|
|
2697
2932
|
setTitle(title) {
|
|
2698
2933
|
this.titleEl.textContent = title;
|
|
2699
2934
|
}
|
|
2935
|
+
setShown(shown) {
|
|
2936
|
+
this.backdrop.style.display = shown ? "" : "none";
|
|
2937
|
+
this.positioner.style.display = shown ? "" : "none";
|
|
2938
|
+
}
|
|
2939
|
+
/** Keep the just-closed sheet up through its exit animation, then hide it — or
|
|
2940
|
+
* remove it, when it was destroyed meanwhile. `spreadProps` re-applies the close
|
|
2941
|
+
* props' `hidden` only when its value changes, so lifting it keeps the sheet and
|
|
2942
|
+
* scrim up until the exit ends. The positioner spans the host: inert, it lets
|
|
2943
|
+
* clicks through to the page while the sheet animates out. */
|
|
2944
|
+
holdExit() {
|
|
2945
|
+
const held = [this.panel, this.backdrop];
|
|
2946
|
+
for (const el of held) el.hidden = false;
|
|
2947
|
+
this.exit = holdForExit(
|
|
2948
|
+
held,
|
|
2949
|
+
() => {
|
|
2950
|
+
this.exit = null;
|
|
2951
|
+
if (this.destroyed) {
|
|
2952
|
+
this.detach();
|
|
2953
|
+
} else if (!this.open) {
|
|
2954
|
+
for (const el of held) el.hidden = true;
|
|
2955
|
+
this.setShown(false);
|
|
2956
|
+
}
|
|
2957
|
+
},
|
|
2958
|
+
{ inert: [this.positioner] }
|
|
2959
|
+
);
|
|
2960
|
+
}
|
|
2961
|
+
detach() {
|
|
2962
|
+
this.backdrop.remove();
|
|
2963
|
+
this.positioner.remove();
|
|
2964
|
+
}
|
|
2965
|
+
/** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
|
|
2966
|
+
* teardown that outruns the machine's close notification). */
|
|
2967
|
+
restoreOpener() {
|
|
2968
|
+
const opener = this.opener;
|
|
2969
|
+
this.opener = null;
|
|
2970
|
+
if (opener?.isConnected) opener.focus({ preventScroll: true });
|
|
2971
|
+
}
|
|
2972
|
+
announce(open2) {
|
|
2973
|
+
const opener = this.opener === this.panel.ownerDocument.body ? null : this.opener;
|
|
2974
|
+
announceSurface(this.panel, open2, "drawer", opener);
|
|
2975
|
+
}
|
|
2700
2976
|
get open() {
|
|
2701
2977
|
return this.ctrl.connect(this.handle.service).open;
|
|
2702
2978
|
}
|
|
2703
2979
|
show() {
|
|
2980
|
+
this.opener = this.panel.ownerDocument.activeElement;
|
|
2704
2981
|
this.ctrl.connect(this.handle.service).setOpen(true);
|
|
2705
2982
|
}
|
|
2706
2983
|
hide() {
|
|
2707
2984
|
this.ctrl.connect(this.handle.service).setOpen(false);
|
|
2708
2985
|
}
|
|
2709
2986
|
destroy() {
|
|
2987
|
+
if (this.wasOpen && !this.open) this.handle.flush();
|
|
2710
2988
|
this.handle.stop();
|
|
2711
|
-
this.
|
|
2712
|
-
this.
|
|
2989
|
+
this.destroyed = true;
|
|
2990
|
+
if (this.wasOpen) {
|
|
2991
|
+
this.wasOpen = false;
|
|
2992
|
+
this.announce(false);
|
|
2993
|
+
}
|
|
2994
|
+
this.restoreOpener();
|
|
2995
|
+
if (!this.exit) this.detach();
|
|
2713
2996
|
}
|
|
2714
2997
|
};
|
|
2715
2998
|
|
|
@@ -4845,11 +5128,11 @@ var DatePicker = class {
|
|
|
4845
5128
|
};
|
|
4846
5129
|
|
|
4847
5130
|
// src/widget/layout-picker.ts
|
|
4848
|
-
var STYLE_ID = "vela-widget-layout-picker-
|
|
5131
|
+
var STYLE_ID = "vela-widget-layout-picker-v15";
|
|
4849
5132
|
var CSS2 = `
|
|
4850
5133
|
.vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
|
|
4851
5134
|
.vela-lp {
|
|
4852
|
-
background: var(--vela-surface
|
|
5135
|
+
background: var(--vela-surface);
|
|
4853
5136
|
color: var(--vela-fg);
|
|
4854
5137
|
border: 1px solid var(--vela-border-strong);
|
|
4855
5138
|
border-radius: 8px;
|
|
@@ -4978,6 +5261,8 @@ var LayoutPicker = class {
|
|
|
4978
5261
|
constructor(opts) {
|
|
4979
5262
|
this.squares = [];
|
|
4980
5263
|
this.isOpen = false;
|
|
5264
|
+
/** The card's exit animation after a close; a reopen cancels it. */
|
|
5265
|
+
this.exit = null;
|
|
4981
5266
|
/** Hover preview (1-based rows/cols), null = show current shape. */
|
|
4982
5267
|
this.hover = null;
|
|
4983
5268
|
this.onDocPointerDown = (e) => {
|
|
@@ -4998,6 +5283,7 @@ var LayoutPicker = class {
|
|
|
4998
5283
|
const panel = doc.createElement("div");
|
|
4999
5284
|
panel.className = "vela-lp";
|
|
5000
5285
|
this.layer.appendChild(panel);
|
|
5286
|
+
this.panel = panel;
|
|
5001
5287
|
const cols = doc.createElement("div");
|
|
5002
5288
|
cols.className = "vela-lp-cols";
|
|
5003
5289
|
panel.appendChild(cols);
|
|
@@ -5071,6 +5357,8 @@ var LayoutPicker = class {
|
|
|
5071
5357
|
open() {
|
|
5072
5358
|
if (this.isOpen) return;
|
|
5073
5359
|
this.isOpen = true;
|
|
5360
|
+
this.exit?.cancel();
|
|
5361
|
+
this.exit = null;
|
|
5074
5362
|
this.hover = null;
|
|
5075
5363
|
this.refresh();
|
|
5076
5364
|
this.layer.style.display = "";
|
|
@@ -5079,11 +5367,16 @@ var LayoutPicker = class {
|
|
|
5079
5367
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
5080
5368
|
this.doc.addEventListener("keydown", this.onDocKeydown, true);
|
|
5081
5369
|
this.opts.onOpenChange?.(true);
|
|
5370
|
+
if (this.isOpen) announceSurface(this.panel, true, "popover", this.opts.trigger);
|
|
5082
5371
|
}
|
|
5083
5372
|
close() {
|
|
5084
5373
|
if (!this.isOpen) return;
|
|
5085
5374
|
this.isOpen = false;
|
|
5086
|
-
this.
|
|
5375
|
+
announceSurface(this.panel, false, "popover", this.opts.trigger);
|
|
5376
|
+
this.exit = holdForExit(this.panel, () => {
|
|
5377
|
+
this.exit = null;
|
|
5378
|
+
if (!this.isOpen) this.layer.style.display = "none";
|
|
5379
|
+
});
|
|
5087
5380
|
this.opts.trigger.setAttribute("aria-expanded", "false");
|
|
5088
5381
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
5089
5382
|
this.doc.removeEventListener("keydown", this.onDocKeydown, true);
|
|
@@ -5098,6 +5391,7 @@ var LayoutPicker = class {
|
|
|
5098
5391
|
}
|
|
5099
5392
|
destroy() {
|
|
5100
5393
|
this.close();
|
|
5394
|
+
this.exit?.finish();
|
|
5101
5395
|
this.infoTip.destroy();
|
|
5102
5396
|
this.layer.remove();
|
|
5103
5397
|
}
|
|
@@ -5164,7 +5458,7 @@ var LayoutPicker = class {
|
|
|
5164
5458
|
const hostRect = this.opts.host.getBoundingClientRect();
|
|
5165
5459
|
const trigRect = this.opts.trigger.getBoundingClientRect();
|
|
5166
5460
|
let left = trigRect.left - hostRect.left;
|
|
5167
|
-
const top = trigRect.bottom - hostRect.top +
|
|
5461
|
+
const top = trigRect.bottom - hostRect.top + 8;
|
|
5168
5462
|
const width = this.layer.offsetWidth;
|
|
5169
5463
|
if (left + width > hostRect.width - 8) left = Math.max(8, hostRect.width - 8 - width);
|
|
5170
5464
|
this.layer.style.left = `${left}px`;
|
|
@@ -5323,10 +5617,12 @@ function legendActions() {
|
|
|
5323
5617
|
}
|
|
5324
5618
|
function legendActionsProviderFor(chart, context) {
|
|
5325
5619
|
return (indicatorId) => {
|
|
5326
|
-
const
|
|
5620
|
+
const find = () => chart.indicators().find((h) => h.id === indicatorId);
|
|
5621
|
+
const infoOf = (h) => ({ id: h.id, title: h.title, ...h.source !== void 0 ? { source: h.source } : {} });
|
|
5622
|
+
const handle = find();
|
|
5327
5623
|
if (!handle) return [];
|
|
5328
|
-
const info =
|
|
5329
|
-
return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(),
|
|
5624
|
+
const info = infoOf(handle);
|
|
5625
|
+
return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(), infoOf(find() ?? handle)) }));
|
|
5330
5626
|
};
|
|
5331
5627
|
}
|
|
5332
5628
|
var calloutRegistry = /* @__PURE__ */ new Map();
|
|
@@ -6193,7 +6489,8 @@ var Topbar = class {
|
|
|
6193
6489
|
triggerId: "vela-topbar-style",
|
|
6194
6490
|
host,
|
|
6195
6491
|
items: this.styleItems(),
|
|
6196
|
-
onSelect: (id) => opts.onPriceStyle(id)
|
|
6492
|
+
onSelect: (id) => opts.onPriceStyle(id),
|
|
6493
|
+
iconBadges: true
|
|
6197
6494
|
});
|
|
6198
6495
|
}
|
|
6199
6496
|
setSymbol(symbol) {
|
|
@@ -6259,8 +6556,14 @@ var Topbar = class {
|
|
|
6259
6556
|
renderLayoutButton(doc) {
|
|
6260
6557
|
if (!this.layoutButton) return;
|
|
6261
6558
|
this.layoutButton.replaceChildren();
|
|
6262
|
-
|
|
6263
|
-
|
|
6559
|
+
const glyph = this.opts.layout?.glyph?.();
|
|
6560
|
+
if (glyph || iconMarkup("layout")) {
|
|
6561
|
+
const icon2 = iconEl("layout", doc);
|
|
6562
|
+
if (glyph) icon2.innerHTML = glyph;
|
|
6563
|
+
this.layoutButton.appendChild(icon2);
|
|
6564
|
+
} else {
|
|
6565
|
+
this.layoutButton.appendChild(doc.createTextNode(this.layoutId ?? ""));
|
|
6566
|
+
}
|
|
6264
6567
|
this.layoutButton.setAttribute("aria-label", `Layout \u2014 ${this.layoutId ?? ""}`);
|
|
6265
6568
|
}
|
|
6266
6569
|
renderStyleButton(doc) {
|
|
@@ -9543,6 +9846,10 @@ var FibRatios = class extends Drawing {
|
|
|
9543
9846
|
|
|
9544
9847
|
// src/core/drawings/types/FibLevels.ts
|
|
9545
9848
|
var FibLevels = class extends FibRatios {
|
|
9849
|
+
/** Price of a level for the two anchor prices; subclasses override to change which anchor ratio 0 sits on. */
|
|
9850
|
+
levelPrice(ratio, p1, p2) {
|
|
9851
|
+
return p1 + ratio * (p2 - p1);
|
|
9852
|
+
}
|
|
9546
9853
|
/** Per-level pixel line + price for the ENABLED levels, spanning the anchors' time range. */
|
|
9547
9854
|
levelLines(proj) {
|
|
9548
9855
|
const a = this.anchors[0];
|
|
@@ -9552,11 +9859,10 @@ var FibLevels = class extends FibRatios {
|
|
|
9552
9859
|
const xb = proj.xOf(b.time);
|
|
9553
9860
|
const x1 = Math.min(xa, xb);
|
|
9554
9861
|
const x2 = Math.max(xa, xb);
|
|
9555
|
-
const delta = b.price - a.price;
|
|
9556
9862
|
const out = [];
|
|
9557
9863
|
for (const lv of this.levels) {
|
|
9558
9864
|
if (!lv.enabled) continue;
|
|
9559
|
-
const price =
|
|
9865
|
+
const price = this.levelPrice(lv.ratio, a.price, b.price);
|
|
9560
9866
|
const y = proj.yOf(price, this.paneId);
|
|
9561
9867
|
if (y == null) continue;
|
|
9562
9868
|
out.push({ ratio: lv.ratio, color: lv.color, label: lv.label, price, x1, x2, y });
|
|
@@ -9596,8 +9902,7 @@ var FibLevels = class extends FibRatios {
|
|
|
9596
9902
|
const a = this.anchors[0];
|
|
9597
9903
|
const b = this.anchors[1];
|
|
9598
9904
|
if (!a || !b) return null;
|
|
9599
|
-
const
|
|
9600
|
-
const prices = this.levels.filter((l) => l.enabled).map((l) => a.price + l.ratio * delta);
|
|
9905
|
+
const prices = this.levels.filter((l) => l.enabled).map((l) => this.levelPrice(l.ratio, a.price, b.price));
|
|
9601
9906
|
if (prices.length === 0) return null;
|
|
9602
9907
|
return { min: Math.min(...prices), max: Math.max(...prices) };
|
|
9603
9908
|
}
|
|
@@ -9683,13 +9988,24 @@ function cycleLevels(count, enabledCount = count) {
|
|
|
9683
9988
|
// src/core/drawings/types/FibRetracement.ts
|
|
9684
9989
|
var LEVELS = fibLevels([0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]);
|
|
9685
9990
|
var FibRetracement = class extends FibLevels {
|
|
9686
|
-
constructor() {
|
|
9687
|
-
super(
|
|
9991
|
+
constructor(init) {
|
|
9992
|
+
super(init);
|
|
9688
9993
|
this.type = "fibretracement";
|
|
9994
|
+
if (this.reverse === void 0) this.reverse = false;
|
|
9689
9995
|
}
|
|
9690
9996
|
defaultLevels() {
|
|
9691
9997
|
return LEVELS;
|
|
9692
9998
|
}
|
|
9999
|
+
levelPrice(ratio, p1, p2) {
|
|
10000
|
+
return this.reverse ? p1 + ratio * (p2 - p1) : p2 + ratio * (p1 - p2);
|
|
10001
|
+
}
|
|
10002
|
+
writeProps() {
|
|
10003
|
+
return { ...super.writeProps(), reverse: this.reverse };
|
|
10004
|
+
}
|
|
10005
|
+
readProps(props) {
|
|
10006
|
+
super.readProps(props);
|
|
10007
|
+
this.reverse = typeof props.reverse === "boolean" ? props.reverse : true;
|
|
10008
|
+
}
|
|
9693
10009
|
};
|
|
9694
10010
|
|
|
9695
10011
|
// src/core/drawings/types/FibExtension.ts
|
|
@@ -12816,6 +13132,32 @@ var CSS5 = `
|
|
|
12816
13132
|
.vela-panel-pin .vela-icon { width: 15px; height: 15px; }
|
|
12817
13133
|
.vela-panel-pin:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12818
13134
|
.vela-panel-pin[data-on='1'] { color: var(--vela-fg-bright); }
|
|
13135
|
+
.vela-panel-max {
|
|
13136
|
+
all: unset;
|
|
13137
|
+
cursor: pointer;
|
|
13138
|
+
width: 26px;
|
|
13139
|
+
height: 26px;
|
|
13140
|
+
flex: none;
|
|
13141
|
+
display: inline-flex;
|
|
13142
|
+
align-items: center;
|
|
13143
|
+
justify-content: center;
|
|
13144
|
+
border-radius: 4px;
|
|
13145
|
+
color: var(--vela-fg-muted);
|
|
13146
|
+
}
|
|
13147
|
+
.vela-panel-max .vela-icon { width: 15px; height: 15px; }
|
|
13148
|
+
.vela-panel-max:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
13149
|
+
/* MAXIMIZED: the panel covers the whole dock host \u2014 every chart of the shell \u2014 whatever
|
|
13150
|
+
its placement; the width handle and the pin have nothing to do meanwhile. */
|
|
13151
|
+
.vela-panel[data-maximized] {
|
|
13152
|
+
position: absolute;
|
|
13153
|
+
inset: 0;
|
|
13154
|
+
width: auto;
|
|
13155
|
+
max-width: none;
|
|
13156
|
+
z-index: 26;
|
|
13157
|
+
border-left: none;
|
|
13158
|
+
}
|
|
13159
|
+
.vela-panel[data-maximized] .vela-panel-resizer,
|
|
13160
|
+
.vela-panel[data-maximized] .vela-panel-pin { display: none; }
|
|
12819
13161
|
.vela-panel-body { flex: 1; overflow: auto; padding: 8px; }
|
|
12820
13162
|
.vela-panel-body::-webkit-scrollbar { width: 8px; }
|
|
12821
13163
|
.vela-panel-body::-webkit-scrollbar-thumb { background: var(--vela-scroll); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
|
|
@@ -12868,6 +13210,7 @@ var CSS5 = `
|
|
|
12868
13210
|
border-left: none;
|
|
12869
13211
|
}
|
|
12870
13212
|
[data-layout='mobile'] .vela-panel-resizer { display: none; }
|
|
13213
|
+
[data-layout='mobile'] .vela-panel-max { display: none; }
|
|
12871
13214
|
`;
|
|
12872
13215
|
function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_MAX_WIDTH) {
|
|
12873
13216
|
const lo = Number.isFinite(min) && min > 0 ? min : DEFAULT_PANEL_MIN_WIDTH;
|
|
@@ -12891,6 +13234,15 @@ var SidePanel = class {
|
|
|
12891
13234
|
* never on a programmatic {@link setOverlay}, for the same reason as widths. */
|
|
12892
13235
|
this.onPlacementChange = null;
|
|
12893
13236
|
this.pin = null;
|
|
13237
|
+
this.maxButton = null;
|
|
13238
|
+
this.maximizedOn = false;
|
|
13239
|
+
/** Set while the close is announced, so a listener's own close does not re-enter. */
|
|
13240
|
+
this.closing = false;
|
|
13241
|
+
/** Open as far as the dock and the host are concerned — a closed panel can still be on
|
|
13242
|
+
* screen while its exit animation runs. */
|
|
13243
|
+
this.isOpen = false;
|
|
13244
|
+
/** The exit animation of a close; a reopen cancels it. */
|
|
13245
|
+
this.exit = null;
|
|
12894
13246
|
const doc = host.ownerDocument;
|
|
12895
13247
|
injectStyles(STYLE_ID4, CSS5, doc);
|
|
12896
13248
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
@@ -12925,7 +13277,13 @@ var SidePanel = class {
|
|
|
12925
13277
|
});
|
|
12926
13278
|
this.refreshPin();
|
|
12927
13279
|
}
|
|
12928
|
-
|
|
13280
|
+
if (opts.maximizable) {
|
|
13281
|
+
this.maxButton = doc.createElement("button");
|
|
13282
|
+
this.maxButton.className = "vela-panel-max";
|
|
13283
|
+
this.maxButton.addEventListener("click", () => this.setMaximized(!this.maximizedOn));
|
|
13284
|
+
this.refreshMaxButton();
|
|
13285
|
+
}
|
|
13286
|
+
header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], ...this.maxButton ? [this.maxButton] : [], close);
|
|
12929
13287
|
this.body = doc.createElement("div");
|
|
12930
13288
|
this.body.className = "vela-panel-body";
|
|
12931
13289
|
this.el.append(header, this.body);
|
|
@@ -12933,13 +13291,65 @@ var SidePanel = class {
|
|
|
12933
13291
|
host.appendChild(this.el);
|
|
12934
13292
|
}
|
|
12935
13293
|
get open() {
|
|
12936
|
-
return
|
|
13294
|
+
return this.isOpen;
|
|
12937
13295
|
}
|
|
12938
|
-
/**
|
|
12939
|
-
|
|
12940
|
-
|
|
12941
|
-
|
|
13296
|
+
/**
|
|
13297
|
+
* Open/close the panel — a bare call flips it. The panel reports closed at once, but a
|
|
13298
|
+
* close keeps it on screen, marked `data-closing`, while a host exit animation runs.
|
|
13299
|
+
* `instant` skips that, and also cuts short the exit of a panel already closing: a panel
|
|
13300
|
+
* handing the dock to another leaves at once.
|
|
13301
|
+
*/
|
|
13302
|
+
toggle(open2 = !this.isOpen, instant = false) {
|
|
13303
|
+
if (open2 === this.isOpen) {
|
|
13304
|
+
if (!open2 && instant) this.exit?.finish();
|
|
13305
|
+
return;
|
|
13306
|
+
}
|
|
13307
|
+
if (!open2 && this.closing) return;
|
|
13308
|
+
if (!open2) {
|
|
13309
|
+
this.closing = true;
|
|
13310
|
+
announceSurface(this.el, false, "panel");
|
|
13311
|
+
this.closing = false;
|
|
13312
|
+
}
|
|
13313
|
+
this.isOpen = open2;
|
|
13314
|
+
if (open2) {
|
|
13315
|
+
if (this.exit) {
|
|
13316
|
+
this.exit.cancel();
|
|
13317
|
+
this.exit = null;
|
|
13318
|
+
this.setMaximized(false);
|
|
13319
|
+
}
|
|
13320
|
+
this.el.hidden = false;
|
|
13321
|
+
} else {
|
|
13322
|
+
const hide = () => {
|
|
13323
|
+
this.exit = null;
|
|
13324
|
+
this.el.hidden = true;
|
|
13325
|
+
this.setMaximized(false);
|
|
13326
|
+
};
|
|
13327
|
+
if (instant) hide();
|
|
13328
|
+
else this.exit = holdForExit(this.el, hide);
|
|
13329
|
+
}
|
|
12942
13330
|
this.onOpenChange?.(open2);
|
|
13331
|
+
if (open2 && this.isOpen) announceSurface(this.el, true, "panel");
|
|
13332
|
+
}
|
|
13333
|
+
/** Whether the panel covers every chart right now. */
|
|
13334
|
+
get maximized() {
|
|
13335
|
+
return this.maximizedOn;
|
|
13336
|
+
}
|
|
13337
|
+
/** Maximize the panel over the shell's charts, or restore it. A no-op on a panel not
|
|
13338
|
+
* declared `maximizable`. */
|
|
13339
|
+
setMaximized(maximized) {
|
|
13340
|
+
if (!this.maxButton || maximized === this.maximizedOn) return;
|
|
13341
|
+
this.maximizedOn = maximized;
|
|
13342
|
+
if (maximized) this.el.dataset.maximized = "1";
|
|
13343
|
+
else delete this.el.dataset.maximized;
|
|
13344
|
+
this.refreshMaxButton();
|
|
13345
|
+
}
|
|
13346
|
+
refreshMaxButton() {
|
|
13347
|
+
if (!this.maxButton) return;
|
|
13348
|
+
const doc = this.maxButton.ownerDocument;
|
|
13349
|
+
this.maxButton.replaceChildren(iconEl(this.maximizedOn ? "restore" : "maximize", doc));
|
|
13350
|
+
this.maxButton.title = this.maximizedOn ? "Restore" : "Maximize";
|
|
13351
|
+
this.maxButton.setAttribute("aria-label", this.maxButton.title);
|
|
13352
|
+
this.maxButton.setAttribute("aria-pressed", this.maximizedOn ? "true" : "false");
|
|
12943
13353
|
}
|
|
12944
13354
|
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
12945
13355
|
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
@@ -12991,6 +13401,8 @@ var SidePanel = class {
|
|
|
12991
13401
|
this.pin.setAttribute("aria-pressed", pinned ? "true" : "false");
|
|
12992
13402
|
}
|
|
12993
13403
|
destroy() {
|
|
13404
|
+
if (this.open) announceSurface(this.el, false, "panel");
|
|
13405
|
+
this.exit?.finish();
|
|
12994
13406
|
this.el.remove();
|
|
12995
13407
|
}
|
|
12996
13408
|
/** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
|
|
@@ -13295,8 +13707,8 @@ var ObjectTree = class extends SidePanel {
|
|
|
13295
13707
|
set groups(next) {
|
|
13296
13708
|
if (this.chart) this.groupsPerChart.set(this.chart, next);
|
|
13297
13709
|
}
|
|
13298
|
-
toggle(open2 = this.
|
|
13299
|
-
super.toggle(open2);
|
|
13710
|
+
toggle(open2 = !this.open, instant = false) {
|
|
13711
|
+
super.toggle(open2, instant);
|
|
13300
13712
|
if (open2) this.refresh();
|
|
13301
13713
|
}
|
|
13302
13714
|
setSymbol(symbol) {
|
|
@@ -14330,8 +14742,8 @@ var DataWindow = class extends SidePanel {
|
|
|
14330
14742
|
this.unsubs = [];
|
|
14331
14743
|
injectStyles(STYLE_ID6, CSS7, host.ownerDocument);
|
|
14332
14744
|
}
|
|
14333
|
-
toggle(open2 = this.
|
|
14334
|
-
super.toggle(open2);
|
|
14745
|
+
toggle(open2 = !this.open, instant = false) {
|
|
14746
|
+
super.toggle(open2, instant);
|
|
14335
14747
|
if (open2) this.refresh();
|
|
14336
14748
|
}
|
|
14337
14749
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
@@ -14407,19 +14819,22 @@ var PanelDock = class {
|
|
|
14407
14819
|
/**
|
|
14408
14820
|
* (Re)build the CONTRIBUTED panels from the registry — call once after the built-ins, and
|
|
14409
14821
|
* again on `refreshActions()` so a late registration appears. Contributed panels that are
|
|
14410
|
-
* gone from the registry are dropped
|
|
14411
|
-
* descriptor
|
|
14822
|
+
* gone from the registry are dropped and a REPLACED descriptor is rebuilt; a panel whose
|
|
14823
|
+
* descriptor is unchanged stays mounted as it is, so its content and state survive.
|
|
14412
14824
|
*/
|
|
14413
14825
|
refresh() {
|
|
14414
14826
|
const openBefore = this.openId;
|
|
14415
|
-
|
|
14416
|
-
for (const
|
|
14827
|
+
const registered = sidePanels();
|
|
14828
|
+
for (const entry of [...this.entries]) if (entry.contributed && !registered.includes(entry.desc)) this.drop(entry);
|
|
14829
|
+
for (const desc of registered) {
|
|
14830
|
+
if (this.entries.some((e) => e.desc === desc)) continue;
|
|
14417
14831
|
const panel = new SidePanel(this.host, desc.title, `vela-panel-${desc.id}`, {
|
|
14418
14832
|
width: desc.width,
|
|
14419
14833
|
resizable: desc.resizable,
|
|
14420
14834
|
minWidth: desc.minWidth,
|
|
14421
14835
|
maxWidth: desc.maxWidth,
|
|
14422
|
-
overlay: desc.overlay
|
|
14836
|
+
overlay: desc.overlay,
|
|
14837
|
+
maximizable: desc.maximizable
|
|
14423
14838
|
});
|
|
14424
14839
|
const entry = {
|
|
14425
14840
|
id: desc.id,
|
|
@@ -14427,10 +14842,19 @@ var PanelDock = class {
|
|
|
14427
14842
|
icon: desc.icon,
|
|
14428
14843
|
order: desc.order ?? DEFAULT_PANEL_ORDER,
|
|
14429
14844
|
panel,
|
|
14430
|
-
contributed: true
|
|
14845
|
+
contributed: true,
|
|
14846
|
+
desc
|
|
14431
14847
|
};
|
|
14432
14848
|
try {
|
|
14433
|
-
|
|
14849
|
+
const header = {
|
|
14850
|
+
slot: panel.headerSlot,
|
|
14851
|
+
setTitle: (t) => panel.setTitle(t),
|
|
14852
|
+
get maximized() {
|
|
14853
|
+
return panel.maximized;
|
|
14854
|
+
},
|
|
14855
|
+
setMaximized: (on) => panel.setMaximized(on)
|
|
14856
|
+
};
|
|
14857
|
+
entry.handle = desc.mount(this.deps.context(), panel.content, header) ?? void 0;
|
|
14434
14858
|
if (this.chart) entry.handle?.onChart?.(this.chart);
|
|
14435
14859
|
} catch (err) {
|
|
14436
14860
|
console.warn(`[vela] side panel "${desc.id}" failed to mount`, err);
|
|
@@ -14489,7 +14913,7 @@ var PanelDock = class {
|
|
|
14489
14913
|
}
|
|
14490
14914
|
this.pinned = new Set(state.pinned ?? []);
|
|
14491
14915
|
for (const entry of this.entries) entry.panel.setOverlay(!this.pinned.has(entry.id));
|
|
14492
|
-
for (const entry of this.entries) entry.panel.toggle(entry.id === state.open);
|
|
14916
|
+
for (const entry of this.entries) entry.panel.toggle(entry.id === state.open, true);
|
|
14493
14917
|
this.pendingOpen = state.open && !this.entries.some((e) => e.id === state.open) ? state.open : null;
|
|
14494
14918
|
}
|
|
14495
14919
|
/** Drop the contributed panels (the shell destroys its own). */
|
|
@@ -14505,7 +14929,7 @@ var PanelDock = class {
|
|
|
14505
14929
|
if (this.pinned.has(entry.id)) entry.panel.setOverlay(false);
|
|
14506
14930
|
entry.panel.onOpenChange = (open2) => {
|
|
14507
14931
|
if (open2) {
|
|
14508
|
-
for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
|
|
14932
|
+
for (const other of this.entries) if (other !== entry) other.panel.toggle(false, true);
|
|
14509
14933
|
this.pendingOpen = null;
|
|
14510
14934
|
}
|
|
14511
14935
|
this.deps.chrome.setPanelActive(entry.id, open2);
|
|
@@ -16209,7 +16633,11 @@ var MoreDrawer = class {
|
|
|
16209
16633
|
row(doc, label, opts) {
|
|
16210
16634
|
const el = doc.createElement("div");
|
|
16211
16635
|
el.className = "vela-md-row";
|
|
16212
|
-
if (opts.icon)
|
|
16636
|
+
if (opts.icon) {
|
|
16637
|
+
const icon2 = iconEl(opts.icon, doc);
|
|
16638
|
+
if (opts.glyph) icon2.innerHTML = opts.glyph;
|
|
16639
|
+
el.appendChild(icon2);
|
|
16640
|
+
}
|
|
16213
16641
|
const text = doc.createElement("span");
|
|
16214
16642
|
text.className = "vela-md-row-label";
|
|
16215
16643
|
text.textContent = label;
|
|
@@ -16251,7 +16679,7 @@ var MoreDrawer = class {
|
|
|
16251
16679
|
if (this.opts.layout) {
|
|
16252
16680
|
const shape = this.opts.layout.shape();
|
|
16253
16681
|
const value = shape ? `${shape.cols} \xD7 ${shape.rows}` : this.opts.layout.presets().find((p) => p.checked)?.label;
|
|
16254
|
-
list.appendChild(this.row(doc, "Layout", { icon: "layout", value, chevron: true, onClick: () => this.show("layout") }));
|
|
16682
|
+
list.appendChild(this.row(doc, "Layout", { icon: "layout", glyph: this.opts.layout.glyph?.(), value, chevron: true, onClick: () => this.show("layout") }));
|
|
16255
16683
|
}
|
|
16256
16684
|
for (const act of this.opts.primaryActions?.() ?? []) list.appendChild(this.actionRow(doc, act));
|
|
16257
16685
|
for (const panel of this.opts.panels()) {
|
|
@@ -16429,8 +16857,8 @@ var SETTINGS_SECTION = {
|
|
|
16429
16857
|
"price-axis": "Scales and lines",
|
|
16430
16858
|
"time-axis": "Scales and lines"
|
|
16431
16859
|
};
|
|
16432
|
-
function settingsItem(zone, label) {
|
|
16433
|
-
return { id: `settings:${SETTINGS_SECTION[zone]}`, label, separatorBefore: true };
|
|
16860
|
+
function settingsItem(zone, label, icon2) {
|
|
16861
|
+
return { id: `settings:${SETTINGS_SECTION[zone]}`, label, ...icon2 ? { icon: icon2 } : {}, separatorBefore: true };
|
|
16434
16862
|
}
|
|
16435
16863
|
function settingsSectionOf(id) {
|
|
16436
16864
|
return id.slice("settings:".length) || void 0;
|
|
@@ -16510,12 +16938,39 @@ function timeAxisItems(timezone) {
|
|
|
16510
16938
|
}
|
|
16511
16939
|
function bodyItems(counts) {
|
|
16512
16940
|
return [
|
|
16513
|
-
{ id: "reset-view", label: "Reset chart view" },
|
|
16514
|
-
{ id: "remove-drawings", label: "Remove drawings", disabled: counts.drawings === 0, separatorBefore: true },
|
|
16515
|
-
{ id: "remove-indicators", label: "Remove indicators", disabled: counts.indicators === 0 },
|
|
16516
|
-
settingsItem("body", "Settings\u2026")
|
|
16941
|
+
{ id: "reset-view", label: "Reset chart view", icon: "reset" },
|
|
16942
|
+
{ id: "remove-drawings", label: "Remove drawings", icon: "eraser", disabled: counts.drawings === 0, separatorBefore: true },
|
|
16943
|
+
{ id: "remove-indicators", label: "Remove indicators", icon: "indicators", disabled: counts.indicators === 0 },
|
|
16944
|
+
settingsItem("body", "Settings\u2026", "gear")
|
|
16517
16945
|
];
|
|
16518
16946
|
}
|
|
16947
|
+
var CONTEXT_MENU_BUILTIN_ORDER = {
|
|
16948
|
+
body: { "reset-view": -30, "remove-drawings": -20, "remove-indicators": -10, settings: 1e3 },
|
|
16949
|
+
"price-axis": {
|
|
16950
|
+
auto: -80,
|
|
16951
|
+
invert: -70,
|
|
16952
|
+
"scale:regular": -60,
|
|
16953
|
+
"scale:percent": -50,
|
|
16954
|
+
"scale:indexed": -40,
|
|
16955
|
+
"scale:log": -30,
|
|
16956
|
+
labels: -20,
|
|
16957
|
+
levels: -10,
|
|
16958
|
+
settings: 1e3
|
|
16959
|
+
},
|
|
16960
|
+
"time-axis": { timezone: -10, settings: 1e3 }
|
|
16961
|
+
};
|
|
16962
|
+
function composeMenu(zone, builtin, contributed) {
|
|
16963
|
+
const ranks = CONTEXT_MENU_BUILTIN_ORDER[zone];
|
|
16964
|
+
const rows = [];
|
|
16965
|
+
let group = 0;
|
|
16966
|
+
for (const item of builtin) {
|
|
16967
|
+
if (item.separatorBefore) group += 1;
|
|
16968
|
+
rows.push({ item, rank: ranks[item.id.startsWith("settings") ? "settings" : item.id] ?? 0, group });
|
|
16969
|
+
}
|
|
16970
|
+
for (const { item, order } of contributed) rows.push({ item, rank: order ?? 0, group: -1 });
|
|
16971
|
+
rows.sort((a, b) => a.rank - b.rank);
|
|
16972
|
+
return rows.map(({ item, group: g }, i) => ({ ...item, separatorBefore: i > 0 && rows[i - 1].group !== g }));
|
|
16973
|
+
}
|
|
16519
16974
|
|
|
16520
16975
|
// src/widget/price-scale-drawer.ts
|
|
16521
16976
|
var STYLE_ID17 = "vela-widget-price-scale-drawer";
|
|
@@ -17790,6 +18245,7 @@ function chromeHint(text, opts) {
|
|
|
17790
18245
|
// src/renderers/native/drawings/DrawingToolbar.ts
|
|
17791
18246
|
var TOOLBAR_WIDTH = 44;
|
|
17792
18247
|
var TOOLBAR_COLLAPSED_WIDTH = 16;
|
|
18248
|
+
var FLYOUT_GAP = 3;
|
|
17793
18249
|
var DrawingToolbar = class {
|
|
17794
18250
|
constructor(host, theme, onArm, onMagnet = () => {
|
|
17795
18251
|
}, onMeasure = () => {
|
|
@@ -17819,6 +18275,9 @@ var DrawingToolbar = class {
|
|
|
17819
18275
|
// group id (or MAGNET_ID) whose flyout is open
|
|
17820
18276
|
this.flyoutCell = null;
|
|
17821
18277
|
// the cell the open flyout is anchored to
|
|
18278
|
+
/** Closed flyouts still playing their exit animation, by owner id — reopening the same
|
|
18279
|
+
* owner takes its element back instead of stacking a second one on it. */
|
|
18280
|
+
this.exitingFlyouts = /* @__PURE__ */ new Map();
|
|
17822
18281
|
this.groupCells = /* @__PURE__ */ new Map();
|
|
17823
18282
|
// the composite cell (hover/active bg + flyout anchor)
|
|
17824
18283
|
this.groupIcons = /* @__PURE__ */ new Map();
|
|
@@ -17927,6 +18386,7 @@ var DrawingToolbar = class {
|
|
|
17927
18386
|
}
|
|
17928
18387
|
destroy() {
|
|
17929
18388
|
this.closeFlyout();
|
|
18389
|
+
for (const { exit } of [...this.exitingFlyouts.values()]) exit.finish();
|
|
17930
18390
|
for (const dispose of this.tipDisposers.splice(0)) dispose();
|
|
17931
18391
|
this.root.remove();
|
|
17932
18392
|
}
|
|
@@ -18154,16 +18614,22 @@ var DrawingToolbar = class {
|
|
|
18154
18614
|
* outside-dismiss. Callers fill it with items. */
|
|
18155
18615
|
beginFlyout(ownerId, cell) {
|
|
18156
18616
|
const t = this.theme;
|
|
18157
|
-
const
|
|
18617
|
+
const back = this.exitingFlyouts.get(ownerId);
|
|
18618
|
+
if (back) {
|
|
18619
|
+
this.exitingFlyouts.delete(ownerId);
|
|
18620
|
+
back.exit.cancel();
|
|
18621
|
+
back.fly.replaceChildren();
|
|
18622
|
+
}
|
|
18623
|
+
const fly = back?.fly ?? document.createElement("div");
|
|
18158
18624
|
fly.className = "vela-dtb-flyout";
|
|
18159
|
-
fly.style.cssText = `position:absolute;z-index:23;display:flex;flex-direction:column;gap:2px;padding:4px;border-radius:
|
|
18625
|
+
fly.style.cssText = `position:absolute;z-index:23;display:flex;flex-direction:column;gap:2px;padding:4px;border-radius:var(--vela-radius-md);background:var(--vela-surface);border:1px solid ${this.borderOverride ?? "var(--vela-border-strong)"};box-shadow:var(--vela-shadow);pointer-events:auto;overflow-y:auto;overscroll-behavior:contain;`;
|
|
18160
18626
|
applyChromeTokens(fly, t);
|
|
18161
18627
|
this.host.appendChild(fly);
|
|
18162
18628
|
const r = cell.getBoundingClientRect();
|
|
18163
18629
|
const rootR = this.root.getBoundingClientRect();
|
|
18164
18630
|
const hostR = this.host.getBoundingClientRect();
|
|
18165
18631
|
const top = r.top - hostR.top;
|
|
18166
|
-
fly.style.left = `${rootR.right - hostR.left
|
|
18632
|
+
fly.style.left = `${rootR.right - hostR.left + FLYOUT_GAP}px`;
|
|
18167
18633
|
fly.style.top = `${top}px`;
|
|
18168
18634
|
fly.style.maxHeight = `${Math.max(120, hostR.height - top - 8)}px`;
|
|
18169
18635
|
this.flyout = fly;
|
|
@@ -18179,6 +18645,7 @@ var DrawingToolbar = class {
|
|
|
18179
18645
|
if (!cell) return;
|
|
18180
18646
|
const fly = this.beginFlyout(group.id, cell);
|
|
18181
18647
|
const sections = group.sections ?? [{ label: "", tools: group.tools }];
|
|
18648
|
+
const badge = sections.some((s) => s.tools.some((tool) => !!tool.icon));
|
|
18182
18649
|
for (let si = 0; si < sections.length; si++) {
|
|
18183
18650
|
const section = sections[si];
|
|
18184
18651
|
if (section.label) {
|
|
@@ -18189,6 +18656,7 @@ var DrawingToolbar = class {
|
|
|
18189
18656
|
fly.appendChild(
|
|
18190
18657
|
this.makeFlyoutItem({
|
|
18191
18658
|
icon: tool.icon,
|
|
18659
|
+
badge,
|
|
18192
18660
|
label: tool.label,
|
|
18193
18661
|
selected: tool.type === this.active,
|
|
18194
18662
|
shortcut: this.shortcuts.get(tool.type),
|
|
@@ -18201,6 +18669,7 @@ var DrawingToolbar = class {
|
|
|
18201
18669
|
);
|
|
18202
18670
|
}
|
|
18203
18671
|
}
|
|
18672
|
+
announceSurface(fly, true, "menu", cell);
|
|
18204
18673
|
}
|
|
18205
18674
|
openMagnetFlyout() {
|
|
18206
18675
|
const cell = this.magnetCell;
|
|
@@ -18224,6 +18693,7 @@ var DrawingToolbar = class {
|
|
|
18224
18693
|
})
|
|
18225
18694
|
);
|
|
18226
18695
|
}
|
|
18696
|
+
announceSurface(fly, true, "menu", cell);
|
|
18227
18697
|
}
|
|
18228
18698
|
flyoutHeader(text) {
|
|
18229
18699
|
const t = this.theme;
|
|
@@ -18237,21 +18707,24 @@ var DrawingToolbar = class {
|
|
|
18237
18707
|
div.style.cssText = `height:1px;margin:4px 8px;background:${this.borderColor};`;
|
|
18238
18708
|
return div;
|
|
18239
18709
|
}
|
|
18240
|
-
/** A flyout row, left to right: optional leading icon, label, a check when it's the
|
|
18241
|
-
* entry, an optional shortcut hint, and — at the far right — the favorite star (tool
|
|
18242
|
-
* Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
|
|
18710
|
+
/** A flyout row, left to right: optional leading icon badge, label, a check when it's the
|
|
18711
|
+
* selected entry, an optional shortcut hint, and — at the far right — the favorite star (tool
|
|
18712
|
+
* rows). Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
|
|
18243
18713
|
makeFlyoutItem(opts) {
|
|
18244
18714
|
const t = this.theme;
|
|
18245
18715
|
const item = document.createElement("button");
|
|
18246
18716
|
item.type = "button";
|
|
18247
18717
|
item.className = "vela-dtb-item";
|
|
18248
18718
|
item.setAttribute("aria-label", opts.label);
|
|
18249
|
-
|
|
18250
|
-
|
|
18251
|
-
|
|
18252
|
-
|
|
18253
|
-
|
|
18254
|
-
|
|
18719
|
+
const badged = opts.badge === true || !!opts.icon;
|
|
18720
|
+
const box = badged ? "gap:10px;padding:4px 10px 4px 6px;" : "gap:8px;padding:5px 10px 5px 8px;";
|
|
18721
|
+
item.style.cssText = `display:flex;align-items:center;${box}cursor:pointer;color:${t.textColor};border-radius:var(--vela-radius-sm);font:13px ${t.fontFamily};white-space:nowrap;min-width:148px;`;
|
|
18722
|
+
if (opts.selected) item.dataset.selected = "1";
|
|
18723
|
+
if (badged) {
|
|
18724
|
+
const badge = document.createElement("span");
|
|
18725
|
+
badge.className = "vela-dtb-badge";
|
|
18726
|
+
if (opts.icon) badge.innerHTML = opts.icon;
|
|
18727
|
+
item.appendChild(badge);
|
|
18255
18728
|
}
|
|
18256
18729
|
const label = document.createElement("span");
|
|
18257
18730
|
label.textContent = opts.label;
|
|
@@ -18292,9 +18765,16 @@ var DrawingToolbar = class {
|
|
|
18292
18765
|
}
|
|
18293
18766
|
closeFlyout() {
|
|
18294
18767
|
this.starEls.clear();
|
|
18295
|
-
|
|
18296
|
-
|
|
18768
|
+
const fly = this.flyout;
|
|
18769
|
+
if (fly) {
|
|
18297
18770
|
this.flyout = null;
|
|
18771
|
+
announceSurface(fly, false, "menu", this.flyoutCell);
|
|
18772
|
+
const owner = this.flyoutOwnerId ?? "";
|
|
18773
|
+
const exit = holdForExit(fly, () => {
|
|
18774
|
+
if (this.exitingFlyouts.get(owner)?.fly === fly) this.exitingFlyouts.delete(owner);
|
|
18775
|
+
fly.remove();
|
|
18776
|
+
});
|
|
18777
|
+
if (exit) this.exitingFlyouts.set(owner, { fly, exit });
|
|
18298
18778
|
this.flyoutCell?.classList.remove("vela-open");
|
|
18299
18779
|
this.flyoutCell = null;
|
|
18300
18780
|
this.flyoutOwnerId = null;
|
|
@@ -18378,6 +18858,11 @@ function ensureStyles() {
|
|
|
18378
18858
|
.vela-dtb[data-collapsed='1'] .vela-dtb-collapse .vela-dtb-hit{width:14px;}
|
|
18379
18859
|
.vela-dtb-item{background:transparent;border:none;transition:background var(--vela-dur-fast) ease;}
|
|
18380
18860
|
.vela-dtb-item:hover{background:var(--vela-hover-strong);}
|
|
18861
|
+
.vela-dtb-badge{width:24px;height:24px;padding:3px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;flex:none;border-radius:var(--vela-radius-md);background:var(--vela-hover);border:1px solid var(--vela-border);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
18862
|
+
.vela-dtb-badge:empty{background:transparent;border-color:transparent;}
|
|
18863
|
+
.vela-dtb-badge svg{width:100%;height:100%;}
|
|
18864
|
+
.vela-dtb-item:hover .vela-dtb-badge:not(:empty){background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
18865
|
+
.vela-dtb-item[data-selected='1'] .vela-dtb-badge:not(:empty){background:color-mix(in srgb,var(--vela-fg-bright) 83%,var(--vela-surface));border-color:color-mix(in srgb,var(--vela-fg-bright) 40%,transparent);color:var(--vela-surface);}
|
|
18381
18866
|
.vela-dtb-star{width:26px;height:22px;margin:-3px -5px -3px 0;padding:3px 5px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;flex:none;opacity:0;color:inherit;border-radius:var(--vela-radius-sm);transition:opacity .1s ease,color .1s ease,background .1s ease;}
|
|
18382
18867
|
.vela-dtb-star svg{width:16px;height:16px;}
|
|
18383
18868
|
.vela-dtb-item:hover .vela-dtb-star{opacity:.55;}
|
|
@@ -18745,15 +19230,48 @@ function syncTargets(originId, setting, cellIds) {
|
|
|
18745
19230
|
function rangesWithin(a, b, epsMs) {
|
|
18746
19231
|
return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
|
|
18747
19232
|
}
|
|
18748
|
-
var STYLE_SYNC_CONFIG_KEYS = [
|
|
18749
|
-
|
|
18750
|
-
|
|
18751
|
-
|
|
18752
|
-
|
|
19233
|
+
var STYLE_SYNC_CONFIG_KEYS = [
|
|
19234
|
+
"layout",
|
|
19235
|
+
"panes",
|
|
19236
|
+
"grid",
|
|
19237
|
+
"margins",
|
|
19238
|
+
"priceScale",
|
|
19239
|
+
"crosshair",
|
|
19240
|
+
"animations",
|
|
19241
|
+
"candles",
|
|
19242
|
+
"bars",
|
|
19243
|
+
"line",
|
|
19244
|
+
"area",
|
|
19245
|
+
"baseline",
|
|
19246
|
+
"sessions"
|
|
19247
|
+
];
|
|
19248
|
+
function styleConfigPatch(origin, follower) {
|
|
19249
|
+
const from = asRecord(origin);
|
|
19250
|
+
const to = asRecord(follower);
|
|
19251
|
+
if (!from || !to) return null;
|
|
19252
|
+
const patch = {};
|
|
18753
19253
|
for (const key of STYLE_SYNC_CONFIG_KEYS) {
|
|
18754
|
-
|
|
19254
|
+
const block = asRecord(from[key]);
|
|
19255
|
+
if (block && JSON.stringify(block) !== JSON.stringify(to[key])) patch[key] = block;
|
|
18755
19256
|
}
|
|
18756
|
-
|
|
19257
|
+
const spacing = asRecord(from.series)?.spacing;
|
|
19258
|
+
if (typeof spacing === "number" && spacing !== asRecord(to.series)?.spacing) patch.series = { spacing };
|
|
19259
|
+
const fromTypes = asRecord(from.chartTypes) ?? {};
|
|
19260
|
+
const toTypes = asRecord(to.chartTypes) ?? {};
|
|
19261
|
+
const types = {};
|
|
19262
|
+
for (const typeId of /* @__PURE__ */ new Set([...Object.keys(fromTypes), ...Object.keys(toTypes)])) {
|
|
19263
|
+
const src = asRecord(fromTypes[typeId]) ?? {};
|
|
19264
|
+
const dst = asRecord(toTypes[typeId]) ?? {};
|
|
19265
|
+
for (const key of CANDLE_OVERRIDE_KEYS) {
|
|
19266
|
+
const value = src[key] ?? null;
|
|
19267
|
+
if (value !== (dst[key] ?? null)) (types[typeId] ?? (types[typeId] = {}))[key] = value;
|
|
19268
|
+
}
|
|
19269
|
+
}
|
|
19270
|
+
if (Object.keys(types).length > 0) patch.chartTypes = types;
|
|
19271
|
+
return Object.keys(patch).length > 0 ? patch : null;
|
|
19272
|
+
}
|
|
19273
|
+
function asRecord(v) {
|
|
19274
|
+
return v != null && typeof v === "object" ? v : null;
|
|
18757
19275
|
}
|
|
18758
19276
|
|
|
18759
19277
|
// src/widget/persist.ts
|
|
@@ -18949,7 +19467,7 @@ var IndicatorHandleImpl = class {
|
|
|
18949
19467
|
this.controller.applyProps(this.id, values);
|
|
18950
19468
|
}
|
|
18951
19469
|
updateCode(source) {
|
|
18952
|
-
this.controller.updateCode(this.id, source);
|
|
19470
|
+
return this.controller.updateCode(this.id, source);
|
|
18953
19471
|
}
|
|
18954
19472
|
setVisible(visible) {
|
|
18955
19473
|
this.controller.setVisible(this.id, visible);
|
|
@@ -19428,6 +19946,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19428
19946
|
this.activeEngineStyle = null;
|
|
19429
19947
|
/** The chart's current price style (tracked from the renderer's change events + initial read). */
|
|
19430
19948
|
this.priceStyle = "candles";
|
|
19949
|
+
this.priceStyleWillChangeUnsub = null;
|
|
19431
19950
|
this.unresolvedUnsub = null;
|
|
19432
19951
|
/** Latest chart visible range (left/right bar times), fed to viewport-dependent scripts. */
|
|
19433
19952
|
this.visibleRange = null;
|
|
@@ -19526,6 +20045,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19526
20045
|
this.renderer.onThemeSelect?.((name) => this.setTheme(resolveTheme(name)));
|
|
19527
20046
|
this.viewportUnsub = this.renderer.onViewportChange((range) => this.onViewportChange(range));
|
|
19528
20047
|
this.renderer.onPriceStyleChange?.((style) => this.syncPriceStyle(style));
|
|
20048
|
+
this.priceStyleWillChangeUnsub = this.renderer.onPriceStyleWillChange?.((from, to) => this.events.emit("priceStyle:change", { from, to })) ?? null;
|
|
19529
20049
|
const initialStyle = this.renderer.readFeature("priceStyle");
|
|
19530
20050
|
if (typeof initialStyle === "string") this.priceStyle = initialStyle;
|
|
19531
20051
|
this.barTransform = barTransformFor(initialStyle);
|
|
@@ -20812,20 +21332,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20812
21332
|
/**
|
|
20813
21333
|
* IndicatorController: replace a SCRIPT indicator's source in place (see
|
|
20814
21334
|
* {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
|
|
20815
|
-
* computing until the new source has compiled
|
|
20816
|
-
*
|
|
21335
|
+
* computing until the new source has compiled; then the new code runs on TRIAL until
|
|
21336
|
+
* its first model lands, and a failure before that puts the previous code back.
|
|
21337
|
+
* Natives have no script — warn and leave them alone.
|
|
20817
21338
|
*/
|
|
20818
21339
|
updateCode(id, source) {
|
|
20819
21340
|
const record = this.registry.get(id);
|
|
20820
21341
|
const handle = this.handles.get(id);
|
|
20821
|
-
if (!record || !handle) return;
|
|
21342
|
+
if (!record || !handle) return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator is no longer on the chart.`) });
|
|
20822
21343
|
if (record.native) {
|
|
20823
21344
|
console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
|
|
20824
|
-
return;
|
|
21345
|
+
return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`) });
|
|
20825
21346
|
}
|
|
20826
|
-
|
|
20827
|
-
record.
|
|
20828
|
-
|
|
21347
|
+
const busy = record.pendingSource !== void 0 || record.codeTrial !== void 0;
|
|
21348
|
+
if (!busy && source === record.source) return Promise.resolve({ ok: true, error: null });
|
|
21349
|
+
return new Promise((resolve) => {
|
|
21350
|
+
(record.codeWaiters ?? (record.codeWaiters = [])).push(resolve);
|
|
21351
|
+
if (record.pendingSource === source || record.pendingSource === void 0 && record.codeTrial && record.source === source) return;
|
|
21352
|
+
record.pendingSource = source;
|
|
21353
|
+
void this.swapSource(id, source, handle);
|
|
21354
|
+
});
|
|
20829
21355
|
}
|
|
20830
21356
|
async swapSource(id, source, handle) {
|
|
20831
21357
|
const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
|
|
@@ -20836,26 +21362,41 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20836
21362
|
const record2 = this.registry.get(id);
|
|
20837
21363
|
if (record2?.pendingSource === source) {
|
|
20838
21364
|
record2.pendingSource = void 0;
|
|
20839
|
-
this.
|
|
21365
|
+
this.rejectCode(id, record2, handle, err);
|
|
20840
21366
|
}
|
|
20841
21367
|
return;
|
|
20842
21368
|
}
|
|
20843
21369
|
const record = this.registry.get(id);
|
|
20844
21370
|
if (!record || record.pendingSource !== source) return;
|
|
20845
21371
|
record.pendingSource = void 0;
|
|
21372
|
+
const previous = record.codeTrial?.previous ?? {
|
|
21373
|
+
source: record.source,
|
|
21374
|
+
engine: record.engine,
|
|
21375
|
+
prepared: record.prepared,
|
|
21376
|
+
inputValues: record.inputValues,
|
|
21377
|
+
propValues: record.propValues,
|
|
21378
|
+
inputs: [...handle.inputs],
|
|
21379
|
+
props: [...handle.props]
|
|
21380
|
+
};
|
|
21381
|
+
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues, record.prepared?.inputs);
|
|
21382
|
+
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues, record.prepared?.props);
|
|
20846
21383
|
record.source = source;
|
|
20847
21384
|
record.engine = engine;
|
|
20848
21385
|
record.prepared = prepared;
|
|
20849
21386
|
handle.setSource(source);
|
|
20850
|
-
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
|
|
20851
|
-
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
|
|
20852
21387
|
handle.setSchema(prepared.inputs);
|
|
20853
21388
|
handle.setPropsSchema(prepared.props ?? []);
|
|
20854
21389
|
record.session?.stop();
|
|
20855
21390
|
record.session = void 0;
|
|
20856
21391
|
record.pendingStructural = true;
|
|
20857
21392
|
record.pendingCause = "code";
|
|
20858
|
-
if (record.hidden)
|
|
21393
|
+
if (record.hidden) {
|
|
21394
|
+
record.codeTrial = void 0;
|
|
21395
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
21396
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21397
|
+
return;
|
|
21398
|
+
}
|
|
21399
|
+
record.codeTrial = { previous };
|
|
20859
21400
|
if (record.renderHandle) {
|
|
20860
21401
|
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
20861
21402
|
this.setLoading(record, true);
|
|
@@ -20863,6 +21404,46 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20863
21404
|
this.mountLoadingPlaceholder(id, record);
|
|
20864
21405
|
}
|
|
20865
21406
|
this.executeIndicator(id, handle);
|
|
21407
|
+
if (this.bars.length === 0 && record.codeTrial) {
|
|
21408
|
+
record.codeTrial = void 0;
|
|
21409
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21410
|
+
}
|
|
21411
|
+
}
|
|
21412
|
+
/** Resolve every `updateCode` caller waiting on this record's chain of edits. */
|
|
21413
|
+
settleCode(record, result) {
|
|
21414
|
+
const waiters = record.codeWaiters;
|
|
21415
|
+
record.codeWaiters = void 0;
|
|
21416
|
+
if (waiters) for (const resolve of waiters) resolve(result);
|
|
21417
|
+
}
|
|
21418
|
+
/**
|
|
21419
|
+
* An update failed — at prepare, or on trial before its first model. A trial puts the
|
|
21420
|
+
* previous code back (source, engine, prepared script, values, schema) and restarts it;
|
|
21421
|
+
* its visuals were never dropped, so the chart keeps painting throughout. Then the
|
|
21422
|
+
* failure reports the usual way and every waiting caller learns it.
|
|
21423
|
+
*/
|
|
21424
|
+
rejectCode(id, record, handle, err) {
|
|
21425
|
+
const trial = record.codeTrial;
|
|
21426
|
+
record.codeTrial = void 0;
|
|
21427
|
+
this.fail(id, handle, err);
|
|
21428
|
+
if (trial) {
|
|
21429
|
+
const p = trial.previous;
|
|
21430
|
+
record.session?.stop();
|
|
21431
|
+
record.session = void 0;
|
|
21432
|
+
record.source = p.source;
|
|
21433
|
+
record.engine = p.engine;
|
|
21434
|
+
record.prepared = p.prepared;
|
|
21435
|
+
record.inputValues = p.inputValues;
|
|
21436
|
+
record.propValues = p.propValues;
|
|
21437
|
+
handle.setSource(p.source);
|
|
21438
|
+
handle.setSchema(p.inputs);
|
|
21439
|
+
handle.setPropsSchema(p.props);
|
|
21440
|
+
record.pendingStructural = true;
|
|
21441
|
+
record.pendingCause = void 0;
|
|
21442
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
21443
|
+
if (!record.hidden) this.executeIndicator(id, handle);
|
|
21444
|
+
}
|
|
21445
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
21446
|
+
this.settleCode(record, { ok: false, error });
|
|
20866
21447
|
}
|
|
20867
21448
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
20868
21449
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
@@ -20877,6 +21458,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20877
21458
|
removeIndicator(id) {
|
|
20878
21459
|
const record = this.registry.remove(id);
|
|
20879
21460
|
if (record?.native?.type === "volume") this.volumeOptedOut = true;
|
|
21461
|
+
if (record) this.settleCode(record, { ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator was removed.`) });
|
|
20880
21462
|
record?.session?.stop();
|
|
20881
21463
|
if (record?.native?.started) record.native.instance.stop();
|
|
20882
21464
|
this.handles.delete(id);
|
|
@@ -20904,6 +21486,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20904
21486
|
if (!visible) {
|
|
20905
21487
|
record.session?.stop();
|
|
20906
21488
|
record.session = void 0;
|
|
21489
|
+
if (record.codeTrial) {
|
|
21490
|
+
record.codeTrial = void 0;
|
|
21491
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21492
|
+
}
|
|
20907
21493
|
if (record.native?.started) record.native.instance.suspend();
|
|
20908
21494
|
if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
|
|
20909
21495
|
else if (record.native) this.mountHiddenNativeRow(id, record);
|
|
@@ -20990,7 +21576,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20990
21576
|
if (this.viewportTimer != null) clearTimeout(this.viewportTimer);
|
|
20991
21577
|
this.viewportUnsub?.();
|
|
20992
21578
|
this.paneActionUnsub?.();
|
|
21579
|
+
this.priceStyleWillChangeUnsub?.();
|
|
21580
|
+
this.priceStyleWillChangeUnsub = null;
|
|
20993
21581
|
for (const record of this.registry.all()) {
|
|
21582
|
+
this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
|
|
20994
21583
|
record.session?.stop();
|
|
20995
21584
|
if (record.native?.started) record.native.instance.stop();
|
|
20996
21585
|
}
|
|
@@ -21059,7 +21648,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21059
21648
|
const record = this.registry.get(id);
|
|
21060
21649
|
if (!record || !record.engine || !record.prepared || record.hidden) return;
|
|
21061
21650
|
const mode = this.config.live && !record.prepared.reactsToViewport && record.engine.capabilities.streaming ? "live" : "static";
|
|
21062
|
-
|
|
21651
|
+
const prepared = record.prepared;
|
|
21652
|
+
const session = record.engine.execute(
|
|
21063
21653
|
{
|
|
21064
21654
|
prepared: record.prepared,
|
|
21065
21655
|
market: this.market(),
|
|
@@ -21080,6 +21670,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21080
21670
|
const cause = record.pendingCause ?? "history";
|
|
21081
21671
|
if (!this.applyModel(id, model)) return;
|
|
21082
21672
|
record.pendingCause = void 0;
|
|
21673
|
+
if (record.codeTrial) {
|
|
21674
|
+
record.codeTrial = void 0;
|
|
21675
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21676
|
+
}
|
|
21083
21677
|
this.emitContextChanged(id);
|
|
21084
21678
|
this.emitScriptRun(id, cause, first);
|
|
21085
21679
|
},
|
|
@@ -21090,9 +21684,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21090
21684
|
handle.emit("alert", { id: a.id, message: a.message, title: a.title, time: a.time });
|
|
21091
21685
|
},
|
|
21092
21686
|
onWarning: (w) => this.events.emit("warning", w),
|
|
21093
|
-
onError: (err) =>
|
|
21687
|
+
onError: (err) => {
|
|
21688
|
+
if (record.codeTrial) this.rejectCode(id, record, handle, err);
|
|
21689
|
+
else this.fail(id, handle, err);
|
|
21690
|
+
}
|
|
21094
21691
|
}
|
|
21095
21692
|
);
|
|
21693
|
+
if (record.prepared !== prepared) {
|
|
21694
|
+
session.stop();
|
|
21695
|
+
return;
|
|
21696
|
+
}
|
|
21697
|
+
record.session = session;
|
|
21096
21698
|
this.emitContextChanged(id);
|
|
21097
21699
|
}
|
|
21098
21700
|
/**
|
|
@@ -21593,7 +22195,7 @@ function yieldToPaint() {
|
|
|
21593
22195
|
}
|
|
21594
22196
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
21595
22197
|
}
|
|
21596
|
-
function valuesOnSchema(schema, previous) {
|
|
22198
|
+
function valuesOnSchema(schema, previous, oldSchema = []) {
|
|
21597
22199
|
const out = {};
|
|
21598
22200
|
const declared = /* @__PURE__ */ new Set();
|
|
21599
22201
|
for (const s of schema) {
|
|
@@ -21601,9 +22203,22 @@ function valuesOnSchema(schema, previous) {
|
|
|
21601
22203
|
declared.add(s.key);
|
|
21602
22204
|
declared.add(s.title);
|
|
21603
22205
|
}
|
|
21604
|
-
|
|
22206
|
+
const oldDefaults = /* @__PURE__ */ new Map();
|
|
22207
|
+
for (const s of oldSchema) {
|
|
22208
|
+
oldDefaults.set(s.key, s.defval);
|
|
22209
|
+
if (!oldDefaults.has(s.title)) oldDefaults.set(s.title, s.defval);
|
|
22210
|
+
}
|
|
22211
|
+
for (const [k, v] of Object.entries(previous)) {
|
|
22212
|
+
if (!declared.has(k)) continue;
|
|
22213
|
+
if (oldDefaults.has(k) && sameInputValue(oldDefaults.get(k), v)) continue;
|
|
22214
|
+
out[k] = v;
|
|
22215
|
+
}
|
|
21605
22216
|
return out;
|
|
21606
22217
|
}
|
|
22218
|
+
function sameInputValue(a, b) {
|
|
22219
|
+
if (a === b) return true;
|
|
22220
|
+
return typeof a === "object" && a !== null && typeof b === "object" && b !== null && JSON.stringify(a) === JSON.stringify(b);
|
|
22221
|
+
}
|
|
21607
22222
|
function blankedModel(model) {
|
|
21608
22223
|
return {
|
|
21609
22224
|
...model,
|
|
@@ -23168,7 +23783,10 @@ var LEGEND_ICON_PX2 = 16;
|
|
|
23168
23783
|
var LEGEND_CTL_PX = 18;
|
|
23169
23784
|
var LEGEND_ROW_PAD_Y = 2;
|
|
23170
23785
|
var LEGEND_ROW_PAD_X = 6;
|
|
23786
|
+
var LEGEND_LEFT_PX = 10;
|
|
23787
|
+
var LEGEND_SCALE_GAP_PX = 6;
|
|
23171
23788
|
var LEGEND_TITLE_VALUES_GAP_PX = 6;
|
|
23789
|
+
var LEGEND_VALUES_GAP_PX = 5;
|
|
23172
23790
|
var LEGEND_TITLE_STATUS_GAP_PX = 8;
|
|
23173
23791
|
var LEGEND_ACTION_GAP_PX = 6;
|
|
23174
23792
|
var LEGEND_CTL_CSS = `cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border:none;line-height:0;padding:0;width:${LEGEND_CTL_PX}px;height:${LEGEND_CTL_PX}px;border-radius:3px;box-sizing:border-box;flex:none;`;
|
|
@@ -23454,14 +24072,15 @@ var InputsUI = class {
|
|
|
23454
24072
|
row.valuesEl.replaceChildren();
|
|
23455
24073
|
return;
|
|
23456
24074
|
}
|
|
23457
|
-
row.valuesEl.style.display = "
|
|
24075
|
+
row.valuesEl.style.display = "block";
|
|
23458
24076
|
row.valuesEl.replaceChildren();
|
|
23459
|
-
|
|
24077
|
+
row.plotValues.forEach((v, i) => {
|
|
23460
24078
|
const span = document.createElement("span");
|
|
23461
24079
|
span.textContent = v.value;
|
|
23462
24080
|
span.style.color = v.color;
|
|
24081
|
+
if (i > 0) span.style.marginLeft = `${LEGEND_VALUES_GAP_PX}px`;
|
|
23463
24082
|
row.valuesEl.appendChild(span);
|
|
23464
|
-
}
|
|
24083
|
+
});
|
|
23465
24084
|
}
|
|
23466
24085
|
// ── legend move/merge (menu + drag) ─────────────────────────────────────
|
|
23467
24086
|
/** Open the "Move to" menu for a row, anchored under its move button. */
|
|
@@ -23562,7 +24181,7 @@ var InputsUI = class {
|
|
|
23562
24181
|
if (!lg) {
|
|
23563
24182
|
lg = document.createElement("div");
|
|
23564
24183
|
lg.dataset.velaPane = paneId;
|
|
23565
|
-
lg.style.cssText =
|
|
24184
|
+
lg.style.cssText = `position:absolute;left:${LEGEND_LEFT_PX}px;z-index:5;display:flex;flex-direction:column;align-items:flex-start;gap:0;pointer-events:none;font:12px -apple-system,Segoe UI,sans-serif;`;
|
|
23566
24185
|
applyChromeTokens(lg, this.theme);
|
|
23567
24186
|
this.positionLegend(lg, paneId);
|
|
23568
24187
|
this.container.appendChild(lg);
|
|
@@ -23574,6 +24193,7 @@ var InputsUI = class {
|
|
|
23574
24193
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
23575
24194
|
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
23576
24195
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
24196
|
+
lg.style.maxWidth = `calc(100% - ${(bounds.rightAxis ?? 0) + LEGEND_LEFT_PX + LEGEND_SCALE_GAP_PX}px)`;
|
|
23577
24197
|
const collapsed = this.paneCollapse.has(paneId);
|
|
23578
24198
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
23579
24199
|
for (const row of this.rows.values()) {
|
|
@@ -23665,7 +24285,7 @@ var InputsUI = class {
|
|
|
23665
24285
|
}
|
|
23666
24286
|
ensureDialogStyles();
|
|
23667
24287
|
const el = document.createElement("div");
|
|
23668
|
-
el.style.cssText = `pointer-events:auto;display:flex;align-items:center;background:${this.idleRowFill()};border-radius:4px;padding:${LEGEND_ROW_PAD_Y}px ${LEGEND_ROW_PAD_X}px;margin-left:-${LEGEND_ROW_PAD_X}px;min-height:${LEGEND_ROW_MIN_H}px;box-sizing:border-box;color:${this.theme.textColor};user-select:none;-webkit-user-select:none;`;
|
|
24288
|
+
el.style.cssText = `pointer-events:auto;display:flex;align-items:center;background:${this.idleRowFill()};border-radius:4px;padding:${LEGEND_ROW_PAD_Y}px ${LEGEND_ROW_PAD_X}px;margin-left:-${LEGEND_ROW_PAD_X}px;max-width:calc(100% + ${LEGEND_ROW_PAD_X}px);min-height:${LEGEND_ROW_MIN_H}px;box-sizing:border-box;color:${this.theme.textColor};user-select:none;-webkit-user-select:none;`;
|
|
23669
24289
|
el.addEventListener("mouseleave", () => {
|
|
23670
24290
|
if (this.selectedId !== id) this.setRowHighlighted(id, false);
|
|
23671
24291
|
});
|
|
@@ -23693,7 +24313,7 @@ var InputsUI = class {
|
|
|
23693
24313
|
const statusEl = document.createElement("span");
|
|
23694
24314
|
statusEl.style.cssText = "display:none;box-sizing:border-box;flex:none;";
|
|
23695
24315
|
const titleWrap = document.createElement("span");
|
|
23696
|
-
titleWrap.style.cssText = "white-space:nowrap;";
|
|
24316
|
+
titleWrap.style.cssText = "white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis;";
|
|
23697
24317
|
titleWrap.addEventListener("mouseenter", () => this.setRowHighlighted(id, true));
|
|
23698
24318
|
const titleEl = document.createElement("span");
|
|
23699
24319
|
titleEl.textContent = title;
|
|
@@ -23711,7 +24331,7 @@ var InputsUI = class {
|
|
|
23711
24331
|
el.appendChild(calloutsEl);
|
|
23712
24332
|
el.appendChild(statusEl);
|
|
23713
24333
|
const valuesEl = document.createElement("span");
|
|
23714
|
-
valuesEl.style.cssText = `display:none;
|
|
24334
|
+
valuesEl.style.cssText = `display:none;flex:1 1 0%;min-width:0;overflow:hidden;text-overflow:ellipsis;margin-left:${LEGEND_TITLE_VALUES_GAP_PX}px;white-space:nowrap;font-variant-numeric:tabular-nums;`;
|
|
23715
24335
|
el.appendChild(valuesEl);
|
|
23716
24336
|
const controlsEl = document.createElement("span");
|
|
23717
24337
|
controlsEl.style.cssText = `display:none;align-items:center;gap:${LEGEND_ACTION_GAP_PX}px;flex:none;margin-left:${LEGEND_ACTION_GAP_PX}px;`;
|
|
@@ -24533,15 +25153,23 @@ var Batch = class {
|
|
|
24533
25153
|
this.seg(x + w, y + h, x, y + h, lineWidth, c);
|
|
24534
25154
|
this.seg(x, y + h, x, y, lineWidth, c);
|
|
24535
25155
|
}
|
|
24536
|
-
/**
|
|
25156
|
+
/**
|
|
25157
|
+
* Filled circle (triangle fan), feathered like a line: the rim is padded by {@link AA_PAD} and
|
|
25158
|
+
* edgeDist runs from 0 at the center to the padded radius at the rim, with edgeHalf = r. The
|
|
25159
|
+
* distance grows linearly along each spoke, so the shader's line feather gives a round soft edge.
|
|
25160
|
+
*/
|
|
24537
25161
|
circle(cx, cy, r, c, segments = 16) {
|
|
24538
|
-
|
|
25162
|
+
const ext = r + AA_PAD;
|
|
25163
|
+
let px = cx + ext;
|
|
24539
25164
|
let py = cy;
|
|
25165
|
+
this.ensure(segments * 3);
|
|
24540
25166
|
for (let i = 1; i <= segments; i += 1) {
|
|
24541
25167
|
const a = i / segments * Math.PI * 2;
|
|
24542
|
-
const nx = cx + Math.cos(a) *
|
|
24543
|
-
const ny = cy + Math.sin(a) *
|
|
24544
|
-
this.
|
|
25168
|
+
const nx = cx + Math.cos(a) * ext;
|
|
25169
|
+
const ny = cy + Math.sin(a) * ext;
|
|
25170
|
+
this.v(cx, cy, c, 0, r);
|
|
25171
|
+
this.v(px, py, c, ext, r);
|
|
25172
|
+
this.v(nx, ny, c, ext, r);
|
|
24545
25173
|
px = nx;
|
|
24546
25174
|
py = ny;
|
|
24547
25175
|
}
|
|
@@ -24722,7 +25350,7 @@ var WebGL2Backend = class {
|
|
|
24722
25350
|
}
|
|
24723
25351
|
mount(canvas) {
|
|
24724
25352
|
this.canvas = canvas;
|
|
24725
|
-
this.gl = canvas.getContext("webgl2", { alpha: true, antialias:
|
|
25353
|
+
this.gl = canvas.getContext("webgl2", { alpha: true, antialias: false, depth: false, stencil: false, premultipliedAlpha: true });
|
|
24726
25354
|
if (!this.gl) return;
|
|
24727
25355
|
if (!this.initGL()) {
|
|
24728
25356
|
this.gl = null;
|
|
@@ -25947,6 +26575,11 @@ var InputController = class {
|
|
|
25947
26575
|
/** When true (the default), the separators between stacked panes are draggable to
|
|
25948
26576
|
* resize the panes above/below. When false, a press there is a normal data-area pan. */
|
|
25949
26577
|
this.paneResize = true;
|
|
26578
|
+
/** When true (the default), the user drawings take part in pointer input: they claim a
|
|
26579
|
+
* press over a drawing or with a tool armed, and see hover moves. When false, every press
|
|
26580
|
+
* is a plain pan/click and hover leaves them alone; a drawing gesture already under way
|
|
26581
|
+
* still finishes. */
|
|
26582
|
+
this.drawings = true;
|
|
25950
26583
|
this.el = null;
|
|
25951
26584
|
this.dragging = false;
|
|
25952
26585
|
this.moved = false;
|
|
@@ -26010,18 +26643,18 @@ var InputController = class {
|
|
|
26010
26643
|
this.onModifier = (e) => {
|
|
26011
26644
|
if (e.repeat || e.key !== "Shift" && e.key !== "Control" && e.key !== "Meta") return;
|
|
26012
26645
|
if (Number.isNaN(this.cursorX)) return;
|
|
26013
|
-
if (this.dragging
|
|
26646
|
+
if (this.dragging ? this.region !== "drawing" : !this.drawings) return;
|
|
26014
26647
|
this.deps.drawingsPointerMove?.(this.cursorX, this.cursorY, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26015
26648
|
};
|
|
26016
26649
|
this.onDown = (e) => {
|
|
26017
26650
|
if (e.button === 1) {
|
|
26018
26651
|
const { x: x2, y: y2 } = this.local(e);
|
|
26019
|
-
this.middleDeleted = this.deps.drawingsDeleteAt?.(x2, y2) ?? false;
|
|
26652
|
+
this.middleDeleted = this.drawings && (this.deps.drawingsDeleteAt?.(x2, y2) ?? false);
|
|
26020
26653
|
if (this.middleDeleted) e.preventDefault();
|
|
26021
26654
|
return;
|
|
26022
26655
|
}
|
|
26023
26656
|
if (e.button === 2) {
|
|
26024
|
-
this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
|
|
26657
|
+
this.rightCancelled = this.drawings && (this.deps.drawingsCancelPlacement?.() ?? false);
|
|
26025
26658
|
if (this.rightCancelled) e.preventDefault();
|
|
26026
26659
|
return;
|
|
26027
26660
|
}
|
|
@@ -26043,20 +26676,20 @@ var InputController = class {
|
|
|
26043
26676
|
this.startY = y;
|
|
26044
26677
|
this.prevPressDrawing = this.lastPressDrawing;
|
|
26045
26678
|
this.lastPressDrawing = false;
|
|
26046
|
-
if (this.deps.drawingsClaim?.(x, y)) {
|
|
26679
|
+
if (this.drawings && this.deps.drawingsClaim?.(x, y)) {
|
|
26047
26680
|
this.region = "drawing";
|
|
26048
26681
|
this.lastPressDrawing = true;
|
|
26049
26682
|
this.deps.drawingsPointerDown?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26050
26683
|
this.capture(e.pointerId);
|
|
26051
26684
|
return;
|
|
26052
26685
|
}
|
|
26053
|
-
if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
26686
|
+
if (this.drawings && e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
26054
26687
|
this.region = "drawing";
|
|
26055
26688
|
this.lastPressDrawing = true;
|
|
26056
26689
|
this.capture(e.pointerId);
|
|
26057
26690
|
return;
|
|
26058
26691
|
}
|
|
26059
|
-
if ((e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
|
|
26692
|
+
if (this.drawings && (e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
|
|
26060
26693
|
this.region = "drawing";
|
|
26061
26694
|
this.lastPressDrawing = true;
|
|
26062
26695
|
this.capture(e.pointerId);
|
|
@@ -26133,7 +26766,7 @@ var InputController = class {
|
|
|
26133
26766
|
const drawCursor = this.deps.drawingsCursor?.(x, y);
|
|
26134
26767
|
const r = this.regionAt(x, y);
|
|
26135
26768
|
this.el.style.cursor = drawCursor ?? (r === "price" ? "ns-resize" : r === "time" ? "ew-resize" : r === "separator" ? "row-resize" : "");
|
|
26136
|
-
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26769
|
+
if (this.drawings) this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26137
26770
|
}
|
|
26138
26771
|
if (e.pointerType !== "touch") this.deps.onPointerMove(x, y);
|
|
26139
26772
|
};
|
|
@@ -26174,7 +26807,7 @@ var InputController = class {
|
|
|
26174
26807
|
this.deps.drawingsPointerUp?.(x, y, this.snapMode(e));
|
|
26175
26808
|
} else if (tapRelease && this.region === "data") {
|
|
26176
26809
|
this.deps.onClick(x, y);
|
|
26177
|
-
} else if (this.dragging && this.region === "data") {
|
|
26810
|
+
} else if (this.dragging && this.region === "data" && wasTouch) {
|
|
26178
26811
|
const stale = e.timeStamp - this.lastT > FLING_STALE_MS;
|
|
26179
26812
|
if (!stale && Math.abs(this.vx) > FLING_MIN_SPEED) {
|
|
26180
26813
|
const pitch = this.deps.getCoords().pxPerBar();
|
|
@@ -26375,7 +27008,7 @@ var InputController = class {
|
|
|
26375
27008
|
}
|
|
26376
27009
|
/** Shared double-click / double-tap routing, by the region under the point. */
|
|
26377
27010
|
doubleActivate(x, y) {
|
|
26378
|
-
if (this.deps.drawingsDblClick?.(x, y)) return;
|
|
27011
|
+
if (this.drawings && this.deps.drawingsDblClick?.(x, y)) return;
|
|
26379
27012
|
const region = this.regionAt(x, y);
|
|
26380
27013
|
if (region === "price") this.deps.resetPriceScale(x, y);
|
|
26381
27014
|
else if (region === "separator") this.deps.resetPaneSize(y);
|
|
@@ -28389,10 +29022,13 @@ function priceTicks(min, max, target = 6) {
|
|
|
28389
29022
|
const norm = raw / mag;
|
|
28390
29023
|
const step = (norm < 1.5 ? 1 : norm < 3 ? 2 : norm < 7 ? 5 : 10) * mag;
|
|
28391
29024
|
const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);
|
|
28392
|
-
const out = [];
|
|
28393
29025
|
const start = Math.ceil(min / step) * step;
|
|
28394
|
-
|
|
28395
|
-
|
|
29026
|
+
if (!(step > 0) || start + step === start) return [];
|
|
29027
|
+
const count = Math.floor((max - start) / step + 1e-6) + 1;
|
|
29028
|
+
if (!(count > 0) || count > 1e3) return [];
|
|
29029
|
+
const out = [];
|
|
29030
|
+
for (let i = 0; i < count; i += 1) {
|
|
29031
|
+
out.push(Number((start + i * step).toFixed(decimals)));
|
|
28396
29032
|
}
|
|
28397
29033
|
return out;
|
|
28398
29034
|
}
|
|
@@ -28590,6 +29226,100 @@ function calendarTicks(zFrom, zTo, months, offsetMs) {
|
|
|
28590
29226
|
return out;
|
|
28591
29227
|
}
|
|
28592
29228
|
|
|
29229
|
+
// src/renderers/native/chrome/priceAxisTicks.ts
|
|
29230
|
+
var PriceAxisTickSource = class {
|
|
29231
|
+
constructor() {
|
|
29232
|
+
this.fn = null;
|
|
29233
|
+
this.memo = /* @__PURE__ */ new WeakMap();
|
|
29234
|
+
this.warned = false;
|
|
29235
|
+
}
|
|
29236
|
+
/** The host function, or null for the built-in ticks. */
|
|
29237
|
+
get hook() {
|
|
29238
|
+
return this.fn;
|
|
29239
|
+
}
|
|
29240
|
+
/** Set (or clear) the host function. Drops every memoized result, so setting the same
|
|
29241
|
+
* function again recomputes — how a host refreshes ticks that depend on its own state. */
|
|
29242
|
+
setHook(fn) {
|
|
29243
|
+
this.fn = fn;
|
|
29244
|
+
this.memo = /* @__PURE__ */ new WeakMap();
|
|
29245
|
+
}
|
|
29246
|
+
/** The pane's master-scale ticks for this frame. An unscaled pane (`axisFormat: 'none'`)
|
|
29247
|
+
* gets none, and the host is never asked for it. */
|
|
29248
|
+
ticksFor(scene, pane, coords) {
|
|
29249
|
+
const format = pane.axisFormat;
|
|
29250
|
+
if (format === "none") return [];
|
|
29251
|
+
const pct = percentScaleFor(scene, pane);
|
|
29252
|
+
const height = pane.bounds.height;
|
|
29253
|
+
const fn = this.fn;
|
|
29254
|
+
if (!fn || !(height > 0)) return paneAxisTicks(pane.scale, height, pct, scene.priceMintick, format);
|
|
29255
|
+
const s = pane.scale;
|
|
29256
|
+
const m = this.memo.get(pane);
|
|
29257
|
+
const fontSize = scene.style.fontSize;
|
|
29258
|
+
if (m && m.kind === pane.kind && m.min === s.min && m.max === s.max && m.log === !!s.log && m.invert === !!s.invert && m.height === height && m.baseline === pct?.baseline && m.indexed === !!pct?.indexed && m.mintick === scene.priceMintick && m.format === format && m.fontSize === fontSize) {
|
|
29259
|
+
return m.ticks;
|
|
29260
|
+
}
|
|
29261
|
+
const defaults2 = paneAxisTicks(s, height, pct, scene.priceMintick, format);
|
|
29262
|
+
const ticks = this.callHook(fn, scene, pane, coords, defaults2, pct?.baseline, pct ? pct.indexed ? "indexed" : "percent" : "price");
|
|
29263
|
+
this.memo.set(pane, {
|
|
29264
|
+
kind: pane.kind,
|
|
29265
|
+
min: s.min,
|
|
29266
|
+
max: s.max,
|
|
29267
|
+
log: !!s.log,
|
|
29268
|
+
invert: !!s.invert,
|
|
29269
|
+
height,
|
|
29270
|
+
baseline: pct?.baseline,
|
|
29271
|
+
indexed: !!pct?.indexed,
|
|
29272
|
+
mintick: scene.priceMintick,
|
|
29273
|
+
format,
|
|
29274
|
+
fontSize,
|
|
29275
|
+
ticks
|
|
29276
|
+
});
|
|
29277
|
+
return ticks;
|
|
29278
|
+
}
|
|
29279
|
+
callHook(fn, scene, pane, coords, defaults2, baseline, mode) {
|
|
29280
|
+
const scale = { min: pane.scale.min, max: pane.scale.max, log: !!pane.scale.log, invert: !!pane.scale.invert };
|
|
29281
|
+
const bounds = { top: 0, height: pane.bounds.height };
|
|
29282
|
+
const ctx = {
|
|
29283
|
+
pane: { id: pane.id, kind: pane.kind },
|
|
29284
|
+
min: Math.min(scale.min, scale.max),
|
|
29285
|
+
max: Math.max(scale.min, scale.max),
|
|
29286
|
+
log: !!scale.log,
|
|
29287
|
+
height: bounds.height,
|
|
29288
|
+
mode,
|
|
29289
|
+
baseline,
|
|
29290
|
+
mintick: scene.priceMintick,
|
|
29291
|
+
fontSize: scene.style.fontSize,
|
|
29292
|
+
priceToY: (price) => coords.priceToY(price, scale, bounds),
|
|
29293
|
+
defaults: defaults2
|
|
29294
|
+
};
|
|
29295
|
+
let out;
|
|
29296
|
+
try {
|
|
29297
|
+
out = fn(ctx);
|
|
29298
|
+
} catch (err) {
|
|
29299
|
+
this.warnOnce("threw", err);
|
|
29300
|
+
return defaults2;
|
|
29301
|
+
}
|
|
29302
|
+
if (out == null) return defaults2;
|
|
29303
|
+
if (!Array.isArray(out)) {
|
|
29304
|
+
this.warnOnce("returned a non-array", out);
|
|
29305
|
+
return defaults2;
|
|
29306
|
+
}
|
|
29307
|
+
const ticks = [];
|
|
29308
|
+
for (const t of out) {
|
|
29309
|
+
if (!t || typeof t !== "object") continue;
|
|
29310
|
+
const { price, label, major } = t;
|
|
29311
|
+
if (typeof price !== "number" || !Number.isFinite(price) || typeof label !== "string") continue;
|
|
29312
|
+
ticks.push(typeof major === "boolean" ? { price, label, major } : { price, label });
|
|
29313
|
+
}
|
|
29314
|
+
return ticks;
|
|
29315
|
+
}
|
|
29316
|
+
warnOnce(what, detail) {
|
|
29317
|
+
if (this.warned) return;
|
|
29318
|
+
this.warned = true;
|
|
29319
|
+
console.warn(`[vela] the priceAxisTicks function ${what} \u2014 the default ticks are used instead.`, detail);
|
|
29320
|
+
}
|
|
29321
|
+
};
|
|
29322
|
+
|
|
28593
29323
|
// src/renderers/native/chrome/countdown.ts
|
|
28594
29324
|
function countdownText(barOpen, barMs, now2) {
|
|
28595
29325
|
if (!(barMs > 0)) return null;
|
|
@@ -28942,8 +29672,11 @@ function standaloneSvg(markup, ink, px) {
|
|
|
28942
29672
|
}
|
|
28943
29673
|
|
|
28944
29674
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
29675
|
+
var MINOR_LABEL_ALPHA = 0.5;
|
|
28945
29676
|
var ChromeRenderer = class {
|
|
28946
|
-
|
|
29677
|
+
/** `axisTicks` is shared with the backdrop layer, so price labels and gridlines agree. */
|
|
29678
|
+
constructor(axisTicks = new PriceAxisTickSource()) {
|
|
29679
|
+
this.axisTicks = axisTicks;
|
|
28947
29680
|
this.canvas = null;
|
|
28948
29681
|
this.ctx = null;
|
|
28949
29682
|
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
@@ -29048,7 +29781,8 @@ var ChromeRenderer = class {
|
|
|
29048
29781
|
this.drawTimeAxis(ctx, scene, coords, theme, dataW, dataH, fullH);
|
|
29049
29782
|
this.drawMarkLane(ctx, scene, coords, theme, dataW, dataH);
|
|
29050
29783
|
}
|
|
29051
|
-
/** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge
|
|
29784
|
+
/** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge,
|
|
29785
|
+
* but clipped to the data area so a glyph on the edge bar never paints over the price scale. */
|
|
29052
29786
|
drawMarkLane(ctx, scene, coords, theme, dataW, dataH) {
|
|
29053
29787
|
if (!scene.marks.visible || scene.timelineMarks.length === 0) {
|
|
29054
29788
|
this.markLayout = { glyphs: [], stacks: /* @__PURE__ */ new Map() };
|
|
@@ -29066,6 +29800,10 @@ var ChromeRenderer = class {
|
|
|
29066
29800
|
expanded: scene.marksExpandedStack
|
|
29067
29801
|
});
|
|
29068
29802
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
29803
|
+
ctx.save();
|
|
29804
|
+
ctx.beginPath();
|
|
29805
|
+
ctx.rect(0, 0, dataW, dataH);
|
|
29806
|
+
ctx.clip();
|
|
29069
29807
|
paintMarkLane(ctx, this.markLayout, {
|
|
29070
29808
|
axisY: dataH,
|
|
29071
29809
|
background: theme.background,
|
|
@@ -29079,6 +29817,7 @@ var ChromeRenderer = class {
|
|
|
29079
29817
|
flashKey: scene.marksFlash && scene.marksFlash.until > nowMs ? scene.marksFlash.key : null,
|
|
29080
29818
|
nowMs
|
|
29081
29819
|
});
|
|
29820
|
+
ctx.restore();
|
|
29082
29821
|
}
|
|
29083
29822
|
barTimes(scene) {
|
|
29084
29823
|
if (this.barTimesSrc !== scene.bars || this.barTimesCache.length !== scene.bars.length) {
|
|
@@ -29135,13 +29874,19 @@ var ChromeRenderer = class {
|
|
|
29135
29874
|
if (!scene.showAxisLabels) return;
|
|
29136
29875
|
ctx.fillStyle = this.axisTextColor;
|
|
29137
29876
|
ctx.textAlign = "left";
|
|
29877
|
+
const font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
29878
|
+
const majorFont = `600 ${font}`;
|
|
29879
|
+
const alpha = ctx.globalAlpha;
|
|
29138
29880
|
for (const pane of panes) {
|
|
29139
29881
|
if (pane.collapsed) continue;
|
|
29140
|
-
const
|
|
29141
|
-
for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, scene.priceMintick, pane.axisFormat)) {
|
|
29882
|
+
for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
|
|
29142
29883
|
const y = coords.priceToY(t.price, pane.scale, pane.bounds);
|
|
29143
29884
|
if (y < pane.bounds.top + 6 || y > pane.bounds.top + pane.bounds.height - 4) continue;
|
|
29885
|
+
if (t.major === true) ctx.font = majorFont;
|
|
29886
|
+
else if (t.major === false) ctx.globalAlpha = alpha * MINOR_LABEL_ALPHA;
|
|
29144
29887
|
ctx.fillText(t.label, dataW + 6, y);
|
|
29888
|
+
if (t.major === true) ctx.font = font;
|
|
29889
|
+
else if (t.major === false) ctx.globalAlpha = alpha;
|
|
29145
29890
|
}
|
|
29146
29891
|
if (pane.axisBands) {
|
|
29147
29892
|
for (const b of pane.axisBands) {
|
|
@@ -29383,15 +30128,23 @@ var CrosshairRenderer = class {
|
|
|
29383
30128
|
constructor() {
|
|
29384
30129
|
this.canvas = null;
|
|
29385
30130
|
this.ctx = null;
|
|
30131
|
+
this.shade = null;
|
|
29386
30132
|
}
|
|
29387
30133
|
mount(canvas) {
|
|
29388
30134
|
this.canvas = canvas;
|
|
29389
30135
|
this.ctx = canvas.getContext("2d");
|
|
29390
30136
|
}
|
|
30137
|
+
/** The plot area the last frame veiled for `crosshairOverride.shadeRight` — from the
|
|
30138
|
+
* picked bar's right edge to the price scale, full plot height (the union when both the
|
|
30139
|
+
* local and a synced crosshair veil) — or null when no veil was painted. */
|
|
30140
|
+
get shadeRightArea() {
|
|
30141
|
+
return this.shade;
|
|
30142
|
+
}
|
|
29391
30143
|
/** Clear the cursor canvas and (re)draw the crosshair lines + axis chips. The optional
|
|
29392
30144
|
* `separatorHoverY` highlights the draggable pane separator under the cursor;
|
|
29393
30145
|
* `external` is a SYNCED ghost crosshair (another chart's pointer, pixel-resolved). */
|
|
29394
30146
|
render(scene, coords, theme, separatorHoverY = null, external = null) {
|
|
30147
|
+
this.shade = null;
|
|
29395
30148
|
const ctx = this.ctx;
|
|
29396
30149
|
const canvas = this.canvas;
|
|
29397
30150
|
if (!ctx || !canvas) return;
|
|
@@ -29414,12 +30167,7 @@ var CrosshairRenderer = class {
|
|
|
29414
30167
|
const x = crisp(coords.logicalToX(logical), ov?.width ?? cs.width);
|
|
29415
30168
|
if (vertical && ov?.shadeRight) {
|
|
29416
30169
|
const from = Math.max(0, Math.round(coords.logicalToX(logical + 0.5)));
|
|
29417
|
-
if (from < dataW)
|
|
29418
|
-
ctx.fillStyle = ov.shadeRight.color;
|
|
29419
|
-
ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
|
|
29420
|
-
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
29421
|
-
ctx.globalAlpha = 1;
|
|
29422
|
-
}
|
|
30170
|
+
if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
|
|
29423
30171
|
}
|
|
29424
30172
|
ctx.strokeStyle = ov?.color ?? cs.color ?? theme.textColor;
|
|
29425
30173
|
ctx.lineWidth = ov?.width ?? cs.width;
|
|
@@ -29455,6 +30203,7 @@ var CrosshairRenderer = class {
|
|
|
29455
30203
|
destroy() {
|
|
29456
30204
|
this.canvas = null;
|
|
29457
30205
|
this.ctx = null;
|
|
30206
|
+
this.shade = null;
|
|
29458
30207
|
}
|
|
29459
30208
|
/** The synced ghost: a dimmed vertical line at the bar the renderer resolved as
|
|
29460
30209
|
* CONTAINING the foreign time (+ horizontal line when a comparable price came
|
|
@@ -29474,12 +30223,7 @@ var CrosshairRenderer = class {
|
|
|
29474
30223
|
if (ov?.vertical === false) return;
|
|
29475
30224
|
if (ov?.shadeRight) {
|
|
29476
30225
|
const from = Math.max(0, Math.round(coords.logicalToX(Math.round(coords.xToLogical(ext.x)) + 0.5)));
|
|
29477
|
-
if (from < dataW)
|
|
29478
|
-
ctx.fillStyle = ov.shadeRight.color;
|
|
29479
|
-
ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
|
|
29480
|
-
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
29481
|
-
ctx.globalAlpha = 1;
|
|
29482
|
-
}
|
|
30226
|
+
if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
|
|
29483
30227
|
}
|
|
29484
30228
|
if (ext.line === false || x < 0 || x > dataW) return;
|
|
29485
30229
|
ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
@@ -29515,6 +30259,15 @@ var CrosshairRenderer = class {
|
|
|
29515
30259
|
this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
29516
30260
|
ctx.globalAlpha = 1;
|
|
29517
30261
|
}
|
|
30262
|
+
/** Paint the `shadeRight` veil from `from` to the price scale and record the veiled area. */
|
|
30263
|
+
paintShade(ctx, shade, from, dataW, dataH) {
|
|
30264
|
+
ctx.fillStyle = shade.color;
|
|
30265
|
+
ctx.globalAlpha = shade.opacity ?? 1;
|
|
30266
|
+
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
30267
|
+
ctx.globalAlpha = 1;
|
|
30268
|
+
const x = this.shade ? Math.min(this.shade.x, from) : from;
|
|
30269
|
+
this.shade = { x, width: dataW - x, height: dataH };
|
|
30270
|
+
}
|
|
29518
30271
|
/** A soft band + a brighter crisp center line over the hovered separator, so it reads as
|
|
29519
30272
|
* a draggable handle (the cursor is already `row-resize`). Spans the full width (data +
|
|
29520
30273
|
* scale gutter) to match the separator itself. Theme-derived (textColor). */
|
|
@@ -32540,6 +33293,12 @@ var DrawingInteraction = class {
|
|
|
32540
33293
|
activeDragId() {
|
|
32541
33294
|
return this.state.kind === "pressed" && this.state.moved ? this.state.id : null;
|
|
32542
33295
|
}
|
|
33296
|
+
/** The store drawings a live drag is moving — the pressed one and its riders — or none
|
|
33297
|
+
* before the drag passes the slop. A Ctrl-drag moves copies, so its sources stay put. */
|
|
33298
|
+
movingIds() {
|
|
33299
|
+
if (this.state.kind !== "pressed" || !this.state.moved || this.state.clones) return /* @__PURE__ */ new Set();
|
|
33300
|
+
return /* @__PURE__ */ new Set([this.state.id, ...this.state.riders.map((r) => r.id)]);
|
|
33301
|
+
}
|
|
32543
33302
|
/** The drawing under a press that has not been released yet (drag or click undecided), or null. */
|
|
32544
33303
|
pressedId() {
|
|
32545
33304
|
return this.state.kind === "pressed" ? this.state.id : null;
|
|
@@ -32914,15 +33673,16 @@ var DrawingSettingsDialog = class {
|
|
|
32914
33673
|
}
|
|
32915
33674
|
open(drawing, actions, kind) {
|
|
32916
33675
|
this.close();
|
|
32917
|
-
const
|
|
33676
|
+
const live = actions.resolve() ?? drawing;
|
|
33677
|
+
const snapshot = clonePlain(live.serialize());
|
|
32918
33678
|
const grid = fieldGrid({ variant: "inputs" });
|
|
32919
33679
|
grid.style.padding = "16px 20px";
|
|
32920
33680
|
grid.style.overflowY = "auto";
|
|
32921
33681
|
grid.style.overflowX = "hidden";
|
|
32922
33682
|
grid.style.flex = "1 1 auto";
|
|
32923
|
-
if (kind === "position" &&
|
|
32924
|
-
else if (kind === "frvp" &&
|
|
32925
|
-
else if (kind === "levels") this.buildLevels(grid,
|
|
33683
|
+
if (kind === "position" && live instanceof PositionTool) this.buildPosition(grid, live, actions);
|
|
33684
|
+
else if (kind === "frvp" && live instanceof FixedRangeVolumeProfile) this.buildFrvp(grid, live, actions);
|
|
33685
|
+
else if (kind === "levels") this.buildLevels(grid, live, actions);
|
|
32926
33686
|
else return;
|
|
32927
33687
|
const ui = new Dialog({
|
|
32928
33688
|
host: this.host,
|
|
@@ -33198,6 +33958,17 @@ var DrawingSettingsDialog = class {
|
|
|
33198
33958
|
}
|
|
33199
33959
|
}));
|
|
33200
33960
|
}
|
|
33961
|
+
if (drawing instanceof FibRetracement) {
|
|
33962
|
+
const fib = drawing;
|
|
33963
|
+
grid.appendChild(fieldRow({
|
|
33964
|
+
label: "Reverse",
|
|
33965
|
+
bool: true,
|
|
33966
|
+
toggle: {
|
|
33967
|
+
checked: fib.reverse,
|
|
33968
|
+
onChange: (v) => actions.patch({ reverse: v })
|
|
33969
|
+
}
|
|
33970
|
+
}));
|
|
33971
|
+
}
|
|
33201
33972
|
levels.forEach((lv, i) => {
|
|
33202
33973
|
const row = document.createElement("div");
|
|
33203
33974
|
row.className = "vela-field-span";
|
|
@@ -33558,7 +34329,7 @@ var DrawingSettingsPopup = class {
|
|
|
33558
34329
|
this.reposition();
|
|
33559
34330
|
return;
|
|
33560
34331
|
}
|
|
33561
|
-
const text = drawing.text;
|
|
34332
|
+
const text = (actions.resolve() ?? drawing).text;
|
|
33562
34333
|
const panel = document.createElement("div");
|
|
33563
34334
|
panel.style.cssText = `padding:6px;border-top:1px solid var(--vela-border);display:flex;flex-direction:column;gap:4px;`;
|
|
33564
34335
|
const ta = new TextArea({
|
|
@@ -34351,6 +35122,8 @@ var UserDrawingController = class {
|
|
|
34351
35122
|
this.drawings = [];
|
|
34352
35123
|
/** Ids painted on an interleave layer this frame — the top canvas paints only their handles. */
|
|
34353
35124
|
this.sliced = /* @__PURE__ */ new Set();
|
|
35125
|
+
/** Interleaved drawings mid-drag: painted on the top canvas, left out of their slice. */
|
|
35126
|
+
this.lifted = /* @__PURE__ */ new Set();
|
|
34354
35127
|
/** Cached slice canvases, keyed `paneId|beforeZ`, reused across frames to avoid churn. */
|
|
34355
35128
|
this.sliceCache = /* @__PURE__ */ new Map();
|
|
34356
35129
|
/** Series boundaries per pane as of the last `prepareSlices` — lets a repaint between data
|
|
@@ -34438,7 +35211,7 @@ var UserDrawingController = class {
|
|
|
34438
35211
|
selectedIds: () => this.selectedIds,
|
|
34439
35212
|
emit: (i) => this.emit(i),
|
|
34440
35213
|
changed: () => {
|
|
34441
|
-
this.
|
|
35214
|
+
this.syncLift();
|
|
34442
35215
|
this.render();
|
|
34443
35216
|
},
|
|
34444
35217
|
openSettings: (id, x, y) => this.openSettingsById(id, x, y),
|
|
@@ -34727,6 +35500,12 @@ var UserDrawingController = class {
|
|
|
34727
35500
|
this.render();
|
|
34728
35501
|
}
|
|
34729
35502
|
}
|
|
35503
|
+
/** Drop the hover (its handles) — the pointer no longer reaches the drawings. */
|
|
35504
|
+
clearHover() {
|
|
35505
|
+
if (this.hoveredId == null) return;
|
|
35506
|
+
this.hoveredId = null;
|
|
35507
|
+
this.render();
|
|
35508
|
+
}
|
|
34730
35509
|
pointerUp(x, y, snap = "off") {
|
|
34731
35510
|
if (this.eraserMode) {
|
|
34732
35511
|
this.erasing = false;
|
|
@@ -35064,6 +35843,20 @@ var UserDrawingController = class {
|
|
|
35064
35843
|
/** A drawing that paints inside the series stack changed (content, not hover): its pixels
|
|
35065
35844
|
* live in the backend composite, so this layer alone can't show the change — ask for a
|
|
35066
35845
|
* data frame, which re-runs `prepareSlices` before the backend composites. */
|
|
35846
|
+
/** A drawing being dragged leaves its interleave slice for the top canvas, so each pointer
|
|
35847
|
+
* move repaints one 2D layer instead of re-rasterizing and re-uploading a plot-sized slice
|
|
35848
|
+
* texture through the data frame. The slices rebuild once as the drag starts and once as it
|
|
35849
|
+
* ends (release, cancel), when the lifted set changes. */
|
|
35850
|
+
syncLift() {
|
|
35851
|
+
const moving = this.interaction.movingIds();
|
|
35852
|
+
let same = moving.size === this.lifted.size;
|
|
35853
|
+
if (same) {
|
|
35854
|
+
for (const id of moving) if (!this.lifted.has(id)) same = false;
|
|
35855
|
+
}
|
|
35856
|
+
if (same) return;
|
|
35857
|
+
this.lifted = new Set(moving);
|
|
35858
|
+
this.invalidateSlices();
|
|
35859
|
+
}
|
|
35067
35860
|
invalidateSlices() {
|
|
35068
35861
|
if (this.sliced.size > 0 || this.drawings.some((d) => this.isInterleaved(d))) this.deps.requestDataPaint();
|
|
35069
35862
|
}
|
|
@@ -35082,7 +35875,7 @@ var UserDrawingController = class {
|
|
|
35082
35875
|
const theme = this.deps.theme();
|
|
35083
35876
|
const buckets = /* @__PURE__ */ new Map();
|
|
35084
35877
|
for (const d of this.drawings) {
|
|
35085
|
-
if (!d.visible) continue;
|
|
35878
|
+
if (!d.visible || this.lifted.has(d.id)) continue;
|
|
35086
35879
|
const beforeZ = sliceKeyFor(d.zIndex, this.lastBounds.get(d.paneId) ?? []);
|
|
35087
35880
|
if (beforeZ === null) continue;
|
|
35088
35881
|
const key = `${d.paneId}|${beforeZ}`;
|
|
@@ -35134,8 +35927,9 @@ var UserDrawingController = class {
|
|
|
35134
35927
|
mutedLabel: edited instanceof TextLabel ? edited.id : null
|
|
35135
35928
|
};
|
|
35136
35929
|
this.painter.seriesLook = this.deps.seriesLook();
|
|
35137
|
-
|
|
35138
|
-
this.painter.
|
|
35930
|
+
const onTop = (d) => !this.isInterleaved(d) || this.lifted.has(d.id);
|
|
35931
|
+
this.painter.paintAll(ctx, this.drawings.filter(onTop), proj, this.deps.theme(), targets);
|
|
35932
|
+
this.painter.paintHighlights(ctx, this.drawings.filter((d) => !onTop(d)), proj, handleIdsFor(targets));
|
|
35139
35933
|
const clones = this.interaction.dragClones();
|
|
35140
35934
|
if (clones) this.painter.paintAll(ctx, clones, proj, this.deps.theme(), { selected: new Set(clones.map((c) => c.id)) });
|
|
35141
35935
|
this.layoutTextEditor();
|
|
@@ -35702,9 +36496,10 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
35702
36496
|
consider(drawings.max);
|
|
35703
36497
|
}
|
|
35704
36498
|
if (min === Infinity || max === -Infinity) return { min: 0, max: 1 };
|
|
35705
|
-
if (min
|
|
35706
|
-
const
|
|
35707
|
-
|
|
36499
|
+
if (max - min <= Math.max(Math.abs(min), Math.abs(max)) * 1e-12) {
|
|
36500
|
+
const mid = (min + max) / 2;
|
|
36501
|
+
const pad = Math.abs(mid) * 0.1 || 1;
|
|
36502
|
+
return { min: mid - pad, max: mid + pad, log: log && mid - pad > 0 };
|
|
35708
36503
|
}
|
|
35709
36504
|
const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
|
|
35710
36505
|
const above = margins.top / 100 / content;
|
|
@@ -36208,7 +37003,9 @@ function bandEdgeSlot(logical) {
|
|
|
36208
37003
|
return Math.ceil(logical) - 0.5;
|
|
36209
37004
|
}
|
|
36210
37005
|
var BackdropRenderer = class {
|
|
36211
|
-
|
|
37006
|
+
/** `axisTicks` is shared with the chrome layer, so gridlines and price labels agree. */
|
|
37007
|
+
constructor(axisTicks = new PriceAxisTickSource()) {
|
|
37008
|
+
this.axisTicks = axisTicks;
|
|
36212
37009
|
this.canvas = null;
|
|
36213
37010
|
this.ctx = null;
|
|
36214
37011
|
}
|
|
@@ -36285,9 +37082,8 @@ var BackdropRenderer = class {
|
|
|
36285
37082
|
if (scene.showGrid && gridHorz.visible && !pane.collapsed) {
|
|
36286
37083
|
ctx.globalAlpha = gridAlpha;
|
|
36287
37084
|
ctx.strokeStyle = horzColor;
|
|
36288
|
-
const pct = percentScaleFor(scene, pane);
|
|
36289
37085
|
ctx.beginPath();
|
|
36290
|
-
for (const t of
|
|
37086
|
+
for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
|
|
36291
37087
|
const y = Math.round(coords.priceToY(t.price, pane.scale, pane.bounds)) + 0.5;
|
|
36292
37088
|
if (y < pane.bounds.top || y > pane.bounds.top + pane.bounds.height) continue;
|
|
36293
37089
|
ctx.moveTo(0, y);
|
|
@@ -36774,8 +37570,16 @@ var NativeRenderer = class {
|
|
|
36774
37570
|
this.toolbarGutter = 0;
|
|
36775
37571
|
// px reserved on the left for the docked drawings toolbar (0 when hidden)
|
|
36776
37572
|
this.mountContainer = null;
|
|
37573
|
+
/** The host-styleable twin of the `shadeRight` veil (`.vela-shade-right`), directly under
|
|
37574
|
+
* the cursor canvas; hidden whenever the crosshair layer paints no veil. */
|
|
37575
|
+
this.shadeRightEl = null;
|
|
37576
|
+
/** The veil geometry last written to {@link shadeRightEl} ('' = hidden) — the DOM is
|
|
37577
|
+
* touched only when it changes, never on a plain pointer move. */
|
|
37578
|
+
this.shadeRightSig = "";
|
|
36777
37579
|
this.userDrawings = null;
|
|
36778
|
-
|
|
37580
|
+
/** One price-axis tick source for the gridlines (backdrop) and the labels (chrome). */
|
|
37581
|
+
this.priceAxisTicks = new PriceAxisTickSource();
|
|
37582
|
+
this.backdropRenderer = new BackdropRenderer(this.priceAxisTicks);
|
|
36779
37583
|
this.volumeRenderer = new VolumeRenderer();
|
|
36780
37584
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
36781
37585
|
/**
|
|
@@ -36808,7 +37612,7 @@ var NativeRenderer = class {
|
|
|
36808
37612
|
this.backendMode = "auto";
|
|
36809
37613
|
this.glowAmount = 0;
|
|
36810
37614
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
36811
|
-
this.chrome = new ChromeRenderer();
|
|
37615
|
+
this.chrome = new ChromeRenderer(this.priceAxisTicks);
|
|
36812
37616
|
/** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
|
|
36813
37617
|
this.indicatorSlices = new IndicatorDrawingSlices();
|
|
36814
37618
|
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
@@ -36925,6 +37729,8 @@ var NativeRenderer = class {
|
|
|
36925
37729
|
// pane just below it
|
|
36926
37730
|
this.resizeSplitStart = null;
|
|
36927
37731
|
// the two panes' shared span when the drag began
|
|
37732
|
+
/** The user drawings take part in pointer input (the runtime `drawingsInteractive` feature). */
|
|
37733
|
+
this.drawingsInteractive = true;
|
|
36928
37734
|
this.hoverSeparatorY = null;
|
|
36929
37735
|
// pixel y of the separator under the cursor (drives its hover highlight)
|
|
36930
37736
|
// ── pane management (merge / reorder / collapse / maximize) ──
|
|
@@ -36980,8 +37786,9 @@ var NativeRenderer = class {
|
|
|
36980
37786
|
this.toggleVisibleCbs = /* @__PURE__ */ new Set();
|
|
36981
37787
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
36982
37788
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
37789
|
+
this.priceStyleWillChangeCbs = /* @__PURE__ */ new Set();
|
|
36983
37790
|
this.name = "native";
|
|
36984
|
-
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animScroll", "animAutoscale", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "marks", "indicatorTitles", "indicatorValues", "crosshairOverride"];
|
|
37791
|
+
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animScroll", "animAutoscale", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "marks", "indicatorTitles", "indicatorValues", "crosshairOverride", "drawingsInteractive", "priceAxisTicks"];
|
|
36985
37792
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
36986
37793
|
* so moving onto the button doesn't count as leaving). */
|
|
36987
37794
|
this.onScrollProximityMove = (e) => {
|
|
@@ -37025,6 +37832,7 @@ var NativeRenderer = class {
|
|
|
37025
37832
|
this.scene.priceStyle = opts.priceStyle;
|
|
37026
37833
|
this.scene.basePainting = basePaintingOf(opts.priceStyle);
|
|
37027
37834
|
this.scene.candleOverride = candleOverrideFor(opts.priceStyle, this.scene.style.chartTypes);
|
|
37835
|
+
if (typeof opts.priceAxisTicks === "function") this.priceAxisTicks.setHook(opts.priceAxisTicks);
|
|
37028
37836
|
}
|
|
37029
37837
|
this.theme = this.deriveTheme(DARK_THEME);
|
|
37030
37838
|
}
|
|
@@ -37163,6 +37971,15 @@ var NativeRenderer = class {
|
|
|
37163
37971
|
case "crosshairOverride":
|
|
37164
37972
|
this.scene.crosshairOverride = sanitizeCrosshairOverride(value);
|
|
37165
37973
|
break;
|
|
37974
|
+
case "drawingsInteractive":
|
|
37975
|
+
this.drawingsInteractive = value !== false;
|
|
37976
|
+
if (this.input) this.input.drawings = this.drawingsInteractive;
|
|
37977
|
+
if (!this.drawingsInteractive) this.userDrawings?.clearHover();
|
|
37978
|
+
return;
|
|
37979
|
+
// affects the next gesture only — clearHover repaints what it drops
|
|
37980
|
+
case "priceAxisTicks":
|
|
37981
|
+
this.priceAxisTicks.setHook(typeof value === "function" ? value : null);
|
|
37982
|
+
break;
|
|
37166
37983
|
case "keyboard":
|
|
37167
37984
|
this.setKeyboardEnabled(Boolean(value));
|
|
37168
37985
|
return;
|
|
@@ -37278,6 +38095,10 @@ var NativeRenderer = class {
|
|
|
37278
38095
|
return { visible: this.scene.marks.visible, groups: { ...this.scene.marks.groups } };
|
|
37279
38096
|
case "crosshairOverride":
|
|
37280
38097
|
return this.scene.crosshairOverride ? { ...this.scene.crosshairOverride } : null;
|
|
38098
|
+
case "drawingsInteractive":
|
|
38099
|
+
return this.drawingsInteractive;
|
|
38100
|
+
case "priceAxisTicks":
|
|
38101
|
+
return this.priceAxisTicks.hook;
|
|
37281
38102
|
case "keyboard":
|
|
37282
38103
|
return this.keyboardEnabled;
|
|
37283
38104
|
case "historyChords":
|
|
@@ -37951,6 +38772,12 @@ var NativeRenderer = class {
|
|
|
37951
38772
|
Object.assign(this.drawingsCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
37952
38773
|
this.cursorCanvas = document.createElement("canvas");
|
|
37953
38774
|
Object.assign(this.cursorCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
38775
|
+
this.shadeRightEl = document.createElement("div");
|
|
38776
|
+
this.shadeRightEl.className = "vela-shade-right";
|
|
38777
|
+
this.shadeRightEl.setAttribute("aria-hidden", "true");
|
|
38778
|
+
this.shadeRightEl.hidden = true;
|
|
38779
|
+
Object.assign(this.shadeRightEl.style, { position: "absolute", top: "0", left: "0", width: "0", height: "0", pointerEvents: "none", display: "none" });
|
|
38780
|
+
this.shadeRightSig = "";
|
|
37954
38781
|
this.overlayRoot = document.createElement("div");
|
|
37955
38782
|
Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
|
|
37956
38783
|
this.plot = document.createElement("div");
|
|
@@ -37958,7 +38785,7 @@ var NativeRenderer = class {
|
|
|
37958
38785
|
this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
|
|
37959
38786
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
37960
38787
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
37961
|
-
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
38788
|
+
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.shadeRightEl, this.cursorCanvas, this.overlayRoot);
|
|
37962
38789
|
this.layerOrderSig = "";
|
|
37963
38790
|
this.wrapper.appendChild(this.plot);
|
|
37964
38791
|
this.factoryConfig = this.getConfig();
|
|
@@ -38016,13 +38843,14 @@ var NativeRenderer = class {
|
|
|
38016
38843
|
drawingsPointerDown: (x, y, snap, shift2, mod) => this.userDrawings?.pointerDown(x, y, snap, shift2, mod),
|
|
38017
38844
|
drawingsPointerMove: (x, y, snap, shift2, mod) => this.userDrawings?.pointerMove(x, y, snap, shift2, mod),
|
|
38018
38845
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
38019
|
-
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
|
|
38846
|
+
drawingsCursor: (x, y) => (this.drawingsInteractive ? this.userDrawings?.cursorAt(x, y) : null) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
|
|
38020
38847
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
38021
38848
|
drawingsClearTransient: () => this.userDrawings?.clearTransient()
|
|
38022
38849
|
});
|
|
38023
38850
|
this.input.rightEdgeZoom = this.zoomAnchorMode === "right";
|
|
38024
38851
|
this.input.axisDrag = this.axisDragEnabled;
|
|
38025
38852
|
this.input.paneResize = this.paneResizeEnabled;
|
|
38853
|
+
this.input.drawings = this.drawingsInteractive;
|
|
38026
38854
|
this.input.attach(this.dataCanvas);
|
|
38027
38855
|
if (this.settingsEnabled) this.setSettingsEnabled(true);
|
|
38028
38856
|
this.syncCountdownTimer();
|
|
@@ -38331,6 +39159,8 @@ var NativeRenderer = class {
|
|
|
38331
39159
|
this.backend.destroy();
|
|
38332
39160
|
this.chrome.destroy();
|
|
38333
39161
|
this.crosshairLayer.destroy();
|
|
39162
|
+
this.shadeRightEl?.remove();
|
|
39163
|
+
this.shadeRightEl = null;
|
|
38334
39164
|
this.attributionEl?.remove();
|
|
38335
39165
|
this.attributionEl = null;
|
|
38336
39166
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
@@ -38677,14 +39507,21 @@ var NativeRenderer = class {
|
|
|
38677
39507
|
this.priceStyleCbs.add(cb);
|
|
38678
39508
|
return () => this.priceStyleCbs.delete(cb);
|
|
38679
39509
|
}
|
|
39510
|
+
onPriceStyleWillChange(cb) {
|
|
39511
|
+
this.priceStyleWillChangeCbs.add(cb);
|
|
39512
|
+
return () => this.priceStyleWillChangeCbs.delete(cb);
|
|
39513
|
+
}
|
|
38680
39514
|
/**
|
|
38681
39515
|
* THE single write path for the base price style at runtime (feature set / settings dialog /
|
|
38682
|
-
* config template — the constructor seeds the field directly, pre-listeners).
|
|
38683
|
-
*
|
|
38684
|
-
*
|
|
39516
|
+
* config template — the constructor seeds the field directly, pre-listeners). Announces the
|
|
39517
|
+
* switch while the scene still holds the old style (a listener may paint/capture that frame),
|
|
39518
|
+
* then updates the scene, eases any reveal layer toward the new style's target, and notifies
|
|
39519
|
+
* the core — which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
|
|
38685
39520
|
*/
|
|
38686
39521
|
setPriceStyle(style) {
|
|
38687
39522
|
if (style === this.scene.priceStyle) return;
|
|
39523
|
+
const from = this.scene.priceStyle;
|
|
39524
|
+
for (const cb of this.priceStyleWillChangeCbs) cb(from, style);
|
|
38688
39525
|
this.scene.priceStyle = style;
|
|
38689
39526
|
this.scene.basePainting = basePaintingOf(style);
|
|
38690
39527
|
this.scene.candleOverride = candleOverrideFor(style, this.scene.style.chartTypes);
|
|
@@ -38914,7 +39751,7 @@ var NativeRenderer = class {
|
|
|
38914
39751
|
if (this.easeLiveBar(dtMs)) active = true;
|
|
38915
39752
|
this.skeletonClockMs += dtMs;
|
|
38916
39753
|
this.paintData();
|
|
38917
|
-
this.
|
|
39754
|
+
this.renderCrosshairLayer();
|
|
38918
39755
|
this.updateLegendValues();
|
|
38919
39756
|
this.emitViewportChange();
|
|
38920
39757
|
return active;
|
|
@@ -39489,10 +40326,31 @@ var NativeRenderer = class {
|
|
|
39489
40326
|
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
39490
40327
|
this.trackMarkPopover();
|
|
39491
40328
|
}
|
|
39492
|
-
this.
|
|
40329
|
+
this.renderCrosshairLayer();
|
|
39493
40330
|
if (!repaintsData(level) && this.paintedData) this.repaintCursorLayers();
|
|
39494
40331
|
this.updateLegendValues();
|
|
39495
40332
|
}
|
|
40333
|
+
/** Repaint the L2 crosshair canvas and keep the `.vela-shade-right` element on the veil it painted. */
|
|
40334
|
+
renderCrosshairLayer() {
|
|
40335
|
+
this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
|
|
40336
|
+
this.syncShadeRight();
|
|
40337
|
+
}
|
|
40338
|
+
/** Position (or hide) the `.vela-shade-right` element over the veil of the last crosshair frame. */
|
|
40339
|
+
syncShadeRight() {
|
|
40340
|
+
const el = this.shadeRightEl;
|
|
40341
|
+
if (!el) return;
|
|
40342
|
+
const area = this.crosshairLayer.shadeRightArea;
|
|
40343
|
+
const sig = area ? `${area.x},${area.width},${area.height}` : "";
|
|
40344
|
+
if (sig === this.shadeRightSig) return;
|
|
40345
|
+
this.shadeRightSig = sig;
|
|
40346
|
+
if (!area) {
|
|
40347
|
+
el.hidden = true;
|
|
40348
|
+
el.style.display = "none";
|
|
40349
|
+
return;
|
|
40350
|
+
}
|
|
40351
|
+
Object.assign(el.style, { left: `${area.x}px`, width: `${area.width}px`, height: `${area.height}px`, display: "" });
|
|
40352
|
+
el.hidden = false;
|
|
40353
|
+
}
|
|
39496
40354
|
/** Repaint the SDK layers that opted into cursor tracking (their own canvas only). */
|
|
39497
40355
|
repaintCursorLayers() {
|
|
39498
40356
|
const pane = this.scene.panes.get(PRICE_PANE_ID2);
|
|
@@ -40286,7 +41144,7 @@ var NativeRenderer = class {
|
|
|
40286
41144
|
if (this.animator.active) {
|
|
40287
41145
|
this.computeScales();
|
|
40288
41146
|
this.paintData();
|
|
40289
|
-
this.
|
|
41147
|
+
this.renderCrosshairLayer();
|
|
40290
41148
|
} else {
|
|
40291
41149
|
this.scheduler.flushNow(4 /* Full */);
|
|
40292
41150
|
}
|
|
@@ -44301,7 +45159,8 @@ var Vela = class {
|
|
|
44301
45159
|
glow: options.glow ?? 0,
|
|
44302
45160
|
upColor: options.upColor ?? BULLISH,
|
|
44303
45161
|
downColor: options.downColor ?? BEARISH,
|
|
44304
|
-
priceStyle: options.priceStyle ?? "candles"
|
|
45162
|
+
priceStyle: options.priceStyle ?? "candles",
|
|
45163
|
+
priceAxisTicks: options.priceAxis?.ticks ?? null
|
|
44305
45164
|
};
|
|
44306
45165
|
const RendererClass = options.renderer ?? NativeRenderer;
|
|
44307
45166
|
if (typeof RendererClass !== "function") {
|
|
@@ -44791,6 +45650,9 @@ var CSS21 = `
|
|
|
44791
45650
|
.vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
|
|
44792
45651
|
.vela-statusline .vela-sl-market > [hidden] { display: none !important; }
|
|
44793
45652
|
.vela-statusline .vela-sl-replay-badge, .vela-statusline .vela-sl-replay-badge svg { display: block; width: 16px; height: 16px; }
|
|
45653
|
+
/* Tabular figures: every digit has one advance, so a readout whose text changes only in
|
|
45654
|
+
* its digits keeps its width \u2014 render() skips the ladder for those (see widthKey). */
|
|
45655
|
+
.vela-statusline .vela-sl-values { font-variant-numeric: tabular-nums; }
|
|
44794
45656
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
44795
45657
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
44796
45658
|
/* The change value wears the SAME ink as the OHLC values (set inline per render) \u2014
|
|
@@ -44908,6 +45770,10 @@ var Statusline = class {
|
|
|
44908
45770
|
this.fitRO = null;
|
|
44909
45771
|
/** The ladder rung currently applied — see {@link fit}. */
|
|
44910
45772
|
this.layout = { stacked: false, level: "full" };
|
|
45773
|
+
/** The readout {@link widthKey} the applied rung was fitted to. */
|
|
45774
|
+
this.fitKey = null;
|
|
45775
|
+
/** Web fonts landing after the last fit change every width without a resize. */
|
|
45776
|
+
this.onFontsLoaded = () => this.fit();
|
|
44911
45777
|
this.onContextMenu = (e) => {
|
|
44912
45778
|
if (!this.menu || !this.menuHooks) return;
|
|
44913
45779
|
e.preventDefault();
|
|
@@ -44971,6 +45837,7 @@ var Statusline = class {
|
|
|
44971
45837
|
this.fitRO = new ResizeObserver(() => this.fit());
|
|
44972
45838
|
this.fitRO.observe(host);
|
|
44973
45839
|
}
|
|
45840
|
+
doc.fonts?.addEventListener("loadingdone", this.onFontsLoaded);
|
|
44974
45841
|
this.marketTip = new Tooltip(this.marketEl, { content: MARKET_LABELS.open, placement: "bottom" });
|
|
44975
45842
|
this.eyeTip = new Tooltip(this.eyeEl, { content: "Show chart", placement: "bottom" });
|
|
44976
45843
|
this.setMarketStatus("open");
|
|
@@ -45023,6 +45890,7 @@ var Statusline = class {
|
|
|
45023
45890
|
* (detached host, node tests) nothing overflows, so the widest rung stays.
|
|
45024
45891
|
*/
|
|
45025
45892
|
fit() {
|
|
45893
|
+
this.fitKey = this.widthKey();
|
|
45026
45894
|
this.syncParts();
|
|
45027
45895
|
const seg = segmentVisibility(this.parts, this.chartHidden);
|
|
45028
45896
|
const overflows = () => this.el.scrollWidth > this.el.clientWidth;
|
|
@@ -45179,6 +46047,7 @@ var Statusline = class {
|
|
|
45179
46047
|
this.detach();
|
|
45180
46048
|
this.fitRO?.disconnect();
|
|
45181
46049
|
this.fitRO = null;
|
|
46050
|
+
this.el.ownerDocument.fonts?.removeEventListener("loadingdone", this.onFontsLoaded);
|
|
45182
46051
|
this.el.removeEventListener("contextmenu", this.onContextMenu);
|
|
45183
46052
|
this.menu?.destroy();
|
|
45184
46053
|
this.menu = null;
|
|
@@ -45194,7 +46063,22 @@ var Statusline = class {
|
|
|
45194
46063
|
}
|
|
45195
46064
|
render() {
|
|
45196
46065
|
if (this.menuHooks) this.setChartHidden(!this.menuHooks.chartVisible());
|
|
45197
|
-
this.
|
|
46066
|
+
if (this.widthKey() === this.fitKey) this.renderValues();
|
|
46067
|
+
else this.fit();
|
|
46068
|
+
}
|
|
46069
|
+
/** Everything the readout's width depends on, at every rung: its shape, and each
|
|
46070
|
+
* value's sign, digit count and decimals (the digits themselves are tabular — see
|
|
46071
|
+
* the CSS; the grouping commas follow from the digit count). `toFixed` carries all
|
|
46072
|
+
* three at a fraction of the locale formatter's cost — this runs per pointer move.
|
|
46073
|
+
* The parts, the host's size and the fonts re-fit through their own paths. */
|
|
46074
|
+
widthKey() {
|
|
46075
|
+
const bar = this.hoverBar ?? this.lastBar;
|
|
46076
|
+
if (!bar) return this.readout;
|
|
46077
|
+
const dp = decimalsFor(bar.close);
|
|
46078
|
+
const diff = bar.close - bar.open;
|
|
46079
|
+
const text = [bar.open, bar.high, bar.low, bar.close, diff].map((v) => v.toFixed(dp));
|
|
46080
|
+
text.push((diff / bar.open * 100).toFixed(2));
|
|
46081
|
+
return `${this.readout} ${text.join(" ")}`.replace(/\d/g, "0");
|
|
45198
46082
|
}
|
|
45199
46083
|
/** Write the value readout for the current bar at the current ladder level. */
|
|
45200
46084
|
renderValues() {
|
|
@@ -45794,16 +46678,25 @@ var CellControls = class {
|
|
|
45794
46678
|
// src/widget/context-menu.ts
|
|
45795
46679
|
var PRICE_AXIS_W = 60;
|
|
45796
46680
|
var TIME_AXIS_H3 = 26;
|
|
46681
|
+
var OFF_PLOT = { price: null, time: null, paneKind: null };
|
|
45797
46682
|
var ChartContextMenu = class {
|
|
45798
46683
|
constructor(host, cbs) {
|
|
45799
46684
|
this.cbs = cbs;
|
|
45800
46685
|
this.chart = null;
|
|
46686
|
+
this.offCrosshair = null;
|
|
46687
|
+
/** The bound chart's latest crosshair report — a right-click always follows a pointer
|
|
46688
|
+
* move to its spot, so this is where it landed. */
|
|
46689
|
+
this.crosshair = OFF_PLOT;
|
|
46690
|
+
/** {@link crosshair} as it stood when the menu opened: the pointer moves on through the
|
|
46691
|
+
* menu before a row is picked, and `run` must see the right-clicked spot. */
|
|
46692
|
+
this.pointer = OFF_PLOT;
|
|
45801
46693
|
this.lastZone = "body";
|
|
45802
46694
|
/** The pane whose scale the open price-axis menu targets (null ⇒ the main scale). */
|
|
45803
46695
|
this.lastPane = null;
|
|
45804
46696
|
this.onContextMenu = (e) => {
|
|
45805
46697
|
e.preventDefault();
|
|
45806
46698
|
if (!this.chart) return;
|
|
46699
|
+
this.pointer = this.crosshair;
|
|
45807
46700
|
this.lastZone = this.zoneOf(e);
|
|
45808
46701
|
this.lastPane = this.lastZone === "price-axis" ? this.paneAt(e) : null;
|
|
45809
46702
|
this.menu.setItems(this.itemsFor(this.lastZone));
|
|
@@ -45817,15 +46710,23 @@ var ChartContextMenu = class {
|
|
|
45817
46710
|
// Pointer-anchored action menu: checked state reads as a leading ✓, not a
|
|
45818
46711
|
// washed row (which would read as hover in a menu with no trigger button).
|
|
45819
46712
|
checkmarks: true,
|
|
46713
|
+
iconBadges: true,
|
|
45820
46714
|
onSelect: (id) => this.run(id)
|
|
45821
46715
|
});
|
|
45822
46716
|
host.addEventListener("contextmenu", this.onContextMenu);
|
|
45823
46717
|
}
|
|
45824
46718
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
45825
46719
|
onChart(chart) {
|
|
46720
|
+
this.offCrosshair?.();
|
|
45826
46721
|
this.chart = chart;
|
|
46722
|
+
this.crosshair = OFF_PLOT;
|
|
46723
|
+
this.offCrosshair = chart.renderer.onCrosshairMove((e) => {
|
|
46724
|
+
this.crosshair = { price: e.price, time: e.time, paneKind: e.paneKind ?? null };
|
|
46725
|
+
});
|
|
45827
46726
|
}
|
|
45828
46727
|
destroy() {
|
|
46728
|
+
this.offCrosshair?.();
|
|
46729
|
+
this.offCrosshair = null;
|
|
45829
46730
|
this.host.removeEventListener("contextmenu", this.onContextMenu);
|
|
45830
46731
|
this.menu.destroy();
|
|
45831
46732
|
}
|
|
@@ -45844,49 +46745,52 @@ var ChartContextMenu = class {
|
|
|
45844
46745
|
flag(feature) {
|
|
45845
46746
|
return Boolean(this.chart?.renderer.get(feature));
|
|
45846
46747
|
}
|
|
45847
|
-
|
|
46748
|
+
/** A fresh widget context carrying the pointer captured at open. Copied by property
|
|
46749
|
+
* descriptor: the context's getters must stay LIVE, and a spread would freeze them. */
|
|
46750
|
+
context() {
|
|
45848
46751
|
const ctx = this.cbs.getContext?.();
|
|
45849
|
-
|
|
45850
|
-
|
|
45851
|
-
|
|
45852
|
-
|
|
45853
|
-
|
|
46752
|
+
if (!ctx) return void 0;
|
|
46753
|
+
return Object.create(Object.getPrototypeOf(ctx), {
|
|
46754
|
+
...Object.getOwnPropertyDescriptors(ctx),
|
|
46755
|
+
pointer: { value: this.pointer, enumerable: true }
|
|
46756
|
+
});
|
|
46757
|
+
}
|
|
46758
|
+
contributed(zone) {
|
|
46759
|
+
return widgetActions(`context:${zone}`, this.context()).map((a) => ({
|
|
46760
|
+
item: { id: `action:${a.id}`, label: a.label, icon: a.icon },
|
|
46761
|
+
order: a.order
|
|
45854
46762
|
}));
|
|
45855
46763
|
}
|
|
45856
46764
|
itemsFor(zone) {
|
|
46765
|
+
return composeMenu(zone, this.builtinItems(zone), this.contributed(zone));
|
|
46766
|
+
}
|
|
46767
|
+
builtinItems(zone) {
|
|
45857
46768
|
if (zone === "price-axis") {
|
|
45858
46769
|
const pane = this.lastPane;
|
|
45859
|
-
return
|
|
45860
|
-
|
|
45861
|
-
|
|
45862
|
-
|
|
45863
|
-
|
|
45864
|
-
|
|
45865
|
-
|
|
45866
|
-
|
|
45867
|
-
|
|
45868
|
-
}),
|
|
45869
|
-
...this.contributed(zone)
|
|
45870
|
-
];
|
|
46770
|
+
return priceAxisItems({
|
|
46771
|
+
auto: this.chart?.renderer.get("autoScale") !== false,
|
|
46772
|
+
invert: pane ? pane.invert : this.flag("invertScale"),
|
|
46773
|
+
choice: scaleChoiceOf(pane ?? { mode: String(this.chart?.renderer.get("scaleMode") ?? "price"), log: this.flag("logScale") }),
|
|
46774
|
+
axisLabels: this.flag("axisLabels"),
|
|
46775
|
+
priceLabel: this.flag("priceLabel"),
|
|
46776
|
+
countdown: this.flag("countdown"),
|
|
46777
|
+
priceLine: this.flag("currentPriceLine")
|
|
46778
|
+
});
|
|
45871
46779
|
}
|
|
45872
46780
|
if (zone === "time-axis") {
|
|
45873
|
-
|
|
45874
|
-
return [...timeAxisItems(tz), ...this.contributed("time-axis")];
|
|
46781
|
+
return timeAxisItems(this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC"));
|
|
45875
46782
|
}
|
|
45876
46783
|
const chart = this.chart;
|
|
45877
|
-
return
|
|
45878
|
-
|
|
45879
|
-
|
|
45880
|
-
|
|
45881
|
-
}),
|
|
45882
|
-
...this.contributed("body")
|
|
45883
|
-
];
|
|
46784
|
+
return bodyItems({
|
|
46785
|
+
drawings: chart?.drawings.supported ? chart.drawings.all().length : 0,
|
|
46786
|
+
indicators: chart?.indicators().length ?? 0
|
|
46787
|
+
});
|
|
45884
46788
|
}
|
|
45885
46789
|
run(id) {
|
|
45886
46790
|
const chart = this.chart;
|
|
45887
46791
|
if (!chart) return;
|
|
45888
46792
|
if (id.startsWith("action:")) {
|
|
45889
|
-
const ctx = this.
|
|
46793
|
+
const ctx = this.context();
|
|
45890
46794
|
if (ctx) widgetActions(`context:${this.lastZone}`, ctx).find((a) => a.id === id.slice("action:".length))?.run(ctx);
|
|
45891
46795
|
return;
|
|
45892
46796
|
}
|
|
@@ -46010,8 +46914,8 @@ function seedDefaults(opts) {
|
|
|
46010
46914
|
};
|
|
46011
46915
|
}
|
|
46012
46916
|
function cellChartDefaults(opts) {
|
|
46013
|
-
const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings } = opts;
|
|
46014
|
-
return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings };
|
|
46917
|
+
const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis } = opts;
|
|
46918
|
+
return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis };
|
|
46015
46919
|
}
|
|
46016
46920
|
function cellDrawings(opt) {
|
|
46017
46921
|
if (opt === false) return false;
|
|
@@ -46581,12 +47485,14 @@ var ChartCell = class {
|
|
|
46581
47485
|
this.watermarkOn = visible;
|
|
46582
47486
|
this.watermark?.setVisible(visible);
|
|
46583
47487
|
this.deps.onStateDirty();
|
|
47488
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
46584
47489
|
}
|
|
46585
47490
|
/** Show/hide the "Replay" line under this cell's watermark while it replays (persisted per cell). */
|
|
46586
47491
|
setReplayWatermarkVisible(visible) {
|
|
46587
47492
|
this.replayWatermarkOn = visible;
|
|
46588
47493
|
this.watermark?.setReplayVisible(visible);
|
|
46589
47494
|
this.deps.onStateDirty();
|
|
47495
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
46590
47496
|
}
|
|
46591
47497
|
/** Show/hide this cell's indicator titles — the in-chart legend rows (persisted per cell). */
|
|
46592
47498
|
setIndicatorTitlesVisible(visible) {
|
|
@@ -46607,18 +47513,20 @@ var ChartCell = class {
|
|
|
46607
47513
|
this.statusline?.setPartVisible(part, visible);
|
|
46608
47514
|
this.deps.onStatusPrefsChanged(this.id);
|
|
46609
47515
|
}
|
|
46610
|
-
/** This cell's Status line tab prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
47516
|
+
/** This cell's Status line tab and watermark prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
46611
47517
|
statusPrefs() {
|
|
46612
47518
|
const sl = this.statusline;
|
|
46613
47519
|
return {
|
|
46614
47520
|
parts: sl ? { logo: sl.partVisible("logo"), name: sl.partVisible("name"), market: sl.partVisible("market"), ohlc: sl.partVisible("ohlc"), change: sl.partVisible("change") } : null,
|
|
46615
47521
|
indicatorTitles: this.indicatorTitlesOn,
|
|
46616
|
-
indicatorValues: this.indicatorValuesOn
|
|
47522
|
+
indicatorValues: this.indicatorValuesOn,
|
|
47523
|
+
watermark: this.watermarkOn,
|
|
47524
|
+
replayWatermark: this.replayWatermarkOn
|
|
46617
47525
|
};
|
|
46618
47526
|
}
|
|
46619
|
-
/** Converge this cell's Status line tab prefs to `prefs` — the
|
|
46620
|
-
* the workspace's style link. Idempotent: matching values change
|
|
46621
|
-
* propagated echo dies on its own. */
|
|
47527
|
+
/** Converge this cell's Status line tab and watermark prefs to `prefs` — the
|
|
47528
|
+
* follower half of the workspace's style link. Idempotent: matching values change
|
|
47529
|
+
* nothing, so a propagated echo dies on its own. */
|
|
46622
47530
|
applyStatusPrefs(prefs) {
|
|
46623
47531
|
if (prefs.parts && this.statusline) {
|
|
46624
47532
|
for (const part of Object.keys(prefs.parts)) {
|
|
@@ -46627,6 +47535,8 @@ var ChartCell = class {
|
|
|
46627
47535
|
}
|
|
46628
47536
|
if (prefs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(prefs.indicatorTitles);
|
|
46629
47537
|
if (prefs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(prefs.indicatorValues);
|
|
47538
|
+
if (prefs.watermark !== this.watermarkOn) this.setWatermarkVisible(prefs.watermark);
|
|
47539
|
+
if (prefs.replayWatermark !== this.replayWatermarkOn) this.setReplayWatermarkVisible(prefs.replayWatermark);
|
|
46630
47540
|
}
|
|
46631
47541
|
/** The LIVE chart of this cell — never cache it across a layout change (the cell's
|
|
46632
47542
|
* identity is what endures; the chart dies with the cell). */
|
|
@@ -47531,6 +48441,55 @@ function occupancyGrid(def) {
|
|
|
47531
48441
|
const cols = def.cols.length;
|
|
47532
48442
|
return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ?? `\xB7${r}x${c}`));
|
|
47533
48443
|
}
|
|
48444
|
+
var GLYPH_MIN = 1.5;
|
|
48445
|
+
var GLYPH_SPAN = 13;
|
|
48446
|
+
function glyphStops(weights, n) {
|
|
48447
|
+
const w = Array.from({ length: n }, (_, i) => {
|
|
48448
|
+
const v = weights[i];
|
|
48449
|
+
return v !== void 0 && Number.isFinite(v) && v > 0 ? v : 1;
|
|
48450
|
+
});
|
|
48451
|
+
const total = w.reduce((a, b) => a + b, 0);
|
|
48452
|
+
const stops = [GLYPH_MIN];
|
|
48453
|
+
let acc = 0;
|
|
48454
|
+
for (const v of w) {
|
|
48455
|
+
acc += v;
|
|
48456
|
+
stops.push(Math.round((GLYPH_MIN + GLYPH_SPAN * acc / total) * 100) / 100);
|
|
48457
|
+
}
|
|
48458
|
+
return stops;
|
|
48459
|
+
}
|
|
48460
|
+
function layoutGlyph(def) {
|
|
48461
|
+
const grid = occupancyGrid(def);
|
|
48462
|
+
const rows = grid.length;
|
|
48463
|
+
const cols = grid.reduce((n, row) => Math.max(n, row.length), 0);
|
|
48464
|
+
const xs = glyphStops(def.cols, cols);
|
|
48465
|
+
const ys = glyphStops(def.rows, rows);
|
|
48466
|
+
const at = (r, c) => grid[r]?.[c];
|
|
48467
|
+
const seams = [];
|
|
48468
|
+
for (let c = 1; c < cols; c += 1) {
|
|
48469
|
+
let start = -1;
|
|
48470
|
+
for (let r = 0; r <= rows; r += 1) {
|
|
48471
|
+
const split = r < rows && at(r, c - 1) !== at(r, c);
|
|
48472
|
+
if (split && start < 0) start = r;
|
|
48473
|
+
if (!split && start >= 0) {
|
|
48474
|
+
seams.push(`M${xs[c]} ${ys[start]}V${ys[r]}`);
|
|
48475
|
+
start = -1;
|
|
48476
|
+
}
|
|
48477
|
+
}
|
|
48478
|
+
}
|
|
48479
|
+
for (let r = 1; r < rows; r += 1) {
|
|
48480
|
+
let start = -1;
|
|
48481
|
+
for (let c = 0; c <= cols; c += 1) {
|
|
48482
|
+
const split = c < cols && at(r - 1, c) !== at(r, c);
|
|
48483
|
+
if (split && start < 0) start = c;
|
|
48484
|
+
if (!split && start >= 0) {
|
|
48485
|
+
seams.push(`M${xs[start]} ${ys[r]}H${xs[c]}`);
|
|
48486
|
+
start = -1;
|
|
48487
|
+
}
|
|
48488
|
+
}
|
|
48489
|
+
}
|
|
48490
|
+
const frame = `<rect x="${GLYPH_MIN}" y="${GLYPH_MIN}" width="${GLYPH_SPAN}" height="${GLYPH_SPAN}" rx="1.5"/>`;
|
|
48491
|
+
return svg16(seams.length > 0 ? `${frame}<path d="${seams.join("")}"/>` : frame);
|
|
48492
|
+
}
|
|
47534
48493
|
function gridStyles(def, trackSizes) {
|
|
47535
48494
|
const cols = trackSizes?.cols?.length === def.cols.length ? trackSizes.cols : def.cols;
|
|
47536
48495
|
const rows = trackSizes?.rows?.length === def.rows.length ? trackSizes.rows : def.rows;
|
|
@@ -48062,7 +49021,8 @@ var VelaWorkspace = class {
|
|
|
48062
49021
|
onToggleSync: (id) => {
|
|
48063
49022
|
const kind = id;
|
|
48064
49023
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
48065
|
-
}
|
|
49024
|
+
},
|
|
49025
|
+
glyph: () => layoutGlyph(this.def)
|
|
48066
49026
|
},
|
|
48067
49027
|
getContext: () => this.context()
|
|
48068
49028
|
});
|
|
@@ -48561,6 +49521,7 @@ var VelaWorkspace = class {
|
|
|
48561
49521
|
this.applyGrid();
|
|
48562
49522
|
this.buildCells();
|
|
48563
49523
|
this.alignNewCellStyles(preexisting);
|
|
49524
|
+
this.alignNewCellDrawings(preexisting);
|
|
48564
49525
|
this.syncCellPresentation();
|
|
48565
49526
|
this.refreshCellControls();
|
|
48566
49527
|
this.topbar.setLayout(next.id);
|
|
@@ -48977,6 +49938,7 @@ var VelaWorkspace = class {
|
|
|
48977
49938
|
const chart = cell.chart;
|
|
48978
49939
|
chart.on("indicator:error", ({ error }) => this.toastHost.show(`[${cell.id}] ${error.message}`, "error", 5e3));
|
|
48979
49940
|
chart.on("script:run", (run) => this.events.emit("script:run", { ...run, cell: cell.id }));
|
|
49941
|
+
chart.on("priceStyle:change", ({ from, to }) => this.events.emit("cell:priceStyle", { id: cell.id, from, to }));
|
|
48980
49942
|
chart.on("alert", (alert) => {
|
|
48981
49943
|
const source = [parseSymbol(cell.symbol).ticker || cell.symbol, timeframeLabel(cell.timeframe), alert.indicator].filter(Boolean).join(" ");
|
|
48982
49944
|
this.alerts.unshift({ cellId: cell.id, source, title: alert.title ?? "Alert", message: alert.message, time: alert.time });
|
|
@@ -49094,12 +50056,66 @@ var VelaWorkspace = class {
|
|
|
49094
50056
|
}
|
|
49095
50057
|
}
|
|
49096
50058
|
/**
|
|
49097
|
-
*
|
|
49098
|
-
*
|
|
49099
|
-
*
|
|
49100
|
-
*
|
|
49101
|
-
* in
|
|
49102
|
-
*
|
|
50059
|
+
* Bring cells minted by a layout change into their drawings group: with the link
|
|
50060
|
+
* on, a NEW cell (fresh slot, or one returning from the pool that missed edits
|
|
50061
|
+
* while dormant) receives the drawings of a pre-existing group peer — the active
|
|
50062
|
+
* cell when it is one. A drawing the arriving cell already holds a linked copy of
|
|
50063
|
+
* is refreshed in place; any other is copied and linked like a freshly synced
|
|
50064
|
+
* drawing, so later edits and removals follow both ways. The arrival is the cell's
|
|
50065
|
+
* starting state, not an edit: it stays out of the cell's undo timeline, and the
|
|
50066
|
+
* busy guard keeps the copies' own events from fanning back out.
|
|
50067
|
+
*/
|
|
50068
|
+
alignNewCellDrawings(preexisting) {
|
|
50069
|
+
const setting = this.syncOpts.drawings;
|
|
50070
|
+
if (!setting) return;
|
|
50071
|
+
const ids = [...this.cellsById.keys()];
|
|
50072
|
+
for (const id of ids) {
|
|
50073
|
+
if (preexisting.has(id)) continue;
|
|
50074
|
+
const cell = this.cellsById.get(id);
|
|
50075
|
+
if (!cell?.chart.drawings.supported) continue;
|
|
50076
|
+
const peers = syncTargets(id, setting, ids).filter((p) => preexisting.has(p));
|
|
50077
|
+
if (peers.length === 0) continue;
|
|
50078
|
+
const sourceId = this.activeId && peers.includes(this.activeId) ? this.activeId : peers[0];
|
|
50079
|
+
const source = this.cellsById.get(sourceId);
|
|
50080
|
+
if (!source) continue;
|
|
50081
|
+
const drawings = cell.chart.drawings;
|
|
50082
|
+
this.drawingSyncBusy = true;
|
|
50083
|
+
try {
|
|
50084
|
+
cell.history.silently(() => {
|
|
50085
|
+
const held = new Set(drawings.all().map((d) => d.id));
|
|
50086
|
+
for (const doc of source.chart.drawings.all()) {
|
|
50087
|
+
const group = this.drawingLinks.get(`${sourceId}\0${doc.id}`) ?? /* @__PURE__ */ new Map([[sourceId, doc.id]]);
|
|
50088
|
+
const peerId = group.get(id);
|
|
50089
|
+
if (peerId != null && held.has(peerId)) {
|
|
50090
|
+
drawings.update(peerId, { anchors: doc.anchors, style: doc.style, text: doc.text, props: doc.props });
|
|
50091
|
+
continue;
|
|
50092
|
+
}
|
|
50093
|
+
const copy = drawings.add(doc.type, {
|
|
50094
|
+
paneId: doc.paneId,
|
|
50095
|
+
anchors: doc.anchors,
|
|
50096
|
+
style: doc.style,
|
|
50097
|
+
text: doc.text,
|
|
50098
|
+
props: doc.props,
|
|
50099
|
+
zIndex: doc.zIndex
|
|
50100
|
+
});
|
|
50101
|
+
if (!copy) continue;
|
|
50102
|
+
if (peerId != null) this.drawingLinks.delete(`${id}\0${peerId}`);
|
|
50103
|
+
group.set(id, copy.id);
|
|
50104
|
+
for (const [cellId, dId] of group) this.drawingLinks.set(`${cellId}\0${dId}`, group);
|
|
50105
|
+
}
|
|
50106
|
+
});
|
|
50107
|
+
} finally {
|
|
50108
|
+
this.drawingSyncBusy = false;
|
|
50109
|
+
}
|
|
50110
|
+
}
|
|
50111
|
+
}
|
|
50112
|
+
/**
|
|
50113
|
+
* Mirror an origin cell's presentation — the style-link slice of its renderer
|
|
50114
|
+
* config ({@link styleConfigPatch}) plus its Status line and watermark prefs —
|
|
50115
|
+
* onto its same-group followers (the style link). Loop-safe two ways: the busy
|
|
50116
|
+
* guard eats the followers' SYNCHRONOUS echoes (their `applyConfig` re-fires
|
|
50117
|
+
* `onConfigChanged` in the same tick), and the equality short-circuits leave
|
|
50118
|
+
* already-converged followers untouched, so nothing re-emits once the group agrees.
|
|
49103
50119
|
*/
|
|
49104
50120
|
propagateStylePrefs(originId) {
|
|
49105
50121
|
if (this.styleSyncBusy || this.destroyed) return;
|
|
@@ -49107,17 +50123,15 @@ var VelaWorkspace = class {
|
|
|
49107
50123
|
if (targets.length === 0) return;
|
|
49108
50124
|
const origin = this.cellsById.get(originId);
|
|
49109
50125
|
if (!origin) return;
|
|
49110
|
-
const
|
|
49111
|
-
const sliceJson = slice ? JSON.stringify(slice) : null;
|
|
50126
|
+
const config = origin.chart.renderer.getConfig();
|
|
49112
50127
|
const prefs = origin.statusPrefs();
|
|
49113
50128
|
this.styleSyncBusy = true;
|
|
49114
50129
|
try {
|
|
49115
50130
|
for (const id of targets) {
|
|
49116
50131
|
const cell = this.cellsById.get(id);
|
|
49117
50132
|
if (!cell) continue;
|
|
49118
|
-
|
|
49119
|
-
|
|
49120
|
-
}
|
|
50133
|
+
const patch = styleConfigPatch(config, cell.chart.renderer.getConfig());
|
|
50134
|
+
if (patch) cell.chart.renderer.applyConfig(patch);
|
|
49121
50135
|
cell.applyStatusPrefs(prefs);
|
|
49122
50136
|
}
|
|
49123
50137
|
} finally {
|
|
@@ -49438,7 +50452,8 @@ var VelaWorkspace = class {
|
|
|
49438
50452
|
onToggleSync: (id) => {
|
|
49439
50453
|
const kind = id;
|
|
49440
50454
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
49441
|
-
}
|
|
50455
|
+
},
|
|
50456
|
+
glyph: () => layoutGlyph(this.def)
|
|
49442
50457
|
},
|
|
49443
50458
|
onOpenChange: (open2) => this.trackDialog(open2)
|
|
49444
50459
|
}));
|
|
@@ -49702,6 +50717,7 @@ var VelaWidget = class {
|
|
|
49702
50717
|
};
|
|
49703
50718
|
|
|
49704
50719
|
exports.Bottombar = Bottombar;
|
|
50720
|
+
exports.CONTEXT_MENU_BUILTIN_ORDER = CONTEXT_MENU_BUILTIN_ORDER;
|
|
49705
50721
|
exports.ChartContextMenu = ChartContextMenu;
|
|
49706
50722
|
exports.DEFAULT_PANEL_MAX_WIDTH = DEFAULT_PANEL_MAX_WIDTH;
|
|
49707
50723
|
exports.DEFAULT_PANEL_MIN_WIDTH = DEFAULT_PANEL_MIN_WIDTH;
|