@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/workspace.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`;
|
|
@@ -5288,10 +5582,12 @@ function legendActions() {
|
|
|
5288
5582
|
}
|
|
5289
5583
|
function legendActionsProviderFor(chart, context) {
|
|
5290
5584
|
return (indicatorId) => {
|
|
5291
|
-
const
|
|
5585
|
+
const find = () => chart.indicators().find((h) => h.id === indicatorId);
|
|
5586
|
+
const infoOf = (h) => ({ id: h.id, title: h.title, ...h.source !== void 0 ? { source: h.source } : {} });
|
|
5587
|
+
const handle = find();
|
|
5292
5588
|
if (!handle) return [];
|
|
5293
|
-
const info =
|
|
5294
|
-
return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(),
|
|
5589
|
+
const info = infoOf(handle);
|
|
5590
|
+
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)) }));
|
|
5295
5591
|
};
|
|
5296
5592
|
}
|
|
5297
5593
|
var calloutRegistry = /* @__PURE__ */ new Map();
|
|
@@ -6139,7 +6435,8 @@ var Topbar = class {
|
|
|
6139
6435
|
triggerId: "vela-topbar-style",
|
|
6140
6436
|
host,
|
|
6141
6437
|
items: this.styleItems(),
|
|
6142
|
-
onSelect: (id) => opts.onPriceStyle(id)
|
|
6438
|
+
onSelect: (id) => opts.onPriceStyle(id),
|
|
6439
|
+
iconBadges: true
|
|
6143
6440
|
});
|
|
6144
6441
|
}
|
|
6145
6442
|
setSymbol(symbol) {
|
|
@@ -6205,8 +6502,14 @@ var Topbar = class {
|
|
|
6205
6502
|
renderLayoutButton(doc) {
|
|
6206
6503
|
if (!this.layoutButton) return;
|
|
6207
6504
|
this.layoutButton.replaceChildren();
|
|
6208
|
-
|
|
6209
|
-
|
|
6505
|
+
const glyph = this.opts.layout?.glyph?.();
|
|
6506
|
+
if (glyph || iconMarkup("layout")) {
|
|
6507
|
+
const icon2 = iconEl("layout", doc);
|
|
6508
|
+
if (glyph) icon2.innerHTML = glyph;
|
|
6509
|
+
this.layoutButton.appendChild(icon2);
|
|
6510
|
+
} else {
|
|
6511
|
+
this.layoutButton.appendChild(doc.createTextNode(this.layoutId ?? ""));
|
|
6512
|
+
}
|
|
6210
6513
|
this.layoutButton.setAttribute("aria-label", `Layout \u2014 ${this.layoutId ?? ""}`);
|
|
6211
6514
|
}
|
|
6212
6515
|
renderStyleButton(doc) {
|
|
@@ -9489,6 +9792,10 @@ var FibRatios = class extends Drawing {
|
|
|
9489
9792
|
|
|
9490
9793
|
// src/core/drawings/types/FibLevels.ts
|
|
9491
9794
|
var FibLevels = class extends FibRatios {
|
|
9795
|
+
/** Price of a level for the two anchor prices; subclasses override to change which anchor ratio 0 sits on. */
|
|
9796
|
+
levelPrice(ratio, p1, p2) {
|
|
9797
|
+
return p1 + ratio * (p2 - p1);
|
|
9798
|
+
}
|
|
9492
9799
|
/** Per-level pixel line + price for the ENABLED levels, spanning the anchors' time range. */
|
|
9493
9800
|
levelLines(proj) {
|
|
9494
9801
|
const a = this.anchors[0];
|
|
@@ -9498,11 +9805,10 @@ var FibLevels = class extends FibRatios {
|
|
|
9498
9805
|
const xb = proj.xOf(b.time);
|
|
9499
9806
|
const x1 = Math.min(xa, xb);
|
|
9500
9807
|
const x2 = Math.max(xa, xb);
|
|
9501
|
-
const delta = b.price - a.price;
|
|
9502
9808
|
const out = [];
|
|
9503
9809
|
for (const lv of this.levels) {
|
|
9504
9810
|
if (!lv.enabled) continue;
|
|
9505
|
-
const price =
|
|
9811
|
+
const price = this.levelPrice(lv.ratio, a.price, b.price);
|
|
9506
9812
|
const y = proj.yOf(price, this.paneId);
|
|
9507
9813
|
if (y == null) continue;
|
|
9508
9814
|
out.push({ ratio: lv.ratio, color: lv.color, label: lv.label, price, x1, x2, y });
|
|
@@ -9542,8 +9848,7 @@ var FibLevels = class extends FibRatios {
|
|
|
9542
9848
|
const a = this.anchors[0];
|
|
9543
9849
|
const b = this.anchors[1];
|
|
9544
9850
|
if (!a || !b) return null;
|
|
9545
|
-
const
|
|
9546
|
-
const prices = this.levels.filter((l) => l.enabled).map((l) => a.price + l.ratio * delta);
|
|
9851
|
+
const prices = this.levels.filter((l) => l.enabled).map((l) => this.levelPrice(l.ratio, a.price, b.price));
|
|
9547
9852
|
if (prices.length === 0) return null;
|
|
9548
9853
|
return { min: Math.min(...prices), max: Math.max(...prices) };
|
|
9549
9854
|
}
|
|
@@ -9629,13 +9934,24 @@ function cycleLevels(count, enabledCount = count) {
|
|
|
9629
9934
|
// src/core/drawings/types/FibRetracement.ts
|
|
9630
9935
|
var LEVELS = fibLevels([0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]);
|
|
9631
9936
|
var FibRetracement = class extends FibLevels {
|
|
9632
|
-
constructor() {
|
|
9633
|
-
super(
|
|
9937
|
+
constructor(init) {
|
|
9938
|
+
super(init);
|
|
9634
9939
|
this.type = "fibretracement";
|
|
9940
|
+
if (this.reverse === void 0) this.reverse = false;
|
|
9635
9941
|
}
|
|
9636
9942
|
defaultLevels() {
|
|
9637
9943
|
return LEVELS;
|
|
9638
9944
|
}
|
|
9945
|
+
levelPrice(ratio, p1, p2) {
|
|
9946
|
+
return this.reverse ? p1 + ratio * (p2 - p1) : p2 + ratio * (p1 - p2);
|
|
9947
|
+
}
|
|
9948
|
+
writeProps() {
|
|
9949
|
+
return { ...super.writeProps(), reverse: this.reverse };
|
|
9950
|
+
}
|
|
9951
|
+
readProps(props) {
|
|
9952
|
+
super.readProps(props);
|
|
9953
|
+
this.reverse = typeof props.reverse === "boolean" ? props.reverse : true;
|
|
9954
|
+
}
|
|
9639
9955
|
};
|
|
9640
9956
|
|
|
9641
9957
|
// src/core/drawings/types/FibExtension.ts
|
|
@@ -12762,6 +13078,32 @@ var CSS5 = `
|
|
|
12762
13078
|
.vela-panel-pin .vela-icon { width: 15px; height: 15px; }
|
|
12763
13079
|
.vela-panel-pin:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12764
13080
|
.vela-panel-pin[data-on='1'] { color: var(--vela-fg-bright); }
|
|
13081
|
+
.vela-panel-max {
|
|
13082
|
+
all: unset;
|
|
13083
|
+
cursor: pointer;
|
|
13084
|
+
width: 26px;
|
|
13085
|
+
height: 26px;
|
|
13086
|
+
flex: none;
|
|
13087
|
+
display: inline-flex;
|
|
13088
|
+
align-items: center;
|
|
13089
|
+
justify-content: center;
|
|
13090
|
+
border-radius: 4px;
|
|
13091
|
+
color: var(--vela-fg-muted);
|
|
13092
|
+
}
|
|
13093
|
+
.vela-panel-max .vela-icon { width: 15px; height: 15px; }
|
|
13094
|
+
.vela-panel-max:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
13095
|
+
/* MAXIMIZED: the panel covers the whole dock host \u2014 every chart of the shell \u2014 whatever
|
|
13096
|
+
its placement; the width handle and the pin have nothing to do meanwhile. */
|
|
13097
|
+
.vela-panel[data-maximized] {
|
|
13098
|
+
position: absolute;
|
|
13099
|
+
inset: 0;
|
|
13100
|
+
width: auto;
|
|
13101
|
+
max-width: none;
|
|
13102
|
+
z-index: 26;
|
|
13103
|
+
border-left: none;
|
|
13104
|
+
}
|
|
13105
|
+
.vela-panel[data-maximized] .vela-panel-resizer,
|
|
13106
|
+
.vela-panel[data-maximized] .vela-panel-pin { display: none; }
|
|
12765
13107
|
.vela-panel-body { flex: 1; overflow: auto; padding: 8px; }
|
|
12766
13108
|
.vela-panel-body::-webkit-scrollbar { width: 8px; }
|
|
12767
13109
|
.vela-panel-body::-webkit-scrollbar-thumb { background: var(--vela-scroll); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
|
|
@@ -12814,6 +13156,7 @@ var CSS5 = `
|
|
|
12814
13156
|
border-left: none;
|
|
12815
13157
|
}
|
|
12816
13158
|
[data-layout='mobile'] .vela-panel-resizer { display: none; }
|
|
13159
|
+
[data-layout='mobile'] .vela-panel-max { display: none; }
|
|
12817
13160
|
`;
|
|
12818
13161
|
function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_MAX_WIDTH) {
|
|
12819
13162
|
const lo = Number.isFinite(min) && min > 0 ? min : DEFAULT_PANEL_MIN_WIDTH;
|
|
@@ -12837,6 +13180,15 @@ var SidePanel = class {
|
|
|
12837
13180
|
* never on a programmatic {@link setOverlay}, for the same reason as widths. */
|
|
12838
13181
|
this.onPlacementChange = null;
|
|
12839
13182
|
this.pin = null;
|
|
13183
|
+
this.maxButton = null;
|
|
13184
|
+
this.maximizedOn = false;
|
|
13185
|
+
/** Set while the close is announced, so a listener's own close does not re-enter. */
|
|
13186
|
+
this.closing = false;
|
|
13187
|
+
/** Open as far as the dock and the host are concerned — a closed panel can still be on
|
|
13188
|
+
* screen while its exit animation runs. */
|
|
13189
|
+
this.isOpen = false;
|
|
13190
|
+
/** The exit animation of a close; a reopen cancels it. */
|
|
13191
|
+
this.exit = null;
|
|
12840
13192
|
const doc = host.ownerDocument;
|
|
12841
13193
|
injectStyles(STYLE_ID4, CSS5, doc);
|
|
12842
13194
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
@@ -12871,7 +13223,13 @@ var SidePanel = class {
|
|
|
12871
13223
|
});
|
|
12872
13224
|
this.refreshPin();
|
|
12873
13225
|
}
|
|
12874
|
-
|
|
13226
|
+
if (opts.maximizable) {
|
|
13227
|
+
this.maxButton = doc.createElement("button");
|
|
13228
|
+
this.maxButton.className = "vela-panel-max";
|
|
13229
|
+
this.maxButton.addEventListener("click", () => this.setMaximized(!this.maximizedOn));
|
|
13230
|
+
this.refreshMaxButton();
|
|
13231
|
+
}
|
|
13232
|
+
header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], ...this.maxButton ? [this.maxButton] : [], close);
|
|
12875
13233
|
this.body = doc.createElement("div");
|
|
12876
13234
|
this.body.className = "vela-panel-body";
|
|
12877
13235
|
this.el.append(header, this.body);
|
|
@@ -12879,13 +13237,65 @@ var SidePanel = class {
|
|
|
12879
13237
|
host.appendChild(this.el);
|
|
12880
13238
|
}
|
|
12881
13239
|
get open() {
|
|
12882
|
-
return
|
|
13240
|
+
return this.isOpen;
|
|
12883
13241
|
}
|
|
12884
|
-
/**
|
|
12885
|
-
|
|
12886
|
-
|
|
12887
|
-
|
|
13242
|
+
/**
|
|
13243
|
+
* Open/close the panel — a bare call flips it. The panel reports closed at once, but a
|
|
13244
|
+
* close keeps it on screen, marked `data-closing`, while a host exit animation runs.
|
|
13245
|
+
* `instant` skips that, and also cuts short the exit of a panel already closing: a panel
|
|
13246
|
+
* handing the dock to another leaves at once.
|
|
13247
|
+
*/
|
|
13248
|
+
toggle(open2 = !this.isOpen, instant = false) {
|
|
13249
|
+
if (open2 === this.isOpen) {
|
|
13250
|
+
if (!open2 && instant) this.exit?.finish();
|
|
13251
|
+
return;
|
|
13252
|
+
}
|
|
13253
|
+
if (!open2 && this.closing) return;
|
|
13254
|
+
if (!open2) {
|
|
13255
|
+
this.closing = true;
|
|
13256
|
+
announceSurface(this.el, false, "panel");
|
|
13257
|
+
this.closing = false;
|
|
13258
|
+
}
|
|
13259
|
+
this.isOpen = open2;
|
|
13260
|
+
if (open2) {
|
|
13261
|
+
if (this.exit) {
|
|
13262
|
+
this.exit.cancel();
|
|
13263
|
+
this.exit = null;
|
|
13264
|
+
this.setMaximized(false);
|
|
13265
|
+
}
|
|
13266
|
+
this.el.hidden = false;
|
|
13267
|
+
} else {
|
|
13268
|
+
const hide = () => {
|
|
13269
|
+
this.exit = null;
|
|
13270
|
+
this.el.hidden = true;
|
|
13271
|
+
this.setMaximized(false);
|
|
13272
|
+
};
|
|
13273
|
+
if (instant) hide();
|
|
13274
|
+
else this.exit = holdForExit(this.el, hide);
|
|
13275
|
+
}
|
|
12888
13276
|
this.onOpenChange?.(open2);
|
|
13277
|
+
if (open2 && this.isOpen) announceSurface(this.el, true, "panel");
|
|
13278
|
+
}
|
|
13279
|
+
/** Whether the panel covers every chart right now. */
|
|
13280
|
+
get maximized() {
|
|
13281
|
+
return this.maximizedOn;
|
|
13282
|
+
}
|
|
13283
|
+
/** Maximize the panel over the shell's charts, or restore it. A no-op on a panel not
|
|
13284
|
+
* declared `maximizable`. */
|
|
13285
|
+
setMaximized(maximized) {
|
|
13286
|
+
if (!this.maxButton || maximized === this.maximizedOn) return;
|
|
13287
|
+
this.maximizedOn = maximized;
|
|
13288
|
+
if (maximized) this.el.dataset.maximized = "1";
|
|
13289
|
+
else delete this.el.dataset.maximized;
|
|
13290
|
+
this.refreshMaxButton();
|
|
13291
|
+
}
|
|
13292
|
+
refreshMaxButton() {
|
|
13293
|
+
if (!this.maxButton) return;
|
|
13294
|
+
const doc = this.maxButton.ownerDocument;
|
|
13295
|
+
this.maxButton.replaceChildren(iconEl(this.maximizedOn ? "restore" : "maximize", doc));
|
|
13296
|
+
this.maxButton.title = this.maximizedOn ? "Restore" : "Maximize";
|
|
13297
|
+
this.maxButton.setAttribute("aria-label", this.maxButton.title);
|
|
13298
|
+
this.maxButton.setAttribute("aria-pressed", this.maximizedOn ? "true" : "false");
|
|
12889
13299
|
}
|
|
12890
13300
|
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
12891
13301
|
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
@@ -12937,6 +13347,8 @@ var SidePanel = class {
|
|
|
12937
13347
|
this.pin.setAttribute("aria-pressed", pinned ? "true" : "false");
|
|
12938
13348
|
}
|
|
12939
13349
|
destroy() {
|
|
13350
|
+
if (this.open) announceSurface(this.el, false, "panel");
|
|
13351
|
+
this.exit?.finish();
|
|
12940
13352
|
this.el.remove();
|
|
12941
13353
|
}
|
|
12942
13354
|
/** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
|
|
@@ -13241,8 +13653,8 @@ var ObjectTree = class extends SidePanel {
|
|
|
13241
13653
|
set groups(next) {
|
|
13242
13654
|
if (this.chart) this.groupsPerChart.set(this.chart, next);
|
|
13243
13655
|
}
|
|
13244
|
-
toggle(open2 = this.
|
|
13245
|
-
super.toggle(open2);
|
|
13656
|
+
toggle(open2 = !this.open, instant = false) {
|
|
13657
|
+
super.toggle(open2, instant);
|
|
13246
13658
|
if (open2) this.refresh();
|
|
13247
13659
|
}
|
|
13248
13660
|
setSymbol(symbol) {
|
|
@@ -14276,8 +14688,8 @@ var DataWindow = class extends SidePanel {
|
|
|
14276
14688
|
this.unsubs = [];
|
|
14277
14689
|
injectStyles(STYLE_ID6, CSS7, host.ownerDocument);
|
|
14278
14690
|
}
|
|
14279
|
-
toggle(open2 = this.
|
|
14280
|
-
super.toggle(open2);
|
|
14691
|
+
toggle(open2 = !this.open, instant = false) {
|
|
14692
|
+
super.toggle(open2, instant);
|
|
14281
14693
|
if (open2) this.refresh();
|
|
14282
14694
|
}
|
|
14283
14695
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
@@ -14353,19 +14765,22 @@ var PanelDock = class {
|
|
|
14353
14765
|
/**
|
|
14354
14766
|
* (Re)build the CONTRIBUTED panels from the registry — call once after the built-ins, and
|
|
14355
14767
|
* again on `refreshActions()` so a late registration appears. Contributed panels that are
|
|
14356
|
-
* gone from the registry are dropped
|
|
14357
|
-
* descriptor
|
|
14768
|
+
* gone from the registry are dropped and a REPLACED descriptor is rebuilt; a panel whose
|
|
14769
|
+
* descriptor is unchanged stays mounted as it is, so its content and state survive.
|
|
14358
14770
|
*/
|
|
14359
14771
|
refresh() {
|
|
14360
14772
|
const openBefore = this.openId;
|
|
14361
|
-
|
|
14362
|
-
for (const
|
|
14773
|
+
const registered = sidePanels();
|
|
14774
|
+
for (const entry of [...this.entries]) if (entry.contributed && !registered.includes(entry.desc)) this.drop(entry);
|
|
14775
|
+
for (const desc of registered) {
|
|
14776
|
+
if (this.entries.some((e) => e.desc === desc)) continue;
|
|
14363
14777
|
const panel = new SidePanel(this.host, desc.title, `vela-panel-${desc.id}`, {
|
|
14364
14778
|
width: desc.width,
|
|
14365
14779
|
resizable: desc.resizable,
|
|
14366
14780
|
minWidth: desc.minWidth,
|
|
14367
14781
|
maxWidth: desc.maxWidth,
|
|
14368
|
-
overlay: desc.overlay
|
|
14782
|
+
overlay: desc.overlay,
|
|
14783
|
+
maximizable: desc.maximizable
|
|
14369
14784
|
});
|
|
14370
14785
|
const entry = {
|
|
14371
14786
|
id: desc.id,
|
|
@@ -14373,10 +14788,19 @@ var PanelDock = class {
|
|
|
14373
14788
|
icon: desc.icon,
|
|
14374
14789
|
order: desc.order ?? DEFAULT_PANEL_ORDER,
|
|
14375
14790
|
panel,
|
|
14376
|
-
contributed: true
|
|
14791
|
+
contributed: true,
|
|
14792
|
+
desc
|
|
14377
14793
|
};
|
|
14378
14794
|
try {
|
|
14379
|
-
|
|
14795
|
+
const header = {
|
|
14796
|
+
slot: panel.headerSlot,
|
|
14797
|
+
setTitle: (t) => panel.setTitle(t),
|
|
14798
|
+
get maximized() {
|
|
14799
|
+
return panel.maximized;
|
|
14800
|
+
},
|
|
14801
|
+
setMaximized: (on) => panel.setMaximized(on)
|
|
14802
|
+
};
|
|
14803
|
+
entry.handle = desc.mount(this.deps.context(), panel.content, header) ?? void 0;
|
|
14380
14804
|
if (this.chart) entry.handle?.onChart?.(this.chart);
|
|
14381
14805
|
} catch (err) {
|
|
14382
14806
|
console.warn(`[vela] side panel "${desc.id}" failed to mount`, err);
|
|
@@ -14435,7 +14859,7 @@ var PanelDock = class {
|
|
|
14435
14859
|
}
|
|
14436
14860
|
this.pinned = new Set(state.pinned ?? []);
|
|
14437
14861
|
for (const entry of this.entries) entry.panel.setOverlay(!this.pinned.has(entry.id));
|
|
14438
|
-
for (const entry of this.entries) entry.panel.toggle(entry.id === state.open);
|
|
14862
|
+
for (const entry of this.entries) entry.panel.toggle(entry.id === state.open, true);
|
|
14439
14863
|
this.pendingOpen = state.open && !this.entries.some((e) => e.id === state.open) ? state.open : null;
|
|
14440
14864
|
}
|
|
14441
14865
|
/** Drop the contributed panels (the shell destroys its own). */
|
|
@@ -14451,7 +14875,7 @@ var PanelDock = class {
|
|
|
14451
14875
|
if (this.pinned.has(entry.id)) entry.panel.setOverlay(false);
|
|
14452
14876
|
entry.panel.onOpenChange = (open2) => {
|
|
14453
14877
|
if (open2) {
|
|
14454
|
-
for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
|
|
14878
|
+
for (const other of this.entries) if (other !== entry) other.panel.toggle(false, true);
|
|
14455
14879
|
this.pendingOpen = null;
|
|
14456
14880
|
}
|
|
14457
14881
|
this.deps.chrome.setPanelActive(entry.id, open2);
|
|
@@ -16127,7 +16551,11 @@ var MoreDrawer = class {
|
|
|
16127
16551
|
row(doc, label, opts) {
|
|
16128
16552
|
const el = doc.createElement("div");
|
|
16129
16553
|
el.className = "vela-md-row";
|
|
16130
|
-
if (opts.icon)
|
|
16554
|
+
if (opts.icon) {
|
|
16555
|
+
const icon2 = iconEl(opts.icon, doc);
|
|
16556
|
+
if (opts.glyph) icon2.innerHTML = opts.glyph;
|
|
16557
|
+
el.appendChild(icon2);
|
|
16558
|
+
}
|
|
16131
16559
|
const text = doc.createElement("span");
|
|
16132
16560
|
text.className = "vela-md-row-label";
|
|
16133
16561
|
text.textContent = label;
|
|
@@ -16169,7 +16597,7 @@ var MoreDrawer = class {
|
|
|
16169
16597
|
if (this.opts.layout) {
|
|
16170
16598
|
const shape = this.opts.layout.shape();
|
|
16171
16599
|
const value = shape ? `${shape.cols} \xD7 ${shape.rows}` : this.opts.layout.presets().find((p) => p.checked)?.label;
|
|
16172
|
-
list.appendChild(this.row(doc, "Layout", { icon: "layout", value, chevron: true, onClick: () => this.show("layout") }));
|
|
16600
|
+
list.appendChild(this.row(doc, "Layout", { icon: "layout", glyph: this.opts.layout.glyph?.(), value, chevron: true, onClick: () => this.show("layout") }));
|
|
16173
16601
|
}
|
|
16174
16602
|
for (const act of this.opts.primaryActions?.() ?? []) list.appendChild(this.actionRow(doc, act));
|
|
16175
16603
|
for (const panel of this.opts.panels()) {
|
|
@@ -16347,8 +16775,8 @@ var SETTINGS_SECTION = {
|
|
|
16347
16775
|
"price-axis": "Scales and lines",
|
|
16348
16776
|
"time-axis": "Scales and lines"
|
|
16349
16777
|
};
|
|
16350
|
-
function settingsItem(zone, label) {
|
|
16351
|
-
return { id: `settings:${SETTINGS_SECTION[zone]}`, label, separatorBefore: true };
|
|
16778
|
+
function settingsItem(zone, label, icon2) {
|
|
16779
|
+
return { id: `settings:${SETTINGS_SECTION[zone]}`, label, ...icon2 ? { icon: icon2 } : {}, separatorBefore: true };
|
|
16352
16780
|
}
|
|
16353
16781
|
function settingsSectionOf(id) {
|
|
16354
16782
|
return id.slice("settings:".length) || void 0;
|
|
@@ -16428,12 +16856,39 @@ function timeAxisItems(timezone) {
|
|
|
16428
16856
|
}
|
|
16429
16857
|
function bodyItems(counts) {
|
|
16430
16858
|
return [
|
|
16431
|
-
{ id: "reset-view", label: "Reset chart view" },
|
|
16432
|
-
{ id: "remove-drawings", label: "Remove drawings", disabled: counts.drawings === 0, separatorBefore: true },
|
|
16433
|
-
{ id: "remove-indicators", label: "Remove indicators", disabled: counts.indicators === 0 },
|
|
16434
|
-
settingsItem("body", "Settings\u2026")
|
|
16859
|
+
{ id: "reset-view", label: "Reset chart view", icon: "reset" },
|
|
16860
|
+
{ id: "remove-drawings", label: "Remove drawings", icon: "eraser", disabled: counts.drawings === 0, separatorBefore: true },
|
|
16861
|
+
{ id: "remove-indicators", label: "Remove indicators", icon: "indicators", disabled: counts.indicators === 0 },
|
|
16862
|
+
settingsItem("body", "Settings\u2026", "gear")
|
|
16435
16863
|
];
|
|
16436
16864
|
}
|
|
16865
|
+
var CONTEXT_MENU_BUILTIN_ORDER = {
|
|
16866
|
+
body: { "reset-view": -30, "remove-drawings": -20, "remove-indicators": -10, settings: 1e3 },
|
|
16867
|
+
"price-axis": {
|
|
16868
|
+
auto: -80,
|
|
16869
|
+
invert: -70,
|
|
16870
|
+
"scale:regular": -60,
|
|
16871
|
+
"scale:percent": -50,
|
|
16872
|
+
"scale:indexed": -40,
|
|
16873
|
+
"scale:log": -30,
|
|
16874
|
+
labels: -20,
|
|
16875
|
+
levels: -10,
|
|
16876
|
+
settings: 1e3
|
|
16877
|
+
},
|
|
16878
|
+
"time-axis": { timezone: -10, settings: 1e3 }
|
|
16879
|
+
};
|
|
16880
|
+
function composeMenu(zone, builtin, contributed) {
|
|
16881
|
+
const ranks = CONTEXT_MENU_BUILTIN_ORDER[zone];
|
|
16882
|
+
const rows = [];
|
|
16883
|
+
let group = 0;
|
|
16884
|
+
for (const item of builtin) {
|
|
16885
|
+
if (item.separatorBefore) group += 1;
|
|
16886
|
+
rows.push({ item, rank: ranks[item.id.startsWith("settings") ? "settings" : item.id] ?? 0, group });
|
|
16887
|
+
}
|
|
16888
|
+
for (const { item, order } of contributed) rows.push({ item, rank: order ?? 0, group: -1 });
|
|
16889
|
+
rows.sort((a, b) => a.rank - b.rank);
|
|
16890
|
+
return rows.map(({ item, group: g }, i) => ({ ...item, separatorBefore: i > 0 && rows[i - 1].group !== g }));
|
|
16891
|
+
}
|
|
16437
16892
|
|
|
16438
16893
|
// src/widget/price-scale-drawer.ts
|
|
16439
16894
|
var STYLE_ID17 = "vela-widget-price-scale-drawer";
|
|
@@ -17708,6 +18163,7 @@ function chromeHint(text, opts) {
|
|
|
17708
18163
|
// src/renderers/native/drawings/DrawingToolbar.ts
|
|
17709
18164
|
var TOOLBAR_WIDTH = 44;
|
|
17710
18165
|
var TOOLBAR_COLLAPSED_WIDTH = 16;
|
|
18166
|
+
var FLYOUT_GAP = 3;
|
|
17711
18167
|
var DrawingToolbar = class {
|
|
17712
18168
|
constructor(host, theme, onArm, onMagnet = () => {
|
|
17713
18169
|
}, onMeasure = () => {
|
|
@@ -17737,6 +18193,9 @@ var DrawingToolbar = class {
|
|
|
17737
18193
|
// group id (or MAGNET_ID) whose flyout is open
|
|
17738
18194
|
this.flyoutCell = null;
|
|
17739
18195
|
// the cell the open flyout is anchored to
|
|
18196
|
+
/** Closed flyouts still playing their exit animation, by owner id — reopening the same
|
|
18197
|
+
* owner takes its element back instead of stacking a second one on it. */
|
|
18198
|
+
this.exitingFlyouts = /* @__PURE__ */ new Map();
|
|
17740
18199
|
this.groupCells = /* @__PURE__ */ new Map();
|
|
17741
18200
|
// the composite cell (hover/active bg + flyout anchor)
|
|
17742
18201
|
this.groupIcons = /* @__PURE__ */ new Map();
|
|
@@ -17845,6 +18304,7 @@ var DrawingToolbar = class {
|
|
|
17845
18304
|
}
|
|
17846
18305
|
destroy() {
|
|
17847
18306
|
this.closeFlyout();
|
|
18307
|
+
for (const { exit } of [...this.exitingFlyouts.values()]) exit.finish();
|
|
17848
18308
|
for (const dispose of this.tipDisposers.splice(0)) dispose();
|
|
17849
18309
|
this.root.remove();
|
|
17850
18310
|
}
|
|
@@ -18072,16 +18532,22 @@ var DrawingToolbar = class {
|
|
|
18072
18532
|
* outside-dismiss. Callers fill it with items. */
|
|
18073
18533
|
beginFlyout(ownerId, cell) {
|
|
18074
18534
|
const t = this.theme;
|
|
18075
|
-
const
|
|
18535
|
+
const back = this.exitingFlyouts.get(ownerId);
|
|
18536
|
+
if (back) {
|
|
18537
|
+
this.exitingFlyouts.delete(ownerId);
|
|
18538
|
+
back.exit.cancel();
|
|
18539
|
+
back.fly.replaceChildren();
|
|
18540
|
+
}
|
|
18541
|
+
const fly = back?.fly ?? document.createElement("div");
|
|
18076
18542
|
fly.className = "vela-dtb-flyout";
|
|
18077
|
-
fly.style.cssText = `position:absolute;z-index:23;display:flex;flex-direction:column;gap:2px;padding:4px;border-radius:
|
|
18543
|
+
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;`;
|
|
18078
18544
|
applyChromeTokens(fly, t);
|
|
18079
18545
|
this.host.appendChild(fly);
|
|
18080
18546
|
const r = cell.getBoundingClientRect();
|
|
18081
18547
|
const rootR = this.root.getBoundingClientRect();
|
|
18082
18548
|
const hostR = this.host.getBoundingClientRect();
|
|
18083
18549
|
const top = r.top - hostR.top;
|
|
18084
|
-
fly.style.left = `${rootR.right - hostR.left
|
|
18550
|
+
fly.style.left = `${rootR.right - hostR.left + FLYOUT_GAP}px`;
|
|
18085
18551
|
fly.style.top = `${top}px`;
|
|
18086
18552
|
fly.style.maxHeight = `${Math.max(120, hostR.height - top - 8)}px`;
|
|
18087
18553
|
this.flyout = fly;
|
|
@@ -18097,6 +18563,7 @@ var DrawingToolbar = class {
|
|
|
18097
18563
|
if (!cell) return;
|
|
18098
18564
|
const fly = this.beginFlyout(group.id, cell);
|
|
18099
18565
|
const sections = group.sections ?? [{ label: "", tools: group.tools }];
|
|
18566
|
+
const badge = sections.some((s) => s.tools.some((tool) => !!tool.icon));
|
|
18100
18567
|
for (let si = 0; si < sections.length; si++) {
|
|
18101
18568
|
const section = sections[si];
|
|
18102
18569
|
if (section.label) {
|
|
@@ -18107,6 +18574,7 @@ var DrawingToolbar = class {
|
|
|
18107
18574
|
fly.appendChild(
|
|
18108
18575
|
this.makeFlyoutItem({
|
|
18109
18576
|
icon: tool.icon,
|
|
18577
|
+
badge,
|
|
18110
18578
|
label: tool.label,
|
|
18111
18579
|
selected: tool.type === this.active,
|
|
18112
18580
|
shortcut: this.shortcuts.get(tool.type),
|
|
@@ -18119,6 +18587,7 @@ var DrawingToolbar = class {
|
|
|
18119
18587
|
);
|
|
18120
18588
|
}
|
|
18121
18589
|
}
|
|
18590
|
+
announceSurface(fly, true, "menu", cell);
|
|
18122
18591
|
}
|
|
18123
18592
|
openMagnetFlyout() {
|
|
18124
18593
|
const cell = this.magnetCell;
|
|
@@ -18142,6 +18611,7 @@ var DrawingToolbar = class {
|
|
|
18142
18611
|
})
|
|
18143
18612
|
);
|
|
18144
18613
|
}
|
|
18614
|
+
announceSurface(fly, true, "menu", cell);
|
|
18145
18615
|
}
|
|
18146
18616
|
flyoutHeader(text) {
|
|
18147
18617
|
const t = this.theme;
|
|
@@ -18155,21 +18625,24 @@ var DrawingToolbar = class {
|
|
|
18155
18625
|
div.style.cssText = `height:1px;margin:4px 8px;background:${this.borderColor};`;
|
|
18156
18626
|
return div;
|
|
18157
18627
|
}
|
|
18158
|
-
/** A flyout row, left to right: optional leading icon, label, a check when it's the
|
|
18159
|
-
* entry, an optional shortcut hint, and — at the far right — the favorite star (tool
|
|
18160
|
-
* Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
|
|
18628
|
+
/** A flyout row, left to right: optional leading icon badge, label, a check when it's the
|
|
18629
|
+
* selected entry, an optional shortcut hint, and — at the far right — the favorite star (tool
|
|
18630
|
+
* rows). Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
|
|
18161
18631
|
makeFlyoutItem(opts) {
|
|
18162
18632
|
const t = this.theme;
|
|
18163
18633
|
const item = document.createElement("button");
|
|
18164
18634
|
item.type = "button";
|
|
18165
18635
|
item.className = "vela-dtb-item";
|
|
18166
18636
|
item.setAttribute("aria-label", opts.label);
|
|
18167
|
-
|
|
18168
|
-
|
|
18169
|
-
|
|
18170
|
-
|
|
18171
|
-
|
|
18172
|
-
|
|
18637
|
+
const badged = opts.badge === true || !!opts.icon;
|
|
18638
|
+
const box = badged ? "gap:10px;padding:4px 10px 4px 6px;" : "gap:8px;padding:5px 10px 5px 8px;";
|
|
18639
|
+
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;`;
|
|
18640
|
+
if (opts.selected) item.dataset.selected = "1";
|
|
18641
|
+
if (badged) {
|
|
18642
|
+
const badge = document.createElement("span");
|
|
18643
|
+
badge.className = "vela-dtb-badge";
|
|
18644
|
+
if (opts.icon) badge.innerHTML = opts.icon;
|
|
18645
|
+
item.appendChild(badge);
|
|
18173
18646
|
}
|
|
18174
18647
|
const label = document.createElement("span");
|
|
18175
18648
|
label.textContent = opts.label;
|
|
@@ -18210,9 +18683,16 @@ var DrawingToolbar = class {
|
|
|
18210
18683
|
}
|
|
18211
18684
|
closeFlyout() {
|
|
18212
18685
|
this.starEls.clear();
|
|
18213
|
-
|
|
18214
|
-
|
|
18686
|
+
const fly = this.flyout;
|
|
18687
|
+
if (fly) {
|
|
18215
18688
|
this.flyout = null;
|
|
18689
|
+
announceSurface(fly, false, "menu", this.flyoutCell);
|
|
18690
|
+
const owner = this.flyoutOwnerId ?? "";
|
|
18691
|
+
const exit = holdForExit(fly, () => {
|
|
18692
|
+
if (this.exitingFlyouts.get(owner)?.fly === fly) this.exitingFlyouts.delete(owner);
|
|
18693
|
+
fly.remove();
|
|
18694
|
+
});
|
|
18695
|
+
if (exit) this.exitingFlyouts.set(owner, { fly, exit });
|
|
18216
18696
|
this.flyoutCell?.classList.remove("vela-open");
|
|
18217
18697
|
this.flyoutCell = null;
|
|
18218
18698
|
this.flyoutOwnerId = null;
|
|
@@ -18296,6 +18776,11 @@ function ensureStyles() {
|
|
|
18296
18776
|
.vela-dtb[data-collapsed='1'] .vela-dtb-collapse .vela-dtb-hit{width:14px;}
|
|
18297
18777
|
.vela-dtb-item{background:transparent;border:none;transition:background var(--vela-dur-fast) ease;}
|
|
18298
18778
|
.vela-dtb-item:hover{background:var(--vela-hover-strong);}
|
|
18779
|
+
.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;}
|
|
18780
|
+
.vela-dtb-badge:empty{background:transparent;border-color:transparent;}
|
|
18781
|
+
.vela-dtb-badge svg{width:100%;height:100%;}
|
|
18782
|
+
.vela-dtb-item:hover .vela-dtb-badge:not(:empty){background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
18783
|
+
.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);}
|
|
18299
18784
|
.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;}
|
|
18300
18785
|
.vela-dtb-star svg{width:16px;height:16px;}
|
|
18301
18786
|
.vela-dtb-item:hover .vela-dtb-star{opacity:.55;}
|
|
@@ -18663,15 +19148,48 @@ function syncTargets(originId, setting, cellIds) {
|
|
|
18663
19148
|
function rangesWithin(a, b, epsMs) {
|
|
18664
19149
|
return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
|
|
18665
19150
|
}
|
|
18666
|
-
var STYLE_SYNC_CONFIG_KEYS = [
|
|
18667
|
-
|
|
18668
|
-
|
|
18669
|
-
|
|
18670
|
-
|
|
19151
|
+
var STYLE_SYNC_CONFIG_KEYS = [
|
|
19152
|
+
"layout",
|
|
19153
|
+
"panes",
|
|
19154
|
+
"grid",
|
|
19155
|
+
"margins",
|
|
19156
|
+
"priceScale",
|
|
19157
|
+
"crosshair",
|
|
19158
|
+
"animations",
|
|
19159
|
+
"candles",
|
|
19160
|
+
"bars",
|
|
19161
|
+
"line",
|
|
19162
|
+
"area",
|
|
19163
|
+
"baseline",
|
|
19164
|
+
"sessions"
|
|
19165
|
+
];
|
|
19166
|
+
function styleConfigPatch(origin, follower) {
|
|
19167
|
+
const from = asRecord(origin);
|
|
19168
|
+
const to = asRecord(follower);
|
|
19169
|
+
if (!from || !to) return null;
|
|
19170
|
+
const patch = {};
|
|
18671
19171
|
for (const key of STYLE_SYNC_CONFIG_KEYS) {
|
|
18672
|
-
|
|
19172
|
+
const block = asRecord(from[key]);
|
|
19173
|
+
if (block && JSON.stringify(block) !== JSON.stringify(to[key])) patch[key] = block;
|
|
18673
19174
|
}
|
|
18674
|
-
|
|
19175
|
+
const spacing = asRecord(from.series)?.spacing;
|
|
19176
|
+
if (typeof spacing === "number" && spacing !== asRecord(to.series)?.spacing) patch.series = { spacing };
|
|
19177
|
+
const fromTypes = asRecord(from.chartTypes) ?? {};
|
|
19178
|
+
const toTypes = asRecord(to.chartTypes) ?? {};
|
|
19179
|
+
const types = {};
|
|
19180
|
+
for (const typeId of /* @__PURE__ */ new Set([...Object.keys(fromTypes), ...Object.keys(toTypes)])) {
|
|
19181
|
+
const src = asRecord(fromTypes[typeId]) ?? {};
|
|
19182
|
+
const dst = asRecord(toTypes[typeId]) ?? {};
|
|
19183
|
+
for (const key of CANDLE_OVERRIDE_KEYS) {
|
|
19184
|
+
const value = src[key] ?? null;
|
|
19185
|
+
if (value !== (dst[key] ?? null)) (types[typeId] ?? (types[typeId] = {}))[key] = value;
|
|
19186
|
+
}
|
|
19187
|
+
}
|
|
19188
|
+
if (Object.keys(types).length > 0) patch.chartTypes = types;
|
|
19189
|
+
return Object.keys(patch).length > 0 ? patch : null;
|
|
19190
|
+
}
|
|
19191
|
+
function asRecord(v) {
|
|
19192
|
+
return v != null && typeof v === "object" ? v : null;
|
|
18675
19193
|
}
|
|
18676
19194
|
|
|
18677
19195
|
// src/workspace/persist.ts
|
|
@@ -18881,7 +19399,7 @@ var IndicatorHandleImpl = class {
|
|
|
18881
19399
|
this.controller.applyProps(this.id, values);
|
|
18882
19400
|
}
|
|
18883
19401
|
updateCode(source) {
|
|
18884
|
-
this.controller.updateCode(this.id, source);
|
|
19402
|
+
return this.controller.updateCode(this.id, source);
|
|
18885
19403
|
}
|
|
18886
19404
|
setVisible(visible) {
|
|
18887
19405
|
this.controller.setVisible(this.id, visible);
|
|
@@ -19360,6 +19878,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19360
19878
|
this.activeEngineStyle = null;
|
|
19361
19879
|
/** The chart's current price style (tracked from the renderer's change events + initial read). */
|
|
19362
19880
|
this.priceStyle = "candles";
|
|
19881
|
+
this.priceStyleWillChangeUnsub = null;
|
|
19363
19882
|
this.unresolvedUnsub = null;
|
|
19364
19883
|
/** Latest chart visible range (left/right bar times), fed to viewport-dependent scripts. */
|
|
19365
19884
|
this.visibleRange = null;
|
|
@@ -19458,6 +19977,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19458
19977
|
this.renderer.onThemeSelect?.((name) => this.setTheme(resolveTheme(name)));
|
|
19459
19978
|
this.viewportUnsub = this.renderer.onViewportChange((range) => this.onViewportChange(range));
|
|
19460
19979
|
this.renderer.onPriceStyleChange?.((style) => this.syncPriceStyle(style));
|
|
19980
|
+
this.priceStyleWillChangeUnsub = this.renderer.onPriceStyleWillChange?.((from, to) => this.events.emit("priceStyle:change", { from, to })) ?? null;
|
|
19461
19981
|
const initialStyle = this.renderer.readFeature("priceStyle");
|
|
19462
19982
|
if (typeof initialStyle === "string") this.priceStyle = initialStyle;
|
|
19463
19983
|
this.barTransform = barTransformFor(initialStyle);
|
|
@@ -20744,20 +21264,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20744
21264
|
/**
|
|
20745
21265
|
* IndicatorController: replace a SCRIPT indicator's source in place (see
|
|
20746
21266
|
* {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
|
|
20747
|
-
* computing until the new source has compiled
|
|
20748
|
-
*
|
|
21267
|
+
* computing until the new source has compiled; then the new code runs on TRIAL until
|
|
21268
|
+
* its first model lands, and a failure before that puts the previous code back.
|
|
21269
|
+
* Natives have no script — warn and leave them alone.
|
|
20749
21270
|
*/
|
|
20750
21271
|
updateCode(id, source) {
|
|
20751
21272
|
const record = this.registry.get(id);
|
|
20752
21273
|
const handle = this.handles.get(id);
|
|
20753
|
-
if (!record || !handle) return;
|
|
21274
|
+
if (!record || !handle) return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator is no longer on the chart.`) });
|
|
20754
21275
|
if (record.native) {
|
|
20755
21276
|
console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
|
|
20756
|
-
return;
|
|
21277
|
+
return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`) });
|
|
20757
21278
|
}
|
|
20758
|
-
|
|
20759
|
-
record.
|
|
20760
|
-
|
|
21279
|
+
const busy = record.pendingSource !== void 0 || record.codeTrial !== void 0;
|
|
21280
|
+
if (!busy && source === record.source) return Promise.resolve({ ok: true, error: null });
|
|
21281
|
+
return new Promise((resolve) => {
|
|
21282
|
+
(record.codeWaiters ?? (record.codeWaiters = [])).push(resolve);
|
|
21283
|
+
if (record.pendingSource === source || record.pendingSource === void 0 && record.codeTrial && record.source === source) return;
|
|
21284
|
+
record.pendingSource = source;
|
|
21285
|
+
void this.swapSource(id, source, handle);
|
|
21286
|
+
});
|
|
20761
21287
|
}
|
|
20762
21288
|
async swapSource(id, source, handle) {
|
|
20763
21289
|
const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
|
|
@@ -20768,26 +21294,41 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20768
21294
|
const record2 = this.registry.get(id);
|
|
20769
21295
|
if (record2?.pendingSource === source) {
|
|
20770
21296
|
record2.pendingSource = void 0;
|
|
20771
|
-
this.
|
|
21297
|
+
this.rejectCode(id, record2, handle, err);
|
|
20772
21298
|
}
|
|
20773
21299
|
return;
|
|
20774
21300
|
}
|
|
20775
21301
|
const record = this.registry.get(id);
|
|
20776
21302
|
if (!record || record.pendingSource !== source) return;
|
|
20777
21303
|
record.pendingSource = void 0;
|
|
21304
|
+
const previous = record.codeTrial?.previous ?? {
|
|
21305
|
+
source: record.source,
|
|
21306
|
+
engine: record.engine,
|
|
21307
|
+
prepared: record.prepared,
|
|
21308
|
+
inputValues: record.inputValues,
|
|
21309
|
+
propValues: record.propValues,
|
|
21310
|
+
inputs: [...handle.inputs],
|
|
21311
|
+
props: [...handle.props]
|
|
21312
|
+
};
|
|
21313
|
+
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues, record.prepared?.inputs);
|
|
21314
|
+
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues, record.prepared?.props);
|
|
20778
21315
|
record.source = source;
|
|
20779
21316
|
record.engine = engine;
|
|
20780
21317
|
record.prepared = prepared;
|
|
20781
21318
|
handle.setSource(source);
|
|
20782
|
-
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
|
|
20783
|
-
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
|
|
20784
21319
|
handle.setSchema(prepared.inputs);
|
|
20785
21320
|
handle.setPropsSchema(prepared.props ?? []);
|
|
20786
21321
|
record.session?.stop();
|
|
20787
21322
|
record.session = void 0;
|
|
20788
21323
|
record.pendingStructural = true;
|
|
20789
21324
|
record.pendingCause = "code";
|
|
20790
|
-
if (record.hidden)
|
|
21325
|
+
if (record.hidden) {
|
|
21326
|
+
record.codeTrial = void 0;
|
|
21327
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
21328
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21329
|
+
return;
|
|
21330
|
+
}
|
|
21331
|
+
record.codeTrial = { previous };
|
|
20791
21332
|
if (record.renderHandle) {
|
|
20792
21333
|
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
20793
21334
|
this.setLoading(record, true);
|
|
@@ -20795,6 +21336,46 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20795
21336
|
this.mountLoadingPlaceholder(id, record);
|
|
20796
21337
|
}
|
|
20797
21338
|
this.executeIndicator(id, handle);
|
|
21339
|
+
if (this.bars.length === 0 && record.codeTrial) {
|
|
21340
|
+
record.codeTrial = void 0;
|
|
21341
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21342
|
+
}
|
|
21343
|
+
}
|
|
21344
|
+
/** Resolve every `updateCode` caller waiting on this record's chain of edits. */
|
|
21345
|
+
settleCode(record, result) {
|
|
21346
|
+
const waiters = record.codeWaiters;
|
|
21347
|
+
record.codeWaiters = void 0;
|
|
21348
|
+
if (waiters) for (const resolve of waiters) resolve(result);
|
|
21349
|
+
}
|
|
21350
|
+
/**
|
|
21351
|
+
* An update failed — at prepare, or on trial before its first model. A trial puts the
|
|
21352
|
+
* previous code back (source, engine, prepared script, values, schema) and restarts it;
|
|
21353
|
+
* its visuals were never dropped, so the chart keeps painting throughout. Then the
|
|
21354
|
+
* failure reports the usual way and every waiting caller learns it.
|
|
21355
|
+
*/
|
|
21356
|
+
rejectCode(id, record, handle, err) {
|
|
21357
|
+
const trial = record.codeTrial;
|
|
21358
|
+
record.codeTrial = void 0;
|
|
21359
|
+
this.fail(id, handle, err);
|
|
21360
|
+
if (trial) {
|
|
21361
|
+
const p = trial.previous;
|
|
21362
|
+
record.session?.stop();
|
|
21363
|
+
record.session = void 0;
|
|
21364
|
+
record.source = p.source;
|
|
21365
|
+
record.engine = p.engine;
|
|
21366
|
+
record.prepared = p.prepared;
|
|
21367
|
+
record.inputValues = p.inputValues;
|
|
21368
|
+
record.propValues = p.propValues;
|
|
21369
|
+
handle.setSource(p.source);
|
|
21370
|
+
handle.setSchema(p.inputs);
|
|
21371
|
+
handle.setPropsSchema(p.props);
|
|
21372
|
+
record.pendingStructural = true;
|
|
21373
|
+
record.pendingCause = void 0;
|
|
21374
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
21375
|
+
if (!record.hidden) this.executeIndicator(id, handle);
|
|
21376
|
+
}
|
|
21377
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
21378
|
+
this.settleCode(record, { ok: false, error });
|
|
20798
21379
|
}
|
|
20799
21380
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
20800
21381
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
@@ -20809,6 +21390,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20809
21390
|
removeIndicator(id) {
|
|
20810
21391
|
const record = this.registry.remove(id);
|
|
20811
21392
|
if (record?.native?.type === "volume") this.volumeOptedOut = true;
|
|
21393
|
+
if (record) this.settleCode(record, { ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator was removed.`) });
|
|
20812
21394
|
record?.session?.stop();
|
|
20813
21395
|
if (record?.native?.started) record.native.instance.stop();
|
|
20814
21396
|
this.handles.delete(id);
|
|
@@ -20836,6 +21418,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20836
21418
|
if (!visible) {
|
|
20837
21419
|
record.session?.stop();
|
|
20838
21420
|
record.session = void 0;
|
|
21421
|
+
if (record.codeTrial) {
|
|
21422
|
+
record.codeTrial = void 0;
|
|
21423
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21424
|
+
}
|
|
20839
21425
|
if (record.native?.started) record.native.instance.suspend();
|
|
20840
21426
|
if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
|
|
20841
21427
|
else if (record.native) this.mountHiddenNativeRow(id, record);
|
|
@@ -20922,7 +21508,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20922
21508
|
if (this.viewportTimer != null) clearTimeout(this.viewportTimer);
|
|
20923
21509
|
this.viewportUnsub?.();
|
|
20924
21510
|
this.paneActionUnsub?.();
|
|
21511
|
+
this.priceStyleWillChangeUnsub?.();
|
|
21512
|
+
this.priceStyleWillChangeUnsub = null;
|
|
20925
21513
|
for (const record of this.registry.all()) {
|
|
21514
|
+
this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
|
|
20926
21515
|
record.session?.stop();
|
|
20927
21516
|
if (record.native?.started) record.native.instance.stop();
|
|
20928
21517
|
}
|
|
@@ -20991,7 +21580,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20991
21580
|
const record = this.registry.get(id);
|
|
20992
21581
|
if (!record || !record.engine || !record.prepared || record.hidden) return;
|
|
20993
21582
|
const mode = this.config.live && !record.prepared.reactsToViewport && record.engine.capabilities.streaming ? "live" : "static";
|
|
20994
|
-
|
|
21583
|
+
const prepared = record.prepared;
|
|
21584
|
+
const session = record.engine.execute(
|
|
20995
21585
|
{
|
|
20996
21586
|
prepared: record.prepared,
|
|
20997
21587
|
market: this.market(),
|
|
@@ -21012,6 +21602,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21012
21602
|
const cause = record.pendingCause ?? "history";
|
|
21013
21603
|
if (!this.applyModel(id, model)) return;
|
|
21014
21604
|
record.pendingCause = void 0;
|
|
21605
|
+
if (record.codeTrial) {
|
|
21606
|
+
record.codeTrial = void 0;
|
|
21607
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21608
|
+
}
|
|
21015
21609
|
this.emitContextChanged(id);
|
|
21016
21610
|
this.emitScriptRun(id, cause, first);
|
|
21017
21611
|
},
|
|
@@ -21022,9 +21616,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21022
21616
|
handle.emit("alert", { id: a.id, message: a.message, title: a.title, time: a.time });
|
|
21023
21617
|
},
|
|
21024
21618
|
onWarning: (w) => this.events.emit("warning", w),
|
|
21025
|
-
onError: (err) =>
|
|
21619
|
+
onError: (err) => {
|
|
21620
|
+
if (record.codeTrial) this.rejectCode(id, record, handle, err);
|
|
21621
|
+
else this.fail(id, handle, err);
|
|
21622
|
+
}
|
|
21026
21623
|
}
|
|
21027
21624
|
);
|
|
21625
|
+
if (record.prepared !== prepared) {
|
|
21626
|
+
session.stop();
|
|
21627
|
+
return;
|
|
21628
|
+
}
|
|
21629
|
+
record.session = session;
|
|
21028
21630
|
this.emitContextChanged(id);
|
|
21029
21631
|
}
|
|
21030
21632
|
/**
|
|
@@ -21525,7 +22127,7 @@ function yieldToPaint() {
|
|
|
21525
22127
|
}
|
|
21526
22128
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
21527
22129
|
}
|
|
21528
|
-
function valuesOnSchema(schema, previous) {
|
|
22130
|
+
function valuesOnSchema(schema, previous, oldSchema = []) {
|
|
21529
22131
|
const out = {};
|
|
21530
22132
|
const declared = /* @__PURE__ */ new Set();
|
|
21531
22133
|
for (const s of schema) {
|
|
@@ -21533,9 +22135,22 @@ function valuesOnSchema(schema, previous) {
|
|
|
21533
22135
|
declared.add(s.key);
|
|
21534
22136
|
declared.add(s.title);
|
|
21535
22137
|
}
|
|
21536
|
-
|
|
22138
|
+
const oldDefaults = /* @__PURE__ */ new Map();
|
|
22139
|
+
for (const s of oldSchema) {
|
|
22140
|
+
oldDefaults.set(s.key, s.defval);
|
|
22141
|
+
if (!oldDefaults.has(s.title)) oldDefaults.set(s.title, s.defval);
|
|
22142
|
+
}
|
|
22143
|
+
for (const [k, v] of Object.entries(previous)) {
|
|
22144
|
+
if (!declared.has(k)) continue;
|
|
22145
|
+
if (oldDefaults.has(k) && sameInputValue(oldDefaults.get(k), v)) continue;
|
|
22146
|
+
out[k] = v;
|
|
22147
|
+
}
|
|
21537
22148
|
return out;
|
|
21538
22149
|
}
|
|
22150
|
+
function sameInputValue(a, b) {
|
|
22151
|
+
if (a === b) return true;
|
|
22152
|
+
return typeof a === "object" && a !== null && typeof b === "object" && b !== null && JSON.stringify(a) === JSON.stringify(b);
|
|
22153
|
+
}
|
|
21539
22154
|
function blankedModel(model) {
|
|
21540
22155
|
return {
|
|
21541
22156
|
...model,
|
|
@@ -23100,7 +23715,10 @@ var LEGEND_ICON_PX2 = 16;
|
|
|
23100
23715
|
var LEGEND_CTL_PX = 18;
|
|
23101
23716
|
var LEGEND_ROW_PAD_Y = 2;
|
|
23102
23717
|
var LEGEND_ROW_PAD_X = 6;
|
|
23718
|
+
var LEGEND_LEFT_PX = 10;
|
|
23719
|
+
var LEGEND_SCALE_GAP_PX = 6;
|
|
23103
23720
|
var LEGEND_TITLE_VALUES_GAP_PX = 6;
|
|
23721
|
+
var LEGEND_VALUES_GAP_PX = 5;
|
|
23104
23722
|
var LEGEND_TITLE_STATUS_GAP_PX = 8;
|
|
23105
23723
|
var LEGEND_ACTION_GAP_PX = 6;
|
|
23106
23724
|
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;`;
|
|
@@ -23386,14 +24004,15 @@ var InputsUI = class {
|
|
|
23386
24004
|
row.valuesEl.replaceChildren();
|
|
23387
24005
|
return;
|
|
23388
24006
|
}
|
|
23389
|
-
row.valuesEl.style.display = "
|
|
24007
|
+
row.valuesEl.style.display = "block";
|
|
23390
24008
|
row.valuesEl.replaceChildren();
|
|
23391
|
-
|
|
24009
|
+
row.plotValues.forEach((v, i) => {
|
|
23392
24010
|
const span = document.createElement("span");
|
|
23393
24011
|
span.textContent = v.value;
|
|
23394
24012
|
span.style.color = v.color;
|
|
24013
|
+
if (i > 0) span.style.marginLeft = `${LEGEND_VALUES_GAP_PX}px`;
|
|
23395
24014
|
row.valuesEl.appendChild(span);
|
|
23396
|
-
}
|
|
24015
|
+
});
|
|
23397
24016
|
}
|
|
23398
24017
|
// ── legend move/merge (menu + drag) ─────────────────────────────────────
|
|
23399
24018
|
/** Open the "Move to" menu for a row, anchored under its move button. */
|
|
@@ -23494,7 +24113,7 @@ var InputsUI = class {
|
|
|
23494
24113
|
if (!lg) {
|
|
23495
24114
|
lg = document.createElement("div");
|
|
23496
24115
|
lg.dataset.velaPane = paneId;
|
|
23497
|
-
lg.style.cssText =
|
|
24116
|
+
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;`;
|
|
23498
24117
|
applyChromeTokens(lg, this.theme);
|
|
23499
24118
|
this.positionLegend(lg, paneId);
|
|
23500
24119
|
this.container.appendChild(lg);
|
|
@@ -23506,6 +24125,7 @@ var InputsUI = class {
|
|
|
23506
24125
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
23507
24126
|
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
23508
24127
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
24128
|
+
lg.style.maxWidth = `calc(100% - ${(bounds.rightAxis ?? 0) + LEGEND_LEFT_PX + LEGEND_SCALE_GAP_PX}px)`;
|
|
23509
24129
|
const collapsed = this.paneCollapse.has(paneId);
|
|
23510
24130
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
23511
24131
|
for (const row of this.rows.values()) {
|
|
@@ -23597,7 +24217,7 @@ var InputsUI = class {
|
|
|
23597
24217
|
}
|
|
23598
24218
|
ensureDialogStyles();
|
|
23599
24219
|
const el = document.createElement("div");
|
|
23600
|
-
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;`;
|
|
24220
|
+
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;`;
|
|
23601
24221
|
el.addEventListener("mouseleave", () => {
|
|
23602
24222
|
if (this.selectedId !== id) this.setRowHighlighted(id, false);
|
|
23603
24223
|
});
|
|
@@ -23625,7 +24245,7 @@ var InputsUI = class {
|
|
|
23625
24245
|
const statusEl = document.createElement("span");
|
|
23626
24246
|
statusEl.style.cssText = "display:none;box-sizing:border-box;flex:none;";
|
|
23627
24247
|
const titleWrap = document.createElement("span");
|
|
23628
|
-
titleWrap.style.cssText = "white-space:nowrap;";
|
|
24248
|
+
titleWrap.style.cssText = "white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis;";
|
|
23629
24249
|
titleWrap.addEventListener("mouseenter", () => this.setRowHighlighted(id, true));
|
|
23630
24250
|
const titleEl = document.createElement("span");
|
|
23631
24251
|
titleEl.textContent = title;
|
|
@@ -23643,7 +24263,7 @@ var InputsUI = class {
|
|
|
23643
24263
|
el.appendChild(calloutsEl);
|
|
23644
24264
|
el.appendChild(statusEl);
|
|
23645
24265
|
const valuesEl = document.createElement("span");
|
|
23646
|
-
valuesEl.style.cssText = `display:none;
|
|
24266
|
+
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;`;
|
|
23647
24267
|
el.appendChild(valuesEl);
|
|
23648
24268
|
const controlsEl = document.createElement("span");
|
|
23649
24269
|
controlsEl.style.cssText = `display:none;align-items:center;gap:${LEGEND_ACTION_GAP_PX}px;flex:none;margin-left:${LEGEND_ACTION_GAP_PX}px;`;
|
|
@@ -24465,15 +25085,23 @@ var Batch = class {
|
|
|
24465
25085
|
this.seg(x + w, y + h, x, y + h, lineWidth, c);
|
|
24466
25086
|
this.seg(x, y + h, x, y, lineWidth, c);
|
|
24467
25087
|
}
|
|
24468
|
-
/**
|
|
25088
|
+
/**
|
|
25089
|
+
* Filled circle (triangle fan), feathered like a line: the rim is padded by {@link AA_PAD} and
|
|
25090
|
+
* edgeDist runs from 0 at the center to the padded radius at the rim, with edgeHalf = r. The
|
|
25091
|
+
* distance grows linearly along each spoke, so the shader's line feather gives a round soft edge.
|
|
25092
|
+
*/
|
|
24469
25093
|
circle(cx, cy, r, c, segments = 16) {
|
|
24470
|
-
|
|
25094
|
+
const ext = r + AA_PAD;
|
|
25095
|
+
let px = cx + ext;
|
|
24471
25096
|
let py = cy;
|
|
25097
|
+
this.ensure(segments * 3);
|
|
24472
25098
|
for (let i = 1; i <= segments; i += 1) {
|
|
24473
25099
|
const a = i / segments * Math.PI * 2;
|
|
24474
|
-
const nx = cx + Math.cos(a) *
|
|
24475
|
-
const ny = cy + Math.sin(a) *
|
|
24476
|
-
this.
|
|
25100
|
+
const nx = cx + Math.cos(a) * ext;
|
|
25101
|
+
const ny = cy + Math.sin(a) * ext;
|
|
25102
|
+
this.v(cx, cy, c, 0, r);
|
|
25103
|
+
this.v(px, py, c, ext, r);
|
|
25104
|
+
this.v(nx, ny, c, ext, r);
|
|
24477
25105
|
px = nx;
|
|
24478
25106
|
py = ny;
|
|
24479
25107
|
}
|
|
@@ -24654,7 +25282,7 @@ var WebGL2Backend = class {
|
|
|
24654
25282
|
}
|
|
24655
25283
|
mount(canvas) {
|
|
24656
25284
|
this.canvas = canvas;
|
|
24657
|
-
this.gl = canvas.getContext("webgl2", { alpha: true, antialias:
|
|
25285
|
+
this.gl = canvas.getContext("webgl2", { alpha: true, antialias: false, depth: false, stencil: false, premultipliedAlpha: true });
|
|
24658
25286
|
if (!this.gl) return;
|
|
24659
25287
|
if (!this.initGL()) {
|
|
24660
25288
|
this.gl = null;
|
|
@@ -25879,6 +26507,11 @@ var InputController = class {
|
|
|
25879
26507
|
/** When true (the default), the separators between stacked panes are draggable to
|
|
25880
26508
|
* resize the panes above/below. When false, a press there is a normal data-area pan. */
|
|
25881
26509
|
this.paneResize = true;
|
|
26510
|
+
/** When true (the default), the user drawings take part in pointer input: they claim a
|
|
26511
|
+
* press over a drawing or with a tool armed, and see hover moves. When false, every press
|
|
26512
|
+
* is a plain pan/click and hover leaves them alone; a drawing gesture already under way
|
|
26513
|
+
* still finishes. */
|
|
26514
|
+
this.drawings = true;
|
|
25882
26515
|
this.el = null;
|
|
25883
26516
|
this.dragging = false;
|
|
25884
26517
|
this.moved = false;
|
|
@@ -25942,18 +26575,18 @@ var InputController = class {
|
|
|
25942
26575
|
this.onModifier = (e) => {
|
|
25943
26576
|
if (e.repeat || e.key !== "Shift" && e.key !== "Control" && e.key !== "Meta") return;
|
|
25944
26577
|
if (Number.isNaN(this.cursorX)) return;
|
|
25945
|
-
if (this.dragging
|
|
26578
|
+
if (this.dragging ? this.region !== "drawing" : !this.drawings) return;
|
|
25946
26579
|
this.deps.drawingsPointerMove?.(this.cursorX, this.cursorY, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
25947
26580
|
};
|
|
25948
26581
|
this.onDown = (e) => {
|
|
25949
26582
|
if (e.button === 1) {
|
|
25950
26583
|
const { x: x2, y: y2 } = this.local(e);
|
|
25951
|
-
this.middleDeleted = this.deps.drawingsDeleteAt?.(x2, y2) ?? false;
|
|
26584
|
+
this.middleDeleted = this.drawings && (this.deps.drawingsDeleteAt?.(x2, y2) ?? false);
|
|
25952
26585
|
if (this.middleDeleted) e.preventDefault();
|
|
25953
26586
|
return;
|
|
25954
26587
|
}
|
|
25955
26588
|
if (e.button === 2) {
|
|
25956
|
-
this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
|
|
26589
|
+
this.rightCancelled = this.drawings && (this.deps.drawingsCancelPlacement?.() ?? false);
|
|
25957
26590
|
if (this.rightCancelled) e.preventDefault();
|
|
25958
26591
|
return;
|
|
25959
26592
|
}
|
|
@@ -25975,20 +26608,20 @@ var InputController = class {
|
|
|
25975
26608
|
this.startY = y;
|
|
25976
26609
|
this.prevPressDrawing = this.lastPressDrawing;
|
|
25977
26610
|
this.lastPressDrawing = false;
|
|
25978
|
-
if (this.deps.drawingsClaim?.(x, y)) {
|
|
26611
|
+
if (this.drawings && this.deps.drawingsClaim?.(x, y)) {
|
|
25979
26612
|
this.region = "drawing";
|
|
25980
26613
|
this.lastPressDrawing = true;
|
|
25981
26614
|
this.deps.drawingsPointerDown?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
25982
26615
|
this.capture(e.pointerId);
|
|
25983
26616
|
return;
|
|
25984
26617
|
}
|
|
25985
|
-
if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
26618
|
+
if (this.drawings && e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
25986
26619
|
this.region = "drawing";
|
|
25987
26620
|
this.lastPressDrawing = true;
|
|
25988
26621
|
this.capture(e.pointerId);
|
|
25989
26622
|
return;
|
|
25990
26623
|
}
|
|
25991
|
-
if ((e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
|
|
26624
|
+
if (this.drawings && (e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
|
|
25992
26625
|
this.region = "drawing";
|
|
25993
26626
|
this.lastPressDrawing = true;
|
|
25994
26627
|
this.capture(e.pointerId);
|
|
@@ -26065,7 +26698,7 @@ var InputController = class {
|
|
|
26065
26698
|
const drawCursor = this.deps.drawingsCursor?.(x, y);
|
|
26066
26699
|
const r = this.regionAt(x, y);
|
|
26067
26700
|
this.el.style.cursor = drawCursor ?? (r === "price" ? "ns-resize" : r === "time" ? "ew-resize" : r === "separator" ? "row-resize" : "");
|
|
26068
|
-
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26701
|
+
if (this.drawings) this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26069
26702
|
}
|
|
26070
26703
|
if (e.pointerType !== "touch") this.deps.onPointerMove(x, y);
|
|
26071
26704
|
};
|
|
@@ -26106,7 +26739,7 @@ var InputController = class {
|
|
|
26106
26739
|
this.deps.drawingsPointerUp?.(x, y, this.snapMode(e));
|
|
26107
26740
|
} else if (tapRelease && this.region === "data") {
|
|
26108
26741
|
this.deps.onClick(x, y);
|
|
26109
|
-
} else if (this.dragging && this.region === "data") {
|
|
26742
|
+
} else if (this.dragging && this.region === "data" && wasTouch) {
|
|
26110
26743
|
const stale = e.timeStamp - this.lastT > FLING_STALE_MS;
|
|
26111
26744
|
if (!stale && Math.abs(this.vx) > FLING_MIN_SPEED) {
|
|
26112
26745
|
const pitch = this.deps.getCoords().pxPerBar();
|
|
@@ -26307,7 +26940,7 @@ var InputController = class {
|
|
|
26307
26940
|
}
|
|
26308
26941
|
/** Shared double-click / double-tap routing, by the region under the point. */
|
|
26309
26942
|
doubleActivate(x, y) {
|
|
26310
|
-
if (this.deps.drawingsDblClick?.(x, y)) return;
|
|
26943
|
+
if (this.drawings && this.deps.drawingsDblClick?.(x, y)) return;
|
|
26311
26944
|
const region = this.regionAt(x, y);
|
|
26312
26945
|
if (region === "price") this.deps.resetPriceScale(x, y);
|
|
26313
26946
|
else if (region === "separator") this.deps.resetPaneSize(y);
|
|
@@ -28321,10 +28954,13 @@ function priceTicks(min, max, target = 6) {
|
|
|
28321
28954
|
const norm = raw / mag;
|
|
28322
28955
|
const step = (norm < 1.5 ? 1 : norm < 3 ? 2 : norm < 7 ? 5 : 10) * mag;
|
|
28323
28956
|
const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);
|
|
28324
|
-
const out = [];
|
|
28325
28957
|
const start = Math.ceil(min / step) * step;
|
|
28326
|
-
|
|
28327
|
-
|
|
28958
|
+
if (!(step > 0) || start + step === start) return [];
|
|
28959
|
+
const count = Math.floor((max - start) / step + 1e-6) + 1;
|
|
28960
|
+
if (!(count > 0) || count > 1e3) return [];
|
|
28961
|
+
const out = [];
|
|
28962
|
+
for (let i = 0; i < count; i += 1) {
|
|
28963
|
+
out.push(Number((start + i * step).toFixed(decimals)));
|
|
28328
28964
|
}
|
|
28329
28965
|
return out;
|
|
28330
28966
|
}
|
|
@@ -28522,6 +29158,100 @@ function calendarTicks(zFrom, zTo, months, offsetMs) {
|
|
|
28522
29158
|
return out;
|
|
28523
29159
|
}
|
|
28524
29160
|
|
|
29161
|
+
// src/renderers/native/chrome/priceAxisTicks.ts
|
|
29162
|
+
var PriceAxisTickSource = class {
|
|
29163
|
+
constructor() {
|
|
29164
|
+
this.fn = null;
|
|
29165
|
+
this.memo = /* @__PURE__ */ new WeakMap();
|
|
29166
|
+
this.warned = false;
|
|
29167
|
+
}
|
|
29168
|
+
/** The host function, or null for the built-in ticks. */
|
|
29169
|
+
get hook() {
|
|
29170
|
+
return this.fn;
|
|
29171
|
+
}
|
|
29172
|
+
/** Set (or clear) the host function. Drops every memoized result, so setting the same
|
|
29173
|
+
* function again recomputes — how a host refreshes ticks that depend on its own state. */
|
|
29174
|
+
setHook(fn) {
|
|
29175
|
+
this.fn = fn;
|
|
29176
|
+
this.memo = /* @__PURE__ */ new WeakMap();
|
|
29177
|
+
}
|
|
29178
|
+
/** The pane's master-scale ticks for this frame. An unscaled pane (`axisFormat: 'none'`)
|
|
29179
|
+
* gets none, and the host is never asked for it. */
|
|
29180
|
+
ticksFor(scene, pane, coords) {
|
|
29181
|
+
const format = pane.axisFormat;
|
|
29182
|
+
if (format === "none") return [];
|
|
29183
|
+
const pct = percentScaleFor(scene, pane);
|
|
29184
|
+
const height = pane.bounds.height;
|
|
29185
|
+
const fn = this.fn;
|
|
29186
|
+
if (!fn || !(height > 0)) return paneAxisTicks(pane.scale, height, pct, scene.priceMintick, format);
|
|
29187
|
+
const s = pane.scale;
|
|
29188
|
+
const m = this.memo.get(pane);
|
|
29189
|
+
const fontSize = scene.style.fontSize;
|
|
29190
|
+
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) {
|
|
29191
|
+
return m.ticks;
|
|
29192
|
+
}
|
|
29193
|
+
const defaults2 = paneAxisTicks(s, height, pct, scene.priceMintick, format);
|
|
29194
|
+
const ticks = this.callHook(fn, scene, pane, coords, defaults2, pct?.baseline, pct ? pct.indexed ? "indexed" : "percent" : "price");
|
|
29195
|
+
this.memo.set(pane, {
|
|
29196
|
+
kind: pane.kind,
|
|
29197
|
+
min: s.min,
|
|
29198
|
+
max: s.max,
|
|
29199
|
+
log: !!s.log,
|
|
29200
|
+
invert: !!s.invert,
|
|
29201
|
+
height,
|
|
29202
|
+
baseline: pct?.baseline,
|
|
29203
|
+
indexed: !!pct?.indexed,
|
|
29204
|
+
mintick: scene.priceMintick,
|
|
29205
|
+
format,
|
|
29206
|
+
fontSize,
|
|
29207
|
+
ticks
|
|
29208
|
+
});
|
|
29209
|
+
return ticks;
|
|
29210
|
+
}
|
|
29211
|
+
callHook(fn, scene, pane, coords, defaults2, baseline, mode) {
|
|
29212
|
+
const scale = { min: pane.scale.min, max: pane.scale.max, log: !!pane.scale.log, invert: !!pane.scale.invert };
|
|
29213
|
+
const bounds = { top: 0, height: pane.bounds.height };
|
|
29214
|
+
const ctx = {
|
|
29215
|
+
pane: { id: pane.id, kind: pane.kind },
|
|
29216
|
+
min: Math.min(scale.min, scale.max),
|
|
29217
|
+
max: Math.max(scale.min, scale.max),
|
|
29218
|
+
log: !!scale.log,
|
|
29219
|
+
height: bounds.height,
|
|
29220
|
+
mode,
|
|
29221
|
+
baseline,
|
|
29222
|
+
mintick: scene.priceMintick,
|
|
29223
|
+
fontSize: scene.style.fontSize,
|
|
29224
|
+
priceToY: (price) => coords.priceToY(price, scale, bounds),
|
|
29225
|
+
defaults: defaults2
|
|
29226
|
+
};
|
|
29227
|
+
let out;
|
|
29228
|
+
try {
|
|
29229
|
+
out = fn(ctx);
|
|
29230
|
+
} catch (err) {
|
|
29231
|
+
this.warnOnce("threw", err);
|
|
29232
|
+
return defaults2;
|
|
29233
|
+
}
|
|
29234
|
+
if (out == null) return defaults2;
|
|
29235
|
+
if (!Array.isArray(out)) {
|
|
29236
|
+
this.warnOnce("returned a non-array", out);
|
|
29237
|
+
return defaults2;
|
|
29238
|
+
}
|
|
29239
|
+
const ticks = [];
|
|
29240
|
+
for (const t of out) {
|
|
29241
|
+
if (!t || typeof t !== "object") continue;
|
|
29242
|
+
const { price, label, major } = t;
|
|
29243
|
+
if (typeof price !== "number" || !Number.isFinite(price) || typeof label !== "string") continue;
|
|
29244
|
+
ticks.push(typeof major === "boolean" ? { price, label, major } : { price, label });
|
|
29245
|
+
}
|
|
29246
|
+
return ticks;
|
|
29247
|
+
}
|
|
29248
|
+
warnOnce(what, detail) {
|
|
29249
|
+
if (this.warned) return;
|
|
29250
|
+
this.warned = true;
|
|
29251
|
+
console.warn(`[vela] the priceAxisTicks function ${what} \u2014 the default ticks are used instead.`, detail);
|
|
29252
|
+
}
|
|
29253
|
+
};
|
|
29254
|
+
|
|
28525
29255
|
// src/renderers/native/chrome/countdown.ts
|
|
28526
29256
|
function countdownText(barOpen, barMs, now2) {
|
|
28527
29257
|
if (!(barMs > 0)) return null;
|
|
@@ -28874,8 +29604,11 @@ function standaloneSvg(markup, ink, px) {
|
|
|
28874
29604
|
}
|
|
28875
29605
|
|
|
28876
29606
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
29607
|
+
var MINOR_LABEL_ALPHA = 0.5;
|
|
28877
29608
|
var ChromeRenderer = class {
|
|
28878
|
-
|
|
29609
|
+
/** `axisTicks` is shared with the backdrop layer, so price labels and gridlines agree. */
|
|
29610
|
+
constructor(axisTicks = new PriceAxisTickSource()) {
|
|
29611
|
+
this.axisTicks = axisTicks;
|
|
28879
29612
|
this.canvas = null;
|
|
28880
29613
|
this.ctx = null;
|
|
28881
29614
|
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
@@ -28980,7 +29713,8 @@ var ChromeRenderer = class {
|
|
|
28980
29713
|
this.drawTimeAxis(ctx, scene, coords, theme, dataW, dataH, fullH);
|
|
28981
29714
|
this.drawMarkLane(ctx, scene, coords, theme, dataW, dataH);
|
|
28982
29715
|
}
|
|
28983
|
-
/** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge
|
|
29716
|
+
/** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge,
|
|
29717
|
+
* but clipped to the data area so a glyph on the edge bar never paints over the price scale. */
|
|
28984
29718
|
drawMarkLane(ctx, scene, coords, theme, dataW, dataH) {
|
|
28985
29719
|
if (!scene.marks.visible || scene.timelineMarks.length === 0) {
|
|
28986
29720
|
this.markLayout = { glyphs: [], stacks: /* @__PURE__ */ new Map() };
|
|
@@ -28998,6 +29732,10 @@ var ChromeRenderer = class {
|
|
|
28998
29732
|
expanded: scene.marksExpandedStack
|
|
28999
29733
|
});
|
|
29000
29734
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
29735
|
+
ctx.save();
|
|
29736
|
+
ctx.beginPath();
|
|
29737
|
+
ctx.rect(0, 0, dataW, dataH);
|
|
29738
|
+
ctx.clip();
|
|
29001
29739
|
paintMarkLane(ctx, this.markLayout, {
|
|
29002
29740
|
axisY: dataH,
|
|
29003
29741
|
background: theme.background,
|
|
@@ -29011,6 +29749,7 @@ var ChromeRenderer = class {
|
|
|
29011
29749
|
flashKey: scene.marksFlash && scene.marksFlash.until > nowMs ? scene.marksFlash.key : null,
|
|
29012
29750
|
nowMs
|
|
29013
29751
|
});
|
|
29752
|
+
ctx.restore();
|
|
29014
29753
|
}
|
|
29015
29754
|
barTimes(scene) {
|
|
29016
29755
|
if (this.barTimesSrc !== scene.bars || this.barTimesCache.length !== scene.bars.length) {
|
|
@@ -29067,13 +29806,19 @@ var ChromeRenderer = class {
|
|
|
29067
29806
|
if (!scene.showAxisLabels) return;
|
|
29068
29807
|
ctx.fillStyle = this.axisTextColor;
|
|
29069
29808
|
ctx.textAlign = "left";
|
|
29809
|
+
const font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
29810
|
+
const majorFont = `600 ${font}`;
|
|
29811
|
+
const alpha = ctx.globalAlpha;
|
|
29070
29812
|
for (const pane of panes) {
|
|
29071
29813
|
if (pane.collapsed) continue;
|
|
29072
|
-
const
|
|
29073
|
-
for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, scene.priceMintick, pane.axisFormat)) {
|
|
29814
|
+
for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
|
|
29074
29815
|
const y = coords.priceToY(t.price, pane.scale, pane.bounds);
|
|
29075
29816
|
if (y < pane.bounds.top + 6 || y > pane.bounds.top + pane.bounds.height - 4) continue;
|
|
29817
|
+
if (t.major === true) ctx.font = majorFont;
|
|
29818
|
+
else if (t.major === false) ctx.globalAlpha = alpha * MINOR_LABEL_ALPHA;
|
|
29076
29819
|
ctx.fillText(t.label, dataW + 6, y);
|
|
29820
|
+
if (t.major === true) ctx.font = font;
|
|
29821
|
+
else if (t.major === false) ctx.globalAlpha = alpha;
|
|
29077
29822
|
}
|
|
29078
29823
|
if (pane.axisBands) {
|
|
29079
29824
|
for (const b of pane.axisBands) {
|
|
@@ -29315,15 +30060,23 @@ var CrosshairRenderer = class {
|
|
|
29315
30060
|
constructor() {
|
|
29316
30061
|
this.canvas = null;
|
|
29317
30062
|
this.ctx = null;
|
|
30063
|
+
this.shade = null;
|
|
29318
30064
|
}
|
|
29319
30065
|
mount(canvas) {
|
|
29320
30066
|
this.canvas = canvas;
|
|
29321
30067
|
this.ctx = canvas.getContext("2d");
|
|
29322
30068
|
}
|
|
30069
|
+
/** The plot area the last frame veiled for `crosshairOverride.shadeRight` — from the
|
|
30070
|
+
* picked bar's right edge to the price scale, full plot height (the union when both the
|
|
30071
|
+
* local and a synced crosshair veil) — or null when no veil was painted. */
|
|
30072
|
+
get shadeRightArea() {
|
|
30073
|
+
return this.shade;
|
|
30074
|
+
}
|
|
29323
30075
|
/** Clear the cursor canvas and (re)draw the crosshair lines + axis chips. The optional
|
|
29324
30076
|
* `separatorHoverY` highlights the draggable pane separator under the cursor;
|
|
29325
30077
|
* `external` is a SYNCED ghost crosshair (another chart's pointer, pixel-resolved). */
|
|
29326
30078
|
render(scene, coords, theme, separatorHoverY = null, external = null) {
|
|
30079
|
+
this.shade = null;
|
|
29327
30080
|
const ctx = this.ctx;
|
|
29328
30081
|
const canvas = this.canvas;
|
|
29329
30082
|
if (!ctx || !canvas) return;
|
|
@@ -29346,12 +30099,7 @@ var CrosshairRenderer = class {
|
|
|
29346
30099
|
const x = crisp(coords.logicalToX(logical), ov?.width ?? cs.width);
|
|
29347
30100
|
if (vertical && ov?.shadeRight) {
|
|
29348
30101
|
const from = Math.max(0, Math.round(coords.logicalToX(logical + 0.5)));
|
|
29349
|
-
if (from < dataW)
|
|
29350
|
-
ctx.fillStyle = ov.shadeRight.color;
|
|
29351
|
-
ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
|
|
29352
|
-
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
29353
|
-
ctx.globalAlpha = 1;
|
|
29354
|
-
}
|
|
30102
|
+
if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
|
|
29355
30103
|
}
|
|
29356
30104
|
ctx.strokeStyle = ov?.color ?? cs.color ?? theme.textColor;
|
|
29357
30105
|
ctx.lineWidth = ov?.width ?? cs.width;
|
|
@@ -29387,6 +30135,7 @@ var CrosshairRenderer = class {
|
|
|
29387
30135
|
destroy() {
|
|
29388
30136
|
this.canvas = null;
|
|
29389
30137
|
this.ctx = null;
|
|
30138
|
+
this.shade = null;
|
|
29390
30139
|
}
|
|
29391
30140
|
/** The synced ghost: a dimmed vertical line at the bar the renderer resolved as
|
|
29392
30141
|
* CONTAINING the foreign time (+ horizontal line when a comparable price came
|
|
@@ -29406,12 +30155,7 @@ var CrosshairRenderer = class {
|
|
|
29406
30155
|
if (ov?.vertical === false) return;
|
|
29407
30156
|
if (ov?.shadeRight) {
|
|
29408
30157
|
const from = Math.max(0, Math.round(coords.logicalToX(Math.round(coords.xToLogical(ext.x)) + 0.5)));
|
|
29409
|
-
if (from < dataW)
|
|
29410
|
-
ctx.fillStyle = ov.shadeRight.color;
|
|
29411
|
-
ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
|
|
29412
|
-
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
29413
|
-
ctx.globalAlpha = 1;
|
|
29414
|
-
}
|
|
30158
|
+
if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
|
|
29415
30159
|
}
|
|
29416
30160
|
if (ext.line === false || x < 0 || x > dataW) return;
|
|
29417
30161
|
ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
@@ -29447,6 +30191,15 @@ var CrosshairRenderer = class {
|
|
|
29447
30191
|
this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
29448
30192
|
ctx.globalAlpha = 1;
|
|
29449
30193
|
}
|
|
30194
|
+
/** Paint the `shadeRight` veil from `from` to the price scale and record the veiled area. */
|
|
30195
|
+
paintShade(ctx, shade, from, dataW, dataH) {
|
|
30196
|
+
ctx.fillStyle = shade.color;
|
|
30197
|
+
ctx.globalAlpha = shade.opacity ?? 1;
|
|
30198
|
+
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
30199
|
+
ctx.globalAlpha = 1;
|
|
30200
|
+
const x = this.shade ? Math.min(this.shade.x, from) : from;
|
|
30201
|
+
this.shade = { x, width: dataW - x, height: dataH };
|
|
30202
|
+
}
|
|
29450
30203
|
/** A soft band + a brighter crisp center line over the hovered separator, so it reads as
|
|
29451
30204
|
* a draggable handle (the cursor is already `row-resize`). Spans the full width (data +
|
|
29452
30205
|
* scale gutter) to match the separator itself. Theme-derived (textColor). */
|
|
@@ -32472,6 +33225,12 @@ var DrawingInteraction = class {
|
|
|
32472
33225
|
activeDragId() {
|
|
32473
33226
|
return this.state.kind === "pressed" && this.state.moved ? this.state.id : null;
|
|
32474
33227
|
}
|
|
33228
|
+
/** The store drawings a live drag is moving — the pressed one and its riders — or none
|
|
33229
|
+
* before the drag passes the slop. A Ctrl-drag moves copies, so its sources stay put. */
|
|
33230
|
+
movingIds() {
|
|
33231
|
+
if (this.state.kind !== "pressed" || !this.state.moved || this.state.clones) return /* @__PURE__ */ new Set();
|
|
33232
|
+
return /* @__PURE__ */ new Set([this.state.id, ...this.state.riders.map((r) => r.id)]);
|
|
33233
|
+
}
|
|
32475
33234
|
/** The drawing under a press that has not been released yet (drag or click undecided), or null. */
|
|
32476
33235
|
pressedId() {
|
|
32477
33236
|
return this.state.kind === "pressed" ? this.state.id : null;
|
|
@@ -32846,15 +33605,16 @@ var DrawingSettingsDialog = class {
|
|
|
32846
33605
|
}
|
|
32847
33606
|
open(drawing, actions, kind) {
|
|
32848
33607
|
this.close();
|
|
32849
|
-
const
|
|
33608
|
+
const live = actions.resolve() ?? drawing;
|
|
33609
|
+
const snapshot = clonePlain(live.serialize());
|
|
32850
33610
|
const grid = fieldGrid({ variant: "inputs" });
|
|
32851
33611
|
grid.style.padding = "16px 20px";
|
|
32852
33612
|
grid.style.overflowY = "auto";
|
|
32853
33613
|
grid.style.overflowX = "hidden";
|
|
32854
33614
|
grid.style.flex = "1 1 auto";
|
|
32855
|
-
if (kind === "position" &&
|
|
32856
|
-
else if (kind === "frvp" &&
|
|
32857
|
-
else if (kind === "levels") this.buildLevels(grid,
|
|
33615
|
+
if (kind === "position" && live instanceof PositionTool) this.buildPosition(grid, live, actions);
|
|
33616
|
+
else if (kind === "frvp" && live instanceof FixedRangeVolumeProfile) this.buildFrvp(grid, live, actions);
|
|
33617
|
+
else if (kind === "levels") this.buildLevels(grid, live, actions);
|
|
32858
33618
|
else return;
|
|
32859
33619
|
const ui = new Dialog({
|
|
32860
33620
|
host: this.host,
|
|
@@ -33130,6 +33890,17 @@ var DrawingSettingsDialog = class {
|
|
|
33130
33890
|
}
|
|
33131
33891
|
}));
|
|
33132
33892
|
}
|
|
33893
|
+
if (drawing instanceof FibRetracement) {
|
|
33894
|
+
const fib = drawing;
|
|
33895
|
+
grid.appendChild(fieldRow({
|
|
33896
|
+
label: "Reverse",
|
|
33897
|
+
bool: true,
|
|
33898
|
+
toggle: {
|
|
33899
|
+
checked: fib.reverse,
|
|
33900
|
+
onChange: (v) => actions.patch({ reverse: v })
|
|
33901
|
+
}
|
|
33902
|
+
}));
|
|
33903
|
+
}
|
|
33133
33904
|
levels.forEach((lv, i) => {
|
|
33134
33905
|
const row = document.createElement("div");
|
|
33135
33906
|
row.className = "vela-field-span";
|
|
@@ -33490,7 +34261,7 @@ var DrawingSettingsPopup = class {
|
|
|
33490
34261
|
this.reposition();
|
|
33491
34262
|
return;
|
|
33492
34263
|
}
|
|
33493
|
-
const text = drawing.text;
|
|
34264
|
+
const text = (actions.resolve() ?? drawing).text;
|
|
33494
34265
|
const panel = document.createElement("div");
|
|
33495
34266
|
panel.style.cssText = `padding:6px;border-top:1px solid var(--vela-border);display:flex;flex-direction:column;gap:4px;`;
|
|
33496
34267
|
const ta = new TextArea({
|
|
@@ -34283,6 +35054,8 @@ var UserDrawingController = class {
|
|
|
34283
35054
|
this.drawings = [];
|
|
34284
35055
|
/** Ids painted on an interleave layer this frame — the top canvas paints only their handles. */
|
|
34285
35056
|
this.sliced = /* @__PURE__ */ new Set();
|
|
35057
|
+
/** Interleaved drawings mid-drag: painted on the top canvas, left out of their slice. */
|
|
35058
|
+
this.lifted = /* @__PURE__ */ new Set();
|
|
34286
35059
|
/** Cached slice canvases, keyed `paneId|beforeZ`, reused across frames to avoid churn. */
|
|
34287
35060
|
this.sliceCache = /* @__PURE__ */ new Map();
|
|
34288
35061
|
/** Series boundaries per pane as of the last `prepareSlices` — lets a repaint between data
|
|
@@ -34370,7 +35143,7 @@ var UserDrawingController = class {
|
|
|
34370
35143
|
selectedIds: () => this.selectedIds,
|
|
34371
35144
|
emit: (i) => this.emit(i),
|
|
34372
35145
|
changed: () => {
|
|
34373
|
-
this.
|
|
35146
|
+
this.syncLift();
|
|
34374
35147
|
this.render();
|
|
34375
35148
|
},
|
|
34376
35149
|
openSettings: (id, x, y) => this.openSettingsById(id, x, y),
|
|
@@ -34659,6 +35432,12 @@ var UserDrawingController = class {
|
|
|
34659
35432
|
this.render();
|
|
34660
35433
|
}
|
|
34661
35434
|
}
|
|
35435
|
+
/** Drop the hover (its handles) — the pointer no longer reaches the drawings. */
|
|
35436
|
+
clearHover() {
|
|
35437
|
+
if (this.hoveredId == null) return;
|
|
35438
|
+
this.hoveredId = null;
|
|
35439
|
+
this.render();
|
|
35440
|
+
}
|
|
34662
35441
|
pointerUp(x, y, snap = "off") {
|
|
34663
35442
|
if (this.eraserMode) {
|
|
34664
35443
|
this.erasing = false;
|
|
@@ -34996,6 +35775,20 @@ var UserDrawingController = class {
|
|
|
34996
35775
|
/** A drawing that paints inside the series stack changed (content, not hover): its pixels
|
|
34997
35776
|
* live in the backend composite, so this layer alone can't show the change — ask for a
|
|
34998
35777
|
* data frame, which re-runs `prepareSlices` before the backend composites. */
|
|
35778
|
+
/** A drawing being dragged leaves its interleave slice for the top canvas, so each pointer
|
|
35779
|
+
* move repaints one 2D layer instead of re-rasterizing and re-uploading a plot-sized slice
|
|
35780
|
+
* texture through the data frame. The slices rebuild once as the drag starts and once as it
|
|
35781
|
+
* ends (release, cancel), when the lifted set changes. */
|
|
35782
|
+
syncLift() {
|
|
35783
|
+
const moving = this.interaction.movingIds();
|
|
35784
|
+
let same = moving.size === this.lifted.size;
|
|
35785
|
+
if (same) {
|
|
35786
|
+
for (const id of moving) if (!this.lifted.has(id)) same = false;
|
|
35787
|
+
}
|
|
35788
|
+
if (same) return;
|
|
35789
|
+
this.lifted = new Set(moving);
|
|
35790
|
+
this.invalidateSlices();
|
|
35791
|
+
}
|
|
34999
35792
|
invalidateSlices() {
|
|
35000
35793
|
if (this.sliced.size > 0 || this.drawings.some((d) => this.isInterleaved(d))) this.deps.requestDataPaint();
|
|
35001
35794
|
}
|
|
@@ -35014,7 +35807,7 @@ var UserDrawingController = class {
|
|
|
35014
35807
|
const theme = this.deps.theme();
|
|
35015
35808
|
const buckets = /* @__PURE__ */ new Map();
|
|
35016
35809
|
for (const d of this.drawings) {
|
|
35017
|
-
if (!d.visible) continue;
|
|
35810
|
+
if (!d.visible || this.lifted.has(d.id)) continue;
|
|
35018
35811
|
const beforeZ = sliceKeyFor(d.zIndex, this.lastBounds.get(d.paneId) ?? []);
|
|
35019
35812
|
if (beforeZ === null) continue;
|
|
35020
35813
|
const key = `${d.paneId}|${beforeZ}`;
|
|
@@ -35066,8 +35859,9 @@ var UserDrawingController = class {
|
|
|
35066
35859
|
mutedLabel: edited instanceof TextLabel ? edited.id : null
|
|
35067
35860
|
};
|
|
35068
35861
|
this.painter.seriesLook = this.deps.seriesLook();
|
|
35069
|
-
|
|
35070
|
-
this.painter.
|
|
35862
|
+
const onTop = (d) => !this.isInterleaved(d) || this.lifted.has(d.id);
|
|
35863
|
+
this.painter.paintAll(ctx, this.drawings.filter(onTop), proj, this.deps.theme(), targets);
|
|
35864
|
+
this.painter.paintHighlights(ctx, this.drawings.filter((d) => !onTop(d)), proj, handleIdsFor(targets));
|
|
35071
35865
|
const clones = this.interaction.dragClones();
|
|
35072
35866
|
if (clones) this.painter.paintAll(ctx, clones, proj, this.deps.theme(), { selected: new Set(clones.map((c) => c.id)) });
|
|
35073
35867
|
this.layoutTextEditor();
|
|
@@ -35634,9 +36428,10 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
35634
36428
|
consider(drawings.max);
|
|
35635
36429
|
}
|
|
35636
36430
|
if (min === Infinity || max === -Infinity) return { min: 0, max: 1 };
|
|
35637
|
-
if (min
|
|
35638
|
-
const
|
|
35639
|
-
|
|
36431
|
+
if (max - min <= Math.max(Math.abs(min), Math.abs(max)) * 1e-12) {
|
|
36432
|
+
const mid = (min + max) / 2;
|
|
36433
|
+
const pad = Math.abs(mid) * 0.1 || 1;
|
|
36434
|
+
return { min: mid - pad, max: mid + pad, log: log && mid - pad > 0 };
|
|
35640
36435
|
}
|
|
35641
36436
|
const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
|
|
35642
36437
|
const above = margins.top / 100 / content;
|
|
@@ -36140,7 +36935,9 @@ function bandEdgeSlot(logical) {
|
|
|
36140
36935
|
return Math.ceil(logical) - 0.5;
|
|
36141
36936
|
}
|
|
36142
36937
|
var BackdropRenderer = class {
|
|
36143
|
-
|
|
36938
|
+
/** `axisTicks` is shared with the chrome layer, so gridlines and price labels agree. */
|
|
36939
|
+
constructor(axisTicks = new PriceAxisTickSource()) {
|
|
36940
|
+
this.axisTicks = axisTicks;
|
|
36144
36941
|
this.canvas = null;
|
|
36145
36942
|
this.ctx = null;
|
|
36146
36943
|
}
|
|
@@ -36217,9 +37014,8 @@ var BackdropRenderer = class {
|
|
|
36217
37014
|
if (scene.showGrid && gridHorz.visible && !pane.collapsed) {
|
|
36218
37015
|
ctx.globalAlpha = gridAlpha;
|
|
36219
37016
|
ctx.strokeStyle = horzColor;
|
|
36220
|
-
const pct = percentScaleFor(scene, pane);
|
|
36221
37017
|
ctx.beginPath();
|
|
36222
|
-
for (const t of
|
|
37018
|
+
for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
|
|
36223
37019
|
const y = Math.round(coords.priceToY(t.price, pane.scale, pane.bounds)) + 0.5;
|
|
36224
37020
|
if (y < pane.bounds.top || y > pane.bounds.top + pane.bounds.height) continue;
|
|
36225
37021
|
ctx.moveTo(0, y);
|
|
@@ -36706,8 +37502,16 @@ var NativeRenderer = class {
|
|
|
36706
37502
|
this.toolbarGutter = 0;
|
|
36707
37503
|
// px reserved on the left for the docked drawings toolbar (0 when hidden)
|
|
36708
37504
|
this.mountContainer = null;
|
|
37505
|
+
/** The host-styleable twin of the `shadeRight` veil (`.vela-shade-right`), directly under
|
|
37506
|
+
* the cursor canvas; hidden whenever the crosshair layer paints no veil. */
|
|
37507
|
+
this.shadeRightEl = null;
|
|
37508
|
+
/** The veil geometry last written to {@link shadeRightEl} ('' = hidden) — the DOM is
|
|
37509
|
+
* touched only when it changes, never on a plain pointer move. */
|
|
37510
|
+
this.shadeRightSig = "";
|
|
36709
37511
|
this.userDrawings = null;
|
|
36710
|
-
|
|
37512
|
+
/** One price-axis tick source for the gridlines (backdrop) and the labels (chrome). */
|
|
37513
|
+
this.priceAxisTicks = new PriceAxisTickSource();
|
|
37514
|
+
this.backdropRenderer = new BackdropRenderer(this.priceAxisTicks);
|
|
36711
37515
|
this.volumeRenderer = new VolumeRenderer();
|
|
36712
37516
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
36713
37517
|
/**
|
|
@@ -36740,7 +37544,7 @@ var NativeRenderer = class {
|
|
|
36740
37544
|
this.backendMode = "auto";
|
|
36741
37545
|
this.glowAmount = 0;
|
|
36742
37546
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
36743
|
-
this.chrome = new ChromeRenderer();
|
|
37547
|
+
this.chrome = new ChromeRenderer(this.priceAxisTicks);
|
|
36744
37548
|
/** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
|
|
36745
37549
|
this.indicatorSlices = new IndicatorDrawingSlices();
|
|
36746
37550
|
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
@@ -36857,6 +37661,8 @@ var NativeRenderer = class {
|
|
|
36857
37661
|
// pane just below it
|
|
36858
37662
|
this.resizeSplitStart = null;
|
|
36859
37663
|
// the two panes' shared span when the drag began
|
|
37664
|
+
/** The user drawings take part in pointer input (the runtime `drawingsInteractive` feature). */
|
|
37665
|
+
this.drawingsInteractive = true;
|
|
36860
37666
|
this.hoverSeparatorY = null;
|
|
36861
37667
|
// pixel y of the separator under the cursor (drives its hover highlight)
|
|
36862
37668
|
// ── pane management (merge / reorder / collapse / maximize) ──
|
|
@@ -36912,8 +37718,9 @@ var NativeRenderer = class {
|
|
|
36912
37718
|
this.toggleVisibleCbs = /* @__PURE__ */ new Set();
|
|
36913
37719
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
36914
37720
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
37721
|
+
this.priceStyleWillChangeCbs = /* @__PURE__ */ new Set();
|
|
36915
37722
|
this.name = "native";
|
|
36916
|
-
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"];
|
|
37723
|
+
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"];
|
|
36917
37724
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
36918
37725
|
* so moving onto the button doesn't count as leaving). */
|
|
36919
37726
|
this.onScrollProximityMove = (e) => {
|
|
@@ -36957,6 +37764,7 @@ var NativeRenderer = class {
|
|
|
36957
37764
|
this.scene.priceStyle = opts.priceStyle;
|
|
36958
37765
|
this.scene.basePainting = basePaintingOf(opts.priceStyle);
|
|
36959
37766
|
this.scene.candleOverride = candleOverrideFor(opts.priceStyle, this.scene.style.chartTypes);
|
|
37767
|
+
if (typeof opts.priceAxisTicks === "function") this.priceAxisTicks.setHook(opts.priceAxisTicks);
|
|
36960
37768
|
}
|
|
36961
37769
|
this.theme = this.deriveTheme(DARK_THEME);
|
|
36962
37770
|
}
|
|
@@ -37095,6 +37903,15 @@ var NativeRenderer = class {
|
|
|
37095
37903
|
case "crosshairOverride":
|
|
37096
37904
|
this.scene.crosshairOverride = sanitizeCrosshairOverride(value);
|
|
37097
37905
|
break;
|
|
37906
|
+
case "drawingsInteractive":
|
|
37907
|
+
this.drawingsInteractive = value !== false;
|
|
37908
|
+
if (this.input) this.input.drawings = this.drawingsInteractive;
|
|
37909
|
+
if (!this.drawingsInteractive) this.userDrawings?.clearHover();
|
|
37910
|
+
return;
|
|
37911
|
+
// affects the next gesture only — clearHover repaints what it drops
|
|
37912
|
+
case "priceAxisTicks":
|
|
37913
|
+
this.priceAxisTicks.setHook(typeof value === "function" ? value : null);
|
|
37914
|
+
break;
|
|
37098
37915
|
case "keyboard":
|
|
37099
37916
|
this.setKeyboardEnabled(Boolean(value));
|
|
37100
37917
|
return;
|
|
@@ -37210,6 +38027,10 @@ var NativeRenderer = class {
|
|
|
37210
38027
|
return { visible: this.scene.marks.visible, groups: { ...this.scene.marks.groups } };
|
|
37211
38028
|
case "crosshairOverride":
|
|
37212
38029
|
return this.scene.crosshairOverride ? { ...this.scene.crosshairOverride } : null;
|
|
38030
|
+
case "drawingsInteractive":
|
|
38031
|
+
return this.drawingsInteractive;
|
|
38032
|
+
case "priceAxisTicks":
|
|
38033
|
+
return this.priceAxisTicks.hook;
|
|
37213
38034
|
case "keyboard":
|
|
37214
38035
|
return this.keyboardEnabled;
|
|
37215
38036
|
case "historyChords":
|
|
@@ -37883,6 +38704,12 @@ var NativeRenderer = class {
|
|
|
37883
38704
|
Object.assign(this.drawingsCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
37884
38705
|
this.cursorCanvas = document.createElement("canvas");
|
|
37885
38706
|
Object.assign(this.cursorCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
38707
|
+
this.shadeRightEl = document.createElement("div");
|
|
38708
|
+
this.shadeRightEl.className = "vela-shade-right";
|
|
38709
|
+
this.shadeRightEl.setAttribute("aria-hidden", "true");
|
|
38710
|
+
this.shadeRightEl.hidden = true;
|
|
38711
|
+
Object.assign(this.shadeRightEl.style, { position: "absolute", top: "0", left: "0", width: "0", height: "0", pointerEvents: "none", display: "none" });
|
|
38712
|
+
this.shadeRightSig = "";
|
|
37886
38713
|
this.overlayRoot = document.createElement("div");
|
|
37887
38714
|
Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
|
|
37888
38715
|
this.plot = document.createElement("div");
|
|
@@ -37890,7 +38717,7 @@ var NativeRenderer = class {
|
|
|
37890
38717
|
this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
|
|
37891
38718
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
37892
38719
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
37893
|
-
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
38720
|
+
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.shadeRightEl, this.cursorCanvas, this.overlayRoot);
|
|
37894
38721
|
this.layerOrderSig = "";
|
|
37895
38722
|
this.wrapper.appendChild(this.plot);
|
|
37896
38723
|
this.factoryConfig = this.getConfig();
|
|
@@ -37948,13 +38775,14 @@ var NativeRenderer = class {
|
|
|
37948
38775
|
drawingsPointerDown: (x, y, snap, shift2, mod) => this.userDrawings?.pointerDown(x, y, snap, shift2, mod),
|
|
37949
38776
|
drawingsPointerMove: (x, y, snap, shift2, mod) => this.userDrawings?.pointerMove(x, y, snap, shift2, mod),
|
|
37950
38777
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
37951
|
-
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
|
|
38778
|
+
drawingsCursor: (x, y) => (this.drawingsInteractive ? this.userDrawings?.cursorAt(x, y) : null) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
|
|
37952
38779
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
37953
38780
|
drawingsClearTransient: () => this.userDrawings?.clearTransient()
|
|
37954
38781
|
});
|
|
37955
38782
|
this.input.rightEdgeZoom = this.zoomAnchorMode === "right";
|
|
37956
38783
|
this.input.axisDrag = this.axisDragEnabled;
|
|
37957
38784
|
this.input.paneResize = this.paneResizeEnabled;
|
|
38785
|
+
this.input.drawings = this.drawingsInteractive;
|
|
37958
38786
|
this.input.attach(this.dataCanvas);
|
|
37959
38787
|
if (this.settingsEnabled) this.setSettingsEnabled(true);
|
|
37960
38788
|
this.syncCountdownTimer();
|
|
@@ -38263,6 +39091,8 @@ var NativeRenderer = class {
|
|
|
38263
39091
|
this.backend.destroy();
|
|
38264
39092
|
this.chrome.destroy();
|
|
38265
39093
|
this.crosshairLayer.destroy();
|
|
39094
|
+
this.shadeRightEl?.remove();
|
|
39095
|
+
this.shadeRightEl = null;
|
|
38266
39096
|
this.attributionEl?.remove();
|
|
38267
39097
|
this.attributionEl = null;
|
|
38268
39098
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
@@ -38609,14 +39439,21 @@ var NativeRenderer = class {
|
|
|
38609
39439
|
this.priceStyleCbs.add(cb);
|
|
38610
39440
|
return () => this.priceStyleCbs.delete(cb);
|
|
38611
39441
|
}
|
|
39442
|
+
onPriceStyleWillChange(cb) {
|
|
39443
|
+
this.priceStyleWillChangeCbs.add(cb);
|
|
39444
|
+
return () => this.priceStyleWillChangeCbs.delete(cb);
|
|
39445
|
+
}
|
|
38612
39446
|
/**
|
|
38613
39447
|
* THE single write path for the base price style at runtime (feature set / settings dialog /
|
|
38614
|
-
* config template — the constructor seeds the field directly, pre-listeners).
|
|
38615
|
-
*
|
|
38616
|
-
*
|
|
39448
|
+
* config template — the constructor seeds the field directly, pre-listeners). Announces the
|
|
39449
|
+
* switch while the scene still holds the old style (a listener may paint/capture that frame),
|
|
39450
|
+
* then updates the scene, eases any reveal layer toward the new style's target, and notifies
|
|
39451
|
+
* the core — which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
|
|
38617
39452
|
*/
|
|
38618
39453
|
setPriceStyle(style) {
|
|
38619
39454
|
if (style === this.scene.priceStyle) return;
|
|
39455
|
+
const from = this.scene.priceStyle;
|
|
39456
|
+
for (const cb of this.priceStyleWillChangeCbs) cb(from, style);
|
|
38620
39457
|
this.scene.priceStyle = style;
|
|
38621
39458
|
this.scene.basePainting = basePaintingOf(style);
|
|
38622
39459
|
this.scene.candleOverride = candleOverrideFor(style, this.scene.style.chartTypes);
|
|
@@ -38846,7 +39683,7 @@ var NativeRenderer = class {
|
|
|
38846
39683
|
if (this.easeLiveBar(dtMs)) active = true;
|
|
38847
39684
|
this.skeletonClockMs += dtMs;
|
|
38848
39685
|
this.paintData();
|
|
38849
|
-
this.
|
|
39686
|
+
this.renderCrosshairLayer();
|
|
38850
39687
|
this.updateLegendValues();
|
|
38851
39688
|
this.emitViewportChange();
|
|
38852
39689
|
return active;
|
|
@@ -39421,10 +40258,31 @@ var NativeRenderer = class {
|
|
|
39421
40258
|
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
39422
40259
|
this.trackMarkPopover();
|
|
39423
40260
|
}
|
|
39424
|
-
this.
|
|
40261
|
+
this.renderCrosshairLayer();
|
|
39425
40262
|
if (!repaintsData(level) && this.paintedData) this.repaintCursorLayers();
|
|
39426
40263
|
this.updateLegendValues();
|
|
39427
40264
|
}
|
|
40265
|
+
/** Repaint the L2 crosshair canvas and keep the `.vela-shade-right` element on the veil it painted. */
|
|
40266
|
+
renderCrosshairLayer() {
|
|
40267
|
+
this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
|
|
40268
|
+
this.syncShadeRight();
|
|
40269
|
+
}
|
|
40270
|
+
/** Position (or hide) the `.vela-shade-right` element over the veil of the last crosshair frame. */
|
|
40271
|
+
syncShadeRight() {
|
|
40272
|
+
const el = this.shadeRightEl;
|
|
40273
|
+
if (!el) return;
|
|
40274
|
+
const area = this.crosshairLayer.shadeRightArea;
|
|
40275
|
+
const sig = area ? `${area.x},${area.width},${area.height}` : "";
|
|
40276
|
+
if (sig === this.shadeRightSig) return;
|
|
40277
|
+
this.shadeRightSig = sig;
|
|
40278
|
+
if (!area) {
|
|
40279
|
+
el.hidden = true;
|
|
40280
|
+
el.style.display = "none";
|
|
40281
|
+
return;
|
|
40282
|
+
}
|
|
40283
|
+
Object.assign(el.style, { left: `${area.x}px`, width: `${area.width}px`, height: `${area.height}px`, display: "" });
|
|
40284
|
+
el.hidden = false;
|
|
40285
|
+
}
|
|
39428
40286
|
/** Repaint the SDK layers that opted into cursor tracking (their own canvas only). */
|
|
39429
40287
|
repaintCursorLayers() {
|
|
39430
40288
|
const pane = this.scene.panes.get(PRICE_PANE_ID2);
|
|
@@ -40218,7 +41076,7 @@ var NativeRenderer = class {
|
|
|
40218
41076
|
if (this.animator.active) {
|
|
40219
41077
|
this.computeScales();
|
|
40220
41078
|
this.paintData();
|
|
40221
|
-
this.
|
|
41079
|
+
this.renderCrosshairLayer();
|
|
40222
41080
|
} else {
|
|
40223
41081
|
this.scheduler.flushNow(4 /* Full */);
|
|
40224
41082
|
}
|
|
@@ -44233,7 +45091,8 @@ var Vela = class {
|
|
|
44233
45091
|
glow: options.glow ?? 0,
|
|
44234
45092
|
upColor: options.upColor ?? BULLISH,
|
|
44235
45093
|
downColor: options.downColor ?? BEARISH,
|
|
44236
|
-
priceStyle: options.priceStyle ?? "candles"
|
|
45094
|
+
priceStyle: options.priceStyle ?? "candles",
|
|
45095
|
+
priceAxisTicks: options.priceAxis?.ticks ?? null
|
|
44237
45096
|
};
|
|
44238
45097
|
const RendererClass = options.renderer ?? NativeRenderer;
|
|
44239
45098
|
if (typeof RendererClass !== "function") {
|
|
@@ -44723,6 +45582,9 @@ var CSS21 = `
|
|
|
44723
45582
|
.vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
|
|
44724
45583
|
.vela-statusline .vela-sl-market > [hidden] { display: none !important; }
|
|
44725
45584
|
.vela-statusline .vela-sl-replay-badge, .vela-statusline .vela-sl-replay-badge svg { display: block; width: 16px; height: 16px; }
|
|
45585
|
+
/* Tabular figures: every digit has one advance, so a readout whose text changes only in
|
|
45586
|
+
* its digits keeps its width \u2014 render() skips the ladder for those (see widthKey). */
|
|
45587
|
+
.vela-statusline .vela-sl-values { font-variant-numeric: tabular-nums; }
|
|
44726
45588
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
44727
45589
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
44728
45590
|
/* The change value wears the SAME ink as the OHLC values (set inline per render) \u2014
|
|
@@ -44840,6 +45702,10 @@ var Statusline = class {
|
|
|
44840
45702
|
this.fitRO = null;
|
|
44841
45703
|
/** The ladder rung currently applied — see {@link fit}. */
|
|
44842
45704
|
this.layout = { stacked: false, level: "full" };
|
|
45705
|
+
/** The readout {@link widthKey} the applied rung was fitted to. */
|
|
45706
|
+
this.fitKey = null;
|
|
45707
|
+
/** Web fonts landing after the last fit change every width without a resize. */
|
|
45708
|
+
this.onFontsLoaded = () => this.fit();
|
|
44843
45709
|
this.onContextMenu = (e) => {
|
|
44844
45710
|
if (!this.menu || !this.menuHooks) return;
|
|
44845
45711
|
e.preventDefault();
|
|
@@ -44903,6 +45769,7 @@ var Statusline = class {
|
|
|
44903
45769
|
this.fitRO = new ResizeObserver(() => this.fit());
|
|
44904
45770
|
this.fitRO.observe(host);
|
|
44905
45771
|
}
|
|
45772
|
+
doc.fonts?.addEventListener("loadingdone", this.onFontsLoaded);
|
|
44906
45773
|
this.marketTip = new Tooltip(this.marketEl, { content: MARKET_LABELS.open, placement: "bottom" });
|
|
44907
45774
|
this.eyeTip = new Tooltip(this.eyeEl, { content: "Show chart", placement: "bottom" });
|
|
44908
45775
|
this.setMarketStatus("open");
|
|
@@ -44955,6 +45822,7 @@ var Statusline = class {
|
|
|
44955
45822
|
* (detached host, node tests) nothing overflows, so the widest rung stays.
|
|
44956
45823
|
*/
|
|
44957
45824
|
fit() {
|
|
45825
|
+
this.fitKey = this.widthKey();
|
|
44958
45826
|
this.syncParts();
|
|
44959
45827
|
const seg = segmentVisibility(this.parts, this.chartHidden);
|
|
44960
45828
|
const overflows = () => this.el.scrollWidth > this.el.clientWidth;
|
|
@@ -45111,6 +45979,7 @@ var Statusline = class {
|
|
|
45111
45979
|
this.detach();
|
|
45112
45980
|
this.fitRO?.disconnect();
|
|
45113
45981
|
this.fitRO = null;
|
|
45982
|
+
this.el.ownerDocument.fonts?.removeEventListener("loadingdone", this.onFontsLoaded);
|
|
45114
45983
|
this.el.removeEventListener("contextmenu", this.onContextMenu);
|
|
45115
45984
|
this.menu?.destroy();
|
|
45116
45985
|
this.menu = null;
|
|
@@ -45126,7 +45995,22 @@ var Statusline = class {
|
|
|
45126
45995
|
}
|
|
45127
45996
|
render() {
|
|
45128
45997
|
if (this.menuHooks) this.setChartHidden(!this.menuHooks.chartVisible());
|
|
45129
|
-
this.
|
|
45998
|
+
if (this.widthKey() === this.fitKey) this.renderValues();
|
|
45999
|
+
else this.fit();
|
|
46000
|
+
}
|
|
46001
|
+
/** Everything the readout's width depends on, at every rung: its shape, and each
|
|
46002
|
+
* value's sign, digit count and decimals (the digits themselves are tabular — see
|
|
46003
|
+
* the CSS; the grouping commas follow from the digit count). `toFixed` carries all
|
|
46004
|
+
* three at a fraction of the locale formatter's cost — this runs per pointer move.
|
|
46005
|
+
* The parts, the host's size and the fonts re-fit through their own paths. */
|
|
46006
|
+
widthKey() {
|
|
46007
|
+
const bar = this.hoverBar ?? this.lastBar;
|
|
46008
|
+
if (!bar) return this.readout;
|
|
46009
|
+
const dp = decimalsFor(bar.close);
|
|
46010
|
+
const diff = bar.close - bar.open;
|
|
46011
|
+
const text = [bar.open, bar.high, bar.low, bar.close, diff].map((v) => v.toFixed(dp));
|
|
46012
|
+
text.push((diff / bar.open * 100).toFixed(2));
|
|
46013
|
+
return `${this.readout} ${text.join(" ")}`.replace(/\d/g, "0");
|
|
45130
46014
|
}
|
|
45131
46015
|
/** Write the value readout for the current bar at the current ladder level. */
|
|
45132
46016
|
renderValues() {
|
|
@@ -45726,16 +46610,25 @@ var CellControls = class {
|
|
|
45726
46610
|
// src/widget/context-menu.ts
|
|
45727
46611
|
var PRICE_AXIS_W = 60;
|
|
45728
46612
|
var TIME_AXIS_H3 = 26;
|
|
46613
|
+
var OFF_PLOT = { price: null, time: null, paneKind: null };
|
|
45729
46614
|
var ChartContextMenu = class {
|
|
45730
46615
|
constructor(host, cbs) {
|
|
45731
46616
|
this.cbs = cbs;
|
|
45732
46617
|
this.chart = null;
|
|
46618
|
+
this.offCrosshair = null;
|
|
46619
|
+
/** The bound chart's latest crosshair report — a right-click always follows a pointer
|
|
46620
|
+
* move to its spot, so this is where it landed. */
|
|
46621
|
+
this.crosshair = OFF_PLOT;
|
|
46622
|
+
/** {@link crosshair} as it stood when the menu opened: the pointer moves on through the
|
|
46623
|
+
* menu before a row is picked, and `run` must see the right-clicked spot. */
|
|
46624
|
+
this.pointer = OFF_PLOT;
|
|
45733
46625
|
this.lastZone = "body";
|
|
45734
46626
|
/** The pane whose scale the open price-axis menu targets (null ⇒ the main scale). */
|
|
45735
46627
|
this.lastPane = null;
|
|
45736
46628
|
this.onContextMenu = (e) => {
|
|
45737
46629
|
e.preventDefault();
|
|
45738
46630
|
if (!this.chart) return;
|
|
46631
|
+
this.pointer = this.crosshair;
|
|
45739
46632
|
this.lastZone = this.zoneOf(e);
|
|
45740
46633
|
this.lastPane = this.lastZone === "price-axis" ? this.paneAt(e) : null;
|
|
45741
46634
|
this.menu.setItems(this.itemsFor(this.lastZone));
|
|
@@ -45749,15 +46642,23 @@ var ChartContextMenu = class {
|
|
|
45749
46642
|
// Pointer-anchored action menu: checked state reads as a leading ✓, not a
|
|
45750
46643
|
// washed row (which would read as hover in a menu with no trigger button).
|
|
45751
46644
|
checkmarks: true,
|
|
46645
|
+
iconBadges: true,
|
|
45752
46646
|
onSelect: (id) => this.run(id)
|
|
45753
46647
|
});
|
|
45754
46648
|
host.addEventListener("contextmenu", this.onContextMenu);
|
|
45755
46649
|
}
|
|
45756
46650
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
45757
46651
|
onChart(chart) {
|
|
46652
|
+
this.offCrosshair?.();
|
|
45758
46653
|
this.chart = chart;
|
|
46654
|
+
this.crosshair = OFF_PLOT;
|
|
46655
|
+
this.offCrosshair = chart.renderer.onCrosshairMove((e) => {
|
|
46656
|
+
this.crosshair = { price: e.price, time: e.time, paneKind: e.paneKind ?? null };
|
|
46657
|
+
});
|
|
45759
46658
|
}
|
|
45760
46659
|
destroy() {
|
|
46660
|
+
this.offCrosshair?.();
|
|
46661
|
+
this.offCrosshair = null;
|
|
45761
46662
|
this.host.removeEventListener("contextmenu", this.onContextMenu);
|
|
45762
46663
|
this.menu.destroy();
|
|
45763
46664
|
}
|
|
@@ -45776,49 +46677,52 @@ var ChartContextMenu = class {
|
|
|
45776
46677
|
flag(feature) {
|
|
45777
46678
|
return Boolean(this.chart?.renderer.get(feature));
|
|
45778
46679
|
}
|
|
45779
|
-
|
|
46680
|
+
/** A fresh widget context carrying the pointer captured at open. Copied by property
|
|
46681
|
+
* descriptor: the context's getters must stay LIVE, and a spread would freeze them. */
|
|
46682
|
+
context() {
|
|
45780
46683
|
const ctx = this.cbs.getContext?.();
|
|
45781
|
-
|
|
45782
|
-
|
|
45783
|
-
|
|
45784
|
-
|
|
45785
|
-
|
|
46684
|
+
if (!ctx) return void 0;
|
|
46685
|
+
return Object.create(Object.getPrototypeOf(ctx), {
|
|
46686
|
+
...Object.getOwnPropertyDescriptors(ctx),
|
|
46687
|
+
pointer: { value: this.pointer, enumerable: true }
|
|
46688
|
+
});
|
|
46689
|
+
}
|
|
46690
|
+
contributed(zone) {
|
|
46691
|
+
return widgetActions(`context:${zone}`, this.context()).map((a) => ({
|
|
46692
|
+
item: { id: `action:${a.id}`, label: a.label, icon: a.icon },
|
|
46693
|
+
order: a.order
|
|
45786
46694
|
}));
|
|
45787
46695
|
}
|
|
45788
46696
|
itemsFor(zone) {
|
|
46697
|
+
return composeMenu(zone, this.builtinItems(zone), this.contributed(zone));
|
|
46698
|
+
}
|
|
46699
|
+
builtinItems(zone) {
|
|
45789
46700
|
if (zone === "price-axis") {
|
|
45790
46701
|
const pane = this.lastPane;
|
|
45791
|
-
return
|
|
45792
|
-
|
|
45793
|
-
|
|
45794
|
-
|
|
45795
|
-
|
|
45796
|
-
|
|
45797
|
-
|
|
45798
|
-
|
|
45799
|
-
|
|
45800
|
-
}),
|
|
45801
|
-
...this.contributed(zone)
|
|
45802
|
-
];
|
|
46702
|
+
return priceAxisItems({
|
|
46703
|
+
auto: this.chart?.renderer.get("autoScale") !== false,
|
|
46704
|
+
invert: pane ? pane.invert : this.flag("invertScale"),
|
|
46705
|
+
choice: scaleChoiceOf(pane ?? { mode: String(this.chart?.renderer.get("scaleMode") ?? "price"), log: this.flag("logScale") }),
|
|
46706
|
+
axisLabels: this.flag("axisLabels"),
|
|
46707
|
+
priceLabel: this.flag("priceLabel"),
|
|
46708
|
+
countdown: this.flag("countdown"),
|
|
46709
|
+
priceLine: this.flag("currentPriceLine")
|
|
46710
|
+
});
|
|
45803
46711
|
}
|
|
45804
46712
|
if (zone === "time-axis") {
|
|
45805
|
-
|
|
45806
|
-
return [...timeAxisItems(tz), ...this.contributed("time-axis")];
|
|
46713
|
+
return timeAxisItems(this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC"));
|
|
45807
46714
|
}
|
|
45808
46715
|
const chart = this.chart;
|
|
45809
|
-
return
|
|
45810
|
-
|
|
45811
|
-
|
|
45812
|
-
|
|
45813
|
-
}),
|
|
45814
|
-
...this.contributed("body")
|
|
45815
|
-
];
|
|
46716
|
+
return bodyItems({
|
|
46717
|
+
drawings: chart?.drawings.supported ? chart.drawings.all().length : 0,
|
|
46718
|
+
indicators: chart?.indicators().length ?? 0
|
|
46719
|
+
});
|
|
45816
46720
|
}
|
|
45817
46721
|
run(id) {
|
|
45818
46722
|
const chart = this.chart;
|
|
45819
46723
|
if (!chart) return;
|
|
45820
46724
|
if (id.startsWith("action:")) {
|
|
45821
|
-
const ctx = this.
|
|
46725
|
+
const ctx = this.context();
|
|
45822
46726
|
if (ctx) widgetActions(`context:${this.lastZone}`, ctx).find((a) => a.id === id.slice("action:".length))?.run(ctx);
|
|
45823
46727
|
return;
|
|
45824
46728
|
}
|
|
@@ -45942,8 +46846,8 @@ function seedDefaults(opts) {
|
|
|
45942
46846
|
};
|
|
45943
46847
|
}
|
|
45944
46848
|
function cellChartDefaults(opts) {
|
|
45945
|
-
const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings } = opts;
|
|
45946
|
-
return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings };
|
|
46849
|
+
const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis } = opts;
|
|
46850
|
+
return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis };
|
|
45947
46851
|
}
|
|
45948
46852
|
function cellDrawings(opt) {
|
|
45949
46853
|
if (opt === false) return false;
|
|
@@ -46513,12 +47417,14 @@ var ChartCell = class {
|
|
|
46513
47417
|
this.watermarkOn = visible;
|
|
46514
47418
|
this.watermark?.setVisible(visible);
|
|
46515
47419
|
this.deps.onStateDirty();
|
|
47420
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
46516
47421
|
}
|
|
46517
47422
|
/** Show/hide the "Replay" line under this cell's watermark while it replays (persisted per cell). */
|
|
46518
47423
|
setReplayWatermarkVisible(visible) {
|
|
46519
47424
|
this.replayWatermarkOn = visible;
|
|
46520
47425
|
this.watermark?.setReplayVisible(visible);
|
|
46521
47426
|
this.deps.onStateDirty();
|
|
47427
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
46522
47428
|
}
|
|
46523
47429
|
/** Show/hide this cell's indicator titles — the in-chart legend rows (persisted per cell). */
|
|
46524
47430
|
setIndicatorTitlesVisible(visible) {
|
|
@@ -46539,18 +47445,20 @@ var ChartCell = class {
|
|
|
46539
47445
|
this.statusline?.setPartVisible(part, visible);
|
|
46540
47446
|
this.deps.onStatusPrefsChanged(this.id);
|
|
46541
47447
|
}
|
|
46542
|
-
/** This cell's Status line tab prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
47448
|
+
/** This cell's Status line tab and watermark prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
46543
47449
|
statusPrefs() {
|
|
46544
47450
|
const sl = this.statusline;
|
|
46545
47451
|
return {
|
|
46546
47452
|
parts: sl ? { logo: sl.partVisible("logo"), name: sl.partVisible("name"), market: sl.partVisible("market"), ohlc: sl.partVisible("ohlc"), change: sl.partVisible("change") } : null,
|
|
46547
47453
|
indicatorTitles: this.indicatorTitlesOn,
|
|
46548
|
-
indicatorValues: this.indicatorValuesOn
|
|
47454
|
+
indicatorValues: this.indicatorValuesOn,
|
|
47455
|
+
watermark: this.watermarkOn,
|
|
47456
|
+
replayWatermark: this.replayWatermarkOn
|
|
46549
47457
|
};
|
|
46550
47458
|
}
|
|
46551
|
-
/** Converge this cell's Status line tab prefs to `prefs` — the
|
|
46552
|
-
* the workspace's style link. Idempotent: matching values change
|
|
46553
|
-
* propagated echo dies on its own. */
|
|
47459
|
+
/** Converge this cell's Status line tab and watermark prefs to `prefs` — the
|
|
47460
|
+
* follower half of the workspace's style link. Idempotent: matching values change
|
|
47461
|
+
* nothing, so a propagated echo dies on its own. */
|
|
46554
47462
|
applyStatusPrefs(prefs) {
|
|
46555
47463
|
if (prefs.parts && this.statusline) {
|
|
46556
47464
|
for (const part of Object.keys(prefs.parts)) {
|
|
@@ -46559,6 +47467,8 @@ var ChartCell = class {
|
|
|
46559
47467
|
}
|
|
46560
47468
|
if (prefs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(prefs.indicatorTitles);
|
|
46561
47469
|
if (prefs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(prefs.indicatorValues);
|
|
47470
|
+
if (prefs.watermark !== this.watermarkOn) this.setWatermarkVisible(prefs.watermark);
|
|
47471
|
+
if (prefs.replayWatermark !== this.replayWatermarkOn) this.setReplayWatermarkVisible(prefs.replayWatermark);
|
|
46562
47472
|
}
|
|
46563
47473
|
/** The LIVE chart of this cell — never cache it across a layout change (the cell's
|
|
46564
47474
|
* identity is what endures; the chart dies with the cell). */
|
|
@@ -47469,6 +48379,55 @@ function occupancyGrid(def) {
|
|
|
47469
48379
|
const cols = def.cols.length;
|
|
47470
48380
|
return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ?? `\xB7${r}x${c}`));
|
|
47471
48381
|
}
|
|
48382
|
+
var GLYPH_MIN = 1.5;
|
|
48383
|
+
var GLYPH_SPAN = 13;
|
|
48384
|
+
function glyphStops(weights, n) {
|
|
48385
|
+
const w = Array.from({ length: n }, (_, i) => {
|
|
48386
|
+
const v = weights[i];
|
|
48387
|
+
return v !== void 0 && Number.isFinite(v) && v > 0 ? v : 1;
|
|
48388
|
+
});
|
|
48389
|
+
const total = w.reduce((a, b) => a + b, 0);
|
|
48390
|
+
const stops = [GLYPH_MIN];
|
|
48391
|
+
let acc = 0;
|
|
48392
|
+
for (const v of w) {
|
|
48393
|
+
acc += v;
|
|
48394
|
+
stops.push(Math.round((GLYPH_MIN + GLYPH_SPAN * acc / total) * 100) / 100);
|
|
48395
|
+
}
|
|
48396
|
+
return stops;
|
|
48397
|
+
}
|
|
48398
|
+
function layoutGlyph(def) {
|
|
48399
|
+
const grid = occupancyGrid(def);
|
|
48400
|
+
const rows = grid.length;
|
|
48401
|
+
const cols = grid.reduce((n, row) => Math.max(n, row.length), 0);
|
|
48402
|
+
const xs = glyphStops(def.cols, cols);
|
|
48403
|
+
const ys = glyphStops(def.rows, rows);
|
|
48404
|
+
const at = (r, c) => grid[r]?.[c];
|
|
48405
|
+
const seams = [];
|
|
48406
|
+
for (let c = 1; c < cols; c += 1) {
|
|
48407
|
+
let start = -1;
|
|
48408
|
+
for (let r = 0; r <= rows; r += 1) {
|
|
48409
|
+
const split = r < rows && at(r, c - 1) !== at(r, c);
|
|
48410
|
+
if (split && start < 0) start = r;
|
|
48411
|
+
if (!split && start >= 0) {
|
|
48412
|
+
seams.push(`M${xs[c]} ${ys[start]}V${ys[r]}`);
|
|
48413
|
+
start = -1;
|
|
48414
|
+
}
|
|
48415
|
+
}
|
|
48416
|
+
}
|
|
48417
|
+
for (let r = 1; r < rows; r += 1) {
|
|
48418
|
+
let start = -1;
|
|
48419
|
+
for (let c = 0; c <= cols; c += 1) {
|
|
48420
|
+
const split = c < cols && at(r - 1, c) !== at(r, c);
|
|
48421
|
+
if (split && start < 0) start = c;
|
|
48422
|
+
if (!split && start >= 0) {
|
|
48423
|
+
seams.push(`M${xs[start]} ${ys[r]}H${xs[c]}`);
|
|
48424
|
+
start = -1;
|
|
48425
|
+
}
|
|
48426
|
+
}
|
|
48427
|
+
}
|
|
48428
|
+
const frame = `<rect x="${GLYPH_MIN}" y="${GLYPH_MIN}" width="${GLYPH_SPAN}" height="${GLYPH_SPAN}" rx="1.5"/>`;
|
|
48429
|
+
return svg16(seams.length > 0 ? `${frame}<path d="${seams.join("")}"/>` : frame);
|
|
48430
|
+
}
|
|
47472
48431
|
function gridStyles(def, trackSizes) {
|
|
47473
48432
|
const cols = trackSizes?.cols?.length === def.cols.length ? trackSizes.cols : def.cols;
|
|
47474
48433
|
const rows = trackSizes?.rows?.length === def.rows.length ? trackSizes.rows : def.rows;
|
|
@@ -48000,7 +48959,8 @@ var VelaWorkspace = class {
|
|
|
48000
48959
|
onToggleSync: (id) => {
|
|
48001
48960
|
const kind = id;
|
|
48002
48961
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
48003
|
-
}
|
|
48962
|
+
},
|
|
48963
|
+
glyph: () => layoutGlyph(this.def)
|
|
48004
48964
|
},
|
|
48005
48965
|
getContext: () => this.context()
|
|
48006
48966
|
});
|
|
@@ -48499,6 +49459,7 @@ var VelaWorkspace = class {
|
|
|
48499
49459
|
this.applyGrid();
|
|
48500
49460
|
this.buildCells();
|
|
48501
49461
|
this.alignNewCellStyles(preexisting);
|
|
49462
|
+
this.alignNewCellDrawings(preexisting);
|
|
48502
49463
|
this.syncCellPresentation();
|
|
48503
49464
|
this.refreshCellControls();
|
|
48504
49465
|
this.topbar.setLayout(next.id);
|
|
@@ -48915,6 +49876,7 @@ var VelaWorkspace = class {
|
|
|
48915
49876
|
const chart = cell.chart;
|
|
48916
49877
|
chart.on("indicator:error", ({ error }) => this.toastHost.show(`[${cell.id}] ${error.message}`, "error", 5e3));
|
|
48917
49878
|
chart.on("script:run", (run) => this.events.emit("script:run", { ...run, cell: cell.id }));
|
|
49879
|
+
chart.on("priceStyle:change", ({ from, to }) => this.events.emit("cell:priceStyle", { id: cell.id, from, to }));
|
|
48918
49880
|
chart.on("alert", (alert) => {
|
|
48919
49881
|
const source = [parseSymbol(cell.symbol).ticker || cell.symbol, timeframeLabel(cell.timeframe), alert.indicator].filter(Boolean).join(" ");
|
|
48920
49882
|
this.alerts.unshift({ cellId: cell.id, source, title: alert.title ?? "Alert", message: alert.message, time: alert.time });
|
|
@@ -49032,12 +49994,66 @@ var VelaWorkspace = class {
|
|
|
49032
49994
|
}
|
|
49033
49995
|
}
|
|
49034
49996
|
/**
|
|
49035
|
-
*
|
|
49036
|
-
*
|
|
49037
|
-
*
|
|
49038
|
-
*
|
|
49039
|
-
* in
|
|
49040
|
-
*
|
|
49997
|
+
* Bring cells minted by a layout change into their drawings group: with the link
|
|
49998
|
+
* on, a NEW cell (fresh slot, or one returning from the pool that missed edits
|
|
49999
|
+
* while dormant) receives the drawings of a pre-existing group peer — the active
|
|
50000
|
+
* cell when it is one. A drawing the arriving cell already holds a linked copy of
|
|
50001
|
+
* is refreshed in place; any other is copied and linked like a freshly synced
|
|
50002
|
+
* drawing, so later edits and removals follow both ways. The arrival is the cell's
|
|
50003
|
+
* starting state, not an edit: it stays out of the cell's undo timeline, and the
|
|
50004
|
+
* busy guard keeps the copies' own events from fanning back out.
|
|
50005
|
+
*/
|
|
50006
|
+
alignNewCellDrawings(preexisting) {
|
|
50007
|
+
const setting = this.syncOpts.drawings;
|
|
50008
|
+
if (!setting) return;
|
|
50009
|
+
const ids = [...this.cellsById.keys()];
|
|
50010
|
+
for (const id of ids) {
|
|
50011
|
+
if (preexisting.has(id)) continue;
|
|
50012
|
+
const cell = this.cellsById.get(id);
|
|
50013
|
+
if (!cell?.chart.drawings.supported) continue;
|
|
50014
|
+
const peers = syncTargets(id, setting, ids).filter((p) => preexisting.has(p));
|
|
50015
|
+
if (peers.length === 0) continue;
|
|
50016
|
+
const sourceId = this.activeId && peers.includes(this.activeId) ? this.activeId : peers[0];
|
|
50017
|
+
const source = this.cellsById.get(sourceId);
|
|
50018
|
+
if (!source) continue;
|
|
50019
|
+
const drawings = cell.chart.drawings;
|
|
50020
|
+
this.drawingSyncBusy = true;
|
|
50021
|
+
try {
|
|
50022
|
+
cell.history.silently(() => {
|
|
50023
|
+
const held = new Set(drawings.all().map((d) => d.id));
|
|
50024
|
+
for (const doc of source.chart.drawings.all()) {
|
|
50025
|
+
const group = this.drawingLinks.get(`${sourceId}\0${doc.id}`) ?? /* @__PURE__ */ new Map([[sourceId, doc.id]]);
|
|
50026
|
+
const peerId = group.get(id);
|
|
50027
|
+
if (peerId != null && held.has(peerId)) {
|
|
50028
|
+
drawings.update(peerId, { anchors: doc.anchors, style: doc.style, text: doc.text, props: doc.props });
|
|
50029
|
+
continue;
|
|
50030
|
+
}
|
|
50031
|
+
const copy = drawings.add(doc.type, {
|
|
50032
|
+
paneId: doc.paneId,
|
|
50033
|
+
anchors: doc.anchors,
|
|
50034
|
+
style: doc.style,
|
|
50035
|
+
text: doc.text,
|
|
50036
|
+
props: doc.props,
|
|
50037
|
+
zIndex: doc.zIndex
|
|
50038
|
+
});
|
|
50039
|
+
if (!copy) continue;
|
|
50040
|
+
if (peerId != null) this.drawingLinks.delete(`${id}\0${peerId}`);
|
|
50041
|
+
group.set(id, copy.id);
|
|
50042
|
+
for (const [cellId, dId] of group) this.drawingLinks.set(`${cellId}\0${dId}`, group);
|
|
50043
|
+
}
|
|
50044
|
+
});
|
|
50045
|
+
} finally {
|
|
50046
|
+
this.drawingSyncBusy = false;
|
|
50047
|
+
}
|
|
50048
|
+
}
|
|
50049
|
+
}
|
|
50050
|
+
/**
|
|
50051
|
+
* Mirror an origin cell's presentation — the style-link slice of its renderer
|
|
50052
|
+
* config ({@link styleConfigPatch}) plus its Status line and watermark prefs —
|
|
50053
|
+
* onto its same-group followers (the style link). Loop-safe two ways: the busy
|
|
50054
|
+
* guard eats the followers' SYNCHRONOUS echoes (their `applyConfig` re-fires
|
|
50055
|
+
* `onConfigChanged` in the same tick), and the equality short-circuits leave
|
|
50056
|
+
* already-converged followers untouched, so nothing re-emits once the group agrees.
|
|
49041
50057
|
*/
|
|
49042
50058
|
propagateStylePrefs(originId) {
|
|
49043
50059
|
if (this.styleSyncBusy || this.destroyed) return;
|
|
@@ -49045,17 +50061,15 @@ var VelaWorkspace = class {
|
|
|
49045
50061
|
if (targets.length === 0) return;
|
|
49046
50062
|
const origin = this.cellsById.get(originId);
|
|
49047
50063
|
if (!origin) return;
|
|
49048
|
-
const
|
|
49049
|
-
const sliceJson = slice ? JSON.stringify(slice) : null;
|
|
50064
|
+
const config = origin.chart.renderer.getConfig();
|
|
49050
50065
|
const prefs = origin.statusPrefs();
|
|
49051
50066
|
this.styleSyncBusy = true;
|
|
49052
50067
|
try {
|
|
49053
50068
|
for (const id of targets) {
|
|
49054
50069
|
const cell = this.cellsById.get(id);
|
|
49055
50070
|
if (!cell) continue;
|
|
49056
|
-
|
|
49057
|
-
|
|
49058
|
-
}
|
|
50071
|
+
const patch = styleConfigPatch(config, cell.chart.renderer.getConfig());
|
|
50072
|
+
if (patch) cell.chart.renderer.applyConfig(patch);
|
|
49059
50073
|
cell.applyStatusPrefs(prefs);
|
|
49060
50074
|
}
|
|
49061
50075
|
} finally {
|
|
@@ -49376,7 +50390,8 @@ var VelaWorkspace = class {
|
|
|
49376
50390
|
onToggleSync: (id) => {
|
|
49377
50391
|
const kind = id;
|
|
49378
50392
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
49379
|
-
}
|
|
50393
|
+
},
|
|
50394
|
+
glyph: () => layoutGlyph(this.def)
|
|
49380
50395
|
},
|
|
49381
50396
|
onOpenChange: (open2) => this.trackDialog(open2)
|
|
49382
50397
|
}));
|