@luxalgo/vela 0.8.0 → 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/NOTICE +1 -1
- package/README.md +16 -8
- 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-KA77UTRF.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-2IAETM2I.js → chunk-NLDTVFOI.js} +79 -6
- package/dist/{chunk-E3GU6AUI.js → chunk-R2JMNIHY.js} +68 -23
- package/dist/{chunk-QBPUT5SU.js → chunk-T3DZIR5Y.js} +211 -54
- 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 +317 -62
- 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 +115 -5
- 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 +317 -62
- package/dist/vela.global.min.js +61 -68
- package/dist/widget.cjs +476 -85
- package/dist/widget.d.cts +20 -10
- package/dist/widget.d.ts +20 -10
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +476 -85
- package/dist/workspace.d.cts +6 -6
- package/dist/workspace.d.ts +6 -6
- package/dist/workspace.js +6 -6
- package/package.json +3 -3
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
|
}
|
|
@@ -3617,14 +3690,49 @@ function buildColorPicker(color, theme, onChange, options = {}) {
|
|
|
3617
3690
|
const knob = document.createElement("div");
|
|
3618
3691
|
knob.style.cssText = "position:absolute;top:50%;width:15px;height:15px;border-radius:50%;background:var(--vela-selected-bg);box-shadow:0 1px 3px rgba(0,0,0,0.55);transform:translate(-50%,-50%);pointer-events:none;";
|
|
3619
3692
|
track.appendChild(knob);
|
|
3620
|
-
const pctBox = document.createElement("
|
|
3621
|
-
pctBox.
|
|
3693
|
+
const pctBox = document.createElement("input");
|
|
3694
|
+
pctBox.type = "text";
|
|
3695
|
+
pctBox.inputMode = "numeric";
|
|
3696
|
+
pctBox.title = "Opacity (%)";
|
|
3697
|
+
pctBox.style.cssText = `width:46px;box-sizing:border-box;text-align:center;font:var(--vela-font-size-md) ${theme.fontFamily};color:var(--vela-fg);background:transparent;border:1px solid var(--vela-border);border-radius:5px;padding:3px 4px;outline:none;`;
|
|
3698
|
+
pctBox.addEventListener("focus", () => {
|
|
3699
|
+
pctBox.style.borderColor = "var(--vela-border-strong)";
|
|
3700
|
+
pctBox.select();
|
|
3701
|
+
});
|
|
3622
3702
|
opRow.append(track, pctBox);
|
|
3623
3703
|
const paintOpacity = () => {
|
|
3624
3704
|
track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
|
|
3625
3705
|
knob.style.left = `${curAlpha * 100}%`;
|
|
3626
|
-
pctBox.
|
|
3706
|
+
pctBox.value = `${Math.round(curAlpha * 100)}%`;
|
|
3707
|
+
};
|
|
3708
|
+
const commitTyped = () => {
|
|
3709
|
+
pctBox.style.borderColor = "var(--vela-border)";
|
|
3710
|
+
const n = parseFloat(pctBox.value.replace("%", ""));
|
|
3711
|
+
if (Number.isFinite(n)) {
|
|
3712
|
+
const next = Math.max(0, Math.min(100, n)) / 100;
|
|
3713
|
+
if (Math.round(next * 100) !== Math.round(curAlpha * 100)) {
|
|
3714
|
+
curAlpha = next;
|
|
3715
|
+
paintOpacity();
|
|
3716
|
+
emit();
|
|
3717
|
+
return;
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
paintOpacity();
|
|
3627
3721
|
};
|
|
3722
|
+
pctBox.addEventListener("change", commitTyped);
|
|
3723
|
+
pctBox.addEventListener("blur", () => {
|
|
3724
|
+
pctBox.style.borderColor = "var(--vela-border)";
|
|
3725
|
+
paintOpacity();
|
|
3726
|
+
});
|
|
3727
|
+
pctBox.addEventListener("keydown", (e) => {
|
|
3728
|
+
if (e.key === "Escape") return;
|
|
3729
|
+
e.stopPropagation();
|
|
3730
|
+
if (e.key === "Enter") {
|
|
3731
|
+
e.preventDefault();
|
|
3732
|
+
commitTyped();
|
|
3733
|
+
pctBox.blur();
|
|
3734
|
+
}
|
|
3735
|
+
});
|
|
3628
3736
|
let dragging = false;
|
|
3629
3737
|
const onDrag = (clientX) => {
|
|
3630
3738
|
const r = track.getBoundingClientRect();
|
|
@@ -4963,6 +5071,7 @@ var LayoutPicker = class {
|
|
|
4963
5071
|
const panel = doc.createElement("div");
|
|
4964
5072
|
panel.className = "vela-lp";
|
|
4965
5073
|
this.layer.appendChild(panel);
|
|
5074
|
+
this.panel = panel;
|
|
4966
5075
|
const cols = doc.createElement("div");
|
|
4967
5076
|
cols.className = "vela-lp-cols";
|
|
4968
5077
|
panel.appendChild(cols);
|
|
@@ -5044,10 +5153,12 @@ var LayoutPicker = class {
|
|
|
5044
5153
|
this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
|
|
5045
5154
|
this.doc.addEventListener("keydown", this.onDocKeydown, true);
|
|
5046
5155
|
this.opts.onOpenChange?.(true);
|
|
5156
|
+
if (this.isOpen) announceSurface(this.panel, true, "popover", this.opts.trigger);
|
|
5047
5157
|
}
|
|
5048
5158
|
close() {
|
|
5049
5159
|
if (!this.isOpen) return;
|
|
5050
5160
|
this.isOpen = false;
|
|
5161
|
+
announceSurface(this.panel, false, "popover", this.opts.trigger);
|
|
5051
5162
|
this.layer.style.display = "none";
|
|
5052
5163
|
this.opts.trigger.setAttribute("aria-expanded", "false");
|
|
5053
5164
|
this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
|
|
@@ -5253,10 +5364,12 @@ function legendActions() {
|
|
|
5253
5364
|
}
|
|
5254
5365
|
function legendActionsProviderFor(chart, context) {
|
|
5255
5366
|
return (indicatorId) => {
|
|
5256
|
-
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();
|
|
5257
5370
|
if (!handle) return [];
|
|
5258
|
-
const info =
|
|
5259
|
-
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)) }));
|
|
5260
5373
|
};
|
|
5261
5374
|
}
|
|
5262
5375
|
var calloutRegistry = /* @__PURE__ */ new Map();
|
|
@@ -9454,6 +9567,10 @@ var FibRatios = class extends Drawing {
|
|
|
9454
9567
|
|
|
9455
9568
|
// src/core/drawings/types/FibLevels.ts
|
|
9456
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
|
+
}
|
|
9457
9574
|
/** Per-level pixel line + price for the ENABLED levels, spanning the anchors' time range. */
|
|
9458
9575
|
levelLines(proj) {
|
|
9459
9576
|
const a = this.anchors[0];
|
|
@@ -9463,11 +9580,10 @@ var FibLevels = class extends FibRatios {
|
|
|
9463
9580
|
const xb = proj.xOf(b.time);
|
|
9464
9581
|
const x1 = Math.min(xa, xb);
|
|
9465
9582
|
const x2 = Math.max(xa, xb);
|
|
9466
|
-
const delta = b.price - a.price;
|
|
9467
9583
|
const out = [];
|
|
9468
9584
|
for (const lv of this.levels) {
|
|
9469
9585
|
if (!lv.enabled) continue;
|
|
9470
|
-
const price =
|
|
9586
|
+
const price = this.levelPrice(lv.ratio, a.price, b.price);
|
|
9471
9587
|
const y = proj.yOf(price, this.paneId);
|
|
9472
9588
|
if (y == null) continue;
|
|
9473
9589
|
out.push({ ratio: lv.ratio, color: lv.color, label: lv.label, price, x1, x2, y });
|
|
@@ -9507,8 +9623,7 @@ var FibLevels = class extends FibRatios {
|
|
|
9507
9623
|
const a = this.anchors[0];
|
|
9508
9624
|
const b = this.anchors[1];
|
|
9509
9625
|
if (!a || !b) return null;
|
|
9510
|
-
const
|
|
9511
|
-
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));
|
|
9512
9627
|
if (prices.length === 0) return null;
|
|
9513
9628
|
return { min: Math.min(...prices), max: Math.max(...prices) };
|
|
9514
9629
|
}
|
|
@@ -9594,13 +9709,24 @@ function cycleLevels(count, enabledCount = count) {
|
|
|
9594
9709
|
// src/core/drawings/types/FibRetracement.ts
|
|
9595
9710
|
var LEVELS = fibLevels([0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]);
|
|
9596
9711
|
var FibRetracement = class extends FibLevels {
|
|
9597
|
-
constructor() {
|
|
9598
|
-
super(
|
|
9712
|
+
constructor(init) {
|
|
9713
|
+
super(init);
|
|
9599
9714
|
this.type = "fibretracement";
|
|
9715
|
+
if (this.reverse === void 0) this.reverse = false;
|
|
9600
9716
|
}
|
|
9601
9717
|
defaultLevels() {
|
|
9602
9718
|
return LEVELS;
|
|
9603
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
|
+
}
|
|
9604
9730
|
};
|
|
9605
9731
|
|
|
9606
9732
|
// src/core/drawings/types/FibExtension.ts
|
|
@@ -12727,6 +12853,32 @@ var CSS5 = `
|
|
|
12727
12853
|
.vela-panel-pin .vela-icon { width: 15px; height: 15px; }
|
|
12728
12854
|
.vela-panel-pin:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12729
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; }
|
|
12730
12882
|
.vela-panel-body { flex: 1; overflow: auto; padding: 8px; }
|
|
12731
12883
|
.vela-panel-body::-webkit-scrollbar { width: 8px; }
|
|
12732
12884
|
.vela-panel-body::-webkit-scrollbar-thumb { background: var(--vela-scroll); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
|
|
@@ -12779,6 +12931,7 @@ var CSS5 = `
|
|
|
12779
12931
|
border-left: none;
|
|
12780
12932
|
}
|
|
12781
12933
|
[data-layout='mobile'] .vela-panel-resizer { display: none; }
|
|
12934
|
+
[data-layout='mobile'] .vela-panel-max { display: none; }
|
|
12782
12935
|
`;
|
|
12783
12936
|
function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_MAX_WIDTH) {
|
|
12784
12937
|
const lo = Number.isFinite(min) && min > 0 ? min : DEFAULT_PANEL_MIN_WIDTH;
|
|
@@ -12802,6 +12955,10 @@ var SidePanel = class {
|
|
|
12802
12955
|
* never on a programmatic {@link setOverlay}, for the same reason as widths. */
|
|
12803
12956
|
this.onPlacementChange = null;
|
|
12804
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;
|
|
12805
12962
|
const doc = host.ownerDocument;
|
|
12806
12963
|
injectStyles(STYLE_ID4, CSS5, doc);
|
|
12807
12964
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
@@ -12836,7 +12993,13 @@ var SidePanel = class {
|
|
|
12836
12993
|
});
|
|
12837
12994
|
this.refreshPin();
|
|
12838
12995
|
}
|
|
12839
|
-
|
|
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);
|
|
12840
13003
|
this.body = doc.createElement("div");
|
|
12841
13004
|
this.body.className = "vela-panel-body";
|
|
12842
13005
|
this.el.append(header, this.body);
|
|
@@ -12848,9 +13011,37 @@ var SidePanel = class {
|
|
|
12848
13011
|
}
|
|
12849
13012
|
/** Open/close the panel — a bare call flips it. */
|
|
12850
13013
|
toggle(open2 = this.el.hidden) {
|
|
12851
|
-
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
|
+
}
|
|
12852
13020
|
this.el.hidden = !open2;
|
|
13021
|
+
if (!open2) this.setMaximized(false);
|
|
12853
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");
|
|
12854
13045
|
}
|
|
12855
13046
|
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
12856
13047
|
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
@@ -12902,6 +13093,7 @@ var SidePanel = class {
|
|
|
12902
13093
|
this.pin.setAttribute("aria-pressed", pinned ? "true" : "false");
|
|
12903
13094
|
}
|
|
12904
13095
|
destroy() {
|
|
13096
|
+
if (this.open) announceSurface(this.el, false, "panel");
|
|
12905
13097
|
this.el.remove();
|
|
12906
13098
|
}
|
|
12907
13099
|
/** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
|
|
@@ -14318,19 +14510,22 @@ var PanelDock = class {
|
|
|
14318
14510
|
/**
|
|
14319
14511
|
* (Re)build the CONTRIBUTED panels from the registry — call once after the built-ins, and
|
|
14320
14512
|
* again on `refreshActions()` so a late registration appears. Contributed panels that are
|
|
14321
|
-
* gone from the registry are dropped
|
|
14322
|
-
* 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.
|
|
14323
14515
|
*/
|
|
14324
14516
|
refresh() {
|
|
14325
14517
|
const openBefore = this.openId;
|
|
14326
|
-
|
|
14327
|
-
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;
|
|
14328
14522
|
const panel = new SidePanel(this.host, desc.title, `vela-panel-${desc.id}`, {
|
|
14329
14523
|
width: desc.width,
|
|
14330
14524
|
resizable: desc.resizable,
|
|
14331
14525
|
minWidth: desc.minWidth,
|
|
14332
14526
|
maxWidth: desc.maxWidth,
|
|
14333
|
-
overlay: desc.overlay
|
|
14527
|
+
overlay: desc.overlay,
|
|
14528
|
+
maximizable: desc.maximizable
|
|
14334
14529
|
});
|
|
14335
14530
|
const entry = {
|
|
14336
14531
|
id: desc.id,
|
|
@@ -14338,10 +14533,19 @@ var PanelDock = class {
|
|
|
14338
14533
|
icon: desc.icon,
|
|
14339
14534
|
order: desc.order ?? DEFAULT_PANEL_ORDER,
|
|
14340
14535
|
panel,
|
|
14341
|
-
contributed: true
|
|
14536
|
+
contributed: true,
|
|
14537
|
+
desc
|
|
14342
14538
|
};
|
|
14343
14539
|
try {
|
|
14344
|
-
|
|
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;
|
|
14345
14549
|
if (this.chart) entry.handle?.onChart?.(this.chart);
|
|
14346
14550
|
} catch (err) {
|
|
14347
14551
|
console.warn(`[vela] side panel "${desc.id}" failed to mount`, err);
|
|
@@ -15129,7 +15333,12 @@ var TimeframeQuick = class {
|
|
|
15129
15333
|
content: (body) => body.append(this.input, this.hint),
|
|
15130
15334
|
onOpenChange: (open2) => opts.onOpenChange?.(open2)
|
|
15131
15335
|
});
|
|
15132
|
-
this.input.addEventListener("input", () =>
|
|
15336
|
+
this.input.addEventListener("input", () => {
|
|
15337
|
+
const { selectionStart, selectionEnd } = this.input;
|
|
15338
|
+
this.input.value = this.input.value.toUpperCase();
|
|
15339
|
+
this.input.setSelectionRange(selectionStart, selectionEnd);
|
|
15340
|
+
this.renderHint();
|
|
15341
|
+
});
|
|
15133
15342
|
this.input.addEventListener("keydown", (e) => {
|
|
15134
15343
|
if (e.key !== "Enter") return;
|
|
15135
15344
|
const parsed = parseTimeframe(this.input.value);
|
|
@@ -15141,7 +15350,7 @@ var TimeframeQuick = class {
|
|
|
15141
15350
|
}
|
|
15142
15351
|
open(seed = "") {
|
|
15143
15352
|
this.dialog.show();
|
|
15144
|
-
this.input.value = seed;
|
|
15353
|
+
this.input.value = seed.toUpperCase();
|
|
15145
15354
|
this.renderHint();
|
|
15146
15355
|
setTimeout(() => {
|
|
15147
15356
|
this.input.focus();
|
|
@@ -15159,7 +15368,7 @@ var TimeframeQuick = class {
|
|
|
15159
15368
|
this.open(text);
|
|
15160
15369
|
return;
|
|
15161
15370
|
}
|
|
15162
|
-
this.input.value += text;
|
|
15371
|
+
this.input.value += text.toUpperCase();
|
|
15163
15372
|
this.renderHint();
|
|
15164
15373
|
}
|
|
15165
15374
|
close() {
|
|
@@ -15172,7 +15381,7 @@ var TimeframeQuick = class {
|
|
|
15172
15381
|
const raw = this.input.value.trim();
|
|
15173
15382
|
const parsed = parseTimeframe(raw);
|
|
15174
15383
|
if (!raw) {
|
|
15175
|
-
this.hint.textContent = "e.g. 15,
|
|
15384
|
+
this.hint.textContent = "e.g. 15, 4H, D, 3M";
|
|
15176
15385
|
delete this.hint.dataset.invalid;
|
|
15177
15386
|
} else if (parsed.valid) {
|
|
15178
15387
|
this.hint.textContent = parsed.label ?? "";
|
|
@@ -15275,7 +15484,7 @@ var ShortcutsHelp = class {
|
|
|
15275
15484
|
addRow("Delete the drawing under the cursor", ["Middle-click"]);
|
|
15276
15485
|
const s = doc.createElement("div");
|
|
15277
15486
|
s.className = "vela-sh-static";
|
|
15278
|
-
s.textContent = "Typing a letter opens the symbol search; typing a digit opens the timeframe entry.";
|
|
15487
|
+
s.textContent = "Typing a letter or 0 opens the symbol search; typing a digit from 1 to 9 opens the timeframe entry.";
|
|
15279
15488
|
this.list.appendChild(s);
|
|
15280
15489
|
}
|
|
15281
15490
|
};
|
|
@@ -18079,6 +18288,7 @@ var DrawingToolbar = class {
|
|
|
18079
18288
|
);
|
|
18080
18289
|
}
|
|
18081
18290
|
}
|
|
18291
|
+
announceSurface(fly, true, "menu", cell);
|
|
18082
18292
|
}
|
|
18083
18293
|
openMagnetFlyout() {
|
|
18084
18294
|
const cell = this.magnetCell;
|
|
@@ -18102,6 +18312,7 @@ var DrawingToolbar = class {
|
|
|
18102
18312
|
})
|
|
18103
18313
|
);
|
|
18104
18314
|
}
|
|
18315
|
+
announceSurface(fly, true, "menu", cell);
|
|
18105
18316
|
}
|
|
18106
18317
|
flyoutHeader(text) {
|
|
18107
18318
|
const t = this.theme;
|
|
@@ -18170,9 +18381,11 @@ var DrawingToolbar = class {
|
|
|
18170
18381
|
}
|
|
18171
18382
|
closeFlyout() {
|
|
18172
18383
|
this.starEls.clear();
|
|
18173
|
-
|
|
18174
|
-
|
|
18384
|
+
const fly = this.flyout;
|
|
18385
|
+
if (fly) {
|
|
18175
18386
|
this.flyout = null;
|
|
18387
|
+
announceSurface(fly, false, "menu", this.flyoutCell);
|
|
18388
|
+
fly.remove();
|
|
18176
18389
|
this.flyoutCell?.classList.remove("vela-open");
|
|
18177
18390
|
this.flyoutCell = null;
|
|
18178
18391
|
this.flyoutOwnerId = null;
|
|
@@ -18297,23 +18510,21 @@ function arrowHitHtml(svg) {
|
|
|
18297
18510
|
return `<span class="vela-dtb-hit vela-dtb-hit--arrow">${iconSpan(svg, 11)}</span>`;
|
|
18298
18511
|
}
|
|
18299
18512
|
|
|
18300
|
-
// src/renderers/native/chrome/
|
|
18301
|
-
var
|
|
18302
|
-
var
|
|
18513
|
+
// src/renderers/native/chrome/vela-logos.ts
|
|
18514
|
+
var VELA_MARK_SVG = '<svg viewBox="0 -142 161.66 148" aria-hidden="true"><g fill="currentColor"><path d="M0 -140L32.91 -140L77.52 -62.73L61.06 -34.24Z"/><path d="M161.66 -140L128.75 -140L84.14 -62.73L100.59 -34.24Z"/><path d="M80.83 -52.04L95.85 -26.02L80.83 0L65.81 -26.02Z"/></g></svg>';
|
|
18515
|
+
var VELA_LETTERS_SVG = '<svg viewBox="161.66 -142 310.57 148" aria-hidden="true"><g fill="currentColor"><path d="M226.3 1.17Q200.81 1.17 186.12 -12.99Q171.42 -27.15 171.42 -51.17Q171.42 -75.2 185.87 -89.6Q200.33 -104 224.25 -104Q247.4 -104 261.7 -90.58Q276.01 -77.15 276.3 -54.59Q276.3 -49.9 275.42 -45.02H200.81V-43.55Q201.2 -32.62 207.75 -26.42Q214.29 -20.21 225.03 -20.21Q233.72 -20.21 239.34 -23.93Q244.95 -27.64 246.81 -34.77H274.64Q272.1 -19.14 259.16 -8.98Q246.22 1.17 226.3 1.17ZM201.5 -63.38H247.88Q246.42 -72.75 240.27 -77.88Q234.12 -83.01 224.45 -83.01Q215.17 -83.01 208.92 -77.73Q202.67 -72.46 201.5 -63.38ZM287.54 0V-140.04H315.76V0ZM429.34 -23.83H432.56V0H418.11Q407.76 0 403.36 -4.44Q398.97 -8.89 399.16 -16.8Q387.83 1.17 365.57 1.17Q348.48 1.17 337.88 -6.74Q327.29 -14.65 327.29 -29Q327.29 -45.41 339.45 -54.3Q351.6 -63.18 374.55 -63.18H394.96V-68.16Q394.96 -75.2 390.08 -79.3Q385.2 -83.4 376.51 -83.4Q368.3 -83.4 363.03 -79.98Q357.76 -76.56 356.88 -70.8H329.73Q331.19 -85.84 344.08 -94.92Q356.97 -104 377.29 -104Q399.06 -104 410.88 -94.43Q422.7 -84.86 422.7 -66.6V-30.18Q422.7 -26.37 424.26 -25.1Q425.82 -23.83 429.34 -23.83ZM394.96 -42.38V-44.43H374.36Q365.47 -44.43 360.59 -40.92Q355.7 -37.4 355.7 -30.96Q355.7 -25.59 359.81 -22.41Q363.91 -19.24 370.94 -19.24Q381.97 -19.24 388.37 -25.54Q394.77 -31.84 394.96 -42.38Z"/><path fill-opacity="0.55" d="M437.44 -124.28H441.96V-113.41H445.34V-124.28H449.87V-127.4H437.44ZM451.43 -113.41H454.7V-123.29L458.97 -113.41H461.62L465.99 -123.29V-113.41H469.21V-127.4H464.53L460.42 -117.68L456.16 -127.4H451.43Z"/></g></svg>';
|
|
18303
18516
|
|
|
18304
18517
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
18305
|
-
var ATTRIBUTION_URL = "https://
|
|
18518
|
+
var ATTRIBUTION_URL = "https://velacharts.dev/?utm_source=vela&utm_medium=attribution";
|
|
18306
18519
|
var STYLE_ID20 = "vela-attribution-styles";
|
|
18307
18520
|
var CSS20 = `
|
|
18308
18521
|
.vela-attribution {
|
|
18309
18522
|
text-decoration: none;
|
|
18310
18523
|
display: flex;
|
|
18311
18524
|
align-items: center;
|
|
18312
|
-
/*
|
|
18313
|
-
|
|
18314
|
-
|
|
18315
|
-
reads wrong because the symbol's ink hangs low and the wordmark descends. */
|
|
18316
|
-
gap: 4px;
|
|
18525
|
+
/* No gutter: the V is the wordmark's first letter, and the letters' viewBox starts
|
|
18526
|
+
where the V's ends, so the kit's own letter spacing comes with them. */
|
|
18527
|
+
gap: 0;
|
|
18317
18528
|
}
|
|
18318
18529
|
.vela-attribution .vela-attr-symbol {
|
|
18319
18530
|
flex: none;
|
|
@@ -18321,7 +18532,7 @@ var CSS20 = `
|
|
|
18321
18532
|
line-height: 0;
|
|
18322
18533
|
}
|
|
18323
18534
|
.vela-attribution .vela-attr-symbol svg {
|
|
18324
|
-
height:
|
|
18535
|
+
height: 24px;
|
|
18325
18536
|
width: auto;
|
|
18326
18537
|
display: block;
|
|
18327
18538
|
filter: drop-shadow(0 1px 2px var(--vela-attr-shadow, rgba(0,0,0,0.45)));
|
|
@@ -18332,15 +18543,13 @@ var CSS20 = `
|
|
|
18332
18543
|
opacity: 0;
|
|
18333
18544
|
display: flex;
|
|
18334
18545
|
align-items: center;
|
|
18335
|
-
/*
|
|
18336
|
-
position: relative;
|
|
18337
|
-
top: 3px;
|
|
18546
|
+
/* Same height and baseline as the V (one viewBox height), so no vertical offset. */
|
|
18338
18547
|
transform: translateX(-8px);
|
|
18339
18548
|
transition: max-width 0.3s ease, opacity 0.25s ease, transform 0.3s ease;
|
|
18340
18549
|
flex: none;
|
|
18341
18550
|
}
|
|
18342
18551
|
.vela-attribution .vela-attr-wordmark svg {
|
|
18343
|
-
height:
|
|
18552
|
+
height: 24px;
|
|
18344
18553
|
width: auto;
|
|
18345
18554
|
display: block;
|
|
18346
18555
|
filter: drop-shadow(0 1px 2px var(--vela-attr-shadow, rgba(0,0,0,0.45)));
|
|
@@ -18355,14 +18564,11 @@ var CSS20 = `
|
|
|
18355
18564
|
(data-layout): the workspace's single grid-wide mark lives OUTSIDE any renderer
|
|
18356
18565
|
container, so only the shell attribute reaches it. */
|
|
18357
18566
|
[data-vela-layout='mobile'] .vela-attribution .vela-attr-symbol svg,
|
|
18358
|
-
[data-layout='mobile'] .vela-attribution .vela-attr-symbol svg { height:
|
|
18567
|
+
[data-layout='mobile'] .vela-attribution .vela-attr-symbol svg { height: 20px; }
|
|
18359
18568
|
[data-vela-layout='mobile'] .vela-attribution .vela-attr-wordmark,
|
|
18360
|
-
[data-layout='mobile'] .vela-attribution .vela-attr-wordmark {
|
|
18361
|
-
top: 2px;
|
|
18362
|
-
transform: translateX(-6px);
|
|
18363
|
-
}
|
|
18569
|
+
[data-layout='mobile'] .vela-attribution .vela-attr-wordmark { transform: translateX(-6px); }
|
|
18364
18570
|
[data-vela-layout='mobile'] .vela-attribution .vela-attr-wordmark svg,
|
|
18365
|
-
[data-layout='mobile'] .vela-attribution .vela-attr-wordmark svg { height:
|
|
18571
|
+
[data-layout='mobile'] .vela-attribution .vela-attr-wordmark svg { height: 20px; }
|
|
18366
18572
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
18367
18573
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
18368
18574
|
`;
|
|
@@ -18419,10 +18625,10 @@ function createAttributionMark(doc, background) {
|
|
|
18419
18625
|
const symbol = doc.createElement("span");
|
|
18420
18626
|
symbol.className = "vela-attr-symbol";
|
|
18421
18627
|
symbol.setAttribute("aria-hidden", "true");
|
|
18422
|
-
symbol.innerHTML =
|
|
18628
|
+
symbol.innerHTML = VELA_MARK_SVG;
|
|
18423
18629
|
const wordmark = doc.createElement("span");
|
|
18424
18630
|
wordmark.className = "vela-attr-wordmark";
|
|
18425
|
-
wordmark.innerHTML =
|
|
18631
|
+
wordmark.innerHTML = VELA_LETTERS_SVG;
|
|
18426
18632
|
a.append(symbol, wordmark);
|
|
18427
18633
|
return a;
|
|
18428
18634
|
}
|
|
@@ -18848,7 +19054,7 @@ var IndicatorHandleImpl = class {
|
|
|
18848
19054
|
this.controller.applyProps(this.id, values);
|
|
18849
19055
|
}
|
|
18850
19056
|
updateCode(source) {
|
|
18851
|
-
this.controller.updateCode(this.id, source);
|
|
19057
|
+
return this.controller.updateCode(this.id, source);
|
|
18852
19058
|
}
|
|
18853
19059
|
setVisible(visible) {
|
|
18854
19060
|
this.controller.setVisible(this.id, visible);
|
|
@@ -20711,20 +20917,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20711
20917
|
/**
|
|
20712
20918
|
* IndicatorController: replace a SCRIPT indicator's source in place (see
|
|
20713
20919
|
* {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
|
|
20714
|
-
* computing until the new source has compiled
|
|
20715
|
-
*
|
|
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.
|
|
20716
20923
|
*/
|
|
20717
20924
|
updateCode(id, source) {
|
|
20718
20925
|
const record = this.registry.get(id);
|
|
20719
20926
|
const handle = this.handles.get(id);
|
|
20720
|
-
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.`) });
|
|
20721
20928
|
if (record.native) {
|
|
20722
20929
|
console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
|
|
20723
|
-
return;
|
|
20930
|
+
return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`) });
|
|
20724
20931
|
}
|
|
20725
|
-
|
|
20726
|
-
record.
|
|
20727
|
-
|
|
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
|
+
});
|
|
20728
20940
|
}
|
|
20729
20941
|
async swapSource(id, source, handle) {
|
|
20730
20942
|
const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
|
|
@@ -20735,26 +20947,41 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20735
20947
|
const record2 = this.registry.get(id);
|
|
20736
20948
|
if (record2?.pendingSource === source) {
|
|
20737
20949
|
record2.pendingSource = void 0;
|
|
20738
|
-
this.
|
|
20950
|
+
this.rejectCode(id, record2, handle, err);
|
|
20739
20951
|
}
|
|
20740
20952
|
return;
|
|
20741
20953
|
}
|
|
20742
20954
|
const record = this.registry.get(id);
|
|
20743
20955
|
if (!record || record.pendingSource !== source) return;
|
|
20744
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);
|
|
20745
20968
|
record.source = source;
|
|
20746
20969
|
record.engine = engine;
|
|
20747
20970
|
record.prepared = prepared;
|
|
20748
20971
|
handle.setSource(source);
|
|
20749
|
-
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
|
|
20750
|
-
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
|
|
20751
20972
|
handle.setSchema(prepared.inputs);
|
|
20752
20973
|
handle.setPropsSchema(prepared.props ?? []);
|
|
20753
20974
|
record.session?.stop();
|
|
20754
20975
|
record.session = void 0;
|
|
20755
20976
|
record.pendingStructural = true;
|
|
20756
20977
|
record.pendingCause = "code";
|
|
20757
|
-
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 };
|
|
20758
20985
|
if (record.renderHandle) {
|
|
20759
20986
|
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
20760
20987
|
this.setLoading(record, true);
|
|
@@ -20762,6 +20989,46 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20762
20989
|
this.mountLoadingPlaceholder(id, record);
|
|
20763
20990
|
}
|
|
20764
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 });
|
|
20765
21032
|
}
|
|
20766
21033
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
20767
21034
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
@@ -20776,6 +21043,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20776
21043
|
removeIndicator(id) {
|
|
20777
21044
|
const record = this.registry.remove(id);
|
|
20778
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.`) });
|
|
20779
21047
|
record?.session?.stop();
|
|
20780
21048
|
if (record?.native?.started) record.native.instance.stop();
|
|
20781
21049
|
this.handles.delete(id);
|
|
@@ -20803,6 +21071,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20803
21071
|
if (!visible) {
|
|
20804
21072
|
record.session?.stop();
|
|
20805
21073
|
record.session = void 0;
|
|
21074
|
+
if (record.codeTrial) {
|
|
21075
|
+
record.codeTrial = void 0;
|
|
21076
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21077
|
+
}
|
|
20806
21078
|
if (record.native?.started) record.native.instance.suspend();
|
|
20807
21079
|
if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
|
|
20808
21080
|
else if (record.native) this.mountHiddenNativeRow(id, record);
|
|
@@ -20890,6 +21162,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20890
21162
|
this.viewportUnsub?.();
|
|
20891
21163
|
this.paneActionUnsub?.();
|
|
20892
21164
|
for (const record of this.registry.all()) {
|
|
21165
|
+
this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
|
|
20893
21166
|
record.session?.stop();
|
|
20894
21167
|
if (record.native?.started) record.native.instance.stop();
|
|
20895
21168
|
}
|
|
@@ -20958,7 +21231,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20958
21231
|
const record = this.registry.get(id);
|
|
20959
21232
|
if (!record || !record.engine || !record.prepared || record.hidden) return;
|
|
20960
21233
|
const mode = this.config.live && !record.prepared.reactsToViewport && record.engine.capabilities.streaming ? "live" : "static";
|
|
20961
|
-
|
|
21234
|
+
const prepared = record.prepared;
|
|
21235
|
+
const session = record.engine.execute(
|
|
20962
21236
|
{
|
|
20963
21237
|
prepared: record.prepared,
|
|
20964
21238
|
market: this.market(),
|
|
@@ -20979,6 +21253,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20979
21253
|
const cause = record.pendingCause ?? "history";
|
|
20980
21254
|
if (!this.applyModel(id, model)) return;
|
|
20981
21255
|
record.pendingCause = void 0;
|
|
21256
|
+
if (record.codeTrial) {
|
|
21257
|
+
record.codeTrial = void 0;
|
|
21258
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21259
|
+
}
|
|
20982
21260
|
this.emitContextChanged(id);
|
|
20983
21261
|
this.emitScriptRun(id, cause, first);
|
|
20984
21262
|
},
|
|
@@ -20989,9 +21267,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20989
21267
|
handle.emit("alert", { id: a.id, message: a.message, title: a.title, time: a.time });
|
|
20990
21268
|
},
|
|
20991
21269
|
onWarning: (w) => this.events.emit("warning", w),
|
|
20992
|
-
onError: (err) =>
|
|
21270
|
+
onError: (err) => {
|
|
21271
|
+
if (record.codeTrial) this.rejectCode(id, record, handle, err);
|
|
21272
|
+
else this.fail(id, handle, err);
|
|
21273
|
+
}
|
|
20993
21274
|
}
|
|
20994
21275
|
);
|
|
21276
|
+
if (record.prepared !== prepared) {
|
|
21277
|
+
session.stop();
|
|
21278
|
+
return;
|
|
21279
|
+
}
|
|
21280
|
+
record.session = session;
|
|
20995
21281
|
this.emitContextChanged(id);
|
|
20996
21282
|
}
|
|
20997
21283
|
/**
|
|
@@ -21492,7 +21778,7 @@ function yieldToPaint() {
|
|
|
21492
21778
|
}
|
|
21493
21779
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
21494
21780
|
}
|
|
21495
|
-
function valuesOnSchema(schema, previous) {
|
|
21781
|
+
function valuesOnSchema(schema, previous, oldSchema = []) {
|
|
21496
21782
|
const out = {};
|
|
21497
21783
|
const declared = /* @__PURE__ */ new Set();
|
|
21498
21784
|
for (const s of schema) {
|
|
@@ -21500,9 +21786,22 @@ function valuesOnSchema(schema, previous) {
|
|
|
21500
21786
|
declared.add(s.key);
|
|
21501
21787
|
declared.add(s.title);
|
|
21502
21788
|
}
|
|
21503
|
-
|
|
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
|
+
}
|
|
21504
21799
|
return out;
|
|
21505
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
|
+
}
|
|
21506
21805
|
function blankedModel(model) {
|
|
21507
21806
|
return {
|
|
21508
21807
|
...model,
|
|
@@ -24432,15 +24731,23 @@ var Batch = class {
|
|
|
24432
24731
|
this.seg(x + w, y + h, x, y + h, lineWidth, c);
|
|
24433
24732
|
this.seg(x, y + h, x, y, lineWidth, c);
|
|
24434
24733
|
}
|
|
24435
|
-
/**
|
|
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
|
+
*/
|
|
24436
24739
|
circle(cx, cy, r, c, segments = 16) {
|
|
24437
|
-
|
|
24740
|
+
const ext = r + AA_PAD;
|
|
24741
|
+
let px = cx + ext;
|
|
24438
24742
|
let py = cy;
|
|
24743
|
+
this.ensure(segments * 3);
|
|
24439
24744
|
for (let i = 1; i <= segments; i += 1) {
|
|
24440
24745
|
const a = i / segments * Math.PI * 2;
|
|
24441
|
-
const nx = cx + Math.cos(a) *
|
|
24442
|
-
const ny = cy + Math.sin(a) *
|
|
24443
|
-
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);
|
|
24444
24751
|
px = nx;
|
|
24445
24752
|
py = ny;
|
|
24446
24753
|
}
|
|
@@ -24621,7 +24928,7 @@ var WebGL2Backend = class {
|
|
|
24621
24928
|
}
|
|
24622
24929
|
mount(canvas) {
|
|
24623
24930
|
this.canvas = canvas;
|
|
24624
|
-
this.gl = canvas.getContext("webgl2", { alpha: true, antialias:
|
|
24931
|
+
this.gl = canvas.getContext("webgl2", { alpha: true, antialias: false, depth: false, stencil: false, premultipliedAlpha: true });
|
|
24625
24932
|
if (!this.gl) return;
|
|
24626
24933
|
if (!this.initGL()) {
|
|
24627
24934
|
this.gl = null;
|
|
@@ -25880,6 +26187,12 @@ var InputController = class {
|
|
|
25880
26187
|
this.lastTapT = 0;
|
|
25881
26188
|
this.lastTapX = 0;
|
|
25882
26189
|
this.lastTapY = 0;
|
|
26190
|
+
// Whether the last two primary presses were claimed by the drawings layer. A double-click
|
|
26191
|
+
// is the last press plus the one before it; a tool-driven click pair must never fall through
|
|
26192
|
+
// to the pane maximize toggle, even when the tool disarmed itself between the two clicks
|
|
26193
|
+
// (a finished placement leaves plain cursor mode for the second click).
|
|
26194
|
+
this.lastPressDrawing = false;
|
|
26195
|
+
this.prevPressDrawing = false;
|
|
25883
26196
|
this.onTouchContextMenu = (e) => {
|
|
25884
26197
|
if (this.touches.size === 0) return;
|
|
25885
26198
|
if (!(e.target instanceof Node) || !this.el?.contains(e.target)) return;
|
|
@@ -25934,19 +26247,24 @@ var InputController = class {
|
|
|
25934
26247
|
this.moved = false;
|
|
25935
26248
|
this.startX = x;
|
|
25936
26249
|
this.startY = y;
|
|
26250
|
+
this.prevPressDrawing = this.lastPressDrawing;
|
|
26251
|
+
this.lastPressDrawing = false;
|
|
25937
26252
|
if (this.deps.drawingsClaim?.(x, y)) {
|
|
25938
26253
|
this.region = "drawing";
|
|
26254
|
+
this.lastPressDrawing = true;
|
|
25939
26255
|
this.deps.drawingsPointerDown?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
25940
26256
|
this.capture(e.pointerId);
|
|
25941
26257
|
return;
|
|
25942
26258
|
}
|
|
25943
26259
|
if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
25944
26260
|
this.region = "drawing";
|
|
26261
|
+
this.lastPressDrawing = true;
|
|
25945
26262
|
this.capture(e.pointerId);
|
|
25946
26263
|
return;
|
|
25947
26264
|
}
|
|
25948
26265
|
if ((e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
|
|
25949
26266
|
this.region = "drawing";
|
|
26267
|
+
this.lastPressDrawing = true;
|
|
25950
26268
|
this.capture(e.pointerId);
|
|
25951
26269
|
return;
|
|
25952
26270
|
}
|
|
@@ -26062,7 +26380,7 @@ var InputController = class {
|
|
|
26062
26380
|
this.deps.drawingsPointerUp?.(x, y, this.snapMode(e));
|
|
26063
26381
|
} else if (tapRelease && this.region === "data") {
|
|
26064
26382
|
this.deps.onClick(x, y);
|
|
26065
|
-
} else if (this.dragging && this.region === "data") {
|
|
26383
|
+
} else if (this.dragging && this.region === "data" && wasTouch) {
|
|
26066
26384
|
const stale = e.timeStamp - this.lastT > FLING_STALE_MS;
|
|
26067
26385
|
if (!stale && Math.abs(this.vx) > FLING_MIN_SPEED) {
|
|
26068
26386
|
const pitch = this.deps.getCoords().pxPerBar();
|
|
@@ -26268,6 +26586,7 @@ var InputController = class {
|
|
|
26268
26586
|
if (region === "price") this.deps.resetPriceScale(x, y);
|
|
26269
26587
|
else if (region === "separator") this.deps.resetPaneSize(y);
|
|
26270
26588
|
else if (region === "time") this.deps.resetView();
|
|
26589
|
+
else if (this.lastPressDrawing || this.prevPressDrawing) return;
|
|
26271
26590
|
else this.deps.dataDblClick(x, y);
|
|
26272
26591
|
}
|
|
26273
26592
|
};
|
|
@@ -28433,6 +28752,7 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
|
|
|
28433
28752
|
const step = pickStep(span / Math.max(1, target));
|
|
28434
28753
|
const zFrom = fromMs + offsetMs;
|
|
28435
28754
|
const zTo = toMs + offsetMs;
|
|
28755
|
+
if (step >= MONTH) return calendarTicks(zFrom, zTo, step >= YEAR ? 12 : step >= 3 * MONTH ? 3 : 1, offsetMs);
|
|
28436
28756
|
const first = Math.ceil(zFrom / step) * step;
|
|
28437
28757
|
const out = [];
|
|
28438
28758
|
for (let zt = first; zt <= zTo; zt += step) {
|
|
@@ -28463,6 +28783,18 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
|
|
|
28463
28783
|
}
|
|
28464
28784
|
return out;
|
|
28465
28785
|
}
|
|
28786
|
+
function calendarTicks(zFrom, zTo, months, offsetMs) {
|
|
28787
|
+
const d0 = new Date(zFrom);
|
|
28788
|
+
let idx = d0.getUTCFullYear() * 12 + d0.getUTCMonth();
|
|
28789
|
+
if (Date.UTC(Math.floor(idx / 12), idx % 12, 1) < zFrom) idx += 1;
|
|
28790
|
+
idx = Math.ceil(idx / months) * months;
|
|
28791
|
+
const out = [];
|
|
28792
|
+
for (let zt = Date.UTC(Math.floor(idx / 12), idx % 12, 1); zt <= zTo; idx += months, zt = Date.UTC(Math.floor(idx / 12), idx % 12, 1)) {
|
|
28793
|
+
const label = months === 12 ? String(Math.floor(idx / 12)) : MONTHS[idx % 12];
|
|
28794
|
+
out.push({ time: zt - offsetMs, label, major: true });
|
|
28795
|
+
}
|
|
28796
|
+
return out;
|
|
28797
|
+
}
|
|
28466
28798
|
|
|
28467
28799
|
// src/renderers/native/chrome/countdown.ts
|
|
28468
28800
|
function countdownText(barOpen, barMs, now2) {
|
|
@@ -32414,6 +32746,12 @@ var DrawingInteraction = class {
|
|
|
32414
32746
|
activeDragId() {
|
|
32415
32747
|
return this.state.kind === "pressed" && this.state.moved ? this.state.id : null;
|
|
32416
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
|
+
}
|
|
32417
32755
|
/** The drawing under a press that has not been released yet (drag or click undecided), or null. */
|
|
32418
32756
|
pressedId() {
|
|
32419
32757
|
return this.state.kind === "pressed" ? this.state.id : null;
|
|
@@ -33072,6 +33410,17 @@ var DrawingSettingsDialog = class {
|
|
|
33072
33410
|
}
|
|
33073
33411
|
}));
|
|
33074
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
|
+
}
|
|
33075
33424
|
levels.forEach((lv, i) => {
|
|
33076
33425
|
const row = document.createElement("div");
|
|
33077
33426
|
row.className = "vela-field-span";
|
|
@@ -34225,6 +34574,8 @@ var UserDrawingController = class {
|
|
|
34225
34574
|
this.drawings = [];
|
|
34226
34575
|
/** Ids painted on an interleave layer this frame — the top canvas paints only their handles. */
|
|
34227
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();
|
|
34228
34579
|
/** Cached slice canvases, keyed `paneId|beforeZ`, reused across frames to avoid churn. */
|
|
34229
34580
|
this.sliceCache = /* @__PURE__ */ new Map();
|
|
34230
34581
|
/** Series boundaries per pane as of the last `prepareSlices` — lets a repaint between data
|
|
@@ -34312,7 +34663,7 @@ var UserDrawingController = class {
|
|
|
34312
34663
|
selectedIds: () => this.selectedIds,
|
|
34313
34664
|
emit: (i) => this.emit(i),
|
|
34314
34665
|
changed: () => {
|
|
34315
|
-
this.
|
|
34666
|
+
this.syncLift();
|
|
34316
34667
|
this.render();
|
|
34317
34668
|
},
|
|
34318
34669
|
openSettings: (id, x, y) => this.openSettingsById(id, x, y),
|
|
@@ -34938,6 +35289,20 @@ var UserDrawingController = class {
|
|
|
34938
35289
|
/** A drawing that paints inside the series stack changed (content, not hover): its pixels
|
|
34939
35290
|
* live in the backend composite, so this layer alone can't show the change — ask for a
|
|
34940
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
|
+
}
|
|
34941
35306
|
invalidateSlices() {
|
|
34942
35307
|
if (this.sliced.size > 0 || this.drawings.some((d) => this.isInterleaved(d))) this.deps.requestDataPaint();
|
|
34943
35308
|
}
|
|
@@ -34956,7 +35321,7 @@ var UserDrawingController = class {
|
|
|
34956
35321
|
const theme = this.deps.theme();
|
|
34957
35322
|
const buckets = /* @__PURE__ */ new Map();
|
|
34958
35323
|
for (const d of this.drawings) {
|
|
34959
|
-
if (!d.visible) continue;
|
|
35324
|
+
if (!d.visible || this.lifted.has(d.id)) continue;
|
|
34960
35325
|
const beforeZ = sliceKeyFor(d.zIndex, this.lastBounds.get(d.paneId) ?? []);
|
|
34961
35326
|
if (beforeZ === null) continue;
|
|
34962
35327
|
const key = `${d.paneId}|${beforeZ}`;
|
|
@@ -35008,8 +35373,9 @@ var UserDrawingController = class {
|
|
|
35008
35373
|
mutedLabel: edited instanceof TextLabel ? edited.id : null
|
|
35009
35374
|
};
|
|
35010
35375
|
this.painter.seriesLook = this.deps.seriesLook();
|
|
35011
|
-
|
|
35012
|
-
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));
|
|
35013
35379
|
const clones = this.interaction.dragClones();
|
|
35014
35380
|
if (clones) this.painter.paintAll(ctx, clones, proj, this.deps.theme(), { selected: new Set(clones.map((c) => c.id)) });
|
|
35015
35381
|
this.layoutTextEditor();
|
|
@@ -44665,6 +45031,9 @@ var CSS21 = `
|
|
|
44665
45031
|
.vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
|
|
44666
45032
|
.vela-statusline .vela-sl-market > [hidden] { display: none !important; }
|
|
44667
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; }
|
|
44668
45037
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
44669
45038
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
44670
45039
|
/* The change value wears the SAME ink as the OHLC values (set inline per render) \u2014
|
|
@@ -44782,6 +45151,10 @@ var Statusline = class {
|
|
|
44782
45151
|
this.fitRO = null;
|
|
44783
45152
|
/** The ladder rung currently applied — see {@link fit}. */
|
|
44784
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();
|
|
44785
45158
|
this.onContextMenu = (e) => {
|
|
44786
45159
|
if (!this.menu || !this.menuHooks) return;
|
|
44787
45160
|
e.preventDefault();
|
|
@@ -44845,6 +45218,7 @@ var Statusline = class {
|
|
|
44845
45218
|
this.fitRO = new ResizeObserver(() => this.fit());
|
|
44846
45219
|
this.fitRO.observe(host);
|
|
44847
45220
|
}
|
|
45221
|
+
doc.fonts?.addEventListener("loadingdone", this.onFontsLoaded);
|
|
44848
45222
|
this.marketTip = new Tooltip(this.marketEl, { content: MARKET_LABELS.open, placement: "bottom" });
|
|
44849
45223
|
this.eyeTip = new Tooltip(this.eyeEl, { content: "Show chart", placement: "bottom" });
|
|
44850
45224
|
this.setMarketStatus("open");
|
|
@@ -44897,6 +45271,7 @@ var Statusline = class {
|
|
|
44897
45271
|
* (detached host, node tests) nothing overflows, so the widest rung stays.
|
|
44898
45272
|
*/
|
|
44899
45273
|
fit() {
|
|
45274
|
+
this.fitKey = this.widthKey();
|
|
44900
45275
|
this.syncParts();
|
|
44901
45276
|
const seg = segmentVisibility(this.parts, this.chartHidden);
|
|
44902
45277
|
const overflows = () => this.el.scrollWidth > this.el.clientWidth;
|
|
@@ -45053,6 +45428,7 @@ var Statusline = class {
|
|
|
45053
45428
|
this.detach();
|
|
45054
45429
|
this.fitRO?.disconnect();
|
|
45055
45430
|
this.fitRO = null;
|
|
45431
|
+
this.el.ownerDocument.fonts?.removeEventListener("loadingdone", this.onFontsLoaded);
|
|
45056
45432
|
this.el.removeEventListener("contextmenu", this.onContextMenu);
|
|
45057
45433
|
this.menu?.destroy();
|
|
45058
45434
|
this.menu = null;
|
|
@@ -45068,7 +45444,22 @@ var Statusline = class {
|
|
|
45068
45444
|
}
|
|
45069
45445
|
render() {
|
|
45070
45446
|
if (this.menuHooks) this.setChartHidden(!this.menuHooks.chartVisible());
|
|
45071
|
-
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");
|
|
45072
45463
|
}
|
|
45073
45464
|
/** Write the value readout for the current bar at the current ladder level. */
|
|
45074
45465
|
renderValues() {
|
|
@@ -46702,7 +47093,7 @@ var ChartCell = class {
|
|
|
46702
47093
|
* then the manifest in the host's own order. */
|
|
46703
47094
|
libraryRows() {
|
|
46704
47095
|
return [
|
|
46705
|
-
...this.supportedNatives().map((n) => ({ name: n.title, category: "
|
|
47096
|
+
...this.supportedNatives().map((n) => ({ name: n.title, category: "Built-in", native: true, nativeType: n.type, beta: n.beta })),
|
|
46706
47097
|
...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
|
|
46707
47098
|
];
|
|
46708
47099
|
}
|
|
@@ -47708,11 +48099,11 @@ function resolveTyping(key, open2) {
|
|
|
47708
48099
|
if (open2.dialogs > 0) {
|
|
47709
48100
|
if (key.length !== 1) return null;
|
|
47710
48101
|
if (open2.symbolSearch) return { target: "symbol", text: key.toUpperCase() };
|
|
47711
|
-
if (open2.timeframeEntry) return { target: "timeframe", text: key };
|
|
48102
|
+
if (open2.timeframeEntry) return { target: "timeframe", text: key.toUpperCase() };
|
|
47712
48103
|
return null;
|
|
47713
48104
|
}
|
|
47714
|
-
if (/^[a-zA-
|
|
47715
|
-
if (/^[
|
|
48105
|
+
if (/^[a-zA-Z0]$/.test(key)) return { target: "symbol", text: key.toUpperCase() };
|
|
48106
|
+
if (/^[1-9]$/.test(key)) return { target: "timeframe", text: key };
|
|
47716
48107
|
return null;
|
|
47717
48108
|
}
|
|
47718
48109
|
var VelaWorkspace = class {
|
|
@@ -49450,7 +49841,7 @@ var VelaWorkspace = class {
|
|
|
49450
49841
|
}
|
|
49451
49842
|
});
|
|
49452
49843
|
}
|
|
49453
|
-
/** Bare-typing router: letters → symbol search (seeded), digits → timeframe entry. */
|
|
49844
|
+
/** Bare-typing router: letters and `0` → symbol search (seeded), digits 1–9 → timeframe entry. */
|
|
49454
49845
|
routeTyping(ev) {
|
|
49455
49846
|
if (this.destroyed) return;
|
|
49456
49847
|
if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
|