@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/workspace.cjs
CHANGED
|
@@ -1256,6 +1256,7 @@ registerIcon("expand", S('<rect x="2.2" y="2.2" width="11.6" height="11.6" rx="1
|
|
|
1256
1256
|
registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
|
|
1257
1257
|
registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
|
|
1258
1258
|
registerIcon("maximize", S('<path d="M2.5 6V3a.5.5 0 0 1 .5-.5h3M10 2.5h3a.5.5 0 0 1 .5.5v3M13.5 10v3a.5.5 0 0 1-.5.5h-3M6 13.5H3a.5.5 0 0 1-.5-.5v-3"/>'));
|
|
1259
|
+
registerIcon("restore", S('<path d="M6 2.5v3a.5.5 0 0 1-.5.5h-3M13.5 6h-3a.5.5 0 0 1-.5-.5v-3M10 13.5v-3a.5.5 0 0 1 .5-.5h3M2.5 10h3a.5.5 0 0 1 .5.5v3"/>'));
|
|
1259
1260
|
registerIcon("restore", S('<path d="M6.2 2.5v3.7H2.5M9.8 13.5V9.8h3.7M13.5 6.2H9.8V2.5M2.5 9.8h3.7v3.7"/>'));
|
|
1260
1261
|
registerIcon("star", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>'));
|
|
1261
1262
|
registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>', 'fill="currentColor"'));
|
|
@@ -1337,6 +1338,17 @@ function nextUid(prefix) {
|
|
|
1337
1338
|
return `${prefix}-${++uid}`;
|
|
1338
1339
|
}
|
|
1339
1340
|
|
|
1341
|
+
// src/ui/surface-events.ts
|
|
1342
|
+
var SURFACE_OPEN_EVENT = "vela:surface-open";
|
|
1343
|
+
var SURFACE_CLOSE_EVENT = "vela:surface-close";
|
|
1344
|
+
function announceSurface(el, open2, kind, trigger = null) {
|
|
1345
|
+
const View = el.ownerDocument.defaultView;
|
|
1346
|
+
if (!View) return;
|
|
1347
|
+
el.dispatchEvent(
|
|
1348
|
+
new View.CustomEvent(open2 ? SURFACE_OPEN_EVENT : SURFACE_CLOSE_EVENT, { bubbles: true, composed: true, detail: { kind, trigger } })
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1340
1352
|
// src/ui/keymap.ts
|
|
1341
1353
|
var KEY_ALIASES = {
|
|
1342
1354
|
esc: "escape",
|
|
@@ -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);
|
|
@@ -5288,10 +5364,12 @@ function legendActions() {
|
|
|
5288
5364
|
}
|
|
5289
5365
|
function legendActionsProviderFor(chart, context) {
|
|
5290
5366
|
return (indicatorId) => {
|
|
5291
|
-
const
|
|
5367
|
+
const find = () => chart.indicators().find((h) => h.id === indicatorId);
|
|
5368
|
+
const infoOf = (h) => ({ id: h.id, title: h.title, ...h.source !== void 0 ? { source: h.source } : {} });
|
|
5369
|
+
const handle = find();
|
|
5292
5370
|
if (!handle) return [];
|
|
5293
|
-
const info =
|
|
5294
|
-
return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(),
|
|
5371
|
+
const info = infoOf(handle);
|
|
5372
|
+
return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(), infoOf(find() ?? handle)) }));
|
|
5295
5373
|
};
|
|
5296
5374
|
}
|
|
5297
5375
|
var calloutRegistry = /* @__PURE__ */ new Map();
|
|
@@ -9489,6 +9567,10 @@ var FibRatios = class extends Drawing {
|
|
|
9489
9567
|
|
|
9490
9568
|
// src/core/drawings/types/FibLevels.ts
|
|
9491
9569
|
var FibLevels = class extends FibRatios {
|
|
9570
|
+
/** Price of a level for the two anchor prices; subclasses override to change which anchor ratio 0 sits on. */
|
|
9571
|
+
levelPrice(ratio, p1, p2) {
|
|
9572
|
+
return p1 + ratio * (p2 - p1);
|
|
9573
|
+
}
|
|
9492
9574
|
/** Per-level pixel line + price for the ENABLED levels, spanning the anchors' time range. */
|
|
9493
9575
|
levelLines(proj) {
|
|
9494
9576
|
const a = this.anchors[0];
|
|
@@ -9498,11 +9580,10 @@ var FibLevels = class extends FibRatios {
|
|
|
9498
9580
|
const xb = proj.xOf(b.time);
|
|
9499
9581
|
const x1 = Math.min(xa, xb);
|
|
9500
9582
|
const x2 = Math.max(xa, xb);
|
|
9501
|
-
const delta = b.price - a.price;
|
|
9502
9583
|
const out = [];
|
|
9503
9584
|
for (const lv of this.levels) {
|
|
9504
9585
|
if (!lv.enabled) continue;
|
|
9505
|
-
const price =
|
|
9586
|
+
const price = this.levelPrice(lv.ratio, a.price, b.price);
|
|
9506
9587
|
const y = proj.yOf(price, this.paneId);
|
|
9507
9588
|
if (y == null) continue;
|
|
9508
9589
|
out.push({ ratio: lv.ratio, color: lv.color, label: lv.label, price, x1, x2, y });
|
|
@@ -9542,8 +9623,7 @@ var FibLevels = class extends FibRatios {
|
|
|
9542
9623
|
const a = this.anchors[0];
|
|
9543
9624
|
const b = this.anchors[1];
|
|
9544
9625
|
if (!a || !b) return null;
|
|
9545
|
-
const
|
|
9546
|
-
const prices = this.levels.filter((l) => l.enabled).map((l) => a.price + l.ratio * delta);
|
|
9626
|
+
const prices = this.levels.filter((l) => l.enabled).map((l) => this.levelPrice(l.ratio, a.price, b.price));
|
|
9547
9627
|
if (prices.length === 0) return null;
|
|
9548
9628
|
return { min: Math.min(...prices), max: Math.max(...prices) };
|
|
9549
9629
|
}
|
|
@@ -9629,13 +9709,24 @@ function cycleLevels(count, enabledCount = count) {
|
|
|
9629
9709
|
// src/core/drawings/types/FibRetracement.ts
|
|
9630
9710
|
var LEVELS = fibLevels([0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]);
|
|
9631
9711
|
var FibRetracement = class extends FibLevels {
|
|
9632
|
-
constructor() {
|
|
9633
|
-
super(
|
|
9712
|
+
constructor(init) {
|
|
9713
|
+
super(init);
|
|
9634
9714
|
this.type = "fibretracement";
|
|
9715
|
+
if (this.reverse === void 0) this.reverse = false;
|
|
9635
9716
|
}
|
|
9636
9717
|
defaultLevels() {
|
|
9637
9718
|
return LEVELS;
|
|
9638
9719
|
}
|
|
9720
|
+
levelPrice(ratio, p1, p2) {
|
|
9721
|
+
return this.reverse ? p1 + ratio * (p2 - p1) : p2 + ratio * (p1 - p2);
|
|
9722
|
+
}
|
|
9723
|
+
writeProps() {
|
|
9724
|
+
return { ...super.writeProps(), reverse: this.reverse };
|
|
9725
|
+
}
|
|
9726
|
+
readProps(props) {
|
|
9727
|
+
super.readProps(props);
|
|
9728
|
+
this.reverse = typeof props.reverse === "boolean" ? props.reverse : true;
|
|
9729
|
+
}
|
|
9639
9730
|
};
|
|
9640
9731
|
|
|
9641
9732
|
// src/core/drawings/types/FibExtension.ts
|
|
@@ -12762,6 +12853,32 @@ var CSS5 = `
|
|
|
12762
12853
|
.vela-panel-pin .vela-icon { width: 15px; height: 15px; }
|
|
12763
12854
|
.vela-panel-pin:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12764
12855
|
.vela-panel-pin[data-on='1'] { color: var(--vela-fg-bright); }
|
|
12856
|
+
.vela-panel-max {
|
|
12857
|
+
all: unset;
|
|
12858
|
+
cursor: pointer;
|
|
12859
|
+
width: 26px;
|
|
12860
|
+
height: 26px;
|
|
12861
|
+
flex: none;
|
|
12862
|
+
display: inline-flex;
|
|
12863
|
+
align-items: center;
|
|
12864
|
+
justify-content: center;
|
|
12865
|
+
border-radius: 4px;
|
|
12866
|
+
color: var(--vela-fg-muted);
|
|
12867
|
+
}
|
|
12868
|
+
.vela-panel-max .vela-icon { width: 15px; height: 15px; }
|
|
12869
|
+
.vela-panel-max:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12870
|
+
/* MAXIMIZED: the panel covers the whole dock host \u2014 every chart of the shell \u2014 whatever
|
|
12871
|
+
its placement; the width handle and the pin have nothing to do meanwhile. */
|
|
12872
|
+
.vela-panel[data-maximized] {
|
|
12873
|
+
position: absolute;
|
|
12874
|
+
inset: 0;
|
|
12875
|
+
width: auto;
|
|
12876
|
+
max-width: none;
|
|
12877
|
+
z-index: 26;
|
|
12878
|
+
border-left: none;
|
|
12879
|
+
}
|
|
12880
|
+
.vela-panel[data-maximized] .vela-panel-resizer,
|
|
12881
|
+
.vela-panel[data-maximized] .vela-panel-pin { display: none; }
|
|
12765
12882
|
.vela-panel-body { flex: 1; overflow: auto; padding: 8px; }
|
|
12766
12883
|
.vela-panel-body::-webkit-scrollbar { width: 8px; }
|
|
12767
12884
|
.vela-panel-body::-webkit-scrollbar-thumb { background: var(--vela-scroll); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
|
|
@@ -12814,6 +12931,7 @@ var CSS5 = `
|
|
|
12814
12931
|
border-left: none;
|
|
12815
12932
|
}
|
|
12816
12933
|
[data-layout='mobile'] .vela-panel-resizer { display: none; }
|
|
12934
|
+
[data-layout='mobile'] .vela-panel-max { display: none; }
|
|
12817
12935
|
`;
|
|
12818
12936
|
function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_MAX_WIDTH) {
|
|
12819
12937
|
const lo = Number.isFinite(min) && min > 0 ? min : DEFAULT_PANEL_MIN_WIDTH;
|
|
@@ -12837,6 +12955,10 @@ var SidePanel = class {
|
|
|
12837
12955
|
* never on a programmatic {@link setOverlay}, for the same reason as widths. */
|
|
12838
12956
|
this.onPlacementChange = null;
|
|
12839
12957
|
this.pin = null;
|
|
12958
|
+
this.maxButton = null;
|
|
12959
|
+
this.maximizedOn = false;
|
|
12960
|
+
/** Set while the close is announced, so a listener's own close does not re-enter. */
|
|
12961
|
+
this.closing = false;
|
|
12840
12962
|
const doc = host.ownerDocument;
|
|
12841
12963
|
injectStyles(STYLE_ID4, CSS5, doc);
|
|
12842
12964
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
@@ -12871,7 +12993,13 @@ var SidePanel = class {
|
|
|
12871
12993
|
});
|
|
12872
12994
|
this.refreshPin();
|
|
12873
12995
|
}
|
|
12874
|
-
|
|
12996
|
+
if (opts.maximizable) {
|
|
12997
|
+
this.maxButton = doc.createElement("button");
|
|
12998
|
+
this.maxButton.className = "vela-panel-max";
|
|
12999
|
+
this.maxButton.addEventListener("click", () => this.setMaximized(!this.maximizedOn));
|
|
13000
|
+
this.refreshMaxButton();
|
|
13001
|
+
}
|
|
13002
|
+
header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], ...this.maxButton ? [this.maxButton] : [], close);
|
|
12875
13003
|
this.body = doc.createElement("div");
|
|
12876
13004
|
this.body.className = "vela-panel-body";
|
|
12877
13005
|
this.el.append(header, this.body);
|
|
@@ -12883,9 +13011,37 @@ var SidePanel = class {
|
|
|
12883
13011
|
}
|
|
12884
13012
|
/** Open/close the panel — a bare call flips it. */
|
|
12885
13013
|
toggle(open2 = this.el.hidden) {
|
|
12886
|
-
if (open2 === !this.el.hidden) return;
|
|
13014
|
+
if (open2 === !this.el.hidden || !open2 && this.closing) return;
|
|
13015
|
+
if (!open2) {
|
|
13016
|
+
this.closing = true;
|
|
13017
|
+
announceSurface(this.el, false, "panel");
|
|
13018
|
+
this.closing = false;
|
|
13019
|
+
}
|
|
12887
13020
|
this.el.hidden = !open2;
|
|
13021
|
+
if (!open2) this.setMaximized(false);
|
|
12888
13022
|
this.onOpenChange?.(open2);
|
|
13023
|
+
if (open2 && !this.el.hidden) announceSurface(this.el, true, "panel");
|
|
13024
|
+
}
|
|
13025
|
+
/** Whether the panel covers every chart right now. */
|
|
13026
|
+
get maximized() {
|
|
13027
|
+
return this.maximizedOn;
|
|
13028
|
+
}
|
|
13029
|
+
/** Maximize the panel over the shell's charts, or restore it. A no-op on a panel not
|
|
13030
|
+
* declared `maximizable`. */
|
|
13031
|
+
setMaximized(maximized) {
|
|
13032
|
+
if (!this.maxButton || maximized === this.maximizedOn) return;
|
|
13033
|
+
this.maximizedOn = maximized;
|
|
13034
|
+
if (maximized) this.el.dataset.maximized = "1";
|
|
13035
|
+
else delete this.el.dataset.maximized;
|
|
13036
|
+
this.refreshMaxButton();
|
|
13037
|
+
}
|
|
13038
|
+
refreshMaxButton() {
|
|
13039
|
+
if (!this.maxButton) return;
|
|
13040
|
+
const doc = this.maxButton.ownerDocument;
|
|
13041
|
+
this.maxButton.replaceChildren(iconEl(this.maximizedOn ? "restore" : "maximize", doc));
|
|
13042
|
+
this.maxButton.title = this.maximizedOn ? "Restore" : "Maximize";
|
|
13043
|
+
this.maxButton.setAttribute("aria-label", this.maxButton.title);
|
|
13044
|
+
this.maxButton.setAttribute("aria-pressed", this.maximizedOn ? "true" : "false");
|
|
12889
13045
|
}
|
|
12890
13046
|
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
12891
13047
|
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
@@ -12937,6 +13093,7 @@ var SidePanel = class {
|
|
|
12937
13093
|
this.pin.setAttribute("aria-pressed", pinned ? "true" : "false");
|
|
12938
13094
|
}
|
|
12939
13095
|
destroy() {
|
|
13096
|
+
if (this.open) announceSurface(this.el, false, "panel");
|
|
12940
13097
|
this.el.remove();
|
|
12941
13098
|
}
|
|
12942
13099
|
/** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
|
|
@@ -14353,19 +14510,22 @@ var PanelDock = class {
|
|
|
14353
14510
|
/**
|
|
14354
14511
|
* (Re)build the CONTRIBUTED panels from the registry — call once after the built-ins, and
|
|
14355
14512
|
* again on `refreshActions()` so a late registration appears. Contributed panels that are
|
|
14356
|
-
* gone from the registry are dropped
|
|
14357
|
-
* descriptor
|
|
14513
|
+
* gone from the registry are dropped and a REPLACED descriptor is rebuilt; a panel whose
|
|
14514
|
+
* descriptor is unchanged stays mounted as it is, so its content and state survive.
|
|
14358
14515
|
*/
|
|
14359
14516
|
refresh() {
|
|
14360
14517
|
const openBefore = this.openId;
|
|
14361
|
-
|
|
14362
|
-
for (const
|
|
14518
|
+
const registered = sidePanels();
|
|
14519
|
+
for (const entry of [...this.entries]) if (entry.contributed && !registered.includes(entry.desc)) this.drop(entry);
|
|
14520
|
+
for (const desc of registered) {
|
|
14521
|
+
if (this.entries.some((e) => e.desc === desc)) continue;
|
|
14363
14522
|
const panel = new SidePanel(this.host, desc.title, `vela-panel-${desc.id}`, {
|
|
14364
14523
|
width: desc.width,
|
|
14365
14524
|
resizable: desc.resizable,
|
|
14366
14525
|
minWidth: desc.minWidth,
|
|
14367
14526
|
maxWidth: desc.maxWidth,
|
|
14368
|
-
overlay: desc.overlay
|
|
14527
|
+
overlay: desc.overlay,
|
|
14528
|
+
maximizable: desc.maximizable
|
|
14369
14529
|
});
|
|
14370
14530
|
const entry = {
|
|
14371
14531
|
id: desc.id,
|
|
@@ -14373,10 +14533,19 @@ var PanelDock = class {
|
|
|
14373
14533
|
icon: desc.icon,
|
|
14374
14534
|
order: desc.order ?? DEFAULT_PANEL_ORDER,
|
|
14375
14535
|
panel,
|
|
14376
|
-
contributed: true
|
|
14536
|
+
contributed: true,
|
|
14537
|
+
desc
|
|
14377
14538
|
};
|
|
14378
14539
|
try {
|
|
14379
|
-
|
|
14540
|
+
const header = {
|
|
14541
|
+
slot: panel.headerSlot,
|
|
14542
|
+
setTitle: (t) => panel.setTitle(t),
|
|
14543
|
+
get maximized() {
|
|
14544
|
+
return panel.maximized;
|
|
14545
|
+
},
|
|
14546
|
+
setMaximized: (on) => panel.setMaximized(on)
|
|
14547
|
+
};
|
|
14548
|
+
entry.handle = desc.mount(this.deps.context(), panel.content, header) ?? void 0;
|
|
14380
14549
|
if (this.chart) entry.handle?.onChart?.(this.chart);
|
|
14381
14550
|
} catch (err) {
|
|
14382
14551
|
console.warn(`[vela] side panel "${desc.id}" failed to mount`, err);
|
|
@@ -18119,6 +18288,7 @@ var DrawingToolbar = class {
|
|
|
18119
18288
|
);
|
|
18120
18289
|
}
|
|
18121
18290
|
}
|
|
18291
|
+
announceSurface(fly, true, "menu", cell);
|
|
18122
18292
|
}
|
|
18123
18293
|
openMagnetFlyout() {
|
|
18124
18294
|
const cell = this.magnetCell;
|
|
@@ -18142,6 +18312,7 @@ var DrawingToolbar = class {
|
|
|
18142
18312
|
})
|
|
18143
18313
|
);
|
|
18144
18314
|
}
|
|
18315
|
+
announceSurface(fly, true, "menu", cell);
|
|
18145
18316
|
}
|
|
18146
18317
|
flyoutHeader(text) {
|
|
18147
18318
|
const t = this.theme;
|
|
@@ -18210,9 +18381,11 @@ var DrawingToolbar = class {
|
|
|
18210
18381
|
}
|
|
18211
18382
|
closeFlyout() {
|
|
18212
18383
|
this.starEls.clear();
|
|
18213
|
-
|
|
18214
|
-
|
|
18384
|
+
const fly = this.flyout;
|
|
18385
|
+
if (fly) {
|
|
18215
18386
|
this.flyout = null;
|
|
18387
|
+
announceSurface(fly, false, "menu", this.flyoutCell);
|
|
18388
|
+
fly.remove();
|
|
18216
18389
|
this.flyoutCell?.classList.remove("vela-open");
|
|
18217
18390
|
this.flyoutCell = null;
|
|
18218
18391
|
this.flyoutOwnerId = null;
|
|
@@ -18881,7 +19054,7 @@ var IndicatorHandleImpl = class {
|
|
|
18881
19054
|
this.controller.applyProps(this.id, values);
|
|
18882
19055
|
}
|
|
18883
19056
|
updateCode(source) {
|
|
18884
|
-
this.controller.updateCode(this.id, source);
|
|
19057
|
+
return this.controller.updateCode(this.id, source);
|
|
18885
19058
|
}
|
|
18886
19059
|
setVisible(visible) {
|
|
18887
19060
|
this.controller.setVisible(this.id, visible);
|
|
@@ -20744,20 +20917,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20744
20917
|
/**
|
|
20745
20918
|
* IndicatorController: replace a SCRIPT indicator's source in place (see
|
|
20746
20919
|
* {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
|
|
20747
|
-
* computing until the new source has compiled
|
|
20748
|
-
*
|
|
20920
|
+
* computing until the new source has compiled; then the new code runs on TRIAL until
|
|
20921
|
+
* its first model lands, and a failure before that puts the previous code back.
|
|
20922
|
+
* Natives have no script — warn and leave them alone.
|
|
20749
20923
|
*/
|
|
20750
20924
|
updateCode(id, source) {
|
|
20751
20925
|
const record = this.registry.get(id);
|
|
20752
20926
|
const handle = this.handles.get(id);
|
|
20753
|
-
if (!record || !handle) return;
|
|
20927
|
+
if (!record || !handle) return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator is no longer on the chart.`) });
|
|
20754
20928
|
if (record.native) {
|
|
20755
20929
|
console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
|
|
20756
|
-
return;
|
|
20930
|
+
return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`) });
|
|
20757
20931
|
}
|
|
20758
|
-
|
|
20759
|
-
record.
|
|
20760
|
-
|
|
20932
|
+
const busy = record.pendingSource !== void 0 || record.codeTrial !== void 0;
|
|
20933
|
+
if (!busy && source === record.source) return Promise.resolve({ ok: true, error: null });
|
|
20934
|
+
return new Promise((resolve) => {
|
|
20935
|
+
(record.codeWaiters ?? (record.codeWaiters = [])).push(resolve);
|
|
20936
|
+
if (record.pendingSource === source || record.pendingSource === void 0 && record.codeTrial && record.source === source) return;
|
|
20937
|
+
record.pendingSource = source;
|
|
20938
|
+
void this.swapSource(id, source, handle);
|
|
20939
|
+
});
|
|
20761
20940
|
}
|
|
20762
20941
|
async swapSource(id, source, handle) {
|
|
20763
20942
|
const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
|
|
@@ -20768,26 +20947,41 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20768
20947
|
const record2 = this.registry.get(id);
|
|
20769
20948
|
if (record2?.pendingSource === source) {
|
|
20770
20949
|
record2.pendingSource = void 0;
|
|
20771
|
-
this.
|
|
20950
|
+
this.rejectCode(id, record2, handle, err);
|
|
20772
20951
|
}
|
|
20773
20952
|
return;
|
|
20774
20953
|
}
|
|
20775
20954
|
const record = this.registry.get(id);
|
|
20776
20955
|
if (!record || record.pendingSource !== source) return;
|
|
20777
20956
|
record.pendingSource = void 0;
|
|
20957
|
+
const previous = record.codeTrial?.previous ?? {
|
|
20958
|
+
source: record.source,
|
|
20959
|
+
engine: record.engine,
|
|
20960
|
+
prepared: record.prepared,
|
|
20961
|
+
inputValues: record.inputValues,
|
|
20962
|
+
propValues: record.propValues,
|
|
20963
|
+
inputs: [...handle.inputs],
|
|
20964
|
+
props: [...handle.props]
|
|
20965
|
+
};
|
|
20966
|
+
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues, record.prepared?.inputs);
|
|
20967
|
+
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues, record.prepared?.props);
|
|
20778
20968
|
record.source = source;
|
|
20779
20969
|
record.engine = engine;
|
|
20780
20970
|
record.prepared = prepared;
|
|
20781
20971
|
handle.setSource(source);
|
|
20782
|
-
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
|
|
20783
|
-
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
|
|
20784
20972
|
handle.setSchema(prepared.inputs);
|
|
20785
20973
|
handle.setPropsSchema(prepared.props ?? []);
|
|
20786
20974
|
record.session?.stop();
|
|
20787
20975
|
record.session = void 0;
|
|
20788
20976
|
record.pendingStructural = true;
|
|
20789
20977
|
record.pendingCause = "code";
|
|
20790
|
-
if (record.hidden)
|
|
20978
|
+
if (record.hidden) {
|
|
20979
|
+
record.codeTrial = void 0;
|
|
20980
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
20981
|
+
this.settleCode(record, { ok: true, error: null });
|
|
20982
|
+
return;
|
|
20983
|
+
}
|
|
20984
|
+
record.codeTrial = { previous };
|
|
20791
20985
|
if (record.renderHandle) {
|
|
20792
20986
|
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
20793
20987
|
this.setLoading(record, true);
|
|
@@ -20795,6 +20989,46 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20795
20989
|
this.mountLoadingPlaceholder(id, record);
|
|
20796
20990
|
}
|
|
20797
20991
|
this.executeIndicator(id, handle);
|
|
20992
|
+
if (this.bars.length === 0 && record.codeTrial) {
|
|
20993
|
+
record.codeTrial = void 0;
|
|
20994
|
+
this.settleCode(record, { ok: true, error: null });
|
|
20995
|
+
}
|
|
20996
|
+
}
|
|
20997
|
+
/** Resolve every `updateCode` caller waiting on this record's chain of edits. */
|
|
20998
|
+
settleCode(record, result) {
|
|
20999
|
+
const waiters = record.codeWaiters;
|
|
21000
|
+
record.codeWaiters = void 0;
|
|
21001
|
+
if (waiters) for (const resolve of waiters) resolve(result);
|
|
21002
|
+
}
|
|
21003
|
+
/**
|
|
21004
|
+
* An update failed — at prepare, or on trial before its first model. A trial puts the
|
|
21005
|
+
* previous code back (source, engine, prepared script, values, schema) and restarts it;
|
|
21006
|
+
* its visuals were never dropped, so the chart keeps painting throughout. Then the
|
|
21007
|
+
* failure reports the usual way and every waiting caller learns it.
|
|
21008
|
+
*/
|
|
21009
|
+
rejectCode(id, record, handle, err) {
|
|
21010
|
+
const trial = record.codeTrial;
|
|
21011
|
+
record.codeTrial = void 0;
|
|
21012
|
+
this.fail(id, handle, err);
|
|
21013
|
+
if (trial) {
|
|
21014
|
+
const p = trial.previous;
|
|
21015
|
+
record.session?.stop();
|
|
21016
|
+
record.session = void 0;
|
|
21017
|
+
record.source = p.source;
|
|
21018
|
+
record.engine = p.engine;
|
|
21019
|
+
record.prepared = p.prepared;
|
|
21020
|
+
record.inputValues = p.inputValues;
|
|
21021
|
+
record.propValues = p.propValues;
|
|
21022
|
+
handle.setSource(p.source);
|
|
21023
|
+
handle.setSchema(p.inputs);
|
|
21024
|
+
handle.setPropsSchema(p.props);
|
|
21025
|
+
record.pendingStructural = true;
|
|
21026
|
+
record.pendingCause = void 0;
|
|
21027
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
21028
|
+
if (!record.hidden) this.executeIndicator(id, handle);
|
|
21029
|
+
}
|
|
21030
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
21031
|
+
this.settleCode(record, { ok: false, error });
|
|
20798
21032
|
}
|
|
20799
21033
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
20800
21034
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
@@ -20809,6 +21043,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20809
21043
|
removeIndicator(id) {
|
|
20810
21044
|
const record = this.registry.remove(id);
|
|
20811
21045
|
if (record?.native?.type === "volume") this.volumeOptedOut = true;
|
|
21046
|
+
if (record) this.settleCode(record, { ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator was removed.`) });
|
|
20812
21047
|
record?.session?.stop();
|
|
20813
21048
|
if (record?.native?.started) record.native.instance.stop();
|
|
20814
21049
|
this.handles.delete(id);
|
|
@@ -20836,6 +21071,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20836
21071
|
if (!visible) {
|
|
20837
21072
|
record.session?.stop();
|
|
20838
21073
|
record.session = void 0;
|
|
21074
|
+
if (record.codeTrial) {
|
|
21075
|
+
record.codeTrial = void 0;
|
|
21076
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21077
|
+
}
|
|
20839
21078
|
if (record.native?.started) record.native.instance.suspend();
|
|
20840
21079
|
if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
|
|
20841
21080
|
else if (record.native) this.mountHiddenNativeRow(id, record);
|
|
@@ -20923,6 +21162,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20923
21162
|
this.viewportUnsub?.();
|
|
20924
21163
|
this.paneActionUnsub?.();
|
|
20925
21164
|
for (const record of this.registry.all()) {
|
|
21165
|
+
this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
|
|
20926
21166
|
record.session?.stop();
|
|
20927
21167
|
if (record.native?.started) record.native.instance.stop();
|
|
20928
21168
|
}
|
|
@@ -20991,7 +21231,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20991
21231
|
const record = this.registry.get(id);
|
|
20992
21232
|
if (!record || !record.engine || !record.prepared || record.hidden) return;
|
|
20993
21233
|
const mode = this.config.live && !record.prepared.reactsToViewport && record.engine.capabilities.streaming ? "live" : "static";
|
|
20994
|
-
|
|
21234
|
+
const prepared = record.prepared;
|
|
21235
|
+
const session = record.engine.execute(
|
|
20995
21236
|
{
|
|
20996
21237
|
prepared: record.prepared,
|
|
20997
21238
|
market: this.market(),
|
|
@@ -21012,6 +21253,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21012
21253
|
const cause = record.pendingCause ?? "history";
|
|
21013
21254
|
if (!this.applyModel(id, model)) return;
|
|
21014
21255
|
record.pendingCause = void 0;
|
|
21256
|
+
if (record.codeTrial) {
|
|
21257
|
+
record.codeTrial = void 0;
|
|
21258
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21259
|
+
}
|
|
21015
21260
|
this.emitContextChanged(id);
|
|
21016
21261
|
this.emitScriptRun(id, cause, first);
|
|
21017
21262
|
},
|
|
@@ -21022,9 +21267,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21022
21267
|
handle.emit("alert", { id: a.id, message: a.message, title: a.title, time: a.time });
|
|
21023
21268
|
},
|
|
21024
21269
|
onWarning: (w) => this.events.emit("warning", w),
|
|
21025
|
-
onError: (err) =>
|
|
21270
|
+
onError: (err) => {
|
|
21271
|
+
if (record.codeTrial) this.rejectCode(id, record, handle, err);
|
|
21272
|
+
else this.fail(id, handle, err);
|
|
21273
|
+
}
|
|
21026
21274
|
}
|
|
21027
21275
|
);
|
|
21276
|
+
if (record.prepared !== prepared) {
|
|
21277
|
+
session.stop();
|
|
21278
|
+
return;
|
|
21279
|
+
}
|
|
21280
|
+
record.session = session;
|
|
21028
21281
|
this.emitContextChanged(id);
|
|
21029
21282
|
}
|
|
21030
21283
|
/**
|
|
@@ -21525,7 +21778,7 @@ function yieldToPaint() {
|
|
|
21525
21778
|
}
|
|
21526
21779
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
21527
21780
|
}
|
|
21528
|
-
function valuesOnSchema(schema, previous) {
|
|
21781
|
+
function valuesOnSchema(schema, previous, oldSchema = []) {
|
|
21529
21782
|
const out = {};
|
|
21530
21783
|
const declared = /* @__PURE__ */ new Set();
|
|
21531
21784
|
for (const s of schema) {
|
|
@@ -21533,9 +21786,22 @@ function valuesOnSchema(schema, previous) {
|
|
|
21533
21786
|
declared.add(s.key);
|
|
21534
21787
|
declared.add(s.title);
|
|
21535
21788
|
}
|
|
21536
|
-
|
|
21789
|
+
const oldDefaults = /* @__PURE__ */ new Map();
|
|
21790
|
+
for (const s of oldSchema) {
|
|
21791
|
+
oldDefaults.set(s.key, s.defval);
|
|
21792
|
+
if (!oldDefaults.has(s.title)) oldDefaults.set(s.title, s.defval);
|
|
21793
|
+
}
|
|
21794
|
+
for (const [k, v] of Object.entries(previous)) {
|
|
21795
|
+
if (!declared.has(k)) continue;
|
|
21796
|
+
if (oldDefaults.has(k) && sameInputValue(oldDefaults.get(k), v)) continue;
|
|
21797
|
+
out[k] = v;
|
|
21798
|
+
}
|
|
21537
21799
|
return out;
|
|
21538
21800
|
}
|
|
21801
|
+
function sameInputValue(a, b) {
|
|
21802
|
+
if (a === b) return true;
|
|
21803
|
+
return typeof a === "object" && a !== null && typeof b === "object" && b !== null && JSON.stringify(a) === JSON.stringify(b);
|
|
21804
|
+
}
|
|
21539
21805
|
function blankedModel(model) {
|
|
21540
21806
|
return {
|
|
21541
21807
|
...model,
|
|
@@ -24465,15 +24731,23 @@ var Batch = class {
|
|
|
24465
24731
|
this.seg(x + w, y + h, x, y + h, lineWidth, c);
|
|
24466
24732
|
this.seg(x, y + h, x, y, lineWidth, c);
|
|
24467
24733
|
}
|
|
24468
|
-
/**
|
|
24734
|
+
/**
|
|
24735
|
+
* Filled circle (triangle fan), feathered like a line: the rim is padded by {@link AA_PAD} and
|
|
24736
|
+
* edgeDist runs from 0 at the center to the padded radius at the rim, with edgeHalf = r. The
|
|
24737
|
+
* distance grows linearly along each spoke, so the shader's line feather gives a round soft edge.
|
|
24738
|
+
*/
|
|
24469
24739
|
circle(cx, cy, r, c, segments = 16) {
|
|
24470
|
-
|
|
24740
|
+
const ext = r + AA_PAD;
|
|
24741
|
+
let px = cx + ext;
|
|
24471
24742
|
let py = cy;
|
|
24743
|
+
this.ensure(segments * 3);
|
|
24472
24744
|
for (let i = 1; i <= segments; i += 1) {
|
|
24473
24745
|
const a = i / segments * Math.PI * 2;
|
|
24474
|
-
const nx = cx + Math.cos(a) *
|
|
24475
|
-
const ny = cy + Math.sin(a) *
|
|
24476
|
-
this.
|
|
24746
|
+
const nx = cx + Math.cos(a) * ext;
|
|
24747
|
+
const ny = cy + Math.sin(a) * ext;
|
|
24748
|
+
this.v(cx, cy, c, 0, r);
|
|
24749
|
+
this.v(px, py, c, ext, r);
|
|
24750
|
+
this.v(nx, ny, c, ext, r);
|
|
24477
24751
|
px = nx;
|
|
24478
24752
|
py = ny;
|
|
24479
24753
|
}
|
|
@@ -24654,7 +24928,7 @@ var WebGL2Backend = class {
|
|
|
24654
24928
|
}
|
|
24655
24929
|
mount(canvas) {
|
|
24656
24930
|
this.canvas = canvas;
|
|
24657
|
-
this.gl = canvas.getContext("webgl2", { alpha: true, antialias:
|
|
24931
|
+
this.gl = canvas.getContext("webgl2", { alpha: true, antialias: false, depth: false, stencil: false, premultipliedAlpha: true });
|
|
24658
24932
|
if (!this.gl) return;
|
|
24659
24933
|
if (!this.initGL()) {
|
|
24660
24934
|
this.gl = null;
|
|
@@ -26106,7 +26380,7 @@ var InputController = class {
|
|
|
26106
26380
|
this.deps.drawingsPointerUp?.(x, y, this.snapMode(e));
|
|
26107
26381
|
} else if (tapRelease && this.region === "data") {
|
|
26108
26382
|
this.deps.onClick(x, y);
|
|
26109
|
-
} else if (this.dragging && this.region === "data") {
|
|
26383
|
+
} else if (this.dragging && this.region === "data" && wasTouch) {
|
|
26110
26384
|
const stale = e.timeStamp - this.lastT > FLING_STALE_MS;
|
|
26111
26385
|
if (!stale && Math.abs(this.vx) > FLING_MIN_SPEED) {
|
|
26112
26386
|
const pitch = this.deps.getCoords().pxPerBar();
|
|
@@ -32472,6 +32746,12 @@ var DrawingInteraction = class {
|
|
|
32472
32746
|
activeDragId() {
|
|
32473
32747
|
return this.state.kind === "pressed" && this.state.moved ? this.state.id : null;
|
|
32474
32748
|
}
|
|
32749
|
+
/** The store drawings a live drag is moving — the pressed one and its riders — or none
|
|
32750
|
+
* before the drag passes the slop. A Ctrl-drag moves copies, so its sources stay put. */
|
|
32751
|
+
movingIds() {
|
|
32752
|
+
if (this.state.kind !== "pressed" || !this.state.moved || this.state.clones) return /* @__PURE__ */ new Set();
|
|
32753
|
+
return /* @__PURE__ */ new Set([this.state.id, ...this.state.riders.map((r) => r.id)]);
|
|
32754
|
+
}
|
|
32475
32755
|
/** The drawing under a press that has not been released yet (drag or click undecided), or null. */
|
|
32476
32756
|
pressedId() {
|
|
32477
32757
|
return this.state.kind === "pressed" ? this.state.id : null;
|
|
@@ -33130,6 +33410,17 @@ var DrawingSettingsDialog = class {
|
|
|
33130
33410
|
}
|
|
33131
33411
|
}));
|
|
33132
33412
|
}
|
|
33413
|
+
if (drawing instanceof FibRetracement) {
|
|
33414
|
+
const fib = drawing;
|
|
33415
|
+
grid.appendChild(fieldRow({
|
|
33416
|
+
label: "Reverse",
|
|
33417
|
+
bool: true,
|
|
33418
|
+
toggle: {
|
|
33419
|
+
checked: fib.reverse,
|
|
33420
|
+
onChange: (v) => actions.patch({ reverse: v })
|
|
33421
|
+
}
|
|
33422
|
+
}));
|
|
33423
|
+
}
|
|
33133
33424
|
levels.forEach((lv, i) => {
|
|
33134
33425
|
const row = document.createElement("div");
|
|
33135
33426
|
row.className = "vela-field-span";
|
|
@@ -34283,6 +34574,8 @@ var UserDrawingController = class {
|
|
|
34283
34574
|
this.drawings = [];
|
|
34284
34575
|
/** Ids painted on an interleave layer this frame — the top canvas paints only their handles. */
|
|
34285
34576
|
this.sliced = /* @__PURE__ */ new Set();
|
|
34577
|
+
/** Interleaved drawings mid-drag: painted on the top canvas, left out of their slice. */
|
|
34578
|
+
this.lifted = /* @__PURE__ */ new Set();
|
|
34286
34579
|
/** Cached slice canvases, keyed `paneId|beforeZ`, reused across frames to avoid churn. */
|
|
34287
34580
|
this.sliceCache = /* @__PURE__ */ new Map();
|
|
34288
34581
|
/** Series boundaries per pane as of the last `prepareSlices` — lets a repaint between data
|
|
@@ -34370,7 +34663,7 @@ var UserDrawingController = class {
|
|
|
34370
34663
|
selectedIds: () => this.selectedIds,
|
|
34371
34664
|
emit: (i) => this.emit(i),
|
|
34372
34665
|
changed: () => {
|
|
34373
|
-
this.
|
|
34666
|
+
this.syncLift();
|
|
34374
34667
|
this.render();
|
|
34375
34668
|
},
|
|
34376
34669
|
openSettings: (id, x, y) => this.openSettingsById(id, x, y),
|
|
@@ -34996,6 +35289,20 @@ var UserDrawingController = class {
|
|
|
34996
35289
|
/** A drawing that paints inside the series stack changed (content, not hover): its pixels
|
|
34997
35290
|
* live in the backend composite, so this layer alone can't show the change — ask for a
|
|
34998
35291
|
* data frame, which re-runs `prepareSlices` before the backend composites. */
|
|
35292
|
+
/** A drawing being dragged leaves its interleave slice for the top canvas, so each pointer
|
|
35293
|
+
* move repaints one 2D layer instead of re-rasterizing and re-uploading a plot-sized slice
|
|
35294
|
+
* texture through the data frame. The slices rebuild once as the drag starts and once as it
|
|
35295
|
+
* ends (release, cancel), when the lifted set changes. */
|
|
35296
|
+
syncLift() {
|
|
35297
|
+
const moving = this.interaction.movingIds();
|
|
35298
|
+
let same = moving.size === this.lifted.size;
|
|
35299
|
+
if (same) {
|
|
35300
|
+
for (const id of moving) if (!this.lifted.has(id)) same = false;
|
|
35301
|
+
}
|
|
35302
|
+
if (same) return;
|
|
35303
|
+
this.lifted = new Set(moving);
|
|
35304
|
+
this.invalidateSlices();
|
|
35305
|
+
}
|
|
34999
35306
|
invalidateSlices() {
|
|
35000
35307
|
if (this.sliced.size > 0 || this.drawings.some((d) => this.isInterleaved(d))) this.deps.requestDataPaint();
|
|
35001
35308
|
}
|
|
@@ -35014,7 +35321,7 @@ var UserDrawingController = class {
|
|
|
35014
35321
|
const theme = this.deps.theme();
|
|
35015
35322
|
const buckets = /* @__PURE__ */ new Map();
|
|
35016
35323
|
for (const d of this.drawings) {
|
|
35017
|
-
if (!d.visible) continue;
|
|
35324
|
+
if (!d.visible || this.lifted.has(d.id)) continue;
|
|
35018
35325
|
const beforeZ = sliceKeyFor(d.zIndex, this.lastBounds.get(d.paneId) ?? []);
|
|
35019
35326
|
if (beforeZ === null) continue;
|
|
35020
35327
|
const key = `${d.paneId}|${beforeZ}`;
|
|
@@ -35066,8 +35373,9 @@ var UserDrawingController = class {
|
|
|
35066
35373
|
mutedLabel: edited instanceof TextLabel ? edited.id : null
|
|
35067
35374
|
};
|
|
35068
35375
|
this.painter.seriesLook = this.deps.seriesLook();
|
|
35069
|
-
|
|
35070
|
-
this.painter.
|
|
35376
|
+
const onTop = (d) => !this.isInterleaved(d) || this.lifted.has(d.id);
|
|
35377
|
+
this.painter.paintAll(ctx, this.drawings.filter(onTop), proj, this.deps.theme(), targets);
|
|
35378
|
+
this.painter.paintHighlights(ctx, this.drawings.filter((d) => !onTop(d)), proj, handleIdsFor(targets));
|
|
35071
35379
|
const clones = this.interaction.dragClones();
|
|
35072
35380
|
if (clones) this.painter.paintAll(ctx, clones, proj, this.deps.theme(), { selected: new Set(clones.map((c) => c.id)) });
|
|
35073
35381
|
this.layoutTextEditor();
|
|
@@ -44723,6 +45031,9 @@ var CSS21 = `
|
|
|
44723
45031
|
.vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
|
|
44724
45032
|
.vela-statusline .vela-sl-market > [hidden] { display: none !important; }
|
|
44725
45033
|
.vela-statusline .vela-sl-replay-badge, .vela-statusline .vela-sl-replay-badge svg { display: block; width: 16px; height: 16px; }
|
|
45034
|
+
/* Tabular figures: every digit has one advance, so a readout whose text changes only in
|
|
45035
|
+
* its digits keeps its width \u2014 render() skips the ladder for those (see widthKey). */
|
|
45036
|
+
.vela-statusline .vela-sl-values { font-variant-numeric: tabular-nums; }
|
|
44726
45037
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
44727
45038
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
44728
45039
|
/* The change value wears the SAME ink as the OHLC values (set inline per render) \u2014
|
|
@@ -44840,6 +45151,10 @@ var Statusline = class {
|
|
|
44840
45151
|
this.fitRO = null;
|
|
44841
45152
|
/** The ladder rung currently applied — see {@link fit}. */
|
|
44842
45153
|
this.layout = { stacked: false, level: "full" };
|
|
45154
|
+
/** The readout {@link widthKey} the applied rung was fitted to. */
|
|
45155
|
+
this.fitKey = null;
|
|
45156
|
+
/** Web fonts landing after the last fit change every width without a resize. */
|
|
45157
|
+
this.onFontsLoaded = () => this.fit();
|
|
44843
45158
|
this.onContextMenu = (e) => {
|
|
44844
45159
|
if (!this.menu || !this.menuHooks) return;
|
|
44845
45160
|
e.preventDefault();
|
|
@@ -44903,6 +45218,7 @@ var Statusline = class {
|
|
|
44903
45218
|
this.fitRO = new ResizeObserver(() => this.fit());
|
|
44904
45219
|
this.fitRO.observe(host);
|
|
44905
45220
|
}
|
|
45221
|
+
doc.fonts?.addEventListener("loadingdone", this.onFontsLoaded);
|
|
44906
45222
|
this.marketTip = new Tooltip(this.marketEl, { content: MARKET_LABELS.open, placement: "bottom" });
|
|
44907
45223
|
this.eyeTip = new Tooltip(this.eyeEl, { content: "Show chart", placement: "bottom" });
|
|
44908
45224
|
this.setMarketStatus("open");
|
|
@@ -44955,6 +45271,7 @@ var Statusline = class {
|
|
|
44955
45271
|
* (detached host, node tests) nothing overflows, so the widest rung stays.
|
|
44956
45272
|
*/
|
|
44957
45273
|
fit() {
|
|
45274
|
+
this.fitKey = this.widthKey();
|
|
44958
45275
|
this.syncParts();
|
|
44959
45276
|
const seg = segmentVisibility(this.parts, this.chartHidden);
|
|
44960
45277
|
const overflows = () => this.el.scrollWidth > this.el.clientWidth;
|
|
@@ -45111,6 +45428,7 @@ var Statusline = class {
|
|
|
45111
45428
|
this.detach();
|
|
45112
45429
|
this.fitRO?.disconnect();
|
|
45113
45430
|
this.fitRO = null;
|
|
45431
|
+
this.el.ownerDocument.fonts?.removeEventListener("loadingdone", this.onFontsLoaded);
|
|
45114
45432
|
this.el.removeEventListener("contextmenu", this.onContextMenu);
|
|
45115
45433
|
this.menu?.destroy();
|
|
45116
45434
|
this.menu = null;
|
|
@@ -45126,7 +45444,22 @@ var Statusline = class {
|
|
|
45126
45444
|
}
|
|
45127
45445
|
render() {
|
|
45128
45446
|
if (this.menuHooks) this.setChartHidden(!this.menuHooks.chartVisible());
|
|
45129
|
-
this.
|
|
45447
|
+
if (this.widthKey() === this.fitKey) this.renderValues();
|
|
45448
|
+
else this.fit();
|
|
45449
|
+
}
|
|
45450
|
+
/** Everything the readout's width depends on, at every rung: its shape, and each
|
|
45451
|
+
* value's sign, digit count and decimals (the digits themselves are tabular — see
|
|
45452
|
+
* the CSS; the grouping commas follow from the digit count). `toFixed` carries all
|
|
45453
|
+
* three at a fraction of the locale formatter's cost — this runs per pointer move.
|
|
45454
|
+
* The parts, the host's size and the fonts re-fit through their own paths. */
|
|
45455
|
+
widthKey() {
|
|
45456
|
+
const bar = this.hoverBar ?? this.lastBar;
|
|
45457
|
+
if (!bar) return this.readout;
|
|
45458
|
+
const dp = decimalsFor(bar.close);
|
|
45459
|
+
const diff = bar.close - bar.open;
|
|
45460
|
+
const text = [bar.open, bar.high, bar.low, bar.close, diff].map((v) => v.toFixed(dp));
|
|
45461
|
+
text.push((diff / bar.open * 100).toFixed(2));
|
|
45462
|
+
return `${this.readout} ${text.join(" ")}`.replace(/\d/g, "0");
|
|
45130
45463
|
}
|
|
45131
45464
|
/** Write the value readout for the current bar at the current ladder level. */
|
|
45132
45465
|
renderValues() {
|