@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/index.cjs
CHANGED
|
@@ -552,6 +552,8 @@ registerIcon("folder-plus", S('<path d="M1.6 4.4a1 1 0 0 1 1-1h2.7l1.3 1.7h6.8a1
|
|
|
552
552
|
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"/>'));
|
|
553
553
|
registerIcon("collapse", S('<path d="M3 8h10"/>'));
|
|
554
554
|
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"/>'));
|
|
555
|
+
registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
|
|
556
|
+
registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
|
|
555
557
|
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"/>'));
|
|
556
558
|
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"/>'));
|
|
557
559
|
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"/>'));
|
|
@@ -559,7 +561,7 @@ registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L
|
|
|
559
561
|
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"'));
|
|
560
562
|
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"'));
|
|
561
563
|
registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
|
|
562
|
-
registerIcon("reset", S('<
|
|
564
|
+
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"/>'));
|
|
563
565
|
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"/>'));
|
|
564
566
|
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"/>'));
|
|
565
567
|
registerIcon("cursor", svg24('<path d="M5 3l6 16 2-6 6-2z"/>', 'fill="currentColor" stroke="none"'));
|
|
@@ -6930,6 +6932,23 @@ function timeframeToMs(timeframe) {
|
|
|
6930
6932
|
return 36e5;
|
|
6931
6933
|
}
|
|
6932
6934
|
|
|
6935
|
+
// src/data/symbol-groups.ts
|
|
6936
|
+
function isGroupRow(d) {
|
|
6937
|
+
return d.group != null && d.ticker === d.group;
|
|
6938
|
+
}
|
|
6939
|
+
function groupKeyOf(d) {
|
|
6940
|
+
return `${(d.prefix ?? d.provider ?? "").toLowerCase()}:${(d.group ?? "").toUpperCase()}`;
|
|
6941
|
+
}
|
|
6942
|
+
function groupMembers(pool, groupRow) {
|
|
6943
|
+
const key = groupKeyOf(groupRow);
|
|
6944
|
+
return pool.filter((s) => s.group != null && !isGroupRow(s) && groupKeyOf(s) === key);
|
|
6945
|
+
}
|
|
6946
|
+
function defaultMemberOf(pool, groupRow) {
|
|
6947
|
+
const members = groupMembers(pool, groupRow);
|
|
6948
|
+
const defaults2 = members.filter((m) => m.default);
|
|
6949
|
+
return (defaults2.length === 1 ? defaults2[0] : members[0]) ?? null;
|
|
6950
|
+
}
|
|
6951
|
+
|
|
6933
6952
|
// src/data/ProviderRegistry.ts
|
|
6934
6953
|
var normName = (n) => n.trim().toLowerCase();
|
|
6935
6954
|
var normTicker = (t) => t.trim().toUpperCase();
|
|
@@ -7019,17 +7038,24 @@ var ProviderRegistry = class {
|
|
|
7019
7038
|
resolve(raw, opts = {}) {
|
|
7020
7039
|
const { provider, ticker } = this.parse(raw);
|
|
7021
7040
|
if (provider) {
|
|
7022
|
-
if (this.entries.has(provider))
|
|
7041
|
+
if (this.entries.has(provider)) {
|
|
7042
|
+
const d = this.entries.get(provider).byTicker?.get(normTicker(ticker));
|
|
7043
|
+
return { provider, ticker: d != null && isGroupRow(d) ? this.loadableTicker(provider, d) : ticker };
|
|
7044
|
+
}
|
|
7023
7045
|
const key = `${provider}:${normTicker(ticker)}`;
|
|
7024
7046
|
for (const name of this.candidateOrder(opts.default)) {
|
|
7025
7047
|
const d = this.entries.get(name).prefixIndex?.get(key);
|
|
7026
|
-
if (d) return { provider: name, ticker:
|
|
7048
|
+
if (d) return { provider: name, ticker: this.loadableTicker(name, d) };
|
|
7027
7049
|
}
|
|
7028
7050
|
return null;
|
|
7029
7051
|
}
|
|
7030
7052
|
const norm = normTicker(ticker);
|
|
7031
7053
|
for (const name of this.candidateOrder(opts.default)) {
|
|
7032
|
-
|
|
7054
|
+
const e = this.entries.get(name);
|
|
7055
|
+
if (e.index?.has(norm)) {
|
|
7056
|
+
const d = e.byTicker?.get(norm);
|
|
7057
|
+
return { provider: name, ticker: d != null && isGroupRow(d) ? this.loadableTicker(name, d) : ticker };
|
|
7058
|
+
}
|
|
7033
7059
|
}
|
|
7034
7060
|
if (opts.lenient && this.entries.size === 1) {
|
|
7035
7061
|
const only = this.names()[0];
|
|
@@ -7038,6 +7064,13 @@ var ProviderRegistry = class {
|
|
|
7038
7064
|
}
|
|
7039
7065
|
return null;
|
|
7040
7066
|
}
|
|
7067
|
+
/** What a resolved descriptor LOADS: itself — unless it is a GROUP row (listed, never
|
|
7068
|
+
* served), which loads its default member (single `default`, else first listed). A
|
|
7069
|
+
* memberless group keeps its own ticker and fails downstream like any unknown symbol. */
|
|
7070
|
+
loadableTicker(name, d) {
|
|
7071
|
+
if (!isGroupRow(d)) return d.ticker;
|
|
7072
|
+
return defaultMemberOf(this.entries.get(name)?.descriptors ?? [], d)?.ticker ?? d.ticker;
|
|
7073
|
+
}
|
|
7041
7074
|
/**
|
|
7042
7075
|
* Resolve now if possible, else resolve later — re-attempting after each provider
|
|
7043
7076
|
* settles. The promise stays pending until some registered provider can serve the
|
|
@@ -7326,6 +7359,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
7326
7359
|
/** Invalidates detached async work (backfill loops, in-flight loads, gap heals):
|
|
7327
7360
|
* bumped by init(), setMarket() and destroy(). */
|
|
7328
7361
|
this.generation = 0;
|
|
7362
|
+
/** Aborts the in-flight PROGRESSIVE load's source polling on supersession — an
|
|
7363
|
+
* abandoned stream left polling to its own budget starves the browser's per-host
|
|
7364
|
+
* connection pool, and the NEXT symbol's very first fetch with it (measured). */
|
|
7365
|
+
this.progressiveAbort = null;
|
|
7329
7366
|
/** Awaiters racing a superseded load (setMarket callers) — released on every bump so they never hang. */
|
|
7330
7367
|
this.supersedeWaiters = [];
|
|
7331
7368
|
/** `history:complete` fired for the CURRENT load. Each market load re-arms the cycle
|
|
@@ -7468,6 +7505,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
7468
7505
|
* superseded setMarket awaiters so their promises resolve instead of hanging. */
|
|
7469
7506
|
bumpGeneration() {
|
|
7470
7507
|
const gen = ++this.generation;
|
|
7508
|
+
this.progressiveAbort?.abort();
|
|
7509
|
+
this.progressiveAbort = null;
|
|
7471
7510
|
for (const w of this.supersedeWaiters.splice(0)) w();
|
|
7472
7511
|
return gen;
|
|
7473
7512
|
}
|
|
@@ -7521,7 +7560,47 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
7521
7560
|
const requested = market.bars ?? 500;
|
|
7522
7561
|
const initialRange = market.visibleRange;
|
|
7523
7562
|
const deep = !market.data?.length && initialRange == null && requested > SINGLE_LOAD_BARS;
|
|
7524
|
-
|
|
7563
|
+
let progressiveServed = false;
|
|
7564
|
+
if (!market.data?.length && initialRange == null && this.feed.loadProgressive) {
|
|
7565
|
+
let painted = false;
|
|
7566
|
+
const paint = (bars, final) => {
|
|
7567
|
+
if (this.generation !== gen || !final && bars.length === 0) return;
|
|
7568
|
+
this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
|
|
7569
|
+
if (!painted && bars.length > 0) {
|
|
7570
|
+
painted = true;
|
|
7571
|
+
if (opts.firstLoad) this.activateBarLayers();
|
|
7572
|
+
if (!final) this.historyState = "backfill";
|
|
7573
|
+
}
|
|
7574
|
+
};
|
|
7575
|
+
const abort = new AbortController();
|
|
7576
|
+
this.progressiveAbort = abort;
|
|
7577
|
+
progressiveServed = await new Promise((firstPaint) => {
|
|
7578
|
+
let signaled = false;
|
|
7579
|
+
const signal = (served) => {
|
|
7580
|
+
if (!signaled) {
|
|
7581
|
+
signaled = true;
|
|
7582
|
+
firstPaint(served);
|
|
7583
|
+
}
|
|
7584
|
+
};
|
|
7585
|
+
abort.signal.addEventListener("abort", () => signal(true), { once: true });
|
|
7586
|
+
this.feed.loadProgressive(market, (bars) => {
|
|
7587
|
+
paint(bars, false);
|
|
7588
|
+
if (painted) signal(true);
|
|
7589
|
+
}, { signal: abort.signal }).then((full) => {
|
|
7590
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
7591
|
+
if (full == null) return signal(false);
|
|
7592
|
+
if (this.generation !== gen) return signal(true);
|
|
7593
|
+
paint(full, true);
|
|
7594
|
+
this.completeHistory(full.length >= requested ? "depth" : "genesis");
|
|
7595
|
+
signal(true);
|
|
7596
|
+
}).catch(() => {
|
|
7597
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
7598
|
+
if (this.generation === gen) this.completeHistory("aborted");
|
|
7599
|
+
signal(true);
|
|
7600
|
+
});
|
|
7601
|
+
});
|
|
7602
|
+
}
|
|
7603
|
+
if (progressiveServed) ; else if (deep && this.feed.loadRange) {
|
|
7525
7604
|
const head = await this.feed.load({ ...market, bars: Math.min(requested, CHUNK_BARS) });
|
|
7526
7605
|
if (this.generation !== gen) return;
|
|
7527
7606
|
this.setBarSeries(head);
|
|
@@ -9193,6 +9272,28 @@ var CachingDataFeed = class {
|
|
|
9193
9272
|
this.store.merge(key, dropForming(bars));
|
|
9194
9273
|
return bars;
|
|
9195
9274
|
}
|
|
9275
|
+
/**
|
|
9276
|
+
* Progressive twin of {@link load}: a COLD load streams through the inner feed's
|
|
9277
|
+
* progressive path — batches forwarded verbatim, the FINAL answer cached exactly as
|
|
9278
|
+
* `load` caches — while a cache-covered load answers once through `load` itself (a
|
|
9279
|
+
* warm chart has nothing to stream). Falls back to `load` wholesale when the inner
|
|
9280
|
+
* feed lacks the capability, so callers may prefer this method unconditionally.
|
|
9281
|
+
*/
|
|
9282
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9283
|
+
if (cfg.data && cfg.data.length > 0) return this.inner.load(cfg);
|
|
9284
|
+
if (!this.inner.loadProgressive) return null;
|
|
9285
|
+
const symbol = cfg.symbol ?? "TEST";
|
|
9286
|
+
const key = cacheKey(symbol, cfg.timeframe ?? "60", cfg.session);
|
|
9287
|
+
const cached = this.store.get(key);
|
|
9288
|
+
const n = cfg.bars ?? 500;
|
|
9289
|
+
const lastCached = cached?.[cached.length - 1];
|
|
9290
|
+
if (cached && lastCached && cached.length >= n - 1 && this.inner.loadRange) return this.load(cfg);
|
|
9291
|
+
this.store.retainSymbol(symbol);
|
|
9292
|
+
const bars = await this.inner.loadProgressive(cfg, onBatch, opts);
|
|
9293
|
+
if (bars == null) return null;
|
|
9294
|
+
this.store.merge(key, dropForming(bars));
|
|
9295
|
+
return bars;
|
|
9296
|
+
}
|
|
9196
9297
|
/**
|
|
9197
9298
|
* Cache-backed ranged fetch — the gateway engines use for secondary series
|
|
9198
9299
|
* (`request.security` HTF/LTF/cross-symbol) and the orchestrator's backward
|
|
@@ -9438,6 +9539,17 @@ var MultiProviderFeed = class {
|
|
|
9438
9539
|
this.prefetchSymbolInfo(resolved);
|
|
9439
9540
|
return this.cache.load(canonical(cfg, resolved));
|
|
9440
9541
|
}
|
|
9542
|
+
/** Progressive twin of {@link load} — same resolution, the cache streams the batches. */
|
|
9543
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9544
|
+
if (cfg.data && cfg.data.length > 0) {
|
|
9545
|
+
this.liveBars = cfg.data.map((b) => ({ ...b }));
|
|
9546
|
+
return cfg.data;
|
|
9547
|
+
}
|
|
9548
|
+
const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
|
|
9549
|
+
this.primaryProvider = resolved.provider;
|
|
9550
|
+
this.prefetchSymbolInfo(resolved);
|
|
9551
|
+
return this.cache.loadProgressive(canonical(cfg, resolved), onBatch, opts);
|
|
9552
|
+
}
|
|
9441
9553
|
/**
|
|
9442
9554
|
* Synchronous per-symbol metadata for engines (Pine `syminfo.*`), served from the cache
|
|
9443
9555
|
* warmed by load(). Undefined until the prefetch lands (the engine then synthesizes a
|
|
@@ -9525,6 +9637,18 @@ var RegistryFetchFeed = class {
|
|
|
9525
9637
|
if (!provider) return Promise.resolve([]);
|
|
9526
9638
|
return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session });
|
|
9527
9639
|
}
|
|
9640
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9641
|
+
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
9642
|
+
const provider = this.registry.get(name ?? "");
|
|
9643
|
+
if (!provider) return [];
|
|
9644
|
+
if (!provider.getBarsProgressive) return null;
|
|
9645
|
+
try {
|
|
9646
|
+
return await provider.getBarsProgressive(ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session }, onBatch, opts);
|
|
9647
|
+
} catch (e) {
|
|
9648
|
+
console.warn(`[vela] progressive fetch failed for ${ticker} ${cfg.timeframe ?? "60"} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
9649
|
+
return [];
|
|
9650
|
+
}
|
|
9651
|
+
}
|
|
9528
9652
|
loadRange(cfg, range) {
|
|
9529
9653
|
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
9530
9654
|
const provider = this.registry.get(name ?? "");
|
|
@@ -10787,6 +10911,10 @@ var CalloutBubble = class {
|
|
|
10787
10911
|
get open() {
|
|
10788
10912
|
return this.pop?.open ?? false;
|
|
10789
10913
|
}
|
|
10914
|
+
/** Close the deployed panel, if any. The bubble itself stays. */
|
|
10915
|
+
hidePanel() {
|
|
10916
|
+
this.pop?.hide();
|
|
10917
|
+
}
|
|
10790
10918
|
destroy() {
|
|
10791
10919
|
this.pop?.destroy();
|
|
10792
10920
|
this.pop = null;
|
|
@@ -13694,6 +13822,9 @@ var CLOSE_SVG = iconAt("close", LEGEND_ICON_PX2);
|
|
|
13694
13822
|
var FOLD_SVG = iconAt("chevron-up", LEGEND_ICON_PX2);
|
|
13695
13823
|
var UNFOLD_SVG = iconAt("chevron-down", LEGEND_ICON_PX2);
|
|
13696
13824
|
var OVERVIEW_SVG = iconAt("objects", LEGEND_ICON_PX2);
|
|
13825
|
+
function legendCalloutsDisplay(open2, hasCallouts) {
|
|
13826
|
+
return !open2 && hasCallouts ? "inline-flex" : "none";
|
|
13827
|
+
}
|
|
13697
13828
|
var InputsUI = class {
|
|
13698
13829
|
constructor(container, theme, paneBoundsOf) {
|
|
13699
13830
|
this.container = container;
|
|
@@ -13897,7 +14028,7 @@ var InputsUI = class {
|
|
|
13897
14028
|
row.callouts = [];
|
|
13898
14029
|
row.calloutsEl.replaceChildren();
|
|
13899
14030
|
const views = this.legendCallouts?.(row.id) ?? [];
|
|
13900
|
-
row.calloutsEl.style.display = views.length > 0
|
|
14031
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(row.highlighted, views.length > 0);
|
|
13901
14032
|
for (const view of views) {
|
|
13902
14033
|
const bubble = new CalloutBubble({
|
|
13903
14034
|
icon: view.icon,
|
|
@@ -14381,11 +14512,11 @@ var InputsUI = class {
|
|
|
14381
14512
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
14382
14513
|
if (open2) {
|
|
14383
14514
|
row.el.appendChild(row.statusEl);
|
|
14384
|
-
row.
|
|
14515
|
+
for (const bubble of row.callouts) bubble.hidePanel();
|
|
14385
14516
|
} else {
|
|
14386
14517
|
row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
14387
|
-
row.el.insertBefore(row.calloutsEl, row.statusEl);
|
|
14388
14518
|
}
|
|
14519
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(open2, row.callouts.length > 0);
|
|
14389
14520
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
14390
14521
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
14391
14522
|
if (child === row.extrasEl) {
|
|
@@ -14475,7 +14606,7 @@ var ICONS = {
|
|
|
14475
14606
|
};
|
|
14476
14607
|
var STYLE_ID = "vela-pane-controls";
|
|
14477
14608
|
var ICON_PX = 12;
|
|
14478
|
-
var CLUSTER_PILL = "rgba(0,0,0,0.
|
|
14609
|
+
var CLUSTER_PILL = "rgba(0,0,0,0.65)";
|
|
14479
14610
|
function ensureStyles() {
|
|
14480
14611
|
if (typeof document === "undefined" || document.getElementById(STYLE_ID)) return;
|
|
14481
14612
|
const st = document.createElement("style");
|
|
@@ -14495,8 +14626,12 @@ var PaneControls = class {
|
|
|
14495
14626
|
this.deps = deps;
|
|
14496
14627
|
this.clusters = /* @__PURE__ */ new Map();
|
|
14497
14628
|
this.hoverPaneId = null;
|
|
14629
|
+
/** Mobile: hover clusters are meaningless without a cursor — suppressed; a
|
|
14630
|
+
* collapsed pane's standalone expand chip stays (the only way back up). */
|
|
14631
|
+
this.suspended = false;
|
|
14498
14632
|
/** Reveal the cluster for the pane under the cursor, resolved from the pointer's y in the plot. */
|
|
14499
14633
|
this.onPlotMove = (e) => {
|
|
14634
|
+
if (this.suspended) return;
|
|
14500
14635
|
const rect = this.plot.getBoundingClientRect();
|
|
14501
14636
|
const y = e.clientY - rect.top;
|
|
14502
14637
|
let hit = null;
|
|
@@ -14568,7 +14703,12 @@ var PaneControls = class {
|
|
|
14568
14703
|
}
|
|
14569
14704
|
if (p.count > 1) {
|
|
14570
14705
|
cluster.appendChild(
|
|
14571
|
-
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
14706
|
+
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
14707
|
+
role: "maximize",
|
|
14708
|
+
// Same inverse-chip treatment as the collapsed pane's expand toggle: the
|
|
14709
|
+
// maximized state must read as an active state, not just a swapped glyph.
|
|
14710
|
+
selected: p.maximized
|
|
14711
|
+
})
|
|
14572
14712
|
);
|
|
14573
14713
|
}
|
|
14574
14714
|
}
|
|
@@ -14605,17 +14745,18 @@ var PaneControls = class {
|
|
|
14605
14745
|
}
|
|
14606
14746
|
const hovered = id === this.hoverPaneId;
|
|
14607
14747
|
const hasButtons = cluster.children.length > 0;
|
|
14608
|
-
const
|
|
14748
|
+
const stateChipRole = p.collapsed ? "collapse" : !this.suspended && p.maximized ? "maximize" : null;
|
|
14749
|
+
const visible = hasButtons && (hovered || stateChipRole != null) && p.height > 8;
|
|
14609
14750
|
cluster.style.right = `${rightPx}px`;
|
|
14610
14751
|
cluster.style.top = p.collapsed ? `${p.top + Math.max(1, Math.round((p.height - 24) / 2))}px` : `${p.top + 4}px`;
|
|
14611
14752
|
cluster.style.display = visible ? "flex" : "none";
|
|
14612
14753
|
if (!visible) continue;
|
|
14613
|
-
const
|
|
14614
|
-
cluster.style.background =
|
|
14754
|
+
const soloChip = stateChipRole != null && !hovered;
|
|
14755
|
+
cluster.style.background = soloChip ? "transparent" : CLUSTER_PILL;
|
|
14615
14756
|
for (const child of cluster.children) {
|
|
14616
14757
|
const btn2 = child;
|
|
14617
14758
|
btn2.style.display = "inline-flex";
|
|
14618
|
-
btn2.style.visibility =
|
|
14759
|
+
btn2.style.visibility = soloChip && btn2.dataset.role !== stateChipRole ? "hidden" : "visible";
|
|
14619
14760
|
}
|
|
14620
14761
|
}
|
|
14621
14762
|
}
|
|
@@ -14625,6 +14766,14 @@ var PaneControls = class {
|
|
|
14625
14766
|
this.hoverPaneId = paneId;
|
|
14626
14767
|
this.reposition();
|
|
14627
14768
|
}
|
|
14769
|
+
/** Mobile suppression: no hover clusters (touch has no cursor; the shell's own
|
|
14770
|
+
* chrome covers maximize), while collapsed panes keep their expand chips. */
|
|
14771
|
+
setSuspended(on) {
|
|
14772
|
+
if (on === this.suspended) return;
|
|
14773
|
+
this.suspended = on;
|
|
14774
|
+
if (on) this.hoverPaneId = null;
|
|
14775
|
+
this.reposition();
|
|
14776
|
+
}
|
|
14628
14777
|
destroy() {
|
|
14629
14778
|
this.plot.removeEventListener("pointermove", this.onPlotMove);
|
|
14630
14779
|
this.plot.removeEventListener("pointerleave", this.onPlotLeave);
|
|
@@ -14775,160 +14924,6 @@ var AxisScaleButtons = class {
|
|
|
14775
14924
|
}
|
|
14776
14925
|
};
|
|
14777
14926
|
|
|
14778
|
-
// src/renderers/shared/TableOverlay.ts
|
|
14779
|
-
var SIZE_PX3 = {
|
|
14780
|
-
auto: 13,
|
|
14781
|
-
tiny: 10,
|
|
14782
|
-
small: 11,
|
|
14783
|
-
normal: 13,
|
|
14784
|
-
large: 16,
|
|
14785
|
-
huge: 20
|
|
14786
|
-
};
|
|
14787
|
-
function fontPxOf(size) {
|
|
14788
|
-
if (typeof size === "number") return size > 0 ? size : SIZE_PX3.auto;
|
|
14789
|
-
return SIZE_PX3[size] ?? SIZE_PX3.auto;
|
|
14790
|
-
}
|
|
14791
|
-
function tableHasContent(t) {
|
|
14792
|
-
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
14793
|
-
}
|
|
14794
|
-
function mergeRenderPlan(t) {
|
|
14795
|
-
const span = /* @__PURE__ */ new Map();
|
|
14796
|
-
const omit = /* @__PURE__ */ new Set();
|
|
14797
|
-
for (const m of t.merges) {
|
|
14798
|
-
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
14799
|
-
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
14800
|
-
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
14801
|
-
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
14802
|
-
}
|
|
14803
|
-
}
|
|
14804
|
-
}
|
|
14805
|
-
for (let r = 0; r < t.rows; r += 1) {
|
|
14806
|
-
for (let c = 0; c < t.columns; c += 1) {
|
|
14807
|
-
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
14808
|
-
}
|
|
14809
|
-
}
|
|
14810
|
-
for (const key of span.keys()) omit.delete(key);
|
|
14811
|
-
return { span, omit };
|
|
14812
|
-
}
|
|
14813
|
-
var TableOverlay = class {
|
|
14814
|
-
constructor(container, theme, paneBounds) {
|
|
14815
|
-
this.container = container;
|
|
14816
|
-
this.theme = theme;
|
|
14817
|
-
this.paneBounds = paneBounds;
|
|
14818
|
-
this.lastTables = [];
|
|
14819
|
-
if (getComputedStyle(container).position === "static") container.style.position = "relative";
|
|
14820
|
-
this.root = document.createElement("div");
|
|
14821
|
-
Object.assign(this.root.style, {
|
|
14822
|
-
position: "absolute",
|
|
14823
|
-
inset: "0",
|
|
14824
|
-
pointerEvents: "none",
|
|
14825
|
-
overflow: "hidden",
|
|
14826
|
-
zIndex: "3"
|
|
14827
|
-
});
|
|
14828
|
-
container.appendChild(this.root);
|
|
14829
|
-
}
|
|
14830
|
-
update(tables) {
|
|
14831
|
-
this.lastTables = tables;
|
|
14832
|
-
this.root.replaceChildren();
|
|
14833
|
-
for (const t of tables) {
|
|
14834
|
-
if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
|
|
14835
|
-
}
|
|
14836
|
-
}
|
|
14837
|
-
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
14838
|
-
reposition() {
|
|
14839
|
-
if (this.root.isConnected) this.update(this.lastTables);
|
|
14840
|
-
}
|
|
14841
|
-
/** Show/hide the whole overlay. Tables anchor to pane corners, not to bars, so unlike the
|
|
14842
|
-
* series content they DON'T vanish with an emptied chart — the loading state hides them. */
|
|
14843
|
-
setVisible(visible) {
|
|
14844
|
-
this.root.style.display = visible ? "" : "none";
|
|
14845
|
-
}
|
|
14846
|
-
destroy() {
|
|
14847
|
-
this.root.remove();
|
|
14848
|
-
}
|
|
14849
|
-
renderTable(t) {
|
|
14850
|
-
const b = this.paneBounds(t.paneId);
|
|
14851
|
-
const wrap = document.createElement("div");
|
|
14852
|
-
wrap.style.position = "absolute";
|
|
14853
|
-
if (t.frameColor && t.frameWidth > 0) wrap.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
|
|
14854
|
-
this.anchor(wrap, t.position, b);
|
|
14855
|
-
const table = document.createElement("table");
|
|
14856
|
-
Object.assign(table.style, {
|
|
14857
|
-
borderCollapse: "collapse",
|
|
14858
|
-
background: t.bgColor ?? "transparent",
|
|
14859
|
-
fontFamily: this.theme.fontFamily || "sans-serif",
|
|
14860
|
-
border: "none",
|
|
14861
|
-
tableLayout: "auto",
|
|
14862
|
-
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
14863
|
-
pointerEvents: "auto"
|
|
14864
|
-
});
|
|
14865
|
-
const { span, omit } = mergeRenderPlan(t);
|
|
14866
|
-
const plotW = Math.max(0, (this.root.clientWidth || this.container.clientWidth) - b.rightAxis);
|
|
14867
|
-
const paneH = b.height;
|
|
14868
|
-
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
14869
|
-
for (let r = 0; r < t.rows; r += 1) {
|
|
14870
|
-
const tr = document.createElement("tr");
|
|
14871
|
-
for (let c = 0; c < t.columns; c += 1) {
|
|
14872
|
-
if (omit.has(`${r}:${c}`)) continue;
|
|
14873
|
-
const cell = t.cells[r]?.[c] ?? null;
|
|
14874
|
-
const td = document.createElement("td");
|
|
14875
|
-
if (cell === null) {
|
|
14876
|
-
Object.assign(td.style, { padding: "0", border: "none" });
|
|
14877
|
-
tr.appendChild(td);
|
|
14878
|
-
continue;
|
|
14879
|
-
}
|
|
14880
|
-
const sp = span.get(`${r}:${c}`);
|
|
14881
|
-
if (sp) {
|
|
14882
|
-
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
14883
|
-
if (sp.rs > 1) td.rowSpan = sp.rs;
|
|
14884
|
-
}
|
|
14885
|
-
Object.assign(td.style, {
|
|
14886
|
-
border: cellBorder,
|
|
14887
|
-
padding: "2px 6px",
|
|
14888
|
-
background: cell.bgColor ?? "transparent",
|
|
14889
|
-
color: cell.textColor ?? this.theme.textColor,
|
|
14890
|
-
textAlign: cell.hAlign,
|
|
14891
|
-
verticalAlign: cell.vAlign === "top" ? "top" : cell.vAlign === "bottom" ? "bottom" : "middle",
|
|
14892
|
-
fontSize: `${fontPxOf(cell.textSize)}px`,
|
|
14893
|
-
fontFamily: cell.fontFamily === "monospace" ? "monospace" : "inherit",
|
|
14894
|
-
fontWeight: cell.bold ? "bold" : "normal",
|
|
14895
|
-
fontStyle: cell.italic ? "italic" : "normal",
|
|
14896
|
-
// Pine cell text never wraps; `\n` still breaks lines. Wrapping
|
|
14897
|
-
// used to collapse unicode sparklines and ━━━ dividers.
|
|
14898
|
-
whiteSpace: "pre"
|
|
14899
|
-
});
|
|
14900
|
-
if (cell.width) td.style.width = `${cell.width / 100 * plotW}px`;
|
|
14901
|
-
if (cell.height) td.style.height = `${cell.height / 100 * paneH}px`;
|
|
14902
|
-
if (cell.tooltip) td.title = cell.tooltip;
|
|
14903
|
-
td.textContent = cell.text ?? "";
|
|
14904
|
-
tr.appendChild(td);
|
|
14905
|
-
}
|
|
14906
|
-
table.appendChild(tr);
|
|
14907
|
-
}
|
|
14908
|
-
wrap.appendChild(table);
|
|
14909
|
-
return wrap;
|
|
14910
|
-
}
|
|
14911
|
-
/**
|
|
14912
|
-
* Position the wrapper at a Pine `position.*` corner/edge of the table's PANE
|
|
14913
|
-
* (not the whole chart), inset past the right price axis so it never overlaps
|
|
14914
|
-
* the Y-axis labels.
|
|
14915
|
-
*/
|
|
14916
|
-
anchor(el, position, b) {
|
|
14917
|
-
const m = 6;
|
|
14918
|
-
const containerH = this.root.clientHeight || this.container.clientHeight;
|
|
14919
|
-
const paneBottom = b.top + b.height;
|
|
14920
|
-
if (position.startsWith("top")) el.style.top = `${b.top + m}px`;
|
|
14921
|
-
else if (position.startsWith("bottom")) el.style.bottom = `${Math.max(0, containerH - paneBottom) + m}px`;
|
|
14922
|
-
else el.style.top = `${b.top + b.height / 2}px`;
|
|
14923
|
-
if (position.endsWith("left")) el.style.left = `${m}px`;
|
|
14924
|
-
else if (position.endsWith("right")) el.style.right = `${b.rightAxis + m}px`;
|
|
14925
|
-
else el.style.left = `calc(50% - ${b.rightAxis / 2}px)`;
|
|
14926
|
-
const tx = position.endsWith("center") ? "-50%" : "0";
|
|
14927
|
-
const ty = position.startsWith("middle") ? "-50%" : "0";
|
|
14928
|
-
if (tx !== "0" || ty !== "0") el.style.transform = `translate(${tx}, ${ty})`;
|
|
14929
|
-
}
|
|
14930
|
-
};
|
|
14931
|
-
|
|
14932
14927
|
// src/renderers/native/capabilities.ts
|
|
14933
14928
|
var NATIVE_CAPABILITIES = {
|
|
14934
14929
|
panes: true,
|
|
@@ -14949,7 +14944,7 @@ var NATIVE_CAPABILITIES = {
|
|
|
14949
14944
|
drawingDepth: true,
|
|
14950
14945
|
// drawings share the series' z space (backend-composited interleave layers)
|
|
14951
14946
|
tables: true,
|
|
14952
|
-
//
|
|
14947
|
+
// canvas-painted into the owning indicator's interleave slice
|
|
14953
14948
|
trades: true,
|
|
14954
14949
|
// strategy order-fill markers (arrows + labels + fill-price ticks)
|
|
14955
14950
|
inputsUI: true
|
|
@@ -14980,6 +14975,9 @@ function candleTier(spacing) {
|
|
|
14980
14975
|
if (spacing < CANDLE_BODY_MIN_SPACING) return "wick";
|
|
14981
14976
|
return "full";
|
|
14982
14977
|
}
|
|
14978
|
+
function snapY(yCss, dpr) {
|
|
14979
|
+
return Math.round(yCss * dpr) / dpr;
|
|
14980
|
+
}
|
|
14983
14981
|
function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
14984
14982
|
const wickDev = Math.max(1, Math.round(wickWidth(spacing) * dpr));
|
|
14985
14983
|
const wickLeftDev = Math.round(xCss * dpr - wickDev / 2);
|
|
@@ -15786,11 +15784,9 @@ var WebGL2Backend = class {
|
|
|
15786
15784
|
};
|
|
15787
15785
|
b.alpha = this.modelAlpha;
|
|
15788
15786
|
for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
|
|
15789
|
-
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));
|
|
15790
15787
|
if (isPrice) {
|
|
15791
15788
|
for (const m of scene.indicators.values()) {
|
|
15792
15789
|
for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
|
|
15793
|
-
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
15794
15790
|
}
|
|
15795
15791
|
}
|
|
15796
15792
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
@@ -15806,6 +15802,7 @@ var WebGL2Backend = class {
|
|
|
15806
15802
|
b.alpha = this.modelAlpha;
|
|
15807
15803
|
const off = scene.offsetOf(m.id);
|
|
15808
15804
|
const mp = effPane(m);
|
|
15805
|
+
for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, mp, coords, i0, i1, off);
|
|
15809
15806
|
for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
15810
15807
|
}
|
|
15811
15808
|
if (drawCandles && !candleDrawn) {
|
|
@@ -15813,14 +15810,18 @@ var WebGL2Backend = class {
|
|
|
15813
15810
|
b.alpha = this.candleStructureAlpha;
|
|
15814
15811
|
this.emitPriceSeries(b, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
15815
15812
|
}
|
|
15816
|
-
drawSlicesUpTo(Infinity);
|
|
15817
15813
|
b.alpha = this.modelAlpha;
|
|
15818
15814
|
if (isPrice) {
|
|
15815
|
+
for (const m of scene.indicators.values()) {
|
|
15816
|
+
const off = scene.offsetOf(m.id);
|
|
15817
|
+
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, off);
|
|
15818
|
+
}
|
|
15819
15819
|
for (const m of scene.indicators.values()) {
|
|
15820
15820
|
const off = scene.offsetOf(m.id);
|
|
15821
15821
|
for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
|
|
15822
15822
|
}
|
|
15823
15823
|
}
|
|
15824
|
+
drawSlicesUpTo(Infinity);
|
|
15824
15825
|
for (const m of models) {
|
|
15825
15826
|
const mp = effPane(m);
|
|
15826
15827
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -16215,12 +16216,12 @@ var WebGL2Backend = class {
|
|
|
16215
16216
|
if (drawBody) {
|
|
16216
16217
|
const oY = coords.priceToY(bar.open, pane.scale, pane.bounds);
|
|
16217
16218
|
const cY = coords.priceToY(bar.close, pane.scale, pane.bounds);
|
|
16218
|
-
bodyTop = Math.min(oY, cY);
|
|
16219
|
-
bodyH = Math.max(1, Math.
|
|
16219
|
+
bodyTop = snapY(Math.min(oY, cY), coords.dpr);
|
|
16220
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - bodyTop);
|
|
16220
16221
|
}
|
|
16221
16222
|
if (cs.wickVisible) {
|
|
16222
|
-
const hY = coords.priceToY(bar.high, pane.scale, pane.bounds);
|
|
16223
|
-
const lY = coords.priceToY(bar.low, pane.scale, pane.bounds);
|
|
16223
|
+
const hY = snapY(coords.priceToY(bar.high, pane.scale, pane.bounds), coords.dpr);
|
|
16224
|
+
const lY = snapY(coords.priceToY(bar.low, pane.scale, pane.bounds), coords.dpr);
|
|
16224
16225
|
b.alpha = this.candleStructureAlpha;
|
|
16225
16226
|
const wCol = parseColor((isUp ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : bodyColorStr));
|
|
16226
16227
|
if (drawBody) {
|
|
@@ -17553,9 +17554,10 @@ var SceneGraph = class {
|
|
|
17553
17554
|
* so each indicator arrives behind the candles (and behind older indicators);
|
|
17554
17555
|
* `setIndicatorZ`/`bringToFront`/`sendToBack` change it. */
|
|
17555
17556
|
this.seriesZ = /* @__PURE__ */ new Map();
|
|
17556
|
-
/** Per-pane raster layers of
|
|
17557
|
+
/** Per-pane raster layers of drawings interleaved into the series stack (each
|
|
17558
|
+
* indicator's Pine drawings at its model's z, plus in-stack user drawings) — each a
|
|
17557
17559
|
* prepainted canvas the backend composites just before the series carrying `beforeZ`.
|
|
17558
|
-
* Rebuilt by the renderer per data frame
|
|
17560
|
+
* Rebuilt by the renderer per data frame. */
|
|
17559
17561
|
this.drawingSlices = /* @__PURE__ */ new Map();
|
|
17560
17562
|
/** Per-model index offset: the chart bar index of the model's `anchorTime` — its
|
|
17561
17563
|
* index-aligned payloads (dense series arrays, `bar_index` drawings) count from that
|
|
@@ -17790,11 +17792,9 @@ var Canvas2dBackend = class {
|
|
|
17790
17792
|
};
|
|
17791
17793
|
ctx.globalAlpha = this.modelAlpha;
|
|
17792
17794
|
for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
|
|
17793
|
-
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));
|
|
17794
17795
|
if (isPrice) {
|
|
17795
17796
|
for (const m of scene.indicators.values()) {
|
|
17796
17797
|
for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
|
|
17797
|
-
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
17798
17798
|
}
|
|
17799
17799
|
}
|
|
17800
17800
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
@@ -17818,6 +17818,7 @@ var Canvas2dBackend = class {
|
|
|
17818
17818
|
ctx.globalAlpha = this.modelAlpha;
|
|
17819
17819
|
const off = scene.offsetOf(m.id);
|
|
17820
17820
|
const mp = effPane(m);
|
|
17821
|
+
for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, mp, coords, i0, i1, off);
|
|
17821
17822
|
for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
17822
17823
|
}
|
|
17823
17824
|
if (drawCandles && !candleDrawn) {
|
|
@@ -17825,14 +17826,18 @@ var Canvas2dBackend = class {
|
|
|
17825
17826
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
17826
17827
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
17827
17828
|
}
|
|
17828
|
-
drawSlicesUpTo(Infinity);
|
|
17829
17829
|
if (isPrice) {
|
|
17830
17830
|
ctx.globalAlpha = this.modelAlpha;
|
|
17831
|
+
for (const m of scene.indicators.values()) {
|
|
17832
|
+
const off = scene.offsetOf(m.id);
|
|
17833
|
+
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, off);
|
|
17834
|
+
}
|
|
17831
17835
|
for (const m of scene.indicators.values()) {
|
|
17832
17836
|
const off = scene.offsetOf(m.id);
|
|
17833
17837
|
for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
|
|
17834
17838
|
}
|
|
17835
17839
|
}
|
|
17840
|
+
drawSlicesUpTo(Infinity);
|
|
17836
17841
|
ctx.globalAlpha = this.modelAlpha;
|
|
17837
17842
|
for (const m of models) {
|
|
17838
17843
|
const mp = effPane(m);
|
|
@@ -18076,13 +18081,13 @@ var Canvas2dBackend = class {
|
|
|
18076
18081
|
if (drawBody) {
|
|
18077
18082
|
const oY = coords.priceToY(b.open, pane.scale, pane.bounds);
|
|
18078
18083
|
const cY = coords.priceToY(b.close, pane.scale, pane.bounds);
|
|
18079
|
-
top = Math.min(oY, cY);
|
|
18080
|
-
bodyH = Math.max(1, Math.
|
|
18084
|
+
top = snapY(Math.min(oY, cY), coords.dpr);
|
|
18085
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - top);
|
|
18081
18086
|
}
|
|
18082
18087
|
if (cs.wickVisible) {
|
|
18083
18088
|
const wick = (up ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : color);
|
|
18084
|
-
const hY = coords.priceToY(b.high, pane.scale, pane.bounds);
|
|
18085
|
-
const lY = coords.priceToY(b.low, pane.scale, pane.bounds);
|
|
18089
|
+
const hY = snapY(coords.priceToY(b.high, pane.scale, pane.bounds), coords.dpr);
|
|
18090
|
+
const lY = snapY(coords.priceToY(b.low, pane.scale, pane.bounds), coords.dpr);
|
|
18086
18091
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
18087
18092
|
ctx.strokeStyle = wick;
|
|
18088
18093
|
ctx.lineWidth = g.wickW;
|
|
@@ -18529,6 +18534,19 @@ function autoFontSize(lines, boxW, boxH, bold) {
|
|
|
18529
18534
|
|
|
18530
18535
|
// src/renderers/shared/DrawingSceneRenderer.ts
|
|
18531
18536
|
var EMPTY_DRAWING_SET = { lines: [], boxes: [], labels: [], polylines: [], linefills: [] };
|
|
18537
|
+
function modelDrawingSet(m, overlay) {
|
|
18538
|
+
const want = (d) => Boolean(d.overlay) === overlay;
|
|
18539
|
+
return {
|
|
18540
|
+
lines: (m.lines ?? []).filter(want),
|
|
18541
|
+
boxes: (m.boxes ?? []).filter(want),
|
|
18542
|
+
labels: (m.labels ?? []).filter(want),
|
|
18543
|
+
polylines: (m.polylines ?? []).filter(want),
|
|
18544
|
+
linefills: (m.linefills ?? []).filter(want)
|
|
18545
|
+
};
|
|
18546
|
+
}
|
|
18547
|
+
function drawingSetEmpty(s) {
|
|
18548
|
+
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
18549
|
+
}
|
|
18532
18550
|
function fontSizePx(size) {
|
|
18533
18551
|
return size === "auto" ? 12 : namedFontSize(size);
|
|
18534
18552
|
}
|
|
@@ -19381,10 +19399,8 @@ var ChromeRenderer = class {
|
|
|
19381
19399
|
this.ctx = null;
|
|
19382
19400
|
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
19383
19401
|
this.axisTextColor = DARK_THEME.textColor;
|
|
19384
|
-
// Shared Pine-drawing renderer
|
|
19402
|
+
// Shared Pine-drawing renderer, used here for autoscale geometry only; widthCache persists.
|
|
19385
19403
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
19386
|
-
// Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per render).
|
|
19387
|
-
this.labelTips = [];
|
|
19388
19404
|
}
|
|
19389
19405
|
mount(canvas) {
|
|
19390
19406
|
this.canvas = canvas;
|
|
@@ -19408,8 +19424,8 @@ var ChromeRenderer = class {
|
|
|
19408
19424
|
*/
|
|
19409
19425
|
paneDrawingsRange(ownModels, scene, isPricePane, vr) {
|
|
19410
19426
|
let dr = null;
|
|
19411
|
-
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(
|
|
19412
|
-
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(
|
|
19427
|
+
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, false), vr, scene.offsetOf(m.id)));
|
|
19428
|
+
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, true), vr, scene.offsetOf(m.id)));
|
|
19413
19429
|
return dr;
|
|
19414
19430
|
}
|
|
19415
19431
|
/** Clear the chrome canvas and draw drawings + axes + current-price line.
|
|
@@ -19426,7 +19442,6 @@ var ChromeRenderer = class {
|
|
|
19426
19442
|
const dataW = coords.width;
|
|
19427
19443
|
const dataH = coords.height;
|
|
19428
19444
|
this.axisTextColor = surface?.textColor ?? theme.textColor;
|
|
19429
|
-
this.labelTips = [];
|
|
19430
19445
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
19431
19446
|
ctx.clearRect(0, 0, fullW, fullH);
|
|
19432
19447
|
if (surface && (fullW > dataW || fullH > dataH)) {
|
|
@@ -19442,17 +19457,6 @@ var ChromeRenderer = class {
|
|
|
19442
19457
|
return;
|
|
19443
19458
|
}
|
|
19444
19459
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
19445
|
-
for (const pane of panes) {
|
|
19446
|
-
if (pane.collapsed) continue;
|
|
19447
|
-
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
19448
|
-
const sc = scene.scaleFor(m, pane);
|
|
19449
|
-
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
19450
|
-
this.renderDrawings(ctx, coords, this.ownDrawings(m), mp, dataW, scene.offsetOf(m.id));
|
|
19451
|
-
}
|
|
19452
|
-
}
|
|
19453
|
-
if (pricePane) {
|
|
19454
|
-
for (const m of scene.indicators.values()) this.renderDrawings(ctx, coords, this.overlayDrawings(m), pricePane, dataW, scene.offsetOf(m.id));
|
|
19455
|
-
}
|
|
19456
19460
|
if (pricePane && !pricePane.collapsed && scene.tradeMarkers.visible) {
|
|
19457
19461
|
for (const m of scene.indicators.values()) {
|
|
19458
19462
|
if (m.trades?.length) this.renderTrades(ctx, coords, scene, theme, m.trades, pricePane, dataW);
|
|
@@ -19468,25 +19472,6 @@ var ChromeRenderer = class {
|
|
|
19468
19472
|
this.canvas = null;
|
|
19469
19473
|
this.ctx = null;
|
|
19470
19474
|
}
|
|
19471
|
-
// ── Pine-drawing helpers (own vs force_overlay routing) ──
|
|
19472
|
-
ownDrawings(m) {
|
|
19473
|
-
return {
|
|
19474
|
-
lines: (m.lines ?? []).filter((d) => !d.overlay),
|
|
19475
|
-
boxes: (m.boxes ?? []).filter((d) => !d.overlay),
|
|
19476
|
-
labels: (m.labels ?? []).filter((d) => !d.overlay),
|
|
19477
|
-
polylines: (m.polylines ?? []).filter((d) => !d.overlay),
|
|
19478
|
-
linefills: (m.linefills ?? []).filter((d) => !d.overlay)
|
|
19479
|
-
};
|
|
19480
|
-
}
|
|
19481
|
-
overlayDrawings(m) {
|
|
19482
|
-
return {
|
|
19483
|
-
lines: (m.lines ?? []).filter((d) => d.overlay),
|
|
19484
|
-
boxes: (m.boxes ?? []).filter((d) => d.overlay),
|
|
19485
|
-
labels: (m.labels ?? []).filter((d) => d.overlay),
|
|
19486
|
-
polylines: (m.polylines ?? []).filter((d) => d.overlay),
|
|
19487
|
-
linefills: (m.linefills ?? []).filter((d) => d.overlay)
|
|
19488
|
-
};
|
|
19489
|
-
}
|
|
19490
19475
|
drawingsRange(set, vr, indexOffset = 0) {
|
|
19491
19476
|
this.drawScene.setSet(set, indexOffset);
|
|
19492
19477
|
if (this.drawScene.isEmpty()) return null;
|
|
@@ -19520,34 +19505,6 @@ var ChromeRenderer = class {
|
|
|
19520
19505
|
);
|
|
19521
19506
|
ctx.restore();
|
|
19522
19507
|
}
|
|
19523
|
-
renderDrawings(ctx, coords, set, pane, dataW, indexOffset = 0) {
|
|
19524
|
-
this.drawScene.setSet(set, indexOffset);
|
|
19525
|
-
if (this.drawScene.isEmpty()) return;
|
|
19526
|
-
ctx.save();
|
|
19527
|
-
ctx.translate(0, pane.bounds.top);
|
|
19528
|
-
ctx.beginPath();
|
|
19529
|
-
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
19530
|
-
ctx.clip();
|
|
19531
|
-
this.drawScene.render(
|
|
19532
|
-
ctx,
|
|
19533
|
-
dataW,
|
|
19534
|
-
pane.bounds.height,
|
|
19535
|
-
(l) => coords.logicalToX(l),
|
|
19536
|
-
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
19537
|
-
);
|
|
19538
|
-
ctx.restore();
|
|
19539
|
-
for (const r of this.drawScene.labelTipRegions()) {
|
|
19540
|
-
this.labelTips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
19541
|
-
}
|
|
19542
|
-
}
|
|
19543
|
-
/** Tooltip of the topmost label under a plot-space point, or null. Fed by the last render. */
|
|
19544
|
-
labelTooltipAt(x, y) {
|
|
19545
|
-
for (let i = this.labelTips.length - 1; i >= 0; i -= 1) {
|
|
19546
|
-
const r = this.labelTips[i];
|
|
19547
|
-
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
19548
|
-
}
|
|
19549
|
-
return null;
|
|
19550
|
-
}
|
|
19551
19508
|
// ── axes ──
|
|
19552
19509
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
19553
19510
|
ctx.strokeStyle = scene.style.borderColor ?? theme.borderColor;
|
|
@@ -23956,17 +23913,17 @@ function glyphIcon(glyph) {
|
|
|
23956
23913
|
return textGlyph(String(glyph), 15);
|
|
23957
23914
|
}
|
|
23958
23915
|
function stampSizeIcon(size) {
|
|
23959
|
-
return textGlyph("\u25CF", (
|
|
23916
|
+
return textGlyph("\u25CF", (SIZE_PX3[String(size)] ?? 13) + 4);
|
|
23960
23917
|
}
|
|
23961
23918
|
function sizeIcon(size) {
|
|
23962
23919
|
return textGlyph(String(size).charAt(0).toUpperCase(), 15);
|
|
23963
23920
|
}
|
|
23964
|
-
var
|
|
23921
|
+
var SIZE_PX3 = { small: 10, normal: 13, large: 16, huge: 20 };
|
|
23965
23922
|
function numbersSizeIcon(size) {
|
|
23966
|
-
return textGlyph("12", (
|
|
23923
|
+
return textGlyph("12", (SIZE_PX3[String(size)] ?? 13) - 1, 16.5, 'font-weight="600"');
|
|
23967
23924
|
}
|
|
23968
23925
|
function labelSizeIcon(size) {
|
|
23969
|
-
return textGlyph("T", (
|
|
23926
|
+
return textGlyph("T", (SIZE_PX3[String(size)] ?? 13) + 2);
|
|
23970
23927
|
}
|
|
23971
23928
|
function capitalize(s) {
|
|
23972
23929
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -24130,7 +24087,7 @@ var DrawingToolbar = class {
|
|
|
24130
24087
|
this.root.replaceChildren();
|
|
24131
24088
|
this.groupCells.clear();
|
|
24132
24089
|
this.groupIcons.clear();
|
|
24133
|
-
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.
|
|
24090
|
+
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.onCursorClick());
|
|
24134
24091
|
this.root.appendChild(this.cursorBtn);
|
|
24135
24092
|
if (this.def.groups.length > 0) this.root.appendChild(this.divider());
|
|
24136
24093
|
for (const g of this.def.groups) {
|
|
@@ -24234,6 +24191,14 @@ var DrawingToolbar = class {
|
|
|
24234
24191
|
this.magnetIcon = icon2;
|
|
24235
24192
|
return cell;
|
|
24236
24193
|
}
|
|
24194
|
+
/** Cursor returns to select/idle: an active measure/eraser mode exits through its own
|
|
24195
|
+
* toggle callback (disarming a tool via `onArm(null)` alone can't — the host treats a
|
|
24196
|
+
* null arm as a no-op side effect of entering those modes), then the tool disarms. */
|
|
24197
|
+
onCursorClick() {
|
|
24198
|
+
if (this.measureActive) this.onMeasure();
|
|
24199
|
+
if (this.eraserActive) this.onEraser();
|
|
24200
|
+
this.onArm(null);
|
|
24201
|
+
}
|
|
24237
24202
|
/** Clicking the icon arms the group's last-used tool (it does NOT open the flyout). */
|
|
24238
24203
|
onGroupIconClick(group) {
|
|
24239
24204
|
const type = this.lastUsed.get(group.id) ?? group.tools[0]?.type;
|
|
@@ -25565,6 +25530,315 @@ var UserDrawingController = class {
|
|
|
25565
25530
|
}
|
|
25566
25531
|
};
|
|
25567
25532
|
|
|
25533
|
+
// src/renderers/shared/TableOverlay.ts
|
|
25534
|
+
var SIZE_PX4 = {
|
|
25535
|
+
auto: 13,
|
|
25536
|
+
tiny: 10,
|
|
25537
|
+
small: 11,
|
|
25538
|
+
normal: 13,
|
|
25539
|
+
large: 16,
|
|
25540
|
+
huge: 20
|
|
25541
|
+
};
|
|
25542
|
+
function fontPxOf(size) {
|
|
25543
|
+
if (typeof size === "number") return size > 0 ? size : SIZE_PX4.auto;
|
|
25544
|
+
return SIZE_PX4[size] ?? SIZE_PX4.auto;
|
|
25545
|
+
}
|
|
25546
|
+
function tableHasContent(t) {
|
|
25547
|
+
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
25548
|
+
}
|
|
25549
|
+
function mergeRenderPlan(t) {
|
|
25550
|
+
const span = /* @__PURE__ */ new Map();
|
|
25551
|
+
const omit = /* @__PURE__ */ new Set();
|
|
25552
|
+
for (const m of t.merges) {
|
|
25553
|
+
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
25554
|
+
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
25555
|
+
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
25556
|
+
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
25557
|
+
}
|
|
25558
|
+
}
|
|
25559
|
+
}
|
|
25560
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
25561
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
25562
|
+
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
25563
|
+
}
|
|
25564
|
+
}
|
|
25565
|
+
for (const key of span.keys()) omit.delete(key);
|
|
25566
|
+
return { span, omit };
|
|
25567
|
+
}
|
|
25568
|
+
|
|
25569
|
+
// src/renderers/shared/TableCanvasRenderer.ts
|
|
25570
|
+
var PAD_X = 6;
|
|
25571
|
+
var PAD_Y = 2;
|
|
25572
|
+
var MARGIN = 6;
|
|
25573
|
+
var LINE_HEIGHT = 1.2;
|
|
25574
|
+
function paintTable(ctx, t, args, tips) {
|
|
25575
|
+
if (!tableHasContent(t)) return;
|
|
25576
|
+
const layout = layoutTable(ctx, t, args);
|
|
25577
|
+
if (!layout || layout.w <= 0 || layout.h <= 0) return;
|
|
25578
|
+
const fw = t.frameColor && t.frameWidth > 0 ? t.frameWidth : 0;
|
|
25579
|
+
const { x, y } = anchorOrigin(t.position, layout.w + 2 * fw, layout.h + 2 * fw, args);
|
|
25580
|
+
const x0 = x + fw;
|
|
25581
|
+
const y0 = y + fw;
|
|
25582
|
+
if (t.bgColor) {
|
|
25583
|
+
ctx.fillStyle = t.bgColor;
|
|
25584
|
+
ctx.fillRect(x0, y0, layout.w, layout.h);
|
|
25585
|
+
}
|
|
25586
|
+
if (fw > 0 && t.frameColor) {
|
|
25587
|
+
ctx.strokeStyle = t.frameColor;
|
|
25588
|
+
ctx.lineWidth = fw;
|
|
25589
|
+
ctx.strokeRect(x + fw / 2, y + fw / 2, layout.w + fw, layout.h + fw);
|
|
25590
|
+
}
|
|
25591
|
+
const colX = [0];
|
|
25592
|
+
for (const w of layout.colW) colX.push(colX[colX.length - 1] + w);
|
|
25593
|
+
const rowY = [0];
|
|
25594
|
+
for (const h of layout.rowH) rowY.push(rowY[rowY.length - 1] + h);
|
|
25595
|
+
const prevBaseline = ctx.textBaseline;
|
|
25596
|
+
const prevAlign = ctx.textAlign;
|
|
25597
|
+
ctx.textBaseline = "middle";
|
|
25598
|
+
for (const box of layout.boxes) {
|
|
25599
|
+
const rx = x0 + colX[box.c];
|
|
25600
|
+
const ry = y0 + rowY[box.r];
|
|
25601
|
+
const rw = colX[box.c + box.cs] - colX[box.c];
|
|
25602
|
+
const rh = rowY[box.r + box.rs] - rowY[box.r];
|
|
25603
|
+
const cell = box.cell;
|
|
25604
|
+
if (cell.bgColor) {
|
|
25605
|
+
ctx.fillStyle = cell.bgColor;
|
|
25606
|
+
ctx.fillRect(rx, ry, rw, rh);
|
|
25607
|
+
}
|
|
25608
|
+
const text = cell.text ?? "";
|
|
25609
|
+
if (text.length > 0) {
|
|
25610
|
+
const px = fontPxOf(cell.textSize);
|
|
25611
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
25612
|
+
ctx.fillStyle = cell.textColor ?? args.theme.textColor;
|
|
25613
|
+
const lines = text.split("\n");
|
|
25614
|
+
const blockH = lines.length * px * LINE_HEIGHT;
|
|
25615
|
+
const blockTop = cell.vAlign === "top" ? ry + PAD_Y : cell.vAlign === "bottom" ? ry + rh - PAD_Y - blockH : ry + (rh - blockH) / 2;
|
|
25616
|
+
const tx = cell.hAlign === "left" ? rx + PAD_X : cell.hAlign === "right" ? rx + rw - PAD_X : rx + rw / 2;
|
|
25617
|
+
ctx.textAlign = cell.hAlign;
|
|
25618
|
+
lines.forEach((line, i) => ctx.fillText(line, tx, blockTop + (i + 0.5) * px * LINE_HEIGHT));
|
|
25619
|
+
}
|
|
25620
|
+
if (cell.tooltip) tips.push({ left: rx, top: ry, right: rx + rw, bottom: ry + rh, text: cell.tooltip });
|
|
25621
|
+
}
|
|
25622
|
+
ctx.textBaseline = prevBaseline;
|
|
25623
|
+
ctx.textAlign = prevAlign;
|
|
25624
|
+
if (t.borderColor && t.borderWidth > 0) {
|
|
25625
|
+
ctx.strokeStyle = t.borderColor;
|
|
25626
|
+
ctx.lineWidth = t.borderWidth;
|
|
25627
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25628
|
+
ctx.beginPath();
|
|
25629
|
+
const edge = (ax, ay, bx, by) => {
|
|
25630
|
+
const key = `${ax},${ay},${bx},${by}`;
|
|
25631
|
+
if (seen.has(key)) return;
|
|
25632
|
+
seen.add(key);
|
|
25633
|
+
ctx.moveTo(ax, ay);
|
|
25634
|
+
ctx.lineTo(bx, by);
|
|
25635
|
+
};
|
|
25636
|
+
for (const box of layout.boxes) {
|
|
25637
|
+
const l = Math.round(x0 + colX[box.c]);
|
|
25638
|
+
const r = Math.round(x0 + colX[box.c + box.cs]);
|
|
25639
|
+
const tp = Math.round(y0 + rowY[box.r]);
|
|
25640
|
+
const bt = Math.round(y0 + rowY[box.r + box.rs]);
|
|
25641
|
+
edge(l, tp, r, tp);
|
|
25642
|
+
edge(l, bt, r, bt);
|
|
25643
|
+
edge(l, tp, l, bt);
|
|
25644
|
+
edge(r, tp, r, bt);
|
|
25645
|
+
}
|
|
25646
|
+
ctx.stroke();
|
|
25647
|
+
}
|
|
25648
|
+
}
|
|
25649
|
+
function layoutTable(ctx, t, args) {
|
|
25650
|
+
const { span, omit } = mergeRenderPlan(t);
|
|
25651
|
+
const colW = new Array(t.columns).fill(0);
|
|
25652
|
+
const rowH = new Array(t.rows).fill(0);
|
|
25653
|
+
const boxes = [];
|
|
25654
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
25655
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
25656
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
25657
|
+
const cell = t.cells[r]?.[c];
|
|
25658
|
+
if (cell == null) continue;
|
|
25659
|
+
const sp = span.get(`${r}:${c}`);
|
|
25660
|
+
boxes.push({ cell, r, c, cs: Math.min(sp?.cs ?? 1, t.columns - c), rs: Math.min(sp?.rs ?? 1, t.rows - r) });
|
|
25661
|
+
}
|
|
25662
|
+
}
|
|
25663
|
+
if (boxes.length === 0) return null;
|
|
25664
|
+
const sizeOf = (cell) => {
|
|
25665
|
+
const px = fontPxOf(cell.textSize);
|
|
25666
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
25667
|
+
const lines = (cell.text ?? "").split("\n");
|
|
25668
|
+
let maxW = 0;
|
|
25669
|
+
for (const line of lines) maxW = Math.max(maxW, ctx.measureText(line).width);
|
|
25670
|
+
let w2 = Math.ceil(maxW) + 2 * PAD_X;
|
|
25671
|
+
let h2 = Math.ceil(lines.length * px * LINE_HEIGHT) + 2 * PAD_Y;
|
|
25672
|
+
if (cell.width) w2 = Math.max(w2, cell.width / 100 * args.plotWidth);
|
|
25673
|
+
if (cell.height) h2 = Math.max(h2, cell.height / 100 * args.paneHeight);
|
|
25674
|
+
return { w: w2, h: h2 };
|
|
25675
|
+
};
|
|
25676
|
+
const spanning = [];
|
|
25677
|
+
for (const box of boxes) {
|
|
25678
|
+
const { w: w2, h: h2 } = sizeOf(box.cell);
|
|
25679
|
+
if (box.cs === 1) colW[box.c] = Math.max(colW[box.c], w2);
|
|
25680
|
+
if (box.rs === 1) rowH[box.r] = Math.max(rowH[box.r], h2);
|
|
25681
|
+
if (box.cs > 1 || box.rs > 1) spanning.push({ box, w: w2, h: h2 });
|
|
25682
|
+
}
|
|
25683
|
+
for (const { box, w: w2, h: h2 } of spanning) {
|
|
25684
|
+
if (box.cs > 1) {
|
|
25685
|
+
let sum = 0;
|
|
25686
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
|
|
25687
|
+
if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
|
|
25688
|
+
}
|
|
25689
|
+
if (box.rs > 1) {
|
|
25690
|
+
let sum = 0;
|
|
25691
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
|
|
25692
|
+
if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
|
|
25693
|
+
}
|
|
25694
|
+
}
|
|
25695
|
+
let w = 0;
|
|
25696
|
+
for (const cw of colW) w += cw;
|
|
25697
|
+
let h = 0;
|
|
25698
|
+
for (const rh of rowH) h += rh;
|
|
25699
|
+
return { colW, rowH, w, h, boxes };
|
|
25700
|
+
}
|
|
25701
|
+
function anchorOrigin(position, totalW, totalH, args) {
|
|
25702
|
+
let y;
|
|
25703
|
+
if (position.startsWith("top")) y = MARGIN;
|
|
25704
|
+
else if (position.startsWith("bottom")) y = args.paneHeight - MARGIN - totalH;
|
|
25705
|
+
else y = args.paneHeight / 2 - totalH / 2;
|
|
25706
|
+
let x;
|
|
25707
|
+
if (position.endsWith("left")) x = MARGIN;
|
|
25708
|
+
else if (position.endsWith("right")) x = args.plotWidth - MARGIN - totalW;
|
|
25709
|
+
else x = args.plotWidth / 2 - totalW / 2;
|
|
25710
|
+
return { x, y };
|
|
25711
|
+
}
|
|
25712
|
+
function cellFont(cell, px, theme) {
|
|
25713
|
+
const family = cell.fontFamily === "monospace" ? "monospace" : theme.fontFamily || "sans-serif";
|
|
25714
|
+
return `${cell.italic ? "italic " : ""}${cell.bold ? "bold " : ""}${px}px ${family}`;
|
|
25715
|
+
}
|
|
25716
|
+
|
|
25717
|
+
// src/renderers/native/drawings/IndicatorDrawingSlices.ts
|
|
25718
|
+
function indicatorSliceKey(z, boundaries) {
|
|
25719
|
+
return boundaries.find((b) => b > z) ?? Infinity;
|
|
25720
|
+
}
|
|
25721
|
+
var IndicatorDrawingSlices = class {
|
|
25722
|
+
constructor() {
|
|
25723
|
+
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
25724
|
+
/** Slice canvas cache, keyed `paneId|beforeZ` — same lifecycle as the user-drawing cache. */
|
|
25725
|
+
this.sliceCache = /* @__PURE__ */ new Map();
|
|
25726
|
+
/** Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per prepare). */
|
|
25727
|
+
this.tips = [];
|
|
25728
|
+
}
|
|
25729
|
+
/**
|
|
25730
|
+
* Rebuild the per-indicator drawing slices for this data frame. `ref` is the data
|
|
25731
|
+
* canvas the slices must match pixel-for-pixel (the backend composites them 1:1).
|
|
25732
|
+
* Runs from the renderer's data paint, just before the backend composites the scene.
|
|
25733
|
+
*/
|
|
25734
|
+
prepare(scene, coords, theme, ref) {
|
|
25735
|
+
this.tips = [];
|
|
25736
|
+
const out = /* @__PURE__ */ new Map();
|
|
25737
|
+
if (ref.width === 0 || ref.height === 0) {
|
|
25738
|
+
this.sliceCache.clear();
|
|
25739
|
+
return out;
|
|
25740
|
+
}
|
|
25741
|
+
this.drawScene.setDeps({
|
|
25742
|
+
timeToLogical: (ms) => coords.timeToLogical(ms),
|
|
25743
|
+
barAt: (logical) => {
|
|
25744
|
+
const b = scene.bars[Math.round(logical)];
|
|
25745
|
+
return b ? { high: b.high, low: b.low } : null;
|
|
25746
|
+
},
|
|
25747
|
+
theme
|
|
25748
|
+
});
|
|
25749
|
+
const dpr = coords.dpr;
|
|
25750
|
+
const dataW = coords.width;
|
|
25751
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
25752
|
+
const add = (paneId, beforeZ, entry) => {
|
|
25753
|
+
const key = `${paneId}|${beforeZ}`;
|
|
25754
|
+
const bucket = buckets.get(key);
|
|
25755
|
+
if (bucket) bucket.entries.push(entry);
|
|
25756
|
+
else buckets.set(key, { paneId, beforeZ, entries: [entry] });
|
|
25757
|
+
};
|
|
25758
|
+
for (const pane of scene.orderedPanes()) {
|
|
25759
|
+
if (pane.collapsed) continue;
|
|
25760
|
+
const boundaries = scene.seriesBoundaries(pane.id);
|
|
25761
|
+
for (const m of scene.orderedIndicatorsForPane(pane.id)) {
|
|
25762
|
+
const set = modelDrawingSet(m, false);
|
|
25763
|
+
const tables = (m.tables ?? []).filter((t) => !t.overlay);
|
|
25764
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
25765
|
+
const sc = scene.scaleFor(m, pane);
|
|
25766
|
+
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
25767
|
+
const beforeZ = indicatorSliceKey(scene.zOf(m.id), boundaries);
|
|
25768
|
+
add(pane.id, beforeZ, { set, tables, pane: mp, indexOffset: scene.offsetOf(m.id) });
|
|
25769
|
+
}
|
|
25770
|
+
if (pane.kind === "price") {
|
|
25771
|
+
for (const m of scene.indicators.values()) {
|
|
25772
|
+
const set = modelDrawingSet(m, true);
|
|
25773
|
+
const tables = (m.tables ?? []).filter((t) => t.overlay === true);
|
|
25774
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
25775
|
+
add(pane.id, Infinity, { set, tables, pane, indexOffset: scene.offsetOf(m.id) });
|
|
25776
|
+
}
|
|
25777
|
+
}
|
|
25778
|
+
}
|
|
25779
|
+
for (const [key, { paneId, beforeZ, entries }] of buckets) {
|
|
25780
|
+
let canvas = this.sliceCache.get(key);
|
|
25781
|
+
if (!canvas) {
|
|
25782
|
+
canvas = document.createElement("canvas");
|
|
25783
|
+
this.sliceCache.set(key, canvas);
|
|
25784
|
+
}
|
|
25785
|
+
if (canvas.width !== ref.width || canvas.height !== ref.height) {
|
|
25786
|
+
canvas.width = ref.width;
|
|
25787
|
+
canvas.height = ref.height;
|
|
25788
|
+
}
|
|
25789
|
+
const ctx = canvas.getContext("2d");
|
|
25790
|
+
if (!ctx) continue;
|
|
25791
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
25792
|
+
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
25793
|
+
for (const e of entries) this.paintEntry(ctx, e, coords, dataW, theme);
|
|
25794
|
+
const slices = out.get(paneId) ?? [];
|
|
25795
|
+
slices.push({ beforeZ, canvas });
|
|
25796
|
+
out.set(paneId, slices);
|
|
25797
|
+
}
|
|
25798
|
+
for (const key of [...this.sliceCache.keys()]) if (!buckets.has(key)) this.sliceCache.delete(key);
|
|
25799
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
25800
|
+
return out;
|
|
25801
|
+
}
|
|
25802
|
+
paintEntry(ctx, e, coords, dataW, theme) {
|
|
25803
|
+
const { pane } = e;
|
|
25804
|
+
const paneTips = [];
|
|
25805
|
+
ctx.save();
|
|
25806
|
+
ctx.translate(0, pane.bounds.top);
|
|
25807
|
+
ctx.beginPath();
|
|
25808
|
+
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
25809
|
+
ctx.clip();
|
|
25810
|
+
this.drawScene.setSet(e.set, e.indexOffset);
|
|
25811
|
+
this.drawScene.render(
|
|
25812
|
+
ctx,
|
|
25813
|
+
dataW,
|
|
25814
|
+
pane.bounds.height,
|
|
25815
|
+
(l) => coords.logicalToX(l),
|
|
25816
|
+
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
25817
|
+
);
|
|
25818
|
+
paneTips.push(...this.drawScene.labelTipRegions());
|
|
25819
|
+
for (const t of e.tables) paintTable(ctx, t, { paneHeight: pane.bounds.height, plotWidth: dataW, theme }, paneTips);
|
|
25820
|
+
ctx.restore();
|
|
25821
|
+
for (const r of paneTips) {
|
|
25822
|
+
this.tips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
25823
|
+
}
|
|
25824
|
+
}
|
|
25825
|
+
/** Tooltip of the topmost label or table cell under a plot-space point, or null. Fed by the last prepare. */
|
|
25826
|
+
labelTooltipAt(x, y) {
|
|
25827
|
+
for (let i = this.tips.length - 1; i >= 0; i -= 1) {
|
|
25828
|
+
const r = this.tips[i];
|
|
25829
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
25830
|
+
}
|
|
25831
|
+
return null;
|
|
25832
|
+
}
|
|
25833
|
+
};
|
|
25834
|
+
function mergeSlices(indicator, user) {
|
|
25835
|
+
const out = /* @__PURE__ */ new Map();
|
|
25836
|
+
for (const [paneId, slices] of indicator) out.set(paneId, [...slices]);
|
|
25837
|
+
for (const [paneId, slices] of user) out.set(paneId, [...out.get(paneId) ?? [], ...slices]);
|
|
25838
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
25839
|
+
return out;
|
|
25840
|
+
}
|
|
25841
|
+
|
|
25568
25842
|
// src/renderers/native/drawings/Projector.ts
|
|
25569
25843
|
function createProjector(coords, paneOf, paneIdAtY, barsInRange) {
|
|
25570
25844
|
return {
|
|
@@ -26444,6 +26718,11 @@ var NativeRenderer = class {
|
|
|
26444
26718
|
this.vpvrRenderer = new VpvrRenderer();
|
|
26445
26719
|
this.resizeObserver = null;
|
|
26446
26720
|
this.dprMedia = null;
|
|
26721
|
+
/** Plot size in INTEGER device px, as last reported by the resize observer's
|
|
26722
|
+
* device-pixel-content-box — the browser's own statement of how many device pixels
|
|
26723
|
+
* it paints the plot into. `null` until the first report or where the box type is
|
|
26724
|
+
* unsupported (WebKit); syncSize then falls back to rounding the client rect. */
|
|
26725
|
+
this.plotDeviceSize = null;
|
|
26447
26726
|
this.coords = new CoordinateSystem();
|
|
26448
26727
|
this.scene = new SceneGraph();
|
|
26449
26728
|
// chosen at mount (WebGL2 if available, else canvas2d)
|
|
@@ -26451,6 +26730,8 @@ var NativeRenderer = class {
|
|
|
26451
26730
|
this.glowAmount = 0;
|
|
26452
26731
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
26453
26732
|
this.chrome = new ChromeRenderer();
|
|
26733
|
+
/** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
|
|
26734
|
+
this.indicatorSlices = new IndicatorDrawingSlices();
|
|
26454
26735
|
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
26455
26736
|
this.labelTooltip = null;
|
|
26456
26737
|
this.crosshairLayer = new CrosshairRenderer();
|
|
@@ -26599,7 +26880,6 @@ var NativeRenderer = class {
|
|
|
26599
26880
|
this.toggleVisibleCbs = /* @__PURE__ */ new Set();
|
|
26600
26881
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
26601
26882
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
26602
|
-
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
26603
26883
|
this.name = "native";
|
|
26604
26884
|
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"];
|
|
26605
26885
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
@@ -26913,7 +27193,6 @@ var NativeRenderer = class {
|
|
|
26913
27193
|
* hidden) on clear so a re-show picks up the current theme.
|
|
26914
27194
|
*/
|
|
26915
27195
|
setLoading(loading) {
|
|
26916
|
-
for (const overlay of this.tableOverlays.values()) overlay.setVisible(!loading);
|
|
26917
27196
|
if (!loading || !this.wrapper) {
|
|
26918
27197
|
this.loadingEl?.remove();
|
|
26919
27198
|
this.loadingEl = null;
|
|
@@ -27575,7 +27854,7 @@ var NativeRenderer = class {
|
|
|
27575
27854
|
this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
|
|
27576
27855
|
this.labelTooltip = new LabelTooltip(this.plot, {
|
|
27577
27856
|
theme: () => this.chromeTheme(),
|
|
27578
|
-
lookup: (x, y) => this.
|
|
27857
|
+
lookup: (x, y) => this.indicatorSlices.labelTooltipAt(x, y)
|
|
27579
27858
|
});
|
|
27580
27859
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
27581
27860
|
projector: () => this.drawingProjector(),
|
|
@@ -27660,6 +27939,7 @@ var NativeRenderer = class {
|
|
|
27660
27939
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
27661
27940
|
}
|
|
27662
27941
|
});
|
|
27942
|
+
this.paneControls.setSuspended(this.layoutMode === "mobile");
|
|
27663
27943
|
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
27664
27944
|
panes: () => this.axisScaleViews(),
|
|
27665
27945
|
rightAxis: () => this.rightAxisW,
|
|
@@ -27669,8 +27949,19 @@ var NativeRenderer = class {
|
|
|
27669
27949
|
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
27670
27950
|
}
|
|
27671
27951
|
});
|
|
27672
|
-
this.resizeObserver = new ResizeObserver(() =>
|
|
27952
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
27953
|
+
for (const e of entries) {
|
|
27954
|
+
if (e.target !== this.plot) continue;
|
|
27955
|
+
const s = e.devicePixelContentBoxSize?.[0];
|
|
27956
|
+
if (s) this.plotDeviceSize = { width: s.inlineSize, height: s.blockSize };
|
|
27957
|
+
}
|
|
27958
|
+
this.resize();
|
|
27959
|
+
});
|
|
27673
27960
|
this.resizeObserver.observe(this.wrapper);
|
|
27961
|
+
try {
|
|
27962
|
+
this.resizeObserver.observe(this.plot, { box: "device-pixel-content-box" });
|
|
27963
|
+
} catch {
|
|
27964
|
+
}
|
|
27674
27965
|
this.watchDpr();
|
|
27675
27966
|
this.syncSize();
|
|
27676
27967
|
}
|
|
@@ -27795,8 +28086,6 @@ var NativeRenderer = class {
|
|
|
27795
28086
|
this.inputsUI?.destroy();
|
|
27796
28087
|
this.paneControls?.destroy();
|
|
27797
28088
|
this.axisScaleButtons?.destroy();
|
|
27798
|
-
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
27799
|
-
this.tableOverlays.clear();
|
|
27800
28089
|
this.resizeObserver?.disconnect();
|
|
27801
28090
|
this.resizeObserver = null;
|
|
27802
28091
|
this.dprMedia?.removeEventListener("change", this.onDprChange);
|
|
@@ -27825,6 +28114,7 @@ var NativeRenderer = class {
|
|
|
27825
28114
|
this.attributionEl = null;
|
|
27826
28115
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
27827
28116
|
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
28117
|
+
this.mountContainer?.style.removeProperty("--vela-bottom-gutter");
|
|
27828
28118
|
this.mountContainer?.style.removeProperty("--vela-price-pane-top");
|
|
27829
28119
|
this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
|
|
27830
28120
|
this.mountContainer = null;
|
|
@@ -27917,7 +28207,6 @@ var NativeRenderer = class {
|
|
|
27917
28207
|
ensurePane(pane) {
|
|
27918
28208
|
this.scene.ensurePane(pane.id, pane.kind, pane.order, pane.heightWeight ?? (pane.kind === "price" ? 3 : 1));
|
|
27919
28209
|
this.layoutPanes();
|
|
27920
|
-
this.repositionTables();
|
|
27921
28210
|
this.paneControls?.refresh();
|
|
27922
28211
|
this.scheduler.invalidate(4 /* Full */);
|
|
27923
28212
|
}
|
|
@@ -27939,17 +28228,14 @@ var NativeRenderer = class {
|
|
|
27939
28228
|
if (!model.ownScale) this.scene.dropIndicatorScale(handle.id);
|
|
27940
28229
|
this.inputsUI.setPane(handle.id, paneId);
|
|
27941
28230
|
this.refreshAnchorOffset(model);
|
|
27942
|
-
this.syncTables(model);
|
|
27943
28231
|
this.refreshAxisWidth();
|
|
27944
28232
|
this.layoutPanes();
|
|
27945
|
-
this.repositionTables();
|
|
27946
28233
|
this.paneControls?.refresh();
|
|
27947
28234
|
this.scheduler.invalidate(4 /* Full */);
|
|
27948
28235
|
}
|
|
27949
28236
|
orderPanes(orderedIds) {
|
|
27950
28237
|
this.scene.orderPanes(orderedIds);
|
|
27951
28238
|
this.layoutPanes();
|
|
27952
|
-
this.repositionTables();
|
|
27953
28239
|
this.paneControls?.refresh();
|
|
27954
28240
|
this.scheduler.invalidate(4 /* Full */);
|
|
27955
28241
|
}
|
|
@@ -27958,7 +28244,6 @@ var NativeRenderer = class {
|
|
|
27958
28244
|
if (!pane || pane.collapsed === collapsed) return;
|
|
27959
28245
|
pane.collapsed = collapsed;
|
|
27960
28246
|
this.layoutPanes();
|
|
27961
|
-
this.repositionTables();
|
|
27962
28247
|
this.paneControls?.refresh();
|
|
27963
28248
|
this.scheduler.invalidate(4 /* Full */);
|
|
27964
28249
|
}
|
|
@@ -27966,7 +28251,6 @@ var NativeRenderer = class {
|
|
|
27966
28251
|
if (paneId !== null && !this.scene.panes.has(paneId)) paneId = null;
|
|
27967
28252
|
this.maximizedPaneId = paneId;
|
|
27968
28253
|
this.layoutPanes();
|
|
27969
|
-
this.repositionTables();
|
|
27970
28254
|
this.paneControls?.refresh();
|
|
27971
28255
|
this.scheduler.invalidate(4 /* Full */);
|
|
27972
28256
|
}
|
|
@@ -28053,7 +28337,6 @@ var NativeRenderer = class {
|
|
|
28053
28337
|
native: !!model.native,
|
|
28054
28338
|
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
28055
28339
|
});
|
|
28056
|
-
this.syncTables(model);
|
|
28057
28340
|
if (model.native?.type === "volume") {
|
|
28058
28341
|
this.volumeActive = true;
|
|
28059
28342
|
this.volumeHidden = false;
|
|
@@ -28077,7 +28360,6 @@ var NativeRenderer = class {
|
|
|
28077
28360
|
}
|
|
28078
28361
|
}
|
|
28079
28362
|
applyPatch(model, patch);
|
|
28080
|
-
this.syncTables(model);
|
|
28081
28363
|
this.scheduler.invalidate(3 /* Light */);
|
|
28082
28364
|
}
|
|
28083
28365
|
removeIndicator(handle) {
|
|
@@ -28096,8 +28378,6 @@ var NativeRenderer = class {
|
|
|
28096
28378
|
this.scene.forgetAnchorOffset(handle.id);
|
|
28097
28379
|
this.scene.dropIndicatorScale(handle.id);
|
|
28098
28380
|
this.inputsUI.remove(handle.id);
|
|
28099
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
28100
|
-
this.tableOverlays.delete(handle.id);
|
|
28101
28381
|
this.refreshAxisWidth();
|
|
28102
28382
|
this.paneControls?.refresh();
|
|
28103
28383
|
this.scheduler.invalidate(4 /* Full */);
|
|
@@ -28141,8 +28421,6 @@ var NativeRenderer = class {
|
|
|
28141
28421
|
}
|
|
28142
28422
|
if (!visible) {
|
|
28143
28423
|
this.scene.indicators.delete(handle.id);
|
|
28144
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
28145
|
-
this.tableOverlays.delete(handle.id);
|
|
28146
28424
|
}
|
|
28147
28425
|
this.inputsUI.setVisible(handle.id, visible);
|
|
28148
28426
|
this.scheduler.invalidate(4 /* Full */);
|
|
@@ -28188,6 +28466,7 @@ var NativeRenderer = class {
|
|
|
28188
28466
|
this.userDrawings?.setLayoutMode(mode);
|
|
28189
28467
|
this.settingsDialog?.setLayoutMode(mode);
|
|
28190
28468
|
this.inputsUI?.setLayoutMode(mode);
|
|
28469
|
+
this.paneControls?.setSuspended(mode === "mobile");
|
|
28191
28470
|
if (this.scrollButton) {
|
|
28192
28471
|
const px = mode === "mobile" ? SCROLL_BTN_SIZE_TOUCH : SCROLL_BTN_SIZE;
|
|
28193
28472
|
this.scrollButton.style.width = `${px}px`;
|
|
@@ -28583,7 +28862,6 @@ var NativeRenderer = class {
|
|
|
28583
28862
|
/** Relayout + repaint + refresh the hover buttons after a collapse/maximize/order change. */
|
|
28584
28863
|
afterPaneLayoutChange() {
|
|
28585
28864
|
this.layoutPanes();
|
|
28586
|
-
this.repositionTables();
|
|
28587
28865
|
this.paneControls?.refresh();
|
|
28588
28866
|
this.scheduler.invalidate(4 /* Full */);
|
|
28589
28867
|
}
|
|
@@ -28664,7 +28942,6 @@ var NativeRenderer = class {
|
|
|
28664
28942
|
above.heightWeight = next.above;
|
|
28665
28943
|
below.heightWeight = next.below;
|
|
28666
28944
|
this.layoutPanes();
|
|
28667
|
-
this.repositionTables();
|
|
28668
28945
|
this.scheduler.invalidate(4 /* Full */);
|
|
28669
28946
|
}
|
|
28670
28947
|
/** Double-click a separator → split the two adjacent panes evenly (each gets half of
|
|
@@ -28679,7 +28956,6 @@ var NativeRenderer = class {
|
|
|
28679
28956
|
above.heightWeight = half;
|
|
28680
28957
|
below.heightWeight = half;
|
|
28681
28958
|
this.layoutPanes();
|
|
28682
|
-
this.repositionTables();
|
|
28683
28959
|
this.scheduler.invalidate(4 /* Full */);
|
|
28684
28960
|
}
|
|
28685
28961
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -28958,7 +29234,10 @@ var NativeRenderer = class {
|
|
|
28958
29234
|
const liveActual = li >= 0 ? this.bars[li] : void 0;
|
|
28959
29235
|
const easeLive = !!liveActual && this.liveEaseTime === liveActual.time && (liveActual.high !== this.liveEaseHigh || liveActual.low !== this.liveEaseLow || liveActual.close !== this.liveEaseClose);
|
|
28960
29236
|
if (easeLive && liveActual) this.bars[li] = { ...liveActual, high: this.liveEaseHigh, low: this.liveEaseLow, close: this.liveEaseClose };
|
|
28961
|
-
this.scene.drawingSlices =
|
|
29237
|
+
this.scene.drawingSlices = mergeSlices(
|
|
29238
|
+
this.indicatorSlices.prepare(this.scene, this.coords, this.theme, this.dataCanvas),
|
|
29239
|
+
this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map()
|
|
29240
|
+
);
|
|
28962
29241
|
this.backdropRenderer.render(this.scene, this.coords, this.theme, gridAlpha);
|
|
28963
29242
|
this.backend.render(this.scene, this.coords, this.theme);
|
|
28964
29243
|
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
@@ -29396,27 +29675,6 @@ var NativeRenderer = class {
|
|
|
29396
29675
|
}
|
|
29397
29676
|
return maxVol;
|
|
29398
29677
|
}
|
|
29399
|
-
/** Create/update/destroy an indicator's DOM table overlay (anchored off real pane geometry). */
|
|
29400
|
-
syncTables(model) {
|
|
29401
|
-
const tables = model.tables ?? [];
|
|
29402
|
-
let overlay = this.tableOverlays.get(model.id);
|
|
29403
|
-
if (tables.length === 0) {
|
|
29404
|
-
if (overlay) {
|
|
29405
|
-
overlay.destroy();
|
|
29406
|
-
this.tableOverlays.delete(model.id);
|
|
29407
|
-
}
|
|
29408
|
-
return;
|
|
29409
|
-
}
|
|
29410
|
-
if (!overlay) {
|
|
29411
|
-
overlay = new TableOverlay(this.plot, this.theme, (id) => this.paneBoundsFor(id));
|
|
29412
|
-
overlay.setVisible(this.loadingEl === null);
|
|
29413
|
-
this.tableOverlays.set(model.id, overlay);
|
|
29414
|
-
}
|
|
29415
|
-
overlay.update(tables);
|
|
29416
|
-
}
|
|
29417
|
-
repositionTables() {
|
|
29418
|
-
for (const overlay of this.tableOverlays.values()) overlay.reposition();
|
|
29419
|
-
}
|
|
29420
29678
|
layoutPanes() {
|
|
29421
29679
|
const panes = this.scene.orderedPanes();
|
|
29422
29680
|
const dataHeight = this.coords.height;
|
|
@@ -29467,6 +29725,7 @@ var NativeRenderer = class {
|
|
|
29467
29725
|
const visible = maxPane ? [maxPane] : this.scene.orderedPanes().filter((p) => !p.collapsed);
|
|
29468
29726
|
const paneBottom = visible.length ? Math.max(...visible.map((p) => p.bounds.top + p.bounds.height)) : dataHeight;
|
|
29469
29727
|
this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM + Math.max(0, dataHeight - paneBottom);
|
|
29728
|
+
this.mountContainer?.style.setProperty("--vela-bottom-gutter", `${TIME_AXIS_H + Math.max(0, dataHeight - paneBottom)}px`);
|
|
29470
29729
|
this.scrollBtnRightPx = this.rightAxisW + SCROLL_BTN_RIGHT_INSET;
|
|
29471
29730
|
if (this.scrollButton) {
|
|
29472
29731
|
this.scrollButton.style.bottom = `${this.scrollBtnBottomPx}px`;
|
|
@@ -29559,30 +29818,33 @@ var NativeRenderer = class {
|
|
|
29559
29818
|
if (w <= 0 || h <= 0) return;
|
|
29560
29819
|
const dpr = window.devicePixelRatio || 1;
|
|
29561
29820
|
this.plot.style.left = `${this.toolbarGutter}px`;
|
|
29562
|
-
const
|
|
29563
|
-
|
|
29564
|
-
|
|
29565
|
-
|
|
29566
|
-
|
|
29567
|
-
|
|
29568
|
-
|
|
29569
|
-
|
|
29570
|
-
|
|
29571
|
-
|
|
29572
|
-
|
|
29573
|
-
|
|
29574
|
-
|
|
29575
|
-
|
|
29576
|
-
|
|
29577
|
-
|
|
29578
|
-
this.
|
|
29579
|
-
this.
|
|
29580
|
-
this.
|
|
29581
|
-
|
|
29821
|
+
const rect = this.plot.getBoundingClientRect();
|
|
29822
|
+
let bw = Math.max(1, Math.round(rect.width * dpr));
|
|
29823
|
+
let bh = Math.max(1, Math.round(rect.height * dpr));
|
|
29824
|
+
const dev = this.plotDeviceSize;
|
|
29825
|
+
if (dev && Math.abs(dev.width - rect.width * dpr) <= 1 && Math.abs(dev.height - rect.height * dpr) <= 1) {
|
|
29826
|
+
bw = Math.max(1, dev.width);
|
|
29827
|
+
bh = Math.max(1, dev.height);
|
|
29828
|
+
}
|
|
29829
|
+
const pw = bw / dpr;
|
|
29830
|
+
const ph = bh / dpr;
|
|
29831
|
+
const size = (canvas) => {
|
|
29832
|
+
canvas.width = bw;
|
|
29833
|
+
canvas.height = bh;
|
|
29834
|
+
canvas.style.width = `${pw}px`;
|
|
29835
|
+
canvas.style.height = `${ph}px`;
|
|
29836
|
+
};
|
|
29837
|
+
size(this.dataCanvas);
|
|
29838
|
+
size(this.backdropCanvas);
|
|
29839
|
+
size(this.volumeCanvas);
|
|
29840
|
+
for (const l of this.extLayers) size(l.canvas);
|
|
29841
|
+
size(this.vpvrCanvas);
|
|
29842
|
+
size(this.chromeCanvas);
|
|
29843
|
+
size(this.drawingsCanvas);
|
|
29844
|
+
size(this.cursorCanvas);
|
|
29582
29845
|
this.coords.setSize(Math.max(1, pw - this.rightAxisW), Math.max(1, ph - TIME_AXIS_H), dpr);
|
|
29583
29846
|
this.scene.crosshair = null;
|
|
29584
29847
|
this.layoutPanes();
|
|
29585
|
-
this.repositionTables();
|
|
29586
29848
|
this.userDrawings?.onResize();
|
|
29587
29849
|
if (!this.didInitialFit && this.coords.barCount > 0) {
|
|
29588
29850
|
this.fitContent();
|