@luxalgo/vela 0.6.9 → 0.6.11
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-BBf-jc6W.d.ts} +49 -1
- package/dist/{DataProvider-DExJrfut.d.ts → DataProvider-p0TEyhlX.d.cts} +49 -1
- package/dist/{chunk-6WDDVMBJ.js → chunk-73PEA4MU.js} +707 -353
- package/dist/{chunk-62SVGONC.js → chunk-G77Y7LK2.js} +2 -2
- package/dist/{chunk-STHSKXOR.js → chunk-IO3NYSQV.js} +650 -157
- package/dist/{chunk-WFSZBX3R.js → chunk-KG4YT3TI.js} +3 -1
- package/dist/{chunk-FCVIG7JG.js → chunk-MHY7MVXH.js} +5 -1
- package/dist/{chunk-EQCHJZOT.js → chunk-MTLJKZDZ.js} +10 -2
- package/dist/{contributions-D7PVZO2i.d.ts → contributions-Bbe2R-mQ.d.ts} +20 -6
- package/dist/{contributions-C1U2Krwg.d.cts → contributions-CO01zWve.d.cts} +20 -6
- package/dist/index.cjs +719 -350
- package/dist/index.d.cts +37 -10
- package/dist/index.d.ts +37 -10
- package/dist/index.js +5 -5
- package/dist/{options-FM0peknS.d.ts → options-yp7sA96q.d.cts} +14 -7
- package/dist/{options-FM0peknS.d.cts → options-yp7sA96q.d.ts} +14 -7
- package/dist/{plugin-DfVqBz9p.d.cts → plugin-CkkH8QnX.d.cts} +3 -3
- package/dist/{plugin-7bkF32Rk.d.ts → plugin-DwxjM3Ni.d.ts} +3 -3
- package/dist/plugin.cjs +12 -1
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +3 -3
- 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-DOPiT6I6.d.cts → statusline-CHPDuKNp.d.ts} +13 -5
- package/dist/{statusline-zdF4eZLr.d.ts → statusline-DTHZUFqK.d.cts} +13 -5
- 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 +719 -350
- package/dist/vela.global.min.js +51 -49
- package/dist/widget.cjs +1249 -388
- package/dist/widget.d.cts +17 -6
- package/dist/widget.d.ts +17 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +1249 -388
- package/dist/workspace.d.cts +103 -10
- package/dist/workspace.d.ts +103 -10
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
- /package/dist/{chunk-AOGZBKUE.js → chunk-H26BEHF4.js} +0 -0
package/dist/vela.global.js
CHANGED
|
@@ -130,6 +130,12 @@ var Vela = (function (exports) {
|
|
|
130
130
|
get visible() {
|
|
131
131
|
return this.visibleState;
|
|
132
132
|
}
|
|
133
|
+
inputValues() {
|
|
134
|
+
return this.controller.inputValuesOf(this.id);
|
|
135
|
+
}
|
|
136
|
+
propValues() {
|
|
137
|
+
return this.controller.propValuesOf(this.id);
|
|
138
|
+
}
|
|
133
139
|
setInput(key, value) {
|
|
134
140
|
this.controller.applyInputs(this.id, { [key]: value });
|
|
135
141
|
}
|
|
@@ -528,6 +534,8 @@ var Vela = (function (exports) {
|
|
|
528
534
|
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
535
|
registerIcon("collapse", S('<path d="M3 8h10"/>'));
|
|
530
536
|
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"/>'));
|
|
537
|
+
registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
|
|
538
|
+
registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
|
|
531
539
|
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
540
|
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
541
|
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 +543,7 @@ var Vela = (function (exports) {
|
|
|
535
543
|
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
544
|
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
545
|
registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
|
|
538
|
-
registerIcon("reset", S('<
|
|
546
|
+
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
547
|
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
548
|
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
549
|
registerIcon("cursor", svg24('<path d="M5 3l6 16 2-6 6-2z"/>', 'fill="currentColor" stroke="none"'));
|
|
@@ -6906,6 +6914,23 @@ var Vela = (function (exports) {
|
|
|
6906
6914
|
return 36e5;
|
|
6907
6915
|
}
|
|
6908
6916
|
|
|
6917
|
+
// src/data/symbol-groups.ts
|
|
6918
|
+
function isGroupRow(d) {
|
|
6919
|
+
return d.group != null && d.ticker === d.group;
|
|
6920
|
+
}
|
|
6921
|
+
function groupKeyOf(d) {
|
|
6922
|
+
return `${(d.prefix ?? d.provider ?? "").toLowerCase()}:${(d.group ?? "").toUpperCase()}`;
|
|
6923
|
+
}
|
|
6924
|
+
function groupMembers(pool, groupRow) {
|
|
6925
|
+
const key = groupKeyOf(groupRow);
|
|
6926
|
+
return pool.filter((s) => s.group != null && !isGroupRow(s) && groupKeyOf(s) === key);
|
|
6927
|
+
}
|
|
6928
|
+
function defaultMemberOf(pool, groupRow) {
|
|
6929
|
+
const members = groupMembers(pool, groupRow);
|
|
6930
|
+
const defaults2 = members.filter((m) => m.default);
|
|
6931
|
+
return (defaults2.length === 1 ? defaults2[0] : members[0]) ?? null;
|
|
6932
|
+
}
|
|
6933
|
+
|
|
6909
6934
|
// src/data/ProviderRegistry.ts
|
|
6910
6935
|
var normName = (n) => n.trim().toLowerCase();
|
|
6911
6936
|
var normTicker = (t) => t.trim().toUpperCase();
|
|
@@ -6995,17 +7020,24 @@ var Vela = (function (exports) {
|
|
|
6995
7020
|
resolve(raw, opts = {}) {
|
|
6996
7021
|
const { provider, ticker } = this.parse(raw);
|
|
6997
7022
|
if (provider) {
|
|
6998
|
-
if (this.entries.has(provider))
|
|
7023
|
+
if (this.entries.has(provider)) {
|
|
7024
|
+
const d = this.entries.get(provider).byTicker?.get(normTicker(ticker));
|
|
7025
|
+
return { provider, ticker: d != null && isGroupRow(d) ? this.loadableTicker(provider, d) : ticker };
|
|
7026
|
+
}
|
|
6999
7027
|
const key = `${provider}:${normTicker(ticker)}`;
|
|
7000
7028
|
for (const name of this.candidateOrder(opts.default)) {
|
|
7001
7029
|
const d = this.entries.get(name).prefixIndex?.get(key);
|
|
7002
|
-
if (d) return { provider: name, ticker:
|
|
7030
|
+
if (d) return { provider: name, ticker: this.loadableTicker(name, d) };
|
|
7003
7031
|
}
|
|
7004
7032
|
return null;
|
|
7005
7033
|
}
|
|
7006
7034
|
const norm = normTicker(ticker);
|
|
7007
7035
|
for (const name of this.candidateOrder(opts.default)) {
|
|
7008
|
-
|
|
7036
|
+
const e = this.entries.get(name);
|
|
7037
|
+
if (e.index?.has(norm)) {
|
|
7038
|
+
const d = e.byTicker?.get(norm);
|
|
7039
|
+
return { provider: name, ticker: d != null && isGroupRow(d) ? this.loadableTicker(name, d) : ticker };
|
|
7040
|
+
}
|
|
7009
7041
|
}
|
|
7010
7042
|
if (opts.lenient && this.entries.size === 1) {
|
|
7011
7043
|
const only = this.names()[0];
|
|
@@ -7014,6 +7046,13 @@ var Vela = (function (exports) {
|
|
|
7014
7046
|
}
|
|
7015
7047
|
return null;
|
|
7016
7048
|
}
|
|
7049
|
+
/** What a resolved descriptor LOADS: itself — unless it is a GROUP row (listed, never
|
|
7050
|
+
* served), which loads its default member (single `default`, else first listed). A
|
|
7051
|
+
* memberless group keeps its own ticker and fails downstream like any unknown symbol. */
|
|
7052
|
+
loadableTicker(name, d) {
|
|
7053
|
+
if (!isGroupRow(d)) return d.ticker;
|
|
7054
|
+
return defaultMemberOf(this.entries.get(name)?.descriptors ?? [], d)?.ticker ?? d.ticker;
|
|
7055
|
+
}
|
|
7017
7056
|
/**
|
|
7018
7057
|
* Resolve now if possible, else resolve later — re-attempting after each provider
|
|
7019
7058
|
* settles. The promise stays pending until some registered provider can serve the
|
|
@@ -7239,6 +7278,7 @@ var Vela = (function (exports) {
|
|
|
7239
7278
|
var PREVIEW_BARS = 300;
|
|
7240
7279
|
var SINGLE_LOAD_BARS = 5e3;
|
|
7241
7280
|
var CHUNK_BARS = 1e4;
|
|
7281
|
+
var FIRST_PAINT_BARS = 100;
|
|
7242
7282
|
var GAP_FACTOR = 1.5;
|
|
7243
7283
|
var HEAL_COOLDOWN_MS = 5e3;
|
|
7244
7284
|
var EngineOrchestrator = class _EngineOrchestrator {
|
|
@@ -7302,6 +7342,10 @@ var Vela = (function (exports) {
|
|
|
7302
7342
|
/** Invalidates detached async work (backfill loops, in-flight loads, gap heals):
|
|
7303
7343
|
* bumped by init(), setMarket() and destroy(). */
|
|
7304
7344
|
this.generation = 0;
|
|
7345
|
+
/** Aborts the in-flight PROGRESSIVE load's source polling on supersession — an
|
|
7346
|
+
* abandoned stream left polling to its own budget starves the browser's per-host
|
|
7347
|
+
* connection pool, and the NEXT symbol's very first fetch with it (measured). */
|
|
7348
|
+
this.progressiveAbort = null;
|
|
7305
7349
|
/** Awaiters racing a superseded load (setMarket callers) — released on every bump so they never hang. */
|
|
7306
7350
|
this.supersedeWaiters = [];
|
|
7307
7351
|
/** `history:complete` fired for the CURRENT load. Each market load re-arms the cycle
|
|
@@ -7444,6 +7488,8 @@ var Vela = (function (exports) {
|
|
|
7444
7488
|
* superseded setMarket awaiters so their promises resolve instead of hanging. */
|
|
7445
7489
|
bumpGeneration() {
|
|
7446
7490
|
const gen = ++this.generation;
|
|
7491
|
+
this.progressiveAbort?.abort();
|
|
7492
|
+
this.progressiveAbort = null;
|
|
7447
7493
|
for (const w of this.supersedeWaiters.splice(0)) w();
|
|
7448
7494
|
return gen;
|
|
7449
7495
|
}
|
|
@@ -7497,7 +7543,48 @@ var Vela = (function (exports) {
|
|
|
7497
7543
|
const requested = market.bars ?? 500;
|
|
7498
7544
|
const initialRange = market.visibleRange;
|
|
7499
7545
|
const deep = !market.data?.length && initialRange == null && requested > SINGLE_LOAD_BARS;
|
|
7500
|
-
|
|
7546
|
+
let progressiveServed = false;
|
|
7547
|
+
if (!market.data?.length && initialRange == null && this.feed.loadProgressive) {
|
|
7548
|
+
let painted = false;
|
|
7549
|
+
const paint = (bars, final) => {
|
|
7550
|
+
if (this.generation !== gen || !final && bars.length === 0) return;
|
|
7551
|
+
if (!painted && !final && bars.length < Math.min(requested, FIRST_PAINT_BARS)) return;
|
|
7552
|
+
this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
|
|
7553
|
+
if (!painted && bars.length > 0) {
|
|
7554
|
+
painted = true;
|
|
7555
|
+
if (opts.firstLoad) this.activateBarLayers();
|
|
7556
|
+
if (!final) this.historyState = "backfill";
|
|
7557
|
+
}
|
|
7558
|
+
};
|
|
7559
|
+
const abort = new AbortController();
|
|
7560
|
+
this.progressiveAbort = abort;
|
|
7561
|
+
progressiveServed = await new Promise((firstPaint) => {
|
|
7562
|
+
let signaled = false;
|
|
7563
|
+
const signal = (served) => {
|
|
7564
|
+
if (!signaled) {
|
|
7565
|
+
signaled = true;
|
|
7566
|
+
firstPaint(served);
|
|
7567
|
+
}
|
|
7568
|
+
};
|
|
7569
|
+
abort.signal.addEventListener("abort", () => signal(true), { once: true });
|
|
7570
|
+
this.feed.loadProgressive(market, (bars) => {
|
|
7571
|
+
paint(bars, false);
|
|
7572
|
+
if (painted) signal(true);
|
|
7573
|
+
}, { signal: abort.signal }).then((full) => {
|
|
7574
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
7575
|
+
if (full == null) return signal(false);
|
|
7576
|
+
if (this.generation !== gen) return signal(true);
|
|
7577
|
+
paint(full, true);
|
|
7578
|
+
this.completeHistory(full.length >= requested ? "depth" : "genesis");
|
|
7579
|
+
signal(true);
|
|
7580
|
+
}).catch(() => {
|
|
7581
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
7582
|
+
if (this.generation === gen) this.completeHistory("aborted");
|
|
7583
|
+
signal(true);
|
|
7584
|
+
});
|
|
7585
|
+
});
|
|
7586
|
+
}
|
|
7587
|
+
if (progressiveServed) ; else if (deep && this.feed.loadRange) {
|
|
7501
7588
|
const head = await this.feed.load({ ...market, bars: Math.min(requested, CHUNK_BARS) });
|
|
7502
7589
|
if (this.generation !== gen) return;
|
|
7503
7590
|
this.setBarSeries(head);
|
|
@@ -8092,6 +8179,15 @@ var Vela = (function (exports) {
|
|
|
8092
8179
|
record.pendingCause = "inputs";
|
|
8093
8180
|
if (record.session) record.session.update(record.inputValues);
|
|
8094
8181
|
else if (record.native && !record.hidden) record.native.instance.setInputs(record.inputValues);
|
|
8182
|
+
this.events.emit("indicator:inputs", { id });
|
|
8183
|
+
}
|
|
8184
|
+
/** IndicatorController: the CURRENT stored input values (defaults merged with edits). */
|
|
8185
|
+
inputValuesOf(id) {
|
|
8186
|
+
return { ...this.registry.get(id)?.inputValues };
|
|
8187
|
+
}
|
|
8188
|
+
/** IndicatorController: the CURRENT declaration-prop overrides. */
|
|
8189
|
+
propValuesOf(id) {
|
|
8190
|
+
return { ...this.registry.get(id)?.propValues };
|
|
8095
8191
|
}
|
|
8096
8192
|
/** IndicatorController: re-run an indicator with merged declaration-prop overrides.
|
|
8097
8193
|
* Same lifecycle as {@link applyInputs} — a prop change replays the whole script.
|
|
@@ -8105,6 +8201,7 @@ var Vela = (function (exports) {
|
|
|
8105
8201
|
if (!record.hidden) this.setLoading(record, true);
|
|
8106
8202
|
record.pendingCause = "inputs";
|
|
8107
8203
|
record.session.update(record.inputValues, record.propValues);
|
|
8204
|
+
this.events.emit("indicator:inputs", { id });
|
|
8108
8205
|
}
|
|
8109
8206
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
8110
8207
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
@@ -9169,6 +9266,28 @@ var Vela = (function (exports) {
|
|
|
9169
9266
|
this.store.merge(key, dropForming(bars));
|
|
9170
9267
|
return bars;
|
|
9171
9268
|
}
|
|
9269
|
+
/**
|
|
9270
|
+
* Progressive twin of {@link load}: a COLD load streams through the inner feed's
|
|
9271
|
+
* progressive path — batches forwarded verbatim, the FINAL answer cached exactly as
|
|
9272
|
+
* `load` caches — while a cache-covered load answers once through `load` itself (a
|
|
9273
|
+
* warm chart has nothing to stream). Falls back to `load` wholesale when the inner
|
|
9274
|
+
* feed lacks the capability, so callers may prefer this method unconditionally.
|
|
9275
|
+
*/
|
|
9276
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9277
|
+
if (cfg.data && cfg.data.length > 0) return this.inner.load(cfg);
|
|
9278
|
+
if (!this.inner.loadProgressive) return null;
|
|
9279
|
+
const symbol = cfg.symbol ?? "TEST";
|
|
9280
|
+
const key = cacheKey(symbol, cfg.timeframe ?? "60", cfg.session);
|
|
9281
|
+
const cached = this.store.get(key);
|
|
9282
|
+
const n = cfg.bars ?? 500;
|
|
9283
|
+
const lastCached = cached?.[cached.length - 1];
|
|
9284
|
+
if (cached && lastCached && cached.length >= n - 1 && this.inner.loadRange) return this.load(cfg);
|
|
9285
|
+
this.store.retainSymbol(symbol);
|
|
9286
|
+
const bars = await this.inner.loadProgressive(cfg, onBatch, opts);
|
|
9287
|
+
if (bars == null) return null;
|
|
9288
|
+
this.store.merge(key, dropForming(bars));
|
|
9289
|
+
return bars;
|
|
9290
|
+
}
|
|
9172
9291
|
/**
|
|
9173
9292
|
* Cache-backed ranged fetch — the gateway engines use for secondary series
|
|
9174
9293
|
* (`request.security` HTF/LTF/cross-symbol) and the orchestrator's backward
|
|
@@ -9414,6 +9533,17 @@ var Vela = (function (exports) {
|
|
|
9414
9533
|
this.prefetchSymbolInfo(resolved);
|
|
9415
9534
|
return this.cache.load(canonical(cfg, resolved));
|
|
9416
9535
|
}
|
|
9536
|
+
/** Progressive twin of {@link load} — same resolution, the cache streams the batches. */
|
|
9537
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9538
|
+
if (cfg.data && cfg.data.length > 0) {
|
|
9539
|
+
this.liveBars = cfg.data.map((b) => ({ ...b }));
|
|
9540
|
+
return cfg.data;
|
|
9541
|
+
}
|
|
9542
|
+
const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
|
|
9543
|
+
this.primaryProvider = resolved.provider;
|
|
9544
|
+
this.prefetchSymbolInfo(resolved);
|
|
9545
|
+
return this.cache.loadProgressive(canonical(cfg, resolved), onBatch, opts);
|
|
9546
|
+
}
|
|
9417
9547
|
/**
|
|
9418
9548
|
* Synchronous per-symbol metadata for engines (Pine `syminfo.*`), served from the cache
|
|
9419
9549
|
* warmed by load(). Undefined until the prefetch lands (the engine then synthesizes a
|
|
@@ -9501,6 +9631,18 @@ var Vela = (function (exports) {
|
|
|
9501
9631
|
if (!provider) return Promise.resolve([]);
|
|
9502
9632
|
return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session });
|
|
9503
9633
|
}
|
|
9634
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9635
|
+
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
9636
|
+
const provider = this.registry.get(name ?? "");
|
|
9637
|
+
if (!provider) return [];
|
|
9638
|
+
if (!provider.getBarsProgressive) return null;
|
|
9639
|
+
try {
|
|
9640
|
+
return await provider.getBarsProgressive(ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session }, onBatch, opts);
|
|
9641
|
+
} catch (e) {
|
|
9642
|
+
console.warn(`[vela] progressive fetch failed for ${ticker} ${cfg.timeframe ?? "60"} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
9643
|
+
return [];
|
|
9644
|
+
}
|
|
9645
|
+
}
|
|
9504
9646
|
loadRange(cfg, range) {
|
|
9505
9647
|
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
9506
9648
|
const provider = this.registry.get(name ?? "");
|
|
@@ -17014,6 +17156,10 @@ ${STATIC_DECLS}
|
|
|
17014
17156
|
get open() {
|
|
17015
17157
|
return this.pop?.open ?? false;
|
|
17016
17158
|
}
|
|
17159
|
+
/** Close the deployed panel, if any. The bubble itself stays. */
|
|
17160
|
+
hidePanel() {
|
|
17161
|
+
this.pop?.hide();
|
|
17162
|
+
}
|
|
17017
17163
|
destroy() {
|
|
17018
17164
|
this.pop?.destroy();
|
|
17019
17165
|
this.pop = null;
|
|
@@ -17094,6 +17240,14 @@ ${STATIC_DECLS}
|
|
|
17094
17240
|
const conds = Array.isArray(when) ? when : [when];
|
|
17095
17241
|
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
|
|
17096
17242
|
}
|
|
17243
|
+
function inputDeltas(schema, values) {
|
|
17244
|
+
const out = {};
|
|
17245
|
+
for (const s of schema) {
|
|
17246
|
+
const v = values[s.key];
|
|
17247
|
+
if (v !== void 0 && JSON.stringify(v) !== JSON.stringify(s.defval)) out[s.key] = v;
|
|
17248
|
+
}
|
|
17249
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
17250
|
+
}
|
|
17097
17251
|
|
|
17098
17252
|
// src/ui/components/select/controller.ts
|
|
17099
17253
|
function selectController(opts) {
|
|
@@ -21110,6 +21264,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21110
21264
|
var FOLD_SVG = iconAt("chevron-up", LEGEND_ICON_PX2);
|
|
21111
21265
|
var UNFOLD_SVG = iconAt("chevron-down", LEGEND_ICON_PX2);
|
|
21112
21266
|
var OVERVIEW_SVG = iconAt("objects", LEGEND_ICON_PX2);
|
|
21267
|
+
function legendCalloutsDisplay(open2, hasCallouts) {
|
|
21268
|
+
return !open2 && hasCallouts ? "inline-flex" : "none";
|
|
21269
|
+
}
|
|
21113
21270
|
var InputsUI = class {
|
|
21114
21271
|
constructor(container, theme, paneBoundsOf) {
|
|
21115
21272
|
this.container = container;
|
|
@@ -21313,7 +21470,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21313
21470
|
row.callouts = [];
|
|
21314
21471
|
row.calloutsEl.replaceChildren();
|
|
21315
21472
|
const views = this.legendCallouts?.(row.id) ?? [];
|
|
21316
|
-
row.calloutsEl.style.display = views.length > 0
|
|
21473
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(row.highlighted, views.length > 0);
|
|
21317
21474
|
for (const view of views) {
|
|
21318
21475
|
const bubble = new CalloutBubble({
|
|
21319
21476
|
icon: view.icon,
|
|
@@ -21797,11 +21954,11 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21797
21954
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
21798
21955
|
if (open2) {
|
|
21799
21956
|
row.el.appendChild(row.statusEl);
|
|
21800
|
-
row.
|
|
21957
|
+
for (const bubble of row.callouts) bubble.hidePanel();
|
|
21801
21958
|
} else {
|
|
21802
21959
|
row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
21803
|
-
row.el.insertBefore(row.calloutsEl, row.statusEl);
|
|
21804
21960
|
}
|
|
21961
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(open2, row.callouts.length > 0);
|
|
21805
21962
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
21806
21963
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
21807
21964
|
if (child === row.extrasEl) {
|
|
@@ -21891,7 +22048,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21891
22048
|
};
|
|
21892
22049
|
var STYLE_ID = "vela-pane-controls";
|
|
21893
22050
|
var ICON_PX = 12;
|
|
21894
|
-
var CLUSTER_PILL = "rgba(0,0,0,0.
|
|
22051
|
+
var CLUSTER_PILL = "rgba(0,0,0,0.65)";
|
|
21895
22052
|
function ensureStyles() {
|
|
21896
22053
|
if (typeof document === "undefined" || document.getElementById(STYLE_ID)) return;
|
|
21897
22054
|
const st = document.createElement("style");
|
|
@@ -21911,8 +22068,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21911
22068
|
this.deps = deps;
|
|
21912
22069
|
this.clusters = /* @__PURE__ */ new Map();
|
|
21913
22070
|
this.hoverPaneId = null;
|
|
22071
|
+
/** Mobile: hover clusters are meaningless without a cursor — suppressed; a
|
|
22072
|
+
* collapsed pane's standalone expand chip stays (the only way back up). */
|
|
22073
|
+
this.suspended = false;
|
|
21914
22074
|
/** Reveal the cluster for the pane under the cursor, resolved from the pointer's y in the plot. */
|
|
21915
22075
|
this.onPlotMove = (e) => {
|
|
22076
|
+
if (this.suspended) return;
|
|
21916
22077
|
const rect = this.plot.getBoundingClientRect();
|
|
21917
22078
|
const y = e.clientY - rect.top;
|
|
21918
22079
|
let hit = null;
|
|
@@ -21984,7 +22145,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21984
22145
|
}
|
|
21985
22146
|
if (p.count > 1) {
|
|
21986
22147
|
cluster.appendChild(
|
|
21987
|
-
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
22148
|
+
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
22149
|
+
role: "maximize",
|
|
22150
|
+
// Same inverse-chip treatment as the collapsed pane's expand toggle: the
|
|
22151
|
+
// maximized state must read as an active state, not just a swapped glyph.
|
|
22152
|
+
selected: p.maximized
|
|
22153
|
+
})
|
|
21988
22154
|
);
|
|
21989
22155
|
}
|
|
21990
22156
|
}
|
|
@@ -22021,17 +22187,18 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22021
22187
|
}
|
|
22022
22188
|
const hovered = id === this.hoverPaneId;
|
|
22023
22189
|
const hasButtons = cluster.children.length > 0;
|
|
22024
|
-
const
|
|
22190
|
+
const stateChipRole = p.collapsed ? "collapse" : !this.suspended && p.maximized ? "maximize" : null;
|
|
22191
|
+
const visible = hasButtons && (hovered || stateChipRole != null) && p.height > 8;
|
|
22025
22192
|
cluster.style.right = `${rightPx}px`;
|
|
22026
22193
|
cluster.style.top = p.collapsed ? `${p.top + Math.max(1, Math.round((p.height - 24) / 2))}px` : `${p.top + 4}px`;
|
|
22027
22194
|
cluster.style.display = visible ? "flex" : "none";
|
|
22028
22195
|
if (!visible) continue;
|
|
22029
|
-
const
|
|
22030
|
-
cluster.style.background =
|
|
22196
|
+
const soloChip = stateChipRole != null && !hovered;
|
|
22197
|
+
cluster.style.background = soloChip ? "transparent" : CLUSTER_PILL;
|
|
22031
22198
|
for (const child of cluster.children) {
|
|
22032
22199
|
const btn2 = child;
|
|
22033
22200
|
btn2.style.display = "inline-flex";
|
|
22034
|
-
btn2.style.visibility =
|
|
22201
|
+
btn2.style.visibility = soloChip && btn2.dataset.role !== stateChipRole ? "hidden" : "visible";
|
|
22035
22202
|
}
|
|
22036
22203
|
}
|
|
22037
22204
|
}
|
|
@@ -22041,6 +22208,14 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22041
22208
|
this.hoverPaneId = paneId;
|
|
22042
22209
|
this.reposition();
|
|
22043
22210
|
}
|
|
22211
|
+
/** Mobile suppression: no hover clusters (touch has no cursor; the shell's own
|
|
22212
|
+
* chrome covers maximize), while collapsed panes keep their expand chips. */
|
|
22213
|
+
setSuspended(on) {
|
|
22214
|
+
if (on === this.suspended) return;
|
|
22215
|
+
this.suspended = on;
|
|
22216
|
+
if (on) this.hoverPaneId = null;
|
|
22217
|
+
this.reposition();
|
|
22218
|
+
}
|
|
22044
22219
|
destroy() {
|
|
22045
22220
|
this.plot.removeEventListener("pointermove", this.onPlotMove);
|
|
22046
22221
|
this.plot.removeEventListener("pointerleave", this.onPlotLeave);
|
|
@@ -22191,160 +22366,6 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22191
22366
|
}
|
|
22192
22367
|
};
|
|
22193
22368
|
|
|
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
22369
|
// src/renderers/native/capabilities.ts
|
|
22349
22370
|
var NATIVE_CAPABILITIES = {
|
|
22350
22371
|
panes: true,
|
|
@@ -22365,7 +22386,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22365
22386
|
drawingDepth: true,
|
|
22366
22387
|
// drawings share the series' z space (backend-composited interleave layers)
|
|
22367
22388
|
tables: true,
|
|
22368
|
-
//
|
|
22389
|
+
// canvas-painted into the owning indicator's interleave slice
|
|
22369
22390
|
trades: true,
|
|
22370
22391
|
// strategy order-fill markers (arrows + labels + fill-price ticks)
|
|
22371
22392
|
inputsUI: true
|
|
@@ -22396,6 +22417,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22396
22417
|
if (spacing < CANDLE_BODY_MIN_SPACING) return "wick";
|
|
22397
22418
|
return "full";
|
|
22398
22419
|
}
|
|
22420
|
+
function snapY(yCss, dpr) {
|
|
22421
|
+
return Math.round(yCss * dpr) / dpr;
|
|
22422
|
+
}
|
|
22399
22423
|
function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
22400
22424
|
const wickDev = Math.max(1, Math.round(wickWidth(spacing) * dpr));
|
|
22401
22425
|
const wickLeftDev = Math.round(xCss * dpr - wickDev / 2);
|
|
@@ -23202,11 +23226,9 @@ void main() {
|
|
|
23202
23226
|
};
|
|
23203
23227
|
b.alpha = this.modelAlpha;
|
|
23204
23228
|
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
23229
|
if (isPrice) {
|
|
23207
23230
|
for (const m of scene.indicators.values()) {
|
|
23208
23231
|
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
23232
|
}
|
|
23211
23233
|
}
|
|
23212
23234
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
@@ -23222,6 +23244,7 @@ void main() {
|
|
|
23222
23244
|
b.alpha = this.modelAlpha;
|
|
23223
23245
|
const off = scene.offsetOf(m.id);
|
|
23224
23246
|
const mp = effPane(m);
|
|
23247
|
+
for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, mp, coords, i0, i1, off);
|
|
23225
23248
|
for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
23226
23249
|
}
|
|
23227
23250
|
if (drawCandles && !candleDrawn) {
|
|
@@ -23229,14 +23252,18 @@ void main() {
|
|
|
23229
23252
|
b.alpha = this.candleStructureAlpha;
|
|
23230
23253
|
this.emitPriceSeries(b, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
23231
23254
|
}
|
|
23232
|
-
drawSlicesUpTo(Infinity);
|
|
23233
23255
|
b.alpha = this.modelAlpha;
|
|
23234
23256
|
if (isPrice) {
|
|
23257
|
+
for (const m of scene.indicators.values()) {
|
|
23258
|
+
const off = scene.offsetOf(m.id);
|
|
23259
|
+
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, off);
|
|
23260
|
+
}
|
|
23235
23261
|
for (const m of scene.indicators.values()) {
|
|
23236
23262
|
const off = scene.offsetOf(m.id);
|
|
23237
23263
|
for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
|
|
23238
23264
|
}
|
|
23239
23265
|
}
|
|
23266
|
+
drawSlicesUpTo(Infinity);
|
|
23240
23267
|
for (const m of models) {
|
|
23241
23268
|
const mp = effPane(m);
|
|
23242
23269
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -23631,12 +23658,12 @@ void main() {
|
|
|
23631
23658
|
if (drawBody) {
|
|
23632
23659
|
const oY = coords.priceToY(bar.open, pane.scale, pane.bounds);
|
|
23633
23660
|
const cY = coords.priceToY(bar.close, pane.scale, pane.bounds);
|
|
23634
|
-
bodyTop = Math.min(oY, cY);
|
|
23635
|
-
bodyH = Math.max(1, Math.
|
|
23661
|
+
bodyTop = snapY(Math.min(oY, cY), coords.dpr);
|
|
23662
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - bodyTop);
|
|
23636
23663
|
}
|
|
23637
23664
|
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);
|
|
23665
|
+
const hY = snapY(coords.priceToY(bar.high, pane.scale, pane.bounds), coords.dpr);
|
|
23666
|
+
const lY = snapY(coords.priceToY(bar.low, pane.scale, pane.bounds), coords.dpr);
|
|
23640
23667
|
b.alpha = this.candleStructureAlpha;
|
|
23641
23668
|
const wCol = parseColor((isUp ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : bodyColorStr));
|
|
23642
23669
|
if (drawBody) {
|
|
@@ -24193,6 +24220,7 @@ void main() {
|
|
|
24193
24220
|
var DOUBLE_TAP_SLOP = 30;
|
|
24194
24221
|
var TIME_SCALE_K = 4e-3;
|
|
24195
24222
|
var WHEEL_ZOOM_K = 4e-3;
|
|
24223
|
+
var WHEEL_PRICE_DRAG_PX = 0.25;
|
|
24196
24224
|
function wheelZoomAnchor(coords, cursorX, rightEdge) {
|
|
24197
24225
|
if (rightEdge) return { logical: coords.rightEdgeLogical, x: coords.width };
|
|
24198
24226
|
return { logical: coords.xToLogical(cursorX), x: cursorX };
|
|
@@ -24323,7 +24351,7 @@ void main() {
|
|
|
24323
24351
|
this.capture(e.pointerId);
|
|
24324
24352
|
return;
|
|
24325
24353
|
}
|
|
24326
|
-
if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y)) {
|
|
24354
|
+
if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
24327
24355
|
this.region = "drawing";
|
|
24328
24356
|
this.capture(e.pointerId);
|
|
24329
24357
|
return;
|
|
@@ -24437,7 +24465,7 @@ void main() {
|
|
|
24437
24465
|
const wasTouch = e.pointerType === "touch";
|
|
24438
24466
|
const tapRelease = this.dragging && !this.moved && (!wasTouch || Math.hypot(x - this.startX, y - this.startY) <= TOUCH_TAP_SLOP);
|
|
24439
24467
|
if (this.dragging && this.region === "drawing") {
|
|
24440
|
-
this.deps.drawingsPointerUp?.(x, y);
|
|
24468
|
+
this.deps.drawingsPointerUp?.(x, y, this.snapMode(e));
|
|
24441
24469
|
} else if (tapRelease && this.region === "data") {
|
|
24442
24470
|
this.deps.onClick(x, y);
|
|
24443
24471
|
} else if (this.dragging && this.region === "data") {
|
|
@@ -24458,7 +24486,7 @@ void main() {
|
|
|
24458
24486
|
if (e.pointerType === "touch") this.touches.delete(e.pointerId);
|
|
24459
24487
|
this.cancelLongPress();
|
|
24460
24488
|
if (!this.dragging) return;
|
|
24461
|
-
if (this.region === "drawing" && !Number.isNaN(this.cursorX)) this.deps.drawingsPointerUp?.(this.cursorX, this.cursorY);
|
|
24489
|
+
if (this.region === "drawing" && !Number.isNaN(this.cursorX)) this.deps.drawingsPointerUp?.(this.cursorX, this.cursorY, this.snapMode(e));
|
|
24462
24490
|
if (this.region === "crosshair" || e.pointerType === "touch") this.deps.onPointerMove(null, null);
|
|
24463
24491
|
this.endGesture(e);
|
|
24464
24492
|
};
|
|
@@ -24474,6 +24502,12 @@ void main() {
|
|
|
24474
24502
|
this.onWheel = (e) => {
|
|
24475
24503
|
e.preventDefault();
|
|
24476
24504
|
this.deps.drawingsClearTransient?.();
|
|
24505
|
+
const { x, y } = this.local(e);
|
|
24506
|
+
if (this.regionAt(x, y) === "price" && e.deltaY !== 0) {
|
|
24507
|
+
this.deps.beginPriceScale(x, y);
|
|
24508
|
+
this.deps.priceScaleBy(e.deltaY * WHEEL_PRICE_DRAG_PX);
|
|
24509
|
+
return;
|
|
24510
|
+
}
|
|
24477
24511
|
const coords = this.deps.getCoords();
|
|
24478
24512
|
const vp = coords.getViewport();
|
|
24479
24513
|
const pan = wheelPanDelta(e.deltaX, e.deltaY, e.shiftKey);
|
|
@@ -24481,9 +24515,8 @@ void main() {
|
|
|
24481
24515
|
this.deps.apply({ barSpacing: vp.barSpacing, rightOffset: wheelPanRightOffset(vp.rightOffset, pan, coords.pxPerBar()) });
|
|
24482
24516
|
return;
|
|
24483
24517
|
}
|
|
24484
|
-
const cursorX = this.local(e).x;
|
|
24485
24518
|
const rightEdge = this.rightEdgeZoom && !(e.ctrlKey || e.metaKey);
|
|
24486
|
-
const anchor = wheelZoomAnchor(coords,
|
|
24519
|
+
const anchor = wheelZoomAnchor(coords, x, rightEdge);
|
|
24487
24520
|
const target = clampBarSpacing(vp.barSpacing * Math.exp(-e.deltaY * WHEEL_ZOOM_K));
|
|
24488
24521
|
this.deps.zoomTo(target, anchor.logical, anchor.x);
|
|
24489
24522
|
};
|
|
@@ -24969,9 +25002,10 @@ void main() {
|
|
|
24969
25002
|
* so each indicator arrives behind the candles (and behind older indicators);
|
|
24970
25003
|
* `setIndicatorZ`/`bringToFront`/`sendToBack` change it. */
|
|
24971
25004
|
this.seriesZ = /* @__PURE__ */ new Map();
|
|
24972
|
-
/** Per-pane raster layers of
|
|
25005
|
+
/** Per-pane raster layers of drawings interleaved into the series stack (each
|
|
25006
|
+
* indicator's Pine drawings at its model's z, plus in-stack user drawings) — each a
|
|
24973
25007
|
* prepainted canvas the backend composites just before the series carrying `beforeZ`.
|
|
24974
|
-
* Rebuilt by the renderer per data frame
|
|
25008
|
+
* Rebuilt by the renderer per data frame. */
|
|
24975
25009
|
this.drawingSlices = /* @__PURE__ */ new Map();
|
|
24976
25010
|
/** Per-model index offset: the chart bar index of the model's `anchorTime` — its
|
|
24977
25011
|
* index-aligned payloads (dense series arrays, `bar_index` drawings) count from that
|
|
@@ -25206,11 +25240,9 @@ void main() {
|
|
|
25206
25240
|
};
|
|
25207
25241
|
ctx.globalAlpha = this.modelAlpha;
|
|
25208
25242
|
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
25243
|
if (isPrice) {
|
|
25211
25244
|
for (const m of scene.indicators.values()) {
|
|
25212
25245
|
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
25246
|
}
|
|
25215
25247
|
}
|
|
25216
25248
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
@@ -25234,6 +25266,7 @@ void main() {
|
|
|
25234
25266
|
ctx.globalAlpha = this.modelAlpha;
|
|
25235
25267
|
const off = scene.offsetOf(m.id);
|
|
25236
25268
|
const mp = effPane(m);
|
|
25269
|
+
for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, mp, coords, i0, i1, off);
|
|
25237
25270
|
for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
25238
25271
|
}
|
|
25239
25272
|
if (drawCandles && !candleDrawn) {
|
|
@@ -25241,14 +25274,18 @@ void main() {
|
|
|
25241
25274
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
25242
25275
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
25243
25276
|
}
|
|
25244
|
-
drawSlicesUpTo(Infinity);
|
|
25245
25277
|
if (isPrice) {
|
|
25246
25278
|
ctx.globalAlpha = this.modelAlpha;
|
|
25279
|
+
for (const m of scene.indicators.values()) {
|
|
25280
|
+
const off = scene.offsetOf(m.id);
|
|
25281
|
+
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, off);
|
|
25282
|
+
}
|
|
25247
25283
|
for (const m of scene.indicators.values()) {
|
|
25248
25284
|
const off = scene.offsetOf(m.id);
|
|
25249
25285
|
for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
|
|
25250
25286
|
}
|
|
25251
25287
|
}
|
|
25288
|
+
drawSlicesUpTo(Infinity);
|
|
25252
25289
|
ctx.globalAlpha = this.modelAlpha;
|
|
25253
25290
|
for (const m of models) {
|
|
25254
25291
|
const mp = effPane(m);
|
|
@@ -25492,13 +25529,13 @@ void main() {
|
|
|
25492
25529
|
if (drawBody) {
|
|
25493
25530
|
const oY = coords.priceToY(b.open, pane.scale, pane.bounds);
|
|
25494
25531
|
const cY = coords.priceToY(b.close, pane.scale, pane.bounds);
|
|
25495
|
-
top = Math.min(oY, cY);
|
|
25496
|
-
bodyH = Math.max(1, Math.
|
|
25532
|
+
top = snapY(Math.min(oY, cY), coords.dpr);
|
|
25533
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - top);
|
|
25497
25534
|
}
|
|
25498
25535
|
if (cs.wickVisible) {
|
|
25499
25536
|
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);
|
|
25537
|
+
const hY = snapY(coords.priceToY(b.high, pane.scale, pane.bounds), coords.dpr);
|
|
25538
|
+
const lY = snapY(coords.priceToY(b.low, pane.scale, pane.bounds), coords.dpr);
|
|
25502
25539
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
25503
25540
|
ctx.strokeStyle = wick;
|
|
25504
25541
|
ctx.lineWidth = g.wickW;
|
|
@@ -25945,9 +25982,31 @@ void main() {
|
|
|
25945
25982
|
|
|
25946
25983
|
// src/renderers/shared/DrawingSceneRenderer.ts
|
|
25947
25984
|
var EMPTY_DRAWING_SET = { lines: [], boxes: [], labels: [], polylines: [], linefills: [] };
|
|
25985
|
+
function modelDrawingSet(m, overlay) {
|
|
25986
|
+
const want = (d) => Boolean(d.overlay) === overlay;
|
|
25987
|
+
return {
|
|
25988
|
+
lines: (m.lines ?? []).filter(want),
|
|
25989
|
+
boxes: (m.boxes ?? []).filter(want),
|
|
25990
|
+
labels: (m.labels ?? []).filter(want),
|
|
25991
|
+
polylines: (m.polylines ?? []).filter(want),
|
|
25992
|
+
linefills: (m.linefills ?? []).filter(want)
|
|
25993
|
+
};
|
|
25994
|
+
}
|
|
25995
|
+
function drawingSetEmpty(s) {
|
|
25996
|
+
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
25997
|
+
}
|
|
25948
25998
|
function fontSizePx(size3) {
|
|
25949
25999
|
return size3 === "auto" ? 12 : namedFontSize(size3);
|
|
25950
26000
|
}
|
|
26001
|
+
function lineCoversWindow(a, b, extend, lo, hi) {
|
|
26002
|
+
const minX = Math.min(a, b);
|
|
26003
|
+
const maxX = Math.max(a, b);
|
|
26004
|
+
if (a === b) return a >= lo && a <= hi;
|
|
26005
|
+
if (extend === "both") return true;
|
|
26006
|
+
if (extend === "left") return maxX >= lo;
|
|
26007
|
+
if (extend === "right") return minX <= hi;
|
|
26008
|
+
return maxX >= lo && minX <= hi;
|
|
26009
|
+
}
|
|
25951
26010
|
var DrawingSceneRenderer = class {
|
|
25952
26011
|
constructor(deps, set = EMPTY_DRAWING_SET) {
|
|
25953
26012
|
this.deps = deps;
|
|
@@ -26012,7 +26071,7 @@ void main() {
|
|
|
26012
26071
|
};
|
|
26013
26072
|
for (const ln of this.set.lines) {
|
|
26014
26073
|
if (ln.invisible) continue;
|
|
26015
|
-
if (!
|
|
26074
|
+
if (!lineCoversWindow(this.logicalOf(ln.xloc, ln.x1), this.logicalOf(ln.xloc, ln.x2), ln.extend, lo, hi)) continue;
|
|
26016
26075
|
fold(ln.y1);
|
|
26017
26076
|
fold(ln.y2);
|
|
26018
26077
|
}
|
|
@@ -26797,10 +26856,8 @@ void main() {
|
|
|
26797
26856
|
this.ctx = null;
|
|
26798
26857
|
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
26799
26858
|
this.axisTextColor = DARK_THEME.textColor;
|
|
26800
|
-
// Shared Pine-drawing renderer
|
|
26859
|
+
// Shared Pine-drawing renderer, used here for autoscale geometry only; widthCache persists.
|
|
26801
26860
|
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
26861
|
}
|
|
26805
26862
|
mount(canvas) {
|
|
26806
26863
|
this.canvas = canvas;
|
|
@@ -26824,8 +26881,8 @@ void main() {
|
|
|
26824
26881
|
*/
|
|
26825
26882
|
paneDrawingsRange(ownModels, scene, isPricePane, vr) {
|
|
26826
26883
|
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(
|
|
26884
|
+
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, false), vr, scene.offsetOf(m.id)));
|
|
26885
|
+
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, true), vr, scene.offsetOf(m.id)));
|
|
26829
26886
|
return dr;
|
|
26830
26887
|
}
|
|
26831
26888
|
/** Clear the chrome canvas and draw drawings + axes + current-price line.
|
|
@@ -26842,7 +26899,6 @@ void main() {
|
|
|
26842
26899
|
const dataW = coords.width;
|
|
26843
26900
|
const dataH = coords.height;
|
|
26844
26901
|
this.axisTextColor = surface?.textColor ?? theme.textColor;
|
|
26845
|
-
this.labelTips = [];
|
|
26846
26902
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
26847
26903
|
ctx.clearRect(0, 0, fullW, fullH);
|
|
26848
26904
|
if (surface && (fullW > dataW || fullH > dataH)) {
|
|
@@ -26858,17 +26914,6 @@ void main() {
|
|
|
26858
26914
|
return;
|
|
26859
26915
|
}
|
|
26860
26916
|
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
26917
|
if (pricePane && !pricePane.collapsed && scene.tradeMarkers.visible) {
|
|
26873
26918
|
for (const m of scene.indicators.values()) {
|
|
26874
26919
|
if (m.trades?.length) this.renderTrades(ctx, coords, scene, theme, m.trades, pricePane, dataW);
|
|
@@ -26884,25 +26929,6 @@ void main() {
|
|
|
26884
26929
|
this.canvas = null;
|
|
26885
26930
|
this.ctx = null;
|
|
26886
26931
|
}
|
|
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
26932
|
drawingsRange(set, vr, indexOffset = 0) {
|
|
26907
26933
|
this.drawScene.setSet(set, indexOffset);
|
|
26908
26934
|
if (this.drawScene.isEmpty()) return null;
|
|
@@ -26936,34 +26962,6 @@ void main() {
|
|
|
26936
26962
|
);
|
|
26937
26963
|
ctx.restore();
|
|
26938
26964
|
}
|
|
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
26965
|
// ── axes ──
|
|
26968
26966
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
26969
26967
|
ctx.strokeStyle = scene.style.borderColor ?? theme.borderColor;
|
|
@@ -30013,6 +30011,22 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30013
30011
|
this.snapAt = changed ? { point: snapped, paneId } : null;
|
|
30014
30012
|
return snapped;
|
|
30015
30013
|
}
|
|
30014
|
+
/**
|
|
30015
|
+
* Resolve a cursor pixel through the magnet and return the snapped pixel — the same
|
|
30016
|
+
* conversion drawing placement uses. Updates the snap-ring marker. The measure
|
|
30017
|
+
* ruler goes through this so its endpoints follow weak/strong/Ctrl magnet too.
|
|
30018
|
+
*/
|
|
30019
|
+
snapCursor(x, y, mode) {
|
|
30020
|
+
const proj = this.deps.projector();
|
|
30021
|
+
const paneId = proj.paneIdAtY(y) ?? "price";
|
|
30022
|
+
const point = this.resolve(x, y, paneId, mode);
|
|
30023
|
+
const sy = proj.yOf(point.price, paneId);
|
|
30024
|
+
return { x: proj.xOf(point.time), y: sy ?? y };
|
|
30025
|
+
}
|
|
30026
|
+
/** Drop the snap-ring marker (a transient mode ended without going through `up`). */
|
|
30027
|
+
clearSnapMarker() {
|
|
30028
|
+
this.snapAt = null;
|
|
30029
|
+
}
|
|
30016
30030
|
/** Resolve a pixel to a data point with the segment angle locked to 45° steps around
|
|
30017
30031
|
* `pivot` (Shift held on a line tool). Works in PIXEL space — the user reasons about
|
|
30018
30032
|
* the angle they see, not about time/price units. The magnet is bypassed: snapping
|
|
@@ -31372,17 +31386,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
31372
31386
|
return textGlyph(String(glyph), 15);
|
|
31373
31387
|
}
|
|
31374
31388
|
function stampSizeIcon(size3) {
|
|
31375
|
-
return textGlyph("\u25CF", (
|
|
31389
|
+
return textGlyph("\u25CF", (SIZE_PX3[String(size3)] ?? 13) + 4);
|
|
31376
31390
|
}
|
|
31377
31391
|
function sizeIcon(size3) {
|
|
31378
31392
|
return textGlyph(String(size3).charAt(0).toUpperCase(), 15);
|
|
31379
31393
|
}
|
|
31380
|
-
var
|
|
31394
|
+
var SIZE_PX3 = { small: 10, normal: 13, large: 16, huge: 20 };
|
|
31381
31395
|
function numbersSizeIcon(size3) {
|
|
31382
|
-
return textGlyph("12", (
|
|
31396
|
+
return textGlyph("12", (SIZE_PX3[String(size3)] ?? 13) - 1, 16.5, 'font-weight="600"');
|
|
31383
31397
|
}
|
|
31384
31398
|
function labelSizeIcon(size3) {
|
|
31385
|
-
return textGlyph("T", (
|
|
31399
|
+
return textGlyph("T", (SIZE_PX3[String(size3)] ?? 13) + 2);
|
|
31386
31400
|
}
|
|
31387
31401
|
function capitalize(s) {
|
|
31388
31402
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -31546,7 +31560,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
31546
31560
|
this.root.replaceChildren();
|
|
31547
31561
|
this.groupCells.clear();
|
|
31548
31562
|
this.groupIcons.clear();
|
|
31549
|
-
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.
|
|
31563
|
+
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.onCursorClick());
|
|
31550
31564
|
this.root.appendChild(this.cursorBtn);
|
|
31551
31565
|
if (this.def.groups.length > 0) this.root.appendChild(this.divider());
|
|
31552
31566
|
for (const g of this.def.groups) {
|
|
@@ -31650,6 +31664,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
31650
31664
|
this.magnetIcon = icon2;
|
|
31651
31665
|
return cell;
|
|
31652
31666
|
}
|
|
31667
|
+
/** Cursor returns to select/idle: an active measure/eraser mode exits through its own
|
|
31668
|
+
* toggle callback (disarming a tool via `onArm(null)` alone can't — the host treats a
|
|
31669
|
+
* null arm as a no-op side effect of entering those modes), then the tool disarms. */
|
|
31670
|
+
onCursorClick() {
|
|
31671
|
+
if (this.measureActive) this.onMeasure();
|
|
31672
|
+
if (this.eraserActive) this.onEraser();
|
|
31673
|
+
this.onArm(null);
|
|
31674
|
+
}
|
|
31653
31675
|
/** Clicking the icon arms the group's last-used tool (it does NOT open the flyout). */
|
|
31654
31676
|
onGroupIconClick(group) {
|
|
31655
31677
|
const type = this.lastUsed.get(group.id) ?? group.tools[0]?.type;
|
|
@@ -32042,30 +32064,39 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32042
32064
|
isFinished() {
|
|
32043
32065
|
return this.state === "finished";
|
|
32044
32066
|
}
|
|
32045
|
-
/** A press: begin the measurement, or finish it on the second click.
|
|
32046
|
-
|
|
32067
|
+
/** A press: begin the measurement, or finish it on the second click.
|
|
32068
|
+
* `x,y` are the raw cursor (drag-slop vs click-move-click). `gx,gy` are the
|
|
32069
|
+
* graphic endpoints — magnet-snapped when the magnet is on, else the same as `x,y`. */
|
|
32070
|
+
down(x, y, gx = x, gy = y) {
|
|
32047
32071
|
if (this.state === "measuring") {
|
|
32048
|
-
this.end = { x, y };
|
|
32072
|
+
this.end = { x: gx, y: gy };
|
|
32049
32073
|
this.state = "finished";
|
|
32050
32074
|
return;
|
|
32051
32075
|
}
|
|
32052
|
-
this.start = { x, y };
|
|
32053
|
-
this.end = { x, y };
|
|
32076
|
+
this.start = { x: gx, y: gy };
|
|
32077
|
+
this.end = { x: gx, y: gy };
|
|
32054
32078
|
this.pressX = x;
|
|
32055
32079
|
this.pressY = y;
|
|
32056
32080
|
this.state = "measuring";
|
|
32057
32081
|
}
|
|
32082
|
+
/** Size the in-progress ruler. `x,y` are the graphic (magnet-snapped) cursor. */
|
|
32058
32083
|
move(x, y) {
|
|
32059
32084
|
if (this.state === "measuring") this.end = { x, y };
|
|
32060
32085
|
}
|
|
32061
32086
|
/** A release: finish if the press was actually dragged (press-drag-release), else wait
|
|
32062
|
-
* for the second click (click-move-click).
|
|
32063
|
-
|
|
32087
|
+
* for the second click (click-move-click). `x,y` are the raw cursor (slop); `gx,gy`
|
|
32088
|
+
* are the graphic end (magnet-snapped when the magnet is on). */
|
|
32089
|
+
up(x, y, gx = x, gy = y) {
|
|
32064
32090
|
if (this.state === "measuring" && Math.hypot(x - this.pressX, y - this.pressY) > DRAG_SLOP3) {
|
|
32065
|
-
this.end = { x, y };
|
|
32091
|
+
this.end = { x: gx, y: gy };
|
|
32066
32092
|
this.state = "finished";
|
|
32067
32093
|
}
|
|
32068
32094
|
}
|
|
32095
|
+
/** Current graphic endpoints in media pixels, or null when idle. */
|
|
32096
|
+
points() {
|
|
32097
|
+
if (!this.start || !this.end) return null;
|
|
32098
|
+
return { start: this.start, end: this.end };
|
|
32099
|
+
}
|
|
32069
32100
|
clear() {
|
|
32070
32101
|
this.state = "idle";
|
|
32071
32102
|
this.start = null;
|
|
@@ -32442,11 +32473,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32442
32473
|
}
|
|
32443
32474
|
/** Shift+press on the empty plot: arm the measure ruler AND start it at (x, y) in one
|
|
32444
32475
|
* gesture — the equivalent of clicking the toolbar's Measure button, then pressing.
|
|
32445
|
-
*
|
|
32446
|
-
|
|
32476
|
+
* `snap` is the effective magnet (sticky mode, or Ctrl/Cmd-forced strong). Returns
|
|
32477
|
+
* false when a mode/tool is already active (the normal press path owns it). */
|
|
32478
|
+
beginMeasureAt(x, y, snap = "off") {
|
|
32447
32479
|
if (this.measureMode || this.eraserMode || this.activeTool != null) return false;
|
|
32448
32480
|
this.withModeIntent(() => this.toggleMeasure());
|
|
32449
|
-
this.
|
|
32481
|
+
const g = this.interaction.snapCursor(x, y, snap);
|
|
32482
|
+
this.measure.down(x, y, g.x, g.y);
|
|
32450
32483
|
this.render();
|
|
32451
32484
|
return true;
|
|
32452
32485
|
}
|
|
@@ -32468,7 +32501,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32468
32501
|
return;
|
|
32469
32502
|
}
|
|
32470
32503
|
if (this.measureMode) {
|
|
32471
|
-
this.
|
|
32504
|
+
const g = this.interaction.snapCursor(x, y, snap);
|
|
32505
|
+
this.measure.down(x, y, g.x, g.y);
|
|
32472
32506
|
if (this.measure.isFinished()) this.withModeIntent(() => this.exitMeasure(false));
|
|
32473
32507
|
this.render();
|
|
32474
32508
|
return;
|
|
@@ -32481,7 +32515,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32481
32515
|
return;
|
|
32482
32516
|
}
|
|
32483
32517
|
if (this.measureMode) {
|
|
32484
|
-
this.
|
|
32518
|
+
const g = this.interaction.snapCursor(x, y, snap);
|
|
32519
|
+
this.measure.move(g.x, g.y);
|
|
32485
32520
|
this.render();
|
|
32486
32521
|
return;
|
|
32487
32522
|
}
|
|
@@ -32499,13 +32534,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32499
32534
|
this.render();
|
|
32500
32535
|
}
|
|
32501
32536
|
}
|
|
32502
|
-
pointerUp(x, y) {
|
|
32537
|
+
pointerUp(x, y, snap = "off") {
|
|
32503
32538
|
if (this.eraserMode) {
|
|
32504
32539
|
this.erasing = false;
|
|
32505
32540
|
return;
|
|
32506
32541
|
}
|
|
32507
32542
|
if (this.measureMode) {
|
|
32508
|
-
this.
|
|
32543
|
+
const g = this.interaction.snapCursor(x, y, snap);
|
|
32544
|
+
this.measure.up(x, y, g.x, g.y);
|
|
32509
32545
|
if (this.measure.isFinished()) this.withModeIntent(() => this.exitMeasure(false));
|
|
32510
32546
|
this.render();
|
|
32511
32547
|
return;
|
|
@@ -32550,6 +32586,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32550
32586
|
/** Leave ruler mode. `clearGraphic` keeps a just-finished measurement on screen (false). */
|
|
32551
32587
|
exitMeasure(clearGraphic = true) {
|
|
32552
32588
|
this.measureMode = false;
|
|
32589
|
+
this.interaction.clearSnapMarker();
|
|
32553
32590
|
if (clearGraphic) this.measure.clear();
|
|
32554
32591
|
this.toolbar.setMeasureActive(false);
|
|
32555
32592
|
this.render();
|
|
@@ -32690,15 +32727,28 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32690
32727
|
if (this.eraserMode) return "pointer";
|
|
32691
32728
|
return this.interaction.cursorAt(x, y);
|
|
32692
32729
|
}
|
|
32693
|
-
/** Right-click
|
|
32694
|
-
*
|
|
32695
|
-
*
|
|
32696
|
-
*
|
|
32730
|
+
/** Right-click: an explicit escape back to the pointer. Cancels an in-progress
|
|
32731
|
+
* placement or measurement, and also plain-disarms an armed-but-idle drawing
|
|
32732
|
+
* tool or the eraser — so a right-click ALWAYS reverts to the pointer, even in
|
|
32733
|
+
* stay-in-drawing-mode (where Escape would leave a drawing tool armed).
|
|
32734
|
+
* Persistent toggles (magnet, stay-mode, favorites) are untouched. Returns
|
|
32735
|
+
* whether the press was consumed; false lets the host's context menu open
|
|
32736
|
+
* normally. */
|
|
32697
32737
|
cancelPlacement() {
|
|
32698
|
-
if (
|
|
32699
|
-
|
|
32700
|
-
|
|
32701
|
-
|
|
32738
|
+
if (this.measureMode || this.eraserMode) {
|
|
32739
|
+
this.withModeIntent(() => this.measureMode ? this.exitMeasure() : this.exitEraser());
|
|
32740
|
+
return true;
|
|
32741
|
+
}
|
|
32742
|
+
if (this.interaction.isPlacing()) {
|
|
32743
|
+
this.interaction.cancel();
|
|
32744
|
+
if (this.activeTool != null) this.emit({ kind: "arm", type: null });
|
|
32745
|
+
return true;
|
|
32746
|
+
}
|
|
32747
|
+
if (this.activeTool != null) {
|
|
32748
|
+
this.emit({ kind: "arm", type: null });
|
|
32749
|
+
return true;
|
|
32750
|
+
}
|
|
32751
|
+
return false;
|
|
32702
32752
|
}
|
|
32703
32753
|
/** Double-click over a drawing → suppress the chart's view reset (single-click already
|
|
32704
32754
|
* opens settings). Returns true only when a drawing is under the cursor. */
|
|
@@ -32724,6 +32774,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32724
32774
|
return true;
|
|
32725
32775
|
}
|
|
32726
32776
|
if (this.interaction.cancel()) return true;
|
|
32777
|
+
if (this.measureMode) {
|
|
32778
|
+
this.withModeIntent(() => this.exitMeasure());
|
|
32779
|
+
return true;
|
|
32780
|
+
}
|
|
32727
32781
|
if (this.selectedIds.size) {
|
|
32728
32782
|
this.clearSelection();
|
|
32729
32783
|
return true;
|
|
@@ -32981,6 +33035,315 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32981
33035
|
}
|
|
32982
33036
|
};
|
|
32983
33037
|
|
|
33038
|
+
// src/renderers/shared/TableOverlay.ts
|
|
33039
|
+
var SIZE_PX4 = {
|
|
33040
|
+
auto: 13,
|
|
33041
|
+
tiny: 10,
|
|
33042
|
+
small: 11,
|
|
33043
|
+
normal: 13,
|
|
33044
|
+
large: 16,
|
|
33045
|
+
huge: 20
|
|
33046
|
+
};
|
|
33047
|
+
function fontPxOf(size3) {
|
|
33048
|
+
if (typeof size3 === "number") return size3 > 0 ? size3 : SIZE_PX4.auto;
|
|
33049
|
+
return SIZE_PX4[size3] ?? SIZE_PX4.auto;
|
|
33050
|
+
}
|
|
33051
|
+
function tableHasContent(t) {
|
|
33052
|
+
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
33053
|
+
}
|
|
33054
|
+
function mergeRenderPlan(t) {
|
|
33055
|
+
const span = /* @__PURE__ */ new Map();
|
|
33056
|
+
const omit = /* @__PURE__ */ new Set();
|
|
33057
|
+
for (const m of t.merges) {
|
|
33058
|
+
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
33059
|
+
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
33060
|
+
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
33061
|
+
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
33062
|
+
}
|
|
33063
|
+
}
|
|
33064
|
+
}
|
|
33065
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
33066
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
33067
|
+
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
33068
|
+
}
|
|
33069
|
+
}
|
|
33070
|
+
for (const key of span.keys()) omit.delete(key);
|
|
33071
|
+
return { span, omit };
|
|
33072
|
+
}
|
|
33073
|
+
|
|
33074
|
+
// src/renderers/shared/TableCanvasRenderer.ts
|
|
33075
|
+
var PAD_X = 6;
|
|
33076
|
+
var PAD_Y = 2;
|
|
33077
|
+
var MARGIN = 6;
|
|
33078
|
+
var LINE_HEIGHT = 1.2;
|
|
33079
|
+
function paintTable(ctx, t, args, tips) {
|
|
33080
|
+
if (!tableHasContent(t)) return;
|
|
33081
|
+
const layout = layoutTable(ctx, t, args);
|
|
33082
|
+
if (!layout || layout.w <= 0 || layout.h <= 0) return;
|
|
33083
|
+
const fw = t.frameColor && t.frameWidth > 0 ? t.frameWidth : 0;
|
|
33084
|
+
const { x, y } = anchorOrigin(t.position, layout.w + 2 * fw, layout.h + 2 * fw, args);
|
|
33085
|
+
const x0 = x + fw;
|
|
33086
|
+
const y0 = y + fw;
|
|
33087
|
+
if (t.bgColor) {
|
|
33088
|
+
ctx.fillStyle = t.bgColor;
|
|
33089
|
+
ctx.fillRect(x0, y0, layout.w, layout.h);
|
|
33090
|
+
}
|
|
33091
|
+
if (fw > 0 && t.frameColor) {
|
|
33092
|
+
ctx.strokeStyle = t.frameColor;
|
|
33093
|
+
ctx.lineWidth = fw;
|
|
33094
|
+
ctx.strokeRect(x + fw / 2, y + fw / 2, layout.w + fw, layout.h + fw);
|
|
33095
|
+
}
|
|
33096
|
+
const colX = [0];
|
|
33097
|
+
for (const w of layout.colW) colX.push(colX[colX.length - 1] + w);
|
|
33098
|
+
const rowY = [0];
|
|
33099
|
+
for (const h of layout.rowH) rowY.push(rowY[rowY.length - 1] + h);
|
|
33100
|
+
const prevBaseline = ctx.textBaseline;
|
|
33101
|
+
const prevAlign = ctx.textAlign;
|
|
33102
|
+
ctx.textBaseline = "middle";
|
|
33103
|
+
for (const box of layout.boxes) {
|
|
33104
|
+
const rx = x0 + colX[box.c];
|
|
33105
|
+
const ry = y0 + rowY[box.r];
|
|
33106
|
+
const rw = colX[box.c + box.cs] - colX[box.c];
|
|
33107
|
+
const rh = rowY[box.r + box.rs] - rowY[box.r];
|
|
33108
|
+
const cell = box.cell;
|
|
33109
|
+
if (cell.bgColor) {
|
|
33110
|
+
ctx.fillStyle = cell.bgColor;
|
|
33111
|
+
ctx.fillRect(rx, ry, rw, rh);
|
|
33112
|
+
}
|
|
33113
|
+
const text = cell.text ?? "";
|
|
33114
|
+
if (text.length > 0) {
|
|
33115
|
+
const px = fontPxOf(cell.textSize);
|
|
33116
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
33117
|
+
ctx.fillStyle = cell.textColor ?? args.theme.textColor;
|
|
33118
|
+
const lines = text.split("\n");
|
|
33119
|
+
const blockH = lines.length * px * LINE_HEIGHT;
|
|
33120
|
+
const blockTop = cell.vAlign === "top" ? ry + PAD_Y : cell.vAlign === "bottom" ? ry + rh - PAD_Y - blockH : ry + (rh - blockH) / 2;
|
|
33121
|
+
const tx = cell.hAlign === "left" ? rx + PAD_X : cell.hAlign === "right" ? rx + rw - PAD_X : rx + rw / 2;
|
|
33122
|
+
ctx.textAlign = cell.hAlign;
|
|
33123
|
+
lines.forEach((line, i) => ctx.fillText(line, tx, blockTop + (i + 0.5) * px * LINE_HEIGHT));
|
|
33124
|
+
}
|
|
33125
|
+
if (cell.tooltip) tips.push({ left: rx, top: ry, right: rx + rw, bottom: ry + rh, text: cell.tooltip });
|
|
33126
|
+
}
|
|
33127
|
+
ctx.textBaseline = prevBaseline;
|
|
33128
|
+
ctx.textAlign = prevAlign;
|
|
33129
|
+
if (t.borderColor && t.borderWidth > 0) {
|
|
33130
|
+
ctx.strokeStyle = t.borderColor;
|
|
33131
|
+
ctx.lineWidth = t.borderWidth;
|
|
33132
|
+
const seen = /* @__PURE__ */ new Set();
|
|
33133
|
+
ctx.beginPath();
|
|
33134
|
+
const edge = (ax, ay, bx, by) => {
|
|
33135
|
+
const key = `${ax},${ay},${bx},${by}`;
|
|
33136
|
+
if (seen.has(key)) return;
|
|
33137
|
+
seen.add(key);
|
|
33138
|
+
ctx.moveTo(ax, ay);
|
|
33139
|
+
ctx.lineTo(bx, by);
|
|
33140
|
+
};
|
|
33141
|
+
for (const box of layout.boxes) {
|
|
33142
|
+
const l = Math.round(x0 + colX[box.c]);
|
|
33143
|
+
const r = Math.round(x0 + colX[box.c + box.cs]);
|
|
33144
|
+
const tp = Math.round(y0 + rowY[box.r]);
|
|
33145
|
+
const bt = Math.round(y0 + rowY[box.r + box.rs]);
|
|
33146
|
+
edge(l, tp, r, tp);
|
|
33147
|
+
edge(l, bt, r, bt);
|
|
33148
|
+
edge(l, tp, l, bt);
|
|
33149
|
+
edge(r, tp, r, bt);
|
|
33150
|
+
}
|
|
33151
|
+
ctx.stroke();
|
|
33152
|
+
}
|
|
33153
|
+
}
|
|
33154
|
+
function layoutTable(ctx, t, args) {
|
|
33155
|
+
const { span, omit } = mergeRenderPlan(t);
|
|
33156
|
+
const colW = new Array(t.columns).fill(0);
|
|
33157
|
+
const rowH = new Array(t.rows).fill(0);
|
|
33158
|
+
const boxes = [];
|
|
33159
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
33160
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
33161
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
33162
|
+
const cell = t.cells[r]?.[c];
|
|
33163
|
+
if (cell == null) continue;
|
|
33164
|
+
const sp = span.get(`${r}:${c}`);
|
|
33165
|
+
boxes.push({ cell, r, c, cs: Math.min(sp?.cs ?? 1, t.columns - c), rs: Math.min(sp?.rs ?? 1, t.rows - r) });
|
|
33166
|
+
}
|
|
33167
|
+
}
|
|
33168
|
+
if (boxes.length === 0) return null;
|
|
33169
|
+
const sizeOf = (cell) => {
|
|
33170
|
+
const px = fontPxOf(cell.textSize);
|
|
33171
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
33172
|
+
const lines = (cell.text ?? "").split("\n");
|
|
33173
|
+
let maxW = 0;
|
|
33174
|
+
for (const line of lines) maxW = Math.max(maxW, ctx.measureText(line).width);
|
|
33175
|
+
let w2 = Math.ceil(maxW) + 2 * PAD_X;
|
|
33176
|
+
let h2 = Math.ceil(lines.length * px * LINE_HEIGHT) + 2 * PAD_Y;
|
|
33177
|
+
if (cell.width) w2 = Math.max(w2, cell.width / 100 * args.plotWidth);
|
|
33178
|
+
if (cell.height) h2 = Math.max(h2, cell.height / 100 * args.paneHeight);
|
|
33179
|
+
return { w: w2, h: h2 };
|
|
33180
|
+
};
|
|
33181
|
+
const spanning = [];
|
|
33182
|
+
for (const box of boxes) {
|
|
33183
|
+
const { w: w2, h: h2 } = sizeOf(box.cell);
|
|
33184
|
+
if (box.cs === 1) colW[box.c] = Math.max(colW[box.c], w2);
|
|
33185
|
+
if (box.rs === 1) rowH[box.r] = Math.max(rowH[box.r], h2);
|
|
33186
|
+
if (box.cs > 1 || box.rs > 1) spanning.push({ box, w: w2, h: h2 });
|
|
33187
|
+
}
|
|
33188
|
+
for (const { box, w: w2, h: h2 } of spanning) {
|
|
33189
|
+
if (box.cs > 1) {
|
|
33190
|
+
let sum = 0;
|
|
33191
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
|
|
33192
|
+
if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
|
|
33193
|
+
}
|
|
33194
|
+
if (box.rs > 1) {
|
|
33195
|
+
let sum = 0;
|
|
33196
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
|
|
33197
|
+
if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
|
|
33198
|
+
}
|
|
33199
|
+
}
|
|
33200
|
+
let w = 0;
|
|
33201
|
+
for (const cw of colW) w += cw;
|
|
33202
|
+
let h = 0;
|
|
33203
|
+
for (const rh of rowH) h += rh;
|
|
33204
|
+
return { colW, rowH, w, h, boxes };
|
|
33205
|
+
}
|
|
33206
|
+
function anchorOrigin(position, totalW, totalH, args) {
|
|
33207
|
+
let y;
|
|
33208
|
+
if (position.startsWith("top")) y = MARGIN;
|
|
33209
|
+
else if (position.startsWith("bottom")) y = args.paneHeight - MARGIN - totalH;
|
|
33210
|
+
else y = args.paneHeight / 2 - totalH / 2;
|
|
33211
|
+
let x;
|
|
33212
|
+
if (position.endsWith("left")) x = MARGIN;
|
|
33213
|
+
else if (position.endsWith("right")) x = args.plotWidth - MARGIN - totalW;
|
|
33214
|
+
else x = args.plotWidth / 2 - totalW / 2;
|
|
33215
|
+
return { x, y };
|
|
33216
|
+
}
|
|
33217
|
+
function cellFont(cell, px, theme) {
|
|
33218
|
+
const family = cell.fontFamily === "monospace" ? "monospace" : theme.fontFamily || "sans-serif";
|
|
33219
|
+
return `${cell.italic ? "italic " : ""}${cell.bold ? "bold " : ""}${px}px ${family}`;
|
|
33220
|
+
}
|
|
33221
|
+
|
|
33222
|
+
// src/renderers/native/drawings/IndicatorDrawingSlices.ts
|
|
33223
|
+
function indicatorSliceKey(z, boundaries) {
|
|
33224
|
+
return boundaries.find((b) => b > z) ?? Infinity;
|
|
33225
|
+
}
|
|
33226
|
+
var IndicatorDrawingSlices = class {
|
|
33227
|
+
constructor() {
|
|
33228
|
+
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
33229
|
+
/** Slice canvas cache, keyed `paneId|beforeZ` — same lifecycle as the user-drawing cache. */
|
|
33230
|
+
this.sliceCache = /* @__PURE__ */ new Map();
|
|
33231
|
+
/** Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per prepare). */
|
|
33232
|
+
this.tips = [];
|
|
33233
|
+
}
|
|
33234
|
+
/**
|
|
33235
|
+
* Rebuild the per-indicator drawing slices for this data frame. `ref` is the data
|
|
33236
|
+
* canvas the slices must match pixel-for-pixel (the backend composites them 1:1).
|
|
33237
|
+
* Runs from the renderer's data paint, just before the backend composites the scene.
|
|
33238
|
+
*/
|
|
33239
|
+
prepare(scene, coords, theme, ref2) {
|
|
33240
|
+
this.tips = [];
|
|
33241
|
+
const out = /* @__PURE__ */ new Map();
|
|
33242
|
+
if (ref2.width === 0 || ref2.height === 0) {
|
|
33243
|
+
this.sliceCache.clear();
|
|
33244
|
+
return out;
|
|
33245
|
+
}
|
|
33246
|
+
this.drawScene.setDeps({
|
|
33247
|
+
timeToLogical: (ms) => coords.timeToLogical(ms),
|
|
33248
|
+
barAt: (logical) => {
|
|
33249
|
+
const b = scene.bars[Math.round(logical)];
|
|
33250
|
+
return b ? { high: b.high, low: b.low } : null;
|
|
33251
|
+
},
|
|
33252
|
+
theme
|
|
33253
|
+
});
|
|
33254
|
+
const dpr = coords.dpr;
|
|
33255
|
+
const dataW = coords.width;
|
|
33256
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
33257
|
+
const add = (paneId, beforeZ, entry) => {
|
|
33258
|
+
const key = `${paneId}|${beforeZ}`;
|
|
33259
|
+
const bucket = buckets.get(key);
|
|
33260
|
+
if (bucket) bucket.entries.push(entry);
|
|
33261
|
+
else buckets.set(key, { paneId, beforeZ, entries: [entry] });
|
|
33262
|
+
};
|
|
33263
|
+
for (const pane of scene.orderedPanes()) {
|
|
33264
|
+
if (pane.collapsed) continue;
|
|
33265
|
+
const boundaries = scene.seriesBoundaries(pane.id);
|
|
33266
|
+
for (const m of scene.orderedIndicatorsForPane(pane.id)) {
|
|
33267
|
+
const set = modelDrawingSet(m, false);
|
|
33268
|
+
const tables = (m.tables ?? []).filter((t) => !t.overlay);
|
|
33269
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
33270
|
+
const sc = scene.scaleFor(m, pane);
|
|
33271
|
+
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
33272
|
+
const beforeZ = indicatorSliceKey(scene.zOf(m.id), boundaries);
|
|
33273
|
+
add(pane.id, beforeZ, { set, tables, pane: mp, indexOffset: scene.offsetOf(m.id) });
|
|
33274
|
+
}
|
|
33275
|
+
if (pane.kind === "price") {
|
|
33276
|
+
for (const m of scene.indicators.values()) {
|
|
33277
|
+
const set = modelDrawingSet(m, true);
|
|
33278
|
+
const tables = (m.tables ?? []).filter((t) => t.overlay === true);
|
|
33279
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
33280
|
+
add(pane.id, Infinity, { set, tables, pane, indexOffset: scene.offsetOf(m.id) });
|
|
33281
|
+
}
|
|
33282
|
+
}
|
|
33283
|
+
}
|
|
33284
|
+
for (const [key, { paneId, beforeZ, entries }] of buckets) {
|
|
33285
|
+
let canvas = this.sliceCache.get(key);
|
|
33286
|
+
if (!canvas) {
|
|
33287
|
+
canvas = document.createElement("canvas");
|
|
33288
|
+
this.sliceCache.set(key, canvas);
|
|
33289
|
+
}
|
|
33290
|
+
if (canvas.width !== ref2.width || canvas.height !== ref2.height) {
|
|
33291
|
+
canvas.width = ref2.width;
|
|
33292
|
+
canvas.height = ref2.height;
|
|
33293
|
+
}
|
|
33294
|
+
const ctx = canvas.getContext("2d");
|
|
33295
|
+
if (!ctx) continue;
|
|
33296
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
33297
|
+
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
33298
|
+
for (const e of entries) this.paintEntry(ctx, e, coords, dataW, theme);
|
|
33299
|
+
const slices = out.get(paneId) ?? [];
|
|
33300
|
+
slices.push({ beforeZ, canvas });
|
|
33301
|
+
out.set(paneId, slices);
|
|
33302
|
+
}
|
|
33303
|
+
for (const key of [...this.sliceCache.keys()]) if (!buckets.has(key)) this.sliceCache.delete(key);
|
|
33304
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
33305
|
+
return out;
|
|
33306
|
+
}
|
|
33307
|
+
paintEntry(ctx, e, coords, dataW, theme) {
|
|
33308
|
+
const { pane } = e;
|
|
33309
|
+
const paneTips = [];
|
|
33310
|
+
ctx.save();
|
|
33311
|
+
ctx.translate(0, pane.bounds.top);
|
|
33312
|
+
ctx.beginPath();
|
|
33313
|
+
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
33314
|
+
ctx.clip();
|
|
33315
|
+
this.drawScene.setSet(e.set, e.indexOffset);
|
|
33316
|
+
this.drawScene.render(
|
|
33317
|
+
ctx,
|
|
33318
|
+
dataW,
|
|
33319
|
+
pane.bounds.height,
|
|
33320
|
+
(l) => coords.logicalToX(l),
|
|
33321
|
+
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
33322
|
+
);
|
|
33323
|
+
paneTips.push(...this.drawScene.labelTipRegions());
|
|
33324
|
+
for (const t of e.tables) paintTable(ctx, t, { paneHeight: pane.bounds.height, plotWidth: dataW, theme }, paneTips);
|
|
33325
|
+
ctx.restore();
|
|
33326
|
+
for (const r of paneTips) {
|
|
33327
|
+
this.tips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
33328
|
+
}
|
|
33329
|
+
}
|
|
33330
|
+
/** Tooltip of the topmost label or table cell under a plot-space point, or null. Fed by the last prepare. */
|
|
33331
|
+
labelTooltipAt(x, y) {
|
|
33332
|
+
for (let i = this.tips.length - 1; i >= 0; i -= 1) {
|
|
33333
|
+
const r = this.tips[i];
|
|
33334
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
33335
|
+
}
|
|
33336
|
+
return null;
|
|
33337
|
+
}
|
|
33338
|
+
};
|
|
33339
|
+
function mergeSlices(indicator, user) {
|
|
33340
|
+
const out = /* @__PURE__ */ new Map();
|
|
33341
|
+
for (const [paneId, slices] of indicator) out.set(paneId, [...slices]);
|
|
33342
|
+
for (const [paneId, slices] of user) out.set(paneId, [...out.get(paneId) ?? [], ...slices]);
|
|
33343
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
33344
|
+
return out;
|
|
33345
|
+
}
|
|
33346
|
+
|
|
32984
33347
|
// src/renderers/native/drawings/Projector.ts
|
|
32985
33348
|
function createProjector(coords, paneOf, paneIdAtY, barsInRange) {
|
|
32986
33349
|
return {
|
|
@@ -33860,6 +34223,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33860
34223
|
this.vpvrRenderer = new VpvrRenderer();
|
|
33861
34224
|
this.resizeObserver = null;
|
|
33862
34225
|
this.dprMedia = null;
|
|
34226
|
+
/** Plot size in INTEGER device px, as last reported by the resize observer's
|
|
34227
|
+
* device-pixel-content-box — the browser's own statement of how many device pixels
|
|
34228
|
+
* it paints the plot into. `null` until the first report or where the box type is
|
|
34229
|
+
* unsupported (WebKit); syncSize then falls back to rounding the client rect. */
|
|
34230
|
+
this.plotDeviceSize = null;
|
|
33863
34231
|
this.coords = new CoordinateSystem();
|
|
33864
34232
|
this.scene = new SceneGraph();
|
|
33865
34233
|
// chosen at mount (WebGL2 if available, else canvas2d)
|
|
@@ -33867,6 +34235,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33867
34235
|
this.glowAmount = 0;
|
|
33868
34236
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
33869
34237
|
this.chrome = new ChromeRenderer();
|
|
34238
|
+
/** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
|
|
34239
|
+
this.indicatorSlices = new IndicatorDrawingSlices();
|
|
33870
34240
|
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
33871
34241
|
this.labelTooltip = null;
|
|
33872
34242
|
this.crosshairLayer = new CrosshairRenderer();
|
|
@@ -34015,7 +34385,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34015
34385
|
this.toggleVisibleCbs = /* @__PURE__ */ new Set();
|
|
34016
34386
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
34017
34387
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
34018
|
-
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
34019
34388
|
this.name = "native";
|
|
34020
34389
|
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
34390
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
@@ -34329,7 +34698,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34329
34698
|
* hidden) on clear so a re-show picks up the current theme.
|
|
34330
34699
|
*/
|
|
34331
34700
|
setLoading(loading) {
|
|
34332
|
-
for (const overlay of this.tableOverlays.values()) overlay.setVisible(!loading);
|
|
34333
34701
|
if (!loading || !this.wrapper) {
|
|
34334
34702
|
this.loadingEl?.remove();
|
|
34335
34703
|
this.loadingEl = null;
|
|
@@ -34968,13 +35336,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34968
35336
|
resetView: () => this.resetView(),
|
|
34969
35337
|
// User drawings claim a gesture before pan when armed / over a drawing.
|
|
34970
35338
|
drawingsClaim: (x, y) => this.userDrawings?.claim(x, y) ?? false,
|
|
34971
|
-
drawingsMeasureStart: (x, y) => this.userDrawings?.beginMeasureAt(x, y) ?? false,
|
|
35339
|
+
drawingsMeasureStart: (x, y, snap) => this.userDrawings?.beginMeasureAt(x, y, snap) ?? false,
|
|
34972
35340
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
34973
35341
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
34974
35342
|
drawingsSnapMode: () => this.snapMode,
|
|
34975
35343
|
drawingsPointerDown: (x, y, snap, shift3) => this.userDrawings?.pointerDown(x, y, snap, shift3),
|
|
34976
35344
|
drawingsPointerMove: (x, y, snap, shift3) => this.userDrawings?.pointerMove(x, y, snap, shift3),
|
|
34977
|
-
drawingsPointerUp: (x, y) => this.userDrawings?.pointerUp(x, y),
|
|
35345
|
+
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
34978
35346
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
34979
35347
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
34980
35348
|
drawingsClearTransient: () => this.userDrawings?.clearTransient()
|
|
@@ -34991,7 +35359,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34991
35359
|
this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
|
|
34992
35360
|
this.labelTooltip = new LabelTooltip(this.plot, {
|
|
34993
35361
|
theme: () => this.chromeTheme(),
|
|
34994
|
-
lookup: (x, y) => this.
|
|
35362
|
+
lookup: (x, y) => this.indicatorSlices.labelTooltipAt(x, y)
|
|
34995
35363
|
});
|
|
34996
35364
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
34997
35365
|
projector: () => this.drawingProjector(),
|
|
@@ -35076,6 +35444,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35076
35444
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
35077
35445
|
}
|
|
35078
35446
|
});
|
|
35447
|
+
this.paneControls.setSuspended(this.layoutMode === "mobile");
|
|
35079
35448
|
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
35080
35449
|
panes: () => this.axisScaleViews(),
|
|
35081
35450
|
rightAxis: () => this.rightAxisW,
|
|
@@ -35085,8 +35454,19 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35085
35454
|
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
35086
35455
|
}
|
|
35087
35456
|
});
|
|
35088
|
-
this.resizeObserver = new ResizeObserver(() =>
|
|
35457
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
35458
|
+
for (const e of entries) {
|
|
35459
|
+
if (e.target !== this.plot) continue;
|
|
35460
|
+
const s = e.devicePixelContentBoxSize?.[0];
|
|
35461
|
+
if (s) this.plotDeviceSize = { width: s.inlineSize, height: s.blockSize };
|
|
35462
|
+
}
|
|
35463
|
+
this.resize();
|
|
35464
|
+
});
|
|
35089
35465
|
this.resizeObserver.observe(this.wrapper);
|
|
35466
|
+
try {
|
|
35467
|
+
this.resizeObserver.observe(this.plot, { box: "device-pixel-content-box" });
|
|
35468
|
+
} catch {
|
|
35469
|
+
}
|
|
35090
35470
|
this.watchDpr();
|
|
35091
35471
|
this.syncSize();
|
|
35092
35472
|
}
|
|
@@ -35211,8 +35591,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35211
35591
|
this.inputsUI?.destroy();
|
|
35212
35592
|
this.paneControls?.destroy();
|
|
35213
35593
|
this.axisScaleButtons?.destroy();
|
|
35214
|
-
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
35215
|
-
this.tableOverlays.clear();
|
|
35216
35594
|
this.resizeObserver?.disconnect();
|
|
35217
35595
|
this.resizeObserver = null;
|
|
35218
35596
|
this.dprMedia?.removeEventListener("change", this.onDprChange);
|
|
@@ -35241,6 +35619,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35241
35619
|
this.attributionEl = null;
|
|
35242
35620
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
35243
35621
|
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
35622
|
+
this.mountContainer?.style.removeProperty("--vela-bottom-gutter");
|
|
35244
35623
|
this.mountContainer?.style.removeProperty("--vela-price-pane-top");
|
|
35245
35624
|
this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
|
|
35246
35625
|
this.mountContainer = null;
|
|
@@ -35278,7 +35657,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35278
35657
|
}
|
|
35279
35658
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
35280
35659
|
if (this.coords.width > 0 && !skipFit) {
|
|
35281
|
-
this.
|
|
35660
|
+
if (this.didInitialFit) this.reframeKeepZoom();
|
|
35661
|
+
else this.fitContent();
|
|
35282
35662
|
this.didInitialFit = true;
|
|
35283
35663
|
}
|
|
35284
35664
|
if (!this.introPlayed && this.bars.length > 0) {
|
|
@@ -35333,7 +35713,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35333
35713
|
ensurePane(pane) {
|
|
35334
35714
|
this.scene.ensurePane(pane.id, pane.kind, pane.order, pane.heightWeight ?? (pane.kind === "price" ? 3 : 1));
|
|
35335
35715
|
this.layoutPanes();
|
|
35336
|
-
this.repositionTables();
|
|
35337
35716
|
this.paneControls?.refresh();
|
|
35338
35717
|
this.scheduler.invalidate(4 /* Full */);
|
|
35339
35718
|
}
|
|
@@ -35355,17 +35734,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35355
35734
|
if (!model.ownScale) this.scene.dropIndicatorScale(handle.id);
|
|
35356
35735
|
this.inputsUI.setPane(handle.id, paneId);
|
|
35357
35736
|
this.refreshAnchorOffset(model);
|
|
35358
|
-
this.syncTables(model);
|
|
35359
35737
|
this.refreshAxisWidth();
|
|
35360
35738
|
this.layoutPanes();
|
|
35361
|
-
this.repositionTables();
|
|
35362
35739
|
this.paneControls?.refresh();
|
|
35363
35740
|
this.scheduler.invalidate(4 /* Full */);
|
|
35364
35741
|
}
|
|
35365
35742
|
orderPanes(orderedIds) {
|
|
35366
35743
|
this.scene.orderPanes(orderedIds);
|
|
35367
35744
|
this.layoutPanes();
|
|
35368
|
-
this.repositionTables();
|
|
35369
35745
|
this.paneControls?.refresh();
|
|
35370
35746
|
this.scheduler.invalidate(4 /* Full */);
|
|
35371
35747
|
}
|
|
@@ -35374,7 +35750,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35374
35750
|
if (!pane || pane.collapsed === collapsed) return;
|
|
35375
35751
|
pane.collapsed = collapsed;
|
|
35376
35752
|
this.layoutPanes();
|
|
35377
|
-
this.repositionTables();
|
|
35378
35753
|
this.paneControls?.refresh();
|
|
35379
35754
|
this.scheduler.invalidate(4 /* Full */);
|
|
35380
35755
|
}
|
|
@@ -35382,7 +35757,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35382
35757
|
if (paneId !== null && !this.scene.panes.has(paneId)) paneId = null;
|
|
35383
35758
|
this.maximizedPaneId = paneId;
|
|
35384
35759
|
this.layoutPanes();
|
|
35385
|
-
this.repositionTables();
|
|
35386
35760
|
this.paneControls?.refresh();
|
|
35387
35761
|
this.scheduler.invalidate(4 /* Full */);
|
|
35388
35762
|
}
|
|
@@ -35469,7 +35843,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35469
35843
|
native: !!model.native,
|
|
35470
35844
|
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
35471
35845
|
});
|
|
35472
|
-
this.syncTables(model);
|
|
35473
35846
|
if (model.native?.type === "volume") {
|
|
35474
35847
|
this.volumeActive = true;
|
|
35475
35848
|
this.volumeHidden = false;
|
|
@@ -35493,7 +35866,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35493
35866
|
}
|
|
35494
35867
|
}
|
|
35495
35868
|
applyPatch(model, patch);
|
|
35496
|
-
this.syncTables(model);
|
|
35497
35869
|
this.scheduler.invalidate(3 /* Light */);
|
|
35498
35870
|
}
|
|
35499
35871
|
removeIndicator(handle) {
|
|
@@ -35512,8 +35884,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35512
35884
|
this.scene.forgetAnchorOffset(handle.id);
|
|
35513
35885
|
this.scene.dropIndicatorScale(handle.id);
|
|
35514
35886
|
this.inputsUI.remove(handle.id);
|
|
35515
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
35516
|
-
this.tableOverlays.delete(handle.id);
|
|
35517
35887
|
this.refreshAxisWidth();
|
|
35518
35888
|
this.paneControls?.refresh();
|
|
35519
35889
|
this.scheduler.invalidate(4 /* Full */);
|
|
@@ -35557,8 +35927,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35557
35927
|
}
|
|
35558
35928
|
if (!visible) {
|
|
35559
35929
|
this.scene.indicators.delete(handle.id);
|
|
35560
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
35561
|
-
this.tableOverlays.delete(handle.id);
|
|
35562
35930
|
}
|
|
35563
35931
|
this.inputsUI.setVisible(handle.id, visible);
|
|
35564
35932
|
this.scheduler.invalidate(4 /* Full */);
|
|
@@ -35604,6 +35972,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35604
35972
|
this.userDrawings?.setLayoutMode(mode);
|
|
35605
35973
|
this.settingsDialog?.setLayoutMode(mode);
|
|
35606
35974
|
this.inputsUI?.setLayoutMode(mode);
|
|
35975
|
+
this.paneControls?.setSuspended(mode === "mobile");
|
|
35607
35976
|
if (this.scrollButton) {
|
|
35608
35977
|
const px = mode === "mobile" ? SCROLL_BTN_SIZE_TOUCH : SCROLL_BTN_SIZE;
|
|
35609
35978
|
this.scrollButton.style.width = `${px}px`;
|
|
@@ -35937,6 +36306,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35937
36306
|
this.scaleDragHeight = res.height;
|
|
35938
36307
|
this.scaleDragStart = { ...res.holder.scale };
|
|
35939
36308
|
res.holder.manualScale = { ...res.holder.scale };
|
|
36309
|
+
this.axisScaleButtons?.reposition();
|
|
35940
36310
|
this.scheduler.invalidate(4 /* Full */);
|
|
35941
36311
|
}
|
|
35942
36312
|
/** Rescale the grabbed scale around its center by the total drag (down ⇒ zoom out). */
|
|
@@ -35968,6 +36338,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35968
36338
|
const res = this.resolveScaleHolder(x, y);
|
|
35969
36339
|
if (!res) return;
|
|
35970
36340
|
res.holder.manualScale = null;
|
|
36341
|
+
this.axisScaleButtons?.reposition();
|
|
35971
36342
|
this.scheduler.invalidate(4 /* Full */);
|
|
35972
36343
|
}
|
|
35973
36344
|
/**
|
|
@@ -35999,7 +36370,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35999
36370
|
/** Relayout + repaint + refresh the hover buttons after a collapse/maximize/order change. */
|
|
36000
36371
|
afterPaneLayoutChange() {
|
|
36001
36372
|
this.layoutPanes();
|
|
36002
|
-
this.repositionTables();
|
|
36003
36373
|
this.paneControls?.refresh();
|
|
36004
36374
|
this.scheduler.invalidate(4 /* Full */);
|
|
36005
36375
|
}
|
|
@@ -36080,7 +36450,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36080
36450
|
above.heightWeight = next2.above;
|
|
36081
36451
|
below.heightWeight = next2.below;
|
|
36082
36452
|
this.layoutPanes();
|
|
36083
|
-
this.repositionTables();
|
|
36084
36453
|
this.scheduler.invalidate(4 /* Full */);
|
|
36085
36454
|
}
|
|
36086
36455
|
/** Double-click a separator → split the two adjacent panes evenly (each gets half of
|
|
@@ -36095,7 +36464,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36095
36464
|
above.heightWeight = half;
|
|
36096
36465
|
below.heightWeight = half;
|
|
36097
36466
|
this.layoutPanes();
|
|
36098
|
-
this.repositionTables();
|
|
36099
36467
|
this.scheduler.invalidate(4 /* Full */);
|
|
36100
36468
|
}
|
|
36101
36469
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -36374,7 +36742,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36374
36742
|
const liveActual = li >= 0 ? this.bars[li] : void 0;
|
|
36375
36743
|
const easeLive = !!liveActual && this.liveEaseTime === liveActual.time && (liveActual.high !== this.liveEaseHigh || liveActual.low !== this.liveEaseLow || liveActual.close !== this.liveEaseClose);
|
|
36376
36744
|
if (easeLive && liveActual) this.bars[li] = { ...liveActual, high: this.liveEaseHigh, low: this.liveEaseLow, close: this.liveEaseClose };
|
|
36377
|
-
this.scene.drawingSlices =
|
|
36745
|
+
this.scene.drawingSlices = mergeSlices(
|
|
36746
|
+
this.indicatorSlices.prepare(this.scene, this.coords, this.theme, this.dataCanvas),
|
|
36747
|
+
this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map()
|
|
36748
|
+
);
|
|
36378
36749
|
this.backdropRenderer.render(this.scene, this.coords, this.theme, gridAlpha);
|
|
36379
36750
|
this.backend.render(this.scene, this.coords, this.theme);
|
|
36380
36751
|
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
@@ -36605,6 +36976,20 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36605
36976
|
this.coords.setViewport(v);
|
|
36606
36977
|
this.targetBarSpacing = v.barSpacing;
|
|
36607
36978
|
}
|
|
36979
|
+
/** Re-frame after a series replacement (a symbol/timeframe switch): keep the user's
|
|
36980
|
+
* zoom (bar spacing), re-anchor the newest bars at the default right offset.
|
|
36981
|
+
* `clampViewport`'s fit-all-bars floor deliberately does NOT apply — a progressive
|
|
36982
|
+
* head may still be backfilling toward the previous depth, and raising the spacing
|
|
36983
|
+
* to its temporary bar count would lose the zoom this exists to keep. */
|
|
36984
|
+
reframeKeepZoom() {
|
|
36985
|
+
this.animator?.stop();
|
|
36986
|
+
this.panVelocity = 0;
|
|
36987
|
+
for (const pane of this.scene.panes.values()) pane.manualScale = null;
|
|
36988
|
+
for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
|
|
36989
|
+
const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset: defaultViewport().rightOffset };
|
|
36990
|
+
this.coords.setViewport(v);
|
|
36991
|
+
this.targetBarSpacing = v.barSpacing;
|
|
36992
|
+
}
|
|
36608
36993
|
paneBoundsFor(paneId) {
|
|
36609
36994
|
const p = this.scene.panes.get(paneId);
|
|
36610
36995
|
return { top: p?.bounds.top ?? 0, height: p?.bounds.height ?? 0, rightAxis: this.rightAxisW };
|
|
@@ -36812,27 +37197,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36812
37197
|
}
|
|
36813
37198
|
return maxVol;
|
|
36814
37199
|
}
|
|
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
37200
|
layoutPanes() {
|
|
36837
37201
|
const panes = this.scene.orderedPanes();
|
|
36838
37202
|
const dataHeight = this.coords.height;
|
|
@@ -36883,6 +37247,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36883
37247
|
const visible = maxPane ? [maxPane] : this.scene.orderedPanes().filter((p) => !p.collapsed);
|
|
36884
37248
|
const paneBottom = visible.length ? Math.max(...visible.map((p) => p.bounds.top + p.bounds.height)) : dataHeight;
|
|
36885
37249
|
this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM + Math.max(0, dataHeight - paneBottom);
|
|
37250
|
+
this.mountContainer?.style.setProperty("--vela-bottom-gutter", `${TIME_AXIS_H + Math.max(0, dataHeight - paneBottom)}px`);
|
|
36886
37251
|
this.scrollBtnRightPx = this.rightAxisW + SCROLL_BTN_RIGHT_INSET;
|
|
36887
37252
|
if (this.scrollButton) {
|
|
36888
37253
|
this.scrollButton.style.bottom = `${this.scrollBtnBottomPx}px`;
|
|
@@ -36975,30 +37340,33 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36975
37340
|
if (w <= 0 || h <= 0) return;
|
|
36976
37341
|
const dpr = window.devicePixelRatio || 1;
|
|
36977
37342
|
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
|
-
|
|
37343
|
+
const rect = this.plot.getBoundingClientRect();
|
|
37344
|
+
let bw = Math.max(1, Math.round(rect.width * dpr));
|
|
37345
|
+
let bh = Math.max(1, Math.round(rect.height * dpr));
|
|
37346
|
+
const dev = this.plotDeviceSize;
|
|
37347
|
+
if (dev && Math.abs(dev.width - rect.width * dpr) <= 1 && Math.abs(dev.height - rect.height * dpr) <= 1) {
|
|
37348
|
+
bw = Math.max(1, dev.width);
|
|
37349
|
+
bh = Math.max(1, dev.height);
|
|
37350
|
+
}
|
|
37351
|
+
const pw = bw / dpr;
|
|
37352
|
+
const ph = bh / dpr;
|
|
37353
|
+
const size3 = (canvas) => {
|
|
37354
|
+
canvas.width = bw;
|
|
37355
|
+
canvas.height = bh;
|
|
37356
|
+
canvas.style.width = `${pw}px`;
|
|
37357
|
+
canvas.style.height = `${ph}px`;
|
|
37358
|
+
};
|
|
37359
|
+
size3(this.dataCanvas);
|
|
37360
|
+
size3(this.backdropCanvas);
|
|
37361
|
+
size3(this.volumeCanvas);
|
|
37362
|
+
for (const l of this.extLayers) size3(l.canvas);
|
|
37363
|
+
size3(this.vpvrCanvas);
|
|
37364
|
+
size3(this.chromeCanvas);
|
|
37365
|
+
size3(this.drawingsCanvas);
|
|
37366
|
+
size3(this.cursorCanvas);
|
|
36998
37367
|
this.coords.setSize(Math.max(1, pw - this.rightAxisW), Math.max(1, ph - TIME_AXIS_H), dpr);
|
|
36999
37368
|
this.scene.crosshair = null;
|
|
37000
37369
|
this.layoutPanes();
|
|
37001
|
-
this.repositionTables();
|
|
37002
37370
|
this.userDrawings?.onResize();
|
|
37003
37371
|
if (!this.didInitialFit && this.coords.barCount > 0) {
|
|
37004
37372
|
this.fitContent();
|
|
@@ -39051,6 +39419,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
39051
39419
|
exports.getDrawingType = getDrawingType;
|
|
39052
39420
|
exports.getNativeIndicator = getNativeIndicator;
|
|
39053
39421
|
exports.iconMarkup = iconMarkup;
|
|
39422
|
+
exports.inputDeltas = inputDeltas;
|
|
39054
39423
|
exports.inputVisible = inputVisible;
|
|
39055
39424
|
exports.legendActions = legendActions;
|
|
39056
39425
|
exports.legendCallouts = legendCallouts;
|