@luxalgo/vela 0.6.9 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataProvider-8Z95Q-RJ.d.cts → DataProvider-CWmp31dA.d.ts} +49 -1
- package/dist/{DataProvider-DExJrfut.d.ts → DataProvider-l_eLMly_.d.cts} +49 -1
- package/dist/{chunk-EQCHJZOT.js → chunk-4KZVZ7QQ.js} +1 -1
- package/dist/{chunk-6WDDVMBJ.js → chunk-FFOP37FQ.js} +577 -321
- package/dist/{chunk-62SVGONC.js → chunk-G77Y7LK2.js} +2 -2
- package/dist/{chunk-WFSZBX3R.js → chunk-KG4YT3TI.js} +3 -1
- package/dist/{chunk-FCVIG7JG.js → chunk-MHY7MVXH.js} +5 -1
- package/dist/{chunk-STHSKXOR.js → chunk-NMTQXNT4.js} +591 -141
- package/dist/{contributions-D7PVZO2i.d.ts → contributions-BCz6Dr6a.d.ts} +6 -6
- package/dist/{contributions-C1U2Krwg.d.cts → contributions-D9vTzm5p.d.cts} +6 -6
- package/dist/index.cjs +580 -318
- package/dist/index.d.cts +31 -10
- package/dist/index.d.ts +31 -10
- package/dist/index.js +4 -4
- package/dist/{options-FM0peknS.d.ts → options-DSqHsQyN.d.cts} +6 -6
- package/dist/{options-FM0peknS.d.cts → options-DSqHsQyN.d.ts} +6 -6
- package/dist/{plugin-DfVqBz9p.d.cts → plugin-Bt8hLR8Z.d.cts} +3 -3
- package/dist/{plugin-7bkF32Rk.d.ts → plugin-CH7pfMrE.d.ts} +3 -3
- package/dist/plugin.cjs +3 -1
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- 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/{statusline-zdF4eZLr.d.ts → statusline-5K7y4Ya2.d.ts} +3 -3
- package/dist/{statusline-DOPiT6I6.d.cts → statusline-TYLQmoeh.d.cts} +3 -3
- package/dist/ui.cjs +7 -1
- package/dist/ui.d.cts +3 -1
- package/dist/ui.d.ts +3 -1
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +580 -318
- package/dist/vela.global.min.js +50 -48
- package/dist/widget.cjs +1052 -340
- package/dist/widget.d.cts +17 -6
- package/dist/widget.d.ts +17 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +1052 -340
- package/dist/workspace.d.cts +89 -5
- package/dist/workspace.d.ts +89 -5
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
package/dist/vela.global.js
CHANGED
|
@@ -528,6 +528,8 @@ var Vela = (function (exports) {
|
|
|
528
528
|
registerIcon("folder-minus", S('<path d="M1.6 4.4a1 1 0 0 1 1-1h2.7l1.3 1.7h6.8a1 1 0 0 1 1 1v6.5a1 1 0 0 1-1 1h-10.8a1 1 0 0 1-1-1z"/><path d="M6.2 9.4h3.6"/>'));
|
|
529
529
|
registerIcon("collapse", S('<path d="M3 8h10"/>'));
|
|
530
530
|
registerIcon("expand", S('<rect x="2.2" y="2.2" width="11.6" height="11.6" rx="1.4"/><path d="M8 5.4v5.2M5.4 8h5.2"/>'));
|
|
531
|
+
registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
|
|
532
|
+
registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
|
|
531
533
|
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"/>'));
|
|
532
534
|
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"/>'));
|
|
533
535
|
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"/>'));
|
|
@@ -535,7 +537,7 @@ var Vela = (function (exports) {
|
|
|
535
537
|
registerIcon("grip", S('<circle cx="6" cy="3.5" r="1"/><circle cx="10" cy="3.5" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12.5" r="1"/><circle cx="10" cy="12.5" r="1"/>', 'fill="currentColor" stroke="none"'));
|
|
536
538
|
registerIcon("kebab", S('<circle cx="8" cy="3.2" r="1.2"/><circle cx="8" cy="8" r="1.2"/><circle cx="8" cy="12.8" r="1.2"/>', 'fill="currentColor" stroke="none"'));
|
|
537
539
|
registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
|
|
538
|
-
registerIcon("reset", S('<
|
|
540
|
+
registerIcon("reset", S('<path d="M2 8a6 6 0 1 0 6-6 6.5 6.5 0 0 0-4.5 1.83L2 5.33"/><path d="M2 2v3.33h3.33"/>'));
|
|
539
541
|
registerIcon("pane-collapse", S('<path d="M2.6 9.4h4v4M13.4 6.6h-4v-4"/><path d="m9.4 6.6 4.2-4.2M2.4 13.6l4.2-4.2"/>'));
|
|
540
542
|
registerIcon("pane-expand", S('<path d="M9.8 2.4h3.8v3.8M6.2 13.6H2.4V9.8"/><path d="m13.6 2.4-4.4 4.4M2.4 13.6l4.4-4.4"/>'));
|
|
541
543
|
registerIcon("cursor", svg24('<path d="M5 3l6 16 2-6 6-2z"/>', 'fill="currentColor" stroke="none"'));
|
|
@@ -6906,6 +6908,23 @@ var Vela = (function (exports) {
|
|
|
6906
6908
|
return 36e5;
|
|
6907
6909
|
}
|
|
6908
6910
|
|
|
6911
|
+
// src/data/symbol-groups.ts
|
|
6912
|
+
function isGroupRow(d) {
|
|
6913
|
+
return d.group != null && d.ticker === d.group;
|
|
6914
|
+
}
|
|
6915
|
+
function groupKeyOf(d) {
|
|
6916
|
+
return `${(d.prefix ?? d.provider ?? "").toLowerCase()}:${(d.group ?? "").toUpperCase()}`;
|
|
6917
|
+
}
|
|
6918
|
+
function groupMembers(pool, groupRow) {
|
|
6919
|
+
const key = groupKeyOf(groupRow);
|
|
6920
|
+
return pool.filter((s) => s.group != null && !isGroupRow(s) && groupKeyOf(s) === key);
|
|
6921
|
+
}
|
|
6922
|
+
function defaultMemberOf(pool, groupRow) {
|
|
6923
|
+
const members = groupMembers(pool, groupRow);
|
|
6924
|
+
const defaults2 = members.filter((m) => m.default);
|
|
6925
|
+
return (defaults2.length === 1 ? defaults2[0] : members[0]) ?? null;
|
|
6926
|
+
}
|
|
6927
|
+
|
|
6909
6928
|
// src/data/ProviderRegistry.ts
|
|
6910
6929
|
var normName = (n) => n.trim().toLowerCase();
|
|
6911
6930
|
var normTicker = (t) => t.trim().toUpperCase();
|
|
@@ -6995,17 +7014,24 @@ var Vela = (function (exports) {
|
|
|
6995
7014
|
resolve(raw, opts = {}) {
|
|
6996
7015
|
const { provider, ticker } = this.parse(raw);
|
|
6997
7016
|
if (provider) {
|
|
6998
|
-
if (this.entries.has(provider))
|
|
7017
|
+
if (this.entries.has(provider)) {
|
|
7018
|
+
const d = this.entries.get(provider).byTicker?.get(normTicker(ticker));
|
|
7019
|
+
return { provider, ticker: d != null && isGroupRow(d) ? this.loadableTicker(provider, d) : ticker };
|
|
7020
|
+
}
|
|
6999
7021
|
const key = `${provider}:${normTicker(ticker)}`;
|
|
7000
7022
|
for (const name of this.candidateOrder(opts.default)) {
|
|
7001
7023
|
const d = this.entries.get(name).prefixIndex?.get(key);
|
|
7002
|
-
if (d) return { provider: name, ticker:
|
|
7024
|
+
if (d) return { provider: name, ticker: this.loadableTicker(name, d) };
|
|
7003
7025
|
}
|
|
7004
7026
|
return null;
|
|
7005
7027
|
}
|
|
7006
7028
|
const norm = normTicker(ticker);
|
|
7007
7029
|
for (const name of this.candidateOrder(opts.default)) {
|
|
7008
|
-
|
|
7030
|
+
const e = this.entries.get(name);
|
|
7031
|
+
if (e.index?.has(norm)) {
|
|
7032
|
+
const d = e.byTicker?.get(norm);
|
|
7033
|
+
return { provider: name, ticker: d != null && isGroupRow(d) ? this.loadableTicker(name, d) : ticker };
|
|
7034
|
+
}
|
|
7009
7035
|
}
|
|
7010
7036
|
if (opts.lenient && this.entries.size === 1) {
|
|
7011
7037
|
const only = this.names()[0];
|
|
@@ -7014,6 +7040,13 @@ var Vela = (function (exports) {
|
|
|
7014
7040
|
}
|
|
7015
7041
|
return null;
|
|
7016
7042
|
}
|
|
7043
|
+
/** What a resolved descriptor LOADS: itself — unless it is a GROUP row (listed, never
|
|
7044
|
+
* served), which loads its default member (single `default`, else first listed). A
|
|
7045
|
+
* memberless group keeps its own ticker and fails downstream like any unknown symbol. */
|
|
7046
|
+
loadableTicker(name, d) {
|
|
7047
|
+
if (!isGroupRow(d)) return d.ticker;
|
|
7048
|
+
return defaultMemberOf(this.entries.get(name)?.descriptors ?? [], d)?.ticker ?? d.ticker;
|
|
7049
|
+
}
|
|
7017
7050
|
/**
|
|
7018
7051
|
* Resolve now if possible, else resolve later — re-attempting after each provider
|
|
7019
7052
|
* settles. The promise stays pending until some registered provider can serve the
|
|
@@ -7302,6 +7335,10 @@ var Vela = (function (exports) {
|
|
|
7302
7335
|
/** Invalidates detached async work (backfill loops, in-flight loads, gap heals):
|
|
7303
7336
|
* bumped by init(), setMarket() and destroy(). */
|
|
7304
7337
|
this.generation = 0;
|
|
7338
|
+
/** Aborts the in-flight PROGRESSIVE load's source polling on supersession — an
|
|
7339
|
+
* abandoned stream left polling to its own budget starves the browser's per-host
|
|
7340
|
+
* connection pool, and the NEXT symbol's very first fetch with it (measured). */
|
|
7341
|
+
this.progressiveAbort = null;
|
|
7305
7342
|
/** Awaiters racing a superseded load (setMarket callers) — released on every bump so they never hang. */
|
|
7306
7343
|
this.supersedeWaiters = [];
|
|
7307
7344
|
/** `history:complete` fired for the CURRENT load. Each market load re-arms the cycle
|
|
@@ -7444,6 +7481,8 @@ var Vela = (function (exports) {
|
|
|
7444
7481
|
* superseded setMarket awaiters so their promises resolve instead of hanging. */
|
|
7445
7482
|
bumpGeneration() {
|
|
7446
7483
|
const gen = ++this.generation;
|
|
7484
|
+
this.progressiveAbort?.abort();
|
|
7485
|
+
this.progressiveAbort = null;
|
|
7447
7486
|
for (const w of this.supersedeWaiters.splice(0)) w();
|
|
7448
7487
|
return gen;
|
|
7449
7488
|
}
|
|
@@ -7497,7 +7536,47 @@ var Vela = (function (exports) {
|
|
|
7497
7536
|
const requested = market.bars ?? 500;
|
|
7498
7537
|
const initialRange = market.visibleRange;
|
|
7499
7538
|
const deep = !market.data?.length && initialRange == null && requested > SINGLE_LOAD_BARS;
|
|
7500
|
-
|
|
7539
|
+
let progressiveServed = false;
|
|
7540
|
+
if (!market.data?.length && initialRange == null && this.feed.loadProgressive) {
|
|
7541
|
+
let painted = false;
|
|
7542
|
+
const paint = (bars, final) => {
|
|
7543
|
+
if (this.generation !== gen || !final && bars.length === 0) return;
|
|
7544
|
+
this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
|
|
7545
|
+
if (!painted && bars.length > 0) {
|
|
7546
|
+
painted = true;
|
|
7547
|
+
if (opts.firstLoad) this.activateBarLayers();
|
|
7548
|
+
if (!final) this.historyState = "backfill";
|
|
7549
|
+
}
|
|
7550
|
+
};
|
|
7551
|
+
const abort = new AbortController();
|
|
7552
|
+
this.progressiveAbort = abort;
|
|
7553
|
+
progressiveServed = await new Promise((firstPaint) => {
|
|
7554
|
+
let signaled = false;
|
|
7555
|
+
const signal = (served) => {
|
|
7556
|
+
if (!signaled) {
|
|
7557
|
+
signaled = true;
|
|
7558
|
+
firstPaint(served);
|
|
7559
|
+
}
|
|
7560
|
+
};
|
|
7561
|
+
abort.signal.addEventListener("abort", () => signal(true), { once: true });
|
|
7562
|
+
this.feed.loadProgressive(market, (bars) => {
|
|
7563
|
+
paint(bars, false);
|
|
7564
|
+
if (painted) signal(true);
|
|
7565
|
+
}, { signal: abort.signal }).then((full) => {
|
|
7566
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
7567
|
+
if (full == null) return signal(false);
|
|
7568
|
+
if (this.generation !== gen) return signal(true);
|
|
7569
|
+
paint(full, true);
|
|
7570
|
+
this.completeHistory(full.length >= requested ? "depth" : "genesis");
|
|
7571
|
+
signal(true);
|
|
7572
|
+
}).catch(() => {
|
|
7573
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
7574
|
+
if (this.generation === gen) this.completeHistory("aborted");
|
|
7575
|
+
signal(true);
|
|
7576
|
+
});
|
|
7577
|
+
});
|
|
7578
|
+
}
|
|
7579
|
+
if (progressiveServed) ; else if (deep && this.feed.loadRange) {
|
|
7501
7580
|
const head = await this.feed.load({ ...market, bars: Math.min(requested, CHUNK_BARS) });
|
|
7502
7581
|
if (this.generation !== gen) return;
|
|
7503
7582
|
this.setBarSeries(head);
|
|
@@ -9169,6 +9248,28 @@ var Vela = (function (exports) {
|
|
|
9169
9248
|
this.store.merge(key, dropForming(bars));
|
|
9170
9249
|
return bars;
|
|
9171
9250
|
}
|
|
9251
|
+
/**
|
|
9252
|
+
* Progressive twin of {@link load}: a COLD load streams through the inner feed's
|
|
9253
|
+
* progressive path — batches forwarded verbatim, the FINAL answer cached exactly as
|
|
9254
|
+
* `load` caches — while a cache-covered load answers once through `load` itself (a
|
|
9255
|
+
* warm chart has nothing to stream). Falls back to `load` wholesale when the inner
|
|
9256
|
+
* feed lacks the capability, so callers may prefer this method unconditionally.
|
|
9257
|
+
*/
|
|
9258
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9259
|
+
if (cfg.data && cfg.data.length > 0) return this.inner.load(cfg);
|
|
9260
|
+
if (!this.inner.loadProgressive) return null;
|
|
9261
|
+
const symbol = cfg.symbol ?? "TEST";
|
|
9262
|
+
const key = cacheKey(symbol, cfg.timeframe ?? "60", cfg.session);
|
|
9263
|
+
const cached = this.store.get(key);
|
|
9264
|
+
const n = cfg.bars ?? 500;
|
|
9265
|
+
const lastCached = cached?.[cached.length - 1];
|
|
9266
|
+
if (cached && lastCached && cached.length >= n - 1 && this.inner.loadRange) return this.load(cfg);
|
|
9267
|
+
this.store.retainSymbol(symbol);
|
|
9268
|
+
const bars = await this.inner.loadProgressive(cfg, onBatch, opts);
|
|
9269
|
+
if (bars == null) return null;
|
|
9270
|
+
this.store.merge(key, dropForming(bars));
|
|
9271
|
+
return bars;
|
|
9272
|
+
}
|
|
9172
9273
|
/**
|
|
9173
9274
|
* Cache-backed ranged fetch — the gateway engines use for secondary series
|
|
9174
9275
|
* (`request.security` HTF/LTF/cross-symbol) and the orchestrator's backward
|
|
@@ -9414,6 +9515,17 @@ var Vela = (function (exports) {
|
|
|
9414
9515
|
this.prefetchSymbolInfo(resolved);
|
|
9415
9516
|
return this.cache.load(canonical(cfg, resolved));
|
|
9416
9517
|
}
|
|
9518
|
+
/** Progressive twin of {@link load} — same resolution, the cache streams the batches. */
|
|
9519
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9520
|
+
if (cfg.data && cfg.data.length > 0) {
|
|
9521
|
+
this.liveBars = cfg.data.map((b) => ({ ...b }));
|
|
9522
|
+
return cfg.data;
|
|
9523
|
+
}
|
|
9524
|
+
const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
|
|
9525
|
+
this.primaryProvider = resolved.provider;
|
|
9526
|
+
this.prefetchSymbolInfo(resolved);
|
|
9527
|
+
return this.cache.loadProgressive(canonical(cfg, resolved), onBatch, opts);
|
|
9528
|
+
}
|
|
9417
9529
|
/**
|
|
9418
9530
|
* Synchronous per-symbol metadata for engines (Pine `syminfo.*`), served from the cache
|
|
9419
9531
|
* warmed by load(). Undefined until the prefetch lands (the engine then synthesizes a
|
|
@@ -9501,6 +9613,18 @@ var Vela = (function (exports) {
|
|
|
9501
9613
|
if (!provider) return Promise.resolve([]);
|
|
9502
9614
|
return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session });
|
|
9503
9615
|
}
|
|
9616
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9617
|
+
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
9618
|
+
const provider = this.registry.get(name ?? "");
|
|
9619
|
+
if (!provider) return [];
|
|
9620
|
+
if (!provider.getBarsProgressive) return null;
|
|
9621
|
+
try {
|
|
9622
|
+
return await provider.getBarsProgressive(ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session }, onBatch, opts);
|
|
9623
|
+
} catch (e) {
|
|
9624
|
+
console.warn(`[vela] progressive fetch failed for ${ticker} ${cfg.timeframe ?? "60"} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
9625
|
+
return [];
|
|
9626
|
+
}
|
|
9627
|
+
}
|
|
9504
9628
|
loadRange(cfg, range) {
|
|
9505
9629
|
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
9506
9630
|
const provider = this.registry.get(name ?? "");
|
|
@@ -17014,6 +17138,10 @@ ${STATIC_DECLS}
|
|
|
17014
17138
|
get open() {
|
|
17015
17139
|
return this.pop?.open ?? false;
|
|
17016
17140
|
}
|
|
17141
|
+
/** Close the deployed panel, if any. The bubble itself stays. */
|
|
17142
|
+
hidePanel() {
|
|
17143
|
+
this.pop?.hide();
|
|
17144
|
+
}
|
|
17017
17145
|
destroy() {
|
|
17018
17146
|
this.pop?.destroy();
|
|
17019
17147
|
this.pop = null;
|
|
@@ -21110,6 +21238,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21110
21238
|
var FOLD_SVG = iconAt("chevron-up", LEGEND_ICON_PX2);
|
|
21111
21239
|
var UNFOLD_SVG = iconAt("chevron-down", LEGEND_ICON_PX2);
|
|
21112
21240
|
var OVERVIEW_SVG = iconAt("objects", LEGEND_ICON_PX2);
|
|
21241
|
+
function legendCalloutsDisplay(open2, hasCallouts) {
|
|
21242
|
+
return !open2 && hasCallouts ? "inline-flex" : "none";
|
|
21243
|
+
}
|
|
21113
21244
|
var InputsUI = class {
|
|
21114
21245
|
constructor(container, theme, paneBoundsOf) {
|
|
21115
21246
|
this.container = container;
|
|
@@ -21313,7 +21444,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21313
21444
|
row.callouts = [];
|
|
21314
21445
|
row.calloutsEl.replaceChildren();
|
|
21315
21446
|
const views = this.legendCallouts?.(row.id) ?? [];
|
|
21316
|
-
row.calloutsEl.style.display = views.length > 0
|
|
21447
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(row.highlighted, views.length > 0);
|
|
21317
21448
|
for (const view of views) {
|
|
21318
21449
|
const bubble = new CalloutBubble({
|
|
21319
21450
|
icon: view.icon,
|
|
@@ -21797,11 +21928,11 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21797
21928
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
21798
21929
|
if (open2) {
|
|
21799
21930
|
row.el.appendChild(row.statusEl);
|
|
21800
|
-
row.
|
|
21931
|
+
for (const bubble of row.callouts) bubble.hidePanel();
|
|
21801
21932
|
} else {
|
|
21802
21933
|
row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
21803
|
-
row.el.insertBefore(row.calloutsEl, row.statusEl);
|
|
21804
21934
|
}
|
|
21935
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(open2, row.callouts.length > 0);
|
|
21805
21936
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
21806
21937
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
21807
21938
|
if (child === row.extrasEl) {
|
|
@@ -21891,7 +22022,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21891
22022
|
};
|
|
21892
22023
|
var STYLE_ID = "vela-pane-controls";
|
|
21893
22024
|
var ICON_PX = 12;
|
|
21894
|
-
var CLUSTER_PILL = "rgba(0,0,0,0.
|
|
22025
|
+
var CLUSTER_PILL = "rgba(0,0,0,0.65)";
|
|
21895
22026
|
function ensureStyles() {
|
|
21896
22027
|
if (typeof document === "undefined" || document.getElementById(STYLE_ID)) return;
|
|
21897
22028
|
const st = document.createElement("style");
|
|
@@ -21911,8 +22042,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21911
22042
|
this.deps = deps;
|
|
21912
22043
|
this.clusters = /* @__PURE__ */ new Map();
|
|
21913
22044
|
this.hoverPaneId = null;
|
|
22045
|
+
/** Mobile: hover clusters are meaningless without a cursor — suppressed; a
|
|
22046
|
+
* collapsed pane's standalone expand chip stays (the only way back up). */
|
|
22047
|
+
this.suspended = false;
|
|
21914
22048
|
/** Reveal the cluster for the pane under the cursor, resolved from the pointer's y in the plot. */
|
|
21915
22049
|
this.onPlotMove = (e) => {
|
|
22050
|
+
if (this.suspended) return;
|
|
21916
22051
|
const rect = this.plot.getBoundingClientRect();
|
|
21917
22052
|
const y = e.clientY - rect.top;
|
|
21918
22053
|
let hit = null;
|
|
@@ -21984,7 +22119,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21984
22119
|
}
|
|
21985
22120
|
if (p.count > 1) {
|
|
21986
22121
|
cluster.appendChild(
|
|
21987
|
-
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
22122
|
+
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
22123
|
+
role: "maximize",
|
|
22124
|
+
// Same inverse-chip treatment as the collapsed pane's expand toggle: the
|
|
22125
|
+
// maximized state must read as an active state, not just a swapped glyph.
|
|
22126
|
+
selected: p.maximized
|
|
22127
|
+
})
|
|
21988
22128
|
);
|
|
21989
22129
|
}
|
|
21990
22130
|
}
|
|
@@ -22021,17 +22161,18 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22021
22161
|
}
|
|
22022
22162
|
const hovered = id === this.hoverPaneId;
|
|
22023
22163
|
const hasButtons = cluster.children.length > 0;
|
|
22024
|
-
const
|
|
22164
|
+
const stateChipRole = p.collapsed ? "collapse" : !this.suspended && p.maximized ? "maximize" : null;
|
|
22165
|
+
const visible = hasButtons && (hovered || stateChipRole != null) && p.height > 8;
|
|
22025
22166
|
cluster.style.right = `${rightPx}px`;
|
|
22026
22167
|
cluster.style.top = p.collapsed ? `${p.top + Math.max(1, Math.round((p.height - 24) / 2))}px` : `${p.top + 4}px`;
|
|
22027
22168
|
cluster.style.display = visible ? "flex" : "none";
|
|
22028
22169
|
if (!visible) continue;
|
|
22029
|
-
const
|
|
22030
|
-
cluster.style.background =
|
|
22170
|
+
const soloChip = stateChipRole != null && !hovered;
|
|
22171
|
+
cluster.style.background = soloChip ? "transparent" : CLUSTER_PILL;
|
|
22031
22172
|
for (const child of cluster.children) {
|
|
22032
22173
|
const btn2 = child;
|
|
22033
22174
|
btn2.style.display = "inline-flex";
|
|
22034
|
-
btn2.style.visibility =
|
|
22175
|
+
btn2.style.visibility = soloChip && btn2.dataset.role !== stateChipRole ? "hidden" : "visible";
|
|
22035
22176
|
}
|
|
22036
22177
|
}
|
|
22037
22178
|
}
|
|
@@ -22041,6 +22182,14 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22041
22182
|
this.hoverPaneId = paneId;
|
|
22042
22183
|
this.reposition();
|
|
22043
22184
|
}
|
|
22185
|
+
/** Mobile suppression: no hover clusters (touch has no cursor; the shell's own
|
|
22186
|
+
* chrome covers maximize), while collapsed panes keep their expand chips. */
|
|
22187
|
+
setSuspended(on) {
|
|
22188
|
+
if (on === this.suspended) return;
|
|
22189
|
+
this.suspended = on;
|
|
22190
|
+
if (on) this.hoverPaneId = null;
|
|
22191
|
+
this.reposition();
|
|
22192
|
+
}
|
|
22044
22193
|
destroy() {
|
|
22045
22194
|
this.plot.removeEventListener("pointermove", this.onPlotMove);
|
|
22046
22195
|
this.plot.removeEventListener("pointerleave", this.onPlotLeave);
|
|
@@ -22191,160 +22340,6 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22191
22340
|
}
|
|
22192
22341
|
};
|
|
22193
22342
|
|
|
22194
|
-
// src/renderers/shared/TableOverlay.ts
|
|
22195
|
-
var SIZE_PX3 = {
|
|
22196
|
-
auto: 13,
|
|
22197
|
-
tiny: 10,
|
|
22198
|
-
small: 11,
|
|
22199
|
-
normal: 13,
|
|
22200
|
-
large: 16,
|
|
22201
|
-
huge: 20
|
|
22202
|
-
};
|
|
22203
|
-
function fontPxOf(size3) {
|
|
22204
|
-
if (typeof size3 === "number") return size3 > 0 ? size3 : SIZE_PX3.auto;
|
|
22205
|
-
return SIZE_PX3[size3] ?? SIZE_PX3.auto;
|
|
22206
|
-
}
|
|
22207
|
-
function tableHasContent(t) {
|
|
22208
|
-
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
22209
|
-
}
|
|
22210
|
-
function mergeRenderPlan(t) {
|
|
22211
|
-
const span = /* @__PURE__ */ new Map();
|
|
22212
|
-
const omit = /* @__PURE__ */ new Set();
|
|
22213
|
-
for (const m of t.merges) {
|
|
22214
|
-
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
22215
|
-
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
22216
|
-
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
22217
|
-
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
22218
|
-
}
|
|
22219
|
-
}
|
|
22220
|
-
}
|
|
22221
|
-
for (let r = 0; r < t.rows; r += 1) {
|
|
22222
|
-
for (let c = 0; c < t.columns; c += 1) {
|
|
22223
|
-
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
22224
|
-
}
|
|
22225
|
-
}
|
|
22226
|
-
for (const key of span.keys()) omit.delete(key);
|
|
22227
|
-
return { span, omit };
|
|
22228
|
-
}
|
|
22229
|
-
var TableOverlay = class {
|
|
22230
|
-
constructor(container, theme, paneBounds) {
|
|
22231
|
-
this.container = container;
|
|
22232
|
-
this.theme = theme;
|
|
22233
|
-
this.paneBounds = paneBounds;
|
|
22234
|
-
this.lastTables = [];
|
|
22235
|
-
if (getComputedStyle(container).position === "static") container.style.position = "relative";
|
|
22236
|
-
this.root = document.createElement("div");
|
|
22237
|
-
Object.assign(this.root.style, {
|
|
22238
|
-
position: "absolute",
|
|
22239
|
-
inset: "0",
|
|
22240
|
-
pointerEvents: "none",
|
|
22241
|
-
overflow: "hidden",
|
|
22242
|
-
zIndex: "3"
|
|
22243
|
-
});
|
|
22244
|
-
container.appendChild(this.root);
|
|
22245
|
-
}
|
|
22246
|
-
update(tables) {
|
|
22247
|
-
this.lastTables = tables;
|
|
22248
|
-
this.root.replaceChildren();
|
|
22249
|
-
for (const t of tables) {
|
|
22250
|
-
if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
|
|
22251
|
-
}
|
|
22252
|
-
}
|
|
22253
|
-
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
22254
|
-
reposition() {
|
|
22255
|
-
if (this.root.isConnected) this.update(this.lastTables);
|
|
22256
|
-
}
|
|
22257
|
-
/** Show/hide the whole overlay. Tables anchor to pane corners, not to bars, so unlike the
|
|
22258
|
-
* series content they DON'T vanish with an emptied chart — the loading state hides them. */
|
|
22259
|
-
setVisible(visible) {
|
|
22260
|
-
this.root.style.display = visible ? "" : "none";
|
|
22261
|
-
}
|
|
22262
|
-
destroy() {
|
|
22263
|
-
this.root.remove();
|
|
22264
|
-
}
|
|
22265
|
-
renderTable(t) {
|
|
22266
|
-
const b = this.paneBounds(t.paneId);
|
|
22267
|
-
const wrap2 = document.createElement("div");
|
|
22268
|
-
wrap2.style.position = "absolute";
|
|
22269
|
-
if (t.frameColor && t.frameWidth > 0) wrap2.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
|
|
22270
|
-
this.anchor(wrap2, t.position, b);
|
|
22271
|
-
const table = document.createElement("table");
|
|
22272
|
-
Object.assign(table.style, {
|
|
22273
|
-
borderCollapse: "collapse",
|
|
22274
|
-
background: t.bgColor ?? "transparent",
|
|
22275
|
-
fontFamily: this.theme.fontFamily || "sans-serif",
|
|
22276
|
-
border: "none",
|
|
22277
|
-
tableLayout: "auto",
|
|
22278
|
-
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
22279
|
-
pointerEvents: "auto"
|
|
22280
|
-
});
|
|
22281
|
-
const { span, omit } = mergeRenderPlan(t);
|
|
22282
|
-
const plotW = Math.max(0, (this.root.clientWidth || this.container.clientWidth) - b.rightAxis);
|
|
22283
|
-
const paneH = b.height;
|
|
22284
|
-
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
22285
|
-
for (let r = 0; r < t.rows; r += 1) {
|
|
22286
|
-
const tr = document.createElement("tr");
|
|
22287
|
-
for (let c = 0; c < t.columns; c += 1) {
|
|
22288
|
-
if (omit.has(`${r}:${c}`)) continue;
|
|
22289
|
-
const cell = t.cells[r]?.[c] ?? null;
|
|
22290
|
-
const td = document.createElement("td");
|
|
22291
|
-
if (cell === null) {
|
|
22292
|
-
Object.assign(td.style, { padding: "0", border: "none" });
|
|
22293
|
-
tr.appendChild(td);
|
|
22294
|
-
continue;
|
|
22295
|
-
}
|
|
22296
|
-
const sp = span.get(`${r}:${c}`);
|
|
22297
|
-
if (sp) {
|
|
22298
|
-
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
22299
|
-
if (sp.rs > 1) td.rowSpan = sp.rs;
|
|
22300
|
-
}
|
|
22301
|
-
Object.assign(td.style, {
|
|
22302
|
-
border: cellBorder,
|
|
22303
|
-
padding: "2px 6px",
|
|
22304
|
-
background: cell.bgColor ?? "transparent",
|
|
22305
|
-
color: cell.textColor ?? this.theme.textColor,
|
|
22306
|
-
textAlign: cell.hAlign,
|
|
22307
|
-
verticalAlign: cell.vAlign === "top" ? "top" : cell.vAlign === "bottom" ? "bottom" : "middle",
|
|
22308
|
-
fontSize: `${fontPxOf(cell.textSize)}px`,
|
|
22309
|
-
fontFamily: cell.fontFamily === "monospace" ? "monospace" : "inherit",
|
|
22310
|
-
fontWeight: cell.bold ? "bold" : "normal",
|
|
22311
|
-
fontStyle: cell.italic ? "italic" : "normal",
|
|
22312
|
-
// Pine cell text never wraps; `\n` still breaks lines. Wrapping
|
|
22313
|
-
// used to collapse unicode sparklines and ━━━ dividers.
|
|
22314
|
-
whiteSpace: "pre"
|
|
22315
|
-
});
|
|
22316
|
-
if (cell.width) td.style.width = `${cell.width / 100 * plotW}px`;
|
|
22317
|
-
if (cell.height) td.style.height = `${cell.height / 100 * paneH}px`;
|
|
22318
|
-
if (cell.tooltip) td.title = cell.tooltip;
|
|
22319
|
-
td.textContent = cell.text ?? "";
|
|
22320
|
-
tr.appendChild(td);
|
|
22321
|
-
}
|
|
22322
|
-
table.appendChild(tr);
|
|
22323
|
-
}
|
|
22324
|
-
wrap2.appendChild(table);
|
|
22325
|
-
return wrap2;
|
|
22326
|
-
}
|
|
22327
|
-
/**
|
|
22328
|
-
* Position the wrapper at a Pine `position.*` corner/edge of the table's PANE
|
|
22329
|
-
* (not the whole chart), inset past the right price axis so it never overlaps
|
|
22330
|
-
* the Y-axis labels.
|
|
22331
|
-
*/
|
|
22332
|
-
anchor(el, position, b) {
|
|
22333
|
-
const m = 6;
|
|
22334
|
-
const containerH = this.root.clientHeight || this.container.clientHeight;
|
|
22335
|
-
const paneBottom = b.top + b.height;
|
|
22336
|
-
if (position.startsWith("top")) el.style.top = `${b.top + m}px`;
|
|
22337
|
-
else if (position.startsWith("bottom")) el.style.bottom = `${Math.max(0, containerH - paneBottom) + m}px`;
|
|
22338
|
-
else el.style.top = `${b.top + b.height / 2}px`;
|
|
22339
|
-
if (position.endsWith("left")) el.style.left = `${m}px`;
|
|
22340
|
-
else if (position.endsWith("right")) el.style.right = `${b.rightAxis + m}px`;
|
|
22341
|
-
else el.style.left = `calc(50% - ${b.rightAxis / 2}px)`;
|
|
22342
|
-
const tx = position.endsWith("center") ? "-50%" : "0";
|
|
22343
|
-
const ty = position.startsWith("middle") ? "-50%" : "0";
|
|
22344
|
-
if (tx !== "0" || ty !== "0") el.style.transform = `translate(${tx}, ${ty})`;
|
|
22345
|
-
}
|
|
22346
|
-
};
|
|
22347
|
-
|
|
22348
22343
|
// src/renderers/native/capabilities.ts
|
|
22349
22344
|
var NATIVE_CAPABILITIES = {
|
|
22350
22345
|
panes: true,
|
|
@@ -22365,7 +22360,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22365
22360
|
drawingDepth: true,
|
|
22366
22361
|
// drawings share the series' z space (backend-composited interleave layers)
|
|
22367
22362
|
tables: true,
|
|
22368
|
-
//
|
|
22363
|
+
// canvas-painted into the owning indicator's interleave slice
|
|
22369
22364
|
trades: true,
|
|
22370
22365
|
// strategy order-fill markers (arrows + labels + fill-price ticks)
|
|
22371
22366
|
inputsUI: true
|
|
@@ -22396,6 +22391,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22396
22391
|
if (spacing < CANDLE_BODY_MIN_SPACING) return "wick";
|
|
22397
22392
|
return "full";
|
|
22398
22393
|
}
|
|
22394
|
+
function snapY(yCss, dpr) {
|
|
22395
|
+
return Math.round(yCss * dpr) / dpr;
|
|
22396
|
+
}
|
|
22399
22397
|
function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
22400
22398
|
const wickDev = Math.max(1, Math.round(wickWidth(spacing) * dpr));
|
|
22401
22399
|
const wickLeftDev = Math.round(xCss * dpr - wickDev / 2);
|
|
@@ -23202,11 +23200,9 @@ void main() {
|
|
|
23202
23200
|
};
|
|
23203
23201
|
b.alpha = this.modelAlpha;
|
|
23204
23202
|
for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
|
|
23205
|
-
for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
23206
23203
|
if (isPrice) {
|
|
23207
23204
|
for (const m of scene.indicators.values()) {
|
|
23208
23205
|
for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
|
|
23209
|
-
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
23210
23206
|
}
|
|
23211
23207
|
}
|
|
23212
23208
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
@@ -23222,6 +23218,7 @@ void main() {
|
|
|
23222
23218
|
b.alpha = this.modelAlpha;
|
|
23223
23219
|
const off = scene.offsetOf(m.id);
|
|
23224
23220
|
const mp = effPane(m);
|
|
23221
|
+
for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, mp, coords, i0, i1, off);
|
|
23225
23222
|
for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
23226
23223
|
}
|
|
23227
23224
|
if (drawCandles && !candleDrawn) {
|
|
@@ -23229,14 +23226,18 @@ void main() {
|
|
|
23229
23226
|
b.alpha = this.candleStructureAlpha;
|
|
23230
23227
|
this.emitPriceSeries(b, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
23231
23228
|
}
|
|
23232
|
-
drawSlicesUpTo(Infinity);
|
|
23233
23229
|
b.alpha = this.modelAlpha;
|
|
23234
23230
|
if (isPrice) {
|
|
23231
|
+
for (const m of scene.indicators.values()) {
|
|
23232
|
+
const off = scene.offsetOf(m.id);
|
|
23233
|
+
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, off);
|
|
23234
|
+
}
|
|
23235
23235
|
for (const m of scene.indicators.values()) {
|
|
23236
23236
|
const off = scene.offsetOf(m.id);
|
|
23237
23237
|
for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
|
|
23238
23238
|
}
|
|
23239
23239
|
}
|
|
23240
|
+
drawSlicesUpTo(Infinity);
|
|
23240
23241
|
for (const m of models) {
|
|
23241
23242
|
const mp = effPane(m);
|
|
23242
23243
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -23631,12 +23632,12 @@ void main() {
|
|
|
23631
23632
|
if (drawBody) {
|
|
23632
23633
|
const oY = coords.priceToY(bar.open, pane.scale, pane.bounds);
|
|
23633
23634
|
const cY = coords.priceToY(bar.close, pane.scale, pane.bounds);
|
|
23634
|
-
bodyTop = Math.min(oY, cY);
|
|
23635
|
-
bodyH = Math.max(1, Math.
|
|
23635
|
+
bodyTop = snapY(Math.min(oY, cY), coords.dpr);
|
|
23636
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - bodyTop);
|
|
23636
23637
|
}
|
|
23637
23638
|
if (cs.wickVisible) {
|
|
23638
|
-
const hY = coords.priceToY(bar.high, pane.scale, pane.bounds);
|
|
23639
|
-
const lY = coords.priceToY(bar.low, pane.scale, pane.bounds);
|
|
23639
|
+
const hY = snapY(coords.priceToY(bar.high, pane.scale, pane.bounds), coords.dpr);
|
|
23640
|
+
const lY = snapY(coords.priceToY(bar.low, pane.scale, pane.bounds), coords.dpr);
|
|
23640
23641
|
b.alpha = this.candleStructureAlpha;
|
|
23641
23642
|
const wCol = parseColor((isUp ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : bodyColorStr));
|
|
23642
23643
|
if (drawBody) {
|
|
@@ -24969,9 +24970,10 @@ void main() {
|
|
|
24969
24970
|
* so each indicator arrives behind the candles (and behind older indicators);
|
|
24970
24971
|
* `setIndicatorZ`/`bringToFront`/`sendToBack` change it. */
|
|
24971
24972
|
this.seriesZ = /* @__PURE__ */ new Map();
|
|
24972
|
-
/** Per-pane raster layers of
|
|
24973
|
+
/** Per-pane raster layers of drawings interleaved into the series stack (each
|
|
24974
|
+
* indicator's Pine drawings at its model's z, plus in-stack user drawings) — each a
|
|
24973
24975
|
* prepainted canvas the backend composites just before the series carrying `beforeZ`.
|
|
24974
|
-
* Rebuilt by the renderer per data frame
|
|
24976
|
+
* Rebuilt by the renderer per data frame. */
|
|
24975
24977
|
this.drawingSlices = /* @__PURE__ */ new Map();
|
|
24976
24978
|
/** Per-model index offset: the chart bar index of the model's `anchorTime` — its
|
|
24977
24979
|
* index-aligned payloads (dense series arrays, `bar_index` drawings) count from that
|
|
@@ -25206,11 +25208,9 @@ void main() {
|
|
|
25206
25208
|
};
|
|
25207
25209
|
ctx.globalAlpha = this.modelAlpha;
|
|
25208
25210
|
for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
|
|
25209
|
-
for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
25210
25211
|
if (isPrice) {
|
|
25211
25212
|
for (const m of scene.indicators.values()) {
|
|
25212
25213
|
for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
|
|
25213
|
-
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
25214
25214
|
}
|
|
25215
25215
|
}
|
|
25216
25216
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
@@ -25234,6 +25234,7 @@ void main() {
|
|
|
25234
25234
|
ctx.globalAlpha = this.modelAlpha;
|
|
25235
25235
|
const off = scene.offsetOf(m.id);
|
|
25236
25236
|
const mp = effPane(m);
|
|
25237
|
+
for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, mp, coords, i0, i1, off);
|
|
25237
25238
|
for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
25238
25239
|
}
|
|
25239
25240
|
if (drawCandles && !candleDrawn) {
|
|
@@ -25241,14 +25242,18 @@ void main() {
|
|
|
25241
25242
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
25242
25243
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
25243
25244
|
}
|
|
25244
|
-
drawSlicesUpTo(Infinity);
|
|
25245
25245
|
if (isPrice) {
|
|
25246
25246
|
ctx.globalAlpha = this.modelAlpha;
|
|
25247
|
+
for (const m of scene.indicators.values()) {
|
|
25248
|
+
const off = scene.offsetOf(m.id);
|
|
25249
|
+
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, off);
|
|
25250
|
+
}
|
|
25247
25251
|
for (const m of scene.indicators.values()) {
|
|
25248
25252
|
const off = scene.offsetOf(m.id);
|
|
25249
25253
|
for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
|
|
25250
25254
|
}
|
|
25251
25255
|
}
|
|
25256
|
+
drawSlicesUpTo(Infinity);
|
|
25252
25257
|
ctx.globalAlpha = this.modelAlpha;
|
|
25253
25258
|
for (const m of models) {
|
|
25254
25259
|
const mp = effPane(m);
|
|
@@ -25492,13 +25497,13 @@ void main() {
|
|
|
25492
25497
|
if (drawBody) {
|
|
25493
25498
|
const oY = coords.priceToY(b.open, pane.scale, pane.bounds);
|
|
25494
25499
|
const cY = coords.priceToY(b.close, pane.scale, pane.bounds);
|
|
25495
|
-
top = Math.min(oY, cY);
|
|
25496
|
-
bodyH = Math.max(1, Math.
|
|
25500
|
+
top = snapY(Math.min(oY, cY), coords.dpr);
|
|
25501
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - top);
|
|
25497
25502
|
}
|
|
25498
25503
|
if (cs.wickVisible) {
|
|
25499
25504
|
const wick = (up ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : color);
|
|
25500
|
-
const hY = coords.priceToY(b.high, pane.scale, pane.bounds);
|
|
25501
|
-
const lY = coords.priceToY(b.low, pane.scale, pane.bounds);
|
|
25505
|
+
const hY = snapY(coords.priceToY(b.high, pane.scale, pane.bounds), coords.dpr);
|
|
25506
|
+
const lY = snapY(coords.priceToY(b.low, pane.scale, pane.bounds), coords.dpr);
|
|
25502
25507
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
25503
25508
|
ctx.strokeStyle = wick;
|
|
25504
25509
|
ctx.lineWidth = g.wickW;
|
|
@@ -25945,6 +25950,19 @@ void main() {
|
|
|
25945
25950
|
|
|
25946
25951
|
// src/renderers/shared/DrawingSceneRenderer.ts
|
|
25947
25952
|
var EMPTY_DRAWING_SET = { lines: [], boxes: [], labels: [], polylines: [], linefills: [] };
|
|
25953
|
+
function modelDrawingSet(m, overlay) {
|
|
25954
|
+
const want = (d) => Boolean(d.overlay) === overlay;
|
|
25955
|
+
return {
|
|
25956
|
+
lines: (m.lines ?? []).filter(want),
|
|
25957
|
+
boxes: (m.boxes ?? []).filter(want),
|
|
25958
|
+
labels: (m.labels ?? []).filter(want),
|
|
25959
|
+
polylines: (m.polylines ?? []).filter(want),
|
|
25960
|
+
linefills: (m.linefills ?? []).filter(want)
|
|
25961
|
+
};
|
|
25962
|
+
}
|
|
25963
|
+
function drawingSetEmpty(s) {
|
|
25964
|
+
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
25965
|
+
}
|
|
25948
25966
|
function fontSizePx(size3) {
|
|
25949
25967
|
return size3 === "auto" ? 12 : namedFontSize(size3);
|
|
25950
25968
|
}
|
|
@@ -26797,10 +26815,8 @@ void main() {
|
|
|
26797
26815
|
this.ctx = null;
|
|
26798
26816
|
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
26799
26817
|
this.axisTextColor = DARK_THEME.textColor;
|
|
26800
|
-
// Shared Pine-drawing renderer
|
|
26818
|
+
// Shared Pine-drawing renderer, used here for autoscale geometry only; widthCache persists.
|
|
26801
26819
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
26802
|
-
// Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per render).
|
|
26803
|
-
this.labelTips = [];
|
|
26804
26820
|
}
|
|
26805
26821
|
mount(canvas) {
|
|
26806
26822
|
this.canvas = canvas;
|
|
@@ -26824,8 +26840,8 @@ void main() {
|
|
|
26824
26840
|
*/
|
|
26825
26841
|
paneDrawingsRange(ownModels, scene, isPricePane, vr) {
|
|
26826
26842
|
let dr = null;
|
|
26827
|
-
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(
|
|
26828
|
-
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(
|
|
26843
|
+
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, false), vr, scene.offsetOf(m.id)));
|
|
26844
|
+
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, true), vr, scene.offsetOf(m.id)));
|
|
26829
26845
|
return dr;
|
|
26830
26846
|
}
|
|
26831
26847
|
/** Clear the chrome canvas and draw drawings + axes + current-price line.
|
|
@@ -26842,7 +26858,6 @@ void main() {
|
|
|
26842
26858
|
const dataW = coords.width;
|
|
26843
26859
|
const dataH = coords.height;
|
|
26844
26860
|
this.axisTextColor = surface?.textColor ?? theme.textColor;
|
|
26845
|
-
this.labelTips = [];
|
|
26846
26861
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
26847
26862
|
ctx.clearRect(0, 0, fullW, fullH);
|
|
26848
26863
|
if (surface && (fullW > dataW || fullH > dataH)) {
|
|
@@ -26858,17 +26873,6 @@ void main() {
|
|
|
26858
26873
|
return;
|
|
26859
26874
|
}
|
|
26860
26875
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
26861
|
-
for (const pane of panes) {
|
|
26862
|
-
if (pane.collapsed) continue;
|
|
26863
|
-
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
26864
|
-
const sc = scene.scaleFor(m, pane);
|
|
26865
|
-
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
26866
|
-
this.renderDrawings(ctx, coords, this.ownDrawings(m), mp, dataW, scene.offsetOf(m.id));
|
|
26867
|
-
}
|
|
26868
|
-
}
|
|
26869
|
-
if (pricePane) {
|
|
26870
|
-
for (const m of scene.indicators.values()) this.renderDrawings(ctx, coords, this.overlayDrawings(m), pricePane, dataW, scene.offsetOf(m.id));
|
|
26871
|
-
}
|
|
26872
26876
|
if (pricePane && !pricePane.collapsed && scene.tradeMarkers.visible) {
|
|
26873
26877
|
for (const m of scene.indicators.values()) {
|
|
26874
26878
|
if (m.trades?.length) this.renderTrades(ctx, coords, scene, theme, m.trades, pricePane, dataW);
|
|
@@ -26884,25 +26888,6 @@ void main() {
|
|
|
26884
26888
|
this.canvas = null;
|
|
26885
26889
|
this.ctx = null;
|
|
26886
26890
|
}
|
|
26887
|
-
// ── Pine-drawing helpers (own vs force_overlay routing) ──
|
|
26888
|
-
ownDrawings(m) {
|
|
26889
|
-
return {
|
|
26890
|
-
lines: (m.lines ?? []).filter((d) => !d.overlay),
|
|
26891
|
-
boxes: (m.boxes ?? []).filter((d) => !d.overlay),
|
|
26892
|
-
labels: (m.labels ?? []).filter((d) => !d.overlay),
|
|
26893
|
-
polylines: (m.polylines ?? []).filter((d) => !d.overlay),
|
|
26894
|
-
linefills: (m.linefills ?? []).filter((d) => !d.overlay)
|
|
26895
|
-
};
|
|
26896
|
-
}
|
|
26897
|
-
overlayDrawings(m) {
|
|
26898
|
-
return {
|
|
26899
|
-
lines: (m.lines ?? []).filter((d) => d.overlay),
|
|
26900
|
-
boxes: (m.boxes ?? []).filter((d) => d.overlay),
|
|
26901
|
-
labels: (m.labels ?? []).filter((d) => d.overlay),
|
|
26902
|
-
polylines: (m.polylines ?? []).filter((d) => d.overlay),
|
|
26903
|
-
linefills: (m.linefills ?? []).filter((d) => d.overlay)
|
|
26904
|
-
};
|
|
26905
|
-
}
|
|
26906
26891
|
drawingsRange(set, vr, indexOffset = 0) {
|
|
26907
26892
|
this.drawScene.setSet(set, indexOffset);
|
|
26908
26893
|
if (this.drawScene.isEmpty()) return null;
|
|
@@ -26936,34 +26921,6 @@ void main() {
|
|
|
26936
26921
|
);
|
|
26937
26922
|
ctx.restore();
|
|
26938
26923
|
}
|
|
26939
|
-
renderDrawings(ctx, coords, set, pane, dataW, indexOffset = 0) {
|
|
26940
|
-
this.drawScene.setSet(set, indexOffset);
|
|
26941
|
-
if (this.drawScene.isEmpty()) return;
|
|
26942
|
-
ctx.save();
|
|
26943
|
-
ctx.translate(0, pane.bounds.top);
|
|
26944
|
-
ctx.beginPath();
|
|
26945
|
-
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
26946
|
-
ctx.clip();
|
|
26947
|
-
this.drawScene.render(
|
|
26948
|
-
ctx,
|
|
26949
|
-
dataW,
|
|
26950
|
-
pane.bounds.height,
|
|
26951
|
-
(l) => coords.logicalToX(l),
|
|
26952
|
-
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
26953
|
-
);
|
|
26954
|
-
ctx.restore();
|
|
26955
|
-
for (const r of this.drawScene.labelTipRegions()) {
|
|
26956
|
-
this.labelTips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
26957
|
-
}
|
|
26958
|
-
}
|
|
26959
|
-
/** Tooltip of the topmost label under a plot-space point, or null. Fed by the last render. */
|
|
26960
|
-
labelTooltipAt(x, y) {
|
|
26961
|
-
for (let i = this.labelTips.length - 1; i >= 0; i -= 1) {
|
|
26962
|
-
const r = this.labelTips[i];
|
|
26963
|
-
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
26964
|
-
}
|
|
26965
|
-
return null;
|
|
26966
|
-
}
|
|
26967
26924
|
// ── axes ──
|
|
26968
26925
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
26969
26926
|
ctx.strokeStyle = scene.style.borderColor ?? theme.borderColor;
|
|
@@ -31372,17 +31329,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
31372
31329
|
return textGlyph(String(glyph), 15);
|
|
31373
31330
|
}
|
|
31374
31331
|
function stampSizeIcon(size3) {
|
|
31375
|
-
return textGlyph("\u25CF", (
|
|
31332
|
+
return textGlyph("\u25CF", (SIZE_PX3[String(size3)] ?? 13) + 4);
|
|
31376
31333
|
}
|
|
31377
31334
|
function sizeIcon(size3) {
|
|
31378
31335
|
return textGlyph(String(size3).charAt(0).toUpperCase(), 15);
|
|
31379
31336
|
}
|
|
31380
|
-
var
|
|
31337
|
+
var SIZE_PX3 = { small: 10, normal: 13, large: 16, huge: 20 };
|
|
31381
31338
|
function numbersSizeIcon(size3) {
|
|
31382
|
-
return textGlyph("12", (
|
|
31339
|
+
return textGlyph("12", (SIZE_PX3[String(size3)] ?? 13) - 1, 16.5, 'font-weight="600"');
|
|
31383
31340
|
}
|
|
31384
31341
|
function labelSizeIcon(size3) {
|
|
31385
|
-
return textGlyph("T", (
|
|
31342
|
+
return textGlyph("T", (SIZE_PX3[String(size3)] ?? 13) + 2);
|
|
31386
31343
|
}
|
|
31387
31344
|
function capitalize(s) {
|
|
31388
31345
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -31546,7 +31503,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
31546
31503
|
this.root.replaceChildren();
|
|
31547
31504
|
this.groupCells.clear();
|
|
31548
31505
|
this.groupIcons.clear();
|
|
31549
|
-
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.
|
|
31506
|
+
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.onCursorClick());
|
|
31550
31507
|
this.root.appendChild(this.cursorBtn);
|
|
31551
31508
|
if (this.def.groups.length > 0) this.root.appendChild(this.divider());
|
|
31552
31509
|
for (const g of this.def.groups) {
|
|
@@ -31650,6 +31607,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
31650
31607
|
this.magnetIcon = icon2;
|
|
31651
31608
|
return cell;
|
|
31652
31609
|
}
|
|
31610
|
+
/** Cursor returns to select/idle: an active measure/eraser mode exits through its own
|
|
31611
|
+
* toggle callback (disarming a tool via `onArm(null)` alone can't — the host treats a
|
|
31612
|
+
* null arm as a no-op side effect of entering those modes), then the tool disarms. */
|
|
31613
|
+
onCursorClick() {
|
|
31614
|
+
if (this.measureActive) this.onMeasure();
|
|
31615
|
+
if (this.eraserActive) this.onEraser();
|
|
31616
|
+
this.onArm(null);
|
|
31617
|
+
}
|
|
31653
31618
|
/** Clicking the icon arms the group's last-used tool (it does NOT open the flyout). */
|
|
31654
31619
|
onGroupIconClick(group) {
|
|
31655
31620
|
const type = this.lastUsed.get(group.id) ?? group.tools[0]?.type;
|
|
@@ -32981,6 +32946,315 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32981
32946
|
}
|
|
32982
32947
|
};
|
|
32983
32948
|
|
|
32949
|
+
// src/renderers/shared/TableOverlay.ts
|
|
32950
|
+
var SIZE_PX4 = {
|
|
32951
|
+
auto: 13,
|
|
32952
|
+
tiny: 10,
|
|
32953
|
+
small: 11,
|
|
32954
|
+
normal: 13,
|
|
32955
|
+
large: 16,
|
|
32956
|
+
huge: 20
|
|
32957
|
+
};
|
|
32958
|
+
function fontPxOf(size3) {
|
|
32959
|
+
if (typeof size3 === "number") return size3 > 0 ? size3 : SIZE_PX4.auto;
|
|
32960
|
+
return SIZE_PX4[size3] ?? SIZE_PX4.auto;
|
|
32961
|
+
}
|
|
32962
|
+
function tableHasContent(t) {
|
|
32963
|
+
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
32964
|
+
}
|
|
32965
|
+
function mergeRenderPlan(t) {
|
|
32966
|
+
const span = /* @__PURE__ */ new Map();
|
|
32967
|
+
const omit = /* @__PURE__ */ new Set();
|
|
32968
|
+
for (const m of t.merges) {
|
|
32969
|
+
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
32970
|
+
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
32971
|
+
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
32972
|
+
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
32973
|
+
}
|
|
32974
|
+
}
|
|
32975
|
+
}
|
|
32976
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
32977
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
32978
|
+
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
32979
|
+
}
|
|
32980
|
+
}
|
|
32981
|
+
for (const key of span.keys()) omit.delete(key);
|
|
32982
|
+
return { span, omit };
|
|
32983
|
+
}
|
|
32984
|
+
|
|
32985
|
+
// src/renderers/shared/TableCanvasRenderer.ts
|
|
32986
|
+
var PAD_X = 6;
|
|
32987
|
+
var PAD_Y = 2;
|
|
32988
|
+
var MARGIN = 6;
|
|
32989
|
+
var LINE_HEIGHT = 1.2;
|
|
32990
|
+
function paintTable(ctx, t, args, tips) {
|
|
32991
|
+
if (!tableHasContent(t)) return;
|
|
32992
|
+
const layout = layoutTable(ctx, t, args);
|
|
32993
|
+
if (!layout || layout.w <= 0 || layout.h <= 0) return;
|
|
32994
|
+
const fw = t.frameColor && t.frameWidth > 0 ? t.frameWidth : 0;
|
|
32995
|
+
const { x, y } = anchorOrigin(t.position, layout.w + 2 * fw, layout.h + 2 * fw, args);
|
|
32996
|
+
const x0 = x + fw;
|
|
32997
|
+
const y0 = y + fw;
|
|
32998
|
+
if (t.bgColor) {
|
|
32999
|
+
ctx.fillStyle = t.bgColor;
|
|
33000
|
+
ctx.fillRect(x0, y0, layout.w, layout.h);
|
|
33001
|
+
}
|
|
33002
|
+
if (fw > 0 && t.frameColor) {
|
|
33003
|
+
ctx.strokeStyle = t.frameColor;
|
|
33004
|
+
ctx.lineWidth = fw;
|
|
33005
|
+
ctx.strokeRect(x + fw / 2, y + fw / 2, layout.w + fw, layout.h + fw);
|
|
33006
|
+
}
|
|
33007
|
+
const colX = [0];
|
|
33008
|
+
for (const w of layout.colW) colX.push(colX[colX.length - 1] + w);
|
|
33009
|
+
const rowY = [0];
|
|
33010
|
+
for (const h of layout.rowH) rowY.push(rowY[rowY.length - 1] + h);
|
|
33011
|
+
const prevBaseline = ctx.textBaseline;
|
|
33012
|
+
const prevAlign = ctx.textAlign;
|
|
33013
|
+
ctx.textBaseline = "middle";
|
|
33014
|
+
for (const box of layout.boxes) {
|
|
33015
|
+
const rx = x0 + colX[box.c];
|
|
33016
|
+
const ry = y0 + rowY[box.r];
|
|
33017
|
+
const rw = colX[box.c + box.cs] - colX[box.c];
|
|
33018
|
+
const rh = rowY[box.r + box.rs] - rowY[box.r];
|
|
33019
|
+
const cell = box.cell;
|
|
33020
|
+
if (cell.bgColor) {
|
|
33021
|
+
ctx.fillStyle = cell.bgColor;
|
|
33022
|
+
ctx.fillRect(rx, ry, rw, rh);
|
|
33023
|
+
}
|
|
33024
|
+
const text = cell.text ?? "";
|
|
33025
|
+
if (text.length > 0) {
|
|
33026
|
+
const px = fontPxOf(cell.textSize);
|
|
33027
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
33028
|
+
ctx.fillStyle = cell.textColor ?? args.theme.textColor;
|
|
33029
|
+
const lines = text.split("\n");
|
|
33030
|
+
const blockH = lines.length * px * LINE_HEIGHT;
|
|
33031
|
+
const blockTop = cell.vAlign === "top" ? ry + PAD_Y : cell.vAlign === "bottom" ? ry + rh - PAD_Y - blockH : ry + (rh - blockH) / 2;
|
|
33032
|
+
const tx = cell.hAlign === "left" ? rx + PAD_X : cell.hAlign === "right" ? rx + rw - PAD_X : rx + rw / 2;
|
|
33033
|
+
ctx.textAlign = cell.hAlign;
|
|
33034
|
+
lines.forEach((line, i) => ctx.fillText(line, tx, blockTop + (i + 0.5) * px * LINE_HEIGHT));
|
|
33035
|
+
}
|
|
33036
|
+
if (cell.tooltip) tips.push({ left: rx, top: ry, right: rx + rw, bottom: ry + rh, text: cell.tooltip });
|
|
33037
|
+
}
|
|
33038
|
+
ctx.textBaseline = prevBaseline;
|
|
33039
|
+
ctx.textAlign = prevAlign;
|
|
33040
|
+
if (t.borderColor && t.borderWidth > 0) {
|
|
33041
|
+
ctx.strokeStyle = t.borderColor;
|
|
33042
|
+
ctx.lineWidth = t.borderWidth;
|
|
33043
|
+
const seen = /* @__PURE__ */ new Set();
|
|
33044
|
+
ctx.beginPath();
|
|
33045
|
+
const edge = (ax, ay, bx, by) => {
|
|
33046
|
+
const key = `${ax},${ay},${bx},${by}`;
|
|
33047
|
+
if (seen.has(key)) return;
|
|
33048
|
+
seen.add(key);
|
|
33049
|
+
ctx.moveTo(ax, ay);
|
|
33050
|
+
ctx.lineTo(bx, by);
|
|
33051
|
+
};
|
|
33052
|
+
for (const box of layout.boxes) {
|
|
33053
|
+
const l = Math.round(x0 + colX[box.c]);
|
|
33054
|
+
const r = Math.round(x0 + colX[box.c + box.cs]);
|
|
33055
|
+
const tp = Math.round(y0 + rowY[box.r]);
|
|
33056
|
+
const bt = Math.round(y0 + rowY[box.r + box.rs]);
|
|
33057
|
+
edge(l, tp, r, tp);
|
|
33058
|
+
edge(l, bt, r, bt);
|
|
33059
|
+
edge(l, tp, l, bt);
|
|
33060
|
+
edge(r, tp, r, bt);
|
|
33061
|
+
}
|
|
33062
|
+
ctx.stroke();
|
|
33063
|
+
}
|
|
33064
|
+
}
|
|
33065
|
+
function layoutTable(ctx, t, args) {
|
|
33066
|
+
const { span, omit } = mergeRenderPlan(t);
|
|
33067
|
+
const colW = new Array(t.columns).fill(0);
|
|
33068
|
+
const rowH = new Array(t.rows).fill(0);
|
|
33069
|
+
const boxes = [];
|
|
33070
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
33071
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
33072
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
33073
|
+
const cell = t.cells[r]?.[c];
|
|
33074
|
+
if (cell == null) continue;
|
|
33075
|
+
const sp = span.get(`${r}:${c}`);
|
|
33076
|
+
boxes.push({ cell, r, c, cs: Math.min(sp?.cs ?? 1, t.columns - c), rs: Math.min(sp?.rs ?? 1, t.rows - r) });
|
|
33077
|
+
}
|
|
33078
|
+
}
|
|
33079
|
+
if (boxes.length === 0) return null;
|
|
33080
|
+
const sizeOf = (cell) => {
|
|
33081
|
+
const px = fontPxOf(cell.textSize);
|
|
33082
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
33083
|
+
const lines = (cell.text ?? "").split("\n");
|
|
33084
|
+
let maxW = 0;
|
|
33085
|
+
for (const line of lines) maxW = Math.max(maxW, ctx.measureText(line).width);
|
|
33086
|
+
let w2 = Math.ceil(maxW) + 2 * PAD_X;
|
|
33087
|
+
let h2 = Math.ceil(lines.length * px * LINE_HEIGHT) + 2 * PAD_Y;
|
|
33088
|
+
if (cell.width) w2 = Math.max(w2, cell.width / 100 * args.plotWidth);
|
|
33089
|
+
if (cell.height) h2 = Math.max(h2, cell.height / 100 * args.paneHeight);
|
|
33090
|
+
return { w: w2, h: h2 };
|
|
33091
|
+
};
|
|
33092
|
+
const spanning = [];
|
|
33093
|
+
for (const box of boxes) {
|
|
33094
|
+
const { w: w2, h: h2 } = sizeOf(box.cell);
|
|
33095
|
+
if (box.cs === 1) colW[box.c] = Math.max(colW[box.c], w2);
|
|
33096
|
+
if (box.rs === 1) rowH[box.r] = Math.max(rowH[box.r], h2);
|
|
33097
|
+
if (box.cs > 1 || box.rs > 1) spanning.push({ box, w: w2, h: h2 });
|
|
33098
|
+
}
|
|
33099
|
+
for (const { box, w: w2, h: h2 } of spanning) {
|
|
33100
|
+
if (box.cs > 1) {
|
|
33101
|
+
let sum = 0;
|
|
33102
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
|
|
33103
|
+
if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
|
|
33104
|
+
}
|
|
33105
|
+
if (box.rs > 1) {
|
|
33106
|
+
let sum = 0;
|
|
33107
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
|
|
33108
|
+
if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
|
|
33109
|
+
}
|
|
33110
|
+
}
|
|
33111
|
+
let w = 0;
|
|
33112
|
+
for (const cw of colW) w += cw;
|
|
33113
|
+
let h = 0;
|
|
33114
|
+
for (const rh of rowH) h += rh;
|
|
33115
|
+
return { colW, rowH, w, h, boxes };
|
|
33116
|
+
}
|
|
33117
|
+
function anchorOrigin(position, totalW, totalH, args) {
|
|
33118
|
+
let y;
|
|
33119
|
+
if (position.startsWith("top")) y = MARGIN;
|
|
33120
|
+
else if (position.startsWith("bottom")) y = args.paneHeight - MARGIN - totalH;
|
|
33121
|
+
else y = args.paneHeight / 2 - totalH / 2;
|
|
33122
|
+
let x;
|
|
33123
|
+
if (position.endsWith("left")) x = MARGIN;
|
|
33124
|
+
else if (position.endsWith("right")) x = args.plotWidth - MARGIN - totalW;
|
|
33125
|
+
else x = args.plotWidth / 2 - totalW / 2;
|
|
33126
|
+
return { x, y };
|
|
33127
|
+
}
|
|
33128
|
+
function cellFont(cell, px, theme) {
|
|
33129
|
+
const family = cell.fontFamily === "monospace" ? "monospace" : theme.fontFamily || "sans-serif";
|
|
33130
|
+
return `${cell.italic ? "italic " : ""}${cell.bold ? "bold " : ""}${px}px ${family}`;
|
|
33131
|
+
}
|
|
33132
|
+
|
|
33133
|
+
// src/renderers/native/drawings/IndicatorDrawingSlices.ts
|
|
33134
|
+
function indicatorSliceKey(z, boundaries) {
|
|
33135
|
+
return boundaries.find((b) => b > z) ?? Infinity;
|
|
33136
|
+
}
|
|
33137
|
+
var IndicatorDrawingSlices = class {
|
|
33138
|
+
constructor() {
|
|
33139
|
+
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
33140
|
+
/** Slice canvas cache, keyed `paneId|beforeZ` — same lifecycle as the user-drawing cache. */
|
|
33141
|
+
this.sliceCache = /* @__PURE__ */ new Map();
|
|
33142
|
+
/** Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per prepare). */
|
|
33143
|
+
this.tips = [];
|
|
33144
|
+
}
|
|
33145
|
+
/**
|
|
33146
|
+
* Rebuild the per-indicator drawing slices for this data frame. `ref` is the data
|
|
33147
|
+
* canvas the slices must match pixel-for-pixel (the backend composites them 1:1).
|
|
33148
|
+
* Runs from the renderer's data paint, just before the backend composites the scene.
|
|
33149
|
+
*/
|
|
33150
|
+
prepare(scene, coords, theme, ref2) {
|
|
33151
|
+
this.tips = [];
|
|
33152
|
+
const out = /* @__PURE__ */ new Map();
|
|
33153
|
+
if (ref2.width === 0 || ref2.height === 0) {
|
|
33154
|
+
this.sliceCache.clear();
|
|
33155
|
+
return out;
|
|
33156
|
+
}
|
|
33157
|
+
this.drawScene.setDeps({
|
|
33158
|
+
timeToLogical: (ms) => coords.timeToLogical(ms),
|
|
33159
|
+
barAt: (logical) => {
|
|
33160
|
+
const b = scene.bars[Math.round(logical)];
|
|
33161
|
+
return b ? { high: b.high, low: b.low } : null;
|
|
33162
|
+
},
|
|
33163
|
+
theme
|
|
33164
|
+
});
|
|
33165
|
+
const dpr = coords.dpr;
|
|
33166
|
+
const dataW = coords.width;
|
|
33167
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
33168
|
+
const add = (paneId, beforeZ, entry) => {
|
|
33169
|
+
const key = `${paneId}|${beforeZ}`;
|
|
33170
|
+
const bucket = buckets.get(key);
|
|
33171
|
+
if (bucket) bucket.entries.push(entry);
|
|
33172
|
+
else buckets.set(key, { paneId, beforeZ, entries: [entry] });
|
|
33173
|
+
};
|
|
33174
|
+
for (const pane of scene.orderedPanes()) {
|
|
33175
|
+
if (pane.collapsed) continue;
|
|
33176
|
+
const boundaries = scene.seriesBoundaries(pane.id);
|
|
33177
|
+
for (const m of scene.orderedIndicatorsForPane(pane.id)) {
|
|
33178
|
+
const set = modelDrawingSet(m, false);
|
|
33179
|
+
const tables = (m.tables ?? []).filter((t) => !t.overlay);
|
|
33180
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
33181
|
+
const sc = scene.scaleFor(m, pane);
|
|
33182
|
+
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
33183
|
+
const beforeZ = indicatorSliceKey(scene.zOf(m.id), boundaries);
|
|
33184
|
+
add(pane.id, beforeZ, { set, tables, pane: mp, indexOffset: scene.offsetOf(m.id) });
|
|
33185
|
+
}
|
|
33186
|
+
if (pane.kind === "price") {
|
|
33187
|
+
for (const m of scene.indicators.values()) {
|
|
33188
|
+
const set = modelDrawingSet(m, true);
|
|
33189
|
+
const tables = (m.tables ?? []).filter((t) => t.overlay === true);
|
|
33190
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
33191
|
+
add(pane.id, Infinity, { set, tables, pane, indexOffset: scene.offsetOf(m.id) });
|
|
33192
|
+
}
|
|
33193
|
+
}
|
|
33194
|
+
}
|
|
33195
|
+
for (const [key, { paneId, beforeZ, entries }] of buckets) {
|
|
33196
|
+
let canvas = this.sliceCache.get(key);
|
|
33197
|
+
if (!canvas) {
|
|
33198
|
+
canvas = document.createElement("canvas");
|
|
33199
|
+
this.sliceCache.set(key, canvas);
|
|
33200
|
+
}
|
|
33201
|
+
if (canvas.width !== ref2.width || canvas.height !== ref2.height) {
|
|
33202
|
+
canvas.width = ref2.width;
|
|
33203
|
+
canvas.height = ref2.height;
|
|
33204
|
+
}
|
|
33205
|
+
const ctx = canvas.getContext("2d");
|
|
33206
|
+
if (!ctx) continue;
|
|
33207
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
33208
|
+
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
33209
|
+
for (const e of entries) this.paintEntry(ctx, e, coords, dataW, theme);
|
|
33210
|
+
const slices = out.get(paneId) ?? [];
|
|
33211
|
+
slices.push({ beforeZ, canvas });
|
|
33212
|
+
out.set(paneId, slices);
|
|
33213
|
+
}
|
|
33214
|
+
for (const key of [...this.sliceCache.keys()]) if (!buckets.has(key)) this.sliceCache.delete(key);
|
|
33215
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
33216
|
+
return out;
|
|
33217
|
+
}
|
|
33218
|
+
paintEntry(ctx, e, coords, dataW, theme) {
|
|
33219
|
+
const { pane } = e;
|
|
33220
|
+
const paneTips = [];
|
|
33221
|
+
ctx.save();
|
|
33222
|
+
ctx.translate(0, pane.bounds.top);
|
|
33223
|
+
ctx.beginPath();
|
|
33224
|
+
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
33225
|
+
ctx.clip();
|
|
33226
|
+
this.drawScene.setSet(e.set, e.indexOffset);
|
|
33227
|
+
this.drawScene.render(
|
|
33228
|
+
ctx,
|
|
33229
|
+
dataW,
|
|
33230
|
+
pane.bounds.height,
|
|
33231
|
+
(l) => coords.logicalToX(l),
|
|
33232
|
+
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
33233
|
+
);
|
|
33234
|
+
paneTips.push(...this.drawScene.labelTipRegions());
|
|
33235
|
+
for (const t of e.tables) paintTable(ctx, t, { paneHeight: pane.bounds.height, plotWidth: dataW, theme }, paneTips);
|
|
33236
|
+
ctx.restore();
|
|
33237
|
+
for (const r of paneTips) {
|
|
33238
|
+
this.tips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
33239
|
+
}
|
|
33240
|
+
}
|
|
33241
|
+
/** Tooltip of the topmost label or table cell under a plot-space point, or null. Fed by the last prepare. */
|
|
33242
|
+
labelTooltipAt(x, y) {
|
|
33243
|
+
for (let i = this.tips.length - 1; i >= 0; i -= 1) {
|
|
33244
|
+
const r = this.tips[i];
|
|
33245
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
33246
|
+
}
|
|
33247
|
+
return null;
|
|
33248
|
+
}
|
|
33249
|
+
};
|
|
33250
|
+
function mergeSlices(indicator, user) {
|
|
33251
|
+
const out = /* @__PURE__ */ new Map();
|
|
33252
|
+
for (const [paneId, slices] of indicator) out.set(paneId, [...slices]);
|
|
33253
|
+
for (const [paneId, slices] of user) out.set(paneId, [...out.get(paneId) ?? [], ...slices]);
|
|
33254
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
33255
|
+
return out;
|
|
33256
|
+
}
|
|
33257
|
+
|
|
32984
33258
|
// src/renderers/native/drawings/Projector.ts
|
|
32985
33259
|
function createProjector(coords, paneOf, paneIdAtY, barsInRange) {
|
|
32986
33260
|
return {
|
|
@@ -33860,6 +34134,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33860
34134
|
this.vpvrRenderer = new VpvrRenderer();
|
|
33861
34135
|
this.resizeObserver = null;
|
|
33862
34136
|
this.dprMedia = null;
|
|
34137
|
+
/** Plot size in INTEGER device px, as last reported by the resize observer's
|
|
34138
|
+
* device-pixel-content-box — the browser's own statement of how many device pixels
|
|
34139
|
+
* it paints the plot into. `null` until the first report or where the box type is
|
|
34140
|
+
* unsupported (WebKit); syncSize then falls back to rounding the client rect. */
|
|
34141
|
+
this.plotDeviceSize = null;
|
|
33863
34142
|
this.coords = new CoordinateSystem();
|
|
33864
34143
|
this.scene = new SceneGraph();
|
|
33865
34144
|
// chosen at mount (WebGL2 if available, else canvas2d)
|
|
@@ -33867,6 +34146,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33867
34146
|
this.glowAmount = 0;
|
|
33868
34147
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
33869
34148
|
this.chrome = new ChromeRenderer();
|
|
34149
|
+
/** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
|
|
34150
|
+
this.indicatorSlices = new IndicatorDrawingSlices();
|
|
33870
34151
|
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
33871
34152
|
this.labelTooltip = null;
|
|
33872
34153
|
this.crosshairLayer = new CrosshairRenderer();
|
|
@@ -34015,7 +34296,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34015
34296
|
this.toggleVisibleCbs = /* @__PURE__ */ new Set();
|
|
34016
34297
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
34017
34298
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
34018
|
-
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
34019
34299
|
this.name = "native";
|
|
34020
34300
|
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
34021
34301
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
@@ -34329,7 +34609,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34329
34609
|
* hidden) on clear so a re-show picks up the current theme.
|
|
34330
34610
|
*/
|
|
34331
34611
|
setLoading(loading) {
|
|
34332
|
-
for (const overlay of this.tableOverlays.values()) overlay.setVisible(!loading);
|
|
34333
34612
|
if (!loading || !this.wrapper) {
|
|
34334
34613
|
this.loadingEl?.remove();
|
|
34335
34614
|
this.loadingEl = null;
|
|
@@ -34991,7 +35270,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34991
35270
|
this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
|
|
34992
35271
|
this.labelTooltip = new LabelTooltip(this.plot, {
|
|
34993
35272
|
theme: () => this.chromeTheme(),
|
|
34994
|
-
lookup: (x, y) => this.
|
|
35273
|
+
lookup: (x, y) => this.indicatorSlices.labelTooltipAt(x, y)
|
|
34995
35274
|
});
|
|
34996
35275
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
34997
35276
|
projector: () => this.drawingProjector(),
|
|
@@ -35076,6 +35355,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35076
35355
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
35077
35356
|
}
|
|
35078
35357
|
});
|
|
35358
|
+
this.paneControls.setSuspended(this.layoutMode === "mobile");
|
|
35079
35359
|
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
35080
35360
|
panes: () => this.axisScaleViews(),
|
|
35081
35361
|
rightAxis: () => this.rightAxisW,
|
|
@@ -35085,8 +35365,19 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35085
35365
|
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
35086
35366
|
}
|
|
35087
35367
|
});
|
|
35088
|
-
this.resizeObserver = new ResizeObserver(() =>
|
|
35368
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
35369
|
+
for (const e of entries) {
|
|
35370
|
+
if (e.target !== this.plot) continue;
|
|
35371
|
+
const s = e.devicePixelContentBoxSize?.[0];
|
|
35372
|
+
if (s) this.plotDeviceSize = { width: s.inlineSize, height: s.blockSize };
|
|
35373
|
+
}
|
|
35374
|
+
this.resize();
|
|
35375
|
+
});
|
|
35089
35376
|
this.resizeObserver.observe(this.wrapper);
|
|
35377
|
+
try {
|
|
35378
|
+
this.resizeObserver.observe(this.plot, { box: "device-pixel-content-box" });
|
|
35379
|
+
} catch {
|
|
35380
|
+
}
|
|
35090
35381
|
this.watchDpr();
|
|
35091
35382
|
this.syncSize();
|
|
35092
35383
|
}
|
|
@@ -35211,8 +35502,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35211
35502
|
this.inputsUI?.destroy();
|
|
35212
35503
|
this.paneControls?.destroy();
|
|
35213
35504
|
this.axisScaleButtons?.destroy();
|
|
35214
|
-
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
35215
|
-
this.tableOverlays.clear();
|
|
35216
35505
|
this.resizeObserver?.disconnect();
|
|
35217
35506
|
this.resizeObserver = null;
|
|
35218
35507
|
this.dprMedia?.removeEventListener("change", this.onDprChange);
|
|
@@ -35241,6 +35530,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35241
35530
|
this.attributionEl = null;
|
|
35242
35531
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
35243
35532
|
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
35533
|
+
this.mountContainer?.style.removeProperty("--vela-bottom-gutter");
|
|
35244
35534
|
this.mountContainer?.style.removeProperty("--vela-price-pane-top");
|
|
35245
35535
|
this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
|
|
35246
35536
|
this.mountContainer = null;
|
|
@@ -35333,7 +35623,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35333
35623
|
ensurePane(pane) {
|
|
35334
35624
|
this.scene.ensurePane(pane.id, pane.kind, pane.order, pane.heightWeight ?? (pane.kind === "price" ? 3 : 1));
|
|
35335
35625
|
this.layoutPanes();
|
|
35336
|
-
this.repositionTables();
|
|
35337
35626
|
this.paneControls?.refresh();
|
|
35338
35627
|
this.scheduler.invalidate(4 /* Full */);
|
|
35339
35628
|
}
|
|
@@ -35355,17 +35644,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35355
35644
|
if (!model.ownScale) this.scene.dropIndicatorScale(handle.id);
|
|
35356
35645
|
this.inputsUI.setPane(handle.id, paneId);
|
|
35357
35646
|
this.refreshAnchorOffset(model);
|
|
35358
|
-
this.syncTables(model);
|
|
35359
35647
|
this.refreshAxisWidth();
|
|
35360
35648
|
this.layoutPanes();
|
|
35361
|
-
this.repositionTables();
|
|
35362
35649
|
this.paneControls?.refresh();
|
|
35363
35650
|
this.scheduler.invalidate(4 /* Full */);
|
|
35364
35651
|
}
|
|
35365
35652
|
orderPanes(orderedIds) {
|
|
35366
35653
|
this.scene.orderPanes(orderedIds);
|
|
35367
35654
|
this.layoutPanes();
|
|
35368
|
-
this.repositionTables();
|
|
35369
35655
|
this.paneControls?.refresh();
|
|
35370
35656
|
this.scheduler.invalidate(4 /* Full */);
|
|
35371
35657
|
}
|
|
@@ -35374,7 +35660,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35374
35660
|
if (!pane || pane.collapsed === collapsed) return;
|
|
35375
35661
|
pane.collapsed = collapsed;
|
|
35376
35662
|
this.layoutPanes();
|
|
35377
|
-
this.repositionTables();
|
|
35378
35663
|
this.paneControls?.refresh();
|
|
35379
35664
|
this.scheduler.invalidate(4 /* Full */);
|
|
35380
35665
|
}
|
|
@@ -35382,7 +35667,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35382
35667
|
if (paneId !== null && !this.scene.panes.has(paneId)) paneId = null;
|
|
35383
35668
|
this.maximizedPaneId = paneId;
|
|
35384
35669
|
this.layoutPanes();
|
|
35385
|
-
this.repositionTables();
|
|
35386
35670
|
this.paneControls?.refresh();
|
|
35387
35671
|
this.scheduler.invalidate(4 /* Full */);
|
|
35388
35672
|
}
|
|
@@ -35469,7 +35753,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35469
35753
|
native: !!model.native,
|
|
35470
35754
|
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
35471
35755
|
});
|
|
35472
|
-
this.syncTables(model);
|
|
35473
35756
|
if (model.native?.type === "volume") {
|
|
35474
35757
|
this.volumeActive = true;
|
|
35475
35758
|
this.volumeHidden = false;
|
|
@@ -35493,7 +35776,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35493
35776
|
}
|
|
35494
35777
|
}
|
|
35495
35778
|
applyPatch(model, patch);
|
|
35496
|
-
this.syncTables(model);
|
|
35497
35779
|
this.scheduler.invalidate(3 /* Light */);
|
|
35498
35780
|
}
|
|
35499
35781
|
removeIndicator(handle) {
|
|
@@ -35512,8 +35794,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35512
35794
|
this.scene.forgetAnchorOffset(handle.id);
|
|
35513
35795
|
this.scene.dropIndicatorScale(handle.id);
|
|
35514
35796
|
this.inputsUI.remove(handle.id);
|
|
35515
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
35516
|
-
this.tableOverlays.delete(handle.id);
|
|
35517
35797
|
this.refreshAxisWidth();
|
|
35518
35798
|
this.paneControls?.refresh();
|
|
35519
35799
|
this.scheduler.invalidate(4 /* Full */);
|
|
@@ -35557,8 +35837,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35557
35837
|
}
|
|
35558
35838
|
if (!visible) {
|
|
35559
35839
|
this.scene.indicators.delete(handle.id);
|
|
35560
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
35561
|
-
this.tableOverlays.delete(handle.id);
|
|
35562
35840
|
}
|
|
35563
35841
|
this.inputsUI.setVisible(handle.id, visible);
|
|
35564
35842
|
this.scheduler.invalidate(4 /* Full */);
|
|
@@ -35604,6 +35882,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35604
35882
|
this.userDrawings?.setLayoutMode(mode);
|
|
35605
35883
|
this.settingsDialog?.setLayoutMode(mode);
|
|
35606
35884
|
this.inputsUI?.setLayoutMode(mode);
|
|
35885
|
+
this.paneControls?.setSuspended(mode === "mobile");
|
|
35607
35886
|
if (this.scrollButton) {
|
|
35608
35887
|
const px = mode === "mobile" ? SCROLL_BTN_SIZE_TOUCH : SCROLL_BTN_SIZE;
|
|
35609
35888
|
this.scrollButton.style.width = `${px}px`;
|
|
@@ -35999,7 +36278,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35999
36278
|
/** Relayout + repaint + refresh the hover buttons after a collapse/maximize/order change. */
|
|
36000
36279
|
afterPaneLayoutChange() {
|
|
36001
36280
|
this.layoutPanes();
|
|
36002
|
-
this.repositionTables();
|
|
36003
36281
|
this.paneControls?.refresh();
|
|
36004
36282
|
this.scheduler.invalidate(4 /* Full */);
|
|
36005
36283
|
}
|
|
@@ -36080,7 +36358,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36080
36358
|
above.heightWeight = next2.above;
|
|
36081
36359
|
below.heightWeight = next2.below;
|
|
36082
36360
|
this.layoutPanes();
|
|
36083
|
-
this.repositionTables();
|
|
36084
36361
|
this.scheduler.invalidate(4 /* Full */);
|
|
36085
36362
|
}
|
|
36086
36363
|
/** Double-click a separator → split the two adjacent panes evenly (each gets half of
|
|
@@ -36095,7 +36372,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36095
36372
|
above.heightWeight = half;
|
|
36096
36373
|
below.heightWeight = half;
|
|
36097
36374
|
this.layoutPanes();
|
|
36098
|
-
this.repositionTables();
|
|
36099
36375
|
this.scheduler.invalidate(4 /* Full */);
|
|
36100
36376
|
}
|
|
36101
36377
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -36374,7 +36650,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36374
36650
|
const liveActual = li >= 0 ? this.bars[li] : void 0;
|
|
36375
36651
|
const easeLive = !!liveActual && this.liveEaseTime === liveActual.time && (liveActual.high !== this.liveEaseHigh || liveActual.low !== this.liveEaseLow || liveActual.close !== this.liveEaseClose);
|
|
36376
36652
|
if (easeLive && liveActual) this.bars[li] = { ...liveActual, high: this.liveEaseHigh, low: this.liveEaseLow, close: this.liveEaseClose };
|
|
36377
|
-
this.scene.drawingSlices =
|
|
36653
|
+
this.scene.drawingSlices = mergeSlices(
|
|
36654
|
+
this.indicatorSlices.prepare(this.scene, this.coords, this.theme, this.dataCanvas),
|
|
36655
|
+
this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map()
|
|
36656
|
+
);
|
|
36378
36657
|
this.backdropRenderer.render(this.scene, this.coords, this.theme, gridAlpha);
|
|
36379
36658
|
this.backend.render(this.scene, this.coords, this.theme);
|
|
36380
36659
|
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
@@ -36812,27 +37091,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36812
37091
|
}
|
|
36813
37092
|
return maxVol;
|
|
36814
37093
|
}
|
|
36815
|
-
/** Create/update/destroy an indicator's DOM table overlay (anchored off real pane geometry). */
|
|
36816
|
-
syncTables(model) {
|
|
36817
|
-
const tables = model.tables ?? [];
|
|
36818
|
-
let overlay = this.tableOverlays.get(model.id);
|
|
36819
|
-
if (tables.length === 0) {
|
|
36820
|
-
if (overlay) {
|
|
36821
|
-
overlay.destroy();
|
|
36822
|
-
this.tableOverlays.delete(model.id);
|
|
36823
|
-
}
|
|
36824
|
-
return;
|
|
36825
|
-
}
|
|
36826
|
-
if (!overlay) {
|
|
36827
|
-
overlay = new TableOverlay(this.plot, this.theme, (id) => this.paneBoundsFor(id));
|
|
36828
|
-
overlay.setVisible(this.loadingEl === null);
|
|
36829
|
-
this.tableOverlays.set(model.id, overlay);
|
|
36830
|
-
}
|
|
36831
|
-
overlay.update(tables);
|
|
36832
|
-
}
|
|
36833
|
-
repositionTables() {
|
|
36834
|
-
for (const overlay of this.tableOverlays.values()) overlay.reposition();
|
|
36835
|
-
}
|
|
36836
37094
|
layoutPanes() {
|
|
36837
37095
|
const panes = this.scene.orderedPanes();
|
|
36838
37096
|
const dataHeight = this.coords.height;
|
|
@@ -36883,6 +37141,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36883
37141
|
const visible = maxPane ? [maxPane] : this.scene.orderedPanes().filter((p) => !p.collapsed);
|
|
36884
37142
|
const paneBottom = visible.length ? Math.max(...visible.map((p) => p.bounds.top + p.bounds.height)) : dataHeight;
|
|
36885
37143
|
this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM + Math.max(0, dataHeight - paneBottom);
|
|
37144
|
+
this.mountContainer?.style.setProperty("--vela-bottom-gutter", `${TIME_AXIS_H + Math.max(0, dataHeight - paneBottom)}px`);
|
|
36886
37145
|
this.scrollBtnRightPx = this.rightAxisW + SCROLL_BTN_RIGHT_INSET;
|
|
36887
37146
|
if (this.scrollButton) {
|
|
36888
37147
|
this.scrollButton.style.bottom = `${this.scrollBtnBottomPx}px`;
|
|
@@ -36975,30 +37234,33 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36975
37234
|
if (w <= 0 || h <= 0) return;
|
|
36976
37235
|
const dpr = window.devicePixelRatio || 1;
|
|
36977
37236
|
this.plot.style.left = `${this.toolbarGutter}px`;
|
|
36978
|
-
const
|
|
36979
|
-
|
|
36980
|
-
|
|
36981
|
-
|
|
36982
|
-
|
|
36983
|
-
|
|
36984
|
-
|
|
36985
|
-
|
|
36986
|
-
|
|
36987
|
-
|
|
36988
|
-
|
|
36989
|
-
|
|
36990
|
-
|
|
36991
|
-
|
|
36992
|
-
|
|
36993
|
-
|
|
36994
|
-
this.
|
|
36995
|
-
this.
|
|
36996
|
-
this.
|
|
36997
|
-
|
|
37237
|
+
const rect = this.plot.getBoundingClientRect();
|
|
37238
|
+
let bw = Math.max(1, Math.round(rect.width * dpr));
|
|
37239
|
+
let bh = Math.max(1, Math.round(rect.height * dpr));
|
|
37240
|
+
const dev = this.plotDeviceSize;
|
|
37241
|
+
if (dev && Math.abs(dev.width - rect.width * dpr) <= 1 && Math.abs(dev.height - rect.height * dpr) <= 1) {
|
|
37242
|
+
bw = Math.max(1, dev.width);
|
|
37243
|
+
bh = Math.max(1, dev.height);
|
|
37244
|
+
}
|
|
37245
|
+
const pw = bw / dpr;
|
|
37246
|
+
const ph = bh / dpr;
|
|
37247
|
+
const size3 = (canvas) => {
|
|
37248
|
+
canvas.width = bw;
|
|
37249
|
+
canvas.height = bh;
|
|
37250
|
+
canvas.style.width = `${pw}px`;
|
|
37251
|
+
canvas.style.height = `${ph}px`;
|
|
37252
|
+
};
|
|
37253
|
+
size3(this.dataCanvas);
|
|
37254
|
+
size3(this.backdropCanvas);
|
|
37255
|
+
size3(this.volumeCanvas);
|
|
37256
|
+
for (const l of this.extLayers) size3(l.canvas);
|
|
37257
|
+
size3(this.vpvrCanvas);
|
|
37258
|
+
size3(this.chromeCanvas);
|
|
37259
|
+
size3(this.drawingsCanvas);
|
|
37260
|
+
size3(this.cursorCanvas);
|
|
36998
37261
|
this.coords.setSize(Math.max(1, pw - this.rightAxisW), Math.max(1, ph - TIME_AXIS_H), dpr);
|
|
36999
37262
|
this.scene.crosshair = null;
|
|
37000
37263
|
this.layoutPanes();
|
|
37001
|
-
this.repositionTables();
|
|
37002
37264
|
this.userDrawings?.onResize();
|
|
37003
37265
|
if (!this.didInitialFit && this.coords.barCount > 0) {
|
|
37004
37266
|
this.fitContent();
|