@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/workspace.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`;
|
|
@@ -6217,7 +6435,8 @@ var Topbar = class {
|
|
|
6217
6435
|
triggerId: "vela-topbar-style",
|
|
6218
6436
|
host,
|
|
6219
6437
|
items: this.styleItems(),
|
|
6220
|
-
onSelect: (id) => opts.onPriceStyle(id)
|
|
6438
|
+
onSelect: (id) => opts.onPriceStyle(id),
|
|
6439
|
+
iconBadges: true
|
|
6221
6440
|
});
|
|
6222
6441
|
}
|
|
6223
6442
|
setSymbol(symbol) {
|
|
@@ -6283,8 +6502,14 @@ var Topbar = class {
|
|
|
6283
6502
|
renderLayoutButton(doc) {
|
|
6284
6503
|
if (!this.layoutButton) return;
|
|
6285
6504
|
this.layoutButton.replaceChildren();
|
|
6286
|
-
|
|
6287
|
-
|
|
6505
|
+
const glyph = this.opts.layout?.glyph?.();
|
|
6506
|
+
if (glyph || iconMarkup("layout")) {
|
|
6507
|
+
const icon2 = iconEl("layout", doc);
|
|
6508
|
+
if (glyph) icon2.innerHTML = glyph;
|
|
6509
|
+
this.layoutButton.appendChild(icon2);
|
|
6510
|
+
} else {
|
|
6511
|
+
this.layoutButton.appendChild(doc.createTextNode(this.layoutId ?? ""));
|
|
6512
|
+
}
|
|
6288
6513
|
this.layoutButton.setAttribute("aria-label", `Layout \u2014 ${this.layoutId ?? ""}`);
|
|
6289
6514
|
}
|
|
6290
6515
|
renderStyleButton(doc) {
|
|
@@ -12959,6 +13184,11 @@ var SidePanel = class {
|
|
|
12959
13184
|
this.maximizedOn = false;
|
|
12960
13185
|
/** Set while the close is announced, so a listener's own close does not re-enter. */
|
|
12961
13186
|
this.closing = false;
|
|
13187
|
+
/** Open as far as the dock and the host are concerned — a closed panel can still be on
|
|
13188
|
+
* screen while its exit animation runs. */
|
|
13189
|
+
this.isOpen = false;
|
|
13190
|
+
/** The exit animation of a close; a reopen cancels it. */
|
|
13191
|
+
this.exit = null;
|
|
12962
13192
|
const doc = host.ownerDocument;
|
|
12963
13193
|
injectStyles(STYLE_ID4, CSS5, doc);
|
|
12964
13194
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
@@ -13007,20 +13237,44 @@ var SidePanel = class {
|
|
|
13007
13237
|
host.appendChild(this.el);
|
|
13008
13238
|
}
|
|
13009
13239
|
get open() {
|
|
13010
|
-
return
|
|
13240
|
+
return this.isOpen;
|
|
13011
13241
|
}
|
|
13012
|
-
/**
|
|
13013
|
-
|
|
13014
|
-
|
|
13242
|
+
/**
|
|
13243
|
+
* Open/close the panel — a bare call flips it. The panel reports closed at once, but a
|
|
13244
|
+
* close keeps it on screen, marked `data-closing`, while a host exit animation runs.
|
|
13245
|
+
* `instant` skips that, and also cuts short the exit of a panel already closing: a panel
|
|
13246
|
+
* handing the dock to another leaves at once.
|
|
13247
|
+
*/
|
|
13248
|
+
toggle(open2 = !this.isOpen, instant = false) {
|
|
13249
|
+
if (open2 === this.isOpen) {
|
|
13250
|
+
if (!open2 && instant) this.exit?.finish();
|
|
13251
|
+
return;
|
|
13252
|
+
}
|
|
13253
|
+
if (!open2 && this.closing) return;
|
|
13015
13254
|
if (!open2) {
|
|
13016
13255
|
this.closing = true;
|
|
13017
13256
|
announceSurface(this.el, false, "panel");
|
|
13018
13257
|
this.closing = false;
|
|
13019
13258
|
}
|
|
13020
|
-
this.
|
|
13021
|
-
if (
|
|
13259
|
+
this.isOpen = open2;
|
|
13260
|
+
if (open2) {
|
|
13261
|
+
if (this.exit) {
|
|
13262
|
+
this.exit.cancel();
|
|
13263
|
+
this.exit = null;
|
|
13264
|
+
this.setMaximized(false);
|
|
13265
|
+
}
|
|
13266
|
+
this.el.hidden = false;
|
|
13267
|
+
} else {
|
|
13268
|
+
const hide = () => {
|
|
13269
|
+
this.exit = null;
|
|
13270
|
+
this.el.hidden = true;
|
|
13271
|
+
this.setMaximized(false);
|
|
13272
|
+
};
|
|
13273
|
+
if (instant) hide();
|
|
13274
|
+
else this.exit = holdForExit(this.el, hide);
|
|
13275
|
+
}
|
|
13022
13276
|
this.onOpenChange?.(open2);
|
|
13023
|
-
if (open2 &&
|
|
13277
|
+
if (open2 && this.isOpen) announceSurface(this.el, true, "panel");
|
|
13024
13278
|
}
|
|
13025
13279
|
/** Whether the panel covers every chart right now. */
|
|
13026
13280
|
get maximized() {
|
|
@@ -13094,6 +13348,7 @@ var SidePanel = class {
|
|
|
13094
13348
|
}
|
|
13095
13349
|
destroy() {
|
|
13096
13350
|
if (this.open) announceSurface(this.el, false, "panel");
|
|
13351
|
+
this.exit?.finish();
|
|
13097
13352
|
this.el.remove();
|
|
13098
13353
|
}
|
|
13099
13354
|
/** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
|
|
@@ -13398,8 +13653,8 @@ var ObjectTree = class extends SidePanel {
|
|
|
13398
13653
|
set groups(next) {
|
|
13399
13654
|
if (this.chart) this.groupsPerChart.set(this.chart, next);
|
|
13400
13655
|
}
|
|
13401
|
-
toggle(open2 = this.
|
|
13402
|
-
super.toggle(open2);
|
|
13656
|
+
toggle(open2 = !this.open, instant = false) {
|
|
13657
|
+
super.toggle(open2, instant);
|
|
13403
13658
|
if (open2) this.refresh();
|
|
13404
13659
|
}
|
|
13405
13660
|
setSymbol(symbol) {
|
|
@@ -14433,8 +14688,8 @@ var DataWindow = class extends SidePanel {
|
|
|
14433
14688
|
this.unsubs = [];
|
|
14434
14689
|
injectStyles(STYLE_ID6, CSS7, host.ownerDocument);
|
|
14435
14690
|
}
|
|
14436
|
-
toggle(open2 = this.
|
|
14437
|
-
super.toggle(open2);
|
|
14691
|
+
toggle(open2 = !this.open, instant = false) {
|
|
14692
|
+
super.toggle(open2, instant);
|
|
14438
14693
|
if (open2) this.refresh();
|
|
14439
14694
|
}
|
|
14440
14695
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
@@ -14604,7 +14859,7 @@ var PanelDock = class {
|
|
|
14604
14859
|
}
|
|
14605
14860
|
this.pinned = new Set(state.pinned ?? []);
|
|
14606
14861
|
for (const entry of this.entries) entry.panel.setOverlay(!this.pinned.has(entry.id));
|
|
14607
|
-
for (const entry of this.entries) entry.panel.toggle(entry.id === state.open);
|
|
14862
|
+
for (const entry of this.entries) entry.panel.toggle(entry.id === state.open, true);
|
|
14608
14863
|
this.pendingOpen = state.open && !this.entries.some((e) => e.id === state.open) ? state.open : null;
|
|
14609
14864
|
}
|
|
14610
14865
|
/** Drop the contributed panels (the shell destroys its own). */
|
|
@@ -14620,7 +14875,7 @@ var PanelDock = class {
|
|
|
14620
14875
|
if (this.pinned.has(entry.id)) entry.panel.setOverlay(false);
|
|
14621
14876
|
entry.panel.onOpenChange = (open2) => {
|
|
14622
14877
|
if (open2) {
|
|
14623
|
-
for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
|
|
14878
|
+
for (const other of this.entries) if (other !== entry) other.panel.toggle(false, true);
|
|
14624
14879
|
this.pendingOpen = null;
|
|
14625
14880
|
}
|
|
14626
14881
|
this.deps.chrome.setPanelActive(entry.id, open2);
|
|
@@ -16296,7 +16551,11 @@ var MoreDrawer = class {
|
|
|
16296
16551
|
row(doc, label, opts) {
|
|
16297
16552
|
const el = doc.createElement("div");
|
|
16298
16553
|
el.className = "vela-md-row";
|
|
16299
|
-
if (opts.icon)
|
|
16554
|
+
if (opts.icon) {
|
|
16555
|
+
const icon2 = iconEl(opts.icon, doc);
|
|
16556
|
+
if (opts.glyph) icon2.innerHTML = opts.glyph;
|
|
16557
|
+
el.appendChild(icon2);
|
|
16558
|
+
}
|
|
16300
16559
|
const text = doc.createElement("span");
|
|
16301
16560
|
text.className = "vela-md-row-label";
|
|
16302
16561
|
text.textContent = label;
|
|
@@ -16338,7 +16597,7 @@ var MoreDrawer = class {
|
|
|
16338
16597
|
if (this.opts.layout) {
|
|
16339
16598
|
const shape = this.opts.layout.shape();
|
|
16340
16599
|
const value = shape ? `${shape.cols} \xD7 ${shape.rows}` : this.opts.layout.presets().find((p) => p.checked)?.label;
|
|
16341
|
-
list.appendChild(this.row(doc, "Layout", { icon: "layout", value, chevron: true, onClick: () => this.show("layout") }));
|
|
16600
|
+
list.appendChild(this.row(doc, "Layout", { icon: "layout", glyph: this.opts.layout.glyph?.(), value, chevron: true, onClick: () => this.show("layout") }));
|
|
16342
16601
|
}
|
|
16343
16602
|
for (const act of this.opts.primaryActions?.() ?? []) list.appendChild(this.actionRow(doc, act));
|
|
16344
16603
|
for (const panel of this.opts.panels()) {
|
|
@@ -16516,8 +16775,8 @@ var SETTINGS_SECTION = {
|
|
|
16516
16775
|
"price-axis": "Scales and lines",
|
|
16517
16776
|
"time-axis": "Scales and lines"
|
|
16518
16777
|
};
|
|
16519
|
-
function settingsItem(zone, label) {
|
|
16520
|
-
return { id: `settings:${SETTINGS_SECTION[zone]}`, label, separatorBefore: true };
|
|
16778
|
+
function settingsItem(zone, label, icon2) {
|
|
16779
|
+
return { id: `settings:${SETTINGS_SECTION[zone]}`, label, ...icon2 ? { icon: icon2 } : {}, separatorBefore: true };
|
|
16521
16780
|
}
|
|
16522
16781
|
function settingsSectionOf(id) {
|
|
16523
16782
|
return id.slice("settings:".length) || void 0;
|
|
@@ -16597,12 +16856,39 @@ function timeAxisItems(timezone) {
|
|
|
16597
16856
|
}
|
|
16598
16857
|
function bodyItems(counts) {
|
|
16599
16858
|
return [
|
|
16600
|
-
{ id: "reset-view", label: "Reset chart view" },
|
|
16601
|
-
{ id: "remove-drawings", label: "Remove drawings", disabled: counts.drawings === 0, separatorBefore: true },
|
|
16602
|
-
{ id: "remove-indicators", label: "Remove indicators", disabled: counts.indicators === 0 },
|
|
16603
|
-
settingsItem("body", "Settings\u2026")
|
|
16859
|
+
{ id: "reset-view", label: "Reset chart view", icon: "reset" },
|
|
16860
|
+
{ id: "remove-drawings", label: "Remove drawings", icon: "eraser", disabled: counts.drawings === 0, separatorBefore: true },
|
|
16861
|
+
{ id: "remove-indicators", label: "Remove indicators", icon: "indicators", disabled: counts.indicators === 0 },
|
|
16862
|
+
settingsItem("body", "Settings\u2026", "gear")
|
|
16604
16863
|
];
|
|
16605
16864
|
}
|
|
16865
|
+
var CONTEXT_MENU_BUILTIN_ORDER = {
|
|
16866
|
+
body: { "reset-view": -30, "remove-drawings": -20, "remove-indicators": -10, settings: 1e3 },
|
|
16867
|
+
"price-axis": {
|
|
16868
|
+
auto: -80,
|
|
16869
|
+
invert: -70,
|
|
16870
|
+
"scale:regular": -60,
|
|
16871
|
+
"scale:percent": -50,
|
|
16872
|
+
"scale:indexed": -40,
|
|
16873
|
+
"scale:log": -30,
|
|
16874
|
+
labels: -20,
|
|
16875
|
+
levels: -10,
|
|
16876
|
+
settings: 1e3
|
|
16877
|
+
},
|
|
16878
|
+
"time-axis": { timezone: -10, settings: 1e3 }
|
|
16879
|
+
};
|
|
16880
|
+
function composeMenu(zone, builtin, contributed) {
|
|
16881
|
+
const ranks = CONTEXT_MENU_BUILTIN_ORDER[zone];
|
|
16882
|
+
const rows = [];
|
|
16883
|
+
let group = 0;
|
|
16884
|
+
for (const item of builtin) {
|
|
16885
|
+
if (item.separatorBefore) group += 1;
|
|
16886
|
+
rows.push({ item, rank: ranks[item.id.startsWith("settings") ? "settings" : item.id] ?? 0, group });
|
|
16887
|
+
}
|
|
16888
|
+
for (const { item, order } of contributed) rows.push({ item, rank: order ?? 0, group: -1 });
|
|
16889
|
+
rows.sort((a, b) => a.rank - b.rank);
|
|
16890
|
+
return rows.map(({ item, group: g }, i) => ({ ...item, separatorBefore: i > 0 && rows[i - 1].group !== g }));
|
|
16891
|
+
}
|
|
16606
16892
|
|
|
16607
16893
|
// src/widget/price-scale-drawer.ts
|
|
16608
16894
|
var STYLE_ID17 = "vela-widget-price-scale-drawer";
|
|
@@ -17877,6 +18163,7 @@ function chromeHint(text, opts) {
|
|
|
17877
18163
|
// src/renderers/native/drawings/DrawingToolbar.ts
|
|
17878
18164
|
var TOOLBAR_WIDTH = 44;
|
|
17879
18165
|
var TOOLBAR_COLLAPSED_WIDTH = 16;
|
|
18166
|
+
var FLYOUT_GAP = 3;
|
|
17880
18167
|
var DrawingToolbar = class {
|
|
17881
18168
|
constructor(host, theme, onArm, onMagnet = () => {
|
|
17882
18169
|
}, onMeasure = () => {
|
|
@@ -17906,6 +18193,9 @@ var DrawingToolbar = class {
|
|
|
17906
18193
|
// group id (or MAGNET_ID) whose flyout is open
|
|
17907
18194
|
this.flyoutCell = null;
|
|
17908
18195
|
// the cell the open flyout is anchored to
|
|
18196
|
+
/** Closed flyouts still playing their exit animation, by owner id — reopening the same
|
|
18197
|
+
* owner takes its element back instead of stacking a second one on it. */
|
|
18198
|
+
this.exitingFlyouts = /* @__PURE__ */ new Map();
|
|
17909
18199
|
this.groupCells = /* @__PURE__ */ new Map();
|
|
17910
18200
|
// the composite cell (hover/active bg + flyout anchor)
|
|
17911
18201
|
this.groupIcons = /* @__PURE__ */ new Map();
|
|
@@ -18014,6 +18304,7 @@ var DrawingToolbar = class {
|
|
|
18014
18304
|
}
|
|
18015
18305
|
destroy() {
|
|
18016
18306
|
this.closeFlyout();
|
|
18307
|
+
for (const { exit } of [...this.exitingFlyouts.values()]) exit.finish();
|
|
18017
18308
|
for (const dispose of this.tipDisposers.splice(0)) dispose();
|
|
18018
18309
|
this.root.remove();
|
|
18019
18310
|
}
|
|
@@ -18241,16 +18532,22 @@ var DrawingToolbar = class {
|
|
|
18241
18532
|
* outside-dismiss. Callers fill it with items. */
|
|
18242
18533
|
beginFlyout(ownerId, cell) {
|
|
18243
18534
|
const t = this.theme;
|
|
18244
|
-
const
|
|
18535
|
+
const back = this.exitingFlyouts.get(ownerId);
|
|
18536
|
+
if (back) {
|
|
18537
|
+
this.exitingFlyouts.delete(ownerId);
|
|
18538
|
+
back.exit.cancel();
|
|
18539
|
+
back.fly.replaceChildren();
|
|
18540
|
+
}
|
|
18541
|
+
const fly = back?.fly ?? document.createElement("div");
|
|
18245
18542
|
fly.className = "vela-dtb-flyout";
|
|
18246
|
-
fly.style.cssText = `position:absolute;z-index:23;display:flex;flex-direction:column;gap:2px;padding:4px;border-radius:
|
|
18543
|
+
fly.style.cssText = `position:absolute;z-index:23;display:flex;flex-direction:column;gap:2px;padding:4px;border-radius:var(--vela-radius-md);background:var(--vela-surface);border:1px solid ${this.borderOverride ?? "var(--vela-border-strong)"};box-shadow:var(--vela-shadow);pointer-events:auto;overflow-y:auto;overscroll-behavior:contain;`;
|
|
18247
18544
|
applyChromeTokens(fly, t);
|
|
18248
18545
|
this.host.appendChild(fly);
|
|
18249
18546
|
const r = cell.getBoundingClientRect();
|
|
18250
18547
|
const rootR = this.root.getBoundingClientRect();
|
|
18251
18548
|
const hostR = this.host.getBoundingClientRect();
|
|
18252
18549
|
const top = r.top - hostR.top;
|
|
18253
|
-
fly.style.left = `${rootR.right - hostR.left
|
|
18550
|
+
fly.style.left = `${rootR.right - hostR.left + FLYOUT_GAP}px`;
|
|
18254
18551
|
fly.style.top = `${top}px`;
|
|
18255
18552
|
fly.style.maxHeight = `${Math.max(120, hostR.height - top - 8)}px`;
|
|
18256
18553
|
this.flyout = fly;
|
|
@@ -18266,6 +18563,7 @@ var DrawingToolbar = class {
|
|
|
18266
18563
|
if (!cell) return;
|
|
18267
18564
|
const fly = this.beginFlyout(group.id, cell);
|
|
18268
18565
|
const sections = group.sections ?? [{ label: "", tools: group.tools }];
|
|
18566
|
+
const badge = sections.some((s) => s.tools.some((tool) => !!tool.icon));
|
|
18269
18567
|
for (let si = 0; si < sections.length; si++) {
|
|
18270
18568
|
const section = sections[si];
|
|
18271
18569
|
if (section.label) {
|
|
@@ -18276,6 +18574,7 @@ var DrawingToolbar = class {
|
|
|
18276
18574
|
fly.appendChild(
|
|
18277
18575
|
this.makeFlyoutItem({
|
|
18278
18576
|
icon: tool.icon,
|
|
18577
|
+
badge,
|
|
18279
18578
|
label: tool.label,
|
|
18280
18579
|
selected: tool.type === this.active,
|
|
18281
18580
|
shortcut: this.shortcuts.get(tool.type),
|
|
@@ -18326,21 +18625,24 @@ var DrawingToolbar = class {
|
|
|
18326
18625
|
div.style.cssText = `height:1px;margin:4px 8px;background:${this.borderColor};`;
|
|
18327
18626
|
return div;
|
|
18328
18627
|
}
|
|
18329
|
-
/** A flyout row, left to right: optional leading icon, label, a check when it's the
|
|
18330
|
-
* entry, an optional shortcut hint, and — at the far right — the favorite star (tool
|
|
18331
|
-
* Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
|
|
18628
|
+
/** A flyout row, left to right: optional leading icon badge, label, a check when it's the
|
|
18629
|
+
* selected entry, an optional shortcut hint, and — at the far right — the favorite star (tool
|
|
18630
|
+
* rows). Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
|
|
18332
18631
|
makeFlyoutItem(opts) {
|
|
18333
18632
|
const t = this.theme;
|
|
18334
18633
|
const item = document.createElement("button");
|
|
18335
18634
|
item.type = "button";
|
|
18336
18635
|
item.className = "vela-dtb-item";
|
|
18337
18636
|
item.setAttribute("aria-label", opts.label);
|
|
18338
|
-
|
|
18339
|
-
|
|
18340
|
-
|
|
18341
|
-
|
|
18342
|
-
|
|
18343
|
-
|
|
18637
|
+
const badged = opts.badge === true || !!opts.icon;
|
|
18638
|
+
const box = badged ? "gap:10px;padding:4px 10px 4px 6px;" : "gap:8px;padding:5px 10px 5px 8px;";
|
|
18639
|
+
item.style.cssText = `display:flex;align-items:center;${box}cursor:pointer;color:${t.textColor};border-radius:var(--vela-radius-sm);font:13px ${t.fontFamily};white-space:nowrap;min-width:148px;`;
|
|
18640
|
+
if (opts.selected) item.dataset.selected = "1";
|
|
18641
|
+
if (badged) {
|
|
18642
|
+
const badge = document.createElement("span");
|
|
18643
|
+
badge.className = "vela-dtb-badge";
|
|
18644
|
+
if (opts.icon) badge.innerHTML = opts.icon;
|
|
18645
|
+
item.appendChild(badge);
|
|
18344
18646
|
}
|
|
18345
18647
|
const label = document.createElement("span");
|
|
18346
18648
|
label.textContent = opts.label;
|
|
@@ -18385,7 +18687,12 @@ var DrawingToolbar = class {
|
|
|
18385
18687
|
if (fly) {
|
|
18386
18688
|
this.flyout = null;
|
|
18387
18689
|
announceSurface(fly, false, "menu", this.flyoutCell);
|
|
18388
|
-
|
|
18690
|
+
const owner = this.flyoutOwnerId ?? "";
|
|
18691
|
+
const exit = holdForExit(fly, () => {
|
|
18692
|
+
if (this.exitingFlyouts.get(owner)?.fly === fly) this.exitingFlyouts.delete(owner);
|
|
18693
|
+
fly.remove();
|
|
18694
|
+
});
|
|
18695
|
+
if (exit) this.exitingFlyouts.set(owner, { fly, exit });
|
|
18389
18696
|
this.flyoutCell?.classList.remove("vela-open");
|
|
18390
18697
|
this.flyoutCell = null;
|
|
18391
18698
|
this.flyoutOwnerId = null;
|
|
@@ -18469,6 +18776,11 @@ function ensureStyles() {
|
|
|
18469
18776
|
.vela-dtb[data-collapsed='1'] .vela-dtb-collapse .vela-dtb-hit{width:14px;}
|
|
18470
18777
|
.vela-dtb-item{background:transparent;border:none;transition:background var(--vela-dur-fast) ease;}
|
|
18471
18778
|
.vela-dtb-item:hover{background:var(--vela-hover-strong);}
|
|
18779
|
+
.vela-dtb-badge{width:24px;height:24px;padding:3px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;flex:none;border-radius:var(--vela-radius-md);background:var(--vela-hover);border:1px solid var(--vela-border);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
18780
|
+
.vela-dtb-badge:empty{background:transparent;border-color:transparent;}
|
|
18781
|
+
.vela-dtb-badge svg{width:100%;height:100%;}
|
|
18782
|
+
.vela-dtb-item:hover .vela-dtb-badge:not(:empty){background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
18783
|
+
.vela-dtb-item[data-selected='1'] .vela-dtb-badge:not(:empty){background:color-mix(in srgb,var(--vela-fg-bright) 83%,var(--vela-surface));border-color:color-mix(in srgb,var(--vela-fg-bright) 40%,transparent);color:var(--vela-surface);}
|
|
18472
18784
|
.vela-dtb-star{width:26px;height:22px;margin:-3px -5px -3px 0;padding:3px 5px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;flex:none;opacity:0;color:inherit;border-radius:var(--vela-radius-sm);transition:opacity .1s ease,color .1s ease,background .1s ease;}
|
|
18473
18785
|
.vela-dtb-star svg{width:16px;height:16px;}
|
|
18474
18786
|
.vela-dtb-item:hover .vela-dtb-star{opacity:.55;}
|
|
@@ -18836,15 +19148,48 @@ function syncTargets(originId, setting, cellIds) {
|
|
|
18836
19148
|
function rangesWithin(a, b, epsMs) {
|
|
18837
19149
|
return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
|
|
18838
19150
|
}
|
|
18839
|
-
var STYLE_SYNC_CONFIG_KEYS = [
|
|
18840
|
-
|
|
18841
|
-
|
|
18842
|
-
|
|
18843
|
-
|
|
19151
|
+
var STYLE_SYNC_CONFIG_KEYS = [
|
|
19152
|
+
"layout",
|
|
19153
|
+
"panes",
|
|
19154
|
+
"grid",
|
|
19155
|
+
"margins",
|
|
19156
|
+
"priceScale",
|
|
19157
|
+
"crosshair",
|
|
19158
|
+
"animations",
|
|
19159
|
+
"candles",
|
|
19160
|
+
"bars",
|
|
19161
|
+
"line",
|
|
19162
|
+
"area",
|
|
19163
|
+
"baseline",
|
|
19164
|
+
"sessions"
|
|
19165
|
+
];
|
|
19166
|
+
function styleConfigPatch(origin, follower) {
|
|
19167
|
+
const from = asRecord(origin);
|
|
19168
|
+
const to = asRecord(follower);
|
|
19169
|
+
if (!from || !to) return null;
|
|
19170
|
+
const patch = {};
|
|
18844
19171
|
for (const key of STYLE_SYNC_CONFIG_KEYS) {
|
|
18845
|
-
|
|
19172
|
+
const block = asRecord(from[key]);
|
|
19173
|
+
if (block && JSON.stringify(block) !== JSON.stringify(to[key])) patch[key] = block;
|
|
18846
19174
|
}
|
|
18847
|
-
|
|
19175
|
+
const spacing = asRecord(from.series)?.spacing;
|
|
19176
|
+
if (typeof spacing === "number" && spacing !== asRecord(to.series)?.spacing) patch.series = { spacing };
|
|
19177
|
+
const fromTypes = asRecord(from.chartTypes) ?? {};
|
|
19178
|
+
const toTypes = asRecord(to.chartTypes) ?? {};
|
|
19179
|
+
const types = {};
|
|
19180
|
+
for (const typeId of /* @__PURE__ */ new Set([...Object.keys(fromTypes), ...Object.keys(toTypes)])) {
|
|
19181
|
+
const src = asRecord(fromTypes[typeId]) ?? {};
|
|
19182
|
+
const dst = asRecord(toTypes[typeId]) ?? {};
|
|
19183
|
+
for (const key of CANDLE_OVERRIDE_KEYS) {
|
|
19184
|
+
const value = src[key] ?? null;
|
|
19185
|
+
if (value !== (dst[key] ?? null)) (types[typeId] ?? (types[typeId] = {}))[key] = value;
|
|
19186
|
+
}
|
|
19187
|
+
}
|
|
19188
|
+
if (Object.keys(types).length > 0) patch.chartTypes = types;
|
|
19189
|
+
return Object.keys(patch).length > 0 ? patch : null;
|
|
19190
|
+
}
|
|
19191
|
+
function asRecord(v) {
|
|
19192
|
+
return v != null && typeof v === "object" ? v : null;
|
|
18848
19193
|
}
|
|
18849
19194
|
|
|
18850
19195
|
// src/workspace/persist.ts
|
|
@@ -19533,6 +19878,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19533
19878
|
this.activeEngineStyle = null;
|
|
19534
19879
|
/** The chart's current price style (tracked from the renderer's change events + initial read). */
|
|
19535
19880
|
this.priceStyle = "candles";
|
|
19881
|
+
this.priceStyleWillChangeUnsub = null;
|
|
19536
19882
|
this.unresolvedUnsub = null;
|
|
19537
19883
|
/** Latest chart visible range (left/right bar times), fed to viewport-dependent scripts. */
|
|
19538
19884
|
this.visibleRange = null;
|
|
@@ -19631,6 +19977,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19631
19977
|
this.renderer.onThemeSelect?.((name) => this.setTheme(resolveTheme(name)));
|
|
19632
19978
|
this.viewportUnsub = this.renderer.onViewportChange((range) => this.onViewportChange(range));
|
|
19633
19979
|
this.renderer.onPriceStyleChange?.((style) => this.syncPriceStyle(style));
|
|
19980
|
+
this.priceStyleWillChangeUnsub = this.renderer.onPriceStyleWillChange?.((from, to) => this.events.emit("priceStyle:change", { from, to })) ?? null;
|
|
19634
19981
|
const initialStyle = this.renderer.readFeature("priceStyle");
|
|
19635
19982
|
if (typeof initialStyle === "string") this.priceStyle = initialStyle;
|
|
19636
19983
|
this.barTransform = barTransformFor(initialStyle);
|
|
@@ -21161,6 +21508,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21161
21508
|
if (this.viewportTimer != null) clearTimeout(this.viewportTimer);
|
|
21162
21509
|
this.viewportUnsub?.();
|
|
21163
21510
|
this.paneActionUnsub?.();
|
|
21511
|
+
this.priceStyleWillChangeUnsub?.();
|
|
21512
|
+
this.priceStyleWillChangeUnsub = null;
|
|
21164
21513
|
for (const record of this.registry.all()) {
|
|
21165
21514
|
this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
|
|
21166
21515
|
record.session?.stop();
|
|
@@ -23366,7 +23715,10 @@ var LEGEND_ICON_PX2 = 16;
|
|
|
23366
23715
|
var LEGEND_CTL_PX = 18;
|
|
23367
23716
|
var LEGEND_ROW_PAD_Y = 2;
|
|
23368
23717
|
var LEGEND_ROW_PAD_X = 6;
|
|
23718
|
+
var LEGEND_LEFT_PX = 10;
|
|
23719
|
+
var LEGEND_SCALE_GAP_PX = 6;
|
|
23369
23720
|
var LEGEND_TITLE_VALUES_GAP_PX = 6;
|
|
23721
|
+
var LEGEND_VALUES_GAP_PX = 5;
|
|
23370
23722
|
var LEGEND_TITLE_STATUS_GAP_PX = 8;
|
|
23371
23723
|
var LEGEND_ACTION_GAP_PX = 6;
|
|
23372
23724
|
var LEGEND_CTL_CSS = `cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border:none;line-height:0;padding:0;width:${LEGEND_CTL_PX}px;height:${LEGEND_CTL_PX}px;border-radius:3px;box-sizing:border-box;flex:none;`;
|
|
@@ -23652,14 +24004,15 @@ var InputsUI = class {
|
|
|
23652
24004
|
row.valuesEl.replaceChildren();
|
|
23653
24005
|
return;
|
|
23654
24006
|
}
|
|
23655
|
-
row.valuesEl.style.display = "
|
|
24007
|
+
row.valuesEl.style.display = "block";
|
|
23656
24008
|
row.valuesEl.replaceChildren();
|
|
23657
|
-
|
|
24009
|
+
row.plotValues.forEach((v, i) => {
|
|
23658
24010
|
const span = document.createElement("span");
|
|
23659
24011
|
span.textContent = v.value;
|
|
23660
24012
|
span.style.color = v.color;
|
|
24013
|
+
if (i > 0) span.style.marginLeft = `${LEGEND_VALUES_GAP_PX}px`;
|
|
23661
24014
|
row.valuesEl.appendChild(span);
|
|
23662
|
-
}
|
|
24015
|
+
});
|
|
23663
24016
|
}
|
|
23664
24017
|
// ── legend move/merge (menu + drag) ─────────────────────────────────────
|
|
23665
24018
|
/** Open the "Move to" menu for a row, anchored under its move button. */
|
|
@@ -23760,7 +24113,7 @@ var InputsUI = class {
|
|
|
23760
24113
|
if (!lg) {
|
|
23761
24114
|
lg = document.createElement("div");
|
|
23762
24115
|
lg.dataset.velaPane = paneId;
|
|
23763
|
-
lg.style.cssText =
|
|
24116
|
+
lg.style.cssText = `position:absolute;left:${LEGEND_LEFT_PX}px;z-index:5;display:flex;flex-direction:column;align-items:flex-start;gap:0;pointer-events:none;font:12px -apple-system,Segoe UI,sans-serif;`;
|
|
23764
24117
|
applyChromeTokens(lg, this.theme);
|
|
23765
24118
|
this.positionLegend(lg, paneId);
|
|
23766
24119
|
this.container.appendChild(lg);
|
|
@@ -23772,6 +24125,7 @@ var InputsUI = class {
|
|
|
23772
24125
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
23773
24126
|
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
23774
24127
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
24128
|
+
lg.style.maxWidth = `calc(100% - ${(bounds.rightAxis ?? 0) + LEGEND_LEFT_PX + LEGEND_SCALE_GAP_PX}px)`;
|
|
23775
24129
|
const collapsed = this.paneCollapse.has(paneId);
|
|
23776
24130
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
23777
24131
|
for (const row of this.rows.values()) {
|
|
@@ -23863,7 +24217,7 @@ var InputsUI = class {
|
|
|
23863
24217
|
}
|
|
23864
24218
|
ensureDialogStyles();
|
|
23865
24219
|
const el = document.createElement("div");
|
|
23866
|
-
el.style.cssText = `pointer-events:auto;display:flex;align-items:center;background:${this.idleRowFill()};border-radius:4px;padding:${LEGEND_ROW_PAD_Y}px ${LEGEND_ROW_PAD_X}px;margin-left:-${LEGEND_ROW_PAD_X}px;min-height:${LEGEND_ROW_MIN_H}px;box-sizing:border-box;color:${this.theme.textColor};user-select:none;-webkit-user-select:none;`;
|
|
24220
|
+
el.style.cssText = `pointer-events:auto;display:flex;align-items:center;background:${this.idleRowFill()};border-radius:4px;padding:${LEGEND_ROW_PAD_Y}px ${LEGEND_ROW_PAD_X}px;margin-left:-${LEGEND_ROW_PAD_X}px;max-width:calc(100% + ${LEGEND_ROW_PAD_X}px);min-height:${LEGEND_ROW_MIN_H}px;box-sizing:border-box;color:${this.theme.textColor};user-select:none;-webkit-user-select:none;`;
|
|
23867
24221
|
el.addEventListener("mouseleave", () => {
|
|
23868
24222
|
if (this.selectedId !== id) this.setRowHighlighted(id, false);
|
|
23869
24223
|
});
|
|
@@ -23891,7 +24245,7 @@ var InputsUI = class {
|
|
|
23891
24245
|
const statusEl = document.createElement("span");
|
|
23892
24246
|
statusEl.style.cssText = "display:none;box-sizing:border-box;flex:none;";
|
|
23893
24247
|
const titleWrap = document.createElement("span");
|
|
23894
|
-
titleWrap.style.cssText = "white-space:nowrap;";
|
|
24248
|
+
titleWrap.style.cssText = "white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis;";
|
|
23895
24249
|
titleWrap.addEventListener("mouseenter", () => this.setRowHighlighted(id, true));
|
|
23896
24250
|
const titleEl = document.createElement("span");
|
|
23897
24251
|
titleEl.textContent = title;
|
|
@@ -23909,7 +24263,7 @@ var InputsUI = class {
|
|
|
23909
24263
|
el.appendChild(calloutsEl);
|
|
23910
24264
|
el.appendChild(statusEl);
|
|
23911
24265
|
const valuesEl = document.createElement("span");
|
|
23912
|
-
valuesEl.style.cssText = `display:none;
|
|
24266
|
+
valuesEl.style.cssText = `display:none;flex:1 1 0%;min-width:0;overflow:hidden;text-overflow:ellipsis;margin-left:${LEGEND_TITLE_VALUES_GAP_PX}px;white-space:nowrap;font-variant-numeric:tabular-nums;`;
|
|
23913
24267
|
el.appendChild(valuesEl);
|
|
23914
24268
|
const controlsEl = document.createElement("span");
|
|
23915
24269
|
controlsEl.style.cssText = `display:none;align-items:center;gap:${LEGEND_ACTION_GAP_PX}px;flex:none;margin-left:${LEGEND_ACTION_GAP_PX}px;`;
|
|
@@ -26153,6 +26507,11 @@ var InputController = class {
|
|
|
26153
26507
|
/** When true (the default), the separators between stacked panes are draggable to
|
|
26154
26508
|
* resize the panes above/below. When false, a press there is a normal data-area pan. */
|
|
26155
26509
|
this.paneResize = true;
|
|
26510
|
+
/** When true (the default), the user drawings take part in pointer input: they claim a
|
|
26511
|
+
* press over a drawing or with a tool armed, and see hover moves. When false, every press
|
|
26512
|
+
* is a plain pan/click and hover leaves them alone; a drawing gesture already under way
|
|
26513
|
+
* still finishes. */
|
|
26514
|
+
this.drawings = true;
|
|
26156
26515
|
this.el = null;
|
|
26157
26516
|
this.dragging = false;
|
|
26158
26517
|
this.moved = false;
|
|
@@ -26216,18 +26575,18 @@ var InputController = class {
|
|
|
26216
26575
|
this.onModifier = (e) => {
|
|
26217
26576
|
if (e.repeat || e.key !== "Shift" && e.key !== "Control" && e.key !== "Meta") return;
|
|
26218
26577
|
if (Number.isNaN(this.cursorX)) return;
|
|
26219
|
-
if (this.dragging
|
|
26578
|
+
if (this.dragging ? this.region !== "drawing" : !this.drawings) return;
|
|
26220
26579
|
this.deps.drawingsPointerMove?.(this.cursorX, this.cursorY, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26221
26580
|
};
|
|
26222
26581
|
this.onDown = (e) => {
|
|
26223
26582
|
if (e.button === 1) {
|
|
26224
26583
|
const { x: x2, y: y2 } = this.local(e);
|
|
26225
|
-
this.middleDeleted = this.deps.drawingsDeleteAt?.(x2, y2) ?? false;
|
|
26584
|
+
this.middleDeleted = this.drawings && (this.deps.drawingsDeleteAt?.(x2, y2) ?? false);
|
|
26226
26585
|
if (this.middleDeleted) e.preventDefault();
|
|
26227
26586
|
return;
|
|
26228
26587
|
}
|
|
26229
26588
|
if (e.button === 2) {
|
|
26230
|
-
this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
|
|
26589
|
+
this.rightCancelled = this.drawings && (this.deps.drawingsCancelPlacement?.() ?? false);
|
|
26231
26590
|
if (this.rightCancelled) e.preventDefault();
|
|
26232
26591
|
return;
|
|
26233
26592
|
}
|
|
@@ -26249,20 +26608,20 @@ var InputController = class {
|
|
|
26249
26608
|
this.startY = y;
|
|
26250
26609
|
this.prevPressDrawing = this.lastPressDrawing;
|
|
26251
26610
|
this.lastPressDrawing = false;
|
|
26252
|
-
if (this.deps.drawingsClaim?.(x, y)) {
|
|
26611
|
+
if (this.drawings && this.deps.drawingsClaim?.(x, y)) {
|
|
26253
26612
|
this.region = "drawing";
|
|
26254
26613
|
this.lastPressDrawing = true;
|
|
26255
26614
|
this.deps.drawingsPointerDown?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26256
26615
|
this.capture(e.pointerId);
|
|
26257
26616
|
return;
|
|
26258
26617
|
}
|
|
26259
|
-
if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
26618
|
+
if (this.drawings && e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
26260
26619
|
this.region = "drawing";
|
|
26261
26620
|
this.lastPressDrawing = true;
|
|
26262
26621
|
this.capture(e.pointerId);
|
|
26263
26622
|
return;
|
|
26264
26623
|
}
|
|
26265
|
-
if ((e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
|
|
26624
|
+
if (this.drawings && (e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
|
|
26266
26625
|
this.region = "drawing";
|
|
26267
26626
|
this.lastPressDrawing = true;
|
|
26268
26627
|
this.capture(e.pointerId);
|
|
@@ -26339,7 +26698,7 @@ var InputController = class {
|
|
|
26339
26698
|
const drawCursor = this.deps.drawingsCursor?.(x, y);
|
|
26340
26699
|
const r = this.regionAt(x, y);
|
|
26341
26700
|
this.el.style.cursor = drawCursor ?? (r === "price" ? "ns-resize" : r === "time" ? "ew-resize" : r === "separator" ? "row-resize" : "");
|
|
26342
|
-
this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26701
|
+
if (this.drawings) this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26343
26702
|
}
|
|
26344
26703
|
if (e.pointerType !== "touch") this.deps.onPointerMove(x, y);
|
|
26345
26704
|
};
|
|
@@ -26581,7 +26940,7 @@ var InputController = class {
|
|
|
26581
26940
|
}
|
|
26582
26941
|
/** Shared double-click / double-tap routing, by the region under the point. */
|
|
26583
26942
|
doubleActivate(x, y) {
|
|
26584
|
-
if (this.deps.drawingsDblClick?.(x, y)) return;
|
|
26943
|
+
if (this.drawings && this.deps.drawingsDblClick?.(x, y)) return;
|
|
26585
26944
|
const region = this.regionAt(x, y);
|
|
26586
26945
|
if (region === "price") this.deps.resetPriceScale(x, y);
|
|
26587
26946
|
else if (region === "separator") this.deps.resetPaneSize(y);
|
|
@@ -28595,10 +28954,13 @@ function priceTicks(min, max, target = 6) {
|
|
|
28595
28954
|
const norm = raw / mag;
|
|
28596
28955
|
const step = (norm < 1.5 ? 1 : norm < 3 ? 2 : norm < 7 ? 5 : 10) * mag;
|
|
28597
28956
|
const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);
|
|
28598
|
-
const out = [];
|
|
28599
28957
|
const start = Math.ceil(min / step) * step;
|
|
28600
|
-
|
|
28601
|
-
|
|
28958
|
+
if (!(step > 0) || start + step === start) return [];
|
|
28959
|
+
const count = Math.floor((max - start) / step + 1e-6) + 1;
|
|
28960
|
+
if (!(count > 0) || count > 1e3) return [];
|
|
28961
|
+
const out = [];
|
|
28962
|
+
for (let i = 0; i < count; i += 1) {
|
|
28963
|
+
out.push(Number((start + i * step).toFixed(decimals)));
|
|
28602
28964
|
}
|
|
28603
28965
|
return out;
|
|
28604
28966
|
}
|
|
@@ -28796,6 +29158,100 @@ function calendarTicks(zFrom, zTo, months, offsetMs) {
|
|
|
28796
29158
|
return out;
|
|
28797
29159
|
}
|
|
28798
29160
|
|
|
29161
|
+
// src/renderers/native/chrome/priceAxisTicks.ts
|
|
29162
|
+
var PriceAxisTickSource = class {
|
|
29163
|
+
constructor() {
|
|
29164
|
+
this.fn = null;
|
|
29165
|
+
this.memo = /* @__PURE__ */ new WeakMap();
|
|
29166
|
+
this.warned = false;
|
|
29167
|
+
}
|
|
29168
|
+
/** The host function, or null for the built-in ticks. */
|
|
29169
|
+
get hook() {
|
|
29170
|
+
return this.fn;
|
|
29171
|
+
}
|
|
29172
|
+
/** Set (or clear) the host function. Drops every memoized result, so setting the same
|
|
29173
|
+
* function again recomputes — how a host refreshes ticks that depend on its own state. */
|
|
29174
|
+
setHook(fn) {
|
|
29175
|
+
this.fn = fn;
|
|
29176
|
+
this.memo = /* @__PURE__ */ new WeakMap();
|
|
29177
|
+
}
|
|
29178
|
+
/** The pane's master-scale ticks for this frame. An unscaled pane (`axisFormat: 'none'`)
|
|
29179
|
+
* gets none, and the host is never asked for it. */
|
|
29180
|
+
ticksFor(scene, pane, coords) {
|
|
29181
|
+
const format = pane.axisFormat;
|
|
29182
|
+
if (format === "none") return [];
|
|
29183
|
+
const pct = percentScaleFor(scene, pane);
|
|
29184
|
+
const height = pane.bounds.height;
|
|
29185
|
+
const fn = this.fn;
|
|
29186
|
+
if (!fn || !(height > 0)) return paneAxisTicks(pane.scale, height, pct, scene.priceMintick, format);
|
|
29187
|
+
const s = pane.scale;
|
|
29188
|
+
const m = this.memo.get(pane);
|
|
29189
|
+
const fontSize = scene.style.fontSize;
|
|
29190
|
+
if (m && m.kind === pane.kind && m.min === s.min && m.max === s.max && m.log === !!s.log && m.invert === !!s.invert && m.height === height && m.baseline === pct?.baseline && m.indexed === !!pct?.indexed && m.mintick === scene.priceMintick && m.format === format && m.fontSize === fontSize) {
|
|
29191
|
+
return m.ticks;
|
|
29192
|
+
}
|
|
29193
|
+
const defaults2 = paneAxisTicks(s, height, pct, scene.priceMintick, format);
|
|
29194
|
+
const ticks = this.callHook(fn, scene, pane, coords, defaults2, pct?.baseline, pct ? pct.indexed ? "indexed" : "percent" : "price");
|
|
29195
|
+
this.memo.set(pane, {
|
|
29196
|
+
kind: pane.kind,
|
|
29197
|
+
min: s.min,
|
|
29198
|
+
max: s.max,
|
|
29199
|
+
log: !!s.log,
|
|
29200
|
+
invert: !!s.invert,
|
|
29201
|
+
height,
|
|
29202
|
+
baseline: pct?.baseline,
|
|
29203
|
+
indexed: !!pct?.indexed,
|
|
29204
|
+
mintick: scene.priceMintick,
|
|
29205
|
+
format,
|
|
29206
|
+
fontSize,
|
|
29207
|
+
ticks
|
|
29208
|
+
});
|
|
29209
|
+
return ticks;
|
|
29210
|
+
}
|
|
29211
|
+
callHook(fn, scene, pane, coords, defaults2, baseline, mode) {
|
|
29212
|
+
const scale = { min: pane.scale.min, max: pane.scale.max, log: !!pane.scale.log, invert: !!pane.scale.invert };
|
|
29213
|
+
const bounds = { top: 0, height: pane.bounds.height };
|
|
29214
|
+
const ctx = {
|
|
29215
|
+
pane: { id: pane.id, kind: pane.kind },
|
|
29216
|
+
min: Math.min(scale.min, scale.max),
|
|
29217
|
+
max: Math.max(scale.min, scale.max),
|
|
29218
|
+
log: !!scale.log,
|
|
29219
|
+
height: bounds.height,
|
|
29220
|
+
mode,
|
|
29221
|
+
baseline,
|
|
29222
|
+
mintick: scene.priceMintick,
|
|
29223
|
+
fontSize: scene.style.fontSize,
|
|
29224
|
+
priceToY: (price) => coords.priceToY(price, scale, bounds),
|
|
29225
|
+
defaults: defaults2
|
|
29226
|
+
};
|
|
29227
|
+
let out;
|
|
29228
|
+
try {
|
|
29229
|
+
out = fn(ctx);
|
|
29230
|
+
} catch (err) {
|
|
29231
|
+
this.warnOnce("threw", err);
|
|
29232
|
+
return defaults2;
|
|
29233
|
+
}
|
|
29234
|
+
if (out == null) return defaults2;
|
|
29235
|
+
if (!Array.isArray(out)) {
|
|
29236
|
+
this.warnOnce("returned a non-array", out);
|
|
29237
|
+
return defaults2;
|
|
29238
|
+
}
|
|
29239
|
+
const ticks = [];
|
|
29240
|
+
for (const t of out) {
|
|
29241
|
+
if (!t || typeof t !== "object") continue;
|
|
29242
|
+
const { price, label, major } = t;
|
|
29243
|
+
if (typeof price !== "number" || !Number.isFinite(price) || typeof label !== "string") continue;
|
|
29244
|
+
ticks.push(typeof major === "boolean" ? { price, label, major } : { price, label });
|
|
29245
|
+
}
|
|
29246
|
+
return ticks;
|
|
29247
|
+
}
|
|
29248
|
+
warnOnce(what, detail) {
|
|
29249
|
+
if (this.warned) return;
|
|
29250
|
+
this.warned = true;
|
|
29251
|
+
console.warn(`[vela] the priceAxisTicks function ${what} \u2014 the default ticks are used instead.`, detail);
|
|
29252
|
+
}
|
|
29253
|
+
};
|
|
29254
|
+
|
|
28799
29255
|
// src/renderers/native/chrome/countdown.ts
|
|
28800
29256
|
function countdownText(barOpen, barMs, now2) {
|
|
28801
29257
|
if (!(barMs > 0)) return null;
|
|
@@ -29148,8 +29604,11 @@ function standaloneSvg(markup, ink, px) {
|
|
|
29148
29604
|
}
|
|
29149
29605
|
|
|
29150
29606
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
29607
|
+
var MINOR_LABEL_ALPHA = 0.5;
|
|
29151
29608
|
var ChromeRenderer = class {
|
|
29152
|
-
|
|
29609
|
+
/** `axisTicks` is shared with the backdrop layer, so price labels and gridlines agree. */
|
|
29610
|
+
constructor(axisTicks = new PriceAxisTickSource()) {
|
|
29611
|
+
this.axisTicks = axisTicks;
|
|
29153
29612
|
this.canvas = null;
|
|
29154
29613
|
this.ctx = null;
|
|
29155
29614
|
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
@@ -29254,7 +29713,8 @@ var ChromeRenderer = class {
|
|
|
29254
29713
|
this.drawTimeAxis(ctx, scene, coords, theme, dataW, dataH, fullH);
|
|
29255
29714
|
this.drawMarkLane(ctx, scene, coords, theme, dataW, dataH);
|
|
29256
29715
|
}
|
|
29257
|
-
/** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge
|
|
29716
|
+
/** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge,
|
|
29717
|
+
* but clipped to the data area so a glyph on the edge bar never paints over the price scale. */
|
|
29258
29718
|
drawMarkLane(ctx, scene, coords, theme, dataW, dataH) {
|
|
29259
29719
|
if (!scene.marks.visible || scene.timelineMarks.length === 0) {
|
|
29260
29720
|
this.markLayout = { glyphs: [], stacks: /* @__PURE__ */ new Map() };
|
|
@@ -29272,6 +29732,10 @@ var ChromeRenderer = class {
|
|
|
29272
29732
|
expanded: scene.marksExpandedStack
|
|
29273
29733
|
});
|
|
29274
29734
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
29735
|
+
ctx.save();
|
|
29736
|
+
ctx.beginPath();
|
|
29737
|
+
ctx.rect(0, 0, dataW, dataH);
|
|
29738
|
+
ctx.clip();
|
|
29275
29739
|
paintMarkLane(ctx, this.markLayout, {
|
|
29276
29740
|
axisY: dataH,
|
|
29277
29741
|
background: theme.background,
|
|
@@ -29285,6 +29749,7 @@ var ChromeRenderer = class {
|
|
|
29285
29749
|
flashKey: scene.marksFlash && scene.marksFlash.until > nowMs ? scene.marksFlash.key : null,
|
|
29286
29750
|
nowMs
|
|
29287
29751
|
});
|
|
29752
|
+
ctx.restore();
|
|
29288
29753
|
}
|
|
29289
29754
|
barTimes(scene) {
|
|
29290
29755
|
if (this.barTimesSrc !== scene.bars || this.barTimesCache.length !== scene.bars.length) {
|
|
@@ -29341,13 +29806,19 @@ var ChromeRenderer = class {
|
|
|
29341
29806
|
if (!scene.showAxisLabels) return;
|
|
29342
29807
|
ctx.fillStyle = this.axisTextColor;
|
|
29343
29808
|
ctx.textAlign = "left";
|
|
29809
|
+
const font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
29810
|
+
const majorFont = `600 ${font}`;
|
|
29811
|
+
const alpha = ctx.globalAlpha;
|
|
29344
29812
|
for (const pane of panes) {
|
|
29345
29813
|
if (pane.collapsed) continue;
|
|
29346
|
-
const
|
|
29347
|
-
for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, scene.priceMintick, pane.axisFormat)) {
|
|
29814
|
+
for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
|
|
29348
29815
|
const y = coords.priceToY(t.price, pane.scale, pane.bounds);
|
|
29349
29816
|
if (y < pane.bounds.top + 6 || y > pane.bounds.top + pane.bounds.height - 4) continue;
|
|
29817
|
+
if (t.major === true) ctx.font = majorFont;
|
|
29818
|
+
else if (t.major === false) ctx.globalAlpha = alpha * MINOR_LABEL_ALPHA;
|
|
29350
29819
|
ctx.fillText(t.label, dataW + 6, y);
|
|
29820
|
+
if (t.major === true) ctx.font = font;
|
|
29821
|
+
else if (t.major === false) ctx.globalAlpha = alpha;
|
|
29351
29822
|
}
|
|
29352
29823
|
if (pane.axisBands) {
|
|
29353
29824
|
for (const b of pane.axisBands) {
|
|
@@ -29589,15 +30060,23 @@ var CrosshairRenderer = class {
|
|
|
29589
30060
|
constructor() {
|
|
29590
30061
|
this.canvas = null;
|
|
29591
30062
|
this.ctx = null;
|
|
30063
|
+
this.shade = null;
|
|
29592
30064
|
}
|
|
29593
30065
|
mount(canvas) {
|
|
29594
30066
|
this.canvas = canvas;
|
|
29595
30067
|
this.ctx = canvas.getContext("2d");
|
|
29596
30068
|
}
|
|
30069
|
+
/** The plot area the last frame veiled for `crosshairOverride.shadeRight` — from the
|
|
30070
|
+
* picked bar's right edge to the price scale, full plot height (the union when both the
|
|
30071
|
+
* local and a synced crosshair veil) — or null when no veil was painted. */
|
|
30072
|
+
get shadeRightArea() {
|
|
30073
|
+
return this.shade;
|
|
30074
|
+
}
|
|
29597
30075
|
/** Clear the cursor canvas and (re)draw the crosshair lines + axis chips. The optional
|
|
29598
30076
|
* `separatorHoverY` highlights the draggable pane separator under the cursor;
|
|
29599
30077
|
* `external` is a SYNCED ghost crosshair (another chart's pointer, pixel-resolved). */
|
|
29600
30078
|
render(scene, coords, theme, separatorHoverY = null, external = null) {
|
|
30079
|
+
this.shade = null;
|
|
29601
30080
|
const ctx = this.ctx;
|
|
29602
30081
|
const canvas = this.canvas;
|
|
29603
30082
|
if (!ctx || !canvas) return;
|
|
@@ -29620,12 +30099,7 @@ var CrosshairRenderer = class {
|
|
|
29620
30099
|
const x = crisp(coords.logicalToX(logical), ov?.width ?? cs.width);
|
|
29621
30100
|
if (vertical && ov?.shadeRight) {
|
|
29622
30101
|
const from = Math.max(0, Math.round(coords.logicalToX(logical + 0.5)));
|
|
29623
|
-
if (from < dataW)
|
|
29624
|
-
ctx.fillStyle = ov.shadeRight.color;
|
|
29625
|
-
ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
|
|
29626
|
-
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
29627
|
-
ctx.globalAlpha = 1;
|
|
29628
|
-
}
|
|
30102
|
+
if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
|
|
29629
30103
|
}
|
|
29630
30104
|
ctx.strokeStyle = ov?.color ?? cs.color ?? theme.textColor;
|
|
29631
30105
|
ctx.lineWidth = ov?.width ?? cs.width;
|
|
@@ -29661,6 +30135,7 @@ var CrosshairRenderer = class {
|
|
|
29661
30135
|
destroy() {
|
|
29662
30136
|
this.canvas = null;
|
|
29663
30137
|
this.ctx = null;
|
|
30138
|
+
this.shade = null;
|
|
29664
30139
|
}
|
|
29665
30140
|
/** The synced ghost: a dimmed vertical line at the bar the renderer resolved as
|
|
29666
30141
|
* CONTAINING the foreign time (+ horizontal line when a comparable price came
|
|
@@ -29680,12 +30155,7 @@ var CrosshairRenderer = class {
|
|
|
29680
30155
|
if (ov?.vertical === false) return;
|
|
29681
30156
|
if (ov?.shadeRight) {
|
|
29682
30157
|
const from = Math.max(0, Math.round(coords.logicalToX(Math.round(coords.xToLogical(ext.x)) + 0.5)));
|
|
29683
|
-
if (from < dataW)
|
|
29684
|
-
ctx.fillStyle = ov.shadeRight.color;
|
|
29685
|
-
ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
|
|
29686
|
-
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
29687
|
-
ctx.globalAlpha = 1;
|
|
29688
|
-
}
|
|
30158
|
+
if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
|
|
29689
30159
|
}
|
|
29690
30160
|
if (ext.line === false || x < 0 || x > dataW) return;
|
|
29691
30161
|
ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
|
|
@@ -29721,6 +30191,15 @@ var CrosshairRenderer = class {
|
|
|
29721
30191
|
this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
29722
30192
|
ctx.globalAlpha = 1;
|
|
29723
30193
|
}
|
|
30194
|
+
/** Paint the `shadeRight` veil from `from` to the price scale and record the veiled area. */
|
|
30195
|
+
paintShade(ctx, shade, from, dataW, dataH) {
|
|
30196
|
+
ctx.fillStyle = shade.color;
|
|
30197
|
+
ctx.globalAlpha = shade.opacity ?? 1;
|
|
30198
|
+
ctx.fillRect(from, 0, dataW - from, dataH);
|
|
30199
|
+
ctx.globalAlpha = 1;
|
|
30200
|
+
const x = this.shade ? Math.min(this.shade.x, from) : from;
|
|
30201
|
+
this.shade = { x, width: dataW - x, height: dataH };
|
|
30202
|
+
}
|
|
29724
30203
|
/** A soft band + a brighter crisp center line over the hovered separator, so it reads as
|
|
29725
30204
|
* a draggable handle (the cursor is already `row-resize`). Spans the full width (data +
|
|
29726
30205
|
* scale gutter) to match the separator itself. Theme-derived (textColor). */
|
|
@@ -33126,15 +33605,16 @@ var DrawingSettingsDialog = class {
|
|
|
33126
33605
|
}
|
|
33127
33606
|
open(drawing, actions, kind) {
|
|
33128
33607
|
this.close();
|
|
33129
|
-
const
|
|
33608
|
+
const live = actions.resolve() ?? drawing;
|
|
33609
|
+
const snapshot = clonePlain(live.serialize());
|
|
33130
33610
|
const grid = fieldGrid({ variant: "inputs" });
|
|
33131
33611
|
grid.style.padding = "16px 20px";
|
|
33132
33612
|
grid.style.overflowY = "auto";
|
|
33133
33613
|
grid.style.overflowX = "hidden";
|
|
33134
33614
|
grid.style.flex = "1 1 auto";
|
|
33135
|
-
if (kind === "position" &&
|
|
33136
|
-
else if (kind === "frvp" &&
|
|
33137
|
-
else if (kind === "levels") this.buildLevels(grid,
|
|
33615
|
+
if (kind === "position" && live instanceof PositionTool) this.buildPosition(grid, live, actions);
|
|
33616
|
+
else if (kind === "frvp" && live instanceof FixedRangeVolumeProfile) this.buildFrvp(grid, live, actions);
|
|
33617
|
+
else if (kind === "levels") this.buildLevels(grid, live, actions);
|
|
33138
33618
|
else return;
|
|
33139
33619
|
const ui = new Dialog({
|
|
33140
33620
|
host: this.host,
|
|
@@ -33781,7 +34261,7 @@ var DrawingSettingsPopup = class {
|
|
|
33781
34261
|
this.reposition();
|
|
33782
34262
|
return;
|
|
33783
34263
|
}
|
|
33784
|
-
const text = drawing.text;
|
|
34264
|
+
const text = (actions.resolve() ?? drawing).text;
|
|
33785
34265
|
const panel = document.createElement("div");
|
|
33786
34266
|
panel.style.cssText = `padding:6px;border-top:1px solid var(--vela-border);display:flex;flex-direction:column;gap:4px;`;
|
|
33787
34267
|
const ta = new TextArea({
|
|
@@ -34952,6 +35432,12 @@ var UserDrawingController = class {
|
|
|
34952
35432
|
this.render();
|
|
34953
35433
|
}
|
|
34954
35434
|
}
|
|
35435
|
+
/** Drop the hover (its handles) — the pointer no longer reaches the drawings. */
|
|
35436
|
+
clearHover() {
|
|
35437
|
+
if (this.hoveredId == null) return;
|
|
35438
|
+
this.hoveredId = null;
|
|
35439
|
+
this.render();
|
|
35440
|
+
}
|
|
34955
35441
|
pointerUp(x, y, snap = "off") {
|
|
34956
35442
|
if (this.eraserMode) {
|
|
34957
35443
|
this.erasing = false;
|
|
@@ -35942,9 +36428,10 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
35942
36428
|
consider(drawings.max);
|
|
35943
36429
|
}
|
|
35944
36430
|
if (min === Infinity || max === -Infinity) return { min: 0, max: 1 };
|
|
35945
|
-
if (min
|
|
35946
|
-
const
|
|
35947
|
-
|
|
36431
|
+
if (max - min <= Math.max(Math.abs(min), Math.abs(max)) * 1e-12) {
|
|
36432
|
+
const mid = (min + max) / 2;
|
|
36433
|
+
const pad = Math.abs(mid) * 0.1 || 1;
|
|
36434
|
+
return { min: mid - pad, max: mid + pad, log: log && mid - pad > 0 };
|
|
35948
36435
|
}
|
|
35949
36436
|
const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
|
|
35950
36437
|
const above = margins.top / 100 / content;
|
|
@@ -36448,7 +36935,9 @@ function bandEdgeSlot(logical) {
|
|
|
36448
36935
|
return Math.ceil(logical) - 0.5;
|
|
36449
36936
|
}
|
|
36450
36937
|
var BackdropRenderer = class {
|
|
36451
|
-
|
|
36938
|
+
/** `axisTicks` is shared with the chrome layer, so gridlines and price labels agree. */
|
|
36939
|
+
constructor(axisTicks = new PriceAxisTickSource()) {
|
|
36940
|
+
this.axisTicks = axisTicks;
|
|
36452
36941
|
this.canvas = null;
|
|
36453
36942
|
this.ctx = null;
|
|
36454
36943
|
}
|
|
@@ -36525,9 +37014,8 @@ var BackdropRenderer = class {
|
|
|
36525
37014
|
if (scene.showGrid && gridHorz.visible && !pane.collapsed) {
|
|
36526
37015
|
ctx.globalAlpha = gridAlpha;
|
|
36527
37016
|
ctx.strokeStyle = horzColor;
|
|
36528
|
-
const pct = percentScaleFor(scene, pane);
|
|
36529
37017
|
ctx.beginPath();
|
|
36530
|
-
for (const t of
|
|
37018
|
+
for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
|
|
36531
37019
|
const y = Math.round(coords.priceToY(t.price, pane.scale, pane.bounds)) + 0.5;
|
|
36532
37020
|
if (y < pane.bounds.top || y > pane.bounds.top + pane.bounds.height) continue;
|
|
36533
37021
|
ctx.moveTo(0, y);
|
|
@@ -37014,8 +37502,16 @@ var NativeRenderer = class {
|
|
|
37014
37502
|
this.toolbarGutter = 0;
|
|
37015
37503
|
// px reserved on the left for the docked drawings toolbar (0 when hidden)
|
|
37016
37504
|
this.mountContainer = null;
|
|
37505
|
+
/** The host-styleable twin of the `shadeRight` veil (`.vela-shade-right`), directly under
|
|
37506
|
+
* the cursor canvas; hidden whenever the crosshair layer paints no veil. */
|
|
37507
|
+
this.shadeRightEl = null;
|
|
37508
|
+
/** The veil geometry last written to {@link shadeRightEl} ('' = hidden) — the DOM is
|
|
37509
|
+
* touched only when it changes, never on a plain pointer move. */
|
|
37510
|
+
this.shadeRightSig = "";
|
|
37017
37511
|
this.userDrawings = null;
|
|
37018
|
-
|
|
37512
|
+
/** One price-axis tick source for the gridlines (backdrop) and the labels (chrome). */
|
|
37513
|
+
this.priceAxisTicks = new PriceAxisTickSource();
|
|
37514
|
+
this.backdropRenderer = new BackdropRenderer(this.priceAxisTicks);
|
|
37019
37515
|
this.volumeRenderer = new VolumeRenderer();
|
|
37020
37516
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
37021
37517
|
/**
|
|
@@ -37048,7 +37544,7 @@ var NativeRenderer = class {
|
|
|
37048
37544
|
this.backendMode = "auto";
|
|
37049
37545
|
this.glowAmount = 0;
|
|
37050
37546
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
37051
|
-
this.chrome = new ChromeRenderer();
|
|
37547
|
+
this.chrome = new ChromeRenderer(this.priceAxisTicks);
|
|
37052
37548
|
/** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
|
|
37053
37549
|
this.indicatorSlices = new IndicatorDrawingSlices();
|
|
37054
37550
|
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
@@ -37165,6 +37661,8 @@ var NativeRenderer = class {
|
|
|
37165
37661
|
// pane just below it
|
|
37166
37662
|
this.resizeSplitStart = null;
|
|
37167
37663
|
// the two panes' shared span when the drag began
|
|
37664
|
+
/** The user drawings take part in pointer input (the runtime `drawingsInteractive` feature). */
|
|
37665
|
+
this.drawingsInteractive = true;
|
|
37168
37666
|
this.hoverSeparatorY = null;
|
|
37169
37667
|
// pixel y of the separator under the cursor (drives its hover highlight)
|
|
37170
37668
|
// ── pane management (merge / reorder / collapse / maximize) ──
|
|
@@ -37220,8 +37718,9 @@ var NativeRenderer = class {
|
|
|
37220
37718
|
this.toggleVisibleCbs = /* @__PURE__ */ new Set();
|
|
37221
37719
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
37222
37720
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
37721
|
+
this.priceStyleWillChangeCbs = /* @__PURE__ */ new Set();
|
|
37223
37722
|
this.name = "native";
|
|
37224
|
-
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animScroll", "animAutoscale", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "marks", "indicatorTitles", "indicatorValues", "crosshairOverride"];
|
|
37723
|
+
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animScroll", "animAutoscale", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "marks", "indicatorTitles", "indicatorValues", "crosshairOverride", "drawingsInteractive", "priceAxisTicks"];
|
|
37225
37724
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
37226
37725
|
* so moving onto the button doesn't count as leaving). */
|
|
37227
37726
|
this.onScrollProximityMove = (e) => {
|
|
@@ -37265,6 +37764,7 @@ var NativeRenderer = class {
|
|
|
37265
37764
|
this.scene.priceStyle = opts.priceStyle;
|
|
37266
37765
|
this.scene.basePainting = basePaintingOf(opts.priceStyle);
|
|
37267
37766
|
this.scene.candleOverride = candleOverrideFor(opts.priceStyle, this.scene.style.chartTypes);
|
|
37767
|
+
if (typeof opts.priceAxisTicks === "function") this.priceAxisTicks.setHook(opts.priceAxisTicks);
|
|
37268
37768
|
}
|
|
37269
37769
|
this.theme = this.deriveTheme(DARK_THEME);
|
|
37270
37770
|
}
|
|
@@ -37403,6 +37903,15 @@ var NativeRenderer = class {
|
|
|
37403
37903
|
case "crosshairOverride":
|
|
37404
37904
|
this.scene.crosshairOverride = sanitizeCrosshairOverride(value);
|
|
37405
37905
|
break;
|
|
37906
|
+
case "drawingsInteractive":
|
|
37907
|
+
this.drawingsInteractive = value !== false;
|
|
37908
|
+
if (this.input) this.input.drawings = this.drawingsInteractive;
|
|
37909
|
+
if (!this.drawingsInteractive) this.userDrawings?.clearHover();
|
|
37910
|
+
return;
|
|
37911
|
+
// affects the next gesture only — clearHover repaints what it drops
|
|
37912
|
+
case "priceAxisTicks":
|
|
37913
|
+
this.priceAxisTicks.setHook(typeof value === "function" ? value : null);
|
|
37914
|
+
break;
|
|
37406
37915
|
case "keyboard":
|
|
37407
37916
|
this.setKeyboardEnabled(Boolean(value));
|
|
37408
37917
|
return;
|
|
@@ -37518,6 +38027,10 @@ var NativeRenderer = class {
|
|
|
37518
38027
|
return { visible: this.scene.marks.visible, groups: { ...this.scene.marks.groups } };
|
|
37519
38028
|
case "crosshairOverride":
|
|
37520
38029
|
return this.scene.crosshairOverride ? { ...this.scene.crosshairOverride } : null;
|
|
38030
|
+
case "drawingsInteractive":
|
|
38031
|
+
return this.drawingsInteractive;
|
|
38032
|
+
case "priceAxisTicks":
|
|
38033
|
+
return this.priceAxisTicks.hook;
|
|
37521
38034
|
case "keyboard":
|
|
37522
38035
|
return this.keyboardEnabled;
|
|
37523
38036
|
case "historyChords":
|
|
@@ -38191,6 +38704,12 @@ var NativeRenderer = class {
|
|
|
38191
38704
|
Object.assign(this.drawingsCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
38192
38705
|
this.cursorCanvas = document.createElement("canvas");
|
|
38193
38706
|
Object.assign(this.cursorCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
38707
|
+
this.shadeRightEl = document.createElement("div");
|
|
38708
|
+
this.shadeRightEl.className = "vela-shade-right";
|
|
38709
|
+
this.shadeRightEl.setAttribute("aria-hidden", "true");
|
|
38710
|
+
this.shadeRightEl.hidden = true;
|
|
38711
|
+
Object.assign(this.shadeRightEl.style, { position: "absolute", top: "0", left: "0", width: "0", height: "0", pointerEvents: "none", display: "none" });
|
|
38712
|
+
this.shadeRightSig = "";
|
|
38194
38713
|
this.overlayRoot = document.createElement("div");
|
|
38195
38714
|
Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
|
|
38196
38715
|
this.plot = document.createElement("div");
|
|
@@ -38198,7 +38717,7 @@ var NativeRenderer = class {
|
|
|
38198
38717
|
this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
|
|
38199
38718
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
38200
38719
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
38201
|
-
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
38720
|
+
this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.shadeRightEl, this.cursorCanvas, this.overlayRoot);
|
|
38202
38721
|
this.layerOrderSig = "";
|
|
38203
38722
|
this.wrapper.appendChild(this.plot);
|
|
38204
38723
|
this.factoryConfig = this.getConfig();
|
|
@@ -38256,13 +38775,14 @@ var NativeRenderer = class {
|
|
|
38256
38775
|
drawingsPointerDown: (x, y, snap, shift2, mod) => this.userDrawings?.pointerDown(x, y, snap, shift2, mod),
|
|
38257
38776
|
drawingsPointerMove: (x, y, snap, shift2, mod) => this.userDrawings?.pointerMove(x, y, snap, shift2, mod),
|
|
38258
38777
|
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
38259
|
-
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
|
|
38778
|
+
drawingsCursor: (x, y) => (this.drawingsInteractive ? this.userDrawings?.cursorAt(x, y) : null) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
|
|
38260
38779
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
38261
38780
|
drawingsClearTransient: () => this.userDrawings?.clearTransient()
|
|
38262
38781
|
});
|
|
38263
38782
|
this.input.rightEdgeZoom = this.zoomAnchorMode === "right";
|
|
38264
38783
|
this.input.axisDrag = this.axisDragEnabled;
|
|
38265
38784
|
this.input.paneResize = this.paneResizeEnabled;
|
|
38785
|
+
this.input.drawings = this.drawingsInteractive;
|
|
38266
38786
|
this.input.attach(this.dataCanvas);
|
|
38267
38787
|
if (this.settingsEnabled) this.setSettingsEnabled(true);
|
|
38268
38788
|
this.syncCountdownTimer();
|
|
@@ -38571,6 +39091,8 @@ var NativeRenderer = class {
|
|
|
38571
39091
|
this.backend.destroy();
|
|
38572
39092
|
this.chrome.destroy();
|
|
38573
39093
|
this.crosshairLayer.destroy();
|
|
39094
|
+
this.shadeRightEl?.remove();
|
|
39095
|
+
this.shadeRightEl = null;
|
|
38574
39096
|
this.attributionEl?.remove();
|
|
38575
39097
|
this.attributionEl = null;
|
|
38576
39098
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
@@ -38917,14 +39439,21 @@ var NativeRenderer = class {
|
|
|
38917
39439
|
this.priceStyleCbs.add(cb);
|
|
38918
39440
|
return () => this.priceStyleCbs.delete(cb);
|
|
38919
39441
|
}
|
|
39442
|
+
onPriceStyleWillChange(cb) {
|
|
39443
|
+
this.priceStyleWillChangeCbs.add(cb);
|
|
39444
|
+
return () => this.priceStyleWillChangeCbs.delete(cb);
|
|
39445
|
+
}
|
|
38920
39446
|
/**
|
|
38921
39447
|
* THE single write path for the base price style at runtime (feature set / settings dialog /
|
|
38922
|
-
* config template — the constructor seeds the field directly, pre-listeners).
|
|
38923
|
-
*
|
|
38924
|
-
*
|
|
39448
|
+
* config template — the constructor seeds the field directly, pre-listeners). Announces the
|
|
39449
|
+
* switch while the scene still holds the old style (a listener may paint/capture that frame),
|
|
39450
|
+
* then updates the scene, eases any reveal layer toward the new style's target, and notifies
|
|
39451
|
+
* the core — which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
|
|
38925
39452
|
*/
|
|
38926
39453
|
setPriceStyle(style) {
|
|
38927
39454
|
if (style === this.scene.priceStyle) return;
|
|
39455
|
+
const from = this.scene.priceStyle;
|
|
39456
|
+
for (const cb of this.priceStyleWillChangeCbs) cb(from, style);
|
|
38928
39457
|
this.scene.priceStyle = style;
|
|
38929
39458
|
this.scene.basePainting = basePaintingOf(style);
|
|
38930
39459
|
this.scene.candleOverride = candleOverrideFor(style, this.scene.style.chartTypes);
|
|
@@ -39154,7 +39683,7 @@ var NativeRenderer = class {
|
|
|
39154
39683
|
if (this.easeLiveBar(dtMs)) active = true;
|
|
39155
39684
|
this.skeletonClockMs += dtMs;
|
|
39156
39685
|
this.paintData();
|
|
39157
|
-
this.
|
|
39686
|
+
this.renderCrosshairLayer();
|
|
39158
39687
|
this.updateLegendValues();
|
|
39159
39688
|
this.emitViewportChange();
|
|
39160
39689
|
return active;
|
|
@@ -39729,10 +40258,31 @@ var NativeRenderer = class {
|
|
|
39729
40258
|
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
39730
40259
|
this.trackMarkPopover();
|
|
39731
40260
|
}
|
|
39732
|
-
this.
|
|
40261
|
+
this.renderCrosshairLayer();
|
|
39733
40262
|
if (!repaintsData(level) && this.paintedData) this.repaintCursorLayers();
|
|
39734
40263
|
this.updateLegendValues();
|
|
39735
40264
|
}
|
|
40265
|
+
/** Repaint the L2 crosshair canvas and keep the `.vela-shade-right` element on the veil it painted. */
|
|
40266
|
+
renderCrosshairLayer() {
|
|
40267
|
+
this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
|
|
40268
|
+
this.syncShadeRight();
|
|
40269
|
+
}
|
|
40270
|
+
/** Position (or hide) the `.vela-shade-right` element over the veil of the last crosshair frame. */
|
|
40271
|
+
syncShadeRight() {
|
|
40272
|
+
const el = this.shadeRightEl;
|
|
40273
|
+
if (!el) return;
|
|
40274
|
+
const area = this.crosshairLayer.shadeRightArea;
|
|
40275
|
+
const sig = area ? `${area.x},${area.width},${area.height}` : "";
|
|
40276
|
+
if (sig === this.shadeRightSig) return;
|
|
40277
|
+
this.shadeRightSig = sig;
|
|
40278
|
+
if (!area) {
|
|
40279
|
+
el.hidden = true;
|
|
40280
|
+
el.style.display = "none";
|
|
40281
|
+
return;
|
|
40282
|
+
}
|
|
40283
|
+
Object.assign(el.style, { left: `${area.x}px`, width: `${area.width}px`, height: `${area.height}px`, display: "" });
|
|
40284
|
+
el.hidden = false;
|
|
40285
|
+
}
|
|
39736
40286
|
/** Repaint the SDK layers that opted into cursor tracking (their own canvas only). */
|
|
39737
40287
|
repaintCursorLayers() {
|
|
39738
40288
|
const pane = this.scene.panes.get(PRICE_PANE_ID2);
|
|
@@ -40526,7 +41076,7 @@ var NativeRenderer = class {
|
|
|
40526
41076
|
if (this.animator.active) {
|
|
40527
41077
|
this.computeScales();
|
|
40528
41078
|
this.paintData();
|
|
40529
|
-
this.
|
|
41079
|
+
this.renderCrosshairLayer();
|
|
40530
41080
|
} else {
|
|
40531
41081
|
this.scheduler.flushNow(4 /* Full */);
|
|
40532
41082
|
}
|
|
@@ -44541,7 +45091,8 @@ var Vela = class {
|
|
|
44541
45091
|
glow: options.glow ?? 0,
|
|
44542
45092
|
upColor: options.upColor ?? BULLISH,
|
|
44543
45093
|
downColor: options.downColor ?? BEARISH,
|
|
44544
|
-
priceStyle: options.priceStyle ?? "candles"
|
|
45094
|
+
priceStyle: options.priceStyle ?? "candles",
|
|
45095
|
+
priceAxisTicks: options.priceAxis?.ticks ?? null
|
|
44545
45096
|
};
|
|
44546
45097
|
const RendererClass = options.renderer ?? NativeRenderer;
|
|
44547
45098
|
if (typeof RendererClass !== "function") {
|
|
@@ -46059,16 +46610,25 @@ var CellControls = class {
|
|
|
46059
46610
|
// src/widget/context-menu.ts
|
|
46060
46611
|
var PRICE_AXIS_W = 60;
|
|
46061
46612
|
var TIME_AXIS_H3 = 26;
|
|
46613
|
+
var OFF_PLOT = { price: null, time: null, paneKind: null };
|
|
46062
46614
|
var ChartContextMenu = class {
|
|
46063
46615
|
constructor(host, cbs) {
|
|
46064
46616
|
this.cbs = cbs;
|
|
46065
46617
|
this.chart = null;
|
|
46618
|
+
this.offCrosshair = null;
|
|
46619
|
+
/** The bound chart's latest crosshair report — a right-click always follows a pointer
|
|
46620
|
+
* move to its spot, so this is where it landed. */
|
|
46621
|
+
this.crosshair = OFF_PLOT;
|
|
46622
|
+
/** {@link crosshair} as it stood when the menu opened: the pointer moves on through the
|
|
46623
|
+
* menu before a row is picked, and `run` must see the right-clicked spot. */
|
|
46624
|
+
this.pointer = OFF_PLOT;
|
|
46066
46625
|
this.lastZone = "body";
|
|
46067
46626
|
/** The pane whose scale the open price-axis menu targets (null ⇒ the main scale). */
|
|
46068
46627
|
this.lastPane = null;
|
|
46069
46628
|
this.onContextMenu = (e) => {
|
|
46070
46629
|
e.preventDefault();
|
|
46071
46630
|
if (!this.chart) return;
|
|
46631
|
+
this.pointer = this.crosshair;
|
|
46072
46632
|
this.lastZone = this.zoneOf(e);
|
|
46073
46633
|
this.lastPane = this.lastZone === "price-axis" ? this.paneAt(e) : null;
|
|
46074
46634
|
this.menu.setItems(this.itemsFor(this.lastZone));
|
|
@@ -46082,15 +46642,23 @@ var ChartContextMenu = class {
|
|
|
46082
46642
|
// Pointer-anchored action menu: checked state reads as a leading ✓, not a
|
|
46083
46643
|
// washed row (which would read as hover in a menu with no trigger button).
|
|
46084
46644
|
checkmarks: true,
|
|
46645
|
+
iconBadges: true,
|
|
46085
46646
|
onSelect: (id) => this.run(id)
|
|
46086
46647
|
});
|
|
46087
46648
|
host.addEventListener("contextmenu", this.onContextMenu);
|
|
46088
46649
|
}
|
|
46089
46650
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
46090
46651
|
onChart(chart) {
|
|
46652
|
+
this.offCrosshair?.();
|
|
46091
46653
|
this.chart = chart;
|
|
46654
|
+
this.crosshair = OFF_PLOT;
|
|
46655
|
+
this.offCrosshair = chart.renderer.onCrosshairMove((e) => {
|
|
46656
|
+
this.crosshair = { price: e.price, time: e.time, paneKind: e.paneKind ?? null };
|
|
46657
|
+
});
|
|
46092
46658
|
}
|
|
46093
46659
|
destroy() {
|
|
46660
|
+
this.offCrosshair?.();
|
|
46661
|
+
this.offCrosshair = null;
|
|
46094
46662
|
this.host.removeEventListener("contextmenu", this.onContextMenu);
|
|
46095
46663
|
this.menu.destroy();
|
|
46096
46664
|
}
|
|
@@ -46109,49 +46677,52 @@ var ChartContextMenu = class {
|
|
|
46109
46677
|
flag(feature) {
|
|
46110
46678
|
return Boolean(this.chart?.renderer.get(feature));
|
|
46111
46679
|
}
|
|
46112
|
-
|
|
46680
|
+
/** A fresh widget context carrying the pointer captured at open. Copied by property
|
|
46681
|
+
* descriptor: the context's getters must stay LIVE, and a spread would freeze them. */
|
|
46682
|
+
context() {
|
|
46113
46683
|
const ctx = this.cbs.getContext?.();
|
|
46114
|
-
|
|
46115
|
-
|
|
46116
|
-
|
|
46117
|
-
|
|
46118
|
-
|
|
46684
|
+
if (!ctx) return void 0;
|
|
46685
|
+
return Object.create(Object.getPrototypeOf(ctx), {
|
|
46686
|
+
...Object.getOwnPropertyDescriptors(ctx),
|
|
46687
|
+
pointer: { value: this.pointer, enumerable: true }
|
|
46688
|
+
});
|
|
46689
|
+
}
|
|
46690
|
+
contributed(zone) {
|
|
46691
|
+
return widgetActions(`context:${zone}`, this.context()).map((a) => ({
|
|
46692
|
+
item: { id: `action:${a.id}`, label: a.label, icon: a.icon },
|
|
46693
|
+
order: a.order
|
|
46119
46694
|
}));
|
|
46120
46695
|
}
|
|
46121
46696
|
itemsFor(zone) {
|
|
46697
|
+
return composeMenu(zone, this.builtinItems(zone), this.contributed(zone));
|
|
46698
|
+
}
|
|
46699
|
+
builtinItems(zone) {
|
|
46122
46700
|
if (zone === "price-axis") {
|
|
46123
46701
|
const pane = this.lastPane;
|
|
46124
|
-
return
|
|
46125
|
-
|
|
46126
|
-
|
|
46127
|
-
|
|
46128
|
-
|
|
46129
|
-
|
|
46130
|
-
|
|
46131
|
-
|
|
46132
|
-
|
|
46133
|
-
}),
|
|
46134
|
-
...this.contributed(zone)
|
|
46135
|
-
];
|
|
46702
|
+
return priceAxisItems({
|
|
46703
|
+
auto: this.chart?.renderer.get("autoScale") !== false,
|
|
46704
|
+
invert: pane ? pane.invert : this.flag("invertScale"),
|
|
46705
|
+
choice: scaleChoiceOf(pane ?? { mode: String(this.chart?.renderer.get("scaleMode") ?? "price"), log: this.flag("logScale") }),
|
|
46706
|
+
axisLabels: this.flag("axisLabels"),
|
|
46707
|
+
priceLabel: this.flag("priceLabel"),
|
|
46708
|
+
countdown: this.flag("countdown"),
|
|
46709
|
+
priceLine: this.flag("currentPriceLine")
|
|
46710
|
+
});
|
|
46136
46711
|
}
|
|
46137
46712
|
if (zone === "time-axis") {
|
|
46138
|
-
|
|
46139
|
-
return [...timeAxisItems(tz), ...this.contributed("time-axis")];
|
|
46713
|
+
return timeAxisItems(this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC"));
|
|
46140
46714
|
}
|
|
46141
46715
|
const chart = this.chart;
|
|
46142
|
-
return
|
|
46143
|
-
|
|
46144
|
-
|
|
46145
|
-
|
|
46146
|
-
}),
|
|
46147
|
-
...this.contributed("body")
|
|
46148
|
-
];
|
|
46716
|
+
return bodyItems({
|
|
46717
|
+
drawings: chart?.drawings.supported ? chart.drawings.all().length : 0,
|
|
46718
|
+
indicators: chart?.indicators().length ?? 0
|
|
46719
|
+
});
|
|
46149
46720
|
}
|
|
46150
46721
|
run(id) {
|
|
46151
46722
|
const chart = this.chart;
|
|
46152
46723
|
if (!chart) return;
|
|
46153
46724
|
if (id.startsWith("action:")) {
|
|
46154
|
-
const ctx = this.
|
|
46725
|
+
const ctx = this.context();
|
|
46155
46726
|
if (ctx) widgetActions(`context:${this.lastZone}`, ctx).find((a) => a.id === id.slice("action:".length))?.run(ctx);
|
|
46156
46727
|
return;
|
|
46157
46728
|
}
|
|
@@ -46275,8 +46846,8 @@ function seedDefaults(opts) {
|
|
|
46275
46846
|
};
|
|
46276
46847
|
}
|
|
46277
46848
|
function cellChartDefaults(opts) {
|
|
46278
|
-
const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings } = opts;
|
|
46279
|
-
return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings };
|
|
46849
|
+
const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis } = opts;
|
|
46850
|
+
return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis };
|
|
46280
46851
|
}
|
|
46281
46852
|
function cellDrawings(opt) {
|
|
46282
46853
|
if (opt === false) return false;
|
|
@@ -46846,12 +47417,14 @@ var ChartCell = class {
|
|
|
46846
47417
|
this.watermarkOn = visible;
|
|
46847
47418
|
this.watermark?.setVisible(visible);
|
|
46848
47419
|
this.deps.onStateDirty();
|
|
47420
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
46849
47421
|
}
|
|
46850
47422
|
/** Show/hide the "Replay" line under this cell's watermark while it replays (persisted per cell). */
|
|
46851
47423
|
setReplayWatermarkVisible(visible) {
|
|
46852
47424
|
this.replayWatermarkOn = visible;
|
|
46853
47425
|
this.watermark?.setReplayVisible(visible);
|
|
46854
47426
|
this.deps.onStateDirty();
|
|
47427
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
46855
47428
|
}
|
|
46856
47429
|
/** Show/hide this cell's indicator titles — the in-chart legend rows (persisted per cell). */
|
|
46857
47430
|
setIndicatorTitlesVisible(visible) {
|
|
@@ -46872,18 +47445,20 @@ var ChartCell = class {
|
|
|
46872
47445
|
this.statusline?.setPartVisible(part, visible);
|
|
46873
47446
|
this.deps.onStatusPrefsChanged(this.id);
|
|
46874
47447
|
}
|
|
46875
|
-
/** This cell's Status line tab prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
47448
|
+
/** This cell's Status line tab and watermark prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
46876
47449
|
statusPrefs() {
|
|
46877
47450
|
const sl = this.statusline;
|
|
46878
47451
|
return {
|
|
46879
47452
|
parts: sl ? { logo: sl.partVisible("logo"), name: sl.partVisible("name"), market: sl.partVisible("market"), ohlc: sl.partVisible("ohlc"), change: sl.partVisible("change") } : null,
|
|
46880
47453
|
indicatorTitles: this.indicatorTitlesOn,
|
|
46881
|
-
indicatorValues: this.indicatorValuesOn
|
|
47454
|
+
indicatorValues: this.indicatorValuesOn,
|
|
47455
|
+
watermark: this.watermarkOn,
|
|
47456
|
+
replayWatermark: this.replayWatermarkOn
|
|
46882
47457
|
};
|
|
46883
47458
|
}
|
|
46884
|
-
/** Converge this cell's Status line tab prefs to `prefs` — the
|
|
46885
|
-
* the workspace's style link. Idempotent: matching values change
|
|
46886
|
-
* propagated echo dies on its own. */
|
|
47459
|
+
/** Converge this cell's Status line tab and watermark prefs to `prefs` — the
|
|
47460
|
+
* follower half of the workspace's style link. Idempotent: matching values change
|
|
47461
|
+
* nothing, so a propagated echo dies on its own. */
|
|
46887
47462
|
applyStatusPrefs(prefs) {
|
|
46888
47463
|
if (prefs.parts && this.statusline) {
|
|
46889
47464
|
for (const part of Object.keys(prefs.parts)) {
|
|
@@ -46892,6 +47467,8 @@ var ChartCell = class {
|
|
|
46892
47467
|
}
|
|
46893
47468
|
if (prefs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(prefs.indicatorTitles);
|
|
46894
47469
|
if (prefs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(prefs.indicatorValues);
|
|
47470
|
+
if (prefs.watermark !== this.watermarkOn) this.setWatermarkVisible(prefs.watermark);
|
|
47471
|
+
if (prefs.replayWatermark !== this.replayWatermarkOn) this.setReplayWatermarkVisible(prefs.replayWatermark);
|
|
46895
47472
|
}
|
|
46896
47473
|
/** The LIVE chart of this cell — never cache it across a layout change (the cell's
|
|
46897
47474
|
* identity is what endures; the chart dies with the cell). */
|
|
@@ -47802,6 +48379,55 @@ function occupancyGrid(def) {
|
|
|
47802
48379
|
const cols = def.cols.length;
|
|
47803
48380
|
return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ?? `\xB7${r}x${c}`));
|
|
47804
48381
|
}
|
|
48382
|
+
var GLYPH_MIN = 1.5;
|
|
48383
|
+
var GLYPH_SPAN = 13;
|
|
48384
|
+
function glyphStops(weights, n) {
|
|
48385
|
+
const w = Array.from({ length: n }, (_, i) => {
|
|
48386
|
+
const v = weights[i];
|
|
48387
|
+
return v !== void 0 && Number.isFinite(v) && v > 0 ? v : 1;
|
|
48388
|
+
});
|
|
48389
|
+
const total = w.reduce((a, b) => a + b, 0);
|
|
48390
|
+
const stops = [GLYPH_MIN];
|
|
48391
|
+
let acc = 0;
|
|
48392
|
+
for (const v of w) {
|
|
48393
|
+
acc += v;
|
|
48394
|
+
stops.push(Math.round((GLYPH_MIN + GLYPH_SPAN * acc / total) * 100) / 100);
|
|
48395
|
+
}
|
|
48396
|
+
return stops;
|
|
48397
|
+
}
|
|
48398
|
+
function layoutGlyph(def) {
|
|
48399
|
+
const grid = occupancyGrid(def);
|
|
48400
|
+
const rows = grid.length;
|
|
48401
|
+
const cols = grid.reduce((n, row) => Math.max(n, row.length), 0);
|
|
48402
|
+
const xs = glyphStops(def.cols, cols);
|
|
48403
|
+
const ys = glyphStops(def.rows, rows);
|
|
48404
|
+
const at = (r, c) => grid[r]?.[c];
|
|
48405
|
+
const seams = [];
|
|
48406
|
+
for (let c = 1; c < cols; c += 1) {
|
|
48407
|
+
let start = -1;
|
|
48408
|
+
for (let r = 0; r <= rows; r += 1) {
|
|
48409
|
+
const split = r < rows && at(r, c - 1) !== at(r, c);
|
|
48410
|
+
if (split && start < 0) start = r;
|
|
48411
|
+
if (!split && start >= 0) {
|
|
48412
|
+
seams.push(`M${xs[c]} ${ys[start]}V${ys[r]}`);
|
|
48413
|
+
start = -1;
|
|
48414
|
+
}
|
|
48415
|
+
}
|
|
48416
|
+
}
|
|
48417
|
+
for (let r = 1; r < rows; r += 1) {
|
|
48418
|
+
let start = -1;
|
|
48419
|
+
for (let c = 0; c <= cols; c += 1) {
|
|
48420
|
+
const split = c < cols && at(r - 1, c) !== at(r, c);
|
|
48421
|
+
if (split && start < 0) start = c;
|
|
48422
|
+
if (!split && start >= 0) {
|
|
48423
|
+
seams.push(`M${xs[start]} ${ys[r]}H${xs[c]}`);
|
|
48424
|
+
start = -1;
|
|
48425
|
+
}
|
|
48426
|
+
}
|
|
48427
|
+
}
|
|
48428
|
+
const frame = `<rect x="${GLYPH_MIN}" y="${GLYPH_MIN}" width="${GLYPH_SPAN}" height="${GLYPH_SPAN}" rx="1.5"/>`;
|
|
48429
|
+
return svg16(seams.length > 0 ? `${frame}<path d="${seams.join("")}"/>` : frame);
|
|
48430
|
+
}
|
|
47805
48431
|
function gridStyles(def, trackSizes) {
|
|
47806
48432
|
const cols = trackSizes?.cols?.length === def.cols.length ? trackSizes.cols : def.cols;
|
|
47807
48433
|
const rows = trackSizes?.rows?.length === def.rows.length ? trackSizes.rows : def.rows;
|
|
@@ -48333,7 +48959,8 @@ var VelaWorkspace = class {
|
|
|
48333
48959
|
onToggleSync: (id) => {
|
|
48334
48960
|
const kind = id;
|
|
48335
48961
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
48336
|
-
}
|
|
48962
|
+
},
|
|
48963
|
+
glyph: () => layoutGlyph(this.def)
|
|
48337
48964
|
},
|
|
48338
48965
|
getContext: () => this.context()
|
|
48339
48966
|
});
|
|
@@ -48832,6 +49459,7 @@ var VelaWorkspace = class {
|
|
|
48832
49459
|
this.applyGrid();
|
|
48833
49460
|
this.buildCells();
|
|
48834
49461
|
this.alignNewCellStyles(preexisting);
|
|
49462
|
+
this.alignNewCellDrawings(preexisting);
|
|
48835
49463
|
this.syncCellPresentation();
|
|
48836
49464
|
this.refreshCellControls();
|
|
48837
49465
|
this.topbar.setLayout(next.id);
|
|
@@ -49248,6 +49876,7 @@ var VelaWorkspace = class {
|
|
|
49248
49876
|
const chart = cell.chart;
|
|
49249
49877
|
chart.on("indicator:error", ({ error }) => this.toastHost.show(`[${cell.id}] ${error.message}`, "error", 5e3));
|
|
49250
49878
|
chart.on("script:run", (run) => this.events.emit("script:run", { ...run, cell: cell.id }));
|
|
49879
|
+
chart.on("priceStyle:change", ({ from, to }) => this.events.emit("cell:priceStyle", { id: cell.id, from, to }));
|
|
49251
49880
|
chart.on("alert", (alert) => {
|
|
49252
49881
|
const source = [parseSymbol(cell.symbol).ticker || cell.symbol, timeframeLabel(cell.timeframe), alert.indicator].filter(Boolean).join(" ");
|
|
49253
49882
|
this.alerts.unshift({ cellId: cell.id, source, title: alert.title ?? "Alert", message: alert.message, time: alert.time });
|
|
@@ -49365,12 +49994,66 @@ var VelaWorkspace = class {
|
|
|
49365
49994
|
}
|
|
49366
49995
|
}
|
|
49367
49996
|
/**
|
|
49368
|
-
*
|
|
49369
|
-
*
|
|
49370
|
-
*
|
|
49371
|
-
*
|
|
49372
|
-
* in
|
|
49373
|
-
*
|
|
49997
|
+
* Bring cells minted by a layout change into their drawings group: with the link
|
|
49998
|
+
* on, a NEW cell (fresh slot, or one returning from the pool that missed edits
|
|
49999
|
+
* while dormant) receives the drawings of a pre-existing group peer — the active
|
|
50000
|
+
* cell when it is one. A drawing the arriving cell already holds a linked copy of
|
|
50001
|
+
* is refreshed in place; any other is copied and linked like a freshly synced
|
|
50002
|
+
* drawing, so later edits and removals follow both ways. The arrival is the cell's
|
|
50003
|
+
* starting state, not an edit: it stays out of the cell's undo timeline, and the
|
|
50004
|
+
* busy guard keeps the copies' own events from fanning back out.
|
|
50005
|
+
*/
|
|
50006
|
+
alignNewCellDrawings(preexisting) {
|
|
50007
|
+
const setting = this.syncOpts.drawings;
|
|
50008
|
+
if (!setting) return;
|
|
50009
|
+
const ids = [...this.cellsById.keys()];
|
|
50010
|
+
for (const id of ids) {
|
|
50011
|
+
if (preexisting.has(id)) continue;
|
|
50012
|
+
const cell = this.cellsById.get(id);
|
|
50013
|
+
if (!cell?.chart.drawings.supported) continue;
|
|
50014
|
+
const peers = syncTargets(id, setting, ids).filter((p) => preexisting.has(p));
|
|
50015
|
+
if (peers.length === 0) continue;
|
|
50016
|
+
const sourceId = this.activeId && peers.includes(this.activeId) ? this.activeId : peers[0];
|
|
50017
|
+
const source = this.cellsById.get(sourceId);
|
|
50018
|
+
if (!source) continue;
|
|
50019
|
+
const drawings = cell.chart.drawings;
|
|
50020
|
+
this.drawingSyncBusy = true;
|
|
50021
|
+
try {
|
|
50022
|
+
cell.history.silently(() => {
|
|
50023
|
+
const held = new Set(drawings.all().map((d) => d.id));
|
|
50024
|
+
for (const doc of source.chart.drawings.all()) {
|
|
50025
|
+
const group = this.drawingLinks.get(`${sourceId}\0${doc.id}`) ?? /* @__PURE__ */ new Map([[sourceId, doc.id]]);
|
|
50026
|
+
const peerId = group.get(id);
|
|
50027
|
+
if (peerId != null && held.has(peerId)) {
|
|
50028
|
+
drawings.update(peerId, { anchors: doc.anchors, style: doc.style, text: doc.text, props: doc.props });
|
|
50029
|
+
continue;
|
|
50030
|
+
}
|
|
50031
|
+
const copy = drawings.add(doc.type, {
|
|
50032
|
+
paneId: doc.paneId,
|
|
50033
|
+
anchors: doc.anchors,
|
|
50034
|
+
style: doc.style,
|
|
50035
|
+
text: doc.text,
|
|
50036
|
+
props: doc.props,
|
|
50037
|
+
zIndex: doc.zIndex
|
|
50038
|
+
});
|
|
50039
|
+
if (!copy) continue;
|
|
50040
|
+
if (peerId != null) this.drawingLinks.delete(`${id}\0${peerId}`);
|
|
50041
|
+
group.set(id, copy.id);
|
|
50042
|
+
for (const [cellId, dId] of group) this.drawingLinks.set(`${cellId}\0${dId}`, group);
|
|
50043
|
+
}
|
|
50044
|
+
});
|
|
50045
|
+
} finally {
|
|
50046
|
+
this.drawingSyncBusy = false;
|
|
50047
|
+
}
|
|
50048
|
+
}
|
|
50049
|
+
}
|
|
50050
|
+
/**
|
|
50051
|
+
* Mirror an origin cell's presentation — the style-link slice of its renderer
|
|
50052
|
+
* config ({@link styleConfigPatch}) plus its Status line and watermark prefs —
|
|
50053
|
+
* onto its same-group followers (the style link). Loop-safe two ways: the busy
|
|
50054
|
+
* guard eats the followers' SYNCHRONOUS echoes (their `applyConfig` re-fires
|
|
50055
|
+
* `onConfigChanged` in the same tick), and the equality short-circuits leave
|
|
50056
|
+
* already-converged followers untouched, so nothing re-emits once the group agrees.
|
|
49374
50057
|
*/
|
|
49375
50058
|
propagateStylePrefs(originId) {
|
|
49376
50059
|
if (this.styleSyncBusy || this.destroyed) return;
|
|
@@ -49378,17 +50061,15 @@ var VelaWorkspace = class {
|
|
|
49378
50061
|
if (targets.length === 0) return;
|
|
49379
50062
|
const origin = this.cellsById.get(originId);
|
|
49380
50063
|
if (!origin) return;
|
|
49381
|
-
const
|
|
49382
|
-
const sliceJson = slice ? JSON.stringify(slice) : null;
|
|
50064
|
+
const config = origin.chart.renderer.getConfig();
|
|
49383
50065
|
const prefs = origin.statusPrefs();
|
|
49384
50066
|
this.styleSyncBusy = true;
|
|
49385
50067
|
try {
|
|
49386
50068
|
for (const id of targets) {
|
|
49387
50069
|
const cell = this.cellsById.get(id);
|
|
49388
50070
|
if (!cell) continue;
|
|
49389
|
-
|
|
49390
|
-
|
|
49391
|
-
}
|
|
50071
|
+
const patch = styleConfigPatch(config, cell.chart.renderer.getConfig());
|
|
50072
|
+
if (patch) cell.chart.renderer.applyConfig(patch);
|
|
49392
50073
|
cell.applyStatusPrefs(prefs);
|
|
49393
50074
|
}
|
|
49394
50075
|
} finally {
|
|
@@ -49709,7 +50390,8 @@ var VelaWorkspace = class {
|
|
|
49709
50390
|
onToggleSync: (id) => {
|
|
49710
50391
|
const kind = id;
|
|
49711
50392
|
this.sync.set(kind, this.syncOpts[kind] ? false : true);
|
|
49712
|
-
}
|
|
50393
|
+
},
|
|
50394
|
+
glyph: () => layoutGlyph(this.def)
|
|
49713
50395
|
},
|
|
49714
50396
|
onOpenChange: (open2) => this.trackDialog(open2)
|
|
49715
50397
|
}));
|