@luxalgo/vela 0.8.2 → 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/dist/{DataProvider-DpOWZD8F.d.ts → DataProvider-oH78tYXL.d.ts} +1 -1
- package/dist/{DataProvider-COGDs9Xg.d.cts → DataProvider-wbtJ3-Sa.d.cts} +1 -1
- package/dist/{chunk-R2JMNIHY.js → chunk-2IPXYM4B.js} +258 -169
- package/dist/{chunk-NLDTVFOI.js → chunk-G7B7ZCBF.js} +129 -10
- package/dist/{chunk-T3DZIR5Y.js → chunk-HMHA733L.js} +306 -168
- package/dist/{chunk-DPAA3SRT.js → chunk-KWI3YDBW.js} +254 -9
- package/dist/{chunk-27UTYE7T.js → chunk-TO3GIK7Z.js} +49 -7
- package/dist/{chunk-HKNO24GD.js → chunk-VHGACEHO.js} +50 -1
- package/dist/{contributions-Cgm9RJYP.d.cts → contributions-BhOBRenr.d.cts} +44 -4
- package/dist/{contributions-bWgwyiVi.d.ts → contributions-HGqnNb_P.d.ts} +44 -4
- package/dist/index.cjs +497 -79
- package/dist/index.d.cts +34 -14
- package/dist/index.d.ts +34 -14
- package/dist/index.js +5 -5
- package/dist/{options-NAUHiQEC.d.ts → options-lMUwmRwh.d.cts} +66 -1
- package/dist/{options-NAUHiQEC.d.cts → options-lMUwmRwh.d.ts} +66 -1
- package/dist/{plugin-CqxWoDKS.d.ts → plugin-Cr2Pj8q8.d.ts} +4 -4
- package/dist/{plugin-CjjzZX2F.d.cts → plugin-Do5J5xuF.d.cts} +4 -4
- package/dist/plugin.cjs +18 -0
- 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-wMNfgyZb.d.cts → side-panel-_2UK1gSm.d.cts} +45 -3
- package/dist/{side-panel-wMNfgyZb.d.ts → side-panel-_2UK1gSm.d.ts} +45 -3
- package/dist/{statusline-model-Dpfpq54A.d.cts → statusline-model-B-2xGlB_.d.cts} +3 -3
- package/dist/{statusline-model-Dih6FGWP.d.ts → statusline-model-CY1wzxHg.d.ts} +3 -3
- package/dist/ui.cjs +224 -14
- package/dist/ui.d.cts +119 -87
- package/dist/ui.d.ts +119 -87
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +497 -79
- package/dist/vela.global.min.js +86 -52
- package/dist/widget.cjs +850 -167
- package/dist/widget.d.cts +24 -10
- package/dist/widget.d.ts +24 -10
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +849 -167
- package/dist/workspace.d.cts +47 -20
- package/dist/workspace.d.ts +47 -20
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
- /package/dist/{chunk-JU4CWEBF.js → chunk-W5YPYZ2P.js} +0 -0
package/dist/widget.cjs
CHANGED
|
@@ -1565,6 +1565,55 @@ var Tooltip = class {
|
|
|
1565
1565
|
this.trigger.removeAttribute("data-scope");
|
|
1566
1566
|
}
|
|
1567
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
|
+
}
|
|
1568
1617
|
function menuController(opts) {
|
|
1569
1618
|
return {
|
|
1570
1619
|
machine: menu__namespace.machine,
|
|
@@ -1646,6 +1695,35 @@ var MENU_CSS = `
|
|
|
1646
1695
|
color: var(--vela-fg-bright);
|
|
1647
1696
|
}
|
|
1648
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
|
+
}
|
|
1649
1727
|
/* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
|
|
1650
1728
|
same control language as the settings dialog's toggles. */
|
|
1651
1729
|
.vela-menu-switch {
|
|
@@ -1712,11 +1790,14 @@ var Surface = class _Surface {
|
|
|
1712
1790
|
this.pinnedAnchor = null;
|
|
1713
1791
|
/** The open state last announced — see {@link announce}. */
|
|
1714
1792
|
this.shown = false;
|
|
1793
|
+
/** The list's exit animation after a close; a reopen cancels it. */
|
|
1794
|
+
this.exit = null;
|
|
1715
1795
|
this.doc = doc;
|
|
1716
1796
|
this.host = opts.host;
|
|
1717
1797
|
this.onSelect = opts.onSelect;
|
|
1718
1798
|
this.onFavorite = opts.onFavorite;
|
|
1719
1799
|
this.checkmarks = opts.checkmarks === true;
|
|
1800
|
+
this.iconBadges = opts.iconBadges === true;
|
|
1720
1801
|
this.positioner = doc.createElement("div");
|
|
1721
1802
|
this.positioner.className = "vela-ui-layer";
|
|
1722
1803
|
this.list = doc.createElement("ul");
|
|
@@ -1747,14 +1828,30 @@ var Surface = class _Surface {
|
|
|
1747
1828
|
if (opts.triggerId && trigger) trigger.id = opts.triggerId;
|
|
1748
1829
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
1749
1830
|
const api = this.ctrl.connect(service);
|
|
1750
|
-
|
|
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
|
+
}
|
|
1751
1837
|
if (trigger) vanilla.spreadProps(trigger, api.getTriggerProps(), this.mid);
|
|
1752
1838
|
vanilla.spreadProps(this.positioner, api.getPositionerProps(), this.mid);
|
|
1753
1839
|
vanilla.spreadProps(this.list, api.getContentProps(), this.mid);
|
|
1754
1840
|
this.project(api);
|
|
1841
|
+
if (closing) this.holdExit();
|
|
1755
1842
|
if (api.open && !this.shown) this.announce(true);
|
|
1756
1843
|
});
|
|
1757
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;
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1758
1855
|
get api() {
|
|
1759
1856
|
return this.ctrl.connect(this.handle.service);
|
|
1760
1857
|
}
|
|
@@ -1778,6 +1875,7 @@ var Surface = class _Surface {
|
|
|
1778
1875
|
destroy() {
|
|
1779
1876
|
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
1780
1877
|
this.subs.clear();
|
|
1878
|
+
this.exit?.finish();
|
|
1781
1879
|
this.handle.stop();
|
|
1782
1880
|
if (this.shown) this.announce(false);
|
|
1783
1881
|
this.positioner.remove();
|
|
@@ -1804,6 +1902,10 @@ var Surface = class _Surface {
|
|
|
1804
1902
|
const doc = this.doc;
|
|
1805
1903
|
this.list.replaceChildren();
|
|
1806
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;
|
|
1807
1909
|
for (const item of this.items) {
|
|
1808
1910
|
if (item.separatorBefore) {
|
|
1809
1911
|
const sep = doc.createElement("li");
|
|
@@ -1829,7 +1931,16 @@ var Surface = class _Surface {
|
|
|
1829
1931
|
} else if (!branch && !item.toggle && item.checked) {
|
|
1830
1932
|
li.dataset.checked = "1";
|
|
1831
1933
|
}
|
|
1832
|
-
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
|
+
}
|
|
1833
1944
|
const label = doc.createElement("span");
|
|
1834
1945
|
label.className = "vela-menu-label";
|
|
1835
1946
|
label.textContent = item.label;
|
|
@@ -1877,6 +1988,7 @@ var Surface = class _Surface {
|
|
|
1877
1988
|
onSelect: this.onSelect,
|
|
1878
1989
|
onFavorite: this.onFavorite,
|
|
1879
1990
|
checkmarks: this.checkmarks,
|
|
1991
|
+
iconBadges: this.iconBadges,
|
|
1880
1992
|
id: `${this.mid}--${item.id}`
|
|
1881
1993
|
});
|
|
1882
1994
|
sub2.setItems(item.submenu ?? []);
|
|
@@ -1902,7 +2014,8 @@ var Menu = class {
|
|
|
1902
2014
|
id: opts.id,
|
|
1903
2015
|
minWidth: opts.minWidth,
|
|
1904
2016
|
onFavorite: opts.onFavorite,
|
|
1905
|
-
checkmarks: opts.checkmarks
|
|
2017
|
+
checkmarks: opts.checkmarks,
|
|
2018
|
+
iconBadges: opts.iconBadges
|
|
1906
2019
|
});
|
|
1907
2020
|
this.root.setItems(opts.items);
|
|
1908
2021
|
}
|
|
@@ -2006,6 +2119,7 @@ var POPOVER_CSS = `
|
|
|
2006
2119
|
|
|
2007
2120
|
// src/ui/components/popover/view.ts
|
|
2008
2121
|
var open = null;
|
|
2122
|
+
var leaving = /* @__PURE__ */ new Set();
|
|
2009
2123
|
function closeOpenPopovers() {
|
|
2010
2124
|
open?.hide();
|
|
2011
2125
|
}
|
|
@@ -2033,6 +2147,8 @@ var Popover = class {
|
|
|
2033
2147
|
this.shown = false;
|
|
2034
2148
|
/** The pending removal of a fading-out shell; a show() that reuses the shell cancels it. */
|
|
2035
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;
|
|
2036
2152
|
const doc = opts.trigger.ownerDocument;
|
|
2037
2153
|
injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
|
|
2038
2154
|
this.trigger = opts.trigger;
|
|
@@ -2069,6 +2185,10 @@ var Popover = class {
|
|
|
2069
2185
|
clearTimeout(this.leaveTimer);
|
|
2070
2186
|
this.leaveTimer = null;
|
|
2071
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();
|
|
2072
2192
|
ensureUIHost(this.el, this.inheritsTokens ? void 0 : this.theme);
|
|
2073
2193
|
if (this.fadeMs > 0) {
|
|
2074
2194
|
this.el.style.transition = `opacity ${this.fadeMs}ms ease`;
|
|
@@ -2125,7 +2245,12 @@ var Popover = class {
|
|
|
2125
2245
|
this.el.remove();
|
|
2126
2246
|
}, this.fadeMs);
|
|
2127
2247
|
} else {
|
|
2128
|
-
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);
|
|
2129
2254
|
}
|
|
2130
2255
|
if (open === this) open = null;
|
|
2131
2256
|
this.ctrl.onClose?.();
|
|
@@ -2137,6 +2262,7 @@ var Popover = class {
|
|
|
2137
2262
|
}
|
|
2138
2263
|
destroy() {
|
|
2139
2264
|
this.hide();
|
|
2265
|
+
this.exit?.finish();
|
|
2140
2266
|
}
|
|
2141
2267
|
reposition() {
|
|
2142
2268
|
if (this.shown) this.place();
|
|
@@ -2334,6 +2460,9 @@ var Dialog = class {
|
|
|
2334
2460
|
constructor(opts = {}) {
|
|
2335
2461
|
this.opener = null;
|
|
2336
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;
|
|
2337
2466
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
2338
2467
|
injectStyles(DIALOG_STYLE_ID, DIALOG_CSS, doc);
|
|
2339
2468
|
const host = opts.host ?? doc.body;
|
|
@@ -2430,19 +2559,57 @@ var Dialog = class {
|
|
|
2430
2559
|
const mid = String(this.ctrl.props.id);
|
|
2431
2560
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
2432
2561
|
const api = this.ctrl.connect(service);
|
|
2433
|
-
|
|
2562
|
+
const closing = this.wasOpen && !api.open;
|
|
2563
|
+
if (closing) this.announce(false);
|
|
2434
2564
|
vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
|
|
2435
2565
|
vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
|
|
2436
2566
|
vanilla.spreadProps(this.panel, api.getContentProps(), mid);
|
|
2437
2567
|
vanilla.spreadProps(title, api.getTitleProps(), mid);
|
|
2438
2568
|
vanilla.spreadProps(close, api.getCloseTriggerProps(), mid);
|
|
2439
|
-
|
|
2440
|
-
|
|
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
|
+
}
|
|
2441
2578
|
if (api.open && !this.wasOpen) this.announce(true);
|
|
2442
2579
|
if (this.wasOpen && !api.open) this.restoreOpener();
|
|
2443
2580
|
this.wasOpen = api.open;
|
|
2444
2581
|
});
|
|
2445
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
|
+
}
|
|
2446
2613
|
/** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
|
|
2447
2614
|
* teardown that outruns the machine's close notification). */
|
|
2448
2615
|
restoreOpener() {
|
|
@@ -2471,15 +2638,16 @@ var Dialog = class {
|
|
|
2471
2638
|
return el?.closest(".vela-popover, .vela-menu") != null;
|
|
2472
2639
|
}
|
|
2473
2640
|
destroy() {
|
|
2641
|
+
if (this.wasOpen && !this.open) this.handle.flush();
|
|
2474
2642
|
if (this.open) this.hide();
|
|
2475
2643
|
this.handle.stop();
|
|
2644
|
+
this.destroyed = true;
|
|
2476
2645
|
if (this.wasOpen) {
|
|
2477
2646
|
this.wasOpen = false;
|
|
2478
2647
|
this.announce(false);
|
|
2479
2648
|
}
|
|
2480
2649
|
this.restoreOpener();
|
|
2481
|
-
this.
|
|
2482
|
-
this.positioner.remove();
|
|
2650
|
+
if (!this.exit) this.detach();
|
|
2483
2651
|
}
|
|
2484
2652
|
};
|
|
2485
2653
|
function drawerController(opts = {}) {
|
|
@@ -2608,6 +2776,9 @@ var Drawer = class {
|
|
|
2608
2776
|
constructor(opts = {}) {
|
|
2609
2777
|
this.opener = null;
|
|
2610
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;
|
|
2611
2782
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
2612
2783
|
injectStyles(DRAWER_STYLE_ID, DRAWER_CSS, doc);
|
|
2613
2784
|
const host = opts.host ?? doc.body;
|
|
@@ -2635,13 +2806,21 @@ var Drawer = class {
|
|
|
2635
2806
|
const mid = String(this.ctrl.props.id);
|
|
2636
2807
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
2637
2808
|
const api = this.ctrl.connect(service);
|
|
2638
|
-
|
|
2809
|
+
const closing = this.wasOpen && !api.open;
|
|
2810
|
+
if (closing) this.announce(false);
|
|
2639
2811
|
vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
|
|
2640
2812
|
vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
|
|
2641
2813
|
vanilla.spreadProps(this.panel, api.getContentProps(), mid);
|
|
2642
2814
|
vanilla.spreadProps(this.titleEl, api.getTitleProps(), mid);
|
|
2643
|
-
|
|
2644
|
-
|
|
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
|
+
}
|
|
2645
2824
|
if (api.open && !this.wasOpen) this.announce(true);
|
|
2646
2825
|
if (this.wasOpen && !api.open) this.restoreOpener();
|
|
2647
2826
|
this.wasOpen = api.open;
|
|
@@ -2753,6 +2932,36 @@ var Drawer = class {
|
|
|
2753
2932
|
setTitle(title) {
|
|
2754
2933
|
this.titleEl.textContent = title;
|
|
2755
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
|
+
}
|
|
2756
2965
|
/** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
|
|
2757
2966
|
* teardown that outruns the machine's close notification). */
|
|
2758
2967
|
restoreOpener() {
|
|
@@ -2775,14 +2984,15 @@ var Drawer = class {
|
|
|
2775
2984
|
this.ctrl.connect(this.handle.service).setOpen(false);
|
|
2776
2985
|
}
|
|
2777
2986
|
destroy() {
|
|
2987
|
+
if (this.wasOpen && !this.open) this.handle.flush();
|
|
2778
2988
|
this.handle.stop();
|
|
2989
|
+
this.destroyed = true;
|
|
2779
2990
|
if (this.wasOpen) {
|
|
2780
2991
|
this.wasOpen = false;
|
|
2781
2992
|
this.announce(false);
|
|
2782
2993
|
}
|
|
2783
2994
|
this.restoreOpener();
|
|
2784
|
-
this.
|
|
2785
|
-
this.positioner.remove();
|
|
2995
|
+
if (!this.exit) this.detach();
|
|
2786
2996
|
}
|
|
2787
2997
|
};
|
|
2788
2998
|
|
|
@@ -4918,11 +5128,11 @@ var DatePicker = class {
|
|
|
4918
5128
|
};
|
|
4919
5129
|
|
|
4920
5130
|
// src/widget/layout-picker.ts
|
|
4921
|
-
var STYLE_ID = "vela-widget-layout-picker-
|
|
5131
|
+
var STYLE_ID = "vela-widget-layout-picker-v15";
|
|
4922
5132
|
var CSS2 = `
|
|
4923
5133
|
.vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
|
|
4924
5134
|
.vela-lp {
|
|
4925
|
-
background: var(--vela-surface
|
|
5135
|
+
background: var(--vela-surface);
|
|
4926
5136
|
color: var(--vela-fg);
|
|
4927
5137
|
border: 1px solid var(--vela-border-strong);
|
|
4928
5138
|
border-radius: 8px;
|
|
@@ -5051,6 +5261,8 @@ var LayoutPicker = class {
|
|
|
5051
5261
|
constructor(opts) {
|
|
5052
5262
|
this.squares = [];
|
|
5053
5263
|
this.isOpen = false;
|
|
5264
|
+
/** The card's exit animation after a close; a reopen cancels it. */
|
|
5265
|
+
this.exit = null;
|
|
5054
5266
|
/** Hover preview (1-based rows/cols), null = show current shape. */
|
|
5055
5267
|
this.hover = null;
|
|
5056
5268
|
this.onDocPointerDown = (e) => {
|
|
@@ -5145,6 +5357,8 @@ var LayoutPicker = class {
|
|
|
5145
5357
|
open() {
|
|
5146
5358
|
if (this.isOpen) return;
|
|
5147
5359
|
this.isOpen = true;
|
|
5360
|
+
this.exit?.cancel();
|
|
5361
|
+
this.exit = null;
|
|
5148
5362
|
this.hover = null;
|
|
5149
5363
|
this.refresh();
|
|
5150
5364
|
this.layer.style.display = "";
|
|
@@ -5159,7 +5373,10 @@ var LayoutPicker = class {
|
|
|
5159
5373
|
if (!this.isOpen) return;
|
|
5160
5374
|
this.isOpen = false;
|
|
5161
5375
|
announceSurface(this.panel, false, "popover", this.opts.trigger);
|
|
5162
|
-
this.
|
|
5376
|
+
this.exit = holdForExit(this.panel, () => {
|
|
5377
|
+
this.exit = null;
|
|
5378
|
+
if (!this.isOpen) this.layer.style.display = "none";
|
|
5379
|
+
});
|
|
5163
5380
|
this.opts.trigger.setAttribute("aria-expanded", "false");
|
|
5164
5381
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
5165
5382
|
this.doc.removeEventListener("keydown", this.onDocKeydown, true);
|
|
@@ -5174,6 +5391,7 @@ var LayoutPicker = class {
|
|
|
5174
5391
|
}
|
|
5175
5392
|
destroy() {
|
|
5176
5393
|
this.close();
|
|
5394
|
+
this.exit?.finish();
|
|
5177
5395
|
this.infoTip.destroy();
|
|
5178
5396
|
this.layer.remove();
|
|
5179
5397
|
}
|
|
@@ -5240,7 +5458,7 @@ var LayoutPicker = class {
|
|
|
5240
5458
|
const hostRect = this.opts.host.getBoundingClientRect();
|
|
5241
5459
|
const trigRect = this.opts.trigger.getBoundingClientRect();
|
|
5242
5460
|
let left = trigRect.left - hostRect.left;
|
|
5243
|
-
const top = trigRect.bottom - hostRect.top +
|
|
5461
|
+
const top = trigRect.bottom - hostRect.top + 8;
|
|
5244
5462
|
const width = this.layer.offsetWidth;
|
|
5245
5463
|
if (left + width > hostRect.width - 8) left = Math.max(8, hostRect.width - 8 - width);
|
|
5246
5464
|
this.layer.style.left = `${left}px`;
|
|
@@ -6271,7 +6489,8 @@ var Topbar = class {
|
|
|
6271
6489
|
triggerId: "vela-topbar-style",
|
|
6272
6490
|
host,
|
|
6273
6491
|
items: this.styleItems(),
|
|
6274
|
-
onSelect: (id) => opts.onPriceStyle(id)
|
|
6492
|
+
onSelect: (id) => opts.onPriceStyle(id),
|
|
6493
|
+
iconBadges: true
|
|
6275
6494
|
});
|
|
6276
6495
|
}
|
|
6277
6496
|
setSymbol(symbol) {
|
|
@@ -6337,8 +6556,14 @@ var Topbar = class {
|
|
|
6337
6556
|
renderLayoutButton(doc) {
|
|
6338
6557
|
if (!this.layoutButton) return;
|
|
6339
6558
|
this.layoutButton.replaceChildren();
|
|
6340
|
-
|
|
6341
|
-
|
|
6559
|
+
const glyph = this.opts.layout?.glyph?.();
|
|
6560
|
+
if (glyph || iconMarkup("layout")) {
|
|
6561
|
+
const icon2 = iconEl("layout", doc);
|
|
6562
|
+
if (glyph) icon2.innerHTML = glyph;
|
|
6563
|
+
this.layoutButton.appendChild(icon2);
|
|
6564
|
+
} else {
|
|
6565
|
+
this.layoutButton.appendChild(doc.createTextNode(this.layoutId ?? ""));
|
|
6566
|
+
}
|
|
6342
6567
|
this.layoutButton.setAttribute("aria-label", `Layout \u2014 ${this.layoutId ?? ""}`);
|
|
6343
6568
|
}
|
|
6344
6569
|
renderStyleButton(doc) {
|
|
@@ -13013,6 +13238,11 @@ var SidePanel = class {
|
|
|
13013
13238
|
this.maximizedOn = false;
|
|
13014
13239
|
/** Set while the close is announced, so a listener's own close does not re-enter. */
|
|
13015
13240
|
this.closing = false;
|
|
13241
|
+
/** Open as far as the dock and the host are concerned — a closed panel can still be on
|
|
13242
|
+
* screen while its exit animation runs. */
|
|
13243
|
+
this.isOpen = false;
|
|
13244
|
+
/** The exit animation of a close; a reopen cancels it. */
|
|
13245
|
+
this.exit = null;
|
|
13016
13246
|
const doc = host.ownerDocument;
|
|
13017
13247
|
injectStyles(STYLE_ID4, CSS5, doc);
|
|
13018
13248
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
@@ -13061,20 +13291,44 @@ var SidePanel = class {
|
|
|
13061
13291
|
host.appendChild(this.el);
|
|
13062
13292
|
}
|
|
13063
13293
|
get open() {
|
|
13064
|
-
return
|
|
13294
|
+
return this.isOpen;
|
|
13065
13295
|
}
|
|
13066
|
-
/**
|
|
13067
|
-
|
|
13068
|
-
|
|
13296
|
+
/**
|
|
13297
|
+
* Open/close the panel — a bare call flips it. The panel reports closed at once, but a
|
|
13298
|
+
* close keeps it on screen, marked `data-closing`, while a host exit animation runs.
|
|
13299
|
+
* `instant` skips that, and also cuts short the exit of a panel already closing: a panel
|
|
13300
|
+
* handing the dock to another leaves at once.
|
|
13301
|
+
*/
|
|
13302
|
+
toggle(open2 = !this.isOpen, instant = false) {
|
|
13303
|
+
if (open2 === this.isOpen) {
|
|
13304
|
+
if (!open2 && instant) this.exit?.finish();
|
|
13305
|
+
return;
|
|
13306
|
+
}
|
|
13307
|
+
if (!open2 && this.closing) return;
|
|
13069
13308
|
if (!open2) {
|
|
13070
13309
|
this.closing = true;
|
|
13071
13310
|
announceSurface(this.el, false, "panel");
|
|
13072
13311
|
this.closing = false;
|
|
13073
13312
|
}
|
|
13074
|
-
this.
|
|
13075
|
-
if (
|
|
13313
|
+
this.isOpen = open2;
|
|
13314
|
+
if (open2) {
|
|
13315
|
+
if (this.exit) {
|
|
13316
|
+
this.exit.cancel();
|
|
13317
|
+
this.exit = null;
|
|
13318
|
+
this.setMaximized(false);
|
|
13319
|
+
}
|
|
13320
|
+
this.el.hidden = false;
|
|
13321
|
+
} else {
|
|
13322
|
+
const hide = () => {
|
|
13323
|
+
this.exit = null;
|
|
13324
|
+
this.el.hidden = true;
|
|
13325
|
+
this.setMaximized(false);
|
|
13326
|
+
};
|
|
13327
|
+
if (instant) hide();
|
|
13328
|
+
else this.exit = holdForExit(this.el, hide);
|
|
13329
|
+
}
|
|
13076
13330
|
this.onOpenChange?.(open2);
|
|
13077
|
-
if (open2 &&
|
|
13331
|
+
if (open2 && this.isOpen) announceSurface(this.el, true, "panel");
|
|
13078
13332
|
}
|
|
13079
13333
|
/** Whether the panel covers every chart right now. */
|
|
13080
13334
|
get maximized() {
|
|
@@ -13148,6 +13402,7 @@ var SidePanel = class {
|
|
|
13148
13402
|
}
|
|
13149
13403
|
destroy() {
|
|
13150
13404
|
if (this.open) announceSurface(this.el, false, "panel");
|
|
13405
|
+
this.exit?.finish();
|
|
13151
13406
|
this.el.remove();
|
|
13152
13407
|
}
|
|
13153
13408
|
/** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
|
|
@@ -13452,8 +13707,8 @@ var ObjectTree = class extends SidePanel {
|
|
|
13452
13707
|
set groups(next) {
|
|
13453
13708
|
if (this.chart) this.groupsPerChart.set(this.chart, next);
|
|
13454
13709
|
}
|
|
13455
|
-
toggle(open2 = this.
|
|
13456
|
-
super.toggle(open2);
|
|
13710
|
+
toggle(open2 = !this.open, instant = false) {
|
|
13711
|
+
super.toggle(open2, instant);
|
|
13457
13712
|
if (open2) this.refresh();
|
|
13458
13713
|
}
|
|
13459
13714
|
setSymbol(symbol) {
|
|
@@ -14487,8 +14742,8 @@ var DataWindow = class extends SidePanel {
|
|
|
14487
14742
|
this.unsubs = [];
|
|
14488
14743
|
injectStyles(STYLE_ID6, CSS7, host.ownerDocument);
|
|
14489
14744
|
}
|
|
14490
|
-
toggle(open2 = this.
|
|
14491
|
-
super.toggle(open2);
|
|
14745
|
+
toggle(open2 = !this.open, instant = false) {
|
|
14746
|
+
super.toggle(open2, instant);
|
|
14492
14747
|
if (open2) this.refresh();
|
|
14493
14748
|
}
|
|
14494
14749
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
@@ -14658,7 +14913,7 @@ var PanelDock = class {
|
|
|
14658
14913
|
}
|
|
14659
14914
|
this.pinned = new Set(state.pinned ?? []);
|
|
14660
14915
|
for (const entry of this.entries) entry.panel.setOverlay(!this.pinned.has(entry.id));
|
|
14661
|
-
for (const entry of this.entries) entry.panel.toggle(entry.id === state.open);
|
|
14916
|
+
for (const entry of this.entries) entry.panel.toggle(entry.id === state.open, true);
|
|
14662
14917
|
this.pendingOpen = state.open && !this.entries.some((e) => e.id === state.open) ? state.open : null;
|
|
14663
14918
|
}
|
|
14664
14919
|
/** Drop the contributed panels (the shell destroys its own). */
|
|
@@ -14674,7 +14929,7 @@ var PanelDock = class {
|
|
|
14674
14929
|
if (this.pinned.has(entry.id)) entry.panel.setOverlay(false);
|
|
14675
14930
|
entry.panel.onOpenChange = (open2) => {
|
|
14676
14931
|
if (open2) {
|
|
14677
|
-
for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
|
|
14932
|
+
for (const other of this.entries) if (other !== entry) other.panel.toggle(false, true);
|
|
14678
14933
|
this.pendingOpen = null;
|
|
14679
14934
|
}
|
|
14680
14935
|
this.deps.chrome.setPanelActive(entry.id, open2);
|
|
@@ -16378,7 +16633,11 @@ var MoreDrawer = class {
|
|
|
16378
16633
|
row(doc, label, opts) {
|
|
16379
16634
|
const el = doc.createElement("div");
|
|
16380
16635
|
el.className = "vela-md-row";
|
|
16381
|
-
if (opts.icon)
|
|
16636
|
+
if (opts.icon) {
|
|
16637
|
+
const icon2 = iconEl(opts.icon, doc);
|
|
16638
|
+
if (opts.glyph) icon2.innerHTML = opts.glyph;
|
|
16639
|
+
el.appendChild(icon2);
|
|
16640
|
+
}
|
|
16382
16641
|
const text = doc.createElement("span");
|
|
16383
16642
|
text.className = "vela-md-row-label";
|
|
16384
16643
|
text.textContent = label;
|
|
@@ -16420,7 +16679,7 @@ var MoreDrawer = class {
|
|
|
16420
16679
|
if (this.opts.layout) {
|
|
16421
16680
|
const shape = this.opts.layout.shape();
|
|
16422
16681
|
const value = shape ? `${shape.cols} \xD7 ${shape.rows}` : this.opts.layout.presets().find((p) => p.checked)?.label;
|
|
16423
|
-
list.appendChild(this.row(doc, "Layout", { icon: "layout", value, chevron: true, onClick: () => this.show("layout") }));
|
|
16682
|
+
list.appendChild(this.row(doc, "Layout", { icon: "layout", glyph: this.opts.layout.glyph?.(), value, chevron: true, onClick: () => this.show("layout") }));
|
|
16424
16683
|
}
|
|
16425
16684
|
for (const act of this.opts.primaryActions?.() ?? []) list.appendChild(this.actionRow(doc, act));
|
|
16426
16685
|
for (const panel of this.opts.panels()) {
|
|
@@ -16598,8 +16857,8 @@ var SETTINGS_SECTION = {
|
|
|
16598
16857
|
"price-axis": "Scales and lines",
|
|
16599
16858
|
"time-axis": "Scales and lines"
|
|
16600
16859
|
};
|
|
16601
|
-
function settingsItem(zone, label) {
|
|
16602
|
-
return { id: `settings:${SETTINGS_SECTION[zone]}`, label, separatorBefore: true };
|
|
16860
|
+
function settingsItem(zone, label, icon2) {
|
|
16861
|
+
return { id: `settings:${SETTINGS_SECTION[zone]}`, label, ...icon2 ? { icon: icon2 } : {}, separatorBefore: true };
|
|
16603
16862
|
}
|
|
16604
16863
|
function settingsSectionOf(id) {
|
|
16605
16864
|
return id.slice("settings:".length) || void 0;
|
|
@@ -16679,12 +16938,39 @@ function timeAxisItems(timezone) {
|
|
|
16679
16938
|
}
|
|
16680
16939
|
function bodyItems(counts) {
|
|
16681
16940
|
return [
|
|
16682
|
-
{ id: "reset-view", label: "Reset chart view" },
|
|
16683
|
-
{ id: "remove-drawings", label: "Remove drawings", disabled: counts.drawings === 0, separatorBefore: true },
|
|
16684
|
-
{ id: "remove-indicators", label: "Remove indicators", disabled: counts.indicators === 0 },
|
|
16685
|
-
settingsItem("body", "Settings\u2026")
|
|
16941
|
+
{ id: "reset-view", label: "Reset chart view", icon: "reset" },
|
|
16942
|
+
{ id: "remove-drawings", label: "Remove drawings", icon: "eraser", disabled: counts.drawings === 0, separatorBefore: true },
|
|
16943
|
+
{ id: "remove-indicators", label: "Remove indicators", icon: "indicators", disabled: counts.indicators === 0 },
|
|
16944
|
+
settingsItem("body", "Settings\u2026", "gear")
|
|
16686
16945
|
];
|
|
16687
16946
|
}
|
|
16947
|
+
var CONTEXT_MENU_BUILTIN_ORDER = {
|
|
16948
|
+
body: { "reset-view": -30, "remove-drawings": -20, "remove-indicators": -10, settings: 1e3 },
|
|
16949
|
+
"price-axis": {
|
|
16950
|
+
auto: -80,
|
|
16951
|
+
invert: -70,
|
|
16952
|
+
"scale:regular": -60,
|
|
16953
|
+
"scale:percent": -50,
|
|
16954
|
+
"scale:indexed": -40,
|
|
16955
|
+
"scale:log": -30,
|
|
16956
|
+
labels: -20,
|
|
16957
|
+
levels: -10,
|
|
16958
|
+
settings: 1e3
|
|
16959
|
+
},
|
|
16960
|
+
"time-axis": { timezone: -10, settings: 1e3 }
|
|
16961
|
+
};
|
|
16962
|
+
function composeMenu(zone, builtin, contributed) {
|
|
16963
|
+
const ranks = CONTEXT_MENU_BUILTIN_ORDER[zone];
|
|
16964
|
+
const rows = [];
|
|
16965
|
+
let group = 0;
|
|
16966
|
+
for (const item of builtin) {
|
|
16967
|
+
if (item.separatorBefore) group += 1;
|
|
16968
|
+
rows.push({ item, rank: ranks[item.id.startsWith("settings") ? "settings" : item.id] ?? 0, group });
|
|
16969
|
+
}
|
|
16970
|
+
for (const { item, order } of contributed) rows.push({ item, rank: order ?? 0, group: -1 });
|
|
16971
|
+
rows.sort((a, b) => a.rank - b.rank);
|
|
16972
|
+
return rows.map(({ item, group: g }, i) => ({ ...item, separatorBefore: i > 0 && rows[i - 1].group !== g }));
|
|
16973
|
+
}
|
|
16688
16974
|
|
|
16689
16975
|
// src/widget/price-scale-drawer.ts
|
|
16690
16976
|
var STYLE_ID17 = "vela-widget-price-scale-drawer";
|
|
@@ -17959,6 +18245,7 @@ function chromeHint(text, opts) {
|
|
|
17959
18245
|
// src/renderers/native/drawings/DrawingToolbar.ts
|
|
17960
18246
|
var TOOLBAR_WIDTH = 44;
|
|
17961
18247
|
var TOOLBAR_COLLAPSED_WIDTH = 16;
|
|
18248
|
+
var FLYOUT_GAP = 3;
|
|
17962
18249
|
var DrawingToolbar = class {
|
|
17963
18250
|
constructor(host, theme, onArm, onMagnet = () => {
|
|
17964
18251
|
}, onMeasure = () => {
|
|
@@ -17988,6 +18275,9 @@ var DrawingToolbar = class {
|
|
|
17988
18275
|
// group id (or MAGNET_ID) whose flyout is open
|
|
17989
18276
|
this.flyoutCell = null;
|
|
17990
18277
|
// the cell the open flyout is anchored to
|
|
18278
|
+
/** Closed flyouts still playing their exit animation, by owner id — reopening the same
|
|
18279
|
+
* owner takes its element back instead of stacking a second one on it. */
|
|
18280
|
+
this.exitingFlyouts = /* @__PURE__ */ new Map();
|
|
17991
18281
|
this.groupCells = /* @__PURE__ */ new Map();
|
|
17992
18282
|
// the composite cell (hover/active bg + flyout anchor)
|
|
17993
18283
|
this.groupIcons = /* @__PURE__ */ new Map();
|
|
@@ -18096,6 +18386,7 @@ var DrawingToolbar = class {
|
|
|
18096
18386
|
}
|
|
18097
18387
|
destroy() {
|
|
18098
18388
|
this.closeFlyout();
|
|
18389
|
+
for (const { exit } of [...this.exitingFlyouts.values()]) exit.finish();
|
|
18099
18390
|
for (const dispose of this.tipDisposers.splice(0)) dispose();
|
|
18100
18391
|
this.root.remove();
|
|
18101
18392
|
}
|
|
@@ -18323,16 +18614,22 @@ var DrawingToolbar = class {
|
|
|
18323
18614
|
* outside-dismiss. Callers fill it with items. */
|
|
18324
18615
|
beginFlyout(ownerId, cell) {
|
|
18325
18616
|
const t = this.theme;
|
|
18326
|
-
const
|
|
18617
|
+
const back = this.exitingFlyouts.get(ownerId);
|
|
18618
|
+
if (back) {
|
|
18619
|
+
this.exitingFlyouts.delete(ownerId);
|
|
18620
|
+
back.exit.cancel();
|
|
18621
|
+
back.fly.replaceChildren();
|
|
18622
|
+
}
|
|
18623
|
+
const fly = back?.fly ?? document.createElement("div");
|
|
18327
18624
|
fly.className = "vela-dtb-flyout";
|
|
18328
|
-
fly.style.cssText = `position:absolute;z-index:23;display:flex;flex-direction:column;gap:2px;padding:4px;border-radius:
|
|
18625
|
+
fly.style.cssText = `position:absolute;z-index:23;display:flex;flex-direction:column;gap:2px;padding:4px;border-radius:var(--vela-radius-md);background:var(--vela-surface);border:1px solid ${this.borderOverride ?? "var(--vela-border-strong)"};box-shadow:var(--vela-shadow);pointer-events:auto;overflow-y:auto;overscroll-behavior:contain;`;
|
|
18329
18626
|
applyChromeTokens(fly, t);
|
|
18330
18627
|
this.host.appendChild(fly);
|
|
18331
18628
|
const r = cell.getBoundingClientRect();
|
|
18332
18629
|
const rootR = this.root.getBoundingClientRect();
|
|
18333
18630
|
const hostR = this.host.getBoundingClientRect();
|
|
18334
18631
|
const top = r.top - hostR.top;
|
|
18335
|
-
fly.style.left = `${rootR.right - hostR.left
|
|
18632
|
+
fly.style.left = `${rootR.right - hostR.left + FLYOUT_GAP}px`;
|
|
18336
18633
|
fly.style.top = `${top}px`;
|
|
18337
18634
|
fly.style.maxHeight = `${Math.max(120, hostR.height - top - 8)}px`;
|
|
18338
18635
|
this.flyout = fly;
|
|
@@ -18348,6 +18645,7 @@ var DrawingToolbar = class {
|
|
|
18348
18645
|
if (!cell) return;
|
|
18349
18646
|
const fly = this.beginFlyout(group.id, cell);
|
|
18350
18647
|
const sections = group.sections ?? [{ label: "", tools: group.tools }];
|
|
18648
|
+
const badge = sections.some((s) => s.tools.some((tool) => !!tool.icon));
|
|
18351
18649
|
for (let si = 0; si < sections.length; si++) {
|
|
18352
18650
|
const section = sections[si];
|
|
18353
18651
|
if (section.label) {
|
|
@@ -18358,6 +18656,7 @@ var DrawingToolbar = class {
|
|
|
18358
18656
|
fly.appendChild(
|
|
18359
18657
|
this.makeFlyoutItem({
|
|
18360
18658
|
icon: tool.icon,
|
|
18659
|
+
badge,
|
|
18361
18660
|
label: tool.label,
|
|
18362
18661
|
selected: tool.type === this.active,
|
|
18363
18662
|
shortcut: this.shortcuts.get(tool.type),
|
|
@@ -18408,21 +18707,24 @@ var DrawingToolbar = class {
|
|
|
18408
18707
|
div.style.cssText = `height:1px;margin:4px 8px;background:${this.borderColor};`;
|
|
18409
18708
|
return div;
|
|
18410
18709
|
}
|
|
18411
|
-
/** A flyout row, left to right: optional leading icon, label, a check when it's the
|
|
18412
|
-
* entry, an optional shortcut hint, and — at the far right — the favorite star (tool
|
|
18413
|
-
* Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
|
|
18710
|
+
/** A flyout row, left to right: optional leading icon badge, label, a check when it's the
|
|
18711
|
+
* selected entry, an optional shortcut hint, and — at the far right — the favorite star (tool
|
|
18712
|
+
* rows). Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
|
|
18414
18713
|
makeFlyoutItem(opts) {
|
|
18415
18714
|
const t = this.theme;
|
|
18416
18715
|
const item = document.createElement("button");
|
|
18417
18716
|
item.type = "button";
|
|
18418
18717
|
item.className = "vela-dtb-item";
|
|
18419
18718
|
item.setAttribute("aria-label", opts.label);
|
|
18420
|
-
|
|
18421
|
-
|
|
18422
|
-
|
|
18423
|
-
|
|
18424
|
-
|
|
18425
|
-
|
|
18719
|
+
const badged = opts.badge === true || !!opts.icon;
|
|
18720
|
+
const box = badged ? "gap:10px;padding:4px 10px 4px 6px;" : "gap:8px;padding:5px 10px 5px 8px;";
|
|
18721
|
+
item.style.cssText = `display:flex;align-items:center;${box}cursor:pointer;color:${t.textColor};border-radius:var(--vela-radius-sm);font:13px ${t.fontFamily};white-space:nowrap;min-width:148px;`;
|
|
18722
|
+
if (opts.selected) item.dataset.selected = "1";
|
|
18723
|
+
if (badged) {
|
|
18724
|
+
const badge = document.createElement("span");
|
|
18725
|
+
badge.className = "vela-dtb-badge";
|
|
18726
|
+
if (opts.icon) badge.innerHTML = opts.icon;
|
|
18727
|
+
item.appendChild(badge);
|
|
18426
18728
|
}
|
|
18427
18729
|
const label = document.createElement("span");
|
|
18428
18730
|
label.textContent = opts.label;
|
|
@@ -18467,7 +18769,12 @@ var DrawingToolbar = class {
|
|
|
18467
18769
|
if (fly) {
|
|
18468
18770
|
this.flyout = null;
|
|
18469
18771
|
announceSurface(fly, false, "menu", this.flyoutCell);
|
|
18470
|
-
|
|
18772
|
+
const owner = this.flyoutOwnerId ?? "";
|
|
18773
|
+
const exit = holdForExit(fly, () => {
|
|
18774
|
+
if (this.exitingFlyouts.get(owner)?.fly === fly) this.exitingFlyouts.delete(owner);
|
|
18775
|
+
fly.remove();
|
|
18776
|
+
});
|
|
18777
|
+
if (exit) this.exitingFlyouts.set(owner, { fly, exit });
|
|
18471
18778
|
this.flyoutCell?.classList.remove("vela-open");
|
|
18472
18779
|
this.flyoutCell = null;
|
|
18473
18780
|
this.flyoutOwnerId = null;
|
|
@@ -18551,6 +18858,11 @@ function ensureStyles() {
|
|
|
18551
18858
|
.vela-dtb[data-collapsed='1'] .vela-dtb-collapse .vela-dtb-hit{width:14px;}
|
|
18552
18859
|
.vela-dtb-item{background:transparent;border:none;transition:background var(--vela-dur-fast) ease;}
|
|
18553
18860
|
.vela-dtb-item:hover{background:var(--vela-hover-strong);}
|
|
18861
|
+
.vela-dtb-badge{width:24px;height:24px;padding:3px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;flex:none;border-radius:var(--vela-radius-md);background:var(--vela-hover);border:1px solid var(--vela-border);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
18862
|
+
.vela-dtb-badge:empty{background:transparent;border-color:transparent;}
|
|
18863
|
+
.vela-dtb-badge svg{width:100%;height:100%;}
|
|
18864
|
+
.vela-dtb-item:hover .vela-dtb-badge:not(:empty){background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
18865
|
+
.vela-dtb-item[data-selected='1'] .vela-dtb-badge:not(:empty){background:color-mix(in srgb,var(--vela-fg-bright) 83%,var(--vela-surface));border-color:color-mix(in srgb,var(--vela-fg-bright) 40%,transparent);color:var(--vela-surface);}
|
|
18554
18866
|
.vela-dtb-star{width:26px;height:22px;margin:-3px -5px -3px 0;padding:3px 5px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;flex:none;opacity:0;color:inherit;border-radius:var(--vela-radius-sm);transition:opacity .1s ease,color .1s ease,background .1s ease;}
|
|
18555
18867
|
.vela-dtb-star svg{width:16px;height:16px;}
|
|
18556
18868
|
.vela-dtb-item:hover .vela-dtb-star{opacity:.55;}
|
|
@@ -18918,15 +19230,48 @@ function syncTargets(originId, setting, cellIds) {
|
|
|
18918
19230
|
function rangesWithin(a, b, epsMs) {
|
|
18919
19231
|
return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
|
|
18920
19232
|
}
|
|
18921
|
-
var STYLE_SYNC_CONFIG_KEYS = [
|
|
18922
|
-
|
|
18923
|
-
|
|
18924
|
-
|
|
18925
|
-
|
|
19233
|
+
var STYLE_SYNC_CONFIG_KEYS = [
|
|
19234
|
+
"layout",
|
|
19235
|
+
"panes",
|
|
19236
|
+
"grid",
|
|
19237
|
+
"margins",
|
|
19238
|
+
"priceScale",
|
|
19239
|
+
"crosshair",
|
|
19240
|
+
"animations",
|
|
19241
|
+
"candles",
|
|
19242
|
+
"bars",
|
|
19243
|
+
"line",
|
|
19244
|
+
"area",
|
|
19245
|
+
"baseline",
|
|
19246
|
+
"sessions"
|
|
19247
|
+
];
|
|
19248
|
+
function styleConfigPatch(origin, follower) {
|
|
19249
|
+
const from = asRecord(origin);
|
|
19250
|
+
const to = asRecord(follower);
|
|
19251
|
+
if (!from || !to) return null;
|
|
19252
|
+
const patch = {};
|
|
18926
19253
|
for (const key of STYLE_SYNC_CONFIG_KEYS) {
|
|
18927
|
-
|
|
19254
|
+
const block = asRecord(from[key]);
|
|
19255
|
+
if (block && JSON.stringify(block) !== JSON.stringify(to[key])) patch[key] = block;
|
|
18928
19256
|
}
|
|
18929
|
-
|
|
19257
|
+
const spacing = asRecord(from.series)?.spacing;
|
|
19258
|
+
if (typeof spacing === "number" && spacing !== asRecord(to.series)?.spacing) patch.series = { spacing };
|
|
19259
|
+
const fromTypes = asRecord(from.chartTypes) ?? {};
|
|
19260
|
+
const toTypes = asRecord(to.chartTypes) ?? {};
|
|
19261
|
+
const types = {};
|
|
19262
|
+
for (const typeId of /* @__PURE__ */ new Set([...Object.keys(fromTypes), ...Object.keys(toTypes)])) {
|
|
19263
|
+
const src = asRecord(fromTypes[typeId]) ?? {};
|
|
19264
|
+
const dst = asRecord(toTypes[typeId]) ?? {};
|
|
19265
|
+
for (const key of CANDLE_OVERRIDE_KEYS) {
|
|
19266
|
+
const value = src[key] ?? null;
|
|
19267
|
+
if (value !== (dst[key] ?? null)) (types[typeId] ?? (types[typeId] = {}))[key] = value;
|
|
19268
|
+
}
|
|
19269
|
+
}
|
|
19270
|
+
if (Object.keys(types).length > 0) patch.chartTypes = types;
|
|
19271
|
+
return Object.keys(patch).length > 0 ? patch : null;
|
|
19272
|
+
}
|
|
19273
|
+
function asRecord(v) {
|
|
19274
|
+
return v != null && typeof v === "object" ? v : null;
|
|
18930
19275
|
}
|
|
18931
19276
|
|
|
18932
19277
|
// src/widget/persist.ts
|
|
@@ -19601,6 +19946,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19601
19946
|
this.activeEngineStyle = null;
|
|
19602
19947
|
/** The chart's current price style (tracked from the renderer's change events + initial read). */
|
|
19603
19948
|
this.priceStyle = "candles";
|
|
19949
|
+
this.priceStyleWillChangeUnsub = null;
|
|
19604
19950
|
this.unresolvedUnsub = null;
|
|
19605
19951
|
/** Latest chart visible range (left/right bar times), fed to viewport-dependent scripts. */
|
|
19606
19952
|
this.visibleRange = null;
|
|
@@ -19699,6 +20045,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19699
20045
|
this.renderer.onThemeSelect?.((name) => this.setTheme(resolveTheme(name)));
|
|
19700
20046
|
this.viewportUnsub = this.renderer.onViewportChange((range) => this.onViewportChange(range));
|
|
19701
20047
|
this.renderer.onPriceStyleChange?.((style) => this.syncPriceStyle(style));
|
|
20048
|
+
this.priceStyleWillChangeUnsub = this.renderer.onPriceStyleWillChange?.((from, to) => this.events.emit("priceStyle:change", { from, to })) ?? null;
|
|
19702
20049
|
const initialStyle = this.renderer.readFeature("priceStyle");
|
|
19703
20050
|
if (typeof initialStyle === "string") this.priceStyle = initialStyle;
|
|
19704
20051
|
this.barTransform = barTransformFor(initialStyle);
|
|
@@ -21229,6 +21576,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21229
21576
|
if (this.viewportTimer != null) clearTimeout(this.viewportTimer);
|
|
21230
21577
|
this.viewportUnsub?.();
|
|
21231
21578
|
this.paneActionUnsub?.();
|
|
21579
|
+
this.priceStyleWillChangeUnsub?.();
|
|
21580
|
+
this.priceStyleWillChangeUnsub = null;
|
|
21232
21581
|
for (const record of this.registry.all()) {
|
|
21233
21582
|
this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
|
|
21234
21583
|
record.session?.stop();
|
|
@@ -23434,7 +23783,10 @@ var LEGEND_ICON_PX2 = 16;
|
|
|
23434
23783
|
var LEGEND_CTL_PX = 18;
|
|
23435
23784
|
var LEGEND_ROW_PAD_Y = 2;
|
|
23436
23785
|
var LEGEND_ROW_PAD_X = 6;
|
|
23786
|
+
var LEGEND_LEFT_PX = 10;
|
|
23787
|
+
var LEGEND_SCALE_GAP_PX = 6;
|
|
23437
23788
|
var LEGEND_TITLE_VALUES_GAP_PX = 6;
|
|
23789
|
+
var LEGEND_VALUES_GAP_PX = 5;
|
|
23438
23790
|
var LEGEND_TITLE_STATUS_GAP_PX = 8;
|
|
23439
23791
|
var LEGEND_ACTION_GAP_PX = 6;
|
|
23440
23792
|
var LEGEND_CTL_CSS = `cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border:none;line-height:0;padding:0;width:${LEGEND_CTL_PX}px;height:${LEGEND_CTL_PX}px;border-radius:3px;box-sizing:border-box;flex:none;`;
|
|
@@ -23720,14 +24072,15 @@ var InputsUI = class {
|
|
|
23720
24072
|
row.valuesEl.replaceChildren();
|
|
23721
24073
|
return;
|
|
23722
24074
|
}
|
|
23723
|
-
row.valuesEl.style.display = "
|
|
24075
|
+
row.valuesEl.style.display = "block";
|
|
23724
24076
|
row.valuesEl.replaceChildren();
|
|
23725
|
-
|
|
24077
|
+
row.plotValues.forEach((v, i) => {
|
|
23726
24078
|
const span = document.createElement("span");
|
|
23727
24079
|
span.textContent = v.value;
|
|
23728
24080
|
span.style.color = v.color;
|
|
24081
|
+
if (i > 0) span.style.marginLeft = `${LEGEND_VALUES_GAP_PX}px`;
|
|
23729
24082
|
row.valuesEl.appendChild(span);
|
|
23730
|
-
}
|
|
24083
|
+
});
|
|
23731
24084
|
}
|
|
23732
24085
|
// ── legend move/merge (menu + drag) ─────────────────────────────────────
|
|
23733
24086
|
/** Open the "Move to" menu for a row, anchored under its move button. */
|
|
@@ -23828,7 +24181,7 @@ var InputsUI = class {
|
|
|
23828
24181
|
if (!lg) {
|
|
23829
24182
|
lg = document.createElement("div");
|
|
23830
24183
|
lg.dataset.velaPane = paneId;
|
|
23831
|
-
lg.style.cssText =
|
|
24184
|
+
lg.style.cssText = `position:absolute;left:${LEGEND_LEFT_PX}px;z-index:5;display:flex;flex-direction:column;align-items:flex-start;gap:0;pointer-events:none;font:12px -apple-system,Segoe UI,sans-serif;`;
|
|
23832
24185
|
applyChromeTokens(lg, this.theme);
|
|
23833
24186
|
this.positionLegend(lg, paneId);
|
|
23834
24187
|
this.container.appendChild(lg);
|
|
@@ -23840,6 +24193,7 @@ var InputsUI = class {
|
|
|
23840
24193
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
23841
24194
|
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
23842
24195
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
24196
|
+
lg.style.maxWidth = `calc(100% - ${(bounds.rightAxis ?? 0) + LEGEND_LEFT_PX + LEGEND_SCALE_GAP_PX}px)`;
|
|
23843
24197
|
const collapsed = this.paneCollapse.has(paneId);
|
|
23844
24198
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
23845
24199
|
for (const row of this.rows.values()) {
|
|
@@ -23931,7 +24285,7 @@ var InputsUI = class {
|
|
|
23931
24285
|
}
|
|
23932
24286
|
ensureDialogStyles();
|
|
23933
24287
|
const el = document.createElement("div");
|
|
23934
|
-
el.style.cssText = `pointer-events:auto;display:flex;align-items:center;background:${this.idleRowFill()};border-radius:4px;padding:${LEGEND_ROW_PAD_Y}px ${LEGEND_ROW_PAD_X}px;margin-left:-${LEGEND_ROW_PAD_X}px;min-height:${LEGEND_ROW_MIN_H}px;box-sizing:border-box;color:${this.theme.textColor};user-select:none;-webkit-user-select:none;`;
|
|
24288
|
+
el.style.cssText = `pointer-events:auto;display:flex;align-items:center;background:${this.idleRowFill()};border-radius:4px;padding:${LEGEND_ROW_PAD_Y}px ${LEGEND_ROW_PAD_X}px;margin-left:-${LEGEND_ROW_PAD_X}px;max-width:calc(100% + ${LEGEND_ROW_PAD_X}px);min-height:${LEGEND_ROW_MIN_H}px;box-sizing:border-box;color:${this.theme.textColor};user-select:none;-webkit-user-select:none;`;
|
|
23935
24289
|
el.addEventListener("mouseleave", () => {
|
|
23936
24290
|
if (this.selectedId !== id) this.setRowHighlighted(id, false);
|
|
23937
24291
|
});
|
|
@@ -23959,7 +24313,7 @@ var InputsUI = class {
|
|
|
23959
24313
|
const statusEl = document.createElement("span");
|
|
23960
24314
|
statusEl.style.cssText = "display:none;box-sizing:border-box;flex:none;";
|
|
23961
24315
|
const titleWrap = document.createElement("span");
|
|
23962
|
-
titleWrap.style.cssText = "white-space:nowrap;";
|
|
24316
|
+
titleWrap.style.cssText = "white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis;";
|
|
23963
24317
|
titleWrap.addEventListener("mouseenter", () => this.setRowHighlighted(id, true));
|
|
23964
24318
|
const titleEl = document.createElement("span");
|
|
23965
24319
|
titleEl.textContent = title;
|
|
@@ -23977,7 +24331,7 @@ var InputsUI = class {
|
|
|
23977
24331
|
el.appendChild(calloutsEl);
|
|
23978
24332
|
el.appendChild(statusEl);
|
|
23979
24333
|
const valuesEl = document.createElement("span");
|
|
23980
|
-
valuesEl.style.cssText = `display:none;
|
|
24334
|
+
valuesEl.style.cssText = `display:none;flex:1 1 0%;min-width:0;overflow:hidden;text-overflow:ellipsis;margin-left:${LEGEND_TITLE_VALUES_GAP_PX}px;white-space:nowrap;font-variant-numeric:tabular-nums;`;
|
|
23981
24335
|
el.appendChild(valuesEl);
|
|
23982
24336
|
const controlsEl = document.createElement("span");
|
|
23983
24337
|
controlsEl.style.cssText = `display:none;align-items:center;gap:${LEGEND_ACTION_GAP_PX}px;flex:none;margin-left:${LEGEND_ACTION_GAP_PX}px;`;
|
|
@@ -26221,6 +26575,11 @@ var InputController = class {
|
|
|
26221
26575
|
/** When true (the default), the separators between stacked panes are draggable to
|
|
26222
26576
|
* resize the panes above/below. When false, a press there is a normal data-area pan. */
|
|
26223
26577
|
this.paneResize = true;
|
|
26578
|
+
/** When true (the default), the user drawings take part in pointer input: they claim a
|
|
26579
|
+
* press over a drawing or with a tool armed, and see hover moves. When false, every press
|
|
26580
|
+
* is a plain pan/click and hover leaves them alone; a drawing gesture already under way
|
|
26581
|
+
* still finishes. */
|
|
26582
|
+
this.drawings = true;
|
|
26224
26583
|
this.el = null;
|
|
26225
26584
|
this.dragging = false;
|
|
26226
26585
|
this.moved = false;
|
|
@@ -26284,18 +26643,18 @@ var InputController = class {
|
|
|
26284
26643
|
this.onModifier = (e) => {
|
|
26285
26644
|
if (e.repeat || e.key !== "Shift" && e.key !== "Control" && e.key !== "Meta") return;
|
|
26286
26645
|
if (Number.isNaN(this.cursorX)) return;
|
|
26287
|
-
if (this.dragging
|
|
26646
|
+
if (this.dragging ? this.region !== "drawing" : !this.drawings) return;
|
|
26288
26647
|
this.deps.drawingsPointerMove?.(this.cursorX, this.cursorY, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26289
26648
|
};
|
|
26290
26649
|
this.onDown = (e) => {
|
|
26291
26650
|
if (e.button === 1) {
|
|
26292
26651
|
const { x: x2, y: y2 } = this.local(e);
|
|
26293
|
-
this.middleDeleted = this.deps.drawingsDeleteAt?.(x2, y2) ?? false;
|
|
26652
|
+
this.middleDeleted = this.drawings && (this.deps.drawingsDeleteAt?.(x2, y2) ?? false);
|
|
26294
26653
|
if (this.middleDeleted) e.preventDefault();
|
|
26295
26654
|
return;
|
|
26296
26655
|
}
|
|
26297
26656
|
if (e.button === 2) {
|
|
26298
|
-
this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
|
|
26657
|
+
this.rightCancelled = this.drawings && (this.deps.drawingsCancelPlacement?.() ?? false);
|
|
26299
26658
|
if (this.rightCancelled) e.preventDefault();
|
|
26300
26659
|
return;
|
|
26301
26660
|
}
|
|
@@ -26317,20 +26676,20 @@ var InputController = class {
|
|
|
26317
26676
|
this.startY = y;
|
|
26318
26677
|
this.prevPressDrawing = this.lastPressDrawing;
|
|
26319
26678
|
this.lastPressDrawing = false;
|
|
26320
|
-
if (this.deps.drawingsClaim?.(x, y)) {
|
|
26679
|
+
if (this.drawings && this.deps.drawingsClaim?.(x, y)) {
|
|
26321
26680
|
this.region = "drawing";
|
|
26322
26681
|
this.lastPressDrawing = true;
|
|
26323
26682
|
this.deps.drawingsPointerDown?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26324
26683
|
this.capture(e.pointerId);
|
|
26325
26684
|
return;
|
|
26326
26685
|
}
|
|
26327
|
-
if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
26686
|
+
if (this.drawings && e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
26328
26687
|
this.region = "drawing";
|
|
26329
26688
|
this.lastPressDrawing = true;
|
|
26330
26689
|
this.capture(e.pointerId);
|
|
26331
26690
|
return;
|
|
26332
26691
|
}
|
|
26333
|
-
if ((e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
|
|
26692
|
+
if (this.drawings && (e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
|
|
26334
26693
|
this.region = "drawing";
|
|
26335
26694
|
this.lastPressDrawing = true;
|
|
26336
26695
|
this.capture(e.pointerId);
|
|
@@ -26407,7 +26766,7 @@ var InputController = class {
|
|
|
26407
26766
|
const drawCursor = this.deps.drawingsCursor?.(x, y);
|
|
26408
26767
|
const r = this.regionAt(x, y);
|
|
26409
26768
|
this.el.style.cursor = drawCursor ?? (r === "price" ? "ns-resize" : r === "time" ? "ew-resize" : r === "separator" ? "row-resize" : "");
|
|
26410
|
-
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26769
|
+
if (this.drawings) this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26411
26770
|
}
|
|
26412
26771
|
if (e.pointerType !== "touch") this.deps.onPointerMove(x, y);
|
|
26413
26772
|
};
|
|
@@ -26649,7 +27008,7 @@ var InputController = class {
|
|
|
26649
27008
|
}
|
|
26650
27009
|
/** Shared double-click / double-tap routing, by the region under the point. */
|
|
26651
27010
|
doubleActivate(x, y) {
|
|
26652
|
-
if (this.deps.drawingsDblClick?.(x, y)) return;
|
|
27011
|
+
if (this.drawings && this.deps.drawingsDblClick?.(x, y)) return;
|
|
26653
27012
|
const region = this.regionAt(x, y);
|
|
26654
27013
|
if (region === "price") this.deps.resetPriceScale(x, y);
|
|
26655
27014
|
else if (region === "separator") this.deps.resetPaneSize(y);
|
|
@@ -28663,10 +29022,13 @@ function priceTicks(min, max, target = 6) {
|
|
|
28663
29022
|
const norm = raw / mag;
|
|
28664
29023
|
const step = (norm < 1.5 ? 1 : norm < 3 ? 2 : norm < 7 ? 5 : 10) * mag;
|
|
28665
29024
|
const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);
|
|
28666
|
-
const out = [];
|
|
28667
29025
|
const start = Math.ceil(min / step) * step;
|
|
28668
|
-
|
|
28669
|
-
|
|
29026
|
+
if (!(step > 0) || start + step === start) return [];
|
|
29027
|
+
const count = Math.floor((max - start) / step + 1e-6) + 1;
|
|
29028
|
+
if (!(count > 0) || count > 1e3) return [];
|
|
29029
|
+
const out = [];
|
|
29030
|
+
for (let i = 0; i < count; i += 1) {
|
|
29031
|
+
out.push(Number((start + i * step).toFixed(decimals)));
|
|
28670
29032
|
}
|
|
28671
29033
|
return out;
|
|
28672
29034
|
}
|
|
@@ -28864,6 +29226,100 @@ function calendarTicks(zFrom, zTo, months, offsetMs) {
|
|
|
28864
29226
|
return out;
|
|
28865
29227
|
}
|
|
28866
29228
|
|
|
29229
|
+
// src/renderers/native/chrome/priceAxisTicks.ts
|
|
29230
|
+
var PriceAxisTickSource = class {
|
|
29231
|
+
constructor() {
|
|
29232
|
+
this.fn = null;
|
|
29233
|
+
this.memo = /* @__PURE__ */ new WeakMap();
|
|
29234
|
+
this.warned = false;
|
|
29235
|
+
}
|
|
29236
|
+
/** The host function, or null for the built-in ticks. */
|
|
29237
|
+
get hook() {
|
|
29238
|
+
return this.fn;
|
|
29239
|
+
}
|
|
29240
|
+
/** Set (or clear) the host function. Drops every memoized result, so setting the same
|
|
29241
|
+
* function again recomputes — how a host refreshes ticks that depend on its own state. */
|
|
29242
|
+
setHook(fn) {
|
|
29243
|
+
this.fn = fn;
|
|
29244
|
+
this.memo = /* @__PURE__ */ new WeakMap();
|
|
29245
|
+
}
|
|
29246
|
+
/** The pane's master-scale ticks for this frame. An unscaled pane (`axisFormat: 'none'`)
|
|
29247
|
+
* gets none, and the host is never asked for it. */
|
|
29248
|
+
ticksFor(scene, pane, coords) {
|
|
29249
|
+
const format = pane.axisFormat;
|
|
29250
|
+
if (format === "none") return [];
|
|
29251
|
+
const pct = percentScaleFor(scene, pane);
|
|
29252
|
+
const height = pane.bounds.height;
|
|
29253
|
+
const fn = this.fn;
|
|
29254
|
+
if (!fn || !(height > 0)) return paneAxisTicks(pane.scale, height, pct, scene.priceMintick, format);
|
|
29255
|
+
const s = pane.scale;
|
|
29256
|
+
const m = this.memo.get(pane);
|
|
29257
|
+
const fontSize = scene.style.fontSize;
|
|
29258
|
+
if (m && m.kind === pane.kind && m.min === s.min && m.max === s.max && m.log === !!s.log && m.invert === !!s.invert && m.height === height && m.baseline === pct?.baseline && m.indexed === !!pct?.indexed && m.mintick === scene.priceMintick && m.format === format && m.fontSize === fontSize) {
|
|
29259
|
+
return m.ticks;
|
|
29260
|
+
}
|
|
29261
|
+
const defaults2 = paneAxisTicks(s, height, pct, scene.priceMintick, format);
|
|
29262
|
+
const ticks = this.callHook(fn, scene, pane, coords, defaults2, pct?.baseline, pct ? pct.indexed ? "indexed" : "percent" : "price");
|
|
29263
|
+
this.memo.set(pane, {
|
|
29264
|
+
kind: pane.kind,
|
|
29265
|
+
min: s.min,
|
|
29266
|
+
max: s.max,
|
|
29267
|
+
log: !!s.log,
|
|
29268
|
+
invert: !!s.invert,
|
|
29269
|
+
height,
|
|
29270
|
+
baseline: pct?.baseline,
|
|
29271
|
+
indexed: !!pct?.indexed,
|
|
29272
|
+
mintick: scene.priceMintick,
|
|
29273
|
+
format,
|
|
29274
|
+
fontSize,
|
|
29275
|
+
ticks
|
|
29276
|
+
});
|
|
29277
|
+
return ticks;
|
|
29278
|
+
}
|
|
29279
|
+
callHook(fn, scene, pane, coords, defaults2, baseline, mode) {
|
|
29280
|
+
const scale = { min: pane.scale.min, max: pane.scale.max, log: !!pane.scale.log, invert: !!pane.scale.invert };
|
|
29281
|
+
const bounds = { top: 0, height: pane.bounds.height };
|
|
29282
|
+
const ctx = {
|
|
29283
|
+
pane: { id: pane.id, kind: pane.kind },
|
|
29284
|
+
min: Math.min(scale.min, scale.max),
|
|
29285
|
+
max: Math.max(scale.min, scale.max),
|
|
29286
|
+
log: !!scale.log,
|
|
29287
|
+
height: bounds.height,
|
|
29288
|
+
mode,
|
|
29289
|
+
baseline,
|
|
29290
|
+
mintick: scene.priceMintick,
|
|
29291
|
+
fontSize: scene.style.fontSize,
|
|
29292
|
+
priceToY: (price) => coords.priceToY(price, scale, bounds),
|
|
29293
|
+
defaults: defaults2
|
|
29294
|
+
};
|
|
29295
|
+
let out;
|
|
29296
|
+
try {
|
|
29297
|
+
out = fn(ctx);
|
|
29298
|
+
} catch (err) {
|
|
29299
|
+
this.warnOnce("threw", err);
|
|
29300
|
+
return defaults2;
|
|
29301
|
+
}
|
|
29302
|
+
if (out == null) return defaults2;
|
|
29303
|
+
if (!Array.isArray(out)) {
|
|
29304
|
+
this.warnOnce("returned a non-array", out);
|
|
29305
|
+
return defaults2;
|
|
29306
|
+
}
|
|
29307
|
+
const ticks = [];
|
|
29308
|
+
for (const t of out) {
|
|
29309
|
+
if (!t || typeof t !== "object") continue;
|
|
29310
|
+
const { price, label, major } = t;
|
|
29311
|
+
if (typeof price !== "number" || !Number.isFinite(price) || typeof label !== "string") continue;
|
|
29312
|
+
ticks.push(typeof major === "boolean" ? { price, label, major } : { price, label });
|
|
29313
|
+
}
|
|
29314
|
+
return ticks;
|
|
29315
|
+
}
|
|
29316
|
+
warnOnce(what, detail) {
|
|
29317
|
+
if (this.warned) return;
|
|
29318
|
+
this.warned = true;
|
|
29319
|
+
console.warn(`[vela] the priceAxisTicks function ${what} \u2014 the default ticks are used instead.`, detail);
|
|
29320
|
+
}
|
|
29321
|
+
};
|
|
29322
|
+
|
|
28867
29323
|
// src/renderers/native/chrome/countdown.ts
|
|
28868
29324
|
function countdownText(barOpen, barMs, now2) {
|
|
28869
29325
|
if (!(barMs > 0)) return null;
|
|
@@ -29216,8 +29672,11 @@ function standaloneSvg(markup, ink, px) {
|
|
|
29216
29672
|
}
|
|
29217
29673
|
|
|
29218
29674
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
29675
|
+
var MINOR_LABEL_ALPHA = 0.5;
|
|
29219
29676
|
var ChromeRenderer = class {
|
|
29220
|
-
|
|
29677
|
+
/** `axisTicks` is shared with the backdrop layer, so price labels and gridlines agree. */
|
|
29678
|
+
constructor(axisTicks = new PriceAxisTickSource()) {
|
|
29679
|
+
this.axisTicks = axisTicks;
|
|
29221
29680
|
this.canvas = null;
|
|
29222
29681
|
this.ctx = null;
|
|
29223
29682
|
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
@@ -29322,7 +29781,8 @@ var ChromeRenderer = class {
|
|
|
29322
29781
|
this.drawTimeAxis(ctx, scene, coords, theme, dataW, dataH, fullH);
|
|
29323
29782
|
this.drawMarkLane(ctx, scene, coords, theme, dataW, dataH);
|
|
29324
29783
|
}
|
|
29325
|
-
/** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge
|
|
29784
|
+
/** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge,
|
|
29785
|
+
* but clipped to the data area so a glyph on the edge bar never paints over the price scale. */
|
|
29326
29786
|
drawMarkLane(ctx, scene, coords, theme, dataW, dataH) {
|
|
29327
29787
|
if (!scene.marks.visible || scene.timelineMarks.length === 0) {
|
|
29328
29788
|
this.markLayout = { glyphs: [], stacks: /* @__PURE__ */ new Map() };
|
|
@@ -29340,6 +29800,10 @@ var ChromeRenderer = class {
|
|
|
29340
29800
|
expanded: scene.marksExpandedStack
|
|
29341
29801
|
});
|
|
29342
29802
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
29803
|
+
ctx.save();
|
|
29804
|
+
ctx.beginPath();
|
|
29805
|
+
ctx.rect(0, 0, dataW, dataH);
|
|
29806
|
+
ctx.clip();
|
|
29343
29807
|
paintMarkLane(ctx, this.markLayout, {
|
|
29344
29808
|
axisY: dataH,
|
|
29345
29809
|
background: theme.background,
|
|
@@ -29353,6 +29817,7 @@ var ChromeRenderer = class {
|
|
|
29353
29817
|
flashKey: scene.marksFlash && scene.marksFlash.until > nowMs ? scene.marksFlash.key : null,
|
|
29354
29818
|
nowMs
|
|
29355
29819
|
});
|
|
29820
|
+
ctx.restore();
|
|
29356
29821
|
}
|
|
29357
29822
|
barTimes(scene) {
|
|
29358
29823
|
if (this.barTimesSrc !== scene.bars || this.barTimesCache.length !== scene.bars.length) {
|
|
@@ -29409,13 +29874,19 @@ var ChromeRenderer = class {
|
|
|
29409
29874
|
if (!scene.showAxisLabels) return;
|
|
29410
29875
|
ctx.fillStyle = this.axisTextColor;
|
|
29411
29876
|
ctx.textAlign = "left";
|
|
29877
|
+
const font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
29878
|
+
const majorFont = `600 ${font}`;
|
|
29879
|
+
const alpha = ctx.globalAlpha;
|
|
29412
29880
|
for (const pane of panes) {
|
|
29413
29881
|
if (pane.collapsed) continue;
|
|
29414
|
-
const
|
|
29415
|
-
for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, scene.priceMintick, pane.axisFormat)) {
|
|
29882
|
+
for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
|
|
29416
29883
|
const y = coords.priceToY(t.price, pane.scale, pane.bounds);
|
|
29417
29884
|
if (y < pane.bounds.top + 6 || y > pane.bounds.top + pane.bounds.height - 4) continue;
|
|
29885
|
+
if (t.major === true) ctx.font = majorFont;
|
|
29886
|
+
else if (t.major === false) ctx.globalAlpha = alpha * MINOR_LABEL_ALPHA;
|
|
29418
29887
|
ctx.fillText(t.label, dataW + 6, y);
|
|
29888
|
+
if (t.major === true) ctx.font = font;
|
|
29889
|
+
else if (t.major === false) ctx.globalAlpha = alpha;
|
|
29419
29890
|
}
|
|
29420
29891
|
if (pane.axisBands) {
|
|
29421
29892
|
for (const b of pane.axisBands) {
|
|
@@ -29657,15 +30128,23 @@ var CrosshairRenderer = class {
|
|
|
29657
30128
|
constructor() {
|
|
29658
30129
|
this.canvas = null;
|
|
29659
30130
|
this.ctx = null;
|
|
30131
|
+
this.shade = null;
|
|
29660
30132
|
}
|
|
29661
30133
|
mount(canvas) {
|
|
29662
30134
|
this.canvas = canvas;
|
|
29663
30135
|
this.ctx = canvas.getContext("2d");
|
|
29664
30136
|
}
|
|
30137
|
+
/** The plot area the last frame veiled for `crosshairOverride.shadeRight` — from the
|
|
30138
|
+
* picked bar's right edge to the price scale, full plot height (the union when both the
|
|
30139
|
+
* local and a synced crosshair veil) — or null when no veil was painted. */
|
|
30140
|
+
get shadeRightArea() {
|
|
30141
|
+
return this.shade;
|
|
30142
|
+
}
|
|
29665
30143
|
/** Clear the cursor canvas and (re)draw the crosshair lines + axis chips. The optional
|
|
29666
30144
|
* `separatorHoverY` highlights the draggable pane separator under the cursor;
|
|
29667
30145
|
* `external` is a SYNCED ghost crosshair (another chart's pointer, pixel-resolved). */
|
|
29668
30146
|
render(scene, coords, theme, separatorHoverY = null, external = null) {
|
|
30147
|
+
this.shade = null;
|
|
29669
30148
|
const ctx = this.ctx;
|
|
29670
30149
|
const canvas = this.canvas;
|
|
29671
30150
|
if (!ctx || !canvas) return;
|
|
@@ -29688,12 +30167,7 @@ var CrosshairRenderer = class {
|
|
|
29688
30167
|
const x = crisp(coords.logicalToX(logical), ov?.width ?? cs.width);
|
|
29689
30168
|
if (vertical && ov?.shadeRight) {
|
|
29690
30169
|
const from = Math.max(0, Math.round(coords.logicalToX(logical + 0.5)));
|
|
29691
|
-
if (from < dataW)
|
|
29692
|
-
ctx.fillStyle = ov.shadeRight.color;
|
|
29693
|
-
ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
|
|
29694
|
-
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
29695
|
-
ctx.globalAlpha = 1;
|
|
29696
|
-
}
|
|
30170
|
+
if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
|
|
29697
30171
|
}
|
|
29698
30172
|
ctx.strokeStyle = ov?.color ?? cs.color ?? theme.textColor;
|
|
29699
30173
|
ctx.lineWidth = ov?.width ?? cs.width;
|
|
@@ -29729,6 +30203,7 @@ var CrosshairRenderer = class {
|
|
|
29729
30203
|
destroy() {
|
|
29730
30204
|
this.canvas = null;
|
|
29731
30205
|
this.ctx = null;
|
|
30206
|
+
this.shade = null;
|
|
29732
30207
|
}
|
|
29733
30208
|
/** The synced ghost: a dimmed vertical line at the bar the renderer resolved as
|
|
29734
30209
|
* CONTAINING the foreign time (+ horizontal line when a comparable price came
|
|
@@ -29748,12 +30223,7 @@ var CrosshairRenderer = class {
|
|
|
29748
30223
|
if (ov?.vertical === false) return;
|
|
29749
30224
|
if (ov?.shadeRight) {
|
|
29750
30225
|
const from = Math.max(0, Math.round(coords.logicalToX(Math.round(coords.xToLogical(ext.x)) + 0.5)));
|
|
29751
|
-
if (from < dataW)
|
|
29752
|
-
ctx.fillStyle = ov.shadeRight.color;
|
|
29753
|
-
ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
|
|
29754
|
-
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
29755
|
-
ctx.globalAlpha = 1;
|
|
29756
|
-
}
|
|
30226
|
+
if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
|
|
29757
30227
|
}
|
|
29758
30228
|
if (ext.line === false || x < 0 || x > dataW) return;
|
|
29759
30229
|
ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
@@ -29789,6 +30259,15 @@ var CrosshairRenderer = class {
|
|
|
29789
30259
|
this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
29790
30260
|
ctx.globalAlpha = 1;
|
|
29791
30261
|
}
|
|
30262
|
+
/** Paint the `shadeRight` veil from `from` to the price scale and record the veiled area. */
|
|
30263
|
+
paintShade(ctx, shade, from, dataW, dataH) {
|
|
30264
|
+
ctx.fillStyle = shade.color;
|
|
30265
|
+
ctx.globalAlpha = shade.opacity ?? 1;
|
|
30266
|
+
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
30267
|
+
ctx.globalAlpha = 1;
|
|
30268
|
+
const x = this.shade ? Math.min(this.shade.x, from) : from;
|
|
30269
|
+
this.shade = { x, width: dataW - x, height: dataH };
|
|
30270
|
+
}
|
|
29792
30271
|
/** A soft band + a brighter crisp center line over the hovered separator, so it reads as
|
|
29793
30272
|
* a draggable handle (the cursor is already `row-resize`). Spans the full width (data +
|
|
29794
30273
|
* scale gutter) to match the separator itself. Theme-derived (textColor). */
|
|
@@ -33194,15 +33673,16 @@ var DrawingSettingsDialog = class {
|
|
|
33194
33673
|
}
|
|
33195
33674
|
open(drawing, actions, kind) {
|
|
33196
33675
|
this.close();
|
|
33197
|
-
const
|
|
33676
|
+
const live = actions.resolve() ?? drawing;
|
|
33677
|
+
const snapshot = clonePlain(live.serialize());
|
|
33198
33678
|
const grid = fieldGrid({ variant: "inputs" });
|
|
33199
33679
|
grid.style.padding = "16px 20px";
|
|
33200
33680
|
grid.style.overflowY = "auto";
|
|
33201
33681
|
grid.style.overflowX = "hidden";
|
|
33202
33682
|
grid.style.flex = "1 1 auto";
|
|
33203
|
-
if (kind === "position" &&
|
|
33204
|
-
else if (kind === "frvp" &&
|
|
33205
|
-
else if (kind === "levels") this.buildLevels(grid,
|
|
33683
|
+
if (kind === "position" && live instanceof PositionTool) this.buildPosition(grid, live, actions);
|
|
33684
|
+
else if (kind === "frvp" && live instanceof FixedRangeVolumeProfile) this.buildFrvp(grid, live, actions);
|
|
33685
|
+
else if (kind === "levels") this.buildLevels(grid, live, actions);
|
|
33206
33686
|
else return;
|
|
33207
33687
|
const ui = new Dialog({
|
|
33208
33688
|
host: this.host,
|
|
@@ -33849,7 +34329,7 @@ var DrawingSettingsPopup = class {
|
|
|
33849
34329
|
this.reposition();
|
|
33850
34330
|
return;
|
|
33851
34331
|
}
|
|
33852
|
-
const text = drawing.text;
|
|
34332
|
+
const text = (actions.resolve() ?? drawing).text;
|
|
33853
34333
|
const panel = document.createElement("div");
|
|
33854
34334
|
panel.style.cssText = `padding:6px;border-top:1px solid var(--vela-border);display:flex;flex-direction:column;gap:4px;`;
|
|
33855
34335
|
const ta = new TextArea({
|
|
@@ -35020,6 +35500,12 @@ var UserDrawingController = class {
|
|
|
35020
35500
|
this.render();
|
|
35021
35501
|
}
|
|
35022
35502
|
}
|
|
35503
|
+
/** Drop the hover (its handles) — the pointer no longer reaches the drawings. */
|
|
35504
|
+
clearHover() {
|
|
35505
|
+
if (this.hoveredId == null) return;
|
|
35506
|
+
this.hoveredId = null;
|
|
35507
|
+
this.render();
|
|
35508
|
+
}
|
|
35023
35509
|
pointerUp(x, y, snap = "off") {
|
|
35024
35510
|
if (this.eraserMode) {
|
|
35025
35511
|
this.erasing = false;
|
|
@@ -36010,9 +36496,10 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
36010
36496
|
consider(drawings.max);
|
|
36011
36497
|
}
|
|
36012
36498
|
if (min === Infinity || max === -Infinity) return { min: 0, max: 1 };
|
|
36013
|
-
if (min
|
|
36014
|
-
const
|
|
36015
|
-
|
|
36499
|
+
if (max - min <= Math.max(Math.abs(min), Math.abs(max)) * 1e-12) {
|
|
36500
|
+
const mid = (min + max) / 2;
|
|
36501
|
+
const pad = Math.abs(mid) * 0.1 || 1;
|
|
36502
|
+
return { min: mid - pad, max: mid + pad, log: log && mid - pad > 0 };
|
|
36016
36503
|
}
|
|
36017
36504
|
const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
|
|
36018
36505
|
const above = margins.top / 100 / content;
|
|
@@ -36516,7 +37003,9 @@ function bandEdgeSlot(logical) {
|
|
|
36516
37003
|
return Math.ceil(logical) - 0.5;
|
|
36517
37004
|
}
|
|
36518
37005
|
var BackdropRenderer = class {
|
|
36519
|
-
|
|
37006
|
+
/** `axisTicks` is shared with the chrome layer, so gridlines and price labels agree. */
|
|
37007
|
+
constructor(axisTicks = new PriceAxisTickSource()) {
|
|
37008
|
+
this.axisTicks = axisTicks;
|
|
36520
37009
|
this.canvas = null;
|
|
36521
37010
|
this.ctx = null;
|
|
36522
37011
|
}
|
|
@@ -36593,9 +37082,8 @@ var BackdropRenderer = class {
|
|
|
36593
37082
|
if (scene.showGrid && gridHorz.visible && !pane.collapsed) {
|
|
36594
37083
|
ctx.globalAlpha = gridAlpha;
|
|
36595
37084
|
ctx.strokeStyle = horzColor;
|
|
36596
|
-
const pct = percentScaleFor(scene, pane);
|
|
36597
37085
|
ctx.beginPath();
|
|
36598
|
-
for (const t of
|
|
37086
|
+
for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
|
|
36599
37087
|
const y = Math.round(coords.priceToY(t.price, pane.scale, pane.bounds)) + 0.5;
|
|
36600
37088
|
if (y < pane.bounds.top || y > pane.bounds.top + pane.bounds.height) continue;
|
|
36601
37089
|
ctx.moveTo(0, y);
|
|
@@ -37082,8 +37570,16 @@ var NativeRenderer = class {
|
|
|
37082
37570
|
this.toolbarGutter = 0;
|
|
37083
37571
|
// px reserved on the left for the docked drawings toolbar (0 when hidden)
|
|
37084
37572
|
this.mountContainer = null;
|
|
37573
|
+
/** The host-styleable twin of the `shadeRight` veil (`.vela-shade-right`), directly under
|
|
37574
|
+
* the cursor canvas; hidden whenever the crosshair layer paints no veil. */
|
|
37575
|
+
this.shadeRightEl = null;
|
|
37576
|
+
/** The veil geometry last written to {@link shadeRightEl} ('' = hidden) — the DOM is
|
|
37577
|
+
* touched only when it changes, never on a plain pointer move. */
|
|
37578
|
+
this.shadeRightSig = "";
|
|
37085
37579
|
this.userDrawings = null;
|
|
37086
|
-
|
|
37580
|
+
/** One price-axis tick source for the gridlines (backdrop) and the labels (chrome). */
|
|
37581
|
+
this.priceAxisTicks = new PriceAxisTickSource();
|
|
37582
|
+
this.backdropRenderer = new BackdropRenderer(this.priceAxisTicks);
|
|
37087
37583
|
this.volumeRenderer = new VolumeRenderer();
|
|
37088
37584
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
37089
37585
|
/**
|
|
@@ -37116,7 +37612,7 @@ var NativeRenderer = class {
|
|
|
37116
37612
|
this.backendMode = "auto";
|
|
37117
37613
|
this.glowAmount = 0;
|
|
37118
37614
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
37119
|
-
this.chrome = new ChromeRenderer();
|
|
37615
|
+
this.chrome = new ChromeRenderer(this.priceAxisTicks);
|
|
37120
37616
|
/** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
|
|
37121
37617
|
this.indicatorSlices = new IndicatorDrawingSlices();
|
|
37122
37618
|
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
@@ -37233,6 +37729,8 @@ var NativeRenderer = class {
|
|
|
37233
37729
|
// pane just below it
|
|
37234
37730
|
this.resizeSplitStart = null;
|
|
37235
37731
|
// the two panes' shared span when the drag began
|
|
37732
|
+
/** The user drawings take part in pointer input (the runtime `drawingsInteractive` feature). */
|
|
37733
|
+
this.drawingsInteractive = true;
|
|
37236
37734
|
this.hoverSeparatorY = null;
|
|
37237
37735
|
// pixel y of the separator under the cursor (drives its hover highlight)
|
|
37238
37736
|
// ── pane management (merge / reorder / collapse / maximize) ──
|
|
@@ -37288,8 +37786,9 @@ var NativeRenderer = class {
|
|
|
37288
37786
|
this.toggleVisibleCbs = /* @__PURE__ */ new Set();
|
|
37289
37787
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
37290
37788
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
37789
|
+
this.priceStyleWillChangeCbs = /* @__PURE__ */ new Set();
|
|
37291
37790
|
this.name = "native";
|
|
37292
|
-
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animScroll", "animAutoscale", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "marks", "indicatorTitles", "indicatorValues", "crosshairOverride"];
|
|
37791
|
+
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animScroll", "animAutoscale", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "marks", "indicatorTitles", "indicatorValues", "crosshairOverride", "drawingsInteractive", "priceAxisTicks"];
|
|
37293
37792
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
37294
37793
|
* so moving onto the button doesn't count as leaving). */
|
|
37295
37794
|
this.onScrollProximityMove = (e) => {
|
|
@@ -37333,6 +37832,7 @@ var NativeRenderer = class {
|
|
|
37333
37832
|
this.scene.priceStyle = opts.priceStyle;
|
|
37334
37833
|
this.scene.basePainting = basePaintingOf(opts.priceStyle);
|
|
37335
37834
|
this.scene.candleOverride = candleOverrideFor(opts.priceStyle, this.scene.style.chartTypes);
|
|
37835
|
+
if (typeof opts.priceAxisTicks === "function") this.priceAxisTicks.setHook(opts.priceAxisTicks);
|
|
37336
37836
|
}
|
|
37337
37837
|
this.theme = this.deriveTheme(DARK_THEME);
|
|
37338
37838
|
}
|
|
@@ -37471,6 +37971,15 @@ var NativeRenderer = class {
|
|
|
37471
37971
|
case "crosshairOverride":
|
|
37472
37972
|
this.scene.crosshairOverride = sanitizeCrosshairOverride(value);
|
|
37473
37973
|
break;
|
|
37974
|
+
case "drawingsInteractive":
|
|
37975
|
+
this.drawingsInteractive = value !== false;
|
|
37976
|
+
if (this.input) this.input.drawings = this.drawingsInteractive;
|
|
37977
|
+
if (!this.drawingsInteractive) this.userDrawings?.clearHover();
|
|
37978
|
+
return;
|
|
37979
|
+
// affects the next gesture only — clearHover repaints what it drops
|
|
37980
|
+
case "priceAxisTicks":
|
|
37981
|
+
this.priceAxisTicks.setHook(typeof value === "function" ? value : null);
|
|
37982
|
+
break;
|
|
37474
37983
|
case "keyboard":
|
|
37475
37984
|
this.setKeyboardEnabled(Boolean(value));
|
|
37476
37985
|
return;
|
|
@@ -37586,6 +38095,10 @@ var NativeRenderer = class {
|
|
|
37586
38095
|
return { visible: this.scene.marks.visible, groups: { ...this.scene.marks.groups } };
|
|
37587
38096
|
case "crosshairOverride":
|
|
37588
38097
|
return this.scene.crosshairOverride ? { ...this.scene.crosshairOverride } : null;
|
|
38098
|
+
case "drawingsInteractive":
|
|
38099
|
+
return this.drawingsInteractive;
|
|
38100
|
+
case "priceAxisTicks":
|
|
38101
|
+
return this.priceAxisTicks.hook;
|
|
37589
38102
|
case "keyboard":
|
|
37590
38103
|
return this.keyboardEnabled;
|
|
37591
38104
|
case "historyChords":
|
|
@@ -38259,6 +38772,12 @@ var NativeRenderer = class {
|
|
|
38259
38772
|
Object.assign(this.drawingsCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
38260
38773
|
this.cursorCanvas = document.createElement("canvas");
|
|
38261
38774
|
Object.assign(this.cursorCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
38775
|
+
this.shadeRightEl = document.createElement("div");
|
|
38776
|
+
this.shadeRightEl.className = "vela-shade-right";
|
|
38777
|
+
this.shadeRightEl.setAttribute("aria-hidden", "true");
|
|
38778
|
+
this.shadeRightEl.hidden = true;
|
|
38779
|
+
Object.assign(this.shadeRightEl.style, { position: "absolute", top: "0", left: "0", width: "0", height: "0", pointerEvents: "none", display: "none" });
|
|
38780
|
+
this.shadeRightSig = "";
|
|
38262
38781
|
this.overlayRoot = document.createElement("div");
|
|
38263
38782
|
Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
|
|
38264
38783
|
this.plot = document.createElement("div");
|
|
@@ -38266,7 +38785,7 @@ var NativeRenderer = class {
|
|
|
38266
38785
|
this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
|
|
38267
38786
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
38268
38787
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
38269
|
-
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
38788
|
+
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.shadeRightEl, this.cursorCanvas, this.overlayRoot);
|
|
38270
38789
|
this.layerOrderSig = "";
|
|
38271
38790
|
this.wrapper.appendChild(this.plot);
|
|
38272
38791
|
this.factoryConfig = this.getConfig();
|
|
@@ -38324,13 +38843,14 @@ var NativeRenderer = class {
|
|
|
38324
38843
|
drawingsPointerDown: (x, y, snap, shift2, mod) => this.userDrawings?.pointerDown(x, y, snap, shift2, mod),
|
|
38325
38844
|
drawingsPointerMove: (x, y, snap, shift2, mod) => this.userDrawings?.pointerMove(x, y, snap, shift2, mod),
|
|
38326
38845
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
38327
|
-
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
|
|
38846
|
+
drawingsCursor: (x, y) => (this.drawingsInteractive ? this.userDrawings?.cursorAt(x, y) : null) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
|
|
38328
38847
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
38329
38848
|
drawingsClearTransient: () => this.userDrawings?.clearTransient()
|
|
38330
38849
|
});
|
|
38331
38850
|
this.input.rightEdgeZoom = this.zoomAnchorMode === "right";
|
|
38332
38851
|
this.input.axisDrag = this.axisDragEnabled;
|
|
38333
38852
|
this.input.paneResize = this.paneResizeEnabled;
|
|
38853
|
+
this.input.drawings = this.drawingsInteractive;
|
|
38334
38854
|
this.input.attach(this.dataCanvas);
|
|
38335
38855
|
if (this.settingsEnabled) this.setSettingsEnabled(true);
|
|
38336
38856
|
this.syncCountdownTimer();
|
|
@@ -38639,6 +39159,8 @@ var NativeRenderer = class {
|
|
|
38639
39159
|
this.backend.destroy();
|
|
38640
39160
|
this.chrome.destroy();
|
|
38641
39161
|
this.crosshairLayer.destroy();
|
|
39162
|
+
this.shadeRightEl?.remove();
|
|
39163
|
+
this.shadeRightEl = null;
|
|
38642
39164
|
this.attributionEl?.remove();
|
|
38643
39165
|
this.attributionEl = null;
|
|
38644
39166
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
@@ -38985,14 +39507,21 @@ var NativeRenderer = class {
|
|
|
38985
39507
|
this.priceStyleCbs.add(cb);
|
|
38986
39508
|
return () => this.priceStyleCbs.delete(cb);
|
|
38987
39509
|
}
|
|
39510
|
+
onPriceStyleWillChange(cb) {
|
|
39511
|
+
this.priceStyleWillChangeCbs.add(cb);
|
|
39512
|
+
return () => this.priceStyleWillChangeCbs.delete(cb);
|
|
39513
|
+
}
|
|
38988
39514
|
/**
|
|
38989
39515
|
* THE single write path for the base price style at runtime (feature set / settings dialog /
|
|
38990
|
-
* config template — the constructor seeds the field directly, pre-listeners).
|
|
38991
|
-
*
|
|
38992
|
-
*
|
|
39516
|
+
* config template — the constructor seeds the field directly, pre-listeners). Announces the
|
|
39517
|
+
* switch while the scene still holds the old style (a listener may paint/capture that frame),
|
|
39518
|
+
* then updates the scene, eases any reveal layer toward the new style's target, and notifies
|
|
39519
|
+
* the core — which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
|
|
38993
39520
|
*/
|
|
38994
39521
|
setPriceStyle(style) {
|
|
38995
39522
|
if (style === this.scene.priceStyle) return;
|
|
39523
|
+
const from = this.scene.priceStyle;
|
|
39524
|
+
for (const cb of this.priceStyleWillChangeCbs) cb(from, style);
|
|
38996
39525
|
this.scene.priceStyle = style;
|
|
38997
39526
|
this.scene.basePainting = basePaintingOf(style);
|
|
38998
39527
|
this.scene.candleOverride = candleOverrideFor(style, this.scene.style.chartTypes);
|
|
@@ -39222,7 +39751,7 @@ var NativeRenderer = class {
|
|
|
39222
39751
|
if (this.easeLiveBar(dtMs)) active = true;
|
|
39223
39752
|
this.skeletonClockMs += dtMs;
|
|
39224
39753
|
this.paintData();
|
|
39225
|
-
this.
|
|
39754
|
+
this.renderCrosshairLayer();
|
|
39226
39755
|
this.updateLegendValues();
|
|
39227
39756
|
this.emitViewportChange();
|
|
39228
39757
|
return active;
|
|
@@ -39797,10 +40326,31 @@ var NativeRenderer = class {
|
|
|
39797
40326
|
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
39798
40327
|
this.trackMarkPopover();
|
|
39799
40328
|
}
|
|
39800
|
-
this.
|
|
40329
|
+
this.renderCrosshairLayer();
|
|
39801
40330
|
if (!repaintsData(level) && this.paintedData) this.repaintCursorLayers();
|
|
39802
40331
|
this.updateLegendValues();
|
|
39803
40332
|
}
|
|
40333
|
+
/** Repaint the L2 crosshair canvas and keep the `.vela-shade-right` element on the veil it painted. */
|
|
40334
|
+
renderCrosshairLayer() {
|
|
40335
|
+
this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
|
|
40336
|
+
this.syncShadeRight();
|
|
40337
|
+
}
|
|
40338
|
+
/** Position (or hide) the `.vela-shade-right` element over the veil of the last crosshair frame. */
|
|
40339
|
+
syncShadeRight() {
|
|
40340
|
+
const el = this.shadeRightEl;
|
|
40341
|
+
if (!el) return;
|
|
40342
|
+
const area = this.crosshairLayer.shadeRightArea;
|
|
40343
|
+
const sig = area ? `${area.x},${area.width},${area.height}` : "";
|
|
40344
|
+
if (sig === this.shadeRightSig) return;
|
|
40345
|
+
this.shadeRightSig = sig;
|
|
40346
|
+
if (!area) {
|
|
40347
|
+
el.hidden = true;
|
|
40348
|
+
el.style.display = "none";
|
|
40349
|
+
return;
|
|
40350
|
+
}
|
|
40351
|
+
Object.assign(el.style, { left: `${area.x}px`, width: `${area.width}px`, height: `${area.height}px`, display: "" });
|
|
40352
|
+
el.hidden = false;
|
|
40353
|
+
}
|
|
39804
40354
|
/** Repaint the SDK layers that opted into cursor tracking (their own canvas only). */
|
|
39805
40355
|
repaintCursorLayers() {
|
|
39806
40356
|
const pane = this.scene.panes.get(PRICE_PANE_ID2);
|
|
@@ -40594,7 +41144,7 @@ var NativeRenderer = class {
|
|
|
40594
41144
|
if (this.animator.active) {
|
|
40595
41145
|
this.computeScales();
|
|
40596
41146
|
this.paintData();
|
|
40597
|
-
this.
|
|
41147
|
+
this.renderCrosshairLayer();
|
|
40598
41148
|
} else {
|
|
40599
41149
|
this.scheduler.flushNow(4 /* Full */);
|
|
40600
41150
|
}
|
|
@@ -44609,7 +45159,8 @@ var Vela = class {
|
|
|
44609
45159
|
glow: options.glow ?? 0,
|
|
44610
45160
|
upColor: options.upColor ?? BULLISH,
|
|
44611
45161
|
downColor: options.downColor ?? BEARISH,
|
|
44612
|
-
priceStyle: options.priceStyle ?? "candles"
|
|
45162
|
+
priceStyle: options.priceStyle ?? "candles",
|
|
45163
|
+
priceAxisTicks: options.priceAxis?.ticks ?? null
|
|
44613
45164
|
};
|
|
44614
45165
|
const RendererClass = options.renderer ?? NativeRenderer;
|
|
44615
45166
|
if (typeof RendererClass !== "function") {
|
|
@@ -46127,16 +46678,25 @@ var CellControls = class {
|
|
|
46127
46678
|
// src/widget/context-menu.ts
|
|
46128
46679
|
var PRICE_AXIS_W = 60;
|
|
46129
46680
|
var TIME_AXIS_H3 = 26;
|
|
46681
|
+
var OFF_PLOT = { price: null, time: null, paneKind: null };
|
|
46130
46682
|
var ChartContextMenu = class {
|
|
46131
46683
|
constructor(host, cbs) {
|
|
46132
46684
|
this.cbs = cbs;
|
|
46133
46685
|
this.chart = null;
|
|
46686
|
+
this.offCrosshair = null;
|
|
46687
|
+
/** The bound chart's latest crosshair report — a right-click always follows a pointer
|
|
46688
|
+
* move to its spot, so this is where it landed. */
|
|
46689
|
+
this.crosshair = OFF_PLOT;
|
|
46690
|
+
/** {@link crosshair} as it stood when the menu opened: the pointer moves on through the
|
|
46691
|
+
* menu before a row is picked, and `run` must see the right-clicked spot. */
|
|
46692
|
+
this.pointer = OFF_PLOT;
|
|
46134
46693
|
this.lastZone = "body";
|
|
46135
46694
|
/** The pane whose scale the open price-axis menu targets (null ⇒ the main scale). */
|
|
46136
46695
|
this.lastPane = null;
|
|
46137
46696
|
this.onContextMenu = (e) => {
|
|
46138
46697
|
e.preventDefault();
|
|
46139
46698
|
if (!this.chart) return;
|
|
46699
|
+
this.pointer = this.crosshair;
|
|
46140
46700
|
this.lastZone = this.zoneOf(e);
|
|
46141
46701
|
this.lastPane = this.lastZone === "price-axis" ? this.paneAt(e) : null;
|
|
46142
46702
|
this.menu.setItems(this.itemsFor(this.lastZone));
|
|
@@ -46150,15 +46710,23 @@ var ChartContextMenu = class {
|
|
|
46150
46710
|
// Pointer-anchored action menu: checked state reads as a leading ✓, not a
|
|
46151
46711
|
// washed row (which would read as hover in a menu with no trigger button).
|
|
46152
46712
|
checkmarks: true,
|
|
46713
|
+
iconBadges: true,
|
|
46153
46714
|
onSelect: (id) => this.run(id)
|
|
46154
46715
|
});
|
|
46155
46716
|
host.addEventListener("contextmenu", this.onContextMenu);
|
|
46156
46717
|
}
|
|
46157
46718
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
46158
46719
|
onChart(chart) {
|
|
46720
|
+
this.offCrosshair?.();
|
|
46159
46721
|
this.chart = chart;
|
|
46722
|
+
this.crosshair = OFF_PLOT;
|
|
46723
|
+
this.offCrosshair = chart.renderer.onCrosshairMove((e) => {
|
|
46724
|
+
this.crosshair = { price: e.price, time: e.time, paneKind: e.paneKind ?? null };
|
|
46725
|
+
});
|
|
46160
46726
|
}
|
|
46161
46727
|
destroy() {
|
|
46728
|
+
this.offCrosshair?.();
|
|
46729
|
+
this.offCrosshair = null;
|
|
46162
46730
|
this.host.removeEventListener("contextmenu", this.onContextMenu);
|
|
46163
46731
|
this.menu.destroy();
|
|
46164
46732
|
}
|
|
@@ -46177,49 +46745,52 @@ var ChartContextMenu = class {
|
|
|
46177
46745
|
flag(feature) {
|
|
46178
46746
|
return Boolean(this.chart?.renderer.get(feature));
|
|
46179
46747
|
}
|
|
46180
|
-
|
|
46748
|
+
/** A fresh widget context carrying the pointer captured at open. Copied by property
|
|
46749
|
+
* descriptor: the context's getters must stay LIVE, and a spread would freeze them. */
|
|
46750
|
+
context() {
|
|
46181
46751
|
const ctx = this.cbs.getContext?.();
|
|
46182
|
-
|
|
46183
|
-
|
|
46184
|
-
|
|
46185
|
-
|
|
46186
|
-
|
|
46752
|
+
if (!ctx) return void 0;
|
|
46753
|
+
return Object.create(Object.getPrototypeOf(ctx), {
|
|
46754
|
+
...Object.getOwnPropertyDescriptors(ctx),
|
|
46755
|
+
pointer: { value: this.pointer, enumerable: true }
|
|
46756
|
+
});
|
|
46757
|
+
}
|
|
46758
|
+
contributed(zone) {
|
|
46759
|
+
return widgetActions(`context:${zone}`, this.context()).map((a) => ({
|
|
46760
|
+
item: { id: `action:${a.id}`, label: a.label, icon: a.icon },
|
|
46761
|
+
order: a.order
|
|
46187
46762
|
}));
|
|
46188
46763
|
}
|
|
46189
46764
|
itemsFor(zone) {
|
|
46765
|
+
return composeMenu(zone, this.builtinItems(zone), this.contributed(zone));
|
|
46766
|
+
}
|
|
46767
|
+
builtinItems(zone) {
|
|
46190
46768
|
if (zone === "price-axis") {
|
|
46191
46769
|
const pane = this.lastPane;
|
|
46192
|
-
return
|
|
46193
|
-
|
|
46194
|
-
|
|
46195
|
-
|
|
46196
|
-
|
|
46197
|
-
|
|
46198
|
-
|
|
46199
|
-
|
|
46200
|
-
|
|
46201
|
-
}),
|
|
46202
|
-
...this.contributed(zone)
|
|
46203
|
-
];
|
|
46770
|
+
return priceAxisItems({
|
|
46771
|
+
auto: this.chart?.renderer.get("autoScale") !== false,
|
|
46772
|
+
invert: pane ? pane.invert : this.flag("invertScale"),
|
|
46773
|
+
choice: scaleChoiceOf(pane ?? { mode: String(this.chart?.renderer.get("scaleMode") ?? "price"), log: this.flag("logScale") }),
|
|
46774
|
+
axisLabels: this.flag("axisLabels"),
|
|
46775
|
+
priceLabel: this.flag("priceLabel"),
|
|
46776
|
+
countdown: this.flag("countdown"),
|
|
46777
|
+
priceLine: this.flag("currentPriceLine")
|
|
46778
|
+
});
|
|
46204
46779
|
}
|
|
46205
46780
|
if (zone === "time-axis") {
|
|
46206
|
-
|
|
46207
|
-
return [...timeAxisItems(tz), ...this.contributed("time-axis")];
|
|
46781
|
+
return timeAxisItems(this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC"));
|
|
46208
46782
|
}
|
|
46209
46783
|
const chart = this.chart;
|
|
46210
|
-
return
|
|
46211
|
-
|
|
46212
|
-
|
|
46213
|
-
|
|
46214
|
-
}),
|
|
46215
|
-
...this.contributed("body")
|
|
46216
|
-
];
|
|
46784
|
+
return bodyItems({
|
|
46785
|
+
drawings: chart?.drawings.supported ? chart.drawings.all().length : 0,
|
|
46786
|
+
indicators: chart?.indicators().length ?? 0
|
|
46787
|
+
});
|
|
46217
46788
|
}
|
|
46218
46789
|
run(id) {
|
|
46219
46790
|
const chart = this.chart;
|
|
46220
46791
|
if (!chart) return;
|
|
46221
46792
|
if (id.startsWith("action:")) {
|
|
46222
|
-
const ctx = this.
|
|
46793
|
+
const ctx = this.context();
|
|
46223
46794
|
if (ctx) widgetActions(`context:${this.lastZone}`, ctx).find((a) => a.id === id.slice("action:".length))?.run(ctx);
|
|
46224
46795
|
return;
|
|
46225
46796
|
}
|
|
@@ -46343,8 +46914,8 @@ function seedDefaults(opts) {
|
|
|
46343
46914
|
};
|
|
46344
46915
|
}
|
|
46345
46916
|
function cellChartDefaults(opts) {
|
|
46346
|
-
const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings } = opts;
|
|
46347
|
-
return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings };
|
|
46917
|
+
const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis } = opts;
|
|
46918
|
+
return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis };
|
|
46348
46919
|
}
|
|
46349
46920
|
function cellDrawings(opt) {
|
|
46350
46921
|
if (opt === false) return false;
|
|
@@ -46914,12 +47485,14 @@ var ChartCell = class {
|
|
|
46914
47485
|
this.watermarkOn = visible;
|
|
46915
47486
|
this.watermark?.setVisible(visible);
|
|
46916
47487
|
this.deps.onStateDirty();
|
|
47488
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
46917
47489
|
}
|
|
46918
47490
|
/** Show/hide the "Replay" line under this cell's watermark while it replays (persisted per cell). */
|
|
46919
47491
|
setReplayWatermarkVisible(visible) {
|
|
46920
47492
|
this.replayWatermarkOn = visible;
|
|
46921
47493
|
this.watermark?.setReplayVisible(visible);
|
|
46922
47494
|
this.deps.onStateDirty();
|
|
47495
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
46923
47496
|
}
|
|
46924
47497
|
/** Show/hide this cell's indicator titles — the in-chart legend rows (persisted per cell). */
|
|
46925
47498
|
setIndicatorTitlesVisible(visible) {
|
|
@@ -46940,18 +47513,20 @@ var ChartCell = class {
|
|
|
46940
47513
|
this.statusline?.setPartVisible(part, visible);
|
|
46941
47514
|
this.deps.onStatusPrefsChanged(this.id);
|
|
46942
47515
|
}
|
|
46943
|
-
/** This cell's Status line tab prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
47516
|
+
/** This cell's Status line tab and watermark prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
46944
47517
|
statusPrefs() {
|
|
46945
47518
|
const sl = this.statusline;
|
|
46946
47519
|
return {
|
|
46947
47520
|
parts: sl ? { logo: sl.partVisible("logo"), name: sl.partVisible("name"), market: sl.partVisible("market"), ohlc: sl.partVisible("ohlc"), change: sl.partVisible("change") } : null,
|
|
46948
47521
|
indicatorTitles: this.indicatorTitlesOn,
|
|
46949
|
-
indicatorValues: this.indicatorValuesOn
|
|
47522
|
+
indicatorValues: this.indicatorValuesOn,
|
|
47523
|
+
watermark: this.watermarkOn,
|
|
47524
|
+
replayWatermark: this.replayWatermarkOn
|
|
46950
47525
|
};
|
|
46951
47526
|
}
|
|
46952
|
-
/** Converge this cell's Status line tab prefs to `prefs` — the
|
|
46953
|
-
* the workspace's style link. Idempotent: matching values change
|
|
46954
|
-
* propagated echo dies on its own. */
|
|
47527
|
+
/** Converge this cell's Status line tab and watermark prefs to `prefs` — the
|
|
47528
|
+
* follower half of the workspace's style link. Idempotent: matching values change
|
|
47529
|
+
* nothing, so a propagated echo dies on its own. */
|
|
46955
47530
|
applyStatusPrefs(prefs) {
|
|
46956
47531
|
if (prefs.parts && this.statusline) {
|
|
46957
47532
|
for (const part of Object.keys(prefs.parts)) {
|
|
@@ -46960,6 +47535,8 @@ var ChartCell = class {
|
|
|
46960
47535
|
}
|
|
46961
47536
|
if (prefs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(prefs.indicatorTitles);
|
|
46962
47537
|
if (prefs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(prefs.indicatorValues);
|
|
47538
|
+
if (prefs.watermark !== this.watermarkOn) this.setWatermarkVisible(prefs.watermark);
|
|
47539
|
+
if (prefs.replayWatermark !== this.replayWatermarkOn) this.setReplayWatermarkVisible(prefs.replayWatermark);
|
|
46963
47540
|
}
|
|
46964
47541
|
/** The LIVE chart of this cell — never cache it across a layout change (the cell's
|
|
46965
47542
|
* identity is what endures; the chart dies with the cell). */
|
|
@@ -47864,6 +48441,55 @@ function occupancyGrid(def) {
|
|
|
47864
48441
|
const cols = def.cols.length;
|
|
47865
48442
|
return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ?? `\xB7${r}x${c}`));
|
|
47866
48443
|
}
|
|
48444
|
+
var GLYPH_MIN = 1.5;
|
|
48445
|
+
var GLYPH_SPAN = 13;
|
|
48446
|
+
function glyphStops(weights, n) {
|
|
48447
|
+
const w = Array.from({ length: n }, (_, i) => {
|
|
48448
|
+
const v = weights[i];
|
|
48449
|
+
return v !== void 0 && Number.isFinite(v) && v > 0 ? v : 1;
|
|
48450
|
+
});
|
|
48451
|
+
const total = w.reduce((a, b) => a + b, 0);
|
|
48452
|
+
const stops = [GLYPH_MIN];
|
|
48453
|
+
let acc = 0;
|
|
48454
|
+
for (const v of w) {
|
|
48455
|
+
acc += v;
|
|
48456
|
+
stops.push(Math.round((GLYPH_MIN + GLYPH_SPAN * acc / total) * 100) / 100);
|
|
48457
|
+
}
|
|
48458
|
+
return stops;
|
|
48459
|
+
}
|
|
48460
|
+
function layoutGlyph(def) {
|
|
48461
|
+
const grid = occupancyGrid(def);
|
|
48462
|
+
const rows = grid.length;
|
|
48463
|
+
const cols = grid.reduce((n, row) => Math.max(n, row.length), 0);
|
|
48464
|
+
const xs = glyphStops(def.cols, cols);
|
|
48465
|
+
const ys = glyphStops(def.rows, rows);
|
|
48466
|
+
const at = (r, c) => grid[r]?.[c];
|
|
48467
|
+
const seams = [];
|
|
48468
|
+
for (let c = 1; c < cols; c += 1) {
|
|
48469
|
+
let start = -1;
|
|
48470
|
+
for (let r = 0; r <= rows; r += 1) {
|
|
48471
|
+
const split = r < rows && at(r, c - 1) !== at(r, c);
|
|
48472
|
+
if (split && start < 0) start = r;
|
|
48473
|
+
if (!split && start >= 0) {
|
|
48474
|
+
seams.push(`M${xs[c]} ${ys[start]}V${ys[r]}`);
|
|
48475
|
+
start = -1;
|
|
48476
|
+
}
|
|
48477
|
+
}
|
|
48478
|
+
}
|
|
48479
|
+
for (let r = 1; r < rows; r += 1) {
|
|
48480
|
+
let start = -1;
|
|
48481
|
+
for (let c = 0; c <= cols; c += 1) {
|
|
48482
|
+
const split = c < cols && at(r - 1, c) !== at(r, c);
|
|
48483
|
+
if (split && start < 0) start = c;
|
|
48484
|
+
if (!split && start >= 0) {
|
|
48485
|
+
seams.push(`M${xs[start]} ${ys[r]}H${xs[c]}`);
|
|
48486
|
+
start = -1;
|
|
48487
|
+
}
|
|
48488
|
+
}
|
|
48489
|
+
}
|
|
48490
|
+
const frame = `<rect x="${GLYPH_MIN}" y="${GLYPH_MIN}" width="${GLYPH_SPAN}" height="${GLYPH_SPAN}" rx="1.5"/>`;
|
|
48491
|
+
return svg16(seams.length > 0 ? `${frame}<path d="${seams.join("")}"/>` : frame);
|
|
48492
|
+
}
|
|
47867
48493
|
function gridStyles(def, trackSizes) {
|
|
47868
48494
|
const cols = trackSizes?.cols?.length === def.cols.length ? trackSizes.cols : def.cols;
|
|
47869
48495
|
const rows = trackSizes?.rows?.length === def.rows.length ? trackSizes.rows : def.rows;
|
|
@@ -48395,7 +49021,8 @@ var VelaWorkspace = class {
|
|
|
48395
49021
|
onToggleSync: (id) => {
|
|
48396
49022
|
const kind = id;
|
|
48397
49023
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
48398
|
-
}
|
|
49024
|
+
},
|
|
49025
|
+
glyph: () => layoutGlyph(this.def)
|
|
48399
49026
|
},
|
|
48400
49027
|
getContext: () => this.context()
|
|
48401
49028
|
});
|
|
@@ -48894,6 +49521,7 @@ var VelaWorkspace = class {
|
|
|
48894
49521
|
this.applyGrid();
|
|
48895
49522
|
this.buildCells();
|
|
48896
49523
|
this.alignNewCellStyles(preexisting);
|
|
49524
|
+
this.alignNewCellDrawings(preexisting);
|
|
48897
49525
|
this.syncCellPresentation();
|
|
48898
49526
|
this.refreshCellControls();
|
|
48899
49527
|
this.topbar.setLayout(next.id);
|
|
@@ -49310,6 +49938,7 @@ var VelaWorkspace = class {
|
|
|
49310
49938
|
const chart = cell.chart;
|
|
49311
49939
|
chart.on("indicator:error", ({ error }) => this.toastHost.show(`[${cell.id}] ${error.message}`, "error", 5e3));
|
|
49312
49940
|
chart.on("script:run", (run) => this.events.emit("script:run", { ...run, cell: cell.id }));
|
|
49941
|
+
chart.on("priceStyle:change", ({ from, to }) => this.events.emit("cell:priceStyle", { id: cell.id, from, to }));
|
|
49313
49942
|
chart.on("alert", (alert) => {
|
|
49314
49943
|
const source = [parseSymbol(cell.symbol).ticker || cell.symbol, timeframeLabel(cell.timeframe), alert.indicator].filter(Boolean).join(" ");
|
|
49315
49944
|
this.alerts.unshift({ cellId: cell.id, source, title: alert.title ?? "Alert", message: alert.message, time: alert.time });
|
|
@@ -49427,12 +50056,66 @@ var VelaWorkspace = class {
|
|
|
49427
50056
|
}
|
|
49428
50057
|
}
|
|
49429
50058
|
/**
|
|
49430
|
-
*
|
|
49431
|
-
*
|
|
49432
|
-
*
|
|
49433
|
-
*
|
|
49434
|
-
* in
|
|
49435
|
-
*
|
|
50059
|
+
* Bring cells minted by a layout change into their drawings group: with the link
|
|
50060
|
+
* on, a NEW cell (fresh slot, or one returning from the pool that missed edits
|
|
50061
|
+
* while dormant) receives the drawings of a pre-existing group peer — the active
|
|
50062
|
+
* cell when it is one. A drawing the arriving cell already holds a linked copy of
|
|
50063
|
+
* is refreshed in place; any other is copied and linked like a freshly synced
|
|
50064
|
+
* drawing, so later edits and removals follow both ways. The arrival is the cell's
|
|
50065
|
+
* starting state, not an edit: it stays out of the cell's undo timeline, and the
|
|
50066
|
+
* busy guard keeps the copies' own events from fanning back out.
|
|
50067
|
+
*/
|
|
50068
|
+
alignNewCellDrawings(preexisting) {
|
|
50069
|
+
const setting = this.syncOpts.drawings;
|
|
50070
|
+
if (!setting) return;
|
|
50071
|
+
const ids = [...this.cellsById.keys()];
|
|
50072
|
+
for (const id of ids) {
|
|
50073
|
+
if (preexisting.has(id)) continue;
|
|
50074
|
+
const cell = this.cellsById.get(id);
|
|
50075
|
+
if (!cell?.chart.drawings.supported) continue;
|
|
50076
|
+
const peers = syncTargets(id, setting, ids).filter((p) => preexisting.has(p));
|
|
50077
|
+
if (peers.length === 0) continue;
|
|
50078
|
+
const sourceId = this.activeId && peers.includes(this.activeId) ? this.activeId : peers[0];
|
|
50079
|
+
const source = this.cellsById.get(sourceId);
|
|
50080
|
+
if (!source) continue;
|
|
50081
|
+
const drawings = cell.chart.drawings;
|
|
50082
|
+
this.drawingSyncBusy = true;
|
|
50083
|
+
try {
|
|
50084
|
+
cell.history.silently(() => {
|
|
50085
|
+
const held = new Set(drawings.all().map((d) => d.id));
|
|
50086
|
+
for (const doc of source.chart.drawings.all()) {
|
|
50087
|
+
const group = this.drawingLinks.get(`${sourceId}\0${doc.id}`) ?? /* @__PURE__ */ new Map([[sourceId, doc.id]]);
|
|
50088
|
+
const peerId = group.get(id);
|
|
50089
|
+
if (peerId != null && held.has(peerId)) {
|
|
50090
|
+
drawings.update(peerId, { anchors: doc.anchors, style: doc.style, text: doc.text, props: doc.props });
|
|
50091
|
+
continue;
|
|
50092
|
+
}
|
|
50093
|
+
const copy = drawings.add(doc.type, {
|
|
50094
|
+
paneId: doc.paneId,
|
|
50095
|
+
anchors: doc.anchors,
|
|
50096
|
+
style: doc.style,
|
|
50097
|
+
text: doc.text,
|
|
50098
|
+
props: doc.props,
|
|
50099
|
+
zIndex: doc.zIndex
|
|
50100
|
+
});
|
|
50101
|
+
if (!copy) continue;
|
|
50102
|
+
if (peerId != null) this.drawingLinks.delete(`${id}\0${peerId}`);
|
|
50103
|
+
group.set(id, copy.id);
|
|
50104
|
+
for (const [cellId, dId] of group) this.drawingLinks.set(`${cellId}\0${dId}`, group);
|
|
50105
|
+
}
|
|
50106
|
+
});
|
|
50107
|
+
} finally {
|
|
50108
|
+
this.drawingSyncBusy = false;
|
|
50109
|
+
}
|
|
50110
|
+
}
|
|
50111
|
+
}
|
|
50112
|
+
/**
|
|
50113
|
+
* Mirror an origin cell's presentation — the style-link slice of its renderer
|
|
50114
|
+
* config ({@link styleConfigPatch}) plus its Status line and watermark prefs —
|
|
50115
|
+
* onto its same-group followers (the style link). Loop-safe two ways: the busy
|
|
50116
|
+
* guard eats the followers' SYNCHRONOUS echoes (their `applyConfig` re-fires
|
|
50117
|
+
* `onConfigChanged` in the same tick), and the equality short-circuits leave
|
|
50118
|
+
* already-converged followers untouched, so nothing re-emits once the group agrees.
|
|
49436
50119
|
*/
|
|
49437
50120
|
propagateStylePrefs(originId) {
|
|
49438
50121
|
if (this.styleSyncBusy || this.destroyed) return;
|
|
@@ -49440,17 +50123,15 @@ var VelaWorkspace = class {
|
|
|
49440
50123
|
if (targets.length === 0) return;
|
|
49441
50124
|
const origin = this.cellsById.get(originId);
|
|
49442
50125
|
if (!origin) return;
|
|
49443
|
-
const
|
|
49444
|
-
const sliceJson = slice ? JSON.stringify(slice) : null;
|
|
50126
|
+
const config = origin.chart.renderer.getConfig();
|
|
49445
50127
|
const prefs = origin.statusPrefs();
|
|
49446
50128
|
this.styleSyncBusy = true;
|
|
49447
50129
|
try {
|
|
49448
50130
|
for (const id of targets) {
|
|
49449
50131
|
const cell = this.cellsById.get(id);
|
|
49450
50132
|
if (!cell) continue;
|
|
49451
|
-
|
|
49452
|
-
|
|
49453
|
-
}
|
|
50133
|
+
const patch = styleConfigPatch(config, cell.chart.renderer.getConfig());
|
|
50134
|
+
if (patch) cell.chart.renderer.applyConfig(patch);
|
|
49454
50135
|
cell.applyStatusPrefs(prefs);
|
|
49455
50136
|
}
|
|
49456
50137
|
} finally {
|
|
@@ -49771,7 +50452,8 @@ var VelaWorkspace = class {
|
|
|
49771
50452
|
onToggleSync: (id) => {
|
|
49772
50453
|
const kind = id;
|
|
49773
50454
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
49774
|
-
}
|
|
50455
|
+
},
|
|
50456
|
+
glyph: () => layoutGlyph(this.def)
|
|
49775
50457
|
},
|
|
49776
50458
|
onOpenChange: (open2) => this.trackDialog(open2)
|
|
49777
50459
|
}));
|
|
@@ -50035,6 +50717,7 @@ var VelaWidget = class {
|
|
|
50035
50717
|
};
|
|
50036
50718
|
|
|
50037
50719
|
exports.Bottombar = Bottombar;
|
|
50720
|
+
exports.CONTEXT_MENU_BUILTIN_ORDER = CONTEXT_MENU_BUILTIN_ORDER;
|
|
50038
50721
|
exports.ChartContextMenu = ChartContextMenu;
|
|
50039
50722
|
exports.DEFAULT_PANEL_MAX_WIDTH = DEFAULT_PANEL_MAX_WIDTH;
|
|
50040
50723
|
exports.DEFAULT_PANEL_MIN_WIDTH = DEFAULT_PANEL_MIN_WIDTH;
|