@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/widget.cjs
CHANGED
|
@@ -1256,6 +1256,7 @@ registerIcon("expand", S('<rect x="2.2" y="2.2" width="11.6" height="11.6" rx="1
|
|
|
1256
1256
|
registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
|
|
1257
1257
|
registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
|
|
1258
1258
|
registerIcon("maximize", S('<path d="M2.5 6V3a.5.5 0 0 1 .5-.5h3M10 2.5h3a.5.5 0 0 1 .5.5v3M13.5 10v3a.5.5 0 0 1-.5.5h-3M6 13.5H3a.5.5 0 0 1-.5-.5v-3"/>'));
|
|
1259
|
+
registerIcon("restore", S('<path d="M6 2.5v3a.5.5 0 0 1-.5.5h-3M13.5 6h-3a.5.5 0 0 1-.5-.5v-3M10 13.5v-3a.5.5 0 0 1 .5-.5h3M2.5 10h3a.5.5 0 0 1 .5.5v3"/>'));
|
|
1259
1260
|
registerIcon("restore", S('<path d="M6.2 2.5v3.7H2.5M9.8 13.5V9.8h3.7M13.5 6.2H9.8V2.5M2.5 9.8h3.7v3.7"/>'));
|
|
1260
1261
|
registerIcon("star", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>'));
|
|
1261
1262
|
registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>', 'fill="currentColor"'));
|
|
@@ -1337,6 +1338,17 @@ function nextUid(prefix) {
|
|
|
1337
1338
|
return `${prefix}-${++uid}`;
|
|
1338
1339
|
}
|
|
1339
1340
|
|
|
1341
|
+
// src/ui/surface-events.ts
|
|
1342
|
+
var SURFACE_OPEN_EVENT = "vela:surface-open";
|
|
1343
|
+
var SURFACE_CLOSE_EVENT = "vela:surface-close";
|
|
1344
|
+
function announceSurface(el, open2, kind, trigger = null) {
|
|
1345
|
+
const View = el.ownerDocument.defaultView;
|
|
1346
|
+
if (!View) return;
|
|
1347
|
+
el.dispatchEvent(
|
|
1348
|
+
new View.CustomEvent(open2 ? SURFACE_OPEN_EVENT : SURFACE_CLOSE_EVENT, { bubbles: true, composed: true, detail: { kind, trigger } })
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1340
1352
|
// src/ui/keymap.ts
|
|
1341
1353
|
var KEY_ALIASES = {
|
|
1342
1354
|
esc: "escape",
|
|
@@ -1698,6 +1710,8 @@ var Surface = class _Surface {
|
|
|
1698
1710
|
/** Trigger rect captured when this level opened — the list stays put if the
|
|
1699
1711
|
* trigger then moves (favorite chips shifting the caret). */
|
|
1700
1712
|
this.pinnedAnchor = null;
|
|
1713
|
+
/** The open state last announced — see {@link announce}. */
|
|
1714
|
+
this.shown = false;
|
|
1701
1715
|
this.doc = doc;
|
|
1702
1716
|
this.host = opts.host;
|
|
1703
1717
|
this.onSelect = opts.onSelect;
|
|
@@ -1711,6 +1725,7 @@ var Surface = class _Surface {
|
|
|
1711
1725
|
this.positioner.appendChild(this.list);
|
|
1712
1726
|
this.host.appendChild(this.positioner);
|
|
1713
1727
|
const trigger = opts.trigger;
|
|
1728
|
+
this.opener = trigger ?? opts.opener ?? null;
|
|
1714
1729
|
this.ctrl = menuController({
|
|
1715
1730
|
items: [],
|
|
1716
1731
|
id: opts.id,
|
|
@@ -1732,15 +1747,21 @@ var Surface = class _Surface {
|
|
|
1732
1747
|
if (opts.triggerId && trigger) trigger.id = opts.triggerId;
|
|
1733
1748
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
1734
1749
|
const api = this.ctrl.connect(service);
|
|
1750
|
+
if (this.shown && !api.open) this.announce(false);
|
|
1735
1751
|
if (trigger) vanilla.spreadProps(trigger, api.getTriggerProps(), this.mid);
|
|
1736
1752
|
vanilla.spreadProps(this.positioner, api.getPositionerProps(), this.mid);
|
|
1737
1753
|
vanilla.spreadProps(this.list, api.getContentProps(), this.mid);
|
|
1738
1754
|
this.project(api);
|
|
1755
|
+
if (api.open && !this.shown) this.announce(true);
|
|
1739
1756
|
});
|
|
1740
1757
|
}
|
|
1741
1758
|
get api() {
|
|
1742
1759
|
return this.ctrl.connect(this.handle.service);
|
|
1743
1760
|
}
|
|
1761
|
+
announce(open2) {
|
|
1762
|
+
this.shown = open2;
|
|
1763
|
+
announceSurface(this.list, open2, "menu", this.opener);
|
|
1764
|
+
}
|
|
1744
1765
|
setItems(items) {
|
|
1745
1766
|
this.items = items;
|
|
1746
1767
|
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
@@ -1758,6 +1779,7 @@ var Surface = class _Surface {
|
|
|
1758
1779
|
for (const sub2 of this.subs.values()) sub2.destroy();
|
|
1759
1780
|
this.subs.clear();
|
|
1760
1781
|
this.handle.stop();
|
|
1782
|
+
if (this.shown) this.announce(false);
|
|
1761
1783
|
this.positioner.remove();
|
|
1762
1784
|
}
|
|
1763
1785
|
byId(id) {
|
|
@@ -1851,6 +1873,7 @@ var Surface = class _Surface {
|
|
|
1851
1873
|
const sub2 = new _Surface(doc, {
|
|
1852
1874
|
host: this.host,
|
|
1853
1875
|
placement: "right-start",
|
|
1876
|
+
opener: li,
|
|
1854
1877
|
onSelect: this.onSelect,
|
|
1855
1878
|
onFavorite: this.onFavorite,
|
|
1856
1879
|
checkmarks: this.checkmarks,
|
|
@@ -2070,16 +2093,21 @@ var Popover = class {
|
|
|
2070
2093
|
this.hide();
|
|
2071
2094
|
};
|
|
2072
2095
|
const onReflow = () => this.place();
|
|
2073
|
-
setTimeout(() =>
|
|
2096
|
+
setTimeout(() => {
|
|
2097
|
+
if (this.onOutside === onOutside) document.addEventListener("pointerdown", onOutside, true);
|
|
2098
|
+
}, 0);
|
|
2074
2099
|
document.addEventListener("keydown", onKey, true);
|
|
2075
2100
|
window.addEventListener("resize", onReflow, true);
|
|
2076
2101
|
document.addEventListener("scroll", onReflow, true);
|
|
2077
2102
|
this.onOutside = onOutside;
|
|
2078
2103
|
this.onKey = onKey;
|
|
2079
2104
|
this.onReflow = onReflow;
|
|
2105
|
+
announceSurface(this.el, true, "popover", this.trigger);
|
|
2080
2106
|
}
|
|
2081
2107
|
hide() {
|
|
2082
2108
|
if (!this.shown) return;
|
|
2109
|
+
this.shown = false;
|
|
2110
|
+
announceSurface(this.el, false, "popover", this.trigger);
|
|
2083
2111
|
if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
|
|
2084
2112
|
if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
|
|
2085
2113
|
if (this.onReflow) {
|
|
@@ -2099,7 +2127,6 @@ var Popover = class {
|
|
|
2099
2127
|
} else {
|
|
2100
2128
|
this.el.remove();
|
|
2101
2129
|
}
|
|
2102
|
-
this.shown = false;
|
|
2103
2130
|
if (open === this) open = null;
|
|
2104
2131
|
this.ctrl.onClose?.();
|
|
2105
2132
|
}
|
|
@@ -2305,6 +2332,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
2305
2332
|
`;
|
|
2306
2333
|
var Dialog = class {
|
|
2307
2334
|
constructor(opts = {}) {
|
|
2335
|
+
this.opener = null;
|
|
2336
|
+
this.wasOpen = false;
|
|
2308
2337
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
2309
2338
|
injectStyles(DIALOG_STYLE_ID, DIALOG_CSS, doc);
|
|
2310
2339
|
const host = opts.host ?? doc.body;
|
|
@@ -2401,6 +2430,7 @@ var Dialog = class {
|
|
|
2401
2430
|
const mid = String(this.ctrl.props.id);
|
|
2402
2431
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
2403
2432
|
const api = this.ctrl.connect(service);
|
|
2433
|
+
if (this.wasOpen && !api.open) this.announce(false);
|
|
2404
2434
|
vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
|
|
2405
2435
|
vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
|
|
2406
2436
|
vanilla.spreadProps(this.panel, api.getContentProps(), mid);
|
|
@@ -2408,12 +2438,27 @@ var Dialog = class {
|
|
|
2408
2438
|
vanilla.spreadProps(close, api.getCloseTriggerProps(), mid);
|
|
2409
2439
|
this.backdrop.style.display = api.open ? "" : "none";
|
|
2410
2440
|
this.positioner.style.display = api.open ? "" : "none";
|
|
2441
|
+
if (api.open && !this.wasOpen) this.announce(true);
|
|
2442
|
+
if (this.wasOpen && !api.open) this.restoreOpener();
|
|
2443
|
+
this.wasOpen = api.open;
|
|
2411
2444
|
});
|
|
2412
2445
|
}
|
|
2446
|
+
/** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
|
|
2447
|
+
* teardown that outruns the machine's close notification). */
|
|
2448
|
+
restoreOpener() {
|
|
2449
|
+
const opener = this.opener;
|
|
2450
|
+
this.opener = null;
|
|
2451
|
+
if (opener?.isConnected) opener.focus({ preventScroll: true });
|
|
2452
|
+
}
|
|
2453
|
+
announce(open2) {
|
|
2454
|
+
const opener = this.opener === this.panel.ownerDocument.body ? null : this.opener;
|
|
2455
|
+
announceSurface(this.panel, open2, "dialog", opener);
|
|
2456
|
+
}
|
|
2413
2457
|
get open() {
|
|
2414
2458
|
return this.ctrl.connect(this.handle.service).open;
|
|
2415
2459
|
}
|
|
2416
2460
|
show() {
|
|
2461
|
+
this.opener = this.panel.ownerDocument.activeElement;
|
|
2417
2462
|
this.ctrl.connect(this.handle.service).setOpen(true);
|
|
2418
2463
|
}
|
|
2419
2464
|
hide() {
|
|
@@ -2428,6 +2473,11 @@ var Dialog = class {
|
|
|
2428
2473
|
destroy() {
|
|
2429
2474
|
if (this.open) this.hide();
|
|
2430
2475
|
this.handle.stop();
|
|
2476
|
+
if (this.wasOpen) {
|
|
2477
|
+
this.wasOpen = false;
|
|
2478
|
+
this.announce(false);
|
|
2479
|
+
}
|
|
2480
|
+
this.restoreOpener();
|
|
2431
2481
|
this.backdrop.remove();
|
|
2432
2482
|
this.positioner.remove();
|
|
2433
2483
|
}
|
|
@@ -2556,6 +2606,8 @@ function swipeDirection(dx, dy) {
|
|
|
2556
2606
|
}
|
|
2557
2607
|
var Drawer = class {
|
|
2558
2608
|
constructor(opts = {}) {
|
|
2609
|
+
this.opener = null;
|
|
2610
|
+
this.wasOpen = false;
|
|
2559
2611
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
2560
2612
|
injectStyles(DRAWER_STYLE_ID, DRAWER_CSS, doc);
|
|
2561
2613
|
const host = opts.host ?? doc.body;
|
|
@@ -2583,12 +2635,16 @@ var Drawer = class {
|
|
|
2583
2635
|
const mid = String(this.ctrl.props.id);
|
|
2584
2636
|
this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
|
|
2585
2637
|
const api = this.ctrl.connect(service);
|
|
2638
|
+
if (this.wasOpen && !api.open) this.announce(false);
|
|
2586
2639
|
vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
|
|
2587
2640
|
vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
|
|
2588
2641
|
vanilla.spreadProps(this.panel, api.getContentProps(), mid);
|
|
2589
2642
|
vanilla.spreadProps(this.titleEl, api.getTitleProps(), mid);
|
|
2590
2643
|
this.backdrop.style.display = api.open ? "" : "none";
|
|
2591
2644
|
this.positioner.style.display = api.open ? "" : "none";
|
|
2645
|
+
if (api.open && !this.wasOpen) this.announce(true);
|
|
2646
|
+
if (this.wasOpen && !api.open) this.restoreOpener();
|
|
2647
|
+
this.wasOpen = api.open;
|
|
2592
2648
|
});
|
|
2593
2649
|
}
|
|
2594
2650
|
/** Any element between `from` and the panel that has already been scrolled down —
|
|
@@ -2697,10 +2753,22 @@ var Drawer = class {
|
|
|
2697
2753
|
setTitle(title) {
|
|
2698
2754
|
this.titleEl.textContent = title;
|
|
2699
2755
|
}
|
|
2756
|
+
/** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
|
|
2757
|
+
* teardown that outruns the machine's close notification). */
|
|
2758
|
+
restoreOpener() {
|
|
2759
|
+
const opener = this.opener;
|
|
2760
|
+
this.opener = null;
|
|
2761
|
+
if (opener?.isConnected) opener.focus({ preventScroll: true });
|
|
2762
|
+
}
|
|
2763
|
+
announce(open2) {
|
|
2764
|
+
const opener = this.opener === this.panel.ownerDocument.body ? null : this.opener;
|
|
2765
|
+
announceSurface(this.panel, open2, "drawer", opener);
|
|
2766
|
+
}
|
|
2700
2767
|
get open() {
|
|
2701
2768
|
return this.ctrl.connect(this.handle.service).open;
|
|
2702
2769
|
}
|
|
2703
2770
|
show() {
|
|
2771
|
+
this.opener = this.panel.ownerDocument.activeElement;
|
|
2704
2772
|
this.ctrl.connect(this.handle.service).setOpen(true);
|
|
2705
2773
|
}
|
|
2706
2774
|
hide() {
|
|
@@ -2708,6 +2776,11 @@ var Drawer = class {
|
|
|
2708
2776
|
}
|
|
2709
2777
|
destroy() {
|
|
2710
2778
|
this.handle.stop();
|
|
2779
|
+
if (this.wasOpen) {
|
|
2780
|
+
this.wasOpen = false;
|
|
2781
|
+
this.announce(false);
|
|
2782
|
+
}
|
|
2783
|
+
this.restoreOpener();
|
|
2711
2784
|
this.backdrop.remove();
|
|
2712
2785
|
this.positioner.remove();
|
|
2713
2786
|
}
|
|
@@ -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);
|
|
@@ -5288,10 +5399,12 @@ function legendActions() {
|
|
|
5288
5399
|
}
|
|
5289
5400
|
function legendActionsProviderFor(chart, context) {
|
|
5290
5401
|
return (indicatorId) => {
|
|
5291
|
-
const
|
|
5402
|
+
const find = () => chart.indicators().find((h) => h.id === indicatorId);
|
|
5403
|
+
const infoOf = (h) => ({ id: h.id, title: h.title, ...h.source !== void 0 ? { source: h.source } : {} });
|
|
5404
|
+
const handle = find();
|
|
5292
5405
|
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(),
|
|
5406
|
+
const info = infoOf(handle);
|
|
5407
|
+
return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(), infoOf(find() ?? handle)) }));
|
|
5295
5408
|
};
|
|
5296
5409
|
}
|
|
5297
5410
|
var calloutRegistry = /* @__PURE__ */ new Map();
|
|
@@ -9508,6 +9621,10 @@ var FibRatios = class extends Drawing {
|
|
|
9508
9621
|
|
|
9509
9622
|
// src/core/drawings/types/FibLevels.ts
|
|
9510
9623
|
var FibLevels = class extends FibRatios {
|
|
9624
|
+
/** Price of a level for the two anchor prices; subclasses override to change which anchor ratio 0 sits on. */
|
|
9625
|
+
levelPrice(ratio, p1, p2) {
|
|
9626
|
+
return p1 + ratio * (p2 - p1);
|
|
9627
|
+
}
|
|
9511
9628
|
/** Per-level pixel line + price for the ENABLED levels, spanning the anchors' time range. */
|
|
9512
9629
|
levelLines(proj) {
|
|
9513
9630
|
const a = this.anchors[0];
|
|
@@ -9517,11 +9634,10 @@ var FibLevels = class extends FibRatios {
|
|
|
9517
9634
|
const xb = proj.xOf(b.time);
|
|
9518
9635
|
const x1 = Math.min(xa, xb);
|
|
9519
9636
|
const x2 = Math.max(xa, xb);
|
|
9520
|
-
const delta = b.price - a.price;
|
|
9521
9637
|
const out = [];
|
|
9522
9638
|
for (const lv of this.levels) {
|
|
9523
9639
|
if (!lv.enabled) continue;
|
|
9524
|
-
const price =
|
|
9640
|
+
const price = this.levelPrice(lv.ratio, a.price, b.price);
|
|
9525
9641
|
const y = proj.yOf(price, this.paneId);
|
|
9526
9642
|
if (y == null) continue;
|
|
9527
9643
|
out.push({ ratio: lv.ratio, color: lv.color, label: lv.label, price, x1, x2, y });
|
|
@@ -9561,8 +9677,7 @@ var FibLevels = class extends FibRatios {
|
|
|
9561
9677
|
const a = this.anchors[0];
|
|
9562
9678
|
const b = this.anchors[1];
|
|
9563
9679
|
if (!a || !b) return null;
|
|
9564
|
-
const
|
|
9565
|
-
const prices = this.levels.filter((l) => l.enabled).map((l) => a.price + l.ratio * delta);
|
|
9680
|
+
const prices = this.levels.filter((l) => l.enabled).map((l) => this.levelPrice(l.ratio, a.price, b.price));
|
|
9566
9681
|
if (prices.length === 0) return null;
|
|
9567
9682
|
return { min: Math.min(...prices), max: Math.max(...prices) };
|
|
9568
9683
|
}
|
|
@@ -9648,13 +9763,24 @@ function cycleLevels(count, enabledCount = count) {
|
|
|
9648
9763
|
// src/core/drawings/types/FibRetracement.ts
|
|
9649
9764
|
var LEVELS = fibLevels([0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]);
|
|
9650
9765
|
var FibRetracement = class extends FibLevels {
|
|
9651
|
-
constructor() {
|
|
9652
|
-
super(
|
|
9766
|
+
constructor(init) {
|
|
9767
|
+
super(init);
|
|
9653
9768
|
this.type = "fibretracement";
|
|
9769
|
+
if (this.reverse === void 0) this.reverse = false;
|
|
9654
9770
|
}
|
|
9655
9771
|
defaultLevels() {
|
|
9656
9772
|
return LEVELS;
|
|
9657
9773
|
}
|
|
9774
|
+
levelPrice(ratio, p1, p2) {
|
|
9775
|
+
return this.reverse ? p1 + ratio * (p2 - p1) : p2 + ratio * (p1 - p2);
|
|
9776
|
+
}
|
|
9777
|
+
writeProps() {
|
|
9778
|
+
return { ...super.writeProps(), reverse: this.reverse };
|
|
9779
|
+
}
|
|
9780
|
+
readProps(props) {
|
|
9781
|
+
super.readProps(props);
|
|
9782
|
+
this.reverse = typeof props.reverse === "boolean" ? props.reverse : true;
|
|
9783
|
+
}
|
|
9658
9784
|
};
|
|
9659
9785
|
|
|
9660
9786
|
// src/core/drawings/types/FibExtension.ts
|
|
@@ -12781,6 +12907,32 @@ var CSS5 = `
|
|
|
12781
12907
|
.vela-panel-pin .vela-icon { width: 15px; height: 15px; }
|
|
12782
12908
|
.vela-panel-pin:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12783
12909
|
.vela-panel-pin[data-on='1'] { color: var(--vela-fg-bright); }
|
|
12910
|
+
.vela-panel-max {
|
|
12911
|
+
all: unset;
|
|
12912
|
+
cursor: pointer;
|
|
12913
|
+
width: 26px;
|
|
12914
|
+
height: 26px;
|
|
12915
|
+
flex: none;
|
|
12916
|
+
display: inline-flex;
|
|
12917
|
+
align-items: center;
|
|
12918
|
+
justify-content: center;
|
|
12919
|
+
border-radius: 4px;
|
|
12920
|
+
color: var(--vela-fg-muted);
|
|
12921
|
+
}
|
|
12922
|
+
.vela-panel-max .vela-icon { width: 15px; height: 15px; }
|
|
12923
|
+
.vela-panel-max:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12924
|
+
/* MAXIMIZED: the panel covers the whole dock host \u2014 every chart of the shell \u2014 whatever
|
|
12925
|
+
its placement; the width handle and the pin have nothing to do meanwhile. */
|
|
12926
|
+
.vela-panel[data-maximized] {
|
|
12927
|
+
position: absolute;
|
|
12928
|
+
inset: 0;
|
|
12929
|
+
width: auto;
|
|
12930
|
+
max-width: none;
|
|
12931
|
+
z-index: 26;
|
|
12932
|
+
border-left: none;
|
|
12933
|
+
}
|
|
12934
|
+
.vela-panel[data-maximized] .vela-panel-resizer,
|
|
12935
|
+
.vela-panel[data-maximized] .vela-panel-pin { display: none; }
|
|
12784
12936
|
.vela-panel-body { flex: 1; overflow: auto; padding: 8px; }
|
|
12785
12937
|
.vela-panel-body::-webkit-scrollbar { width: 8px; }
|
|
12786
12938
|
.vela-panel-body::-webkit-scrollbar-thumb { background: var(--vela-scroll); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
|
|
@@ -12833,6 +12985,7 @@ var CSS5 = `
|
|
|
12833
12985
|
border-left: none;
|
|
12834
12986
|
}
|
|
12835
12987
|
[data-layout='mobile'] .vela-panel-resizer { display: none; }
|
|
12988
|
+
[data-layout='mobile'] .vela-panel-max { display: none; }
|
|
12836
12989
|
`;
|
|
12837
12990
|
function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_MAX_WIDTH) {
|
|
12838
12991
|
const lo = Number.isFinite(min) && min > 0 ? min : DEFAULT_PANEL_MIN_WIDTH;
|
|
@@ -12856,6 +13009,10 @@ var SidePanel = class {
|
|
|
12856
13009
|
* never on a programmatic {@link setOverlay}, for the same reason as widths. */
|
|
12857
13010
|
this.onPlacementChange = null;
|
|
12858
13011
|
this.pin = null;
|
|
13012
|
+
this.maxButton = null;
|
|
13013
|
+
this.maximizedOn = false;
|
|
13014
|
+
/** Set while the close is announced, so a listener's own close does not re-enter. */
|
|
13015
|
+
this.closing = false;
|
|
12859
13016
|
const doc = host.ownerDocument;
|
|
12860
13017
|
injectStyles(STYLE_ID4, CSS5, doc);
|
|
12861
13018
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
@@ -12890,7 +13047,13 @@ var SidePanel = class {
|
|
|
12890
13047
|
});
|
|
12891
13048
|
this.refreshPin();
|
|
12892
13049
|
}
|
|
12893
|
-
|
|
13050
|
+
if (opts.maximizable) {
|
|
13051
|
+
this.maxButton = doc.createElement("button");
|
|
13052
|
+
this.maxButton.className = "vela-panel-max";
|
|
13053
|
+
this.maxButton.addEventListener("click", () => this.setMaximized(!this.maximizedOn));
|
|
13054
|
+
this.refreshMaxButton();
|
|
13055
|
+
}
|
|
13056
|
+
header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], ...this.maxButton ? [this.maxButton] : [], close);
|
|
12894
13057
|
this.body = doc.createElement("div");
|
|
12895
13058
|
this.body.className = "vela-panel-body";
|
|
12896
13059
|
this.el.append(header, this.body);
|
|
@@ -12902,9 +13065,37 @@ var SidePanel = class {
|
|
|
12902
13065
|
}
|
|
12903
13066
|
/** Open/close the panel — a bare call flips it. */
|
|
12904
13067
|
toggle(open2 = this.el.hidden) {
|
|
12905
|
-
if (open2 === !this.el.hidden) return;
|
|
13068
|
+
if (open2 === !this.el.hidden || !open2 && this.closing) return;
|
|
13069
|
+
if (!open2) {
|
|
13070
|
+
this.closing = true;
|
|
13071
|
+
announceSurface(this.el, false, "panel");
|
|
13072
|
+
this.closing = false;
|
|
13073
|
+
}
|
|
12906
13074
|
this.el.hidden = !open2;
|
|
13075
|
+
if (!open2) this.setMaximized(false);
|
|
12907
13076
|
this.onOpenChange?.(open2);
|
|
13077
|
+
if (open2 && !this.el.hidden) announceSurface(this.el, true, "panel");
|
|
13078
|
+
}
|
|
13079
|
+
/** Whether the panel covers every chart right now. */
|
|
13080
|
+
get maximized() {
|
|
13081
|
+
return this.maximizedOn;
|
|
13082
|
+
}
|
|
13083
|
+
/** Maximize the panel over the shell's charts, or restore it. A no-op on a panel not
|
|
13084
|
+
* declared `maximizable`. */
|
|
13085
|
+
setMaximized(maximized) {
|
|
13086
|
+
if (!this.maxButton || maximized === this.maximizedOn) return;
|
|
13087
|
+
this.maximizedOn = maximized;
|
|
13088
|
+
if (maximized) this.el.dataset.maximized = "1";
|
|
13089
|
+
else delete this.el.dataset.maximized;
|
|
13090
|
+
this.refreshMaxButton();
|
|
13091
|
+
}
|
|
13092
|
+
refreshMaxButton() {
|
|
13093
|
+
if (!this.maxButton) return;
|
|
13094
|
+
const doc = this.maxButton.ownerDocument;
|
|
13095
|
+
this.maxButton.replaceChildren(iconEl(this.maximizedOn ? "restore" : "maximize", doc));
|
|
13096
|
+
this.maxButton.title = this.maximizedOn ? "Restore" : "Maximize";
|
|
13097
|
+
this.maxButton.setAttribute("aria-label", this.maxButton.title);
|
|
13098
|
+
this.maxButton.setAttribute("aria-pressed", this.maximizedOn ? "true" : "false");
|
|
12908
13099
|
}
|
|
12909
13100
|
/** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
|
|
12910
13101
|
* `mount` receives exactly this element. Subclasses use the protected `body`. */
|
|
@@ -12956,6 +13147,7 @@ var SidePanel = class {
|
|
|
12956
13147
|
this.pin.setAttribute("aria-pressed", pinned ? "true" : "false");
|
|
12957
13148
|
}
|
|
12958
13149
|
destroy() {
|
|
13150
|
+
if (this.open) announceSurface(this.el, false, "panel");
|
|
12959
13151
|
this.el.remove();
|
|
12960
13152
|
}
|
|
12961
13153
|
/** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
|
|
@@ -14372,19 +14564,22 @@ var PanelDock = class {
|
|
|
14372
14564
|
/**
|
|
14373
14565
|
* (Re)build the CONTRIBUTED panels from the registry — call once after the built-ins, and
|
|
14374
14566
|
* again on `refreshActions()` so a late registration appears. Contributed panels that are
|
|
14375
|
-
* gone from the registry are dropped
|
|
14376
|
-
* descriptor
|
|
14567
|
+
* gone from the registry are dropped and a REPLACED descriptor is rebuilt; a panel whose
|
|
14568
|
+
* descriptor is unchanged stays mounted as it is, so its content and state survive.
|
|
14377
14569
|
*/
|
|
14378
14570
|
refresh() {
|
|
14379
14571
|
const openBefore = this.openId;
|
|
14380
|
-
|
|
14381
|
-
for (const
|
|
14572
|
+
const registered = sidePanels();
|
|
14573
|
+
for (const entry of [...this.entries]) if (entry.contributed && !registered.includes(entry.desc)) this.drop(entry);
|
|
14574
|
+
for (const desc of registered) {
|
|
14575
|
+
if (this.entries.some((e) => e.desc === desc)) continue;
|
|
14382
14576
|
const panel = new SidePanel(this.host, desc.title, `vela-panel-${desc.id}`, {
|
|
14383
14577
|
width: desc.width,
|
|
14384
14578
|
resizable: desc.resizable,
|
|
14385
14579
|
minWidth: desc.minWidth,
|
|
14386
14580
|
maxWidth: desc.maxWidth,
|
|
14387
|
-
overlay: desc.overlay
|
|
14581
|
+
overlay: desc.overlay,
|
|
14582
|
+
maximizable: desc.maximizable
|
|
14388
14583
|
});
|
|
14389
14584
|
const entry = {
|
|
14390
14585
|
id: desc.id,
|
|
@@ -14392,10 +14587,19 @@ var PanelDock = class {
|
|
|
14392
14587
|
icon: desc.icon,
|
|
14393
14588
|
order: desc.order ?? DEFAULT_PANEL_ORDER,
|
|
14394
14589
|
panel,
|
|
14395
|
-
contributed: true
|
|
14590
|
+
contributed: true,
|
|
14591
|
+
desc
|
|
14396
14592
|
};
|
|
14397
14593
|
try {
|
|
14398
|
-
|
|
14594
|
+
const header = {
|
|
14595
|
+
slot: panel.headerSlot,
|
|
14596
|
+
setTitle: (t) => panel.setTitle(t),
|
|
14597
|
+
get maximized() {
|
|
14598
|
+
return panel.maximized;
|
|
14599
|
+
},
|
|
14600
|
+
setMaximized: (on) => panel.setMaximized(on)
|
|
14601
|
+
};
|
|
14602
|
+
entry.handle = desc.mount(this.deps.context(), panel.content, header) ?? void 0;
|
|
14399
14603
|
if (this.chart) entry.handle?.onChart?.(this.chart);
|
|
14400
14604
|
} catch (err) {
|
|
14401
14605
|
console.warn(`[vela] side panel "${desc.id}" failed to mount`, err);
|
|
@@ -15211,7 +15415,12 @@ var TimeframeQuick = class {
|
|
|
15211
15415
|
content: (body) => body.append(this.input, this.hint),
|
|
15212
15416
|
onOpenChange: (open2) => opts.onOpenChange?.(open2)
|
|
15213
15417
|
});
|
|
15214
|
-
this.input.addEventListener("input", () =>
|
|
15418
|
+
this.input.addEventListener("input", () => {
|
|
15419
|
+
const { selectionStart, selectionEnd } = this.input;
|
|
15420
|
+
this.input.value = this.input.value.toUpperCase();
|
|
15421
|
+
this.input.setSelectionRange(selectionStart, selectionEnd);
|
|
15422
|
+
this.renderHint();
|
|
15423
|
+
});
|
|
15215
15424
|
this.input.addEventListener("keydown", (e) => {
|
|
15216
15425
|
if (e.key !== "Enter") return;
|
|
15217
15426
|
const parsed = parseTimeframe(this.input.value);
|
|
@@ -15223,7 +15432,7 @@ var TimeframeQuick = class {
|
|
|
15223
15432
|
}
|
|
15224
15433
|
open(seed = "") {
|
|
15225
15434
|
this.dialog.show();
|
|
15226
|
-
this.input.value = seed;
|
|
15435
|
+
this.input.value = seed.toUpperCase();
|
|
15227
15436
|
this.renderHint();
|
|
15228
15437
|
setTimeout(() => {
|
|
15229
15438
|
this.input.focus();
|
|
@@ -15241,7 +15450,7 @@ var TimeframeQuick = class {
|
|
|
15241
15450
|
this.open(text);
|
|
15242
15451
|
return;
|
|
15243
15452
|
}
|
|
15244
|
-
this.input.value += text;
|
|
15453
|
+
this.input.value += text.toUpperCase();
|
|
15245
15454
|
this.renderHint();
|
|
15246
15455
|
}
|
|
15247
15456
|
close() {
|
|
@@ -15254,7 +15463,7 @@ var TimeframeQuick = class {
|
|
|
15254
15463
|
const raw = this.input.value.trim();
|
|
15255
15464
|
const parsed = parseTimeframe(raw);
|
|
15256
15465
|
if (!raw) {
|
|
15257
|
-
this.hint.textContent = "e.g. 15,
|
|
15466
|
+
this.hint.textContent = "e.g. 15, 4H, D, 3M";
|
|
15258
15467
|
delete this.hint.dataset.invalid;
|
|
15259
15468
|
} else if (parsed.valid) {
|
|
15260
15469
|
this.hint.textContent = parsed.label ?? "";
|
|
@@ -15357,7 +15566,7 @@ var ShortcutsHelp = class {
|
|
|
15357
15566
|
addRow("Delete the drawing under the cursor", ["Middle-click"]);
|
|
15358
15567
|
const s = doc.createElement("div");
|
|
15359
15568
|
s.className = "vela-sh-static";
|
|
15360
|
-
s.textContent = "Typing a letter opens the symbol search; typing a digit opens the timeframe entry.";
|
|
15569
|
+
s.textContent = "Typing a letter or 0 opens the symbol search; typing a digit from 1 to 9 opens the timeframe entry.";
|
|
15361
15570
|
this.list.appendChild(s);
|
|
15362
15571
|
}
|
|
15363
15572
|
};
|
|
@@ -18161,6 +18370,7 @@ var DrawingToolbar = class {
|
|
|
18161
18370
|
);
|
|
18162
18371
|
}
|
|
18163
18372
|
}
|
|
18373
|
+
announceSurface(fly, true, "menu", cell);
|
|
18164
18374
|
}
|
|
18165
18375
|
openMagnetFlyout() {
|
|
18166
18376
|
const cell = this.magnetCell;
|
|
@@ -18184,6 +18394,7 @@ var DrawingToolbar = class {
|
|
|
18184
18394
|
})
|
|
18185
18395
|
);
|
|
18186
18396
|
}
|
|
18397
|
+
announceSurface(fly, true, "menu", cell);
|
|
18187
18398
|
}
|
|
18188
18399
|
flyoutHeader(text) {
|
|
18189
18400
|
const t = this.theme;
|
|
@@ -18252,9 +18463,11 @@ var DrawingToolbar = class {
|
|
|
18252
18463
|
}
|
|
18253
18464
|
closeFlyout() {
|
|
18254
18465
|
this.starEls.clear();
|
|
18255
|
-
|
|
18256
|
-
|
|
18466
|
+
const fly = this.flyout;
|
|
18467
|
+
if (fly) {
|
|
18257
18468
|
this.flyout = null;
|
|
18469
|
+
announceSurface(fly, false, "menu", this.flyoutCell);
|
|
18470
|
+
fly.remove();
|
|
18258
18471
|
this.flyoutCell?.classList.remove("vela-open");
|
|
18259
18472
|
this.flyoutCell = null;
|
|
18260
18473
|
this.flyoutOwnerId = null;
|
|
@@ -18379,23 +18592,21 @@ function arrowHitHtml(svg) {
|
|
|
18379
18592
|
return `<span class="vela-dtb-hit vela-dtb-hit--arrow">${iconSpan(svg, 11)}</span>`;
|
|
18380
18593
|
}
|
|
18381
18594
|
|
|
18382
|
-
// src/renderers/native/chrome/
|
|
18383
|
-
var
|
|
18384
|
-
var
|
|
18595
|
+
// src/renderers/native/chrome/vela-logos.ts
|
|
18596
|
+
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>';
|
|
18597
|
+
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>';
|
|
18385
18598
|
|
|
18386
18599
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
18387
|
-
var ATTRIBUTION_URL = "https://
|
|
18600
|
+
var ATTRIBUTION_URL = "https://velacharts.dev/?utm_source=vela&utm_medium=attribution";
|
|
18388
18601
|
var STYLE_ID20 = "vela-attribution-styles";
|
|
18389
18602
|
var CSS20 = `
|
|
18390
18603
|
.vela-attribution {
|
|
18391
18604
|
text-decoration: none;
|
|
18392
18605
|
display: flex;
|
|
18393
18606
|
align-items: center;
|
|
18394
|
-
/*
|
|
18395
|
-
|
|
18396
|
-
|
|
18397
|
-
reads wrong because the symbol's ink hangs low and the wordmark descends. */
|
|
18398
|
-
gap: 4px;
|
|
18607
|
+
/* No gutter: the V is the wordmark's first letter, and the letters' viewBox starts
|
|
18608
|
+
where the V's ends, so the kit's own letter spacing comes with them. */
|
|
18609
|
+
gap: 0;
|
|
18399
18610
|
}
|
|
18400
18611
|
.vela-attribution .vela-attr-symbol {
|
|
18401
18612
|
flex: none;
|
|
@@ -18403,7 +18614,7 @@ var CSS20 = `
|
|
|
18403
18614
|
line-height: 0;
|
|
18404
18615
|
}
|
|
18405
18616
|
.vela-attribution .vela-attr-symbol svg {
|
|
18406
|
-
height:
|
|
18617
|
+
height: 24px;
|
|
18407
18618
|
width: auto;
|
|
18408
18619
|
display: block;
|
|
18409
18620
|
filter: drop-shadow(0 1px 2px var(--vela-attr-shadow, rgba(0,0,0,0.45)));
|
|
@@ -18414,15 +18625,13 @@ var CSS20 = `
|
|
|
18414
18625
|
opacity: 0;
|
|
18415
18626
|
display: flex;
|
|
18416
18627
|
align-items: center;
|
|
18417
|
-
/*
|
|
18418
|
-
position: relative;
|
|
18419
|
-
top: 3px;
|
|
18628
|
+
/* Same height and baseline as the V (one viewBox height), so no vertical offset. */
|
|
18420
18629
|
transform: translateX(-8px);
|
|
18421
18630
|
transition: max-width 0.3s ease, opacity 0.25s ease, transform 0.3s ease;
|
|
18422
18631
|
flex: none;
|
|
18423
18632
|
}
|
|
18424
18633
|
.vela-attribution .vela-attr-wordmark svg {
|
|
18425
|
-
height:
|
|
18634
|
+
height: 24px;
|
|
18426
18635
|
width: auto;
|
|
18427
18636
|
display: block;
|
|
18428
18637
|
filter: drop-shadow(0 1px 2px var(--vela-attr-shadow, rgba(0,0,0,0.45)));
|
|
@@ -18437,14 +18646,11 @@ var CSS20 = `
|
|
|
18437
18646
|
(data-layout): the workspace's single grid-wide mark lives OUTSIDE any renderer
|
|
18438
18647
|
container, so only the shell attribute reaches it. */
|
|
18439
18648
|
[data-vela-layout='mobile'] .vela-attribution .vela-attr-symbol svg,
|
|
18440
|
-
[data-layout='mobile'] .vela-attribution .vela-attr-symbol svg { height:
|
|
18649
|
+
[data-layout='mobile'] .vela-attribution .vela-attr-symbol svg { height: 20px; }
|
|
18441
18650
|
[data-vela-layout='mobile'] .vela-attribution .vela-attr-wordmark,
|
|
18442
|
-
[data-layout='mobile'] .vela-attribution .vela-attr-wordmark {
|
|
18443
|
-
top: 2px;
|
|
18444
|
-
transform: translateX(-6px);
|
|
18445
|
-
}
|
|
18651
|
+
[data-layout='mobile'] .vela-attribution .vela-attr-wordmark { transform: translateX(-6px); }
|
|
18446
18652
|
[data-vela-layout='mobile'] .vela-attribution .vela-attr-wordmark svg,
|
|
18447
|
-
[data-layout='mobile'] .vela-attribution .vela-attr-wordmark svg { height:
|
|
18653
|
+
[data-layout='mobile'] .vela-attribution .vela-attr-wordmark svg { height: 20px; }
|
|
18448
18654
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
18449
18655
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
18450
18656
|
`;
|
|
@@ -18501,10 +18707,10 @@ function createAttributionMark(doc, background) {
|
|
|
18501
18707
|
const symbol = doc.createElement("span");
|
|
18502
18708
|
symbol.className = "vela-attr-symbol";
|
|
18503
18709
|
symbol.setAttribute("aria-hidden", "true");
|
|
18504
|
-
symbol.innerHTML =
|
|
18710
|
+
symbol.innerHTML = VELA_MARK_SVG;
|
|
18505
18711
|
const wordmark = doc.createElement("span");
|
|
18506
18712
|
wordmark.className = "vela-attr-wordmark";
|
|
18507
|
-
wordmark.innerHTML =
|
|
18713
|
+
wordmark.innerHTML = VELA_LETTERS_SVG;
|
|
18508
18714
|
a.append(symbol, wordmark);
|
|
18509
18715
|
return a;
|
|
18510
18716
|
}
|
|
@@ -18916,7 +19122,7 @@ var IndicatorHandleImpl = class {
|
|
|
18916
19122
|
this.controller.applyProps(this.id, values);
|
|
18917
19123
|
}
|
|
18918
19124
|
updateCode(source) {
|
|
18919
|
-
this.controller.updateCode(this.id, source);
|
|
19125
|
+
return this.controller.updateCode(this.id, source);
|
|
18920
19126
|
}
|
|
18921
19127
|
setVisible(visible) {
|
|
18922
19128
|
this.controller.setVisible(this.id, visible);
|
|
@@ -20779,20 +20985,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20779
20985
|
/**
|
|
20780
20986
|
* IndicatorController: replace a SCRIPT indicator's source in place (see
|
|
20781
20987
|
* {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
|
|
20782
|
-
* computing until the new source has compiled
|
|
20783
|
-
*
|
|
20988
|
+
* computing until the new source has compiled; then the new code runs on TRIAL until
|
|
20989
|
+
* its first model lands, and a failure before that puts the previous code back.
|
|
20990
|
+
* Natives have no script — warn and leave them alone.
|
|
20784
20991
|
*/
|
|
20785
20992
|
updateCode(id, source) {
|
|
20786
20993
|
const record = this.registry.get(id);
|
|
20787
20994
|
const handle = this.handles.get(id);
|
|
20788
|
-
if (!record || !handle) return;
|
|
20995
|
+
if (!record || !handle) return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator is no longer on the chart.`) });
|
|
20789
20996
|
if (record.native) {
|
|
20790
20997
|
console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
|
|
20791
|
-
return;
|
|
20998
|
+
return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`) });
|
|
20792
20999
|
}
|
|
20793
|
-
|
|
20794
|
-
record.
|
|
20795
|
-
|
|
21000
|
+
const busy = record.pendingSource !== void 0 || record.codeTrial !== void 0;
|
|
21001
|
+
if (!busy && source === record.source) return Promise.resolve({ ok: true, error: null });
|
|
21002
|
+
return new Promise((resolve) => {
|
|
21003
|
+
(record.codeWaiters ?? (record.codeWaiters = [])).push(resolve);
|
|
21004
|
+
if (record.pendingSource === source || record.pendingSource === void 0 && record.codeTrial && record.source === source) return;
|
|
21005
|
+
record.pendingSource = source;
|
|
21006
|
+
void this.swapSource(id, source, handle);
|
|
21007
|
+
});
|
|
20796
21008
|
}
|
|
20797
21009
|
async swapSource(id, source, handle) {
|
|
20798
21010
|
const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
|
|
@@ -20803,26 +21015,41 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20803
21015
|
const record2 = this.registry.get(id);
|
|
20804
21016
|
if (record2?.pendingSource === source) {
|
|
20805
21017
|
record2.pendingSource = void 0;
|
|
20806
|
-
this.
|
|
21018
|
+
this.rejectCode(id, record2, handle, err);
|
|
20807
21019
|
}
|
|
20808
21020
|
return;
|
|
20809
21021
|
}
|
|
20810
21022
|
const record = this.registry.get(id);
|
|
20811
21023
|
if (!record || record.pendingSource !== source) return;
|
|
20812
21024
|
record.pendingSource = void 0;
|
|
21025
|
+
const previous = record.codeTrial?.previous ?? {
|
|
21026
|
+
source: record.source,
|
|
21027
|
+
engine: record.engine,
|
|
21028
|
+
prepared: record.prepared,
|
|
21029
|
+
inputValues: record.inputValues,
|
|
21030
|
+
propValues: record.propValues,
|
|
21031
|
+
inputs: [...handle.inputs],
|
|
21032
|
+
props: [...handle.props]
|
|
21033
|
+
};
|
|
21034
|
+
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues, record.prepared?.inputs);
|
|
21035
|
+
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues, record.prepared?.props);
|
|
20813
21036
|
record.source = source;
|
|
20814
21037
|
record.engine = engine;
|
|
20815
21038
|
record.prepared = prepared;
|
|
20816
21039
|
handle.setSource(source);
|
|
20817
|
-
record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
|
|
20818
|
-
record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
|
|
20819
21040
|
handle.setSchema(prepared.inputs);
|
|
20820
21041
|
handle.setPropsSchema(prepared.props ?? []);
|
|
20821
21042
|
record.session?.stop();
|
|
20822
21043
|
record.session = void 0;
|
|
20823
21044
|
record.pendingStructural = true;
|
|
20824
21045
|
record.pendingCause = "code";
|
|
20825
|
-
if (record.hidden)
|
|
21046
|
+
if (record.hidden) {
|
|
21047
|
+
record.codeTrial = void 0;
|
|
21048
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
21049
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21050
|
+
return;
|
|
21051
|
+
}
|
|
21052
|
+
record.codeTrial = { previous };
|
|
20826
21053
|
if (record.renderHandle) {
|
|
20827
21054
|
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
20828
21055
|
this.setLoading(record, true);
|
|
@@ -20830,6 +21057,46 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20830
21057
|
this.mountLoadingPlaceholder(id, record);
|
|
20831
21058
|
}
|
|
20832
21059
|
this.executeIndicator(id, handle);
|
|
21060
|
+
if (this.bars.length === 0 && record.codeTrial) {
|
|
21061
|
+
record.codeTrial = void 0;
|
|
21062
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21063
|
+
}
|
|
21064
|
+
}
|
|
21065
|
+
/** Resolve every `updateCode` caller waiting on this record's chain of edits. */
|
|
21066
|
+
settleCode(record, result) {
|
|
21067
|
+
const waiters = record.codeWaiters;
|
|
21068
|
+
record.codeWaiters = void 0;
|
|
21069
|
+
if (waiters) for (const resolve of waiters) resolve(result);
|
|
21070
|
+
}
|
|
21071
|
+
/**
|
|
21072
|
+
* An update failed — at prepare, or on trial before its first model. A trial puts the
|
|
21073
|
+
* previous code back (source, engine, prepared script, values, schema) and restarts it;
|
|
21074
|
+
* its visuals were never dropped, so the chart keeps painting throughout. Then the
|
|
21075
|
+
* failure reports the usual way and every waiting caller learns it.
|
|
21076
|
+
*/
|
|
21077
|
+
rejectCode(id, record, handle, err) {
|
|
21078
|
+
const trial = record.codeTrial;
|
|
21079
|
+
record.codeTrial = void 0;
|
|
21080
|
+
this.fail(id, handle, err);
|
|
21081
|
+
if (trial) {
|
|
21082
|
+
const p = trial.previous;
|
|
21083
|
+
record.session?.stop();
|
|
21084
|
+
record.session = void 0;
|
|
21085
|
+
record.source = p.source;
|
|
21086
|
+
record.engine = p.engine;
|
|
21087
|
+
record.prepared = p.prepared;
|
|
21088
|
+
record.inputValues = p.inputValues;
|
|
21089
|
+
record.propValues = p.propValues;
|
|
21090
|
+
handle.setSource(p.source);
|
|
21091
|
+
handle.setSchema(p.inputs);
|
|
21092
|
+
handle.setPropsSchema(p.props);
|
|
21093
|
+
record.pendingStructural = true;
|
|
21094
|
+
record.pendingCause = void 0;
|
|
21095
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
21096
|
+
if (!record.hidden) this.executeIndicator(id, handle);
|
|
21097
|
+
}
|
|
21098
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
21099
|
+
this.settleCode(record, { ok: false, error });
|
|
20833
21100
|
}
|
|
20834
21101
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
20835
21102
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
@@ -20844,6 +21111,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20844
21111
|
removeIndicator(id) {
|
|
20845
21112
|
const record = this.registry.remove(id);
|
|
20846
21113
|
if (record?.native?.type === "volume") this.volumeOptedOut = true;
|
|
21114
|
+
if (record) this.settleCode(record, { ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator was removed.`) });
|
|
20847
21115
|
record?.session?.stop();
|
|
20848
21116
|
if (record?.native?.started) record.native.instance.stop();
|
|
20849
21117
|
this.handles.delete(id);
|
|
@@ -20871,6 +21139,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20871
21139
|
if (!visible) {
|
|
20872
21140
|
record.session?.stop();
|
|
20873
21141
|
record.session = void 0;
|
|
21142
|
+
if (record.codeTrial) {
|
|
21143
|
+
record.codeTrial = void 0;
|
|
21144
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21145
|
+
}
|
|
20874
21146
|
if (record.native?.started) record.native.instance.suspend();
|
|
20875
21147
|
if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
|
|
20876
21148
|
else if (record.native) this.mountHiddenNativeRow(id, record);
|
|
@@ -20958,6 +21230,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
20958
21230
|
this.viewportUnsub?.();
|
|
20959
21231
|
this.paneActionUnsub?.();
|
|
20960
21232
|
for (const record of this.registry.all()) {
|
|
21233
|
+
this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
|
|
20961
21234
|
record.session?.stop();
|
|
20962
21235
|
if (record.native?.started) record.native.instance.stop();
|
|
20963
21236
|
}
|
|
@@ -21026,7 +21299,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21026
21299
|
const record = this.registry.get(id);
|
|
21027
21300
|
if (!record || !record.engine || !record.prepared || record.hidden) return;
|
|
21028
21301
|
const mode = this.config.live && !record.prepared.reactsToViewport && record.engine.capabilities.streaming ? "live" : "static";
|
|
21029
|
-
|
|
21302
|
+
const prepared = record.prepared;
|
|
21303
|
+
const session = record.engine.execute(
|
|
21030
21304
|
{
|
|
21031
21305
|
prepared: record.prepared,
|
|
21032
21306
|
market: this.market(),
|
|
@@ -21047,6 +21321,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21047
21321
|
const cause = record.pendingCause ?? "history";
|
|
21048
21322
|
if (!this.applyModel(id, model)) return;
|
|
21049
21323
|
record.pendingCause = void 0;
|
|
21324
|
+
if (record.codeTrial) {
|
|
21325
|
+
record.codeTrial = void 0;
|
|
21326
|
+
this.settleCode(record, { ok: true, error: null });
|
|
21327
|
+
}
|
|
21050
21328
|
this.emitContextChanged(id);
|
|
21051
21329
|
this.emitScriptRun(id, cause, first);
|
|
21052
21330
|
},
|
|
@@ -21057,9 +21335,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
21057
21335
|
handle.emit("alert", { id: a.id, message: a.message, title: a.title, time: a.time });
|
|
21058
21336
|
},
|
|
21059
21337
|
onWarning: (w) => this.events.emit("warning", w),
|
|
21060
|
-
onError: (err) =>
|
|
21338
|
+
onError: (err) => {
|
|
21339
|
+
if (record.codeTrial) this.rejectCode(id, record, handle, err);
|
|
21340
|
+
else this.fail(id, handle, err);
|
|
21341
|
+
}
|
|
21061
21342
|
}
|
|
21062
21343
|
);
|
|
21344
|
+
if (record.prepared !== prepared) {
|
|
21345
|
+
session.stop();
|
|
21346
|
+
return;
|
|
21347
|
+
}
|
|
21348
|
+
record.session = session;
|
|
21063
21349
|
this.emitContextChanged(id);
|
|
21064
21350
|
}
|
|
21065
21351
|
/**
|
|
@@ -21560,7 +21846,7 @@ function yieldToPaint() {
|
|
|
21560
21846
|
}
|
|
21561
21847
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
21562
21848
|
}
|
|
21563
|
-
function valuesOnSchema(schema, previous) {
|
|
21849
|
+
function valuesOnSchema(schema, previous, oldSchema = []) {
|
|
21564
21850
|
const out = {};
|
|
21565
21851
|
const declared = /* @__PURE__ */ new Set();
|
|
21566
21852
|
for (const s of schema) {
|
|
@@ -21568,9 +21854,22 @@ function valuesOnSchema(schema, previous) {
|
|
|
21568
21854
|
declared.add(s.key);
|
|
21569
21855
|
declared.add(s.title);
|
|
21570
21856
|
}
|
|
21571
|
-
|
|
21857
|
+
const oldDefaults = /* @__PURE__ */ new Map();
|
|
21858
|
+
for (const s of oldSchema) {
|
|
21859
|
+
oldDefaults.set(s.key, s.defval);
|
|
21860
|
+
if (!oldDefaults.has(s.title)) oldDefaults.set(s.title, s.defval);
|
|
21861
|
+
}
|
|
21862
|
+
for (const [k, v] of Object.entries(previous)) {
|
|
21863
|
+
if (!declared.has(k)) continue;
|
|
21864
|
+
if (oldDefaults.has(k) && sameInputValue(oldDefaults.get(k), v)) continue;
|
|
21865
|
+
out[k] = v;
|
|
21866
|
+
}
|
|
21572
21867
|
return out;
|
|
21573
21868
|
}
|
|
21869
|
+
function sameInputValue(a, b) {
|
|
21870
|
+
if (a === b) return true;
|
|
21871
|
+
return typeof a === "object" && a !== null && typeof b === "object" && b !== null && JSON.stringify(a) === JSON.stringify(b);
|
|
21872
|
+
}
|
|
21574
21873
|
function blankedModel(model) {
|
|
21575
21874
|
return {
|
|
21576
21875
|
...model,
|
|
@@ -24500,15 +24799,23 @@ var Batch = class {
|
|
|
24500
24799
|
this.seg(x + w, y + h, x, y + h, lineWidth, c);
|
|
24501
24800
|
this.seg(x, y + h, x, y, lineWidth, c);
|
|
24502
24801
|
}
|
|
24503
|
-
/**
|
|
24802
|
+
/**
|
|
24803
|
+
* Filled circle (triangle fan), feathered like a line: the rim is padded by {@link AA_PAD} and
|
|
24804
|
+
* edgeDist runs from 0 at the center to the padded radius at the rim, with edgeHalf = r. The
|
|
24805
|
+
* distance grows linearly along each spoke, so the shader's line feather gives a round soft edge.
|
|
24806
|
+
*/
|
|
24504
24807
|
circle(cx, cy, r, c, segments = 16) {
|
|
24505
|
-
|
|
24808
|
+
const ext = r + AA_PAD;
|
|
24809
|
+
let px = cx + ext;
|
|
24506
24810
|
let py = cy;
|
|
24811
|
+
this.ensure(segments * 3);
|
|
24507
24812
|
for (let i = 1; i <= segments; i += 1) {
|
|
24508
24813
|
const a = i / segments * Math.PI * 2;
|
|
24509
|
-
const nx = cx + Math.cos(a) *
|
|
24510
|
-
const ny = cy + Math.sin(a) *
|
|
24511
|
-
this.
|
|
24814
|
+
const nx = cx + Math.cos(a) * ext;
|
|
24815
|
+
const ny = cy + Math.sin(a) * ext;
|
|
24816
|
+
this.v(cx, cy, c, 0, r);
|
|
24817
|
+
this.v(px, py, c, ext, r);
|
|
24818
|
+
this.v(nx, ny, c, ext, r);
|
|
24512
24819
|
px = nx;
|
|
24513
24820
|
py = ny;
|
|
24514
24821
|
}
|
|
@@ -24689,7 +24996,7 @@ var WebGL2Backend = class {
|
|
|
24689
24996
|
}
|
|
24690
24997
|
mount(canvas) {
|
|
24691
24998
|
this.canvas = canvas;
|
|
24692
|
-
this.gl = canvas.getContext("webgl2", { alpha: true, antialias:
|
|
24999
|
+
this.gl = canvas.getContext("webgl2", { alpha: true, antialias: false, depth: false, stencil: false, premultipliedAlpha: true });
|
|
24693
25000
|
if (!this.gl) return;
|
|
24694
25001
|
if (!this.initGL()) {
|
|
24695
25002
|
this.gl = null;
|
|
@@ -25948,6 +26255,12 @@ var InputController = class {
|
|
|
25948
26255
|
this.lastTapT = 0;
|
|
25949
26256
|
this.lastTapX = 0;
|
|
25950
26257
|
this.lastTapY = 0;
|
|
26258
|
+
// Whether the last two primary presses were claimed by the drawings layer. A double-click
|
|
26259
|
+
// is the last press plus the one before it; a tool-driven click pair must never fall through
|
|
26260
|
+
// to the pane maximize toggle, even when the tool disarmed itself between the two clicks
|
|
26261
|
+
// (a finished placement leaves plain cursor mode for the second click).
|
|
26262
|
+
this.lastPressDrawing = false;
|
|
26263
|
+
this.prevPressDrawing = false;
|
|
25951
26264
|
this.onTouchContextMenu = (e) => {
|
|
25952
26265
|
if (this.touches.size === 0) return;
|
|
25953
26266
|
if (!(e.target instanceof Node) || !this.el?.contains(e.target)) return;
|
|
@@ -26002,19 +26315,24 @@ var InputController = class {
|
|
|
26002
26315
|
this.moved = false;
|
|
26003
26316
|
this.startX = x;
|
|
26004
26317
|
this.startY = y;
|
|
26318
|
+
this.prevPressDrawing = this.lastPressDrawing;
|
|
26319
|
+
this.lastPressDrawing = false;
|
|
26005
26320
|
if (this.deps.drawingsClaim?.(x, y)) {
|
|
26006
26321
|
this.region = "drawing";
|
|
26322
|
+
this.lastPressDrawing = true;
|
|
26007
26323
|
this.deps.drawingsPointerDown?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
|
|
26008
26324
|
this.capture(e.pointerId);
|
|
26009
26325
|
return;
|
|
26010
26326
|
}
|
|
26011
26327
|
if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
26012
26328
|
this.region = "drawing";
|
|
26329
|
+
this.lastPressDrawing = true;
|
|
26013
26330
|
this.capture(e.pointerId);
|
|
26014
26331
|
return;
|
|
26015
26332
|
}
|
|
26016
26333
|
if ((e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
|
|
26017
26334
|
this.region = "drawing";
|
|
26335
|
+
this.lastPressDrawing = true;
|
|
26018
26336
|
this.capture(e.pointerId);
|
|
26019
26337
|
return;
|
|
26020
26338
|
}
|
|
@@ -26130,7 +26448,7 @@ var InputController = class {
|
|
|
26130
26448
|
this.deps.drawingsPointerUp?.(x, y, this.snapMode(e));
|
|
26131
26449
|
} else if (tapRelease && this.region === "data") {
|
|
26132
26450
|
this.deps.onClick(x, y);
|
|
26133
|
-
} else if (this.dragging && this.region === "data") {
|
|
26451
|
+
} else if (this.dragging && this.region === "data" && wasTouch) {
|
|
26134
26452
|
const stale = e.timeStamp - this.lastT > FLING_STALE_MS;
|
|
26135
26453
|
if (!stale && Math.abs(this.vx) > FLING_MIN_SPEED) {
|
|
26136
26454
|
const pitch = this.deps.getCoords().pxPerBar();
|
|
@@ -26336,6 +26654,7 @@ var InputController = class {
|
|
|
26336
26654
|
if (region === "price") this.deps.resetPriceScale(x, y);
|
|
26337
26655
|
else if (region === "separator") this.deps.resetPaneSize(y);
|
|
26338
26656
|
else if (region === "time") this.deps.resetView();
|
|
26657
|
+
else if (this.lastPressDrawing || this.prevPressDrawing) return;
|
|
26339
26658
|
else this.deps.dataDblClick(x, y);
|
|
26340
26659
|
}
|
|
26341
26660
|
};
|
|
@@ -28501,6 +28820,7 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
|
|
|
28501
28820
|
const step = pickStep(span / Math.max(1, target));
|
|
28502
28821
|
const zFrom = fromMs + offsetMs;
|
|
28503
28822
|
const zTo = toMs + offsetMs;
|
|
28823
|
+
if (step >= MONTH) return calendarTicks(zFrom, zTo, step >= YEAR ? 12 : step >= 3 * MONTH ? 3 : 1, offsetMs);
|
|
28504
28824
|
const first = Math.ceil(zFrom / step) * step;
|
|
28505
28825
|
const out = [];
|
|
28506
28826
|
for (let zt = first; zt <= zTo; zt += step) {
|
|
@@ -28531,6 +28851,18 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
|
|
|
28531
28851
|
}
|
|
28532
28852
|
return out;
|
|
28533
28853
|
}
|
|
28854
|
+
function calendarTicks(zFrom, zTo, months, offsetMs) {
|
|
28855
|
+
const d0 = new Date(zFrom);
|
|
28856
|
+
let idx = d0.getUTCFullYear() * 12 + d0.getUTCMonth();
|
|
28857
|
+
if (Date.UTC(Math.floor(idx / 12), idx % 12, 1) < zFrom) idx += 1;
|
|
28858
|
+
idx = Math.ceil(idx / months) * months;
|
|
28859
|
+
const out = [];
|
|
28860
|
+
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)) {
|
|
28861
|
+
const label = months === 12 ? String(Math.floor(idx / 12)) : MONTHS[idx % 12];
|
|
28862
|
+
out.push({ time: zt - offsetMs, label, major: true });
|
|
28863
|
+
}
|
|
28864
|
+
return out;
|
|
28865
|
+
}
|
|
28534
28866
|
|
|
28535
28867
|
// src/renderers/native/chrome/countdown.ts
|
|
28536
28868
|
function countdownText(barOpen, barMs, now2) {
|
|
@@ -32482,6 +32814,12 @@ var DrawingInteraction = class {
|
|
|
32482
32814
|
activeDragId() {
|
|
32483
32815
|
return this.state.kind === "pressed" && this.state.moved ? this.state.id : null;
|
|
32484
32816
|
}
|
|
32817
|
+
/** The store drawings a live drag is moving — the pressed one and its riders — or none
|
|
32818
|
+
* before the drag passes the slop. A Ctrl-drag moves copies, so its sources stay put. */
|
|
32819
|
+
movingIds() {
|
|
32820
|
+
if (this.state.kind !== "pressed" || !this.state.moved || this.state.clones) return /* @__PURE__ */ new Set();
|
|
32821
|
+
return /* @__PURE__ */ new Set([this.state.id, ...this.state.riders.map((r) => r.id)]);
|
|
32822
|
+
}
|
|
32485
32823
|
/** The drawing under a press that has not been released yet (drag or click undecided), or null. */
|
|
32486
32824
|
pressedId() {
|
|
32487
32825
|
return this.state.kind === "pressed" ? this.state.id : null;
|
|
@@ -33140,6 +33478,17 @@ var DrawingSettingsDialog = class {
|
|
|
33140
33478
|
}
|
|
33141
33479
|
}));
|
|
33142
33480
|
}
|
|
33481
|
+
if (drawing instanceof FibRetracement) {
|
|
33482
|
+
const fib = drawing;
|
|
33483
|
+
grid.appendChild(fieldRow({
|
|
33484
|
+
label: "Reverse",
|
|
33485
|
+
bool: true,
|
|
33486
|
+
toggle: {
|
|
33487
|
+
checked: fib.reverse,
|
|
33488
|
+
onChange: (v) => actions.patch({ reverse: v })
|
|
33489
|
+
}
|
|
33490
|
+
}));
|
|
33491
|
+
}
|
|
33143
33492
|
levels.forEach((lv, i) => {
|
|
33144
33493
|
const row = document.createElement("div");
|
|
33145
33494
|
row.className = "vela-field-span";
|
|
@@ -34293,6 +34642,8 @@ var UserDrawingController = class {
|
|
|
34293
34642
|
this.drawings = [];
|
|
34294
34643
|
/** Ids painted on an interleave layer this frame — the top canvas paints only their handles. */
|
|
34295
34644
|
this.sliced = /* @__PURE__ */ new Set();
|
|
34645
|
+
/** Interleaved drawings mid-drag: painted on the top canvas, left out of their slice. */
|
|
34646
|
+
this.lifted = /* @__PURE__ */ new Set();
|
|
34296
34647
|
/** Cached slice canvases, keyed `paneId|beforeZ`, reused across frames to avoid churn. */
|
|
34297
34648
|
this.sliceCache = /* @__PURE__ */ new Map();
|
|
34298
34649
|
/** Series boundaries per pane as of the last `prepareSlices` — lets a repaint between data
|
|
@@ -34380,7 +34731,7 @@ var UserDrawingController = class {
|
|
|
34380
34731
|
selectedIds: () => this.selectedIds,
|
|
34381
34732
|
emit: (i) => this.emit(i),
|
|
34382
34733
|
changed: () => {
|
|
34383
|
-
this.
|
|
34734
|
+
this.syncLift();
|
|
34384
34735
|
this.render();
|
|
34385
34736
|
},
|
|
34386
34737
|
openSettings: (id, x, y) => this.openSettingsById(id, x, y),
|
|
@@ -35006,6 +35357,20 @@ var UserDrawingController = class {
|
|
|
35006
35357
|
/** A drawing that paints inside the series stack changed (content, not hover): its pixels
|
|
35007
35358
|
* live in the backend composite, so this layer alone can't show the change — ask for a
|
|
35008
35359
|
* data frame, which re-runs `prepareSlices` before the backend composites. */
|
|
35360
|
+
/** A drawing being dragged leaves its interleave slice for the top canvas, so each pointer
|
|
35361
|
+
* move repaints one 2D layer instead of re-rasterizing and re-uploading a plot-sized slice
|
|
35362
|
+
* texture through the data frame. The slices rebuild once as the drag starts and once as it
|
|
35363
|
+
* ends (release, cancel), when the lifted set changes. */
|
|
35364
|
+
syncLift() {
|
|
35365
|
+
const moving = this.interaction.movingIds();
|
|
35366
|
+
let same = moving.size === this.lifted.size;
|
|
35367
|
+
if (same) {
|
|
35368
|
+
for (const id of moving) if (!this.lifted.has(id)) same = false;
|
|
35369
|
+
}
|
|
35370
|
+
if (same) return;
|
|
35371
|
+
this.lifted = new Set(moving);
|
|
35372
|
+
this.invalidateSlices();
|
|
35373
|
+
}
|
|
35009
35374
|
invalidateSlices() {
|
|
35010
35375
|
if (this.sliced.size > 0 || this.drawings.some((d) => this.isInterleaved(d))) this.deps.requestDataPaint();
|
|
35011
35376
|
}
|
|
@@ -35024,7 +35389,7 @@ var UserDrawingController = class {
|
|
|
35024
35389
|
const theme = this.deps.theme();
|
|
35025
35390
|
const buckets = /* @__PURE__ */ new Map();
|
|
35026
35391
|
for (const d of this.drawings) {
|
|
35027
|
-
if (!d.visible) continue;
|
|
35392
|
+
if (!d.visible || this.lifted.has(d.id)) continue;
|
|
35028
35393
|
const beforeZ = sliceKeyFor(d.zIndex, this.lastBounds.get(d.paneId) ?? []);
|
|
35029
35394
|
if (beforeZ === null) continue;
|
|
35030
35395
|
const key = `${d.paneId}|${beforeZ}`;
|
|
@@ -35076,8 +35441,9 @@ var UserDrawingController = class {
|
|
|
35076
35441
|
mutedLabel: edited instanceof TextLabel ? edited.id : null
|
|
35077
35442
|
};
|
|
35078
35443
|
this.painter.seriesLook = this.deps.seriesLook();
|
|
35079
|
-
|
|
35080
|
-
this.painter.
|
|
35444
|
+
const onTop = (d) => !this.isInterleaved(d) || this.lifted.has(d.id);
|
|
35445
|
+
this.painter.paintAll(ctx, this.drawings.filter(onTop), proj, this.deps.theme(), targets);
|
|
35446
|
+
this.painter.paintHighlights(ctx, this.drawings.filter((d) => !onTop(d)), proj, handleIdsFor(targets));
|
|
35081
35447
|
const clones = this.interaction.dragClones();
|
|
35082
35448
|
if (clones) this.painter.paintAll(ctx, clones, proj, this.deps.theme(), { selected: new Set(clones.map((c) => c.id)) });
|
|
35083
35449
|
this.layoutTextEditor();
|
|
@@ -44733,6 +45099,9 @@ var CSS21 = `
|
|
|
44733
45099
|
.vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
|
|
44734
45100
|
.vela-statusline .vela-sl-market > [hidden] { display: none !important; }
|
|
44735
45101
|
.vela-statusline .vela-sl-replay-badge, .vela-statusline .vela-sl-replay-badge svg { display: block; width: 16px; height: 16px; }
|
|
45102
|
+
/* Tabular figures: every digit has one advance, so a readout whose text changes only in
|
|
45103
|
+
* its digits keeps its width \u2014 render() skips the ladder for those (see widthKey). */
|
|
45104
|
+
.vela-statusline .vela-sl-values { font-variant-numeric: tabular-nums; }
|
|
44736
45105
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
44737
45106
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
44738
45107
|
/* The change value wears the SAME ink as the OHLC values (set inline per render) \u2014
|
|
@@ -44850,6 +45219,10 @@ var Statusline = class {
|
|
|
44850
45219
|
this.fitRO = null;
|
|
44851
45220
|
/** The ladder rung currently applied — see {@link fit}. */
|
|
44852
45221
|
this.layout = { stacked: false, level: "full" };
|
|
45222
|
+
/** The readout {@link widthKey} the applied rung was fitted to. */
|
|
45223
|
+
this.fitKey = null;
|
|
45224
|
+
/** Web fonts landing after the last fit change every width without a resize. */
|
|
45225
|
+
this.onFontsLoaded = () => this.fit();
|
|
44853
45226
|
this.onContextMenu = (e) => {
|
|
44854
45227
|
if (!this.menu || !this.menuHooks) return;
|
|
44855
45228
|
e.preventDefault();
|
|
@@ -44913,6 +45286,7 @@ var Statusline = class {
|
|
|
44913
45286
|
this.fitRO = new ResizeObserver(() => this.fit());
|
|
44914
45287
|
this.fitRO.observe(host);
|
|
44915
45288
|
}
|
|
45289
|
+
doc.fonts?.addEventListener("loadingdone", this.onFontsLoaded);
|
|
44916
45290
|
this.marketTip = new Tooltip(this.marketEl, { content: MARKET_LABELS.open, placement: "bottom" });
|
|
44917
45291
|
this.eyeTip = new Tooltip(this.eyeEl, { content: "Show chart", placement: "bottom" });
|
|
44918
45292
|
this.setMarketStatus("open");
|
|
@@ -44965,6 +45339,7 @@ var Statusline = class {
|
|
|
44965
45339
|
* (detached host, node tests) nothing overflows, so the widest rung stays.
|
|
44966
45340
|
*/
|
|
44967
45341
|
fit() {
|
|
45342
|
+
this.fitKey = this.widthKey();
|
|
44968
45343
|
this.syncParts();
|
|
44969
45344
|
const seg = segmentVisibility(this.parts, this.chartHidden);
|
|
44970
45345
|
const overflows = () => this.el.scrollWidth > this.el.clientWidth;
|
|
@@ -45121,6 +45496,7 @@ var Statusline = class {
|
|
|
45121
45496
|
this.detach();
|
|
45122
45497
|
this.fitRO?.disconnect();
|
|
45123
45498
|
this.fitRO = null;
|
|
45499
|
+
this.el.ownerDocument.fonts?.removeEventListener("loadingdone", this.onFontsLoaded);
|
|
45124
45500
|
this.el.removeEventListener("contextmenu", this.onContextMenu);
|
|
45125
45501
|
this.menu?.destroy();
|
|
45126
45502
|
this.menu = null;
|
|
@@ -45136,7 +45512,22 @@ var Statusline = class {
|
|
|
45136
45512
|
}
|
|
45137
45513
|
render() {
|
|
45138
45514
|
if (this.menuHooks) this.setChartHidden(!this.menuHooks.chartVisible());
|
|
45139
|
-
this.
|
|
45515
|
+
if (this.widthKey() === this.fitKey) this.renderValues();
|
|
45516
|
+
else this.fit();
|
|
45517
|
+
}
|
|
45518
|
+
/** Everything the readout's width depends on, at every rung: its shape, and each
|
|
45519
|
+
* value's sign, digit count and decimals (the digits themselves are tabular — see
|
|
45520
|
+
* the CSS; the grouping commas follow from the digit count). `toFixed` carries all
|
|
45521
|
+
* three at a fraction of the locale formatter's cost — this runs per pointer move.
|
|
45522
|
+
* The parts, the host's size and the fonts re-fit through their own paths. */
|
|
45523
|
+
widthKey() {
|
|
45524
|
+
const bar = this.hoverBar ?? this.lastBar;
|
|
45525
|
+
if (!bar) return this.readout;
|
|
45526
|
+
const dp = decimalsFor(bar.close);
|
|
45527
|
+
const diff = bar.close - bar.open;
|
|
45528
|
+
const text = [bar.open, bar.high, bar.low, bar.close, diff].map((v) => v.toFixed(dp));
|
|
45529
|
+
text.push((diff / bar.open * 100).toFixed(2));
|
|
45530
|
+
return `${this.readout} ${text.join(" ")}`.replace(/\d/g, "0");
|
|
45140
45531
|
}
|
|
45141
45532
|
/** Write the value readout for the current bar at the current ladder level. */
|
|
45142
45533
|
renderValues() {
|
|
@@ -46770,7 +47161,7 @@ var ChartCell = class {
|
|
|
46770
47161
|
* then the manifest in the host's own order. */
|
|
46771
47162
|
libraryRows() {
|
|
46772
47163
|
return [
|
|
46773
|
-
...this.supportedNatives().map((n) => ({ name: n.title, category: "
|
|
47164
|
+
...this.supportedNatives().map((n) => ({ name: n.title, category: "Built-in", native: true, nativeType: n.type, beta: n.beta })),
|
|
46774
47165
|
...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
|
|
46775
47166
|
];
|
|
46776
47167
|
}
|
|
@@ -47770,11 +48161,11 @@ function resolveTyping(key, open2) {
|
|
|
47770
48161
|
if (open2.dialogs > 0) {
|
|
47771
48162
|
if (key.length !== 1) return null;
|
|
47772
48163
|
if (open2.symbolSearch) return { target: "symbol", text: key.toUpperCase() };
|
|
47773
|
-
if (open2.timeframeEntry) return { target: "timeframe", text: key };
|
|
48164
|
+
if (open2.timeframeEntry) return { target: "timeframe", text: key.toUpperCase() };
|
|
47774
48165
|
return null;
|
|
47775
48166
|
}
|
|
47776
|
-
if (/^[a-zA-
|
|
47777
|
-
if (/^[
|
|
48167
|
+
if (/^[a-zA-Z0]$/.test(key)) return { target: "symbol", text: key.toUpperCase() };
|
|
48168
|
+
if (/^[1-9]$/.test(key)) return { target: "timeframe", text: key };
|
|
47778
48169
|
return null;
|
|
47779
48170
|
}
|
|
47780
48171
|
var VelaWorkspace = class {
|
|
@@ -49512,7 +49903,7 @@ var VelaWorkspace = class {
|
|
|
49512
49903
|
}
|
|
49513
49904
|
});
|
|
49514
49905
|
}
|
|
49515
|
-
/** Bare-typing router: letters → symbol search (seeded), digits → timeframe entry. */
|
|
49906
|
+
/** Bare-typing router: letters and `0` → symbol search (seeded), digits 1–9 → timeframe entry. */
|
|
49516
49907
|
routeTyping(ev) {
|
|
49517
49908
|
if (this.destroyed) return;
|
|
49518
49909
|
if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
|