@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/index.cjs
CHANGED
|
@@ -154,6 +154,12 @@ var IndicatorHandleImpl = class {
|
|
|
154
154
|
get visible() {
|
|
155
155
|
return this.visibleState;
|
|
156
156
|
}
|
|
157
|
+
inputValues() {
|
|
158
|
+
return this.controller.inputValuesOf(this.id);
|
|
159
|
+
}
|
|
160
|
+
propValues() {
|
|
161
|
+
return this.controller.propValuesOf(this.id);
|
|
162
|
+
}
|
|
157
163
|
setInput(key, value) {
|
|
158
164
|
this.controller.applyInputs(this.id, { [key]: value });
|
|
159
165
|
}
|
|
@@ -552,6 +558,8 @@ registerIcon("folder-plus", S('<path d="M1.6 4.4a1 1 0 0 1 1-1h2.7l1.3 1.7h6.8a1
|
|
|
552
558
|
registerIcon("folder-minus", S('<path d="M1.6 4.4a1 1 0 0 1 1-1h2.7l1.3 1.7h6.8a1 1 0 0 1 1 1v6.5a1 1 0 0 1-1 1h-10.8a1 1 0 0 1-1-1z"/><path d="M6.2 9.4h3.6"/>'));
|
|
553
559
|
registerIcon("collapse", S('<path d="M3 8h10"/>'));
|
|
554
560
|
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"/>'));
|
|
561
|
+
registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
|
|
562
|
+
registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
|
|
555
563
|
registerIcon("maximize", S('<path d="M2.5 6V3a.5.5 0 0 1 .5-.5h3M10 2.5h3a.5.5 0 0 1 .5.5v3M13.5 10v3a.5.5 0 0 1-.5.5h-3M6 13.5H3a.5.5 0 0 1-.5-.5v-3"/>'));
|
|
556
564
|
registerIcon("restore", S('<path d="M6.2 2.5v3.7H2.5M9.8 13.5V9.8h3.7M13.5 6.2H9.8V2.5M2.5 9.8h3.7v3.7"/>'));
|
|
557
565
|
registerIcon("star", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>'));
|
|
@@ -559,7 +567,7 @@ registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L
|
|
|
559
567
|
registerIcon("grip", S('<circle cx="6" cy="3.5" r="1"/><circle cx="10" cy="3.5" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12.5" r="1"/><circle cx="10" cy="12.5" r="1"/>', 'fill="currentColor" stroke="none"'));
|
|
560
568
|
registerIcon("kebab", S('<circle cx="8" cy="3.2" r="1.2"/><circle cx="8" cy="8" r="1.2"/><circle cx="8" cy="12.8" r="1.2"/>', 'fill="currentColor" stroke="none"'));
|
|
561
569
|
registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
|
|
562
|
-
registerIcon("reset", S('<
|
|
570
|
+
registerIcon("reset", S('<path d="M2 8a6 6 0 1 0 6-6 6.5 6.5 0 0 0-4.5 1.83L2 5.33"/><path d="M2 2v3.33h3.33"/>'));
|
|
563
571
|
registerIcon("pane-collapse", S('<path d="M2.6 9.4h4v4M13.4 6.6h-4v-4"/><path d="m9.4 6.6 4.2-4.2M2.4 13.6l4.2-4.2"/>'));
|
|
564
572
|
registerIcon("pane-expand", S('<path d="M9.8 2.4h3.8v3.8M6.2 13.6H2.4V9.8"/><path d="m13.6 2.4-4.4 4.4M2.4 13.6l4.4-4.4"/>'));
|
|
565
573
|
registerIcon("cursor", svg24('<path d="M5 3l6 16 2-6 6-2z"/>', 'fill="currentColor" stroke="none"'));
|
|
@@ -6930,6 +6938,23 @@ function timeframeToMs(timeframe) {
|
|
|
6930
6938
|
return 36e5;
|
|
6931
6939
|
}
|
|
6932
6940
|
|
|
6941
|
+
// src/data/symbol-groups.ts
|
|
6942
|
+
function isGroupRow(d) {
|
|
6943
|
+
return d.group != null && d.ticker === d.group;
|
|
6944
|
+
}
|
|
6945
|
+
function groupKeyOf(d) {
|
|
6946
|
+
return `${(d.prefix ?? d.provider ?? "").toLowerCase()}:${(d.group ?? "").toUpperCase()}`;
|
|
6947
|
+
}
|
|
6948
|
+
function groupMembers(pool, groupRow) {
|
|
6949
|
+
const key = groupKeyOf(groupRow);
|
|
6950
|
+
return pool.filter((s) => s.group != null && !isGroupRow(s) && groupKeyOf(s) === key);
|
|
6951
|
+
}
|
|
6952
|
+
function defaultMemberOf(pool, groupRow) {
|
|
6953
|
+
const members = groupMembers(pool, groupRow);
|
|
6954
|
+
const defaults2 = members.filter((m) => m.default);
|
|
6955
|
+
return (defaults2.length === 1 ? defaults2[0] : members[0]) ?? null;
|
|
6956
|
+
}
|
|
6957
|
+
|
|
6933
6958
|
// src/data/ProviderRegistry.ts
|
|
6934
6959
|
var normName = (n) => n.trim().toLowerCase();
|
|
6935
6960
|
var normTicker = (t) => t.trim().toUpperCase();
|
|
@@ -7019,17 +7044,24 @@ var ProviderRegistry = class {
|
|
|
7019
7044
|
resolve(raw, opts = {}) {
|
|
7020
7045
|
const { provider, ticker } = this.parse(raw);
|
|
7021
7046
|
if (provider) {
|
|
7022
|
-
if (this.entries.has(provider))
|
|
7047
|
+
if (this.entries.has(provider)) {
|
|
7048
|
+
const d = this.entries.get(provider).byTicker?.get(normTicker(ticker));
|
|
7049
|
+
return { provider, ticker: d != null && isGroupRow(d) ? this.loadableTicker(provider, d) : ticker };
|
|
7050
|
+
}
|
|
7023
7051
|
const key = `${provider}:${normTicker(ticker)}`;
|
|
7024
7052
|
for (const name of this.candidateOrder(opts.default)) {
|
|
7025
7053
|
const d = this.entries.get(name).prefixIndex?.get(key);
|
|
7026
|
-
if (d) return { provider: name, ticker:
|
|
7054
|
+
if (d) return { provider: name, ticker: this.loadableTicker(name, d) };
|
|
7027
7055
|
}
|
|
7028
7056
|
return null;
|
|
7029
7057
|
}
|
|
7030
7058
|
const norm = normTicker(ticker);
|
|
7031
7059
|
for (const name of this.candidateOrder(opts.default)) {
|
|
7032
|
-
|
|
7060
|
+
const e = this.entries.get(name);
|
|
7061
|
+
if (e.index?.has(norm)) {
|
|
7062
|
+
const d = e.byTicker?.get(norm);
|
|
7063
|
+
return { provider: name, ticker: d != null && isGroupRow(d) ? this.loadableTicker(name, d) : ticker };
|
|
7064
|
+
}
|
|
7033
7065
|
}
|
|
7034
7066
|
if (opts.lenient && this.entries.size === 1) {
|
|
7035
7067
|
const only = this.names()[0];
|
|
@@ -7038,6 +7070,13 @@ var ProviderRegistry = class {
|
|
|
7038
7070
|
}
|
|
7039
7071
|
return null;
|
|
7040
7072
|
}
|
|
7073
|
+
/** What a resolved descriptor LOADS: itself — unless it is a GROUP row (listed, never
|
|
7074
|
+
* served), which loads its default member (single `default`, else first listed). A
|
|
7075
|
+
* memberless group keeps its own ticker and fails downstream like any unknown symbol. */
|
|
7076
|
+
loadableTicker(name, d) {
|
|
7077
|
+
if (!isGroupRow(d)) return d.ticker;
|
|
7078
|
+
return defaultMemberOf(this.entries.get(name)?.descriptors ?? [], d)?.ticker ?? d.ticker;
|
|
7079
|
+
}
|
|
7041
7080
|
/**
|
|
7042
7081
|
* Resolve now if possible, else resolve later — re-attempting after each provider
|
|
7043
7082
|
* settles. The promise stays pending until some registered provider can serve the
|
|
@@ -7263,6 +7302,7 @@ var RUN_EMIT_THROTTLE_MS = 1e3;
|
|
|
7263
7302
|
var PREVIEW_BARS = 300;
|
|
7264
7303
|
var SINGLE_LOAD_BARS = 5e3;
|
|
7265
7304
|
var CHUNK_BARS = 1e4;
|
|
7305
|
+
var FIRST_PAINT_BARS = 100;
|
|
7266
7306
|
var GAP_FACTOR = 1.5;
|
|
7267
7307
|
var HEAL_COOLDOWN_MS = 5e3;
|
|
7268
7308
|
var EngineOrchestrator = class _EngineOrchestrator {
|
|
@@ -7326,6 +7366,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
7326
7366
|
/** Invalidates detached async work (backfill loops, in-flight loads, gap heals):
|
|
7327
7367
|
* bumped by init(), setMarket() and destroy(). */
|
|
7328
7368
|
this.generation = 0;
|
|
7369
|
+
/** Aborts the in-flight PROGRESSIVE load's source polling on supersession — an
|
|
7370
|
+
* abandoned stream left polling to its own budget starves the browser's per-host
|
|
7371
|
+
* connection pool, and the NEXT symbol's very first fetch with it (measured). */
|
|
7372
|
+
this.progressiveAbort = null;
|
|
7329
7373
|
/** Awaiters racing a superseded load (setMarket callers) — released on every bump so they never hang. */
|
|
7330
7374
|
this.supersedeWaiters = [];
|
|
7331
7375
|
/** `history:complete` fired for the CURRENT load. Each market load re-arms the cycle
|
|
@@ -7468,6 +7512,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
7468
7512
|
* superseded setMarket awaiters so their promises resolve instead of hanging. */
|
|
7469
7513
|
bumpGeneration() {
|
|
7470
7514
|
const gen = ++this.generation;
|
|
7515
|
+
this.progressiveAbort?.abort();
|
|
7516
|
+
this.progressiveAbort = null;
|
|
7471
7517
|
for (const w of this.supersedeWaiters.splice(0)) w();
|
|
7472
7518
|
return gen;
|
|
7473
7519
|
}
|
|
@@ -7521,7 +7567,48 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
7521
7567
|
const requested = market.bars ?? 500;
|
|
7522
7568
|
const initialRange = market.visibleRange;
|
|
7523
7569
|
const deep = !market.data?.length && initialRange == null && requested > SINGLE_LOAD_BARS;
|
|
7524
|
-
|
|
7570
|
+
let progressiveServed = false;
|
|
7571
|
+
if (!market.data?.length && initialRange == null && this.feed.loadProgressive) {
|
|
7572
|
+
let painted = false;
|
|
7573
|
+
const paint = (bars, final) => {
|
|
7574
|
+
if (this.generation !== gen || !final && bars.length === 0) return;
|
|
7575
|
+
if (!painted && !final && bars.length < Math.min(requested, FIRST_PAINT_BARS)) return;
|
|
7576
|
+
this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
|
|
7577
|
+
if (!painted && bars.length > 0) {
|
|
7578
|
+
painted = true;
|
|
7579
|
+
if (opts.firstLoad) this.activateBarLayers();
|
|
7580
|
+
if (!final) this.historyState = "backfill";
|
|
7581
|
+
}
|
|
7582
|
+
};
|
|
7583
|
+
const abort = new AbortController();
|
|
7584
|
+
this.progressiveAbort = abort;
|
|
7585
|
+
progressiveServed = await new Promise((firstPaint) => {
|
|
7586
|
+
let signaled = false;
|
|
7587
|
+
const signal = (served) => {
|
|
7588
|
+
if (!signaled) {
|
|
7589
|
+
signaled = true;
|
|
7590
|
+
firstPaint(served);
|
|
7591
|
+
}
|
|
7592
|
+
};
|
|
7593
|
+
abort.signal.addEventListener("abort", () => signal(true), { once: true });
|
|
7594
|
+
this.feed.loadProgressive(market, (bars) => {
|
|
7595
|
+
paint(bars, false);
|
|
7596
|
+
if (painted) signal(true);
|
|
7597
|
+
}, { signal: abort.signal }).then((full) => {
|
|
7598
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
7599
|
+
if (full == null) return signal(false);
|
|
7600
|
+
if (this.generation !== gen) return signal(true);
|
|
7601
|
+
paint(full, true);
|
|
7602
|
+
this.completeHistory(full.length >= requested ? "depth" : "genesis");
|
|
7603
|
+
signal(true);
|
|
7604
|
+
}).catch(() => {
|
|
7605
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
7606
|
+
if (this.generation === gen) this.completeHistory("aborted");
|
|
7607
|
+
signal(true);
|
|
7608
|
+
});
|
|
7609
|
+
});
|
|
7610
|
+
}
|
|
7611
|
+
if (progressiveServed) ; else if (deep && this.feed.loadRange) {
|
|
7525
7612
|
const head = await this.feed.load({ ...market, bars: Math.min(requested, CHUNK_BARS) });
|
|
7526
7613
|
if (this.generation !== gen) return;
|
|
7527
7614
|
this.setBarSeries(head);
|
|
@@ -8116,6 +8203,15 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8116
8203
|
record.pendingCause = "inputs";
|
|
8117
8204
|
if (record.session) record.session.update(record.inputValues);
|
|
8118
8205
|
else if (record.native && !record.hidden) record.native.instance.setInputs(record.inputValues);
|
|
8206
|
+
this.events.emit("indicator:inputs", { id });
|
|
8207
|
+
}
|
|
8208
|
+
/** IndicatorController: the CURRENT stored input values (defaults merged with edits). */
|
|
8209
|
+
inputValuesOf(id) {
|
|
8210
|
+
return { ...this.registry.get(id)?.inputValues };
|
|
8211
|
+
}
|
|
8212
|
+
/** IndicatorController: the CURRENT declaration-prop overrides. */
|
|
8213
|
+
propValuesOf(id) {
|
|
8214
|
+
return { ...this.registry.get(id)?.propValues };
|
|
8119
8215
|
}
|
|
8120
8216
|
/** IndicatorController: re-run an indicator with merged declaration-prop overrides.
|
|
8121
8217
|
* Same lifecycle as {@link applyInputs} — a prop change replays the whole script.
|
|
@@ -8129,6 +8225,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8129
8225
|
if (!record.hidden) this.setLoading(record, true);
|
|
8130
8226
|
record.pendingCause = "inputs";
|
|
8131
8227
|
record.session.update(record.inputValues, record.propValues);
|
|
8228
|
+
this.events.emit("indicator:inputs", { id });
|
|
8132
8229
|
}
|
|
8133
8230
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
8134
8231
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
@@ -9193,6 +9290,28 @@ var CachingDataFeed = class {
|
|
|
9193
9290
|
this.store.merge(key, dropForming(bars));
|
|
9194
9291
|
return bars;
|
|
9195
9292
|
}
|
|
9293
|
+
/**
|
|
9294
|
+
* Progressive twin of {@link load}: a COLD load streams through the inner feed's
|
|
9295
|
+
* progressive path — batches forwarded verbatim, the FINAL answer cached exactly as
|
|
9296
|
+
* `load` caches — while a cache-covered load answers once through `load` itself (a
|
|
9297
|
+
* warm chart has nothing to stream). Falls back to `load` wholesale when the inner
|
|
9298
|
+
* feed lacks the capability, so callers may prefer this method unconditionally.
|
|
9299
|
+
*/
|
|
9300
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9301
|
+
if (cfg.data && cfg.data.length > 0) return this.inner.load(cfg);
|
|
9302
|
+
if (!this.inner.loadProgressive) return null;
|
|
9303
|
+
const symbol = cfg.symbol ?? "TEST";
|
|
9304
|
+
const key = cacheKey(symbol, cfg.timeframe ?? "60", cfg.session);
|
|
9305
|
+
const cached = this.store.get(key);
|
|
9306
|
+
const n = cfg.bars ?? 500;
|
|
9307
|
+
const lastCached = cached?.[cached.length - 1];
|
|
9308
|
+
if (cached && lastCached && cached.length >= n - 1 && this.inner.loadRange) return this.load(cfg);
|
|
9309
|
+
this.store.retainSymbol(symbol);
|
|
9310
|
+
const bars = await this.inner.loadProgressive(cfg, onBatch, opts);
|
|
9311
|
+
if (bars == null) return null;
|
|
9312
|
+
this.store.merge(key, dropForming(bars));
|
|
9313
|
+
return bars;
|
|
9314
|
+
}
|
|
9196
9315
|
/**
|
|
9197
9316
|
* Cache-backed ranged fetch — the gateway engines use for secondary series
|
|
9198
9317
|
* (`request.security` HTF/LTF/cross-symbol) and the orchestrator's backward
|
|
@@ -9438,6 +9557,17 @@ var MultiProviderFeed = class {
|
|
|
9438
9557
|
this.prefetchSymbolInfo(resolved);
|
|
9439
9558
|
return this.cache.load(canonical(cfg, resolved));
|
|
9440
9559
|
}
|
|
9560
|
+
/** Progressive twin of {@link load} — same resolution, the cache streams the batches. */
|
|
9561
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9562
|
+
if (cfg.data && cfg.data.length > 0) {
|
|
9563
|
+
this.liveBars = cfg.data.map((b) => ({ ...b }));
|
|
9564
|
+
return cfg.data;
|
|
9565
|
+
}
|
|
9566
|
+
const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
|
|
9567
|
+
this.primaryProvider = resolved.provider;
|
|
9568
|
+
this.prefetchSymbolInfo(resolved);
|
|
9569
|
+
return this.cache.loadProgressive(canonical(cfg, resolved), onBatch, opts);
|
|
9570
|
+
}
|
|
9441
9571
|
/**
|
|
9442
9572
|
* Synchronous per-symbol metadata for engines (Pine `syminfo.*`), served from the cache
|
|
9443
9573
|
* warmed by load(). Undefined until the prefetch lands (the engine then synthesizes a
|
|
@@ -9525,6 +9655,18 @@ var RegistryFetchFeed = class {
|
|
|
9525
9655
|
if (!provider) return Promise.resolve([]);
|
|
9526
9656
|
return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session });
|
|
9527
9657
|
}
|
|
9658
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9659
|
+
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
9660
|
+
const provider = this.registry.get(name ?? "");
|
|
9661
|
+
if (!provider) return [];
|
|
9662
|
+
if (!provider.getBarsProgressive) return null;
|
|
9663
|
+
try {
|
|
9664
|
+
return await provider.getBarsProgressive(ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session }, onBatch, opts);
|
|
9665
|
+
} catch (e) {
|
|
9666
|
+
console.warn(`[vela] progressive fetch failed for ${ticker} ${cfg.timeframe ?? "60"} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
9667
|
+
return [];
|
|
9668
|
+
}
|
|
9669
|
+
}
|
|
9528
9670
|
loadRange(cfg, range) {
|
|
9529
9671
|
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
9530
9672
|
const provider = this.registry.get(name ?? "");
|
|
@@ -10787,6 +10929,10 @@ var CalloutBubble = class {
|
|
|
10787
10929
|
get open() {
|
|
10788
10930
|
return this.pop?.open ?? false;
|
|
10789
10931
|
}
|
|
10932
|
+
/** Close the deployed panel, if any. The bubble itself stays. */
|
|
10933
|
+
hidePanel() {
|
|
10934
|
+
this.pop?.hide();
|
|
10935
|
+
}
|
|
10790
10936
|
destroy() {
|
|
10791
10937
|
this.pop?.destroy();
|
|
10792
10938
|
this.pop = null;
|
|
@@ -10867,6 +11013,14 @@ function inputVisible(when, values) {
|
|
|
10867
11013
|
const conds = Array.isArray(when) ? when : [when];
|
|
10868
11014
|
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
|
|
10869
11015
|
}
|
|
11016
|
+
function inputDeltas(schema, values) {
|
|
11017
|
+
const out = {};
|
|
11018
|
+
for (const s of schema) {
|
|
11019
|
+
const v = values[s.key];
|
|
11020
|
+
if (v !== void 0 && JSON.stringify(v) !== JSON.stringify(s.defval)) out[s.key] = v;
|
|
11021
|
+
}
|
|
11022
|
+
return Object.keys(out).length > 0 ? out : void 0;
|
|
11023
|
+
}
|
|
10870
11024
|
|
|
10871
11025
|
// src/ui/components/select/controller.ts
|
|
10872
11026
|
function selectController(opts) {
|
|
@@ -13694,6 +13848,9 @@ var CLOSE_SVG = iconAt("close", LEGEND_ICON_PX2);
|
|
|
13694
13848
|
var FOLD_SVG = iconAt("chevron-up", LEGEND_ICON_PX2);
|
|
13695
13849
|
var UNFOLD_SVG = iconAt("chevron-down", LEGEND_ICON_PX2);
|
|
13696
13850
|
var OVERVIEW_SVG = iconAt("objects", LEGEND_ICON_PX2);
|
|
13851
|
+
function legendCalloutsDisplay(open2, hasCallouts) {
|
|
13852
|
+
return !open2 && hasCallouts ? "inline-flex" : "none";
|
|
13853
|
+
}
|
|
13697
13854
|
var InputsUI = class {
|
|
13698
13855
|
constructor(container, theme, paneBoundsOf) {
|
|
13699
13856
|
this.container = container;
|
|
@@ -13897,7 +14054,7 @@ var InputsUI = class {
|
|
|
13897
14054
|
row.callouts = [];
|
|
13898
14055
|
row.calloutsEl.replaceChildren();
|
|
13899
14056
|
const views = this.legendCallouts?.(row.id) ?? [];
|
|
13900
|
-
row.calloutsEl.style.display = views.length > 0
|
|
14057
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(row.highlighted, views.length > 0);
|
|
13901
14058
|
for (const view of views) {
|
|
13902
14059
|
const bubble = new CalloutBubble({
|
|
13903
14060
|
icon: view.icon,
|
|
@@ -14381,11 +14538,11 @@ var InputsUI = class {
|
|
|
14381
14538
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
14382
14539
|
if (open2) {
|
|
14383
14540
|
row.el.appendChild(row.statusEl);
|
|
14384
|
-
row.
|
|
14541
|
+
for (const bubble of row.callouts) bubble.hidePanel();
|
|
14385
14542
|
} else {
|
|
14386
14543
|
row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
14387
|
-
row.el.insertBefore(row.calloutsEl, row.statusEl);
|
|
14388
14544
|
}
|
|
14545
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(open2, row.callouts.length > 0);
|
|
14389
14546
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
14390
14547
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
14391
14548
|
if (child === row.extrasEl) {
|
|
@@ -14475,7 +14632,7 @@ var ICONS = {
|
|
|
14475
14632
|
};
|
|
14476
14633
|
var STYLE_ID = "vela-pane-controls";
|
|
14477
14634
|
var ICON_PX = 12;
|
|
14478
|
-
var CLUSTER_PILL = "rgba(0,0,0,0.
|
|
14635
|
+
var CLUSTER_PILL = "rgba(0,0,0,0.65)";
|
|
14479
14636
|
function ensureStyles() {
|
|
14480
14637
|
if (typeof document === "undefined" || document.getElementById(STYLE_ID)) return;
|
|
14481
14638
|
const st = document.createElement("style");
|
|
@@ -14495,8 +14652,12 @@ var PaneControls = class {
|
|
|
14495
14652
|
this.deps = deps;
|
|
14496
14653
|
this.clusters = /* @__PURE__ */ new Map();
|
|
14497
14654
|
this.hoverPaneId = null;
|
|
14655
|
+
/** Mobile: hover clusters are meaningless without a cursor — suppressed; a
|
|
14656
|
+
* collapsed pane's standalone expand chip stays (the only way back up). */
|
|
14657
|
+
this.suspended = false;
|
|
14498
14658
|
/** Reveal the cluster for the pane under the cursor, resolved from the pointer's y in the plot. */
|
|
14499
14659
|
this.onPlotMove = (e) => {
|
|
14660
|
+
if (this.suspended) return;
|
|
14500
14661
|
const rect = this.plot.getBoundingClientRect();
|
|
14501
14662
|
const y = e.clientY - rect.top;
|
|
14502
14663
|
let hit = null;
|
|
@@ -14568,7 +14729,12 @@ var PaneControls = class {
|
|
|
14568
14729
|
}
|
|
14569
14730
|
if (p.count > 1) {
|
|
14570
14731
|
cluster.appendChild(
|
|
14571
|
-
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
14732
|
+
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
14733
|
+
role: "maximize",
|
|
14734
|
+
// Same inverse-chip treatment as the collapsed pane's expand toggle: the
|
|
14735
|
+
// maximized state must read as an active state, not just a swapped glyph.
|
|
14736
|
+
selected: p.maximized
|
|
14737
|
+
})
|
|
14572
14738
|
);
|
|
14573
14739
|
}
|
|
14574
14740
|
}
|
|
@@ -14605,17 +14771,18 @@ var PaneControls = class {
|
|
|
14605
14771
|
}
|
|
14606
14772
|
const hovered = id === this.hoverPaneId;
|
|
14607
14773
|
const hasButtons = cluster.children.length > 0;
|
|
14608
|
-
const
|
|
14774
|
+
const stateChipRole = p.collapsed ? "collapse" : !this.suspended && p.maximized ? "maximize" : null;
|
|
14775
|
+
const visible = hasButtons && (hovered || stateChipRole != null) && p.height > 8;
|
|
14609
14776
|
cluster.style.right = `${rightPx}px`;
|
|
14610
14777
|
cluster.style.top = p.collapsed ? `${p.top + Math.max(1, Math.round((p.height - 24) / 2))}px` : `${p.top + 4}px`;
|
|
14611
14778
|
cluster.style.display = visible ? "flex" : "none";
|
|
14612
14779
|
if (!visible) continue;
|
|
14613
|
-
const
|
|
14614
|
-
cluster.style.background =
|
|
14780
|
+
const soloChip = stateChipRole != null && !hovered;
|
|
14781
|
+
cluster.style.background = soloChip ? "transparent" : CLUSTER_PILL;
|
|
14615
14782
|
for (const child of cluster.children) {
|
|
14616
14783
|
const btn2 = child;
|
|
14617
14784
|
btn2.style.display = "inline-flex";
|
|
14618
|
-
btn2.style.visibility =
|
|
14785
|
+
btn2.style.visibility = soloChip && btn2.dataset.role !== stateChipRole ? "hidden" : "visible";
|
|
14619
14786
|
}
|
|
14620
14787
|
}
|
|
14621
14788
|
}
|
|
@@ -14625,6 +14792,14 @@ var PaneControls = class {
|
|
|
14625
14792
|
this.hoverPaneId = paneId;
|
|
14626
14793
|
this.reposition();
|
|
14627
14794
|
}
|
|
14795
|
+
/** Mobile suppression: no hover clusters (touch has no cursor; the shell's own
|
|
14796
|
+
* chrome covers maximize), while collapsed panes keep their expand chips. */
|
|
14797
|
+
setSuspended(on) {
|
|
14798
|
+
if (on === this.suspended) return;
|
|
14799
|
+
this.suspended = on;
|
|
14800
|
+
if (on) this.hoverPaneId = null;
|
|
14801
|
+
this.reposition();
|
|
14802
|
+
}
|
|
14628
14803
|
destroy() {
|
|
14629
14804
|
this.plot.removeEventListener("pointermove", this.onPlotMove);
|
|
14630
14805
|
this.plot.removeEventListener("pointerleave", this.onPlotLeave);
|
|
@@ -14775,160 +14950,6 @@ var AxisScaleButtons = class {
|
|
|
14775
14950
|
}
|
|
14776
14951
|
};
|
|
14777
14952
|
|
|
14778
|
-
// src/renderers/shared/TableOverlay.ts
|
|
14779
|
-
var SIZE_PX3 = {
|
|
14780
|
-
auto: 13,
|
|
14781
|
-
tiny: 10,
|
|
14782
|
-
small: 11,
|
|
14783
|
-
normal: 13,
|
|
14784
|
-
large: 16,
|
|
14785
|
-
huge: 20
|
|
14786
|
-
};
|
|
14787
|
-
function fontPxOf(size) {
|
|
14788
|
-
if (typeof size === "number") return size > 0 ? size : SIZE_PX3.auto;
|
|
14789
|
-
return SIZE_PX3[size] ?? SIZE_PX3.auto;
|
|
14790
|
-
}
|
|
14791
|
-
function tableHasContent(t) {
|
|
14792
|
-
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
14793
|
-
}
|
|
14794
|
-
function mergeRenderPlan(t) {
|
|
14795
|
-
const span = /* @__PURE__ */ new Map();
|
|
14796
|
-
const omit = /* @__PURE__ */ new Set();
|
|
14797
|
-
for (const m of t.merges) {
|
|
14798
|
-
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
14799
|
-
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
14800
|
-
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
14801
|
-
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
14802
|
-
}
|
|
14803
|
-
}
|
|
14804
|
-
}
|
|
14805
|
-
for (let r = 0; r < t.rows; r += 1) {
|
|
14806
|
-
for (let c = 0; c < t.columns; c += 1) {
|
|
14807
|
-
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
14808
|
-
}
|
|
14809
|
-
}
|
|
14810
|
-
for (const key of span.keys()) omit.delete(key);
|
|
14811
|
-
return { span, omit };
|
|
14812
|
-
}
|
|
14813
|
-
var TableOverlay = class {
|
|
14814
|
-
constructor(container, theme, paneBounds) {
|
|
14815
|
-
this.container = container;
|
|
14816
|
-
this.theme = theme;
|
|
14817
|
-
this.paneBounds = paneBounds;
|
|
14818
|
-
this.lastTables = [];
|
|
14819
|
-
if (getComputedStyle(container).position === "static") container.style.position = "relative";
|
|
14820
|
-
this.root = document.createElement("div");
|
|
14821
|
-
Object.assign(this.root.style, {
|
|
14822
|
-
position: "absolute",
|
|
14823
|
-
inset: "0",
|
|
14824
|
-
pointerEvents: "none",
|
|
14825
|
-
overflow: "hidden",
|
|
14826
|
-
zIndex: "3"
|
|
14827
|
-
});
|
|
14828
|
-
container.appendChild(this.root);
|
|
14829
|
-
}
|
|
14830
|
-
update(tables) {
|
|
14831
|
-
this.lastTables = tables;
|
|
14832
|
-
this.root.replaceChildren();
|
|
14833
|
-
for (const t of tables) {
|
|
14834
|
-
if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
|
|
14835
|
-
}
|
|
14836
|
-
}
|
|
14837
|
-
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
14838
|
-
reposition() {
|
|
14839
|
-
if (this.root.isConnected) this.update(this.lastTables);
|
|
14840
|
-
}
|
|
14841
|
-
/** Show/hide the whole overlay. Tables anchor to pane corners, not to bars, so unlike the
|
|
14842
|
-
* series content they DON'T vanish with an emptied chart — the loading state hides them. */
|
|
14843
|
-
setVisible(visible) {
|
|
14844
|
-
this.root.style.display = visible ? "" : "none";
|
|
14845
|
-
}
|
|
14846
|
-
destroy() {
|
|
14847
|
-
this.root.remove();
|
|
14848
|
-
}
|
|
14849
|
-
renderTable(t) {
|
|
14850
|
-
const b = this.paneBounds(t.paneId);
|
|
14851
|
-
const wrap = document.createElement("div");
|
|
14852
|
-
wrap.style.position = "absolute";
|
|
14853
|
-
if (t.frameColor && t.frameWidth > 0) wrap.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
|
|
14854
|
-
this.anchor(wrap, t.position, b);
|
|
14855
|
-
const table = document.createElement("table");
|
|
14856
|
-
Object.assign(table.style, {
|
|
14857
|
-
borderCollapse: "collapse",
|
|
14858
|
-
background: t.bgColor ?? "transparent",
|
|
14859
|
-
fontFamily: this.theme.fontFamily || "sans-serif",
|
|
14860
|
-
border: "none",
|
|
14861
|
-
tableLayout: "auto",
|
|
14862
|
-
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
14863
|
-
pointerEvents: "auto"
|
|
14864
|
-
});
|
|
14865
|
-
const { span, omit } = mergeRenderPlan(t);
|
|
14866
|
-
const plotW = Math.max(0, (this.root.clientWidth || this.container.clientWidth) - b.rightAxis);
|
|
14867
|
-
const paneH = b.height;
|
|
14868
|
-
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
14869
|
-
for (let r = 0; r < t.rows; r += 1) {
|
|
14870
|
-
const tr = document.createElement("tr");
|
|
14871
|
-
for (let c = 0; c < t.columns; c += 1) {
|
|
14872
|
-
if (omit.has(`${r}:${c}`)) continue;
|
|
14873
|
-
const cell = t.cells[r]?.[c] ?? null;
|
|
14874
|
-
const td = document.createElement("td");
|
|
14875
|
-
if (cell === null) {
|
|
14876
|
-
Object.assign(td.style, { padding: "0", border: "none" });
|
|
14877
|
-
tr.appendChild(td);
|
|
14878
|
-
continue;
|
|
14879
|
-
}
|
|
14880
|
-
const sp = span.get(`${r}:${c}`);
|
|
14881
|
-
if (sp) {
|
|
14882
|
-
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
14883
|
-
if (sp.rs > 1) td.rowSpan = sp.rs;
|
|
14884
|
-
}
|
|
14885
|
-
Object.assign(td.style, {
|
|
14886
|
-
border: cellBorder,
|
|
14887
|
-
padding: "2px 6px",
|
|
14888
|
-
background: cell.bgColor ?? "transparent",
|
|
14889
|
-
color: cell.textColor ?? this.theme.textColor,
|
|
14890
|
-
textAlign: cell.hAlign,
|
|
14891
|
-
verticalAlign: cell.vAlign === "top" ? "top" : cell.vAlign === "bottom" ? "bottom" : "middle",
|
|
14892
|
-
fontSize: `${fontPxOf(cell.textSize)}px`,
|
|
14893
|
-
fontFamily: cell.fontFamily === "monospace" ? "monospace" : "inherit",
|
|
14894
|
-
fontWeight: cell.bold ? "bold" : "normal",
|
|
14895
|
-
fontStyle: cell.italic ? "italic" : "normal",
|
|
14896
|
-
// Pine cell text never wraps; `\n` still breaks lines. Wrapping
|
|
14897
|
-
// used to collapse unicode sparklines and ━━━ dividers.
|
|
14898
|
-
whiteSpace: "pre"
|
|
14899
|
-
});
|
|
14900
|
-
if (cell.width) td.style.width = `${cell.width / 100 * plotW}px`;
|
|
14901
|
-
if (cell.height) td.style.height = `${cell.height / 100 * paneH}px`;
|
|
14902
|
-
if (cell.tooltip) td.title = cell.tooltip;
|
|
14903
|
-
td.textContent = cell.text ?? "";
|
|
14904
|
-
tr.appendChild(td);
|
|
14905
|
-
}
|
|
14906
|
-
table.appendChild(tr);
|
|
14907
|
-
}
|
|
14908
|
-
wrap.appendChild(table);
|
|
14909
|
-
return wrap;
|
|
14910
|
-
}
|
|
14911
|
-
/**
|
|
14912
|
-
* Position the wrapper at a Pine `position.*` corner/edge of the table's PANE
|
|
14913
|
-
* (not the whole chart), inset past the right price axis so it never overlaps
|
|
14914
|
-
* the Y-axis labels.
|
|
14915
|
-
*/
|
|
14916
|
-
anchor(el, position, b) {
|
|
14917
|
-
const m = 6;
|
|
14918
|
-
const containerH = this.root.clientHeight || this.container.clientHeight;
|
|
14919
|
-
const paneBottom = b.top + b.height;
|
|
14920
|
-
if (position.startsWith("top")) el.style.top = `${b.top + m}px`;
|
|
14921
|
-
else if (position.startsWith("bottom")) el.style.bottom = `${Math.max(0, containerH - paneBottom) + m}px`;
|
|
14922
|
-
else el.style.top = `${b.top + b.height / 2}px`;
|
|
14923
|
-
if (position.endsWith("left")) el.style.left = `${m}px`;
|
|
14924
|
-
else if (position.endsWith("right")) el.style.right = `${b.rightAxis + m}px`;
|
|
14925
|
-
else el.style.left = `calc(50% - ${b.rightAxis / 2}px)`;
|
|
14926
|
-
const tx = position.endsWith("center") ? "-50%" : "0";
|
|
14927
|
-
const ty = position.startsWith("middle") ? "-50%" : "0";
|
|
14928
|
-
if (tx !== "0" || ty !== "0") el.style.transform = `translate(${tx}, ${ty})`;
|
|
14929
|
-
}
|
|
14930
|
-
};
|
|
14931
|
-
|
|
14932
14953
|
// src/renderers/native/capabilities.ts
|
|
14933
14954
|
var NATIVE_CAPABILITIES = {
|
|
14934
14955
|
panes: true,
|
|
@@ -14949,7 +14970,7 @@ var NATIVE_CAPABILITIES = {
|
|
|
14949
14970
|
drawingDepth: true,
|
|
14950
14971
|
// drawings share the series' z space (backend-composited interleave layers)
|
|
14951
14972
|
tables: true,
|
|
14952
|
-
//
|
|
14973
|
+
// canvas-painted into the owning indicator's interleave slice
|
|
14953
14974
|
trades: true,
|
|
14954
14975
|
// strategy order-fill markers (arrows + labels + fill-price ticks)
|
|
14955
14976
|
inputsUI: true
|
|
@@ -14980,6 +15001,9 @@ function candleTier(spacing) {
|
|
|
14980
15001
|
if (spacing < CANDLE_BODY_MIN_SPACING) return "wick";
|
|
14981
15002
|
return "full";
|
|
14982
15003
|
}
|
|
15004
|
+
function snapY(yCss, dpr) {
|
|
15005
|
+
return Math.round(yCss * dpr) / dpr;
|
|
15006
|
+
}
|
|
14983
15007
|
function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
14984
15008
|
const wickDev = Math.max(1, Math.round(wickWidth(spacing) * dpr));
|
|
14985
15009
|
const wickLeftDev = Math.round(xCss * dpr - wickDev / 2);
|
|
@@ -15786,11 +15810,9 @@ var WebGL2Backend = class {
|
|
|
15786
15810
|
};
|
|
15787
15811
|
b.alpha = this.modelAlpha;
|
|
15788
15812
|
for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
|
|
15789
|
-
for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
15790
15813
|
if (isPrice) {
|
|
15791
15814
|
for (const m of scene.indicators.values()) {
|
|
15792
15815
|
for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
|
|
15793
|
-
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
15794
15816
|
}
|
|
15795
15817
|
}
|
|
15796
15818
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
@@ -15806,6 +15828,7 @@ var WebGL2Backend = class {
|
|
|
15806
15828
|
b.alpha = this.modelAlpha;
|
|
15807
15829
|
const off = scene.offsetOf(m.id);
|
|
15808
15830
|
const mp = effPane(m);
|
|
15831
|
+
for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, mp, coords, i0, i1, off);
|
|
15809
15832
|
for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
15810
15833
|
}
|
|
15811
15834
|
if (drawCandles && !candleDrawn) {
|
|
@@ -15813,14 +15836,18 @@ var WebGL2Backend = class {
|
|
|
15813
15836
|
b.alpha = this.candleStructureAlpha;
|
|
15814
15837
|
this.emitPriceSeries(b, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
15815
15838
|
}
|
|
15816
|
-
drawSlicesUpTo(Infinity);
|
|
15817
15839
|
b.alpha = this.modelAlpha;
|
|
15818
15840
|
if (isPrice) {
|
|
15841
|
+
for (const m of scene.indicators.values()) {
|
|
15842
|
+
const off = scene.offsetOf(m.id);
|
|
15843
|
+
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, off);
|
|
15844
|
+
}
|
|
15819
15845
|
for (const m of scene.indicators.values()) {
|
|
15820
15846
|
const off = scene.offsetOf(m.id);
|
|
15821
15847
|
for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
|
|
15822
15848
|
}
|
|
15823
15849
|
}
|
|
15850
|
+
drawSlicesUpTo(Infinity);
|
|
15824
15851
|
for (const m of models) {
|
|
15825
15852
|
const mp = effPane(m);
|
|
15826
15853
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -16215,12 +16242,12 @@ var WebGL2Backend = class {
|
|
|
16215
16242
|
if (drawBody) {
|
|
16216
16243
|
const oY = coords.priceToY(bar.open, pane.scale, pane.bounds);
|
|
16217
16244
|
const cY = coords.priceToY(bar.close, pane.scale, pane.bounds);
|
|
16218
|
-
bodyTop = Math.min(oY, cY);
|
|
16219
|
-
bodyH = Math.max(1, Math.
|
|
16245
|
+
bodyTop = snapY(Math.min(oY, cY), coords.dpr);
|
|
16246
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - bodyTop);
|
|
16220
16247
|
}
|
|
16221
16248
|
if (cs.wickVisible) {
|
|
16222
|
-
const hY = coords.priceToY(bar.high, pane.scale, pane.bounds);
|
|
16223
|
-
const lY = coords.priceToY(bar.low, pane.scale, pane.bounds);
|
|
16249
|
+
const hY = snapY(coords.priceToY(bar.high, pane.scale, pane.bounds), coords.dpr);
|
|
16250
|
+
const lY = snapY(coords.priceToY(bar.low, pane.scale, pane.bounds), coords.dpr);
|
|
16224
16251
|
b.alpha = this.candleStructureAlpha;
|
|
16225
16252
|
const wCol = parseColor((isUp ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : bodyColorStr));
|
|
16226
16253
|
if (drawBody) {
|
|
@@ -16777,6 +16804,7 @@ var DOUBLE_TAP_MS = 350;
|
|
|
16777
16804
|
var DOUBLE_TAP_SLOP = 30;
|
|
16778
16805
|
var TIME_SCALE_K = 4e-3;
|
|
16779
16806
|
var WHEEL_ZOOM_K = 4e-3;
|
|
16807
|
+
var WHEEL_PRICE_DRAG_PX = 0.25;
|
|
16780
16808
|
function wheelZoomAnchor(coords, cursorX, rightEdge) {
|
|
16781
16809
|
if (rightEdge) return { logical: coords.rightEdgeLogical, x: coords.width };
|
|
16782
16810
|
return { logical: coords.xToLogical(cursorX), x: cursorX };
|
|
@@ -16907,7 +16935,7 @@ var InputController = class {
|
|
|
16907
16935
|
this.capture(e.pointerId);
|
|
16908
16936
|
return;
|
|
16909
16937
|
}
|
|
16910
|
-
if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y)) {
|
|
16938
|
+
if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
|
|
16911
16939
|
this.region = "drawing";
|
|
16912
16940
|
this.capture(e.pointerId);
|
|
16913
16941
|
return;
|
|
@@ -17021,7 +17049,7 @@ var InputController = class {
|
|
|
17021
17049
|
const wasTouch = e.pointerType === "touch";
|
|
17022
17050
|
const tapRelease = this.dragging && !this.moved && (!wasTouch || Math.hypot(x - this.startX, y - this.startY) <= TOUCH_TAP_SLOP);
|
|
17023
17051
|
if (this.dragging && this.region === "drawing") {
|
|
17024
|
-
this.deps.drawingsPointerUp?.(x, y);
|
|
17052
|
+
this.deps.drawingsPointerUp?.(x, y, this.snapMode(e));
|
|
17025
17053
|
} else if (tapRelease && this.region === "data") {
|
|
17026
17054
|
this.deps.onClick(x, y);
|
|
17027
17055
|
} else if (this.dragging && this.region === "data") {
|
|
@@ -17042,7 +17070,7 @@ var InputController = class {
|
|
|
17042
17070
|
if (e.pointerType === "touch") this.touches.delete(e.pointerId);
|
|
17043
17071
|
this.cancelLongPress();
|
|
17044
17072
|
if (!this.dragging) return;
|
|
17045
|
-
if (this.region === "drawing" && !Number.isNaN(this.cursorX)) this.deps.drawingsPointerUp?.(this.cursorX, this.cursorY);
|
|
17073
|
+
if (this.region === "drawing" && !Number.isNaN(this.cursorX)) this.deps.drawingsPointerUp?.(this.cursorX, this.cursorY, this.snapMode(e));
|
|
17046
17074
|
if (this.region === "crosshair" || e.pointerType === "touch") this.deps.onPointerMove(null, null);
|
|
17047
17075
|
this.endGesture(e);
|
|
17048
17076
|
};
|
|
@@ -17058,6 +17086,12 @@ var InputController = class {
|
|
|
17058
17086
|
this.onWheel = (e) => {
|
|
17059
17087
|
e.preventDefault();
|
|
17060
17088
|
this.deps.drawingsClearTransient?.();
|
|
17089
|
+
const { x, y } = this.local(e);
|
|
17090
|
+
if (this.regionAt(x, y) === "price" && e.deltaY !== 0) {
|
|
17091
|
+
this.deps.beginPriceScale(x, y);
|
|
17092
|
+
this.deps.priceScaleBy(e.deltaY * WHEEL_PRICE_DRAG_PX);
|
|
17093
|
+
return;
|
|
17094
|
+
}
|
|
17061
17095
|
const coords = this.deps.getCoords();
|
|
17062
17096
|
const vp = coords.getViewport();
|
|
17063
17097
|
const pan = wheelPanDelta(e.deltaX, e.deltaY, e.shiftKey);
|
|
@@ -17065,9 +17099,8 @@ var InputController = class {
|
|
|
17065
17099
|
this.deps.apply({ barSpacing: vp.barSpacing, rightOffset: wheelPanRightOffset(vp.rightOffset, pan, coords.pxPerBar()) });
|
|
17066
17100
|
return;
|
|
17067
17101
|
}
|
|
17068
|
-
const cursorX = this.local(e).x;
|
|
17069
17102
|
const rightEdge = this.rightEdgeZoom && !(e.ctrlKey || e.metaKey);
|
|
17070
|
-
const anchor = wheelZoomAnchor(coords,
|
|
17103
|
+
const anchor = wheelZoomAnchor(coords, x, rightEdge);
|
|
17071
17104
|
const target = clampBarSpacing(vp.barSpacing * Math.exp(-e.deltaY * WHEEL_ZOOM_K));
|
|
17072
17105
|
this.deps.zoomTo(target, anchor.logical, anchor.x);
|
|
17073
17106
|
};
|
|
@@ -17553,9 +17586,10 @@ var SceneGraph = class {
|
|
|
17553
17586
|
* so each indicator arrives behind the candles (and behind older indicators);
|
|
17554
17587
|
* `setIndicatorZ`/`bringToFront`/`sendToBack` change it. */
|
|
17555
17588
|
this.seriesZ = /* @__PURE__ */ new Map();
|
|
17556
|
-
/** Per-pane raster layers of
|
|
17589
|
+
/** Per-pane raster layers of drawings interleaved into the series stack (each
|
|
17590
|
+
* indicator's Pine drawings at its model's z, plus in-stack user drawings) — each a
|
|
17557
17591
|
* prepainted canvas the backend composites just before the series carrying `beforeZ`.
|
|
17558
|
-
* Rebuilt by the renderer per data frame
|
|
17592
|
+
* Rebuilt by the renderer per data frame. */
|
|
17559
17593
|
this.drawingSlices = /* @__PURE__ */ new Map();
|
|
17560
17594
|
/** Per-model index offset: the chart bar index of the model's `anchorTime` — its
|
|
17561
17595
|
* index-aligned payloads (dense series arrays, `bar_index` drawings) count from that
|
|
@@ -17790,11 +17824,9 @@ var Canvas2dBackend = class {
|
|
|
17790
17824
|
};
|
|
17791
17825
|
ctx.globalAlpha = this.modelAlpha;
|
|
17792
17826
|
for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
|
|
17793
|
-
for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
17794
17827
|
if (isPrice) {
|
|
17795
17828
|
for (const m of scene.indicators.values()) {
|
|
17796
17829
|
for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
|
|
17797
|
-
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
17798
17830
|
}
|
|
17799
17831
|
}
|
|
17800
17832
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
@@ -17818,6 +17850,7 @@ var Canvas2dBackend = class {
|
|
|
17818
17850
|
ctx.globalAlpha = this.modelAlpha;
|
|
17819
17851
|
const off = scene.offsetOf(m.id);
|
|
17820
17852
|
const mp = effPane(m);
|
|
17853
|
+
for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, mp, coords, i0, i1, off);
|
|
17821
17854
|
for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
17822
17855
|
}
|
|
17823
17856
|
if (drawCandles && !candleDrawn) {
|
|
@@ -17825,14 +17858,18 @@ var Canvas2dBackend = class {
|
|
|
17825
17858
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
17826
17859
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
17827
17860
|
}
|
|
17828
|
-
drawSlicesUpTo(Infinity);
|
|
17829
17861
|
if (isPrice) {
|
|
17830
17862
|
ctx.globalAlpha = this.modelAlpha;
|
|
17863
|
+
for (const m of scene.indicators.values()) {
|
|
17864
|
+
const off = scene.offsetOf(m.id);
|
|
17865
|
+
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, off);
|
|
17866
|
+
}
|
|
17831
17867
|
for (const m of scene.indicators.values()) {
|
|
17832
17868
|
const off = scene.offsetOf(m.id);
|
|
17833
17869
|
for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
|
|
17834
17870
|
}
|
|
17835
17871
|
}
|
|
17872
|
+
drawSlicesUpTo(Infinity);
|
|
17836
17873
|
ctx.globalAlpha = this.modelAlpha;
|
|
17837
17874
|
for (const m of models) {
|
|
17838
17875
|
const mp = effPane(m);
|
|
@@ -18076,13 +18113,13 @@ var Canvas2dBackend = class {
|
|
|
18076
18113
|
if (drawBody) {
|
|
18077
18114
|
const oY = coords.priceToY(b.open, pane.scale, pane.bounds);
|
|
18078
18115
|
const cY = coords.priceToY(b.close, pane.scale, pane.bounds);
|
|
18079
|
-
top = Math.min(oY, cY);
|
|
18080
|
-
bodyH = Math.max(1, Math.
|
|
18116
|
+
top = snapY(Math.min(oY, cY), coords.dpr);
|
|
18117
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - top);
|
|
18081
18118
|
}
|
|
18082
18119
|
if (cs.wickVisible) {
|
|
18083
18120
|
const wick = (up ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : color);
|
|
18084
|
-
const hY = coords.priceToY(b.high, pane.scale, pane.bounds);
|
|
18085
|
-
const lY = coords.priceToY(b.low, pane.scale, pane.bounds);
|
|
18121
|
+
const hY = snapY(coords.priceToY(b.high, pane.scale, pane.bounds), coords.dpr);
|
|
18122
|
+
const lY = snapY(coords.priceToY(b.low, pane.scale, pane.bounds), coords.dpr);
|
|
18086
18123
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
18087
18124
|
ctx.strokeStyle = wick;
|
|
18088
18125
|
ctx.lineWidth = g.wickW;
|
|
@@ -18529,9 +18566,31 @@ function autoFontSize(lines, boxW, boxH, bold) {
|
|
|
18529
18566
|
|
|
18530
18567
|
// src/renderers/shared/DrawingSceneRenderer.ts
|
|
18531
18568
|
var EMPTY_DRAWING_SET = { lines: [], boxes: [], labels: [], polylines: [], linefills: [] };
|
|
18569
|
+
function modelDrawingSet(m, overlay) {
|
|
18570
|
+
const want = (d) => Boolean(d.overlay) === overlay;
|
|
18571
|
+
return {
|
|
18572
|
+
lines: (m.lines ?? []).filter(want),
|
|
18573
|
+
boxes: (m.boxes ?? []).filter(want),
|
|
18574
|
+
labels: (m.labels ?? []).filter(want),
|
|
18575
|
+
polylines: (m.polylines ?? []).filter(want),
|
|
18576
|
+
linefills: (m.linefills ?? []).filter(want)
|
|
18577
|
+
};
|
|
18578
|
+
}
|
|
18579
|
+
function drawingSetEmpty(s) {
|
|
18580
|
+
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
18581
|
+
}
|
|
18532
18582
|
function fontSizePx(size) {
|
|
18533
18583
|
return size === "auto" ? 12 : namedFontSize(size);
|
|
18534
18584
|
}
|
|
18585
|
+
function lineCoversWindow(a, b, extend, lo, hi) {
|
|
18586
|
+
const minX = Math.min(a, b);
|
|
18587
|
+
const maxX = Math.max(a, b);
|
|
18588
|
+
if (a === b) return a >= lo && a <= hi;
|
|
18589
|
+
if (extend === "both") return true;
|
|
18590
|
+
if (extend === "left") return maxX >= lo;
|
|
18591
|
+
if (extend === "right") return minX <= hi;
|
|
18592
|
+
return maxX >= lo && minX <= hi;
|
|
18593
|
+
}
|
|
18535
18594
|
var DrawingSceneRenderer = class {
|
|
18536
18595
|
constructor(deps, set = EMPTY_DRAWING_SET) {
|
|
18537
18596
|
this.deps = deps;
|
|
@@ -18596,7 +18655,7 @@ var DrawingSceneRenderer = class {
|
|
|
18596
18655
|
};
|
|
18597
18656
|
for (const ln of this.set.lines) {
|
|
18598
18657
|
if (ln.invisible) continue;
|
|
18599
|
-
if (!
|
|
18658
|
+
if (!lineCoversWindow(this.logicalOf(ln.xloc, ln.x1), this.logicalOf(ln.xloc, ln.x2), ln.extend, lo, hi)) continue;
|
|
18600
18659
|
fold(ln.y1);
|
|
18601
18660
|
fold(ln.y2);
|
|
18602
18661
|
}
|
|
@@ -19381,10 +19440,8 @@ var ChromeRenderer = class {
|
|
|
19381
19440
|
this.ctx = null;
|
|
19382
19441
|
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
19383
19442
|
this.axisTextColor = DARK_THEME.textColor;
|
|
19384
|
-
// Shared Pine-drawing renderer
|
|
19443
|
+
// Shared Pine-drawing renderer, used here for autoscale geometry only; widthCache persists.
|
|
19385
19444
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
19386
|
-
// Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per render).
|
|
19387
|
-
this.labelTips = [];
|
|
19388
19445
|
}
|
|
19389
19446
|
mount(canvas) {
|
|
19390
19447
|
this.canvas = canvas;
|
|
@@ -19408,8 +19465,8 @@ var ChromeRenderer = class {
|
|
|
19408
19465
|
*/
|
|
19409
19466
|
paneDrawingsRange(ownModels, scene, isPricePane, vr) {
|
|
19410
19467
|
let dr = null;
|
|
19411
|
-
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(
|
|
19412
|
-
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(
|
|
19468
|
+
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, false), vr, scene.offsetOf(m.id)));
|
|
19469
|
+
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, true), vr, scene.offsetOf(m.id)));
|
|
19413
19470
|
return dr;
|
|
19414
19471
|
}
|
|
19415
19472
|
/** Clear the chrome canvas and draw drawings + axes + current-price line.
|
|
@@ -19426,7 +19483,6 @@ var ChromeRenderer = class {
|
|
|
19426
19483
|
const dataW = coords.width;
|
|
19427
19484
|
const dataH = coords.height;
|
|
19428
19485
|
this.axisTextColor = surface?.textColor ?? theme.textColor;
|
|
19429
|
-
this.labelTips = [];
|
|
19430
19486
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
19431
19487
|
ctx.clearRect(0, 0, fullW, fullH);
|
|
19432
19488
|
if (surface && (fullW > dataW || fullH > dataH)) {
|
|
@@ -19442,17 +19498,6 @@ var ChromeRenderer = class {
|
|
|
19442
19498
|
return;
|
|
19443
19499
|
}
|
|
19444
19500
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
19445
|
-
for (const pane of panes) {
|
|
19446
|
-
if (pane.collapsed) continue;
|
|
19447
|
-
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
19448
|
-
const sc = scene.scaleFor(m, pane);
|
|
19449
|
-
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
19450
|
-
this.renderDrawings(ctx, coords, this.ownDrawings(m), mp, dataW, scene.offsetOf(m.id));
|
|
19451
|
-
}
|
|
19452
|
-
}
|
|
19453
|
-
if (pricePane) {
|
|
19454
|
-
for (const m of scene.indicators.values()) this.renderDrawings(ctx, coords, this.overlayDrawings(m), pricePane, dataW, scene.offsetOf(m.id));
|
|
19455
|
-
}
|
|
19456
19501
|
if (pricePane && !pricePane.collapsed && scene.tradeMarkers.visible) {
|
|
19457
19502
|
for (const m of scene.indicators.values()) {
|
|
19458
19503
|
if (m.trades?.length) this.renderTrades(ctx, coords, scene, theme, m.trades, pricePane, dataW);
|
|
@@ -19468,25 +19513,6 @@ var ChromeRenderer = class {
|
|
|
19468
19513
|
this.canvas = null;
|
|
19469
19514
|
this.ctx = null;
|
|
19470
19515
|
}
|
|
19471
|
-
// ── Pine-drawing helpers (own vs force_overlay routing) ──
|
|
19472
|
-
ownDrawings(m) {
|
|
19473
|
-
return {
|
|
19474
|
-
lines: (m.lines ?? []).filter((d) => !d.overlay),
|
|
19475
|
-
boxes: (m.boxes ?? []).filter((d) => !d.overlay),
|
|
19476
|
-
labels: (m.labels ?? []).filter((d) => !d.overlay),
|
|
19477
|
-
polylines: (m.polylines ?? []).filter((d) => !d.overlay),
|
|
19478
|
-
linefills: (m.linefills ?? []).filter((d) => !d.overlay)
|
|
19479
|
-
};
|
|
19480
|
-
}
|
|
19481
|
-
overlayDrawings(m) {
|
|
19482
|
-
return {
|
|
19483
|
-
lines: (m.lines ?? []).filter((d) => d.overlay),
|
|
19484
|
-
boxes: (m.boxes ?? []).filter((d) => d.overlay),
|
|
19485
|
-
labels: (m.labels ?? []).filter((d) => d.overlay),
|
|
19486
|
-
polylines: (m.polylines ?? []).filter((d) => d.overlay),
|
|
19487
|
-
linefills: (m.linefills ?? []).filter((d) => d.overlay)
|
|
19488
|
-
};
|
|
19489
|
-
}
|
|
19490
19516
|
drawingsRange(set, vr, indexOffset = 0) {
|
|
19491
19517
|
this.drawScene.setSet(set, indexOffset);
|
|
19492
19518
|
if (this.drawScene.isEmpty()) return null;
|
|
@@ -19520,34 +19546,6 @@ var ChromeRenderer = class {
|
|
|
19520
19546
|
);
|
|
19521
19547
|
ctx.restore();
|
|
19522
19548
|
}
|
|
19523
|
-
renderDrawings(ctx, coords, set, pane, dataW, indexOffset = 0) {
|
|
19524
|
-
this.drawScene.setSet(set, indexOffset);
|
|
19525
|
-
if (this.drawScene.isEmpty()) return;
|
|
19526
|
-
ctx.save();
|
|
19527
|
-
ctx.translate(0, pane.bounds.top);
|
|
19528
|
-
ctx.beginPath();
|
|
19529
|
-
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
19530
|
-
ctx.clip();
|
|
19531
|
-
this.drawScene.render(
|
|
19532
|
-
ctx,
|
|
19533
|
-
dataW,
|
|
19534
|
-
pane.bounds.height,
|
|
19535
|
-
(l) => coords.logicalToX(l),
|
|
19536
|
-
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
19537
|
-
);
|
|
19538
|
-
ctx.restore();
|
|
19539
|
-
for (const r of this.drawScene.labelTipRegions()) {
|
|
19540
|
-
this.labelTips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
19541
|
-
}
|
|
19542
|
-
}
|
|
19543
|
-
/** Tooltip of the topmost label under a plot-space point, or null. Fed by the last render. */
|
|
19544
|
-
labelTooltipAt(x, y) {
|
|
19545
|
-
for (let i = this.labelTips.length - 1; i >= 0; i -= 1) {
|
|
19546
|
-
const r = this.labelTips[i];
|
|
19547
|
-
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
19548
|
-
}
|
|
19549
|
-
return null;
|
|
19550
|
-
}
|
|
19551
19549
|
// ── axes ──
|
|
19552
19550
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
19553
19551
|
ctx.strokeStyle = scene.style.borderColor ?? theme.borderColor;
|
|
@@ -22597,6 +22595,22 @@ var DrawingInteraction = class {
|
|
|
22597
22595
|
this.snapAt = changed ? { point: snapped, paneId } : null;
|
|
22598
22596
|
return snapped;
|
|
22599
22597
|
}
|
|
22598
|
+
/**
|
|
22599
|
+
* Resolve a cursor pixel through the magnet and return the snapped pixel — the same
|
|
22600
|
+
* conversion drawing placement uses. Updates the snap-ring marker. The measure
|
|
22601
|
+
* ruler goes through this so its endpoints follow weak/strong/Ctrl magnet too.
|
|
22602
|
+
*/
|
|
22603
|
+
snapCursor(x, y, mode) {
|
|
22604
|
+
const proj = this.deps.projector();
|
|
22605
|
+
const paneId = proj.paneIdAtY(y) ?? "price";
|
|
22606
|
+
const point = this.resolve(x, y, paneId, mode);
|
|
22607
|
+
const sy = proj.yOf(point.price, paneId);
|
|
22608
|
+
return { x: proj.xOf(point.time), y: sy ?? y };
|
|
22609
|
+
}
|
|
22610
|
+
/** Drop the snap-ring marker (a transient mode ended without going through `up`). */
|
|
22611
|
+
clearSnapMarker() {
|
|
22612
|
+
this.snapAt = null;
|
|
22613
|
+
}
|
|
22600
22614
|
/** Resolve a pixel to a data point with the segment angle locked to 45° steps around
|
|
22601
22615
|
* `pivot` (Shift held on a line tool). Works in PIXEL space — the user reasons about
|
|
22602
22616
|
* the angle they see, not about time/price units. The magnet is bypassed: snapping
|
|
@@ -23956,17 +23970,17 @@ function glyphIcon(glyph) {
|
|
|
23956
23970
|
return textGlyph(String(glyph), 15);
|
|
23957
23971
|
}
|
|
23958
23972
|
function stampSizeIcon(size) {
|
|
23959
|
-
return textGlyph("\u25CF", (
|
|
23973
|
+
return textGlyph("\u25CF", (SIZE_PX3[String(size)] ?? 13) + 4);
|
|
23960
23974
|
}
|
|
23961
23975
|
function sizeIcon(size) {
|
|
23962
23976
|
return textGlyph(String(size).charAt(0).toUpperCase(), 15);
|
|
23963
23977
|
}
|
|
23964
|
-
var
|
|
23978
|
+
var SIZE_PX3 = { small: 10, normal: 13, large: 16, huge: 20 };
|
|
23965
23979
|
function numbersSizeIcon(size) {
|
|
23966
|
-
return textGlyph("12", (
|
|
23980
|
+
return textGlyph("12", (SIZE_PX3[String(size)] ?? 13) - 1, 16.5, 'font-weight="600"');
|
|
23967
23981
|
}
|
|
23968
23982
|
function labelSizeIcon(size) {
|
|
23969
|
-
return textGlyph("T", (
|
|
23983
|
+
return textGlyph("T", (SIZE_PX3[String(size)] ?? 13) + 2);
|
|
23970
23984
|
}
|
|
23971
23985
|
function capitalize(s) {
|
|
23972
23986
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -24130,7 +24144,7 @@ var DrawingToolbar = class {
|
|
|
24130
24144
|
this.root.replaceChildren();
|
|
24131
24145
|
this.groupCells.clear();
|
|
24132
24146
|
this.groupIcons.clear();
|
|
24133
|
-
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.
|
|
24147
|
+
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.onCursorClick());
|
|
24134
24148
|
this.root.appendChild(this.cursorBtn);
|
|
24135
24149
|
if (this.def.groups.length > 0) this.root.appendChild(this.divider());
|
|
24136
24150
|
for (const g of this.def.groups) {
|
|
@@ -24234,6 +24248,14 @@ var DrawingToolbar = class {
|
|
|
24234
24248
|
this.magnetIcon = icon2;
|
|
24235
24249
|
return cell;
|
|
24236
24250
|
}
|
|
24251
|
+
/** Cursor returns to select/idle: an active measure/eraser mode exits through its own
|
|
24252
|
+
* toggle callback (disarming a tool via `onArm(null)` alone can't — the host treats a
|
|
24253
|
+
* null arm as a no-op side effect of entering those modes), then the tool disarms. */
|
|
24254
|
+
onCursorClick() {
|
|
24255
|
+
if (this.measureActive) this.onMeasure();
|
|
24256
|
+
if (this.eraserActive) this.onEraser();
|
|
24257
|
+
this.onArm(null);
|
|
24258
|
+
}
|
|
24237
24259
|
/** Clicking the icon arms the group's last-used tool (it does NOT open the flyout). */
|
|
24238
24260
|
onGroupIconClick(group) {
|
|
24239
24261
|
const type = this.lastUsed.get(group.id) ?? group.tools[0]?.type;
|
|
@@ -24626,30 +24648,39 @@ var MeasureOverlay = class {
|
|
|
24626
24648
|
isFinished() {
|
|
24627
24649
|
return this.state === "finished";
|
|
24628
24650
|
}
|
|
24629
|
-
/** A press: begin the measurement, or finish it on the second click.
|
|
24630
|
-
|
|
24651
|
+
/** A press: begin the measurement, or finish it on the second click.
|
|
24652
|
+
* `x,y` are the raw cursor (drag-slop vs click-move-click). `gx,gy` are the
|
|
24653
|
+
* graphic endpoints — magnet-snapped when the magnet is on, else the same as `x,y`. */
|
|
24654
|
+
down(x, y, gx = x, gy = y) {
|
|
24631
24655
|
if (this.state === "measuring") {
|
|
24632
|
-
this.end = { x, y };
|
|
24656
|
+
this.end = { x: gx, y: gy };
|
|
24633
24657
|
this.state = "finished";
|
|
24634
24658
|
return;
|
|
24635
24659
|
}
|
|
24636
|
-
this.start = { x, y };
|
|
24637
|
-
this.end = { x, y };
|
|
24660
|
+
this.start = { x: gx, y: gy };
|
|
24661
|
+
this.end = { x: gx, y: gy };
|
|
24638
24662
|
this.pressX = x;
|
|
24639
24663
|
this.pressY = y;
|
|
24640
24664
|
this.state = "measuring";
|
|
24641
24665
|
}
|
|
24666
|
+
/** Size the in-progress ruler. `x,y` are the graphic (magnet-snapped) cursor. */
|
|
24642
24667
|
move(x, y) {
|
|
24643
24668
|
if (this.state === "measuring") this.end = { x, y };
|
|
24644
24669
|
}
|
|
24645
24670
|
/** A release: finish if the press was actually dragged (press-drag-release), else wait
|
|
24646
|
-
* for the second click (click-move-click).
|
|
24647
|
-
|
|
24671
|
+
* for the second click (click-move-click). `x,y` are the raw cursor (slop); `gx,gy`
|
|
24672
|
+
* are the graphic end (magnet-snapped when the magnet is on). */
|
|
24673
|
+
up(x, y, gx = x, gy = y) {
|
|
24648
24674
|
if (this.state === "measuring" && Math.hypot(x - this.pressX, y - this.pressY) > DRAG_SLOP3) {
|
|
24649
|
-
this.end = { x, y };
|
|
24675
|
+
this.end = { x: gx, y: gy };
|
|
24650
24676
|
this.state = "finished";
|
|
24651
24677
|
}
|
|
24652
24678
|
}
|
|
24679
|
+
/** Current graphic endpoints in media pixels, or null when idle. */
|
|
24680
|
+
points() {
|
|
24681
|
+
if (!this.start || !this.end) return null;
|
|
24682
|
+
return { start: this.start, end: this.end };
|
|
24683
|
+
}
|
|
24653
24684
|
clear() {
|
|
24654
24685
|
this.state = "idle";
|
|
24655
24686
|
this.start = null;
|
|
@@ -25026,11 +25057,13 @@ var UserDrawingController = class {
|
|
|
25026
25057
|
}
|
|
25027
25058
|
/** Shift+press on the empty plot: arm the measure ruler AND start it at (x, y) in one
|
|
25028
25059
|
* gesture — the equivalent of clicking the toolbar's Measure button, then pressing.
|
|
25029
|
-
*
|
|
25030
|
-
|
|
25060
|
+
* `snap` is the effective magnet (sticky mode, or Ctrl/Cmd-forced strong). Returns
|
|
25061
|
+
* false when a mode/tool is already active (the normal press path owns it). */
|
|
25062
|
+
beginMeasureAt(x, y, snap = "off") {
|
|
25031
25063
|
if (this.measureMode || this.eraserMode || this.activeTool != null) return false;
|
|
25032
25064
|
this.withModeIntent(() => this.toggleMeasure());
|
|
25033
|
-
this.
|
|
25065
|
+
const g = this.interaction.snapCursor(x, y, snap);
|
|
25066
|
+
this.measure.down(x, y, g.x, g.y);
|
|
25034
25067
|
this.render();
|
|
25035
25068
|
return true;
|
|
25036
25069
|
}
|
|
@@ -25052,7 +25085,8 @@ var UserDrawingController = class {
|
|
|
25052
25085
|
return;
|
|
25053
25086
|
}
|
|
25054
25087
|
if (this.measureMode) {
|
|
25055
|
-
this.
|
|
25088
|
+
const g = this.interaction.snapCursor(x, y, snap);
|
|
25089
|
+
this.measure.down(x, y, g.x, g.y);
|
|
25056
25090
|
if (this.measure.isFinished()) this.withModeIntent(() => this.exitMeasure(false));
|
|
25057
25091
|
this.render();
|
|
25058
25092
|
return;
|
|
@@ -25065,7 +25099,8 @@ var UserDrawingController = class {
|
|
|
25065
25099
|
return;
|
|
25066
25100
|
}
|
|
25067
25101
|
if (this.measureMode) {
|
|
25068
|
-
this.
|
|
25102
|
+
const g = this.interaction.snapCursor(x, y, snap);
|
|
25103
|
+
this.measure.move(g.x, g.y);
|
|
25069
25104
|
this.render();
|
|
25070
25105
|
return;
|
|
25071
25106
|
}
|
|
@@ -25083,13 +25118,14 @@ var UserDrawingController = class {
|
|
|
25083
25118
|
this.render();
|
|
25084
25119
|
}
|
|
25085
25120
|
}
|
|
25086
|
-
pointerUp(x, y) {
|
|
25121
|
+
pointerUp(x, y, snap = "off") {
|
|
25087
25122
|
if (this.eraserMode) {
|
|
25088
25123
|
this.erasing = false;
|
|
25089
25124
|
return;
|
|
25090
25125
|
}
|
|
25091
25126
|
if (this.measureMode) {
|
|
25092
|
-
this.
|
|
25127
|
+
const g = this.interaction.snapCursor(x, y, snap);
|
|
25128
|
+
this.measure.up(x, y, g.x, g.y);
|
|
25093
25129
|
if (this.measure.isFinished()) this.withModeIntent(() => this.exitMeasure(false));
|
|
25094
25130
|
this.render();
|
|
25095
25131
|
return;
|
|
@@ -25134,6 +25170,7 @@ var UserDrawingController = class {
|
|
|
25134
25170
|
/** Leave ruler mode. `clearGraphic` keeps a just-finished measurement on screen (false). */
|
|
25135
25171
|
exitMeasure(clearGraphic = true) {
|
|
25136
25172
|
this.measureMode = false;
|
|
25173
|
+
this.interaction.clearSnapMarker();
|
|
25137
25174
|
if (clearGraphic) this.measure.clear();
|
|
25138
25175
|
this.toolbar.setMeasureActive(false);
|
|
25139
25176
|
this.render();
|
|
@@ -25274,15 +25311,28 @@ var UserDrawingController = class {
|
|
|
25274
25311
|
if (this.eraserMode) return "pointer";
|
|
25275
25312
|
return this.interaction.cursorAt(x, y);
|
|
25276
25313
|
}
|
|
25277
|
-
/** Right-click
|
|
25278
|
-
*
|
|
25279
|
-
*
|
|
25280
|
-
*
|
|
25314
|
+
/** Right-click: an explicit escape back to the pointer. Cancels an in-progress
|
|
25315
|
+
* placement or measurement, and also plain-disarms an armed-but-idle drawing
|
|
25316
|
+
* tool or the eraser — so a right-click ALWAYS reverts to the pointer, even in
|
|
25317
|
+
* stay-in-drawing-mode (where Escape would leave a drawing tool armed).
|
|
25318
|
+
* Persistent toggles (magnet, stay-mode, favorites) are untouched. Returns
|
|
25319
|
+
* whether the press was consumed; false lets the host's context menu open
|
|
25320
|
+
* normally. */
|
|
25281
25321
|
cancelPlacement() {
|
|
25282
|
-
if (
|
|
25283
|
-
|
|
25284
|
-
|
|
25285
|
-
|
|
25322
|
+
if (this.measureMode || this.eraserMode) {
|
|
25323
|
+
this.withModeIntent(() => this.measureMode ? this.exitMeasure() : this.exitEraser());
|
|
25324
|
+
return true;
|
|
25325
|
+
}
|
|
25326
|
+
if (this.interaction.isPlacing()) {
|
|
25327
|
+
this.interaction.cancel();
|
|
25328
|
+
if (this.activeTool != null) this.emit({ kind: "arm", type: null });
|
|
25329
|
+
return true;
|
|
25330
|
+
}
|
|
25331
|
+
if (this.activeTool != null) {
|
|
25332
|
+
this.emit({ kind: "arm", type: null });
|
|
25333
|
+
return true;
|
|
25334
|
+
}
|
|
25335
|
+
return false;
|
|
25286
25336
|
}
|
|
25287
25337
|
/** Double-click over a drawing → suppress the chart's view reset (single-click already
|
|
25288
25338
|
* opens settings). Returns true only when a drawing is under the cursor. */
|
|
@@ -25308,6 +25358,10 @@ var UserDrawingController = class {
|
|
|
25308
25358
|
return true;
|
|
25309
25359
|
}
|
|
25310
25360
|
if (this.interaction.cancel()) return true;
|
|
25361
|
+
if (this.measureMode) {
|
|
25362
|
+
this.withModeIntent(() => this.exitMeasure());
|
|
25363
|
+
return true;
|
|
25364
|
+
}
|
|
25311
25365
|
if (this.selectedIds.size) {
|
|
25312
25366
|
this.clearSelection();
|
|
25313
25367
|
return true;
|
|
@@ -25565,6 +25619,315 @@ var UserDrawingController = class {
|
|
|
25565
25619
|
}
|
|
25566
25620
|
};
|
|
25567
25621
|
|
|
25622
|
+
// src/renderers/shared/TableOverlay.ts
|
|
25623
|
+
var SIZE_PX4 = {
|
|
25624
|
+
auto: 13,
|
|
25625
|
+
tiny: 10,
|
|
25626
|
+
small: 11,
|
|
25627
|
+
normal: 13,
|
|
25628
|
+
large: 16,
|
|
25629
|
+
huge: 20
|
|
25630
|
+
};
|
|
25631
|
+
function fontPxOf(size) {
|
|
25632
|
+
if (typeof size === "number") return size > 0 ? size : SIZE_PX4.auto;
|
|
25633
|
+
return SIZE_PX4[size] ?? SIZE_PX4.auto;
|
|
25634
|
+
}
|
|
25635
|
+
function tableHasContent(t) {
|
|
25636
|
+
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
25637
|
+
}
|
|
25638
|
+
function mergeRenderPlan(t) {
|
|
25639
|
+
const span = /* @__PURE__ */ new Map();
|
|
25640
|
+
const omit = /* @__PURE__ */ new Set();
|
|
25641
|
+
for (const m of t.merges) {
|
|
25642
|
+
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
25643
|
+
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
25644
|
+
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
25645
|
+
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
25646
|
+
}
|
|
25647
|
+
}
|
|
25648
|
+
}
|
|
25649
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
25650
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
25651
|
+
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
25652
|
+
}
|
|
25653
|
+
}
|
|
25654
|
+
for (const key of span.keys()) omit.delete(key);
|
|
25655
|
+
return { span, omit };
|
|
25656
|
+
}
|
|
25657
|
+
|
|
25658
|
+
// src/renderers/shared/TableCanvasRenderer.ts
|
|
25659
|
+
var PAD_X = 6;
|
|
25660
|
+
var PAD_Y = 2;
|
|
25661
|
+
var MARGIN = 6;
|
|
25662
|
+
var LINE_HEIGHT = 1.2;
|
|
25663
|
+
function paintTable(ctx, t, args, tips) {
|
|
25664
|
+
if (!tableHasContent(t)) return;
|
|
25665
|
+
const layout = layoutTable(ctx, t, args);
|
|
25666
|
+
if (!layout || layout.w <= 0 || layout.h <= 0) return;
|
|
25667
|
+
const fw = t.frameColor && t.frameWidth > 0 ? t.frameWidth : 0;
|
|
25668
|
+
const { x, y } = anchorOrigin(t.position, layout.w + 2 * fw, layout.h + 2 * fw, args);
|
|
25669
|
+
const x0 = x + fw;
|
|
25670
|
+
const y0 = y + fw;
|
|
25671
|
+
if (t.bgColor) {
|
|
25672
|
+
ctx.fillStyle = t.bgColor;
|
|
25673
|
+
ctx.fillRect(x0, y0, layout.w, layout.h);
|
|
25674
|
+
}
|
|
25675
|
+
if (fw > 0 && t.frameColor) {
|
|
25676
|
+
ctx.strokeStyle = t.frameColor;
|
|
25677
|
+
ctx.lineWidth = fw;
|
|
25678
|
+
ctx.strokeRect(x + fw / 2, y + fw / 2, layout.w + fw, layout.h + fw);
|
|
25679
|
+
}
|
|
25680
|
+
const colX = [0];
|
|
25681
|
+
for (const w of layout.colW) colX.push(colX[colX.length - 1] + w);
|
|
25682
|
+
const rowY = [0];
|
|
25683
|
+
for (const h of layout.rowH) rowY.push(rowY[rowY.length - 1] + h);
|
|
25684
|
+
const prevBaseline = ctx.textBaseline;
|
|
25685
|
+
const prevAlign = ctx.textAlign;
|
|
25686
|
+
ctx.textBaseline = "middle";
|
|
25687
|
+
for (const box of layout.boxes) {
|
|
25688
|
+
const rx = x0 + colX[box.c];
|
|
25689
|
+
const ry = y0 + rowY[box.r];
|
|
25690
|
+
const rw = colX[box.c + box.cs] - colX[box.c];
|
|
25691
|
+
const rh = rowY[box.r + box.rs] - rowY[box.r];
|
|
25692
|
+
const cell = box.cell;
|
|
25693
|
+
if (cell.bgColor) {
|
|
25694
|
+
ctx.fillStyle = cell.bgColor;
|
|
25695
|
+
ctx.fillRect(rx, ry, rw, rh);
|
|
25696
|
+
}
|
|
25697
|
+
const text = cell.text ?? "";
|
|
25698
|
+
if (text.length > 0) {
|
|
25699
|
+
const px = fontPxOf(cell.textSize);
|
|
25700
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
25701
|
+
ctx.fillStyle = cell.textColor ?? args.theme.textColor;
|
|
25702
|
+
const lines = text.split("\n");
|
|
25703
|
+
const blockH = lines.length * px * LINE_HEIGHT;
|
|
25704
|
+
const blockTop = cell.vAlign === "top" ? ry + PAD_Y : cell.vAlign === "bottom" ? ry + rh - PAD_Y - blockH : ry + (rh - blockH) / 2;
|
|
25705
|
+
const tx = cell.hAlign === "left" ? rx + PAD_X : cell.hAlign === "right" ? rx + rw - PAD_X : rx + rw / 2;
|
|
25706
|
+
ctx.textAlign = cell.hAlign;
|
|
25707
|
+
lines.forEach((line, i) => ctx.fillText(line, tx, blockTop + (i + 0.5) * px * LINE_HEIGHT));
|
|
25708
|
+
}
|
|
25709
|
+
if (cell.tooltip) tips.push({ left: rx, top: ry, right: rx + rw, bottom: ry + rh, text: cell.tooltip });
|
|
25710
|
+
}
|
|
25711
|
+
ctx.textBaseline = prevBaseline;
|
|
25712
|
+
ctx.textAlign = prevAlign;
|
|
25713
|
+
if (t.borderColor && t.borderWidth > 0) {
|
|
25714
|
+
ctx.strokeStyle = t.borderColor;
|
|
25715
|
+
ctx.lineWidth = t.borderWidth;
|
|
25716
|
+
const seen = /* @__PURE__ */ new Set();
|
|
25717
|
+
ctx.beginPath();
|
|
25718
|
+
const edge = (ax, ay, bx, by) => {
|
|
25719
|
+
const key = `${ax},${ay},${bx},${by}`;
|
|
25720
|
+
if (seen.has(key)) return;
|
|
25721
|
+
seen.add(key);
|
|
25722
|
+
ctx.moveTo(ax, ay);
|
|
25723
|
+
ctx.lineTo(bx, by);
|
|
25724
|
+
};
|
|
25725
|
+
for (const box of layout.boxes) {
|
|
25726
|
+
const l = Math.round(x0 + colX[box.c]);
|
|
25727
|
+
const r = Math.round(x0 + colX[box.c + box.cs]);
|
|
25728
|
+
const tp = Math.round(y0 + rowY[box.r]);
|
|
25729
|
+
const bt = Math.round(y0 + rowY[box.r + box.rs]);
|
|
25730
|
+
edge(l, tp, r, tp);
|
|
25731
|
+
edge(l, bt, r, bt);
|
|
25732
|
+
edge(l, tp, l, bt);
|
|
25733
|
+
edge(r, tp, r, bt);
|
|
25734
|
+
}
|
|
25735
|
+
ctx.stroke();
|
|
25736
|
+
}
|
|
25737
|
+
}
|
|
25738
|
+
function layoutTable(ctx, t, args) {
|
|
25739
|
+
const { span, omit } = mergeRenderPlan(t);
|
|
25740
|
+
const colW = new Array(t.columns).fill(0);
|
|
25741
|
+
const rowH = new Array(t.rows).fill(0);
|
|
25742
|
+
const boxes = [];
|
|
25743
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
25744
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
25745
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
25746
|
+
const cell = t.cells[r]?.[c];
|
|
25747
|
+
if (cell == null) continue;
|
|
25748
|
+
const sp = span.get(`${r}:${c}`);
|
|
25749
|
+
boxes.push({ cell, r, c, cs: Math.min(sp?.cs ?? 1, t.columns - c), rs: Math.min(sp?.rs ?? 1, t.rows - r) });
|
|
25750
|
+
}
|
|
25751
|
+
}
|
|
25752
|
+
if (boxes.length === 0) return null;
|
|
25753
|
+
const sizeOf = (cell) => {
|
|
25754
|
+
const px = fontPxOf(cell.textSize);
|
|
25755
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
25756
|
+
const lines = (cell.text ?? "").split("\n");
|
|
25757
|
+
let maxW = 0;
|
|
25758
|
+
for (const line of lines) maxW = Math.max(maxW, ctx.measureText(line).width);
|
|
25759
|
+
let w2 = Math.ceil(maxW) + 2 * PAD_X;
|
|
25760
|
+
let h2 = Math.ceil(lines.length * px * LINE_HEIGHT) + 2 * PAD_Y;
|
|
25761
|
+
if (cell.width) w2 = Math.max(w2, cell.width / 100 * args.plotWidth);
|
|
25762
|
+
if (cell.height) h2 = Math.max(h2, cell.height / 100 * args.paneHeight);
|
|
25763
|
+
return { w: w2, h: h2 };
|
|
25764
|
+
};
|
|
25765
|
+
const spanning = [];
|
|
25766
|
+
for (const box of boxes) {
|
|
25767
|
+
const { w: w2, h: h2 } = sizeOf(box.cell);
|
|
25768
|
+
if (box.cs === 1) colW[box.c] = Math.max(colW[box.c], w2);
|
|
25769
|
+
if (box.rs === 1) rowH[box.r] = Math.max(rowH[box.r], h2);
|
|
25770
|
+
if (box.cs > 1 || box.rs > 1) spanning.push({ box, w: w2, h: h2 });
|
|
25771
|
+
}
|
|
25772
|
+
for (const { box, w: w2, h: h2 } of spanning) {
|
|
25773
|
+
if (box.cs > 1) {
|
|
25774
|
+
let sum = 0;
|
|
25775
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
|
|
25776
|
+
if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
|
|
25777
|
+
}
|
|
25778
|
+
if (box.rs > 1) {
|
|
25779
|
+
let sum = 0;
|
|
25780
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
|
|
25781
|
+
if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
|
|
25782
|
+
}
|
|
25783
|
+
}
|
|
25784
|
+
let w = 0;
|
|
25785
|
+
for (const cw of colW) w += cw;
|
|
25786
|
+
let h = 0;
|
|
25787
|
+
for (const rh of rowH) h += rh;
|
|
25788
|
+
return { colW, rowH, w, h, boxes };
|
|
25789
|
+
}
|
|
25790
|
+
function anchorOrigin(position, totalW, totalH, args) {
|
|
25791
|
+
let y;
|
|
25792
|
+
if (position.startsWith("top")) y = MARGIN;
|
|
25793
|
+
else if (position.startsWith("bottom")) y = args.paneHeight - MARGIN - totalH;
|
|
25794
|
+
else y = args.paneHeight / 2 - totalH / 2;
|
|
25795
|
+
let x;
|
|
25796
|
+
if (position.endsWith("left")) x = MARGIN;
|
|
25797
|
+
else if (position.endsWith("right")) x = args.plotWidth - MARGIN - totalW;
|
|
25798
|
+
else x = args.plotWidth / 2 - totalW / 2;
|
|
25799
|
+
return { x, y };
|
|
25800
|
+
}
|
|
25801
|
+
function cellFont(cell, px, theme) {
|
|
25802
|
+
const family = cell.fontFamily === "monospace" ? "monospace" : theme.fontFamily || "sans-serif";
|
|
25803
|
+
return `${cell.italic ? "italic " : ""}${cell.bold ? "bold " : ""}${px}px ${family}`;
|
|
25804
|
+
}
|
|
25805
|
+
|
|
25806
|
+
// src/renderers/native/drawings/IndicatorDrawingSlices.ts
|
|
25807
|
+
function indicatorSliceKey(z, boundaries) {
|
|
25808
|
+
return boundaries.find((b) => b > z) ?? Infinity;
|
|
25809
|
+
}
|
|
25810
|
+
var IndicatorDrawingSlices = class {
|
|
25811
|
+
constructor() {
|
|
25812
|
+
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
25813
|
+
/** Slice canvas cache, keyed `paneId|beforeZ` — same lifecycle as the user-drawing cache. */
|
|
25814
|
+
this.sliceCache = /* @__PURE__ */ new Map();
|
|
25815
|
+
/** Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per prepare). */
|
|
25816
|
+
this.tips = [];
|
|
25817
|
+
}
|
|
25818
|
+
/**
|
|
25819
|
+
* Rebuild the per-indicator drawing slices for this data frame. `ref` is the data
|
|
25820
|
+
* canvas the slices must match pixel-for-pixel (the backend composites them 1:1).
|
|
25821
|
+
* Runs from the renderer's data paint, just before the backend composites the scene.
|
|
25822
|
+
*/
|
|
25823
|
+
prepare(scene, coords, theme, ref) {
|
|
25824
|
+
this.tips = [];
|
|
25825
|
+
const out = /* @__PURE__ */ new Map();
|
|
25826
|
+
if (ref.width === 0 || ref.height === 0) {
|
|
25827
|
+
this.sliceCache.clear();
|
|
25828
|
+
return out;
|
|
25829
|
+
}
|
|
25830
|
+
this.drawScene.setDeps({
|
|
25831
|
+
timeToLogical: (ms) => coords.timeToLogical(ms),
|
|
25832
|
+
barAt: (logical) => {
|
|
25833
|
+
const b = scene.bars[Math.round(logical)];
|
|
25834
|
+
return b ? { high: b.high, low: b.low } : null;
|
|
25835
|
+
},
|
|
25836
|
+
theme
|
|
25837
|
+
});
|
|
25838
|
+
const dpr = coords.dpr;
|
|
25839
|
+
const dataW = coords.width;
|
|
25840
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
25841
|
+
const add = (paneId, beforeZ, entry) => {
|
|
25842
|
+
const key = `${paneId}|${beforeZ}`;
|
|
25843
|
+
const bucket = buckets.get(key);
|
|
25844
|
+
if (bucket) bucket.entries.push(entry);
|
|
25845
|
+
else buckets.set(key, { paneId, beforeZ, entries: [entry] });
|
|
25846
|
+
};
|
|
25847
|
+
for (const pane of scene.orderedPanes()) {
|
|
25848
|
+
if (pane.collapsed) continue;
|
|
25849
|
+
const boundaries = scene.seriesBoundaries(pane.id);
|
|
25850
|
+
for (const m of scene.orderedIndicatorsForPane(pane.id)) {
|
|
25851
|
+
const set = modelDrawingSet(m, false);
|
|
25852
|
+
const tables = (m.tables ?? []).filter((t) => !t.overlay);
|
|
25853
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
25854
|
+
const sc = scene.scaleFor(m, pane);
|
|
25855
|
+
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
25856
|
+
const beforeZ = indicatorSliceKey(scene.zOf(m.id), boundaries);
|
|
25857
|
+
add(pane.id, beforeZ, { set, tables, pane: mp, indexOffset: scene.offsetOf(m.id) });
|
|
25858
|
+
}
|
|
25859
|
+
if (pane.kind === "price") {
|
|
25860
|
+
for (const m of scene.indicators.values()) {
|
|
25861
|
+
const set = modelDrawingSet(m, true);
|
|
25862
|
+
const tables = (m.tables ?? []).filter((t) => t.overlay === true);
|
|
25863
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
25864
|
+
add(pane.id, Infinity, { set, tables, pane, indexOffset: scene.offsetOf(m.id) });
|
|
25865
|
+
}
|
|
25866
|
+
}
|
|
25867
|
+
}
|
|
25868
|
+
for (const [key, { paneId, beforeZ, entries }] of buckets) {
|
|
25869
|
+
let canvas = this.sliceCache.get(key);
|
|
25870
|
+
if (!canvas) {
|
|
25871
|
+
canvas = document.createElement("canvas");
|
|
25872
|
+
this.sliceCache.set(key, canvas);
|
|
25873
|
+
}
|
|
25874
|
+
if (canvas.width !== ref.width || canvas.height !== ref.height) {
|
|
25875
|
+
canvas.width = ref.width;
|
|
25876
|
+
canvas.height = ref.height;
|
|
25877
|
+
}
|
|
25878
|
+
const ctx = canvas.getContext("2d");
|
|
25879
|
+
if (!ctx) continue;
|
|
25880
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
25881
|
+
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
25882
|
+
for (const e of entries) this.paintEntry(ctx, e, coords, dataW, theme);
|
|
25883
|
+
const slices = out.get(paneId) ?? [];
|
|
25884
|
+
slices.push({ beforeZ, canvas });
|
|
25885
|
+
out.set(paneId, slices);
|
|
25886
|
+
}
|
|
25887
|
+
for (const key of [...this.sliceCache.keys()]) if (!buckets.has(key)) this.sliceCache.delete(key);
|
|
25888
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
25889
|
+
return out;
|
|
25890
|
+
}
|
|
25891
|
+
paintEntry(ctx, e, coords, dataW, theme) {
|
|
25892
|
+
const { pane } = e;
|
|
25893
|
+
const paneTips = [];
|
|
25894
|
+
ctx.save();
|
|
25895
|
+
ctx.translate(0, pane.bounds.top);
|
|
25896
|
+
ctx.beginPath();
|
|
25897
|
+
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
25898
|
+
ctx.clip();
|
|
25899
|
+
this.drawScene.setSet(e.set, e.indexOffset);
|
|
25900
|
+
this.drawScene.render(
|
|
25901
|
+
ctx,
|
|
25902
|
+
dataW,
|
|
25903
|
+
pane.bounds.height,
|
|
25904
|
+
(l) => coords.logicalToX(l),
|
|
25905
|
+
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
25906
|
+
);
|
|
25907
|
+
paneTips.push(...this.drawScene.labelTipRegions());
|
|
25908
|
+
for (const t of e.tables) paintTable(ctx, t, { paneHeight: pane.bounds.height, plotWidth: dataW, theme }, paneTips);
|
|
25909
|
+
ctx.restore();
|
|
25910
|
+
for (const r of paneTips) {
|
|
25911
|
+
this.tips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
25912
|
+
}
|
|
25913
|
+
}
|
|
25914
|
+
/** Tooltip of the topmost label or table cell under a plot-space point, or null. Fed by the last prepare. */
|
|
25915
|
+
labelTooltipAt(x, y) {
|
|
25916
|
+
for (let i = this.tips.length - 1; i >= 0; i -= 1) {
|
|
25917
|
+
const r = this.tips[i];
|
|
25918
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
25919
|
+
}
|
|
25920
|
+
return null;
|
|
25921
|
+
}
|
|
25922
|
+
};
|
|
25923
|
+
function mergeSlices(indicator, user) {
|
|
25924
|
+
const out = /* @__PURE__ */ new Map();
|
|
25925
|
+
for (const [paneId, slices] of indicator) out.set(paneId, [...slices]);
|
|
25926
|
+
for (const [paneId, slices] of user) out.set(paneId, [...out.get(paneId) ?? [], ...slices]);
|
|
25927
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
25928
|
+
return out;
|
|
25929
|
+
}
|
|
25930
|
+
|
|
25568
25931
|
// src/renderers/native/drawings/Projector.ts
|
|
25569
25932
|
function createProjector(coords, paneOf, paneIdAtY, barsInRange) {
|
|
25570
25933
|
return {
|
|
@@ -26444,6 +26807,11 @@ var NativeRenderer = class {
|
|
|
26444
26807
|
this.vpvrRenderer = new VpvrRenderer();
|
|
26445
26808
|
this.resizeObserver = null;
|
|
26446
26809
|
this.dprMedia = null;
|
|
26810
|
+
/** Plot size in INTEGER device px, as last reported by the resize observer's
|
|
26811
|
+
* device-pixel-content-box — the browser's own statement of how many device pixels
|
|
26812
|
+
* it paints the plot into. `null` until the first report or where the box type is
|
|
26813
|
+
* unsupported (WebKit); syncSize then falls back to rounding the client rect. */
|
|
26814
|
+
this.plotDeviceSize = null;
|
|
26447
26815
|
this.coords = new CoordinateSystem();
|
|
26448
26816
|
this.scene = new SceneGraph();
|
|
26449
26817
|
// chosen at mount (WebGL2 if available, else canvas2d)
|
|
@@ -26451,6 +26819,8 @@ var NativeRenderer = class {
|
|
|
26451
26819
|
this.glowAmount = 0;
|
|
26452
26820
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
26453
26821
|
this.chrome = new ChromeRenderer();
|
|
26822
|
+
/** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
|
|
26823
|
+
this.indicatorSlices = new IndicatorDrawingSlices();
|
|
26454
26824
|
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
26455
26825
|
this.labelTooltip = null;
|
|
26456
26826
|
this.crosshairLayer = new CrosshairRenderer();
|
|
@@ -26599,7 +26969,6 @@ var NativeRenderer = class {
|
|
|
26599
26969
|
this.toggleVisibleCbs = /* @__PURE__ */ new Set();
|
|
26600
26970
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
26601
26971
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
26602
|
-
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
26603
26972
|
this.name = "native";
|
|
26604
26973
|
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
26605
26974
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
@@ -26913,7 +27282,6 @@ var NativeRenderer = class {
|
|
|
26913
27282
|
* hidden) on clear so a re-show picks up the current theme.
|
|
26914
27283
|
*/
|
|
26915
27284
|
setLoading(loading) {
|
|
26916
|
-
for (const overlay of this.tableOverlays.values()) overlay.setVisible(!loading);
|
|
26917
27285
|
if (!loading || !this.wrapper) {
|
|
26918
27286
|
this.loadingEl?.remove();
|
|
26919
27287
|
this.loadingEl = null;
|
|
@@ -27552,13 +27920,13 @@ var NativeRenderer = class {
|
|
|
27552
27920
|
resetView: () => this.resetView(),
|
|
27553
27921
|
// User drawings claim a gesture before pan when armed / over a drawing.
|
|
27554
27922
|
drawingsClaim: (x, y) => this.userDrawings?.claim(x, y) ?? false,
|
|
27555
|
-
drawingsMeasureStart: (x, y) => this.userDrawings?.beginMeasureAt(x, y) ?? false,
|
|
27923
|
+
drawingsMeasureStart: (x, y, snap) => this.userDrawings?.beginMeasureAt(x, y, snap) ?? false,
|
|
27556
27924
|
drawingsDeleteAt: (x, y) => this.userDrawings?.deleteAt(x, y) ?? false,
|
|
27557
27925
|
drawingsCancelPlacement: () => this.userDrawings?.cancelPlacement() ?? false,
|
|
27558
27926
|
drawingsSnapMode: () => this.snapMode,
|
|
27559
27927
|
drawingsPointerDown: (x, y, snap, shift) => this.userDrawings?.pointerDown(x, y, snap, shift),
|
|
27560
27928
|
drawingsPointerMove: (x, y, snap, shift) => this.userDrawings?.pointerMove(x, y, snap, shift),
|
|
27561
|
-
drawingsPointerUp: (x, y) => this.userDrawings?.pointerUp(x, y),
|
|
27929
|
+
drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
|
|
27562
27930
|
drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? null,
|
|
27563
27931
|
drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
|
|
27564
27932
|
drawingsClearTransient: () => this.userDrawings?.clearTransient()
|
|
@@ -27575,7 +27943,7 @@ var NativeRenderer = class {
|
|
|
27575
27943
|
this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
|
|
27576
27944
|
this.labelTooltip = new LabelTooltip(this.plot, {
|
|
27577
27945
|
theme: () => this.chromeTheme(),
|
|
27578
|
-
lookup: (x, y) => this.
|
|
27946
|
+
lookup: (x, y) => this.indicatorSlices.labelTooltipAt(x, y)
|
|
27579
27947
|
});
|
|
27580
27948
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
27581
27949
|
projector: () => this.drawingProjector(),
|
|
@@ -27660,6 +28028,7 @@ var NativeRenderer = class {
|
|
|
27660
28028
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
27661
28029
|
}
|
|
27662
28030
|
});
|
|
28031
|
+
this.paneControls.setSuspended(this.layoutMode === "mobile");
|
|
27663
28032
|
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
27664
28033
|
panes: () => this.axisScaleViews(),
|
|
27665
28034
|
rightAxis: () => this.rightAxisW,
|
|
@@ -27669,8 +28038,19 @@ var NativeRenderer = class {
|
|
|
27669
28038
|
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
27670
28039
|
}
|
|
27671
28040
|
});
|
|
27672
|
-
this.resizeObserver = new ResizeObserver(() =>
|
|
28041
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
28042
|
+
for (const e of entries) {
|
|
28043
|
+
if (e.target !== this.plot) continue;
|
|
28044
|
+
const s = e.devicePixelContentBoxSize?.[0];
|
|
28045
|
+
if (s) this.plotDeviceSize = { width: s.inlineSize, height: s.blockSize };
|
|
28046
|
+
}
|
|
28047
|
+
this.resize();
|
|
28048
|
+
});
|
|
27673
28049
|
this.resizeObserver.observe(this.wrapper);
|
|
28050
|
+
try {
|
|
28051
|
+
this.resizeObserver.observe(this.plot, { box: "device-pixel-content-box" });
|
|
28052
|
+
} catch {
|
|
28053
|
+
}
|
|
27674
28054
|
this.watchDpr();
|
|
27675
28055
|
this.syncSize();
|
|
27676
28056
|
}
|
|
@@ -27795,8 +28175,6 @@ var NativeRenderer = class {
|
|
|
27795
28175
|
this.inputsUI?.destroy();
|
|
27796
28176
|
this.paneControls?.destroy();
|
|
27797
28177
|
this.axisScaleButtons?.destroy();
|
|
27798
|
-
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
27799
|
-
this.tableOverlays.clear();
|
|
27800
28178
|
this.resizeObserver?.disconnect();
|
|
27801
28179
|
this.resizeObserver = null;
|
|
27802
28180
|
this.dprMedia?.removeEventListener("change", this.onDprChange);
|
|
@@ -27825,6 +28203,7 @@ var NativeRenderer = class {
|
|
|
27825
28203
|
this.attributionEl = null;
|
|
27826
28204
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
27827
28205
|
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
28206
|
+
this.mountContainer?.style.removeProperty("--vela-bottom-gutter");
|
|
27828
28207
|
this.mountContainer?.style.removeProperty("--vela-price-pane-top");
|
|
27829
28208
|
this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
|
|
27830
28209
|
this.mountContainer = null;
|
|
@@ -27862,7 +28241,8 @@ var NativeRenderer = class {
|
|
|
27862
28241
|
}
|
|
27863
28242
|
const skipFit = opts?.preserveView === true && this.didInitialFit;
|
|
27864
28243
|
if (this.coords.width > 0 && !skipFit) {
|
|
27865
|
-
this.
|
|
28244
|
+
if (this.didInitialFit) this.reframeKeepZoom();
|
|
28245
|
+
else this.fitContent();
|
|
27866
28246
|
this.didInitialFit = true;
|
|
27867
28247
|
}
|
|
27868
28248
|
if (!this.introPlayed && this.bars.length > 0) {
|
|
@@ -27917,7 +28297,6 @@ var NativeRenderer = class {
|
|
|
27917
28297
|
ensurePane(pane) {
|
|
27918
28298
|
this.scene.ensurePane(pane.id, pane.kind, pane.order, pane.heightWeight ?? (pane.kind === "price" ? 3 : 1));
|
|
27919
28299
|
this.layoutPanes();
|
|
27920
|
-
this.repositionTables();
|
|
27921
28300
|
this.paneControls?.refresh();
|
|
27922
28301
|
this.scheduler.invalidate(4 /* Full */);
|
|
27923
28302
|
}
|
|
@@ -27939,17 +28318,14 @@ var NativeRenderer = class {
|
|
|
27939
28318
|
if (!model.ownScale) this.scene.dropIndicatorScale(handle.id);
|
|
27940
28319
|
this.inputsUI.setPane(handle.id, paneId);
|
|
27941
28320
|
this.refreshAnchorOffset(model);
|
|
27942
|
-
this.syncTables(model);
|
|
27943
28321
|
this.refreshAxisWidth();
|
|
27944
28322
|
this.layoutPanes();
|
|
27945
|
-
this.repositionTables();
|
|
27946
28323
|
this.paneControls?.refresh();
|
|
27947
28324
|
this.scheduler.invalidate(4 /* Full */);
|
|
27948
28325
|
}
|
|
27949
28326
|
orderPanes(orderedIds) {
|
|
27950
28327
|
this.scene.orderPanes(orderedIds);
|
|
27951
28328
|
this.layoutPanes();
|
|
27952
|
-
this.repositionTables();
|
|
27953
28329
|
this.paneControls?.refresh();
|
|
27954
28330
|
this.scheduler.invalidate(4 /* Full */);
|
|
27955
28331
|
}
|
|
@@ -27958,7 +28334,6 @@ var NativeRenderer = class {
|
|
|
27958
28334
|
if (!pane || pane.collapsed === collapsed) return;
|
|
27959
28335
|
pane.collapsed = collapsed;
|
|
27960
28336
|
this.layoutPanes();
|
|
27961
|
-
this.repositionTables();
|
|
27962
28337
|
this.paneControls?.refresh();
|
|
27963
28338
|
this.scheduler.invalidate(4 /* Full */);
|
|
27964
28339
|
}
|
|
@@ -27966,7 +28341,6 @@ var NativeRenderer = class {
|
|
|
27966
28341
|
if (paneId !== null && !this.scene.panes.has(paneId)) paneId = null;
|
|
27967
28342
|
this.maximizedPaneId = paneId;
|
|
27968
28343
|
this.layoutPanes();
|
|
27969
|
-
this.repositionTables();
|
|
27970
28344
|
this.paneControls?.refresh();
|
|
27971
28345
|
this.scheduler.invalidate(4 /* Full */);
|
|
27972
28346
|
}
|
|
@@ -28053,7 +28427,6 @@ var NativeRenderer = class {
|
|
|
28053
28427
|
native: !!model.native,
|
|
28054
28428
|
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
28055
28429
|
});
|
|
28056
|
-
this.syncTables(model);
|
|
28057
28430
|
if (model.native?.type === "volume") {
|
|
28058
28431
|
this.volumeActive = true;
|
|
28059
28432
|
this.volumeHidden = false;
|
|
@@ -28077,7 +28450,6 @@ var NativeRenderer = class {
|
|
|
28077
28450
|
}
|
|
28078
28451
|
}
|
|
28079
28452
|
applyPatch(model, patch);
|
|
28080
|
-
this.syncTables(model);
|
|
28081
28453
|
this.scheduler.invalidate(3 /* Light */);
|
|
28082
28454
|
}
|
|
28083
28455
|
removeIndicator(handle) {
|
|
@@ -28096,8 +28468,6 @@ var NativeRenderer = class {
|
|
|
28096
28468
|
this.scene.forgetAnchorOffset(handle.id);
|
|
28097
28469
|
this.scene.dropIndicatorScale(handle.id);
|
|
28098
28470
|
this.inputsUI.remove(handle.id);
|
|
28099
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
28100
|
-
this.tableOverlays.delete(handle.id);
|
|
28101
28471
|
this.refreshAxisWidth();
|
|
28102
28472
|
this.paneControls?.refresh();
|
|
28103
28473
|
this.scheduler.invalidate(4 /* Full */);
|
|
@@ -28141,8 +28511,6 @@ var NativeRenderer = class {
|
|
|
28141
28511
|
}
|
|
28142
28512
|
if (!visible) {
|
|
28143
28513
|
this.scene.indicators.delete(handle.id);
|
|
28144
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
28145
|
-
this.tableOverlays.delete(handle.id);
|
|
28146
28514
|
}
|
|
28147
28515
|
this.inputsUI.setVisible(handle.id, visible);
|
|
28148
28516
|
this.scheduler.invalidate(4 /* Full */);
|
|
@@ -28188,6 +28556,7 @@ var NativeRenderer = class {
|
|
|
28188
28556
|
this.userDrawings?.setLayoutMode(mode);
|
|
28189
28557
|
this.settingsDialog?.setLayoutMode(mode);
|
|
28190
28558
|
this.inputsUI?.setLayoutMode(mode);
|
|
28559
|
+
this.paneControls?.setSuspended(mode === "mobile");
|
|
28191
28560
|
if (this.scrollButton) {
|
|
28192
28561
|
const px = mode === "mobile" ? SCROLL_BTN_SIZE_TOUCH : SCROLL_BTN_SIZE;
|
|
28193
28562
|
this.scrollButton.style.width = `${px}px`;
|
|
@@ -28521,6 +28890,7 @@ var NativeRenderer = class {
|
|
|
28521
28890
|
this.scaleDragHeight = res.height;
|
|
28522
28891
|
this.scaleDragStart = { ...res.holder.scale };
|
|
28523
28892
|
res.holder.manualScale = { ...res.holder.scale };
|
|
28893
|
+
this.axisScaleButtons?.reposition();
|
|
28524
28894
|
this.scheduler.invalidate(4 /* Full */);
|
|
28525
28895
|
}
|
|
28526
28896
|
/** Rescale the grabbed scale around its center by the total drag (down ⇒ zoom out). */
|
|
@@ -28552,6 +28922,7 @@ var NativeRenderer = class {
|
|
|
28552
28922
|
const res = this.resolveScaleHolder(x, y);
|
|
28553
28923
|
if (!res) return;
|
|
28554
28924
|
res.holder.manualScale = null;
|
|
28925
|
+
this.axisScaleButtons?.reposition();
|
|
28555
28926
|
this.scheduler.invalidate(4 /* Full */);
|
|
28556
28927
|
}
|
|
28557
28928
|
/**
|
|
@@ -28583,7 +28954,6 @@ var NativeRenderer = class {
|
|
|
28583
28954
|
/** Relayout + repaint + refresh the hover buttons after a collapse/maximize/order change. */
|
|
28584
28955
|
afterPaneLayoutChange() {
|
|
28585
28956
|
this.layoutPanes();
|
|
28586
|
-
this.repositionTables();
|
|
28587
28957
|
this.paneControls?.refresh();
|
|
28588
28958
|
this.scheduler.invalidate(4 /* Full */);
|
|
28589
28959
|
}
|
|
@@ -28664,7 +29034,6 @@ var NativeRenderer = class {
|
|
|
28664
29034
|
above.heightWeight = next.above;
|
|
28665
29035
|
below.heightWeight = next.below;
|
|
28666
29036
|
this.layoutPanes();
|
|
28667
|
-
this.repositionTables();
|
|
28668
29037
|
this.scheduler.invalidate(4 /* Full */);
|
|
28669
29038
|
}
|
|
28670
29039
|
/** Double-click a separator → split the two adjacent panes evenly (each gets half of
|
|
@@ -28679,7 +29048,6 @@ var NativeRenderer = class {
|
|
|
28679
29048
|
above.heightWeight = half;
|
|
28680
29049
|
below.heightWeight = half;
|
|
28681
29050
|
this.layoutPanes();
|
|
28682
|
-
this.repositionTables();
|
|
28683
29051
|
this.scheduler.invalidate(4 /* Full */);
|
|
28684
29052
|
}
|
|
28685
29053
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -28958,7 +29326,10 @@ var NativeRenderer = class {
|
|
|
28958
29326
|
const liveActual = li >= 0 ? this.bars[li] : void 0;
|
|
28959
29327
|
const easeLive = !!liveActual && this.liveEaseTime === liveActual.time && (liveActual.high !== this.liveEaseHigh || liveActual.low !== this.liveEaseLow || liveActual.close !== this.liveEaseClose);
|
|
28960
29328
|
if (easeLive && liveActual) this.bars[li] = { ...liveActual, high: this.liveEaseHigh, low: this.liveEaseLow, close: this.liveEaseClose };
|
|
28961
|
-
this.scene.drawingSlices =
|
|
29329
|
+
this.scene.drawingSlices = mergeSlices(
|
|
29330
|
+
this.indicatorSlices.prepare(this.scene, this.coords, this.theme, this.dataCanvas),
|
|
29331
|
+
this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map()
|
|
29332
|
+
);
|
|
28962
29333
|
this.backdropRenderer.render(this.scene, this.coords, this.theme, gridAlpha);
|
|
28963
29334
|
this.backend.render(this.scene, this.coords, this.theme);
|
|
28964
29335
|
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
@@ -29189,6 +29560,20 @@ var NativeRenderer = class {
|
|
|
29189
29560
|
this.coords.setViewport(v);
|
|
29190
29561
|
this.targetBarSpacing = v.barSpacing;
|
|
29191
29562
|
}
|
|
29563
|
+
/** Re-frame after a series replacement (a symbol/timeframe switch): keep the user's
|
|
29564
|
+
* zoom (bar spacing), re-anchor the newest bars at the default right offset.
|
|
29565
|
+
* `clampViewport`'s fit-all-bars floor deliberately does NOT apply — a progressive
|
|
29566
|
+
* head may still be backfilling toward the previous depth, and raising the spacing
|
|
29567
|
+
* to its temporary bar count would lose the zoom this exists to keep. */
|
|
29568
|
+
reframeKeepZoom() {
|
|
29569
|
+
this.animator?.stop();
|
|
29570
|
+
this.panVelocity = 0;
|
|
29571
|
+
for (const pane of this.scene.panes.values()) pane.manualScale = null;
|
|
29572
|
+
for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
|
|
29573
|
+
const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset: defaultViewport().rightOffset };
|
|
29574
|
+
this.coords.setViewport(v);
|
|
29575
|
+
this.targetBarSpacing = v.barSpacing;
|
|
29576
|
+
}
|
|
29192
29577
|
paneBoundsFor(paneId) {
|
|
29193
29578
|
const p = this.scene.panes.get(paneId);
|
|
29194
29579
|
return { top: p?.bounds.top ?? 0, height: p?.bounds.height ?? 0, rightAxis: this.rightAxisW };
|
|
@@ -29396,27 +29781,6 @@ var NativeRenderer = class {
|
|
|
29396
29781
|
}
|
|
29397
29782
|
return maxVol;
|
|
29398
29783
|
}
|
|
29399
|
-
/** Create/update/destroy an indicator's DOM table overlay (anchored off real pane geometry). */
|
|
29400
|
-
syncTables(model) {
|
|
29401
|
-
const tables = model.tables ?? [];
|
|
29402
|
-
let overlay = this.tableOverlays.get(model.id);
|
|
29403
|
-
if (tables.length === 0) {
|
|
29404
|
-
if (overlay) {
|
|
29405
|
-
overlay.destroy();
|
|
29406
|
-
this.tableOverlays.delete(model.id);
|
|
29407
|
-
}
|
|
29408
|
-
return;
|
|
29409
|
-
}
|
|
29410
|
-
if (!overlay) {
|
|
29411
|
-
overlay = new TableOverlay(this.plot, this.theme, (id) => this.paneBoundsFor(id));
|
|
29412
|
-
overlay.setVisible(this.loadingEl === null);
|
|
29413
|
-
this.tableOverlays.set(model.id, overlay);
|
|
29414
|
-
}
|
|
29415
|
-
overlay.update(tables);
|
|
29416
|
-
}
|
|
29417
|
-
repositionTables() {
|
|
29418
|
-
for (const overlay of this.tableOverlays.values()) overlay.reposition();
|
|
29419
|
-
}
|
|
29420
29784
|
layoutPanes() {
|
|
29421
29785
|
const panes = this.scene.orderedPanes();
|
|
29422
29786
|
const dataHeight = this.coords.height;
|
|
@@ -29467,6 +29831,7 @@ var NativeRenderer = class {
|
|
|
29467
29831
|
const visible = maxPane ? [maxPane] : this.scene.orderedPanes().filter((p) => !p.collapsed);
|
|
29468
29832
|
const paneBottom = visible.length ? Math.max(...visible.map((p) => p.bounds.top + p.bounds.height)) : dataHeight;
|
|
29469
29833
|
this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM + Math.max(0, dataHeight - paneBottom);
|
|
29834
|
+
this.mountContainer?.style.setProperty("--vela-bottom-gutter", `${TIME_AXIS_H + Math.max(0, dataHeight - paneBottom)}px`);
|
|
29470
29835
|
this.scrollBtnRightPx = this.rightAxisW + SCROLL_BTN_RIGHT_INSET;
|
|
29471
29836
|
if (this.scrollButton) {
|
|
29472
29837
|
this.scrollButton.style.bottom = `${this.scrollBtnBottomPx}px`;
|
|
@@ -29559,30 +29924,33 @@ var NativeRenderer = class {
|
|
|
29559
29924
|
if (w <= 0 || h <= 0) return;
|
|
29560
29925
|
const dpr = window.devicePixelRatio || 1;
|
|
29561
29926
|
this.plot.style.left = `${this.toolbarGutter}px`;
|
|
29562
|
-
const
|
|
29563
|
-
|
|
29564
|
-
|
|
29565
|
-
|
|
29566
|
-
|
|
29567
|
-
|
|
29568
|
-
|
|
29569
|
-
|
|
29570
|
-
|
|
29571
|
-
|
|
29572
|
-
|
|
29573
|
-
|
|
29574
|
-
|
|
29575
|
-
|
|
29576
|
-
|
|
29577
|
-
|
|
29578
|
-
this.
|
|
29579
|
-
this.
|
|
29580
|
-
this.
|
|
29581
|
-
|
|
29927
|
+
const rect = this.plot.getBoundingClientRect();
|
|
29928
|
+
let bw = Math.max(1, Math.round(rect.width * dpr));
|
|
29929
|
+
let bh = Math.max(1, Math.round(rect.height * dpr));
|
|
29930
|
+
const dev = this.plotDeviceSize;
|
|
29931
|
+
if (dev && Math.abs(dev.width - rect.width * dpr) <= 1 && Math.abs(dev.height - rect.height * dpr) <= 1) {
|
|
29932
|
+
bw = Math.max(1, dev.width);
|
|
29933
|
+
bh = Math.max(1, dev.height);
|
|
29934
|
+
}
|
|
29935
|
+
const pw = bw / dpr;
|
|
29936
|
+
const ph = bh / dpr;
|
|
29937
|
+
const size = (canvas) => {
|
|
29938
|
+
canvas.width = bw;
|
|
29939
|
+
canvas.height = bh;
|
|
29940
|
+
canvas.style.width = `${pw}px`;
|
|
29941
|
+
canvas.style.height = `${ph}px`;
|
|
29942
|
+
};
|
|
29943
|
+
size(this.dataCanvas);
|
|
29944
|
+
size(this.backdropCanvas);
|
|
29945
|
+
size(this.volumeCanvas);
|
|
29946
|
+
for (const l of this.extLayers) size(l.canvas);
|
|
29947
|
+
size(this.vpvrCanvas);
|
|
29948
|
+
size(this.chromeCanvas);
|
|
29949
|
+
size(this.drawingsCanvas);
|
|
29950
|
+
size(this.cursorCanvas);
|
|
29582
29951
|
this.coords.setSize(Math.max(1, pw - this.rightAxisW), Math.max(1, ph - TIME_AXIS_H), dpr);
|
|
29583
29952
|
this.scene.crosshair = null;
|
|
29584
29953
|
this.layoutPanes();
|
|
29585
|
-
this.repositionTables();
|
|
29586
29954
|
this.userDrawings?.onResize();
|
|
29587
29955
|
if (!this.didInitialFit && this.coords.barCount > 0) {
|
|
29588
29956
|
this.fitContent();
|
|
@@ -30400,6 +30768,7 @@ exports.drawingTypes = drawingTypes;
|
|
|
30400
30768
|
exports.getDrawingType = getDrawingType;
|
|
30401
30769
|
exports.getNativeIndicator = getNativeIndicator;
|
|
30402
30770
|
exports.iconMarkup = iconMarkup;
|
|
30771
|
+
exports.inputDeltas = inputDeltas;
|
|
30403
30772
|
exports.inputVisible = inputVisible;
|
|
30404
30773
|
exports.legendActions = legendActions;
|
|
30405
30774
|
exports.legendCallouts = legendCallouts;
|