@luxalgo/vela 0.8.1 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{DataProvider-BrlxntPN.d.cts → DataProvider-COGDs9Xg.d.cts} +1 -1
- package/dist/{DataProvider-DwFIoV4r.d.ts → DataProvider-DpOWZD8F.d.ts} +1 -1
- package/dist/{chunk-LMBYEGUR.js → chunk-27UTYE7T.js} +25 -2
- package/dist/{chunk-EZ5FWVLA.js → chunk-DPAA3SRT.js} +94 -13
- package/dist/{chunk-BZQM2XO7.js → chunk-HKNO24GD.js} +13 -1
- package/dist/{chunk-NELQJCGK.js → chunk-NLDTVFOI.js} +41 -3
- package/dist/{chunk-DBDXKZUF.js → chunk-R2JMNIHY.js} +53 -13
- package/dist/{chunk-ATGTCWHJ.js → chunk-T3DZIR5Y.js} +171 -32
- package/dist/{contributions-tjNExo1o.d.ts → contributions-Cgm9RJYP.d.cts} +38 -11
- package/dist/{contributions-CtlptxR8.d.cts → contributions-bWgwyiVi.d.ts} +38 -11
- package/dist/index.cjs +239 -37
- package/dist/index.d.cts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +4 -4
- package/dist/{options-BnEPUuVV.d.ts → options-NAUHiQEC.d.cts} +3 -3
- package/dist/{options-BnEPUuVV.d.cts → options-NAUHiQEC.d.ts} +3 -3
- package/dist/{plugin-BWxJRKzS.d.cts → plugin-CjjzZX2F.d.cts} +4 -4
- package/dist/{plugin-e2WUEtiI.d.ts → plugin-CqxWoDKS.d.ts} +4 -4
- package/dist/plugin.cjs +20 -6
- package/dist/plugin.d.cts +5 -5
- package/dist/plugin.d.ts +5 -5
- package/dist/plugin.js +2 -2
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/{side-panel-HF0IAzwf.d.cts → side-panel-wMNfgyZb.d.cts} +13 -0
- package/dist/{side-panel-HF0IAzwf.d.ts → side-panel-wMNfgyZb.d.ts} +13 -0
- package/dist/{statusline-model-zKXtGIjj.d.ts → statusline-model-Dih6FGWP.d.ts} +3 -3
- package/dist/{statusline-model-DFY7tuHj.d.cts → statusline-model-Dpfpq54A.d.cts} +3 -3
- package/dist/ui.cjs +77 -2
- package/dist/ui.d.cts +28 -2
- package/dist/ui.d.ts +28 -2
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +239 -37
- package/dist/vela.global.min.js +52 -52
- package/dist/widget.cjs +383 -50
- package/dist/widget.d.cts +20 -10
- package/dist/widget.d.ts +20 -10
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +383 -50
- package/dist/workspace.d.cts +5 -5
- package/dist/workspace.d.ts +5 -5
- package/dist/workspace.js +6 -6
- package/package.json +2 -2
package/dist/widget.cjs
CHANGED
|
@@ -1256,6 +1256,7 @@ registerIcon("expand", S('<rect x="2.2" y="2.2" width="11.6" height="11.6" rx="1
|
|
|
1256
1256
|
registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
|
|
1257
1257
|
registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
|
|
1258
1258
|
registerIcon("maximize", S('<path d="M2.5 6V3a.5.5 0 0 1 .5-.5h3M10 2.5h3a.5.5 0 0 1 .5.5v3M13.5 10v3a.5.5 0 0 1-.5.5h-3M6 13.5H3a.5.5 0 0 1-.5-.5v-3"/>'));
|
|
1259
|
+
registerIcon("restore", S('<path d="M6 2.5v3a.5.5 0 0 1-.5.5h-3M13.5 6h-3a.5.5 0 0 1-.5-.5v-3M10 13.5v-3a.5.5 0 0 1 .5-.5h3M2.5 10h3a.5.5 0 0 1 .5.5v3"/>'));
|
|
1259
1260
|
registerIcon("restore", S('<path d="M6.2 2.5v3.7H2.5M9.8 13.5V9.8h3.7M13.5 6.2H9.8V2.5M2.5 9.8h3.7v3.7"/>'));
|
|
1260
1261
|
registerIcon("star", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>'));
|
|
1261
1262
|
registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>', 'fill="currentColor"'));
|
|
@@ -1337,6 +1338,17 @@ function nextUid(prefix) {
|
|
|
1337
1338
|
return `${prefix}-${++uid}`;
|
|
1338
1339
|
}
|
|
1339
1340
|
|
|
1341
|
+
// src/ui/surface-events.ts
|
|
1342
|
+
var SURFACE_OPEN_EVENT = "vela:surface-open";
|
|
1343
|
+
var SURFACE_CLOSE_EVENT = "vela:surface-close";
|
|
1344
|
+
function announceSurface(el, open2, kind, trigger = null) {
|
|
1345
|
+
const View = el.ownerDocument.defaultView;
|
|
1346
|
+
if (!View) return;
|
|
1347
|
+
el.dispatchEvent(
|
|
1348
|
+
new View.CustomEvent(open2 ? SURFACE_OPEN_EVENT : SURFACE_CLOSE_EVENT, { bubbles: true, composed: true, detail: { kind, trigger } })
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1340
1352
|
// src/ui/keymap.ts
|
|
1341
1353
|
var KEY_ALIASES = {
|
|
1342
1354
|
esc: "escape",
|
|
@@ -1698,6 +1710,8 @@ var Surface = class _Surface {
|
|
|
1698
1710
|
/** Trigger rect captured when this level opened — the list stays put if the
|
|
1699
1711
|
* trigger then moves (favorite chips shifting the caret). */
|
|
1700
1712
|
this.pinnedAnchor = null;
|
|
1713
|
+
/** The open state last announced — see {@link announce}. */
|
|
1714
|
+
this.shown = false;
|
|
1701
1715
|
this.doc = doc;
|
|
1702
1716
|
this.host = opts.host;
|
|
1703
1717
|
this.onSelect = opts.onSelect;
|
|
@@ -1711,6 +1725,7 @@ var Surface = class _Surface {
|
|
|
1711
1725
|
this.positioner.appendChild(this.list);
|
|
1712
1726
|
this.host.appendChild(this.positioner);
|
|
1713
1727
|
const trigger = opts.trigger;
|
|
1728
|
+
this.opener = trigger ?? opts.opener ?? null;
|
|
1714
1729
|
this.ctrl = menuController({
|
|
1715
1730
|
items: [],
|
|
1716
1731
|
id: opts.id,
|
|
@@ -1732,15 +1747,21 @@ var Surface = class _Surface {
|
|
|
1732
1747
|
if (opts.triggerId && trigger) trigger.id = opts.triggerId;
|
|
1733
1748
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
1734
1749
|
const api = this.ctrl.connect(service);
|
|
1750
|
+
if (this.shown && !api.open) this.announce(false);
|
|
1735
1751
|
if (trigger) vanilla.spreadProps(trigger, api.getTriggerProps(), this.mid);
|
|
1736
1752
|
vanilla.spreadProps(this.positioner, api.getPositionerProps(), this.mid);
|
|
1737
1753
|
vanilla.spreadProps(this.list, api.getContentProps(), this.mid);
|
|
1738
1754
|
this.project(api);
|
|
1755
|
+
if (api.open && !this.shown) this.announce(true);
|
|
1739
1756
|
});
|
|
1740
1757
|
}
|
|
1741
1758
|
get api() {
|
|
1742
1759
|
return this.ctrl.connect(this.handle.service);
|
|
1743
1760
|
}
|
|
1761
|
+
announce(open2) {
|
|
1762
|
+
this.shown = open2;
|
|
1763
|
+
announceSurface(this.list, open2, "menu", this.opener);
|
|
1764
|
+
}
|
|
1744
1765
|
setItems(items) {
|
|
1745
1766
|
this.items = items;
|
|
1746
1767
|
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
@@ -1758,6 +1779,7 @@ var Surface = class _Surface {
|
|
|
1758
1779
|
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
1759
1780
|
this.subs.clear();
|
|
1760
1781
|
this.handle.stop();
|
|
1782
|
+
if (this.shown) this.announce(false);
|
|
1761
1783
|
this.positioner.remove();
|
|
1762
1784
|
}
|
|
1763
1785
|
byId(id) {
|
|
@@ -1851,6 +1873,7 @@ var Surface = class _Surface {
|
|
|
1851
1873
|
const sub2 = new _Surface(doc, {
|
|
1852
1874
|
host: this.host,
|
|
1853
1875
|
placement: "right-start",
|
|
1876
|
+
opener: li,
|
|
1854
1877
|
onSelect: this.onSelect,
|
|
1855
1878
|
onFavorite: this.onFavorite,
|
|
1856
1879
|
checkmarks: this.checkmarks,
|
|
@@ -2070,16 +2093,21 @@ var Popover = class {
|
|
|
2070
2093
|
this.hide();
|
|
2071
2094
|
};
|
|
2072
2095
|
const onReflow = () => this.place();
|
|
2073
|
-
setTimeout(() =>
|
|
2096
|
+
setTimeout(() => {
|
|
2097
|
+
if (this.onOutside === onOutside) document.addEventListener("pointerdown", onOutside, true);
|
|
2098
|
+
}, 0);
|
|
2074
2099
|
document.addEventListener("keydown", onKey, true);
|
|
2075
2100
|
window.addEventListener("resize", onReflow, true);
|
|
2076
2101
|
document.addEventListener("scroll", onReflow, true);
|
|
2077
2102
|
this.onOutside = onOutside;
|
|
2078
2103
|
this.onKey = onKey;
|
|
2079
2104
|
this.onReflow = onReflow;
|
|
2105
|
+
announceSurface(this.el, true, "popover", this.trigger);
|
|
2080
2106
|
}
|
|
2081
2107
|
hide() {
|
|
2082
2108
|
if (!this.shown) return;
|
|
2109
|
+
this.shown = false;
|
|
2110
|
+
announceSurface(this.el, false, "popover", this.trigger);
|
|
2083
2111
|
if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
|
|
2084
2112
|
if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
|
|
2085
2113
|
if (this.onReflow) {
|
|
@@ -2099,7 +2127,6 @@ var Popover = class {
|
|
|
2099
2127
|
} else {
|
|
2100
2128
|
this.el.remove();
|
|
2101
2129
|
}
|
|
2102
|
-
this.shown = false;
|
|
2103
2130
|
if (open === this) open = null;
|
|
2104
2131
|
this.ctrl.onClose?.();
|
|
2105
2132
|
}
|
|
@@ -2305,6 +2332,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
2305
2332
|
`;
|
|
2306
2333
|
var Dialog = class {
|
|
2307
2334
|
constructor(opts = {}) {
|
|
2335
|
+
this.opener = null;
|
|
2336
|
+
this.wasOpen = false;
|
|
2308
2337
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
2309
2338
|
injectStyles(DIALOG_STYLE_ID, DIALOG_CSS, doc);
|
|
2310
2339
|
const host = opts.host ?? doc.body;
|
|
@@ -2401,6 +2430,7 @@ var Dialog = class {
|
|
|
2401
2430
|
const mid = String(this.ctrl.props.id);
|
|
2402
2431
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
2403
2432
|
const api = this.ctrl.connect(service);
|
|
2433
|
+
if (this.wasOpen && !api.open) this.announce(false);
|
|
2404
2434
|
vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
|
|
2405
2435
|
vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
|
|
2406
2436
|
vanilla.spreadProps(this.panel, api.getContentProps(), mid);
|
|
@@ -2408,12 +2438,27 @@ var Dialog = class {
|
|
|
2408
2438
|
vanilla.spreadProps(close, api.getCloseTriggerProps(), mid);
|
|
2409
2439
|
this.backdrop.style.display = api.open ? "" : "none";
|
|
2410
2440
|
this.positioner.style.display = api.open ? "" : "none";
|
|
2441
|
+
if (api.open && !this.wasOpen) this.announce(true);
|
|
2442
|
+
if (this.wasOpen && !api.open) this.restoreOpener();
|
|
2443
|
+
this.wasOpen = api.open;
|
|
2411
2444
|
});
|
|
2412
2445
|
}
|
|
2446
|
+
/** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
|
|
2447
|
+
* teardown that outruns the machine's close notification). */
|
|
2448
|
+
restoreOpener() {
|
|
2449
|
+
const opener = this.opener;
|
|
2450
|
+
this.opener = null;
|
|
2451
|
+
if (opener?.isConnected) opener.focus({ preventScroll: true });
|
|
2452
|
+
}
|
|
2453
|
+
announce(open2) {
|
|
2454
|
+
const opener = this.opener === this.panel.ownerDocument.body ? null : this.opener;
|
|
2455
|
+
announceSurface(this.panel, open2, "dialog", opener);
|
|
2456
|
+
}
|
|
2413
2457
|
get open() {
|
|
2414
2458
|
return this.ctrl.connect(this.handle.service).open;
|
|
2415
2459
|
}
|
|
2416
2460
|
show() {
|
|
2461
|
+
this.opener = this.panel.ownerDocument.activeElement;
|
|
2417
2462
|
this.ctrl.connect(this.handle.service).setOpen(true);
|
|
2418
2463
|
}
|
|
2419
2464
|
hide() {
|
|
@@ -2428,6 +2473,11 @@ var Dialog = class {
|
|
|
2428
2473
|
destroy() {
|
|
2429
2474
|
if (this.open) this.hide();
|
|
2430
2475
|
this.handle.stop();
|
|
2476
|
+
if (this.wasOpen) {
|
|
2477
|
+
this.wasOpen = false;
|
|
2478
|
+
this.announce(false);
|
|
2479
|
+
}
|
|
2480
|
+
this.restoreOpener();
|
|
2431
2481
|
this.backdrop.remove();
|
|
2432
2482
|
this.positioner.remove();
|
|
2433
2483
|
}
|
|
@@ -2556,6 +2606,8 @@ function swipeDirection(dx, dy) {
|
|
|
2556
2606
|
}
|
|
2557
2607
|
var Drawer = class {
|
|
2558
2608
|
constructor(opts = {}) {
|
|
2609
|
+
this.opener = null;
|
|
2610
|
+
this.wasOpen = false;
|
|
2559
2611
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
2560
2612
|
injectStyles(DRAWER_STYLE_ID, DRAWER_CSS, doc);
|
|
2561
2613
|
const host = opts.host ?? doc.body;
|
|
@@ -2583,12 +2635,16 @@ var Drawer = class {
|
|
|
2583
2635
|
const mid = String(this.ctrl.props.id);
|
|
2584
2636
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
2585
2637
|
const api = this.ctrl.connect(service);
|
|
2638
|
+
if (this.wasOpen && !api.open) this.announce(false);
|
|
2586
2639
|
vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
|
|
2587
2640
|
vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
|
|
2588
2641
|
vanilla.spreadProps(this.panel, api.getContentProps(), mid);
|
|
2589
2642
|
vanilla.spreadProps(this.titleEl, api.getTitleProps(), mid);
|
|
2590
2643
|
this.backdrop.style.display = api.open ? "" : "none";
|
|
2591
2644
|
this.positioner.style.display = api.open ? "" : "none";
|
|
2645
|
+
if (api.open && !this.wasOpen) this.announce(true);
|
|
2646
|
+
if (this.wasOpen && !api.open) this.restoreOpener();
|
|
2647
|
+
this.wasOpen = api.open;
|
|
2592
2648
|
});
|
|
2593
2649
|
}
|
|
2594
2650
|
/** Any element between `from` and the panel that has already been scrolled down —
|
|
@@ -2697,10 +2753,22 @@ var Drawer = class {
|
|
|
2697
2753
|
setTitle(title) {
|
|
2698
2754
|
this.titleEl.textContent = title;
|
|
2699
2755
|
}
|
|
2756
|
+
/** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
|
|
2757
|
+
* teardown that outruns the machine's close notification). */
|
|
2758
|
+
restoreOpener() {
|
|
2759
|
+
const opener = this.opener;
|
|
2760
|
+
this.opener = null;
|
|
2761
|
+
if (opener?.isConnected) opener.focus({ preventScroll: true });
|
|
2762
|
+
}
|
|
2763
|
+
announce(open2) {
|
|
2764
|
+
const opener = this.opener === this.panel.ownerDocument.body ? null : this.opener;
|
|
2765
|
+
announceSurface(this.panel, open2, "drawer", opener);
|
|
2766
|
+
}
|
|
2700
2767
|
get open() {
|
|
2701
2768
|
return this.ctrl.connect(this.handle.service).open;
|
|
2702
2769
|
}
|
|
2703
2770
|
show() {
|
|
2771
|
+
this.opener = this.panel.ownerDocument.activeElement;
|
|
2704
2772
|
this.ctrl.connect(this.handle.service).setOpen(true);
|
|
2705
2773
|
}
|
|
2706
2774
|
hide() {
|
|
@@ -2708,6 +2776,11 @@ var Drawer = class {
|
|
|
2708
2776
|
}
|
|
2709
2777
|
destroy() {
|
|
2710
2778
|
this.handle.stop();
|
|
2779
|
+
if (this.wasOpen) {
|
|
2780
|
+
this.wasOpen = false;
|
|
2781
|
+
this.announce(false);
|
|
2782
|
+
}
|
|
2783
|
+
this.restoreOpener();
|
|
2711
2784
|
this.backdrop.remove();
|
|
2712
2785
|
this.positioner.remove();
|
|
2713
2786
|
}
|
|
@@ -4998,6 +5071,7 @@ var LayoutPicker = class {
|
|
|
4998
5071
|
const panel = doc.createElement("div");
|
|
4999
5072
|
panel.className = "vela-lp";
|
|
5000
5073
|
this.layer.appendChild(panel);
|
|
5074
|
+
this.panel = panel;
|
|
5001
5075
|
const cols = doc.createElement("div");
|
|
5002
5076
|
cols.className = "vela-lp-cols";
|
|
5003
5077
|
panel.appendChild(cols);
|
|
@@ -5079,10 +5153,12 @@ var LayoutPicker = class {
|
|
|
5079
5153
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
5080
5154
|
this.doc.addEventListener("keydown", this.onDocKeydown, true);
|
|
5081
5155
|
this.opts.onOpenChange?.(true);
|
|
5156
|
+
if (this.isOpen) announceSurface(this.panel, true, "popover", this.opts.trigger);
|
|
5082
5157
|
}
|
|
5083
5158
|
close() {
|
|
5084
5159
|
if (!this.isOpen) return;
|
|
5085
5160
|
this.isOpen = false;
|
|
5161
|
+
announceSurface(this.panel, false, "popover", this.opts.trigger);
|
|
5086
5162
|
this.layer.style.display = "none";
|
|
5087
5163
|
this.opts.trigger.setAttribute("aria-expanded", "false");
|
|
5088
5164
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -5323,10 +5399,12 @@ function legendActions() {
|
|
|
5323
5399
|
}
|
|
5324
5400
|
function legendActionsProviderFor(chart, context) {
|
|
5325
5401
|
return (indicatorId) => {
|
|
5326
|
-
const
|
|
5402
|
+
const find = () => chart.indicators().find((h) => h.id === indicatorId);
|
|
5403
|
+
const infoOf = (h) => ({ id: h.id, title: h.title, ...h.source !== void 0 ? { source: h.source } : {} });
|
|
5404
|
+
const handle = find();
|
|
5327
5405
|
if (!handle) return [];
|
|
5328
|
-
const info =
|
|
5329
|
-
return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(),
|
|
5406
|
+
const info = infoOf(handle);
|
|
5407
|
+
return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(), infoOf(find() ?? handle)) }));
|
|
5330
5408
|
};
|
|
5331
5409
|
}
|
|
5332
5410
|
var calloutRegistry = /* @__PURE__ */ new Map();
|
|
@@ -9543,6 +9621,10 @@ var FibRatios = class extends Drawing {
|
|
|
9543
9621
|
|
|
9544
9622
|
// src/core/drawings/types/FibLevels.ts
|
|
9545
9623
|
var FibLevels = class extends FibRatios {
|
|
9624
|
+
/** Price of a level for the two anchor prices; subclasses override to change which anchor ratio 0 sits on. */
|
|
9625
|
+
levelPrice(ratio, p1, p2) {
|
|
9626
|
+
return p1 + ratio * (p2 - p1);
|
|
9627
|
+
}
|
|
9546
9628
|
/** Per-level pixel line + price for the ENABLED levels, spanning the anchors' time range. */
|
|
9547
9629
|
levelLines(proj) {
|
|
9548
9630
|
const a = this.anchors[0];
|
|
@@ -9552,11 +9634,10 @@ var FibLevels = class extends FibRatios {
|
|
|
9552
9634
|
const xb = proj.xOf(b.time);
|
|
9553
9635
|
const x1 = Math.min(xa, xb);
|
|
9554
9636
|
const x2 = Math.max(xa, xb);
|
|
9555
|
-
const delta = b.price - a.price;
|
|
9556
9637
|
const out = [];
|
|
9557
9638
|
for (const lv of this.levels) {
|
|
9558
9639
|
if (!lv.enabled) continue;
|
|
9559
|
-
const price =
|
|
9640
|
+
const price = this.levelPrice(lv.ratio, a.price, b.price);
|
|
9560
9641
|
const y = proj.yOf(price, this.paneId);
|
|
9561
9642
|
if (y == null) continue;
|
|
9562
9643
|
out.push({ ratio: lv.ratio, color: lv.color, label: lv.label, price, x1, x2, y });
|
|
@@ -9596,8 +9677,7 @@ var FibLevels = class extends FibRatios {
|
|
|
9596
9677
|
const a = this.anchors[0];
|
|
9597
9678
|
const b = this.anchors[1];
|
|
9598
9679
|
if (!a || !b) return null;
|
|
9599
|
-
const
|
|
9600
|
-
const prices = this.levels.filter((l) => l.enabled).map((l) => a.price + l.ratio * delta);
|
|
9680
|
+
const prices = this.levels.filter((l) => l.enabled).map((l) => this.levelPrice(l.ratio, a.price, b.price));
|
|
9601
9681
|
if (prices.length === 0) return null;
|
|
9602
9682
|
return { min: Math.min(...prices), max: Math.max(...prices) };
|
|
9603
9683
|
}
|
|
@@ -9683,13 +9763,24 @@ function cycleLevels(count, enabledCount = count) {
|
|
|
9683
9763
|
// src/core/drawings/types/FibRetracement.ts
|
|
9684
9764
|
var LEVELS = fibLevels([0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]);
|
|
9685
9765
|
var FibRetracement = class extends FibLevels {
|
|
9686
|
-
constructor() {
|
|
9687
|
-
super(
|
|
9766
|
+
constructor(init) {
|
|
9767
|
+
super(init);
|
|
9688
9768
|
this.type = "fibretracement";
|
|
9769
|
+
if (this.reverse === void 0) this.reverse = false;
|
|
9689
9770
|
}
|
|
9690
9771
|
defaultLevels() {
|
|
9691
9772
|
return LEVELS;
|
|
9692
9773
|
}
|
|
9774
|
+
levelPrice(ratio, p1, p2) {
|
|
9775
|
+
return this.reverse ? p1 + ratio * (p2 - p1) : p2 + ratio * (p1 - p2);
|
|
9776
|
+
}
|
|
9777
|
+
writeProps() {
|
|
9778
|
+
return { ...super.writeProps(), reverse: this.reverse };
|
|
9779
|
+
}
|
|
9780
|
+
readProps(props) {
|
|
9781
|
+
super.readProps(props);
|
|
9782
|
+
this.reverse = typeof props.reverse === "boolean" ? props.reverse : true;
|
|
9783
|
+
}
|
|
9693
9784
|
};
|
|
9694
9785
|
|
|
9695
9786
|
// src/core/drawings/types/FibExtension.ts
|
|
@@ -12816,6 +12907,32 @@ var CSS5 = `
|
|
|
12816
12907
|
.vela-panel-pin .vela-icon { width: 15px; height: 15px; }
|
|
12817
12908
|
.vela-panel-pin:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12818
12909
|
.vela-panel-pin[data-on='1'] { color: var(--vela-fg-bright); }
|
|
12910
|
+
.vela-panel-max {
|
|
12911
|
+
all: unset;
|
|
12912
|
+
cursor: pointer;
|
|
12913
|
+
width: 26px;
|
|
12914
|
+
height: 26px;
|
|
12915
|
+
flex: none;
|
|
12916
|
+
display: inline-flex;
|
|
12917
|
+
align-items: center;
|
|
12918
|
+
justify-content: center;
|
|
12919
|
+
border-radius: 4px;
|
|
12920
|
+
color: var(--vela-fg-muted);
|
|
12921
|
+
}
|
|
12922
|
+
.vela-panel-max .vela-icon { width: 15px; height: 15px; }
|
|
12923
|
+
.vela-panel-max:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12924
|
+
/* MAXIMIZED: the panel covers the whole dock host \u2014 every chart of the shell \u2014 whatever
|
|
12925
|
+
its placement; the width handle and the pin have nothing to do meanwhile. */
|
|
12926
|
+
.vela-panel[data-maximized] {
|
|
12927
|
+
position: absolute;
|
|
12928
|
+
inset: 0;
|
|
12929
|
+
width: auto;
|
|
12930
|
+
max-width: none;
|
|
12931
|
+
z-index: 26;
|
|
12932
|
+
border-left: none;
|
|
12933
|
+
}
|
|
12934
|
+
.vela-panel[data-maximized] .vela-panel-resizer,
|
|
12935
|
+
.vela-panel[data-maximized] .vela-panel-pin { display: none; }
|
|
12819
12936
|
.vela-panel-body { flex: 1; overflow: auto; padding: 8px; }
|
|
12820
12937
|
.vela-panel-body::-webkit-scrollbar { width: 8px; }
|
|
12821
12938
|
.vela-panel-body::-webkit-scrollbar-thumb { background: var(--vela-scroll); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
|
|
@@ -12868,6 +12985,7 @@ var CSS5 = `
|
|
|
12868
12985
|
border-left: none;
|
|
12869
12986
|
}
|
|
12870
12987
|
[data-layout='mobile'] .vela-panel-resizer { display: none; }
|
|
12988
|
+
[data-layout='mobile'] .vela-panel-max { display: none; }
|
|
12871
12989
|
`;
|
|
12872
12990
|
function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_MAX_WIDTH) {
|
|
12873
12991
|
const lo = Number.isFinite(min) && min > 0 ? min : DEFAULT_PANEL_MIN_WIDTH;
|
|
@@ -12891,6 +13009,10 @@ var SidePanel = class {
|
|
|
12891
13009
|
* never on a programmatic {@link setOverlay}, for the same reason as widths. */
|
|
12892
13010
|
this.onPlacementChange = null;
|
|
12893
13011
|
this.pin = null;
|
|
13012
|
+
this.maxButton = null;
|
|
13013
|
+
this.maximizedOn = false;
|
|
13014
|
+
/** Set while the close is announced, so a listener's own close does not re-enter. */
|
|
13015
|
+
this.closing = false;
|
|
12894
13016
|
const doc = host.ownerDocument;
|
|
12895
13017
|
injectStyles(STYLE_ID4, CSS5, doc);
|
|
12896
13018
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
@@ -12925,7 +13047,13 @@ var SidePanel = class {
|
|
|
12925
13047
|
});
|
|
12926
13048
|
this.refreshPin();
|
|
12927
13049
|
}
|
|
12928
|
-
|
|
13050
|
+
if (opts.maximizable) {
|
|
13051
|
+
this.maxButton = doc.createElement("button");
|
|
13052
|
+
this.maxButton.className = "vela-panel-max";
|
|
13053
|
+
this.maxButton.addEventListener("click", () => this.setMaximized(!this.maximizedOn));
|
|
13054
|
+
this.refreshMaxButton();
|
|
13055
|
+
}
|
|
13056
|
+
header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], ...this.maxButton ? [this.maxButton] : [], close);
|
|
12929
13057
|
this.body = doc.createElement("div");
|
|
12930
13058
|
this.body.className = "vela-panel-body";
|
|
12931
13059
|
this.el.append(header, this.body);
|
|
@@ -12937,9 +13065,37 @@ var SidePanel = class {
|
|
|
12937
13065
|
}
|
|
12938
13066
|
/** Open/close the panel — a bare call flips it. */
|
|
12939
13067
|
toggle(open2 = this.el.hidden) {
|
|
12940
|
-
if (open2 === !this.el.hidden) return;
|
|
13068
|
+
if (open2 === !this.el.hidden || !open2 && this.closing) return;
|
|
13069
|
+
if (!open2) {
|
|
13070
|
+
this.closing = true;
|
|
13071
|
+
announceSurface(this.el, false, "panel");
|
|
13072
|
+
this.closing = false;
|
|
13073
|
+
}
|
|
12941
13074
|
this.el.hidden = !open2;
|
|
13075
|
+
if (!open2) this.setMaximized(false);
|
|
12942
13076
|
this.onOpenChange?.(open2);
|
|
13077
|
+
if (open2 && !this.el.hidden) announceSurface(this.el, true, "panel");
|
|
13078
|
+
}
|
|
13079
|
+
/** Whether the panel covers every chart right now. */
|
|
13080
|
+
get maximized() {
|
|
13081
|
+
return this.maximizedOn;
|
|
13082
|
+
}
|
|
13083
|
+
/** Maximize the panel over the shell's charts, or restore it. A no-op on a panel not
|
|
13084
|
+
* declared `maximizable`. */
|
|
13085
|
+
setMaximized(maximized) {
|
|
13086
|
+
if (!this.maxButton || maximized === this.maximizedOn) return;
|
|
13087
|
+
this.maximizedOn = maximized;
|
|
13088
|
+
if (maximized) this.el.dataset.maximized = "1";
|
|
13089
|
+
else delete this.el.dataset.maximized;
|
|
13090
|
+
this.refreshMaxButton();
|
|
13091
|
+
}
|
|
13092
|
+
refreshMaxButton() {
|
|
13093
|
+
if (!this.maxButton) return;
|
|
13094
|
+
const doc = this.maxButton.ownerDocument;
|
|
13095
|
+
this.maxButton.replaceChildren(iconEl(this.maximizedOn ? "restore" : "maximize", doc));
|
|
13096
|
+
this.maxButton.title = this.maximizedOn ? "Restore" : "Maximize";
|
|
13097
|
+
this.maxButton.setAttribute("aria-label", this.maxButton.title);
|
|
13098
|
+
this.maxButton.setAttribute("aria-pressed", this.maximizedOn ? "true" : "false");
|
|
12943
13099
|
}
|
|
12944
13100
|
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
12945
13101
|
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
@@ -12991,6 +13147,7 @@ var SidePanel = class {
|
|
|
12991
13147
|
this.pin.setAttribute("aria-pressed", pinned ? "true" : "false");
|
|
12992
13148
|
}
|
|
12993
13149
|
destroy() {
|
|
13150
|
+
if (this.open) announceSurface(this.el, false, "panel");
|
|
12994
13151
|
this.el.remove();
|
|
12995
13152
|
}
|
|
12996
13153
|
/** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
|
|
@@ -14407,19 +14564,22 @@ var PanelDock = class {
|
|
|
14407
14564
|
/**
|
|
14408
14565
|
* (Re)build the CONTRIBUTED panels from the registry — call once after the built-ins, and
|
|
14409
14566
|
* again on `refreshActions()` so a late registration appears. Contributed panels that are
|
|
14410
|
-
* gone from the registry are dropped
|
|
14411
|
-
* descriptor
|
|
14567
|
+
* gone from the registry are dropped and a REPLACED descriptor is rebuilt; a panel whose
|
|
14568
|
+
* descriptor is unchanged stays mounted as it is, so its content and state survive.
|
|
14412
14569
|
*/
|
|
14413
14570
|
refresh() {
|
|
14414
14571
|
const openBefore = this.openId;
|
|
14415
|
-
|
|
14416
|
-
for (const
|
|
14572
|
+
const registered = sidePanels();
|
|
14573
|
+
for (const entry of [...this.entries]) if (entry.contributed && !registered.includes(entry.desc)) this.drop(entry);
|
|
14574
|
+
for (const desc of registered) {
|
|
14575
|
+
if (this.entries.some((e) => e.desc === desc)) continue;
|
|
14417
14576
|
const panel = new SidePanel(this.host, desc.title, `vela-panel-${desc.id}`, {
|
|
14418
14577
|
width: desc.width,
|
|
14419
14578
|
resizable: desc.resizable,
|
|
14420
14579
|
minWidth: desc.minWidth,
|
|
14421
14580
|
maxWidth: desc.maxWidth,
|
|
14422
|
-
overlay: desc.overlay
|
|
14581
|
+
overlay: desc.overlay,
|
|
14582
|
+
maximizable: desc.maximizable
|
|
14423
14583
|
});
|
|
14424
14584
|
const entry = {
|
|
14425
14585
|
id: desc.id,
|
|
@@ -14427,10 +14587,19 @@ var PanelDock = class {
|
|
|
14427
14587
|
icon: desc.icon,
|
|
14428
14588
|
order: desc.order ?? DEFAULT_PANEL_ORDER,
|
|
14429
14589
|
panel,
|
|
14430
|
-
contributed: true
|
|
14590
|
+
contributed: true,
|
|
14591
|
+
desc
|
|
14431
14592
|
};
|
|
14432
14593
|
try {
|
|
14433
|
-
|
|
14594
|
+
const header = {
|
|
14595
|
+
slot: panel.headerSlot,
|
|
14596
|
+
setTitle: (t) => panel.setTitle(t),
|
|
14597
|
+
get maximized() {
|
|
14598
|
+
return panel.maximized;
|
|
14599
|
+
},
|
|
14600
|
+
setMaximized: (on) => panel.setMaximized(on)
|
|
14601
|
+
};
|
|
14602
|
+
entry.handle = desc.mount(this.deps.context(), panel.content, header) ?? void 0;
|
|
14434
14603
|
if (this.chart) entry.handle?.onChart?.(this.chart);
|
|
14435
14604
|
} catch (err) {
|
|
14436
14605
|
console.warn(`[vela] side panel "${desc.id}" failed to mount`, err);
|
|
@@ -18201,6 +18370,7 @@ var DrawingToolbar = class {
|
|
|
18201
18370
|
);
|
|
18202
18371
|
}
|
|
18203
18372
|
}
|
|
18373
|
+
announceSurface(fly, true, "menu", cell);
|
|
18204
18374
|
}
|
|
18205
18375
|
openMagnetFlyout() {
|
|
18206
18376
|
const cell = this.magnetCell;
|
|
@@ -18224,6 +18394,7 @@ var DrawingToolbar = class {
|
|
|
18224
18394
|
})
|
|
18225
18395
|
);
|
|
18226
18396
|
}
|
|
18397
|
+
announceSurface(fly, true, "menu", cell);
|
|
18227
18398
|
}
|
|
18228
18399
|
flyoutHeader(text) {
|
|
18229
18400
|
const t = this.theme;
|
|
@@ -18292,9 +18463,11 @@ var DrawingToolbar = class {
|
|
|
18292
18463
|
}
|
|
18293
18464
|
closeFlyout() {
|
|
18294
18465
|
this.starEls.clear();
|
|
18295
|
-
|
|
18296
|
-
|
|
18466
|
+
const fly = this.flyout;
|
|
18467
|
+
if (fly) {
|
|
18297
18468
|
this.flyout = null;
|
|
18469
|
+
announceSurface(fly, false, "menu", this.flyoutCell);
|
|
18470
|
+
fly.remove();
|
|
18298
18471
|
this.flyoutCell?.classList.remove("vela-open");
|
|
18299
18472
|
this.flyoutCell = null;
|
|
18300
18473
|
this.flyoutOwnerId = null;
|
|
@@ -18949,7 +19122,7 @@ var IndicatorHandleImpl = class {
|
|
|
18949
19122
|
this.controller.applyProps(this.id, values);
|
|
18950
19123
|
}
|
|
18951
19124
|
updateCode(source) {
|
|
18952
|
-
this.controller.updateCode(this.id, source);
|
|
19125
|
+
return this.controller.updateCode(this.id, source);
|
|
18953
19126
|
}
|
|
18954
19127
|
setVisible(visible) {
|
|
18955
19128
|
this.controller.setVisible(this.id, visible);
|
|
@@ -20812,20 +20985,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20812
20985
|
/**
|
|
20813
20986
|
* IndicatorController: replace a SCRIPT indicator's source in place (see
|
|
20814
20987
|
* {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
|
|
20815
|
-
* computing until the new source has compiled
|
|
20816
|
-
*
|
|
20988
|
+
* computing until the new source has compiled; then the new code runs on TRIAL until
|
|
20989
|
+
* its first model lands, and a failure before that puts the previous code back.
|
|
20990
|
+
* Natives have no script — warn and leave them alone.
|
|
20817
20991
|
*/
|
|
20818
20992
|
updateCode(id, source) {
|
|
20819
20993
|
const record = this.registry.get(id);
|
|
20820
20994
|
const handle = this.handles.get(id);
|
|
20821
|
-
if (!record || !handle) return;
|
|
20995
|
+
if (!record || !handle) return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator is no longer on the chart.`) });
|
|
20822
20996
|
if (record.native) {
|
|
20823
20997
|
console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
|
|
20824
|
-
return;
|
|
20998
|
+
return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`) });
|
|
20825
20999
|
}
|
|
20826
|
-
|
|
20827
|
-
record.
|
|
20828
|
-
|
|
21000
|
+
const busy = record.pendingSource !== void 0 || record.codeTrial !== void 0;
|
|
21001
|
+
if (!busy && source === record.source) return Promise.resolve({ ok: true, error: null });
|
|
21002
|
+
return new Promise((resolve) => {
|
|
21003
|
+
(record.codeWaiters ?? (record.codeWaiters = [])).push(resolve);
|
|
21004
|
+
if (record.pendingSource === source || record.pendingSource === void 0 && record.codeTrial && record.source === source) return;
|
|
21005
|
+
record.pendingSource = source;
|
|
21006
|
+
void this.swapSource(id, source, handle);
|
|
21007
|
+
});
|
|
20829
21008
|
}
|
|
20830
21009
|
async swapSource(id, source, handle) {
|
|
20831
21010
|
const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
|
|
@@ -20836,26 +21015,41 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20836
21015
|
const record2 = this.registry.get(id);
|
|
20837
21016
|
if (record2?.pendingSource === source) {
|
|
20838
21017
|
record2.pendingSource = void 0;
|
|
20839
|
-
this.
|
|
21018
|
+
this.rejectCode(id, record2, handle, err);
|
|
20840
21019
|
}
|
|
20841
21020
|
return;
|
|
20842
21021
|
}
|
|
20843
21022
|
const record = this.registry.get(id);
|
|
20844
21023
|
if (!record || record.pendingSource !== source) return;
|
|
20845
21024
|
record.pendingSource = void 0;
|
|
21025
|
+
const previous = record.codeTrial?.previous ?? {
|
|
21026
|
+
source: record.source,
|
|
21027
|
+
engine: record.engine,
|
|
21028
|
+
prepared: record.prepared,
|
|
21029
|
+
inputValues: record.inputValues,
|
|
21030
|
+
propValues: record.propValues,
|
|
21031
|
+
inputs: [...handle.inputs],
|
|
21032
|
+
props: [...handle.props]
|
|
21033
|
+
};
|
|
21034
|
+
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues, record.prepared?.inputs);
|
|
21035
|
+
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues, record.prepared?.props);
|
|
20846
21036
|
record.source = source;
|
|
20847
21037
|
record.engine = engine;
|
|
20848
21038
|
record.prepared = prepared;
|
|
20849
21039
|
handle.setSource(source);
|
|
20850
|
-
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
|
|
20851
|
-
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
|
|
20852
21040
|
handle.setSchema(prepared.inputs);
|
|
20853
21041
|
handle.setPropsSchema(prepared.props ?? []);
|
|
20854
21042
|
record.session?.stop();
|
|
20855
21043
|
record.session = void 0;
|
|
20856
21044
|
record.pendingStructural = true;
|
|
20857
21045
|
record.pendingCause = "code";
|
|
20858
|
-
if (record.hidden)
|
|
21046
|
+
if (record.hidden) {
|
|
21047
|
+
record.codeTrial = void 0;
|
|
21048
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
21049
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21050
|
+
return;
|
|
21051
|
+
}
|
|
21052
|
+
record.codeTrial = { previous };
|
|
20859
21053
|
if (record.renderHandle) {
|
|
20860
21054
|
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
20861
21055
|
this.setLoading(record, true);
|
|
@@ -20863,6 +21057,46 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20863
21057
|
this.mountLoadingPlaceholder(id, record);
|
|
20864
21058
|
}
|
|
20865
21059
|
this.executeIndicator(id, handle);
|
|
21060
|
+
if (this.bars.length === 0 && record.codeTrial) {
|
|
21061
|
+
record.codeTrial = void 0;
|
|
21062
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21063
|
+
}
|
|
21064
|
+
}
|
|
21065
|
+
/** Resolve every `updateCode` caller waiting on this record's chain of edits. */
|
|
21066
|
+
settleCode(record, result) {
|
|
21067
|
+
const waiters = record.codeWaiters;
|
|
21068
|
+
record.codeWaiters = void 0;
|
|
21069
|
+
if (waiters) for (const resolve of waiters) resolve(result);
|
|
21070
|
+
}
|
|
21071
|
+
/**
|
|
21072
|
+
* An update failed — at prepare, or on trial before its first model. A trial puts the
|
|
21073
|
+
* previous code back (source, engine, prepared script, values, schema) and restarts it;
|
|
21074
|
+
* its visuals were never dropped, so the chart keeps painting throughout. Then the
|
|
21075
|
+
* failure reports the usual way and every waiting caller learns it.
|
|
21076
|
+
*/
|
|
21077
|
+
rejectCode(id, record, handle, err) {
|
|
21078
|
+
const trial = record.codeTrial;
|
|
21079
|
+
record.codeTrial = void 0;
|
|
21080
|
+
this.fail(id, handle, err);
|
|
21081
|
+
if (trial) {
|
|
21082
|
+
const p = trial.previous;
|
|
21083
|
+
record.session?.stop();
|
|
21084
|
+
record.session = void 0;
|
|
21085
|
+
record.source = p.source;
|
|
21086
|
+
record.engine = p.engine;
|
|
21087
|
+
record.prepared = p.prepared;
|
|
21088
|
+
record.inputValues = p.inputValues;
|
|
21089
|
+
record.propValues = p.propValues;
|
|
21090
|
+
handle.setSource(p.source);
|
|
21091
|
+
handle.setSchema(p.inputs);
|
|
21092
|
+
handle.setPropsSchema(p.props);
|
|
21093
|
+
record.pendingStructural = true;
|
|
21094
|
+
record.pendingCause = void 0;
|
|
21095
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
21096
|
+
if (!record.hidden) this.executeIndicator(id, handle);
|
|
21097
|
+
}
|
|
21098
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
21099
|
+
this.settleCode(record, { ok: false, error });
|
|
20866
21100
|
}
|
|
20867
21101
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
20868
21102
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
@@ -20877,6 +21111,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20877
21111
|
removeIndicator(id) {
|
|
20878
21112
|
const record = this.registry.remove(id);
|
|
20879
21113
|
if (record?.native?.type === "volume") this.volumeOptedOut = true;
|
|
21114
|
+
if (record) this.settleCode(record, { ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator was removed.`) });
|
|
20880
21115
|
record?.session?.stop();
|
|
20881
21116
|
if (record?.native?.started) record.native.instance.stop();
|
|
20882
21117
|
this.handles.delete(id);
|
|
@@ -20904,6 +21139,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20904
21139
|
if (!visible) {
|
|
20905
21140
|
record.session?.stop();
|
|
20906
21141
|
record.session = void 0;
|
|
21142
|
+
if (record.codeTrial) {
|
|
21143
|
+
record.codeTrial = void 0;
|
|
21144
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21145
|
+
}
|
|
20907
21146
|
if (record.native?.started) record.native.instance.suspend();
|
|
20908
21147
|
if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
|
|
20909
21148
|
else if (record.native) this.mountHiddenNativeRow(id, record);
|
|
@@ -20991,6 +21230,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20991
21230
|
this.viewportUnsub?.();
|
|
20992
21231
|
this.paneActionUnsub?.();
|
|
20993
21232
|
for (const record of this.registry.all()) {
|
|
21233
|
+
this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
|
|
20994
21234
|
record.session?.stop();
|
|
20995
21235
|
if (record.native?.started) record.native.instance.stop();
|
|
20996
21236
|
}
|
|
@@ -21059,7 +21299,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21059
21299
|
const record = this.registry.get(id);
|
|
21060
21300
|
if (!record || !record.engine || !record.prepared || record.hidden) return;
|
|
21061
21301
|
const mode = this.config.live && !record.prepared.reactsToViewport && record.engine.capabilities.streaming ? "live" : "static";
|
|
21062
|
-
|
|
21302
|
+
const prepared = record.prepared;
|
|
21303
|
+
const session = record.engine.execute(
|
|
21063
21304
|
{
|
|
21064
21305
|
prepared: record.prepared,
|
|
21065
21306
|
market: this.market(),
|
|
@@ -21080,6 +21321,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21080
21321
|
const cause = record.pendingCause ?? "history";
|
|
21081
21322
|
if (!this.applyModel(id, model)) return;
|
|
21082
21323
|
record.pendingCause = void 0;
|
|
21324
|
+
if (record.codeTrial) {
|
|
21325
|
+
record.codeTrial = void 0;
|
|
21326
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21327
|
+
}
|
|
21083
21328
|
this.emitContextChanged(id);
|
|
21084
21329
|
this.emitScriptRun(id, cause, first);
|
|
21085
21330
|
},
|
|
@@ -21090,9 +21335,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21090
21335
|
handle.emit("alert", { id: a.id, message: a.message, title: a.title, time: a.time });
|
|
21091
21336
|
},
|
|
21092
21337
|
onWarning: (w) => this.events.emit("warning", w),
|
|
21093
|
-
onError: (err) =>
|
|
21338
|
+
onError: (err) => {
|
|
21339
|
+
if (record.codeTrial) this.rejectCode(id, record, handle, err);
|
|
21340
|
+
else this.fail(id, handle, err);
|
|
21341
|
+
}
|
|
21094
21342
|
}
|
|
21095
21343
|
);
|
|
21344
|
+
if (record.prepared !== prepared) {
|
|
21345
|
+
session.stop();
|
|
21346
|
+
return;
|
|
21347
|
+
}
|
|
21348
|
+
record.session = session;
|
|
21096
21349
|
this.emitContextChanged(id);
|
|
21097
21350
|
}
|
|
21098
21351
|
/**
|
|
@@ -21593,7 +21846,7 @@ function yieldToPaint() {
|
|
|
21593
21846
|
}
|
|
21594
21847
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
21595
21848
|
}
|
|
21596
|
-
function valuesOnSchema(schema, previous) {
|
|
21849
|
+
function valuesOnSchema(schema, previous, oldSchema = []) {
|
|
21597
21850
|
const out = {};
|
|
21598
21851
|
const declared = /* @__PURE__ */ new Set();
|
|
21599
21852
|
for (const s of schema) {
|
|
@@ -21601,9 +21854,22 @@ function valuesOnSchema(schema, previous) {
|
|
|
21601
21854
|
declared.add(s.key);
|
|
21602
21855
|
declared.add(s.title);
|
|
21603
21856
|
}
|
|
21604
|
-
|
|
21857
|
+
const oldDefaults = /* @__PURE__ */ new Map();
|
|
21858
|
+
for (const s of oldSchema) {
|
|
21859
|
+
oldDefaults.set(s.key, s.defval);
|
|
21860
|
+
if (!oldDefaults.has(s.title)) oldDefaults.set(s.title, s.defval);
|
|
21861
|
+
}
|
|
21862
|
+
for (const [k, v] of Object.entries(previous)) {
|
|
21863
|
+
if (!declared.has(k)) continue;
|
|
21864
|
+
if (oldDefaults.has(k) && sameInputValue(oldDefaults.get(k), v)) continue;
|
|
21865
|
+
out[k] = v;
|
|
21866
|
+
}
|
|
21605
21867
|
return out;
|
|
21606
21868
|
}
|
|
21869
|
+
function sameInputValue(a, b) {
|
|
21870
|
+
if (a === b) return true;
|
|
21871
|
+
return typeof a === "object" && a !== null && typeof b === "object" && b !== null && JSON.stringify(a) === JSON.stringify(b);
|
|
21872
|
+
}
|
|
21607
21873
|
function blankedModel(model) {
|
|
21608
21874
|
return {
|
|
21609
21875
|
...model,
|
|
@@ -24533,15 +24799,23 @@ var Batch = class {
|
|
|
24533
24799
|
this.seg(x + w, y + h, x, y + h, lineWidth, c);
|
|
24534
24800
|
this.seg(x, y + h, x, y, lineWidth, c);
|
|
24535
24801
|
}
|
|
24536
|
-
/**
|
|
24802
|
+
/**
|
|
24803
|
+
* Filled circle (triangle fan), feathered like a line: the rim is padded by {@link AA_PAD} and
|
|
24804
|
+
* edgeDist runs from 0 at the center to the padded radius at the rim, with edgeHalf = r. The
|
|
24805
|
+
* distance grows linearly along each spoke, so the shader's line feather gives a round soft edge.
|
|
24806
|
+
*/
|
|
24537
24807
|
circle(cx, cy, r, c, segments = 16) {
|
|
24538
|
-
|
|
24808
|
+
const ext = r + AA_PAD;
|
|
24809
|
+
let px = cx + ext;
|
|
24539
24810
|
let py = cy;
|
|
24811
|
+
this.ensure(segments * 3);
|
|
24540
24812
|
for (let i = 1; i <= segments; i += 1) {
|
|
24541
24813
|
const a = i / segments * Math.PI * 2;
|
|
24542
|
-
const nx = cx + Math.cos(a) *
|
|
24543
|
-
const ny = cy + Math.sin(a) *
|
|
24544
|
-
this.
|
|
24814
|
+
const nx = cx + Math.cos(a) * ext;
|
|
24815
|
+
const ny = cy + Math.sin(a) * ext;
|
|
24816
|
+
this.v(cx, cy, c, 0, r);
|
|
24817
|
+
this.v(px, py, c, ext, r);
|
|
24818
|
+
this.v(nx, ny, c, ext, r);
|
|
24545
24819
|
px = nx;
|
|
24546
24820
|
py = ny;
|
|
24547
24821
|
}
|
|
@@ -24722,7 +24996,7 @@ var WebGL2Backend = class {
|
|
|
24722
24996
|
}
|
|
24723
24997
|
mount(canvas) {
|
|
24724
24998
|
this.canvas = canvas;
|
|
24725
|
-
this.gl = canvas.getContext("webgl2", { alpha: true, antialias:
|
|
24999
|
+
this.gl = canvas.getContext("webgl2", { alpha: true, antialias: false, depth: false, stencil: false, premultipliedAlpha: true });
|
|
24726
25000
|
if (!this.gl) return;
|
|
24727
25001
|
if (!this.initGL()) {
|
|
24728
25002
|
this.gl = null;
|
|
@@ -26174,7 +26448,7 @@ var InputController = class {
|
|
|
26174
26448
|
this.deps.drawingsPointerUp?.(x, y, this.snapMode(e));
|
|
26175
26449
|
} else if (tapRelease && this.region === "data") {
|
|
26176
26450
|
this.deps.onClick(x, y);
|
|
26177
|
-
} else if (this.dragging && this.region === "data") {
|
|
26451
|
+
} else if (this.dragging && this.region === "data" && wasTouch) {
|
|
26178
26452
|
const stale = e.timeStamp - this.lastT > FLING_STALE_MS;
|
|
26179
26453
|
if (!stale && Math.abs(this.vx) > FLING_MIN_SPEED) {
|
|
26180
26454
|
const pitch = this.deps.getCoords().pxPerBar();
|
|
@@ -32540,6 +32814,12 @@ var DrawingInteraction = class {
|
|
|
32540
32814
|
activeDragId() {
|
|
32541
32815
|
return this.state.kind === "pressed" && this.state.moved ? this.state.id : null;
|
|
32542
32816
|
}
|
|
32817
|
+
/** The store drawings a live drag is moving — the pressed one and its riders — or none
|
|
32818
|
+
* before the drag passes the slop. A Ctrl-drag moves copies, so its sources stay put. */
|
|
32819
|
+
movingIds() {
|
|
32820
|
+
if (this.state.kind !== "pressed" || !this.state.moved || this.state.clones) return /* @__PURE__ */ new Set();
|
|
32821
|
+
return /* @__PURE__ */ new Set([this.state.id, ...this.state.riders.map((r) => r.id)]);
|
|
32822
|
+
}
|
|
32543
32823
|
/** The drawing under a press that has not been released yet (drag or click undecided), or null. */
|
|
32544
32824
|
pressedId() {
|
|
32545
32825
|
return this.state.kind === "pressed" ? this.state.id : null;
|
|
@@ -33198,6 +33478,17 @@ var DrawingSettingsDialog = class {
|
|
|
33198
33478
|
}
|
|
33199
33479
|
}));
|
|
33200
33480
|
}
|
|
33481
|
+
if (drawing instanceof FibRetracement) {
|
|
33482
|
+
const fib = drawing;
|
|
33483
|
+
grid.appendChild(fieldRow({
|
|
33484
|
+
label: "Reverse",
|
|
33485
|
+
bool: true,
|
|
33486
|
+
toggle: {
|
|
33487
|
+
checked: fib.reverse,
|
|
33488
|
+
onChange: (v) => actions.patch({ reverse: v })
|
|
33489
|
+
}
|
|
33490
|
+
}));
|
|
33491
|
+
}
|
|
33201
33492
|
levels.forEach((lv, i) => {
|
|
33202
33493
|
const row = document.createElement("div");
|
|
33203
33494
|
row.className = "vela-field-span";
|
|
@@ -34351,6 +34642,8 @@ var UserDrawingController = class {
|
|
|
34351
34642
|
this.drawings = [];
|
|
34352
34643
|
/** Ids painted on an interleave layer this frame — the top canvas paints only their handles. */
|
|
34353
34644
|
this.sliced = /* @__PURE__ */ new Set();
|
|
34645
|
+
/** Interleaved drawings mid-drag: painted on the top canvas, left out of their slice. */
|
|
34646
|
+
this.lifted = /* @__PURE__ */ new Set();
|
|
34354
34647
|
/** Cached slice canvases, keyed `paneId|beforeZ`, reused across frames to avoid churn. */
|
|
34355
34648
|
this.sliceCache = /* @__PURE__ */ new Map();
|
|
34356
34649
|
/** Series boundaries per pane as of the last `prepareSlices` — lets a repaint between data
|
|
@@ -34438,7 +34731,7 @@ var UserDrawingController = class {
|
|
|
34438
34731
|
selectedIds: () => this.selectedIds,
|
|
34439
34732
|
emit: (i) => this.emit(i),
|
|
34440
34733
|
changed: () => {
|
|
34441
|
-
this.
|
|
34734
|
+
this.syncLift();
|
|
34442
34735
|
this.render();
|
|
34443
34736
|
},
|
|
34444
34737
|
openSettings: (id, x, y) => this.openSettingsById(id, x, y),
|
|
@@ -35064,6 +35357,20 @@ var UserDrawingController = class {
|
|
|
35064
35357
|
/** A drawing that paints inside the series stack changed (content, not hover): its pixels
|
|
35065
35358
|
* live in the backend composite, so this layer alone can't show the change — ask for a
|
|
35066
35359
|
* data frame, which re-runs `prepareSlices` before the backend composites. */
|
|
35360
|
+
/** A drawing being dragged leaves its interleave slice for the top canvas, so each pointer
|
|
35361
|
+
* move repaints one 2D layer instead of re-rasterizing and re-uploading a plot-sized slice
|
|
35362
|
+
* texture through the data frame. The slices rebuild once as the drag starts and once as it
|
|
35363
|
+
* ends (release, cancel), when the lifted set changes. */
|
|
35364
|
+
syncLift() {
|
|
35365
|
+
const moving = this.interaction.movingIds();
|
|
35366
|
+
let same = moving.size === this.lifted.size;
|
|
35367
|
+
if (same) {
|
|
35368
|
+
for (const id of moving) if (!this.lifted.has(id)) same = false;
|
|
35369
|
+
}
|
|
35370
|
+
if (same) return;
|
|
35371
|
+
this.lifted = new Set(moving);
|
|
35372
|
+
this.invalidateSlices();
|
|
35373
|
+
}
|
|
35067
35374
|
invalidateSlices() {
|
|
35068
35375
|
if (this.sliced.size > 0 || this.drawings.some((d) => this.isInterleaved(d))) this.deps.requestDataPaint();
|
|
35069
35376
|
}
|
|
@@ -35082,7 +35389,7 @@ var UserDrawingController = class {
|
|
|
35082
35389
|
const theme = this.deps.theme();
|
|
35083
35390
|
const buckets = /* @__PURE__ */ new Map();
|
|
35084
35391
|
for (const d of this.drawings) {
|
|
35085
|
-
if (!d.visible) continue;
|
|
35392
|
+
if (!d.visible || this.lifted.has(d.id)) continue;
|
|
35086
35393
|
const beforeZ = sliceKeyFor(d.zIndex, this.lastBounds.get(d.paneId) ?? []);
|
|
35087
35394
|
if (beforeZ === null) continue;
|
|
35088
35395
|
const key = `${d.paneId}|${beforeZ}`;
|
|
@@ -35134,8 +35441,9 @@ var UserDrawingController = class {
|
|
|
35134
35441
|
mutedLabel: edited instanceof TextLabel ? edited.id : null
|
|
35135
35442
|
};
|
|
35136
35443
|
this.painter.seriesLook = this.deps.seriesLook();
|
|
35137
|
-
|
|
35138
|
-
this.painter.
|
|
35444
|
+
const onTop = (d) => !this.isInterleaved(d) || this.lifted.has(d.id);
|
|
35445
|
+
this.painter.paintAll(ctx, this.drawings.filter(onTop), proj, this.deps.theme(), targets);
|
|
35446
|
+
this.painter.paintHighlights(ctx, this.drawings.filter((d) => !onTop(d)), proj, handleIdsFor(targets));
|
|
35139
35447
|
const clones = this.interaction.dragClones();
|
|
35140
35448
|
if (clones) this.painter.paintAll(ctx, clones, proj, this.deps.theme(), { selected: new Set(clones.map((c) => c.id)) });
|
|
35141
35449
|
this.layoutTextEditor();
|
|
@@ -44791,6 +45099,9 @@ var CSS21 = `
|
|
|
44791
45099
|
.vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
|
|
44792
45100
|
.vela-statusline .vela-sl-market > [hidden] { display: none !important; }
|
|
44793
45101
|
.vela-statusline .vela-sl-replay-badge, .vela-statusline .vela-sl-replay-badge svg { display: block; width: 16px; height: 16px; }
|
|
45102
|
+
/* Tabular figures: every digit has one advance, so a readout whose text changes only in
|
|
45103
|
+
* its digits keeps its width \u2014 render() skips the ladder for those (see widthKey). */
|
|
45104
|
+
.vela-statusline .vela-sl-values { font-variant-numeric: tabular-nums; }
|
|
44794
45105
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
44795
45106
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
44796
45107
|
/* The change value wears the SAME ink as the OHLC values (set inline per render) \u2014
|
|
@@ -44908,6 +45219,10 @@ var Statusline = class {
|
|
|
44908
45219
|
this.fitRO = null;
|
|
44909
45220
|
/** The ladder rung currently applied — see {@link fit}. */
|
|
44910
45221
|
this.layout = { stacked: false, level: "full" };
|
|
45222
|
+
/** The readout {@link widthKey} the applied rung was fitted to. */
|
|
45223
|
+
this.fitKey = null;
|
|
45224
|
+
/** Web fonts landing after the last fit change every width without a resize. */
|
|
45225
|
+
this.onFontsLoaded = () => this.fit();
|
|
44911
45226
|
this.onContextMenu = (e) => {
|
|
44912
45227
|
if (!this.menu || !this.menuHooks) return;
|
|
44913
45228
|
e.preventDefault();
|
|
@@ -44971,6 +45286,7 @@ var Statusline = class {
|
|
|
44971
45286
|
this.fitRO = new ResizeObserver(() => this.fit());
|
|
44972
45287
|
this.fitRO.observe(host);
|
|
44973
45288
|
}
|
|
45289
|
+
doc.fonts?.addEventListener("loadingdone", this.onFontsLoaded);
|
|
44974
45290
|
this.marketTip = new Tooltip(this.marketEl, { content: MARKET_LABELS.open, placement: "bottom" });
|
|
44975
45291
|
this.eyeTip = new Tooltip(this.eyeEl, { content: "Show chart", placement: "bottom" });
|
|
44976
45292
|
this.setMarketStatus("open");
|
|
@@ -45023,6 +45339,7 @@ var Statusline = class {
|
|
|
45023
45339
|
* (detached host, node tests) nothing overflows, so the widest rung stays.
|
|
45024
45340
|
*/
|
|
45025
45341
|
fit() {
|
|
45342
|
+
this.fitKey = this.widthKey();
|
|
45026
45343
|
this.syncParts();
|
|
45027
45344
|
const seg = segmentVisibility(this.parts, this.chartHidden);
|
|
45028
45345
|
const overflows = () => this.el.scrollWidth > this.el.clientWidth;
|
|
@@ -45179,6 +45496,7 @@ var Statusline = class {
|
|
|
45179
45496
|
this.detach();
|
|
45180
45497
|
this.fitRO?.disconnect();
|
|
45181
45498
|
this.fitRO = null;
|
|
45499
|
+
this.el.ownerDocument.fonts?.removeEventListener("loadingdone", this.onFontsLoaded);
|
|
45182
45500
|
this.el.removeEventListener("contextmenu", this.onContextMenu);
|
|
45183
45501
|
this.menu?.destroy();
|
|
45184
45502
|
this.menu = null;
|
|
@@ -45194,7 +45512,22 @@ var Statusline = class {
|
|
|
45194
45512
|
}
|
|
45195
45513
|
render() {
|
|
45196
45514
|
if (this.menuHooks) this.setChartHidden(!this.menuHooks.chartVisible());
|
|
45197
|
-
this.
|
|
45515
|
+
if (this.widthKey() === this.fitKey) this.renderValues();
|
|
45516
|
+
else this.fit();
|
|
45517
|
+
}
|
|
45518
|
+
/** Everything the readout's width depends on, at every rung: its shape, and each
|
|
45519
|
+
* value's sign, digit count and decimals (the digits themselves are tabular — see
|
|
45520
|
+
* the CSS; the grouping commas follow from the digit count). `toFixed` carries all
|
|
45521
|
+
* three at a fraction of the locale formatter's cost — this runs per pointer move.
|
|
45522
|
+
* The parts, the host's size and the fonts re-fit through their own paths. */
|
|
45523
|
+
widthKey() {
|
|
45524
|
+
const bar = this.hoverBar ?? this.lastBar;
|
|
45525
|
+
if (!bar) return this.readout;
|
|
45526
|
+
const dp = decimalsFor(bar.close);
|
|
45527
|
+
const diff = bar.close - bar.open;
|
|
45528
|
+
const text = [bar.open, bar.high, bar.low, bar.close, diff].map((v) => v.toFixed(dp));
|
|
45529
|
+
text.push((diff / bar.open * 100).toFixed(2));
|
|
45530
|
+
return `${this.readout} ${text.join(" ")}`.replace(/\d/g, "0");
|
|
45198
45531
|
}
|
|
45199
45532
|
/** Write the value readout for the current bar at the current ladder level. */
|
|
45200
45533
|
renderValues() {
|