@luxalgo/vela 0.6.8 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataProvider-BSLlBpB9.d.ts → DataProvider-CWmp31dA.d.ts} +49 -1
- package/dist/{DataProvider-BsQM2WNH.d.cts → DataProvider-l_eLMly_.d.cts} +49 -1
- package/dist/{chunk-L3I2CCYO.js → chunk-4KZVZ7QQ.js} +49 -2
- package/dist/{chunk-SQETIBFU.js → chunk-FFOP37FQ.js} +996 -377
- package/dist/{chunk-2R7BANEM.js → chunk-G77Y7LK2.js} +2 -2
- package/dist/{chunk-PN5KWFZ4.js → chunk-KG4YT3TI.js} +5 -3
- package/dist/{chunk-OICPLNU7.js → chunk-MHY7MVXH.js} +192 -2
- package/dist/{chunk-N7LGMKCE.js → chunk-NMTQXNT4.js} +637 -164
- package/dist/{contributions-DLLdV9jD.d.ts → contributions-BCz6Dr6a.d.ts} +99 -7
- package/dist/{contributions-Vw-Hm58R.d.cts → contributions-D9vTzm5p.d.cts} +99 -7
- package/dist/index.cjs +1210 -383
- package/dist/index.d.cts +37 -11
- package/dist/index.d.ts +37 -11
- package/dist/index.js +4 -4
- package/dist/{options-BaVTMXaO.d.ts → options-DSqHsQyN.d.cts} +84 -7
- package/dist/{options-BaVTMXaO.d.cts → options-DSqHsQyN.d.ts} +84 -7
- package/dist/{plugin-BnJgjLAy.d.cts → plugin-Bt8hLR8Z.d.cts} +3 -3
- package/dist/{plugin-CC7rBrOY.d.ts → plugin-CH7pfMrE.d.ts} +3 -3
- package/dist/plugin.cjs +21 -3
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/{statusline-CGkB2EOo.d.ts → statusline-5K7y4Ya2.d.ts} +5 -3
- package/dist/{statusline-DqzBqy42.d.cts → statusline-TYLQmoeh.d.cts} +5 -3
- package/dist/ui.cjs +208 -5
- package/dist/ui.d.cts +91 -2
- package/dist/ui.d.ts +91 -2
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +1210 -383
- package/dist/vela.global.min.js +99 -48
- package/dist/widget.cjs +1771 -449
- package/dist/widget.d.cts +17 -6
- package/dist/widget.d.ts +17 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +1771 -449
- package/dist/workspace.d.cts +100 -6
- package/dist/workspace.d.ts +100 -6
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
package/dist/vela.global.js
CHANGED
|
@@ -114,6 +114,7 @@ var Vela = (function (exports) {
|
|
|
114
114
|
constructor(id, title, controller, source) {
|
|
115
115
|
this.controller = controller;
|
|
116
116
|
this.schema = [];
|
|
117
|
+
this.propsSchema = [];
|
|
117
118
|
this.visibleState = true;
|
|
118
119
|
this.bus = new TypedEventBus();
|
|
119
120
|
this.id = id;
|
|
@@ -123,6 +124,9 @@ var Vela = (function (exports) {
|
|
|
123
124
|
get inputs() {
|
|
124
125
|
return this.schema;
|
|
125
126
|
}
|
|
127
|
+
get props() {
|
|
128
|
+
return this.propsSchema;
|
|
129
|
+
}
|
|
126
130
|
get visible() {
|
|
127
131
|
return this.visibleState;
|
|
128
132
|
}
|
|
@@ -132,6 +136,12 @@ var Vela = (function (exports) {
|
|
|
132
136
|
setInputs(values) {
|
|
133
137
|
this.controller.applyInputs(this.id, values);
|
|
134
138
|
}
|
|
139
|
+
setProp(key, value) {
|
|
140
|
+
this.controller.applyProps(this.id, { [key]: value });
|
|
141
|
+
}
|
|
142
|
+
setProps(values) {
|
|
143
|
+
this.controller.applyProps(this.id, values);
|
|
144
|
+
}
|
|
135
145
|
setVisible(visible) {
|
|
136
146
|
this.controller.setVisible(this.id, visible);
|
|
137
147
|
}
|
|
@@ -151,6 +161,9 @@ var Vela = (function (exports) {
|
|
|
151
161
|
setSchema(schema) {
|
|
152
162
|
this.schema = schema;
|
|
153
163
|
}
|
|
164
|
+
setPropsSchema(schema) {
|
|
165
|
+
this.propsSchema = schema;
|
|
166
|
+
}
|
|
154
167
|
/** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
|
|
155
168
|
setVisibleState(visible) {
|
|
156
169
|
this.visibleState = visible;
|
|
@@ -164,6 +177,8 @@ var Vela = (function (exports) {
|
|
|
164
177
|
function summarizeModel(model) {
|
|
165
178
|
const series = {};
|
|
166
179
|
for (const s of model.series) series[s.kind] = (series[s.kind] ?? 0) + 1;
|
|
180
|
+
const countOverlay = (items) => items?.filter((i) => i.overlay === true).length ?? 0;
|
|
181
|
+
const forcedOverlay = countOverlay(model.series) + countOverlay(model.fills) + countOverlay(model.backgrounds) + countOverlay(model.lines) + countOverlay(model.boxes) + countOverlay(model.labels) + countOverlay(model.polylines) + countOverlay(model.linefills) + countOverlay(model.tables);
|
|
167
182
|
return {
|
|
168
183
|
id: model.id,
|
|
169
184
|
title: model.title,
|
|
@@ -183,7 +198,8 @@ var Vela = (function (exports) {
|
|
|
183
198
|
tables: model.tables?.length ?? 0,
|
|
184
199
|
barColors: model.barColors?.length ?? 0,
|
|
185
200
|
trades: model.trades?.length ?? 0,
|
|
186
|
-
inputs: model.inputs.length
|
|
201
|
+
inputs: model.inputs.length,
|
|
202
|
+
forcedOverlay
|
|
187
203
|
};
|
|
188
204
|
}
|
|
189
205
|
function inspectModels(models) {
|
|
@@ -204,7 +220,8 @@ var Vela = (function (exports) {
|
|
|
204
220
|
linefills: sum((s) => s.linefills),
|
|
205
221
|
tables: sum((s) => s.tables),
|
|
206
222
|
barColors: sum((s) => s.barColors),
|
|
207
|
-
trades: sum((s) => s.trades)
|
|
223
|
+
trades: sum((s) => s.trades),
|
|
224
|
+
forcedOverlay: sum((s) => s.forcedOverlay)
|
|
208
225
|
}
|
|
209
226
|
};
|
|
210
227
|
}
|
|
@@ -474,11 +491,11 @@ var Vela = (function (exports) {
|
|
|
474
491
|
);
|
|
475
492
|
registerIcon(
|
|
476
493
|
"market-pre",
|
|
477
|
-
svg24('<path d="M12 2v8"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="
|
|
494
|
+
svg24('<path d="M12 2v8"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="m8 6 4-4 4 4"/><path d="M16 18a4 4 0 0 0-8 0"/>')
|
|
478
495
|
);
|
|
479
496
|
registerIcon(
|
|
480
497
|
"market-post",
|
|
481
|
-
svg24('<path d="M12 10V2"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="m16
|
|
498
|
+
svg24('<path d="M12 10V2"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="m16 14-4 4-4-4"/><path d="M16 6a4 4 0 0 0-8 0"/>')
|
|
482
499
|
);
|
|
483
500
|
registerIcon("market-closed", svg24('<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>'));
|
|
484
501
|
registerIcon(
|
|
@@ -511,6 +528,8 @@ var Vela = (function (exports) {
|
|
|
511
528
|
registerIcon("folder-minus", S('<path d="M1.6 4.4a1 1 0 0 1 1-1h2.7l1.3 1.7h6.8a1 1 0 0 1 1 1v6.5a1 1 0 0 1-1 1h-10.8a1 1 0 0 1-1-1z"/><path d="M6.2 9.4h3.6"/>'));
|
|
512
529
|
registerIcon("collapse", S('<path d="M3 8h10"/>'));
|
|
513
530
|
registerIcon("expand", S('<rect x="2.2" y="2.2" width="11.6" height="11.6" rx="1.4"/><path d="M8 5.4v5.2M5.4 8h5.2"/>'));
|
|
531
|
+
registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
|
|
532
|
+
registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
|
|
514
533
|
registerIcon("maximize", S('<path d="M2.5 6V3a.5.5 0 0 1 .5-.5h3M10 2.5h3a.5.5 0 0 1 .5.5v3M13.5 10v3a.5.5 0 0 1-.5.5h-3M6 13.5H3a.5.5 0 0 1-.5-.5v-3"/>'));
|
|
515
534
|
registerIcon("restore", S('<path d="M6.2 2.5v3.7H2.5M9.8 13.5V9.8h3.7M13.5 6.2H9.8V2.5M2.5 9.8h3.7v3.7"/>'));
|
|
516
535
|
registerIcon("star", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>'));
|
|
@@ -518,7 +537,7 @@ var Vela = (function (exports) {
|
|
|
518
537
|
registerIcon("grip", S('<circle cx="6" cy="3.5" r="1"/><circle cx="10" cy="3.5" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12.5" r="1"/><circle cx="10" cy="12.5" r="1"/>', 'fill="currentColor" stroke="none"'));
|
|
519
538
|
registerIcon("kebab", S('<circle cx="8" cy="3.2" r="1.2"/><circle cx="8" cy="8" r="1.2"/><circle cx="8" cy="12.8" r="1.2"/>', 'fill="currentColor" stroke="none"'));
|
|
520
539
|
registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
|
|
521
|
-
registerIcon("reset", S('<
|
|
540
|
+
registerIcon("reset", S('<path d="M2 8a6 6 0 1 0 6-6 6.5 6.5 0 0 0-4.5 1.83L2 5.33"/><path d="M2 2v3.33h3.33"/>'));
|
|
522
541
|
registerIcon("pane-collapse", S('<path d="M2.6 9.4h4v4M13.4 6.6h-4v-4"/><path d="m9.4 6.6 4.2-4.2M2.4 13.6l4.2-4.2"/>'));
|
|
523
542
|
registerIcon("pane-expand", S('<path d="M9.8 2.4h3.8v3.8M6.2 13.6H2.4V9.8"/><path d="m13.6 2.4-4.4 4.4M2.4 13.6l4.4-4.4"/>'));
|
|
524
543
|
registerIcon("cursor", svg24('<path d="M5 3l6 16 2-6 6-2z"/>', 'fill="currentColor" stroke="none"'));
|
|
@@ -6889,6 +6908,23 @@ var Vela = (function (exports) {
|
|
|
6889
6908
|
return 36e5;
|
|
6890
6909
|
}
|
|
6891
6910
|
|
|
6911
|
+
// src/data/symbol-groups.ts
|
|
6912
|
+
function isGroupRow(d) {
|
|
6913
|
+
return d.group != null && d.ticker === d.group;
|
|
6914
|
+
}
|
|
6915
|
+
function groupKeyOf(d) {
|
|
6916
|
+
return `${(d.prefix ?? d.provider ?? "").toLowerCase()}:${(d.group ?? "").toUpperCase()}`;
|
|
6917
|
+
}
|
|
6918
|
+
function groupMembers(pool, groupRow) {
|
|
6919
|
+
const key = groupKeyOf(groupRow);
|
|
6920
|
+
return pool.filter((s) => s.group != null && !isGroupRow(s) && groupKeyOf(s) === key);
|
|
6921
|
+
}
|
|
6922
|
+
function defaultMemberOf(pool, groupRow) {
|
|
6923
|
+
const members = groupMembers(pool, groupRow);
|
|
6924
|
+
const defaults2 = members.filter((m) => m.default);
|
|
6925
|
+
return (defaults2.length === 1 ? defaults2[0] : members[0]) ?? null;
|
|
6926
|
+
}
|
|
6927
|
+
|
|
6892
6928
|
// src/data/ProviderRegistry.ts
|
|
6893
6929
|
var normName = (n) => n.trim().toLowerCase();
|
|
6894
6930
|
var normTicker = (t) => t.trim().toUpperCase();
|
|
@@ -6978,17 +7014,24 @@ var Vela = (function (exports) {
|
|
|
6978
7014
|
resolve(raw, opts = {}) {
|
|
6979
7015
|
const { provider, ticker } = this.parse(raw);
|
|
6980
7016
|
if (provider) {
|
|
6981
|
-
if (this.entries.has(provider))
|
|
7017
|
+
if (this.entries.has(provider)) {
|
|
7018
|
+
const d = this.entries.get(provider).byTicker?.get(normTicker(ticker));
|
|
7019
|
+
return { provider, ticker: d != null && isGroupRow(d) ? this.loadableTicker(provider, d) : ticker };
|
|
7020
|
+
}
|
|
6982
7021
|
const key = `${provider}:${normTicker(ticker)}`;
|
|
6983
7022
|
for (const name of this.candidateOrder(opts.default)) {
|
|
6984
7023
|
const d = this.entries.get(name).prefixIndex?.get(key);
|
|
6985
|
-
if (d) return { provider: name, ticker:
|
|
7024
|
+
if (d) return { provider: name, ticker: this.loadableTicker(name, d) };
|
|
6986
7025
|
}
|
|
6987
7026
|
return null;
|
|
6988
7027
|
}
|
|
6989
7028
|
const norm = normTicker(ticker);
|
|
6990
7029
|
for (const name of this.candidateOrder(opts.default)) {
|
|
6991
|
-
|
|
7030
|
+
const e = this.entries.get(name);
|
|
7031
|
+
if (e.index?.has(norm)) {
|
|
7032
|
+
const d = e.byTicker?.get(norm);
|
|
7033
|
+
return { provider: name, ticker: d != null && isGroupRow(d) ? this.loadableTicker(name, d) : ticker };
|
|
7034
|
+
}
|
|
6992
7035
|
}
|
|
6993
7036
|
if (opts.lenient && this.entries.size === 1) {
|
|
6994
7037
|
const only = this.names()[0];
|
|
@@ -6997,6 +7040,13 @@ var Vela = (function (exports) {
|
|
|
6997
7040
|
}
|
|
6998
7041
|
return null;
|
|
6999
7042
|
}
|
|
7043
|
+
/** What a resolved descriptor LOADS: itself — unless it is a GROUP row (listed, never
|
|
7044
|
+
* served), which loads its default member (single `default`, else first listed). A
|
|
7045
|
+
* memberless group keeps its own ticker and fails downstream like any unknown symbol. */
|
|
7046
|
+
loadableTicker(name, d) {
|
|
7047
|
+
if (!isGroupRow(d)) return d.ticker;
|
|
7048
|
+
return defaultMemberOf(this.entries.get(name)?.descriptors ?? [], d)?.ticker ?? d.ticker;
|
|
7049
|
+
}
|
|
7000
7050
|
/**
|
|
7001
7051
|
* Resolve now if possible, else resolve later — re-attempting after each provider
|
|
7002
7052
|
* settles. The promise stays pending until some registered provider can serve the
|
|
@@ -7285,6 +7335,10 @@ var Vela = (function (exports) {
|
|
|
7285
7335
|
/** Invalidates detached async work (backfill loops, in-flight loads, gap heals):
|
|
7286
7336
|
* bumped by init(), setMarket() and destroy(). */
|
|
7287
7337
|
this.generation = 0;
|
|
7338
|
+
/** Aborts the in-flight PROGRESSIVE load's source polling on supersession — an
|
|
7339
|
+
* abandoned stream left polling to its own budget starves the browser's per-host
|
|
7340
|
+
* connection pool, and the NEXT symbol's very first fetch with it (measured). */
|
|
7341
|
+
this.progressiveAbort = null;
|
|
7288
7342
|
/** Awaiters racing a superseded load (setMarket callers) — released on every bump so they never hang. */
|
|
7289
7343
|
this.supersedeWaiters = [];
|
|
7290
7344
|
/** `history:complete` fired for the CURRENT load. Each market load re-arms the cycle
|
|
@@ -7318,7 +7372,9 @@ var Vela = (function (exports) {
|
|
|
7318
7372
|
for (const engine of engines) this.registerEngine(engine.language, engine);
|
|
7319
7373
|
this.defaultLanguage = config.defaultLanguage;
|
|
7320
7374
|
this.renderer.mount(container, config.theme);
|
|
7321
|
-
this.renderer.onInputChange(
|
|
7375
|
+
this.renderer.onInputChange(
|
|
7376
|
+
(e) => e.kind === "prop" ? this.applyProps(e.indicatorId, { [e.key]: e.value }) : this.applyInputs(e.indicatorId, { [e.key]: e.value })
|
|
7377
|
+
);
|
|
7322
7378
|
this.renderer.onRemoveIndicator((id) => this.removeIndicator(id));
|
|
7323
7379
|
this.renderer.onToggleIndicatorVisible?.((id, visible) => this.setVisible(id, visible));
|
|
7324
7380
|
this.paneActionUnsub = this.renderer.onPaneAction?.((a) => this.handlePaneAction(a)) ?? null;
|
|
@@ -7425,6 +7481,8 @@ var Vela = (function (exports) {
|
|
|
7425
7481
|
* superseded setMarket awaiters so their promises resolve instead of hanging. */
|
|
7426
7482
|
bumpGeneration() {
|
|
7427
7483
|
const gen = ++this.generation;
|
|
7484
|
+
this.progressiveAbort?.abort();
|
|
7485
|
+
this.progressiveAbort = null;
|
|
7428
7486
|
for (const w of this.supersedeWaiters.splice(0)) w();
|
|
7429
7487
|
return gen;
|
|
7430
7488
|
}
|
|
@@ -7478,7 +7536,47 @@ var Vela = (function (exports) {
|
|
|
7478
7536
|
const requested = market.bars ?? 500;
|
|
7479
7537
|
const initialRange = market.visibleRange;
|
|
7480
7538
|
const deep = !market.data?.length && initialRange == null && requested > SINGLE_LOAD_BARS;
|
|
7481
|
-
|
|
7539
|
+
let progressiveServed = false;
|
|
7540
|
+
if (!market.data?.length && initialRange == null && this.feed.loadProgressive) {
|
|
7541
|
+
let painted = false;
|
|
7542
|
+
const paint = (bars, final) => {
|
|
7543
|
+
if (this.generation !== gen || !final && bars.length === 0) return;
|
|
7544
|
+
this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
|
|
7545
|
+
if (!painted && bars.length > 0) {
|
|
7546
|
+
painted = true;
|
|
7547
|
+
if (opts.firstLoad) this.activateBarLayers();
|
|
7548
|
+
if (!final) this.historyState = "backfill";
|
|
7549
|
+
}
|
|
7550
|
+
};
|
|
7551
|
+
const abort = new AbortController();
|
|
7552
|
+
this.progressiveAbort = abort;
|
|
7553
|
+
progressiveServed = await new Promise((firstPaint) => {
|
|
7554
|
+
let signaled = false;
|
|
7555
|
+
const signal = (served) => {
|
|
7556
|
+
if (!signaled) {
|
|
7557
|
+
signaled = true;
|
|
7558
|
+
firstPaint(served);
|
|
7559
|
+
}
|
|
7560
|
+
};
|
|
7561
|
+
abort.signal.addEventListener("abort", () => signal(true), { once: true });
|
|
7562
|
+
this.feed.loadProgressive(market, (bars) => {
|
|
7563
|
+
paint(bars, false);
|
|
7564
|
+
if (painted) signal(true);
|
|
7565
|
+
}, { signal: abort.signal }).then((full) => {
|
|
7566
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
7567
|
+
if (full == null) return signal(false);
|
|
7568
|
+
if (this.generation !== gen) return signal(true);
|
|
7569
|
+
paint(full, true);
|
|
7570
|
+
this.completeHistory(full.length >= requested ? "depth" : "genesis");
|
|
7571
|
+
signal(true);
|
|
7572
|
+
}).catch(() => {
|
|
7573
|
+
if (this.progressiveAbort === abort) this.progressiveAbort = null;
|
|
7574
|
+
if (this.generation === gen) this.completeHistory("aborted");
|
|
7575
|
+
signal(true);
|
|
7576
|
+
});
|
|
7577
|
+
});
|
|
7578
|
+
}
|
|
7579
|
+
if (progressiveServed) ; else if (deep && this.feed.loadRange) {
|
|
7482
7580
|
const head = await this.feed.load({ ...market, bars: Math.min(requested, CHUNK_BARS) });
|
|
7483
7581
|
if (this.generation !== gen) return;
|
|
7484
7582
|
this.setBarSeries(head);
|
|
@@ -7998,7 +8096,7 @@ var Vela = (function (exports) {
|
|
|
7998
8096
|
const title = options.title ?? "Indicator";
|
|
7999
8097
|
const handle = new IndicatorHandleImpl(id, title, this, source);
|
|
8000
8098
|
this.handles.set(id, handle);
|
|
8001
|
-
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} } });
|
|
8099
|
+
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} }, propValues: { ...options.props ?? {} } });
|
|
8002
8100
|
void this.startIndicator(id, source, options, handle);
|
|
8003
8101
|
return handle;
|
|
8004
8102
|
}
|
|
@@ -8021,7 +8119,7 @@ var Vela = (function (exports) {
|
|
|
8021
8119
|
return handle;
|
|
8022
8120
|
}
|
|
8023
8121
|
const inputValues = { ...descriptor.defaultInputs(), ...options.inputs ?? {} };
|
|
8024
|
-
this.registry.add({ id, title, source: type, inputValues, native: { type, instance: descriptor.create(), descriptor } });
|
|
8122
|
+
this.registry.add({ id, title, source: type, inputValues, propValues: {}, native: { type, instance: descriptor.create(), descriptor } });
|
|
8025
8123
|
handle.setSchema(descriptor.inputsSchema());
|
|
8026
8124
|
void this.startNativeIndicator(id, handle);
|
|
8027
8125
|
return handle;
|
|
@@ -8074,6 +8172,19 @@ var Vela = (function (exports) {
|
|
|
8074
8172
|
if (record.session) record.session.update(record.inputValues);
|
|
8075
8173
|
else if (record.native && !record.hidden) record.native.instance.setInputs(record.inputValues);
|
|
8076
8174
|
}
|
|
8175
|
+
/** IndicatorController: re-run an indicator with merged declaration-prop overrides.
|
|
8176
|
+
* Same lifecycle as {@link applyInputs} — a prop change replays the whole script.
|
|
8177
|
+
* Script indicators only: natives have no declaration props. */
|
|
8178
|
+
applyProps(id, values) {
|
|
8179
|
+
const record = this.registry.get(id);
|
|
8180
|
+
if (!record?.session) return;
|
|
8181
|
+
record.propValues = { ...record.propValues, ...values };
|
|
8182
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
8183
|
+
record.pendingStructural = true;
|
|
8184
|
+
if (!record.hidden) this.setLoading(record, true);
|
|
8185
|
+
record.pendingCause = "inputs";
|
|
8186
|
+
record.session.update(record.inputValues, record.propValues);
|
|
8187
|
+
}
|
|
8077
8188
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
8078
8189
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
8079
8190
|
listIndicators() {
|
|
@@ -8222,6 +8333,10 @@ var Vela = (function (exports) {
|
|
|
8222
8333
|
for (const input of prepared.inputs) defaults2[input.key] = input.defval;
|
|
8223
8334
|
record.inputValues = { ...defaults2, ...record.inputValues };
|
|
8224
8335
|
handle.setSchema(prepared.inputs);
|
|
8336
|
+
const propDefaults = {};
|
|
8337
|
+
for (const prop of prepared.props ?? []) propDefaults[prop.key] = prop.defval;
|
|
8338
|
+
record.propValues = { ...propDefaults, ...record.propValues };
|
|
8339
|
+
handle.setPropsSchema(prepared.props ?? []);
|
|
8225
8340
|
this.mountLoadingPlaceholder(id, record);
|
|
8226
8341
|
this.executeIndicator(id, handle);
|
|
8227
8342
|
} catch (err) {
|
|
@@ -8245,6 +8360,7 @@ var Vela = (function (exports) {
|
|
|
8245
8360
|
getBars: () => this.bars,
|
|
8246
8361
|
fetchSeries: (sym, tf, range) => this.fetchSeries(sym, tf, range),
|
|
8247
8362
|
inputs: record.inputValues,
|
|
8363
|
+
props: record.propValues,
|
|
8248
8364
|
visibleRange: this.currentVisibleRange(),
|
|
8249
8365
|
mode,
|
|
8250
8366
|
// Mid-backfill session starts (add / re-show / price-style re-execute) let the
|
|
@@ -8338,6 +8454,9 @@ var Vela = (function (exports) {
|
|
|
8338
8454
|
const meta = record.prepared.meta;
|
|
8339
8455
|
const model = {
|
|
8340
8456
|
id,
|
|
8457
|
+
// Deliberately the FULL title, never a shorttitle: while the script loads the
|
|
8458
|
+
// legend identifies it by its full name; the compact shorttitle arrives with
|
|
8459
|
+
// the first computed model and takes over from there.
|
|
8341
8460
|
title: record.options?.title ?? meta.title,
|
|
8342
8461
|
overlay: meta.overlay,
|
|
8343
8462
|
paneHint: meta.overlay ? "price" : "new",
|
|
@@ -8346,7 +8465,8 @@ var Vela = (function (exports) {
|
|
|
8346
8465
|
backgrounds: [],
|
|
8347
8466
|
priceLines: [],
|
|
8348
8467
|
inputs: record.prepared.inputs,
|
|
8349
|
-
inputValues: record.inputValues
|
|
8468
|
+
inputValues: record.inputValues,
|
|
8469
|
+
...record.prepared.props ? { props: record.prepared.props, propValues: record.propValues } : {}
|
|
8350
8470
|
};
|
|
8351
8471
|
const paneId = this.routePane(id, model, record.options ?? {});
|
|
8352
8472
|
this.placeModel(model, id, paneId);
|
|
@@ -8579,7 +8699,7 @@ var Vela = (function (exports) {
|
|
|
8579
8699
|
record.model = model;
|
|
8580
8700
|
if (record.pendingStructural) {
|
|
8581
8701
|
record.renderHandle = this.renderer.mountIndicator(model);
|
|
8582
|
-
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues);
|
|
8702
|
+
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
8583
8703
|
record.pendingStructural = false;
|
|
8584
8704
|
} else {
|
|
8585
8705
|
this.renderer.updateIndicator(record.renderHandle, modelToValuePatch(model));
|
|
@@ -8606,16 +8726,17 @@ var Vela = (function (exports) {
|
|
|
8606
8726
|
placeModel(model, id, paneId) {
|
|
8607
8727
|
model.id = id;
|
|
8608
8728
|
model.paneId = paneId;
|
|
8609
|
-
|
|
8610
|
-
for (const
|
|
8611
|
-
for (const
|
|
8729
|
+
const paneOf = (item) => item.overlay === true ? "price" : paneId;
|
|
8730
|
+
for (const series of model.series) series.paneId = paneOf(series);
|
|
8731
|
+
for (const fill of model.fills) fill.paneId = paneOf(fill);
|
|
8732
|
+
for (const bg of model.backgrounds) bg.paneId = paneOf(bg);
|
|
8612
8733
|
for (const line of model.priceLines) line.paneId = paneId;
|
|
8613
|
-
if (model.lines) for (const ln of model.lines) ln.paneId =
|
|
8614
|
-
if (model.boxes) for (const bx of model.boxes) bx.paneId =
|
|
8615
|
-
if (model.labels) for (const lb of model.labels) lb.paneId =
|
|
8616
|
-
if (model.polylines) for (const pl of model.polylines) pl.paneId =
|
|
8617
|
-
if (model.linefills) for (const lf of model.linefills) lf.paneId =
|
|
8618
|
-
if (model.tables) for (const tb of model.tables) tb.paneId =
|
|
8734
|
+
if (model.lines) for (const ln of model.lines) ln.paneId = paneOf(ln);
|
|
8735
|
+
if (model.boxes) for (const bx of model.boxes) bx.paneId = paneOf(bx);
|
|
8736
|
+
if (model.labels) for (const lb of model.labels) lb.paneId = paneOf(lb);
|
|
8737
|
+
if (model.polylines) for (const pl of model.polylines) pl.paneId = paneOf(pl);
|
|
8738
|
+
if (model.linefills) for (const lf of model.linefills) lf.paneId = paneOf(lf);
|
|
8739
|
+
if (model.tables) for (const tb of model.tables) tb.paneId = paneOf(tb);
|
|
8619
8740
|
}
|
|
8620
8741
|
emitContextChanged(id) {
|
|
8621
8742
|
if (!this.registry.get(id)?.session?.getContext) return;
|
|
@@ -8768,6 +8889,15 @@ var Vela = (function (exports) {
|
|
|
8768
8889
|
setLegendActions(provider) {
|
|
8769
8890
|
this.renderer.setLegendActions?.(provider);
|
|
8770
8891
|
}
|
|
8892
|
+
/**
|
|
8893
|
+
* Wire the legend rows' HOST-CONTRIBUTED callout bubbles (the shells route the
|
|
8894
|
+
* plugin registry through this; see `registerLegendCallout`). Silent on a renderer
|
|
8895
|
+
* without the seam — contributed callouts simply never show there, same graceful
|
|
8896
|
+
* degradation as {@link setLegendActions}.
|
|
8897
|
+
*/
|
|
8898
|
+
setLegendCallouts(provider) {
|
|
8899
|
+
this.renderer.setLegendCallouts?.(provider);
|
|
8900
|
+
}
|
|
8771
8901
|
/**
|
|
8772
8902
|
* Replace the indicator legend's fold toggle with a host action (or restore it with
|
|
8773
8903
|
* `null`) — multi-chart shells point the chip at their indicator overview instead of
|
|
@@ -9118,6 +9248,28 @@ var Vela = (function (exports) {
|
|
|
9118
9248
|
this.store.merge(key, dropForming(bars));
|
|
9119
9249
|
return bars;
|
|
9120
9250
|
}
|
|
9251
|
+
/**
|
|
9252
|
+
* Progressive twin of {@link load}: a COLD load streams through the inner feed's
|
|
9253
|
+
* progressive path — batches forwarded verbatim, the FINAL answer cached exactly as
|
|
9254
|
+
* `load` caches — while a cache-covered load answers once through `load` itself (a
|
|
9255
|
+
* warm chart has nothing to stream). Falls back to `load` wholesale when the inner
|
|
9256
|
+
* feed lacks the capability, so callers may prefer this method unconditionally.
|
|
9257
|
+
*/
|
|
9258
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9259
|
+
if (cfg.data && cfg.data.length > 0) return this.inner.load(cfg);
|
|
9260
|
+
if (!this.inner.loadProgressive) return null;
|
|
9261
|
+
const symbol = cfg.symbol ?? "TEST";
|
|
9262
|
+
const key = cacheKey(symbol, cfg.timeframe ?? "60", cfg.session);
|
|
9263
|
+
const cached = this.store.get(key);
|
|
9264
|
+
const n = cfg.bars ?? 500;
|
|
9265
|
+
const lastCached = cached?.[cached.length - 1];
|
|
9266
|
+
if (cached && lastCached && cached.length >= n - 1 && this.inner.loadRange) return this.load(cfg);
|
|
9267
|
+
this.store.retainSymbol(symbol);
|
|
9268
|
+
const bars = await this.inner.loadProgressive(cfg, onBatch, opts);
|
|
9269
|
+
if (bars == null) return null;
|
|
9270
|
+
this.store.merge(key, dropForming(bars));
|
|
9271
|
+
return bars;
|
|
9272
|
+
}
|
|
9121
9273
|
/**
|
|
9122
9274
|
* Cache-backed ranged fetch — the gateway engines use for secondary series
|
|
9123
9275
|
* (`request.security` HTF/LTF/cross-symbol) and the orchestrator's backward
|
|
@@ -9363,6 +9515,17 @@ var Vela = (function (exports) {
|
|
|
9363
9515
|
this.prefetchSymbolInfo(resolved);
|
|
9364
9516
|
return this.cache.load(canonical(cfg, resolved));
|
|
9365
9517
|
}
|
|
9518
|
+
/** Progressive twin of {@link load} — same resolution, the cache streams the batches. */
|
|
9519
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9520
|
+
if (cfg.data && cfg.data.length > 0) {
|
|
9521
|
+
this.liveBars = cfg.data.map((b) => ({ ...b }));
|
|
9522
|
+
return cfg.data;
|
|
9523
|
+
}
|
|
9524
|
+
const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
|
|
9525
|
+
this.primaryProvider = resolved.provider;
|
|
9526
|
+
this.prefetchSymbolInfo(resolved);
|
|
9527
|
+
return this.cache.loadProgressive(canonical(cfg, resolved), onBatch, opts);
|
|
9528
|
+
}
|
|
9366
9529
|
/**
|
|
9367
9530
|
* Synchronous per-symbol metadata for engines (Pine `syminfo.*`), served from the cache
|
|
9368
9531
|
* warmed by load(). Undefined until the prefetch lands (the engine then synthesizes a
|
|
@@ -9450,6 +9613,18 @@ var Vela = (function (exports) {
|
|
|
9450
9613
|
if (!provider) return Promise.resolve([]);
|
|
9451
9614
|
return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session });
|
|
9452
9615
|
}
|
|
9616
|
+
async loadProgressive(cfg, onBatch, opts) {
|
|
9617
|
+
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
9618
|
+
const provider = this.registry.get(name ?? "");
|
|
9619
|
+
if (!provider) return [];
|
|
9620
|
+
if (!provider.getBarsProgressive) return null;
|
|
9621
|
+
try {
|
|
9622
|
+
return await provider.getBarsProgressive(ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session }, onBatch, opts);
|
|
9623
|
+
} catch (e) {
|
|
9624
|
+
console.warn(`[vela] progressive fetch failed for ${ticker} ${cfg.timeframe ?? "60"} \u2014 ${e instanceof Error ? e.message : String(e)}`);
|
|
9625
|
+
return [];
|
|
9626
|
+
}
|
|
9627
|
+
}
|
|
9453
9628
|
loadRange(cfg, range) {
|
|
9454
9629
|
const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
|
|
9455
9630
|
const provider = this.registry.get(name ?? "");
|
|
@@ -16606,13 +16781,6 @@ var Vela = (function (exports) {
|
|
|
16606
16781
|
}
|
|
16607
16782
|
};
|
|
16608
16783
|
|
|
16609
|
-
// src/core/model/inputs.ts
|
|
16610
|
-
function inputVisible(when, values) {
|
|
16611
|
-
if (!when) return true;
|
|
16612
|
-
const conds = Array.isArray(when) ? when : [when];
|
|
16613
|
-
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
|
|
16614
|
-
}
|
|
16615
|
-
|
|
16616
16784
|
// src/ui/tokens.ts
|
|
16617
16785
|
var STATIC_ID = "vela-ui-tokens";
|
|
16618
16786
|
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
@@ -16858,6 +17026,203 @@ ${STATIC_DECLS}
|
|
|
16858
17026
|
}
|
|
16859
17027
|
};
|
|
16860
17028
|
|
|
17029
|
+
// src/ui/components/callout-bubble/controller.ts
|
|
17030
|
+
function closesPanel(item) {
|
|
17031
|
+
return item.close !== false;
|
|
17032
|
+
}
|
|
17033
|
+
function calloutPanelRows(items) {
|
|
17034
|
+
const rows = [];
|
|
17035
|
+
for (const item of items) {
|
|
17036
|
+
if (item.type === "text") {
|
|
17037
|
+
rows.push({ type: "text", text: item.text });
|
|
17038
|
+
continue;
|
|
17039
|
+
}
|
|
17040
|
+
const last2 = rows[rows.length - 1];
|
|
17041
|
+
if (last2 && last2.type === "buttons") last2.buttons.push(item);
|
|
17042
|
+
else rows.push({ type: "buttons", buttons: [item] });
|
|
17043
|
+
}
|
|
17044
|
+
return rows;
|
|
17045
|
+
}
|
|
17046
|
+
|
|
17047
|
+
// src/ui/components/callout-bubble/styles.ts
|
|
17048
|
+
var CALLOUT_STYLE_ID = "vela-ui-callout-bubble";
|
|
17049
|
+
var CALLOUT_CSS = `
|
|
17050
|
+
.vela-callout {
|
|
17051
|
+
display: inline-grid;
|
|
17052
|
+
place-items: center;
|
|
17053
|
+
border-radius: 50%;
|
|
17054
|
+
flex: none;
|
|
17055
|
+
line-height: 0;
|
|
17056
|
+
box-sizing: border-box;
|
|
17057
|
+
cursor: default;
|
|
17058
|
+
user-select: none;
|
|
17059
|
+
-webkit-user-select: none;
|
|
17060
|
+
}
|
|
17061
|
+
.vela-callout[role='button'] { cursor: pointer; }
|
|
17062
|
+
.vela-callout svg { display: block; }
|
|
17063
|
+
/* The deployed panel \u2014 carries the kit's elevated-card look itself (the popover
|
|
17064
|
+
shell is bare positioning chrome). */
|
|
17065
|
+
.vela-callout-panel {
|
|
17066
|
+
display: flex;
|
|
17067
|
+
flex-direction: column;
|
|
17068
|
+
gap: 8px;
|
|
17069
|
+
padding: 10px 12px;
|
|
17070
|
+
max-width: 280px;
|
|
17071
|
+
box-sizing: border-box;
|
|
17072
|
+
background: var(--vela-surface-elev);
|
|
17073
|
+
border: 1px solid var(--vela-border-strong);
|
|
17074
|
+
border-radius: 6px;
|
|
17075
|
+
box-shadow: var(--vela-shadow);
|
|
17076
|
+
color: var(--vela-fg);
|
|
17077
|
+
font: var(--vela-font-size-md) var(--vela-font);
|
|
17078
|
+
}
|
|
17079
|
+
.vela-callout-title { font-weight: 600; color: var(--vela-fg-bright); }
|
|
17080
|
+
.vela-callout-text { color: var(--vela-fg-muted); line-height: 1.45; white-space: pre-line; }
|
|
17081
|
+
.vela-callout-actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
17082
|
+
.vela-callout-btn {
|
|
17083
|
+
cursor: pointer;
|
|
17084
|
+
height: 26px;
|
|
17085
|
+
padding: 0 10px;
|
|
17086
|
+
border-radius: 5px;
|
|
17087
|
+
border: 1px solid var(--vela-border);
|
|
17088
|
+
background: transparent;
|
|
17089
|
+
color: var(--vela-fg);
|
|
17090
|
+
font-size: var(--vela-font-size-md);
|
|
17091
|
+
font-family: inherit;
|
|
17092
|
+
transition: background var(--vela-dur-fast) ease, color var(--vela-dur-fast) ease, opacity var(--vela-dur-fast) ease, border-color var(--vela-dur-fast) ease;
|
|
17093
|
+
}
|
|
17094
|
+
.vela-callout-btn:hover { background: var(--vela-hover); color: var(--vela-fg-bright); border-color: var(--vela-fg-muted); }
|
|
17095
|
+
.vela-callout-btn-primary { border-color: var(--vela-selected-bg); background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
|
|
17096
|
+
.vela-callout-btn-primary:hover { background: var(--vela-selected-bg); color: var(--vela-selected-fg); opacity: 0.85; border-color: var(--vela-selected-bg); }
|
|
17097
|
+
`;
|
|
17098
|
+
|
|
17099
|
+
// src/ui/components/callout-bubble/view.ts
|
|
17100
|
+
var CalloutBubble = class {
|
|
17101
|
+
constructor(opts) {
|
|
17102
|
+
this.pop = null;
|
|
17103
|
+
this.spec = { icon: opts.icon, background: opts.background, label: opts.label };
|
|
17104
|
+
if (opts.color !== void 0) this.spec.color = opts.color;
|
|
17105
|
+
if (opts.panel !== void 0) this.spec.panel = opts.panel;
|
|
17106
|
+
this.size = opts.size ?? 16;
|
|
17107
|
+
this.host = opts.host;
|
|
17108
|
+
this.theme = opts.theme;
|
|
17109
|
+
this.boundary = opts.boundary;
|
|
17110
|
+
const doc = (opts.host ?? document.body).ownerDocument;
|
|
17111
|
+
injectStyles(CALLOUT_STYLE_ID, CALLOUT_CSS, doc);
|
|
17112
|
+
this.el = doc.createElement("span");
|
|
17113
|
+
this.el.className = "vela-callout";
|
|
17114
|
+
this.el.style.width = `${this.size}px`;
|
|
17115
|
+
this.el.style.height = `${this.size}px`;
|
|
17116
|
+
this.el.addEventListener("click", (e) => {
|
|
17117
|
+
if (!this.spec.panel) return;
|
|
17118
|
+
e.stopPropagation();
|
|
17119
|
+
this.toggle();
|
|
17120
|
+
});
|
|
17121
|
+
this.el.addEventListener("dblclick", (e) => {
|
|
17122
|
+
if (this.spec.panel) e.stopPropagation();
|
|
17123
|
+
});
|
|
17124
|
+
this.el.addEventListener("keydown", (e) => {
|
|
17125
|
+
if (!this.spec.panel || e.key !== "Enter" && e.key !== " ") return;
|
|
17126
|
+
e.preventDefault();
|
|
17127
|
+
this.toggle();
|
|
17128
|
+
});
|
|
17129
|
+
this.dress();
|
|
17130
|
+
}
|
|
17131
|
+
/** Re-dress the bubble (a status change: new icon, tint, label, panel). */
|
|
17132
|
+
set(spec) {
|
|
17133
|
+
this.spec = { ...this.spec, ...spec };
|
|
17134
|
+
if ("panel" in spec) this.pop?.hide();
|
|
17135
|
+
this.dress();
|
|
17136
|
+
}
|
|
17137
|
+
/** Whether the deployed panel is currently open. */
|
|
17138
|
+
get open() {
|
|
17139
|
+
return this.pop?.open ?? false;
|
|
17140
|
+
}
|
|
17141
|
+
/** Close the deployed panel, if any. The bubble itself stays. */
|
|
17142
|
+
hidePanel() {
|
|
17143
|
+
this.pop?.hide();
|
|
17144
|
+
}
|
|
17145
|
+
destroy() {
|
|
17146
|
+
this.pop?.destroy();
|
|
17147
|
+
this.pop = null;
|
|
17148
|
+
this.el.remove();
|
|
17149
|
+
}
|
|
17150
|
+
dress() {
|
|
17151
|
+
const clickable = this.spec.panel !== void 0;
|
|
17152
|
+
this.el.style.background = this.spec.background;
|
|
17153
|
+
this.el.style.color = this.spec.color ?? "";
|
|
17154
|
+
this.el.innerHTML = iconAt(this.spec.icon, this.size - 4);
|
|
17155
|
+
this.el.setAttribute("aria-label", this.spec.label);
|
|
17156
|
+
if (clickable) {
|
|
17157
|
+
this.el.setAttribute("role", "button");
|
|
17158
|
+
this.el.tabIndex = 0;
|
|
17159
|
+
} else {
|
|
17160
|
+
this.el.removeAttribute("role");
|
|
17161
|
+
this.el.removeAttribute("tabindex");
|
|
17162
|
+
}
|
|
17163
|
+
}
|
|
17164
|
+
toggle() {
|
|
17165
|
+
if (this.pop?.open) {
|
|
17166
|
+
this.pop.hide();
|
|
17167
|
+
return;
|
|
17168
|
+
}
|
|
17169
|
+
const panel = this.spec.panel;
|
|
17170
|
+
if (!panel) return;
|
|
17171
|
+
this.pop?.destroy();
|
|
17172
|
+
this.pop = new Popover({
|
|
17173
|
+
trigger: this.el,
|
|
17174
|
+
gap: 6,
|
|
17175
|
+
content: (body) => this.buildPanel(body, panel),
|
|
17176
|
+
...this.host ? { host: this.host } : {},
|
|
17177
|
+
...this.theme ? { theme: this.theme() } : {},
|
|
17178
|
+
...this.boundary ? { boundary: this.boundary } : {}
|
|
17179
|
+
});
|
|
17180
|
+
this.pop.show();
|
|
17181
|
+
}
|
|
17182
|
+
buildPanel(body, panel) {
|
|
17183
|
+
const doc = body.ownerDocument;
|
|
17184
|
+
const root = doc.createElement("div");
|
|
17185
|
+
root.className = "vela-callout-panel";
|
|
17186
|
+
if (panel.title) {
|
|
17187
|
+
const title = doc.createElement("div");
|
|
17188
|
+
title.className = "vela-callout-title";
|
|
17189
|
+
title.textContent = panel.title;
|
|
17190
|
+
root.appendChild(title);
|
|
17191
|
+
}
|
|
17192
|
+
for (const row of calloutPanelRows(panel.items)) {
|
|
17193
|
+
if (row.type === "text") {
|
|
17194
|
+
const text = doc.createElement("div");
|
|
17195
|
+
text.className = "vela-callout-text";
|
|
17196
|
+
text.textContent = row.text;
|
|
17197
|
+
root.appendChild(text);
|
|
17198
|
+
continue;
|
|
17199
|
+
}
|
|
17200
|
+
const actions = doc.createElement("div");
|
|
17201
|
+
actions.className = "vela-callout-actions";
|
|
17202
|
+
for (const item of row.buttons) {
|
|
17203
|
+
const btn2 = doc.createElement("button");
|
|
17204
|
+
btn2.type = "button";
|
|
17205
|
+
btn2.className = "vela-callout-btn" + (item.primary ? " vela-callout-btn-primary" : "");
|
|
17206
|
+
btn2.textContent = item.label;
|
|
17207
|
+
btn2.addEventListener("click", () => {
|
|
17208
|
+
item.run();
|
|
17209
|
+
if (closesPanel(item)) this.pop?.hide();
|
|
17210
|
+
});
|
|
17211
|
+
actions.appendChild(btn2);
|
|
17212
|
+
}
|
|
17213
|
+
root.appendChild(actions);
|
|
17214
|
+
}
|
|
17215
|
+
body.appendChild(root);
|
|
17216
|
+
}
|
|
17217
|
+
};
|
|
17218
|
+
|
|
17219
|
+
// src/core/model/inputs.ts
|
|
17220
|
+
function inputVisible(when, values) {
|
|
17221
|
+
if (!when) return true;
|
|
17222
|
+
const conds = Array.isArray(when) ? when : [when];
|
|
17223
|
+
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
|
|
17224
|
+
}
|
|
17225
|
+
|
|
16861
17226
|
// src/ui/components/select/controller.ts
|
|
16862
17227
|
function selectController(opts) {
|
|
16863
17228
|
const options = opts.options;
|
|
@@ -19961,6 +20326,10 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
19961
20326
|
}
|
|
19962
20327
|
|
|
19963
20328
|
// src/renderers/shared/IndicatorInputsDialog.ts
|
|
20329
|
+
var PROPS_TAB = "Properties";
|
|
20330
|
+
function bagOf(row, decl) {
|
|
20331
|
+
return decl.prop ? row.propValues : row.values;
|
|
20332
|
+
}
|
|
19964
20333
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
19965
20334
|
var IndicatorInputsDialog = class {
|
|
19966
20335
|
constructor(host) {
|
|
@@ -19971,6 +20340,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
19971
20340
|
this.openId = null;
|
|
19972
20341
|
this.row = null;
|
|
19973
20342
|
this.snapshot = null;
|
|
20343
|
+
this.propSnapshot = null;
|
|
19974
20344
|
this.dialogTips = [];
|
|
19975
20345
|
/** Re-applies every `when` gate against current values; set while the dialog is open. */
|
|
19976
20346
|
this.refreshVisibility = null;
|
|
@@ -19993,10 +20363,11 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
19993
20363
|
}
|
|
19994
20364
|
open(row) {
|
|
19995
20365
|
this.close();
|
|
19996
|
-
if (row.inputs.length === 0) return;
|
|
20366
|
+
if (row.inputs.length === 0 && row.props.length === 0) return;
|
|
19997
20367
|
this.row = row;
|
|
19998
20368
|
this.openId = row.id;
|
|
19999
20369
|
this.snapshot = { ...row.values };
|
|
20370
|
+
this.propSnapshot = { ...row.propValues };
|
|
20000
20371
|
ensureDialogStyles();
|
|
20001
20372
|
const t = this.host.theme();
|
|
20002
20373
|
const border = "var(--vela-border)";
|
|
@@ -20004,7 +20375,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20004
20375
|
const host = this.host.dialogHost() ?? this.host.container;
|
|
20005
20376
|
const ui = new Dialog({
|
|
20006
20377
|
host,
|
|
20007
|
-
title: row.
|
|
20378
|
+
title: row.title,
|
|
20008
20379
|
// Non-modal: live-edit dialog — a modal machine locks pointer events on the
|
|
20009
20380
|
// whole body, killing the chart and the body-portaled popovers.
|
|
20010
20381
|
modal: false,
|
|
@@ -20036,6 +20407,16 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20036
20407
|
text: () => "Close"
|
|
20037
20408
|
}));
|
|
20038
20409
|
const tabDefs = tabInputs(row.inputs);
|
|
20410
|
+
if (row.props.length > 0) {
|
|
20411
|
+
const propDecls = row.props.map((p) => {
|
|
20412
|
+
const d = { ...p, prop: true };
|
|
20413
|
+
delete d.tab;
|
|
20414
|
+
return d;
|
|
20415
|
+
});
|
|
20416
|
+
const existing = tabDefs.find((t2) => t2.name === PROPS_TAB);
|
|
20417
|
+
if (existing) existing.inputs.push(...propDecls);
|
|
20418
|
+
else tabDefs.push({ name: PROPS_TAB, inputs: propDecls });
|
|
20419
|
+
}
|
|
20039
20420
|
const tabs = document.createElement("div");
|
|
20040
20421
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
20041
20422
|
const tabEls = [];
|
|
@@ -20138,9 +20519,10 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20138
20519
|
this.openId = null;
|
|
20139
20520
|
this.row = null;
|
|
20140
20521
|
this.snapshot = null;
|
|
20522
|
+
this.propSnapshot = null;
|
|
20141
20523
|
ui?.destroy();
|
|
20142
20524
|
}
|
|
20143
|
-
/** Restore every input to its open-time value (re-running the indicator), then close. */
|
|
20525
|
+
/** Restore every input and prop to its open-time value (re-running the indicator), then close. */
|
|
20144
20526
|
revertAndClose() {
|
|
20145
20527
|
const row = this.row;
|
|
20146
20528
|
const snap = this.snapshot;
|
|
@@ -20154,6 +20536,17 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20154
20536
|
}
|
|
20155
20537
|
}
|
|
20156
20538
|
}
|
|
20539
|
+
const propSnap = this.propSnapshot;
|
|
20540
|
+
if (row && propSnap) {
|
|
20541
|
+
for (const p of row.props) {
|
|
20542
|
+
const snapped = propSnap[p.key];
|
|
20543
|
+
const before = snapped !== void 0 ? snapped : p.defval;
|
|
20544
|
+
if (row.propValues[p.key] !== before) {
|
|
20545
|
+
row.propValues[p.key] = before;
|
|
20546
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: before, kind: "prop" });
|
|
20547
|
+
}
|
|
20548
|
+
}
|
|
20549
|
+
}
|
|
20157
20550
|
this.close();
|
|
20158
20551
|
}
|
|
20159
20552
|
/** The footer's reset button — same chip as Cancel, pinned to the LEFT edge
|
|
@@ -20171,19 +20564,27 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20171
20564
|
const row = this.row;
|
|
20172
20565
|
if (!row) return;
|
|
20173
20566
|
const snap = this.snapshot;
|
|
20567
|
+
const propSnap = this.propSnapshot;
|
|
20174
20568
|
for (const inp of row.inputs) {
|
|
20175
20569
|
if (row.values[inp.key] !== inp.defval) {
|
|
20176
20570
|
row.values[inp.key] = inp.defval;
|
|
20177
20571
|
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value: inp.defval });
|
|
20178
20572
|
}
|
|
20179
20573
|
}
|
|
20574
|
+
for (const p of row.props) {
|
|
20575
|
+
if (row.propValues[p.key] !== p.defval) {
|
|
20576
|
+
row.propValues[p.key] = p.defval;
|
|
20577
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: p.defval, kind: "prop" });
|
|
20578
|
+
}
|
|
20579
|
+
}
|
|
20180
20580
|
this.open(row);
|
|
20181
20581
|
if (snap) this.snapshot = snap;
|
|
20582
|
+
if (propSnap) this.propSnapshot = propSnap;
|
|
20182
20583
|
}
|
|
20183
20584
|
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
20184
|
-
commit(row,
|
|
20185
|
-
row.
|
|
20186
|
-
this.host.onChange?.({ indicatorId: row.id, key, value });
|
|
20585
|
+
commit(row, decl, value) {
|
|
20586
|
+
bagOf(row, decl)[decl.key] = value;
|
|
20587
|
+
this.host.onChange?.({ indicatorId: row.id, key: decl.key, value, ...decl.prop ? { kind: "prop" } : {} });
|
|
20187
20588
|
this.refreshVisibility?.();
|
|
20188
20589
|
}
|
|
20189
20590
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
@@ -20210,7 +20611,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20210
20611
|
const id = idOf(lead);
|
|
20211
20612
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
20212
20613
|
if (lead.type === "bool") {
|
|
20213
|
-
const current = Boolean(row
|
|
20614
|
+
const current = Boolean(bagOf(row, lead)[lead.key] ?? lead.defval);
|
|
20214
20615
|
return append(fieldRow({
|
|
20215
20616
|
label: nameOf(lead),
|
|
20216
20617
|
id,
|
|
@@ -20219,7 +20620,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20219
20620
|
toggle: {
|
|
20220
20621
|
id,
|
|
20221
20622
|
checked: current,
|
|
20222
|
-
onChange: (v) => this.commit(row, lead
|
|
20623
|
+
onChange: (v) => this.commit(row, lead, v)
|
|
20223
20624
|
}
|
|
20224
20625
|
}));
|
|
20225
20626
|
}
|
|
@@ -20244,8 +20645,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20244
20645
|
}
|
|
20245
20646
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
20246
20647
|
buildControl(row, inp, id) {
|
|
20247
|
-
const current = row
|
|
20248
|
-
const emit = (value) => this.commit(row, inp
|
|
20648
|
+
const current = bagOf(row, inp)[inp.key] ?? inp.defval;
|
|
20649
|
+
const emit = (value) => this.commit(row, inp, value);
|
|
20249
20650
|
if (inp.type === "bool") {
|
|
20250
20651
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
20251
20652
|
}
|
|
@@ -20256,7 +20657,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20256
20657
|
kind: "color",
|
|
20257
20658
|
id,
|
|
20258
20659
|
theme: this.host.theme(),
|
|
20259
|
-
get: () => String(row
|
|
20660
|
+
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
20260
20661
|
onChange: (v) => emit(v)
|
|
20261
20662
|
}).el;
|
|
20262
20663
|
}
|
|
@@ -20819,6 +21220,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
20819
21220
|
}
|
|
20820
21221
|
|
|
20821
21222
|
// src/renderers/shared/InputsUI.ts
|
|
21223
|
+
var LEGEND_AT_TOP_ATTR = "data-vela-pane-at-top";
|
|
20822
21224
|
var STATUS_KEYFRAMES = "@keyframes vela-ind-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.6)}}";
|
|
20823
21225
|
var LEGEND_ICON_PX2 = 16;
|
|
20824
21226
|
var LEGEND_CTL_PX = 18;
|
|
@@ -20836,6 +21238,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
20836
21238
|
var FOLD_SVG = iconAt("chevron-up", LEGEND_ICON_PX2);
|
|
20837
21239
|
var UNFOLD_SVG = iconAt("chevron-down", LEGEND_ICON_PX2);
|
|
20838
21240
|
var OVERVIEW_SVG = iconAt("objects", LEGEND_ICON_PX2);
|
|
21241
|
+
function legendCalloutsDisplay(open2, hasCallouts) {
|
|
21242
|
+
return !open2 && hasCallouts ? "inline-flex" : "none";
|
|
21243
|
+
}
|
|
20839
21244
|
var InputsUI = class {
|
|
20840
21245
|
constructor(container, theme, paneBoundsOf) {
|
|
20841
21246
|
this.container = container;
|
|
@@ -20857,10 +21262,14 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
20857
21262
|
this.moveApi = null;
|
|
20858
21263
|
/** Host-contributed legend actions, resolved PER ROW at render time (see setLegendActions). */
|
|
20859
21264
|
this.legendActions = null;
|
|
21265
|
+
/** Host-contributed callout bubbles, resolved PER ROW at render time (see setLegendCallouts). */
|
|
21266
|
+
this.legendCallouts = null;
|
|
20860
21267
|
/** Chrome-tooltip disposers, per row id — a tip open at removal must not outlive its row. */
|
|
20861
21268
|
this.rowTips = /* @__PURE__ */ new Map();
|
|
20862
21269
|
/** Same, for the contributed extras only (rebuilt independently by setLegendActions). */
|
|
20863
21270
|
this.extrasTips = /* @__PURE__ */ new Map();
|
|
21271
|
+
/** Same, for the contributed callouts only (rebuilt independently by setLegendCallouts). */
|
|
21272
|
+
this.calloutTips = /* @__PURE__ */ new Map();
|
|
20864
21273
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
20865
21274
|
this.moveMenu = null;
|
|
20866
21275
|
this.moveTargets = [];
|
|
@@ -21018,6 +21427,42 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21018
21427
|
extrasEl.appendChild(btn2);
|
|
21019
21428
|
}
|
|
21020
21429
|
}
|
|
21430
|
+
/**
|
|
21431
|
+
* Wire the host-contributed callout bubbles. Same contract as
|
|
21432
|
+
* {@link setLegendActions}: re-calling replaces the provider and re-projects the
|
|
21433
|
+
* rows already on screen.
|
|
21434
|
+
*/
|
|
21435
|
+
setLegendCallouts(provider) {
|
|
21436
|
+
this.legendCallouts = provider;
|
|
21437
|
+
for (const row of this.rows.values()) this.renderCallouts(row);
|
|
21438
|
+
}
|
|
21439
|
+
/** (Re)build one row's callout bubbles — tinted icon circles beside the title whose
|
|
21440
|
+
* click (when the view carries content) deploys a panel of text and actions. */
|
|
21441
|
+
renderCallouts(row) {
|
|
21442
|
+
this.disposeTips(this.calloutTips, row.id);
|
|
21443
|
+
for (const bubble of row.callouts) bubble.destroy();
|
|
21444
|
+
row.callouts = [];
|
|
21445
|
+
row.calloutsEl.replaceChildren();
|
|
21446
|
+
const views = this.legendCallouts?.(row.id) ?? [];
|
|
21447
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(row.highlighted, views.length > 0);
|
|
21448
|
+
for (const view of views) {
|
|
21449
|
+
const bubble = new CalloutBubble({
|
|
21450
|
+
icon: view.icon,
|
|
21451
|
+
background: view.background,
|
|
21452
|
+
...view.color !== void 0 ? { color: view.color } : {},
|
|
21453
|
+
label: view.tooltip,
|
|
21454
|
+
...view.content !== void 0 ? { panel: view.content } : {},
|
|
21455
|
+
// The panel portals into the plot host and self-tokens: it must work on
|
|
21456
|
+
// a bare chart, where no `.vela-ui` token ancestor exists.
|
|
21457
|
+
host: this.container,
|
|
21458
|
+
theme: () => this.theme
|
|
21459
|
+
});
|
|
21460
|
+
bubble.el.dataset.legendCallout = view.id;
|
|
21461
|
+
this.tip(this.calloutTips, row.id, bubble.el, view.tooltip);
|
|
21462
|
+
row.callouts.push(bubble);
|
|
21463
|
+
row.calloutsEl.appendChild(bubble.el);
|
|
21464
|
+
}
|
|
21465
|
+
}
|
|
21021
21466
|
/** Reposition the per-pane legend containers after a layout change. */
|
|
21022
21467
|
reposition() {
|
|
21023
21468
|
for (const [paneId, lg] of this.legends) this.positionLegend(lg, paneId);
|
|
@@ -21185,6 +21630,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21185
21630
|
}
|
|
21186
21631
|
positionLegend(lg, paneId) {
|
|
21187
21632
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
21633
|
+
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
21188
21634
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
21189
21635
|
const collapsed = this.paneCollapse.has(paneId);
|
|
21190
21636
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
@@ -21260,13 +21706,13 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21260
21706
|
}
|
|
21261
21707
|
/** Create or update an indicator's legend row (in the legend for its pane). */
|
|
21262
21708
|
upsert(id, title, inputs, values, paneId = "price", opts = {}) {
|
|
21263
|
-
const settingsTitle = opts.settingsTitle ?? title;
|
|
21264
21709
|
const existing = this.rows.get(id);
|
|
21265
21710
|
if (existing) {
|
|
21266
21711
|
existing.title = title;
|
|
21267
|
-
existing.settingsTitle = settingsTitle;
|
|
21268
21712
|
existing.inputs = inputs;
|
|
21269
21713
|
existing.values = { ...values };
|
|
21714
|
+
existing.props = opts.props ?? [];
|
|
21715
|
+
existing.propValues = { ...opts.propValues ?? {} };
|
|
21270
21716
|
existing.titleEl.textContent = title;
|
|
21271
21717
|
if (existing.paneId !== paneId) {
|
|
21272
21718
|
existing.paneId = paneId;
|
|
@@ -21318,6 +21764,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21318
21764
|
titleWrap.appendChild(beta);
|
|
21319
21765
|
}
|
|
21320
21766
|
el.appendChild(titleWrap);
|
|
21767
|
+
const calloutsEl = document.createElement("span");
|
|
21768
|
+
calloutsEl.style.cssText = `display:none;align-items:center;gap:4px;flex:none;margin-left:${LEGEND_TITLE_STATUS_GAP_PX}px;`;
|
|
21769
|
+
el.appendChild(calloutsEl);
|
|
21321
21770
|
el.appendChild(statusEl);
|
|
21322
21771
|
const valuesEl = document.createElement("span");
|
|
21323
21772
|
valuesEl.style.cssText = `display:none;align-items:center;gap:5px;margin-left:${LEGEND_TITLE_VALUES_GAP_PX}px;white-space:nowrap;font-variant-numeric:tabular-nums;`;
|
|
@@ -21334,13 +21783,13 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21334
21783
|
eye.className = "vela-ind-ctl";
|
|
21335
21784
|
eye.style.cssText = LEGEND_CTL_CSS;
|
|
21336
21785
|
eye.addEventListener("click", () => {
|
|
21337
|
-
const
|
|
21338
|
-
this.onToggleVisible?.(id, Boolean(
|
|
21786
|
+
const row2 = this.rows.get(id);
|
|
21787
|
+
this.onToggleVisible?.(id, Boolean(row2?.hidden));
|
|
21339
21788
|
});
|
|
21340
21789
|
controlsEl.appendChild(eye);
|
|
21341
21790
|
eyeEl = eye;
|
|
21342
21791
|
}
|
|
21343
|
-
if (inputs.length > 0) {
|
|
21792
|
+
if (inputs.length > 0 || (opts.props ?? []).length > 0) {
|
|
21344
21793
|
const gear = document.createElement("button");
|
|
21345
21794
|
gear.type = "button";
|
|
21346
21795
|
gear.setAttribute("aria-label", "Settings");
|
|
@@ -21380,7 +21829,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21380
21829
|
controlsEl.appendChild(close);
|
|
21381
21830
|
el.appendChild(controlsEl);
|
|
21382
21831
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
21383
|
-
|
|
21832
|
+
const row = { id, title, inputs, values: { ...values }, props: opts.props ?? [], propValues: { ...opts.propValues ?? {} }, el, titleEl, statusEl, valuesEl, plotValues: [], plotValuesKey: "", showValues: null, highlighted: false, paneId, hidden: false, eyeEl, controlsEl, extrasEl, calloutsEl, callouts: [], native: !!opts.native };
|
|
21833
|
+
this.rows.set(id, row);
|
|
21834
|
+
this.renderCallouts(row);
|
|
21384
21835
|
this.syncFoldToggle();
|
|
21385
21836
|
}
|
|
21386
21837
|
/** Place a row in its pane's legend — native rows PREPEND (pinned to the top), Pine rows append. */
|
|
@@ -21389,9 +21840,11 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21389
21840
|
else container.appendChild(el);
|
|
21390
21841
|
}
|
|
21391
21842
|
/** Reflect programmatic input changes (so a re-opened dialog shows current values). */
|
|
21392
|
-
setValues(id, values) {
|
|
21843
|
+
setValues(id, values, props) {
|
|
21393
21844
|
const row = this.rows.get(id);
|
|
21394
|
-
if (row)
|
|
21845
|
+
if (!row) return;
|
|
21846
|
+
row.values = { ...row.values, ...values };
|
|
21847
|
+
if (props) row.propValues = { ...row.propValues, ...props };
|
|
21395
21848
|
}
|
|
21396
21849
|
/**
|
|
21397
21850
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
@@ -21473,8 +21926,13 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21473
21926
|
syncRowActions(row) {
|
|
21474
21927
|
const open2 = row.highlighted;
|
|
21475
21928
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
21476
|
-
if (open2)
|
|
21477
|
-
|
|
21929
|
+
if (open2) {
|
|
21930
|
+
row.el.appendChild(row.statusEl);
|
|
21931
|
+
for (const bubble of row.callouts) bubble.hidePanel();
|
|
21932
|
+
} else {
|
|
21933
|
+
row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
21934
|
+
}
|
|
21935
|
+
row.calloutsEl.style.display = legendCalloutsDisplay(open2, row.callouts.length > 0);
|
|
21478
21936
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
21479
21937
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
21480
21938
|
if (child === row.extrasEl) {
|
|
@@ -21492,10 +21950,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21492
21950
|
}
|
|
21493
21951
|
remove(id) {
|
|
21494
21952
|
const row = this.rows.get(id);
|
|
21953
|
+
for (const bubble of row?.callouts ?? []) bubble.destroy();
|
|
21495
21954
|
row?.el.remove();
|
|
21496
21955
|
this.rows.delete(id);
|
|
21497
21956
|
this.disposeTips(this.rowTips, id);
|
|
21498
21957
|
this.disposeTips(this.extrasTips, id);
|
|
21958
|
+
this.disposeTips(this.calloutTips, id);
|
|
21499
21959
|
if (this.selectedId === id) this.selectedId = null;
|
|
21500
21960
|
if (this.inputsDialog.openId === id) this.inputsDialog.close();
|
|
21501
21961
|
this.syncFoldToggle();
|
|
@@ -21515,6 +21975,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21515
21975
|
this.rowMenu = null;
|
|
21516
21976
|
for (const id of [...this.rowTips.keys()]) this.disposeTips(this.rowTips, id);
|
|
21517
21977
|
for (const id of [...this.extrasTips.keys()]) this.disposeTips(this.extrasTips, id);
|
|
21978
|
+
for (const id of [...this.calloutTips.keys()]) this.disposeTips(this.calloutTips, id);
|
|
21979
|
+
for (const row of this.rows.values()) for (const bubble of row.callouts) bubble.destroy();
|
|
21518
21980
|
for (const lg of this.legends.values()) lg.remove();
|
|
21519
21981
|
this.legends.clear();
|
|
21520
21982
|
this.rows.clear();
|
|
@@ -21560,7 +22022,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21560
22022
|
};
|
|
21561
22023
|
var STYLE_ID = "vela-pane-controls";
|
|
21562
22024
|
var ICON_PX = 12;
|
|
21563
|
-
var CLUSTER_PILL = "rgba(0,0,0,0.
|
|
22025
|
+
var CLUSTER_PILL = "rgba(0,0,0,0.65)";
|
|
21564
22026
|
function ensureStyles() {
|
|
21565
22027
|
if (typeof document === "undefined" || document.getElementById(STYLE_ID)) return;
|
|
21566
22028
|
const st = document.createElement("style");
|
|
@@ -21580,8 +22042,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21580
22042
|
this.deps = deps;
|
|
21581
22043
|
this.clusters = /* @__PURE__ */ new Map();
|
|
21582
22044
|
this.hoverPaneId = null;
|
|
22045
|
+
/** Mobile: hover clusters are meaningless without a cursor — suppressed; a
|
|
22046
|
+
* collapsed pane's standalone expand chip stays (the only way back up). */
|
|
22047
|
+
this.suspended = false;
|
|
21583
22048
|
/** Reveal the cluster for the pane under the cursor, resolved from the pointer's y in the plot. */
|
|
21584
22049
|
this.onPlotMove = (e) => {
|
|
22050
|
+
if (this.suspended) return;
|
|
21585
22051
|
const rect = this.plot.getBoundingClientRect();
|
|
21586
22052
|
const y = e.clientY - rect.top;
|
|
21587
22053
|
let hit = null;
|
|
@@ -21653,7 +22119,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21653
22119
|
}
|
|
21654
22120
|
if (p.count > 1) {
|
|
21655
22121
|
cluster.appendChild(
|
|
21656
|
-
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
22122
|
+
this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
|
|
22123
|
+
role: "maximize",
|
|
22124
|
+
// Same inverse-chip treatment as the collapsed pane's expand toggle: the
|
|
22125
|
+
// maximized state must read as an active state, not just a swapped glyph.
|
|
22126
|
+
selected: p.maximized
|
|
22127
|
+
})
|
|
21657
22128
|
);
|
|
21658
22129
|
}
|
|
21659
22130
|
}
|
|
@@ -21690,17 +22161,18 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21690
22161
|
}
|
|
21691
22162
|
const hovered = id === this.hoverPaneId;
|
|
21692
22163
|
const hasButtons = cluster.children.length > 0;
|
|
21693
|
-
const
|
|
22164
|
+
const stateChipRole = p.collapsed ? "collapse" : !this.suspended && p.maximized ? "maximize" : null;
|
|
22165
|
+
const visible = hasButtons && (hovered || stateChipRole != null) && p.height > 8;
|
|
21694
22166
|
cluster.style.right = `${rightPx}px`;
|
|
21695
22167
|
cluster.style.top = p.collapsed ? `${p.top + Math.max(1, Math.round((p.height - 24) / 2))}px` : `${p.top + 4}px`;
|
|
21696
22168
|
cluster.style.display = visible ? "flex" : "none";
|
|
21697
22169
|
if (!visible) continue;
|
|
21698
|
-
const
|
|
21699
|
-
cluster.style.background =
|
|
22170
|
+
const soloChip = stateChipRole != null && !hovered;
|
|
22171
|
+
cluster.style.background = soloChip ? "transparent" : CLUSTER_PILL;
|
|
21700
22172
|
for (const child of cluster.children) {
|
|
21701
22173
|
const btn2 = child;
|
|
21702
22174
|
btn2.style.display = "inline-flex";
|
|
21703
|
-
btn2.style.visibility =
|
|
22175
|
+
btn2.style.visibility = soloChip && btn2.dataset.role !== stateChipRole ? "hidden" : "visible";
|
|
21704
22176
|
}
|
|
21705
22177
|
}
|
|
21706
22178
|
}
|
|
@@ -21710,6 +22182,14 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21710
22182
|
this.hoverPaneId = paneId;
|
|
21711
22183
|
this.reposition();
|
|
21712
22184
|
}
|
|
22185
|
+
/** Mobile suppression: no hover clusters (touch has no cursor; the shell's own
|
|
22186
|
+
* chrome covers maximize), while collapsed panes keep their expand chips. */
|
|
22187
|
+
setSuspended(on) {
|
|
22188
|
+
if (on === this.suspended) return;
|
|
22189
|
+
this.suspended = on;
|
|
22190
|
+
if (on) this.hoverPaneId = null;
|
|
22191
|
+
this.reposition();
|
|
22192
|
+
}
|
|
21713
22193
|
destroy() {
|
|
21714
22194
|
this.plot.removeEventListener("pointermove", this.onPlotMove);
|
|
21715
22195
|
this.plot.removeEventListener("pointerleave", this.onPlotLeave);
|
|
@@ -21860,131 +22340,6 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21860
22340
|
}
|
|
21861
22341
|
};
|
|
21862
22342
|
|
|
21863
|
-
// src/renderers/shared/TableOverlay.ts
|
|
21864
|
-
var SIZE_PX3 = {
|
|
21865
|
-
auto: 13,
|
|
21866
|
-
tiny: 10,
|
|
21867
|
-
small: 11,
|
|
21868
|
-
normal: 13,
|
|
21869
|
-
large: 16,
|
|
21870
|
-
huge: 20
|
|
21871
|
-
};
|
|
21872
|
-
var TableOverlay = class {
|
|
21873
|
-
constructor(container, theme, paneBounds) {
|
|
21874
|
-
this.container = container;
|
|
21875
|
-
this.theme = theme;
|
|
21876
|
-
this.paneBounds = paneBounds;
|
|
21877
|
-
this.lastTables = [];
|
|
21878
|
-
if (getComputedStyle(container).position === "static") container.style.position = "relative";
|
|
21879
|
-
this.root = document.createElement("div");
|
|
21880
|
-
Object.assign(this.root.style, {
|
|
21881
|
-
position: "absolute",
|
|
21882
|
-
inset: "0",
|
|
21883
|
-
pointerEvents: "none",
|
|
21884
|
-
overflow: "hidden",
|
|
21885
|
-
zIndex: "3"
|
|
21886
|
-
});
|
|
21887
|
-
container.appendChild(this.root);
|
|
21888
|
-
}
|
|
21889
|
-
update(tables) {
|
|
21890
|
-
this.lastTables = tables;
|
|
21891
|
-
this.root.replaceChildren();
|
|
21892
|
-
for (const t of tables) this.root.appendChild(this.renderTable(t));
|
|
21893
|
-
}
|
|
21894
|
-
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
21895
|
-
reposition() {
|
|
21896
|
-
if (this.root.isConnected) this.update(this.lastTables);
|
|
21897
|
-
}
|
|
21898
|
-
/** Show/hide the whole overlay. Tables anchor to pane corners, not to bars, so unlike the
|
|
21899
|
-
* series content they DON'T vanish with an emptied chart — the loading state hides them. */
|
|
21900
|
-
setVisible(visible) {
|
|
21901
|
-
this.root.style.display = visible ? "" : "none";
|
|
21902
|
-
}
|
|
21903
|
-
destroy() {
|
|
21904
|
-
this.root.remove();
|
|
21905
|
-
}
|
|
21906
|
-
renderTable(t) {
|
|
21907
|
-
const wrap2 = document.createElement("div");
|
|
21908
|
-
wrap2.style.position = "absolute";
|
|
21909
|
-
this.anchor(wrap2, t.position, this.paneBounds(t.paneId));
|
|
21910
|
-
const table = document.createElement("table");
|
|
21911
|
-
Object.assign(table.style, {
|
|
21912
|
-
borderCollapse: "collapse",
|
|
21913
|
-
background: t.bgColor ?? "transparent",
|
|
21914
|
-
fontFamily: this.theme.fontFamily || "sans-serif",
|
|
21915
|
-
// Frame as an inset shadow (not a `border`) so it stays independent of the
|
|
21916
|
-
// collapsed cell borders even when frame_width != border_width.
|
|
21917
|
-
boxShadow: t.frameColor && t.frameWidth > 0 ? `inset 0 0 0 ${t.frameWidth}px ${t.frameColor}` : "none",
|
|
21918
|
-
border: "none",
|
|
21919
|
-
tableLayout: "auto",
|
|
21920
|
-
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
21921
|
-
pointerEvents: "auto"
|
|
21922
|
-
});
|
|
21923
|
-
const span = /* @__PURE__ */ new Map();
|
|
21924
|
-
const skip = /* @__PURE__ */ new Set();
|
|
21925
|
-
for (const m of t.merges) {
|
|
21926
|
-
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
21927
|
-
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
21928
|
-
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
21929
|
-
if (r !== m.startRow || c !== m.startCol) skip.add(`${r}:${c}`);
|
|
21930
|
-
}
|
|
21931
|
-
}
|
|
21932
|
-
}
|
|
21933
|
-
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
21934
|
-
for (let r = 0; r < t.rows; r += 1) {
|
|
21935
|
-
const tr = document.createElement("tr");
|
|
21936
|
-
for (let c = 0; c < t.columns; c += 1) {
|
|
21937
|
-
const cell = t.cells[r]?.[c] ?? null;
|
|
21938
|
-
if (skip.has(`${r}:${c}`) || cell?.merged) continue;
|
|
21939
|
-
const td = document.createElement("td");
|
|
21940
|
-
const sp = span.get(`${r}:${c}`);
|
|
21941
|
-
if (sp) {
|
|
21942
|
-
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
21943
|
-
if (sp.rs > 1) td.rowSpan = sp.rs;
|
|
21944
|
-
}
|
|
21945
|
-
Object.assign(td.style, {
|
|
21946
|
-
border: cellBorder,
|
|
21947
|
-
padding: "2px 6px",
|
|
21948
|
-
background: cell?.bgColor ?? "transparent",
|
|
21949
|
-
color: cell?.textColor ?? this.theme.textColor,
|
|
21950
|
-
textAlign: cell?.hAlign ?? "center",
|
|
21951
|
-
verticalAlign: cell?.vAlign === "top" ? "top" : cell?.vAlign === "bottom" ? "bottom" : "middle",
|
|
21952
|
-
fontSize: `${SIZE_PX3[cell?.textSize ?? "normal"]}px`,
|
|
21953
|
-
fontFamily: cell?.fontFamily === "monospace" ? "monospace" : "inherit",
|
|
21954
|
-
fontWeight: cell?.bold ? "bold" : "normal",
|
|
21955
|
-
fontStyle: cell?.italic ? "italic" : "normal",
|
|
21956
|
-
whiteSpace: "pre-line"
|
|
21957
|
-
});
|
|
21958
|
-
if (cell?.tooltip) td.title = cell.tooltip;
|
|
21959
|
-
td.textContent = cell?.text ?? "";
|
|
21960
|
-
tr.appendChild(td);
|
|
21961
|
-
}
|
|
21962
|
-
table.appendChild(tr);
|
|
21963
|
-
}
|
|
21964
|
-
wrap2.appendChild(table);
|
|
21965
|
-
return wrap2;
|
|
21966
|
-
}
|
|
21967
|
-
/**
|
|
21968
|
-
* Position the wrapper at a Pine `position.*` corner/edge of the table's PANE
|
|
21969
|
-
* (not the whole chart), inset past the right price axis so it never overlaps
|
|
21970
|
-
* the Y-axis labels.
|
|
21971
|
-
*/
|
|
21972
|
-
anchor(el, position, b) {
|
|
21973
|
-
const m = 6;
|
|
21974
|
-
const containerH = this.root.clientHeight || this.container.clientHeight;
|
|
21975
|
-
const paneBottom = b.top + b.height;
|
|
21976
|
-
if (position.startsWith("top")) el.style.top = `${b.top + m}px`;
|
|
21977
|
-
else if (position.startsWith("bottom")) el.style.bottom = `${Math.max(0, containerH - paneBottom) + m}px`;
|
|
21978
|
-
else el.style.top = `${b.top + b.height / 2}px`;
|
|
21979
|
-
if (position.endsWith("left")) el.style.left = `${m}px`;
|
|
21980
|
-
else if (position.endsWith("right")) el.style.right = `${b.rightAxis + m}px`;
|
|
21981
|
-
else el.style.left = `calc(50% - ${b.rightAxis / 2}px)`;
|
|
21982
|
-
const tx = position.endsWith("center") ? "-50%" : "0";
|
|
21983
|
-
const ty = position.startsWith("middle") ? "-50%" : "0";
|
|
21984
|
-
if (tx !== "0" || ty !== "0") el.style.transform = `translate(${tx}, ${ty})`;
|
|
21985
|
-
}
|
|
21986
|
-
};
|
|
21987
|
-
|
|
21988
22343
|
// src/renderers/native/capabilities.ts
|
|
21989
22344
|
var NATIVE_CAPABILITIES = {
|
|
21990
22345
|
panes: true,
|
|
@@ -22005,7 +22360,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22005
22360
|
drawingDepth: true,
|
|
22006
22361
|
// drawings share the series' z space (backend-composited interleave layers)
|
|
22007
22362
|
tables: true,
|
|
22008
|
-
//
|
|
22363
|
+
// canvas-painted into the owning indicator's interleave slice
|
|
22009
22364
|
trades: true,
|
|
22010
22365
|
// strategy order-fill markers (arrows + labels + fill-price ticks)
|
|
22011
22366
|
inputsUI: true
|
|
@@ -22036,6 +22391,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
22036
22391
|
if (spacing < CANDLE_BODY_MIN_SPACING) return "wick";
|
|
22037
22392
|
return "full";
|
|
22038
22393
|
}
|
|
22394
|
+
function snapY(yCss, dpr) {
|
|
22395
|
+
return Math.round(yCss * dpr) / dpr;
|
|
22396
|
+
}
|
|
22039
22397
|
function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
22040
22398
|
const wickDev = Math.max(1, Math.round(wickWidth(spacing) * dpr));
|
|
22041
22399
|
const wickLeftDev = Math.round(xCss * dpr - wickDev / 2);
|
|
@@ -22841,8 +23199,12 @@ void main() {
|
|
|
22841
23199
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
22842
23200
|
};
|
|
22843
23201
|
b.alpha = this.modelAlpha;
|
|
22844
|
-
for (const m of models) for (const bgSpan of m.backgrounds) this.emitBackground(b, bgSpan, pane, coords);
|
|
22845
|
-
|
|
23202
|
+
for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
|
|
23203
|
+
if (isPrice) {
|
|
23204
|
+
for (const m of scene.indicators.values()) {
|
|
23205
|
+
for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
|
|
23206
|
+
}
|
|
23207
|
+
}
|
|
22846
23208
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
22847
23209
|
let candleDrawn = false;
|
|
22848
23210
|
for (const m of models) {
|
|
@@ -22856,15 +23218,26 @@ void main() {
|
|
|
22856
23218
|
b.alpha = this.modelAlpha;
|
|
22857
23219
|
const off = scene.offsetOf(m.id);
|
|
22858
23220
|
const mp = effPane(m);
|
|
22859
|
-
for (const
|
|
23221
|
+
for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, mp, coords, i0, i1, off);
|
|
23222
|
+
for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
22860
23223
|
}
|
|
22861
23224
|
if (drawCandles && !candleDrawn) {
|
|
22862
23225
|
drawSlicesUpTo(scene.candleZ);
|
|
22863
23226
|
b.alpha = this.candleStructureAlpha;
|
|
22864
23227
|
this.emitPriceSeries(b, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
22865
23228
|
}
|
|
22866
|
-
drawSlicesUpTo(Infinity);
|
|
22867
23229
|
b.alpha = this.modelAlpha;
|
|
23230
|
+
if (isPrice) {
|
|
23231
|
+
for (const m of scene.indicators.values()) {
|
|
23232
|
+
const off = scene.offsetOf(m.id);
|
|
23233
|
+
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, off);
|
|
23234
|
+
}
|
|
23235
|
+
for (const m of scene.indicators.values()) {
|
|
23236
|
+
const off = scene.offsetOf(m.id);
|
|
23237
|
+
for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
|
|
23238
|
+
}
|
|
23239
|
+
}
|
|
23240
|
+
drawSlicesUpTo(Infinity);
|
|
22868
23241
|
for (const m of models) {
|
|
22869
23242
|
const mp = effPane(m);
|
|
22870
23243
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -22911,7 +23284,7 @@ void main() {
|
|
|
22911
23284
|
for (const pane of scene.orderedPanes()) {
|
|
22912
23285
|
const b = this.glowBatch;
|
|
22913
23286
|
b.reset();
|
|
22914
|
-
this.emitGlowSources(b, scene
|
|
23287
|
+
this.emitGlowSources(b, scene, pane, coords, i0, i1);
|
|
22915
23288
|
if (b.vertexCount === 0) continue;
|
|
22916
23289
|
const topH = Math.round(pane.bounds.top * dpr * 0.5);
|
|
22917
23290
|
const botH = Math.round((pane.bounds.top + pane.bounds.height) * dpr * 0.5);
|
|
@@ -22980,15 +23353,23 @@ void main() {
|
|
|
22980
23353
|
gl.bindVertexArray(null);
|
|
22981
23354
|
return true;
|
|
22982
23355
|
}
|
|
22983
|
-
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
22984
|
-
|
|
22985
|
-
|
|
22986
|
-
|
|
22987
|
-
|
|
22988
|
-
|
|
22989
|
-
|
|
22990
|
-
|
|
22991
|
-
|
|
23356
|
+
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
23357
|
+
* Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
|
|
23358
|
+
emitGlowSources(b, scene, pane, coords, i0, i1) {
|
|
23359
|
+
const emitOne = (s, off) => {
|
|
23360
|
+
if (!isLineLikeSeries(s) || s.visible === false) return;
|
|
23361
|
+
if (s.kind === "histogram" || s.kind === "columns") return;
|
|
23362
|
+
if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
|
|
23363
|
+
else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
|
|
23364
|
+
};
|
|
23365
|
+
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
23366
|
+
const off = scene.offsetOf(m.id);
|
|
23367
|
+
for (const s of m.series) if (s.overlay !== true) emitOne(s, off);
|
|
23368
|
+
}
|
|
23369
|
+
if (pane.kind === "price") {
|
|
23370
|
+
for (const m of scene.indicators.values()) {
|
|
23371
|
+
const off = scene.offsetOf(m.id);
|
|
23372
|
+
for (const s of m.series) if (s.overlay === true) emitOne(s, off);
|
|
22992
23373
|
}
|
|
22993
23374
|
}
|
|
22994
23375
|
}
|
|
@@ -23251,12 +23632,12 @@ void main() {
|
|
|
23251
23632
|
if (drawBody) {
|
|
23252
23633
|
const oY = coords.priceToY(bar.open, pane.scale, pane.bounds);
|
|
23253
23634
|
const cY = coords.priceToY(bar.close, pane.scale, pane.bounds);
|
|
23254
|
-
bodyTop = Math.min(oY, cY);
|
|
23255
|
-
bodyH = Math.max(1, Math.
|
|
23635
|
+
bodyTop = snapY(Math.min(oY, cY), coords.dpr);
|
|
23636
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - bodyTop);
|
|
23256
23637
|
}
|
|
23257
23638
|
if (cs.wickVisible) {
|
|
23258
|
-
const hY = coords.priceToY(bar.high, pane.scale, pane.bounds);
|
|
23259
|
-
const lY = coords.priceToY(bar.low, pane.scale, pane.bounds);
|
|
23639
|
+
const hY = snapY(coords.priceToY(bar.high, pane.scale, pane.bounds), coords.dpr);
|
|
23640
|
+
const lY = snapY(coords.priceToY(bar.low, pane.scale, pane.bounds), coords.dpr);
|
|
23260
23641
|
b.alpha = this.candleStructureAlpha;
|
|
23261
23642
|
const wCol = parseColor((isUp ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : bodyColorStr));
|
|
23262
23643
|
if (drawBody) {
|
|
@@ -23271,9 +23652,9 @@ void main() {
|
|
|
23271
23652
|
b.alpha = this.candleBodyAlpha;
|
|
23272
23653
|
b.rect(g.bodyX, bodyTop, g.bodyW, bodyH, c);
|
|
23273
23654
|
}
|
|
23274
|
-
if (cs.borderVisible ||
|
|
23655
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
23275
23656
|
b.alpha = this.candleStructureAlpha;
|
|
23276
|
-
const bord = cs.borderVisible
|
|
23657
|
+
const bord = cs.borderVisible ? parseColor((isUp ? cs.borderUpColor : cs.borderDownColor) ?? bodyColorStr) : c;
|
|
23277
23658
|
const bw = Math.max(0, g.bodyW - 1);
|
|
23278
23659
|
const bh = Math.max(0, bodyH - 1);
|
|
23279
23660
|
b.rectStroke(g.bodyX + 0.5, bodyTop + 0.5, bw, bh, 1, bord);
|
|
@@ -24589,9 +24970,10 @@ void main() {
|
|
|
24589
24970
|
* so each indicator arrives behind the candles (and behind older indicators);
|
|
24590
24971
|
* `setIndicatorZ`/`bringToFront`/`sendToBack` change it. */
|
|
24591
24972
|
this.seriesZ = /* @__PURE__ */ new Map();
|
|
24592
|
-
/** Per-pane raster layers of
|
|
24973
|
+
/** Per-pane raster layers of drawings interleaved into the series stack (each
|
|
24974
|
+
* indicator's Pine drawings at its model's z, plus in-stack user drawings) — each a
|
|
24593
24975
|
* prepainted canvas the backend composites just before the series carrying `beforeZ`.
|
|
24594
|
-
* Rebuilt by the renderer per data frame
|
|
24976
|
+
* Rebuilt by the renderer per data frame. */
|
|
24595
24977
|
this.drawingSlices = /* @__PURE__ */ new Map();
|
|
24596
24978
|
/** Per-model index offset: the chart bar index of the model's `anchorTime` — its
|
|
24597
24979
|
* index-aligned payloads (dense series arrays, `bar_index` drawings) count from that
|
|
@@ -24825,8 +25207,12 @@ void main() {
|
|
|
24825
25207
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
24826
25208
|
};
|
|
24827
25209
|
ctx.globalAlpha = this.modelAlpha;
|
|
24828
|
-
for (const m of models) for (const bg of m.backgrounds) this.drawBackground(ctx, bg, pane, coords);
|
|
24829
|
-
|
|
25210
|
+
for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
|
|
25211
|
+
if (isPrice) {
|
|
25212
|
+
for (const m of scene.indicators.values()) {
|
|
25213
|
+
for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
|
|
25214
|
+
}
|
|
25215
|
+
}
|
|
24830
25216
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
24831
25217
|
let si = 0;
|
|
24832
25218
|
const drawSlicesUpTo = (z) => {
|
|
@@ -24848,13 +25234,25 @@ void main() {
|
|
|
24848
25234
|
ctx.globalAlpha = this.modelAlpha;
|
|
24849
25235
|
const off = scene.offsetOf(m.id);
|
|
24850
25236
|
const mp = effPane(m);
|
|
24851
|
-
for (const
|
|
25237
|
+
for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, mp, coords, i0, i1, off);
|
|
25238
|
+
for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
24852
25239
|
}
|
|
24853
25240
|
if (drawCandles && !candleDrawn) {
|
|
24854
25241
|
drawSlicesUpTo(scene.candleZ);
|
|
24855
25242
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
24856
25243
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
24857
25244
|
}
|
|
25245
|
+
if (isPrice) {
|
|
25246
|
+
ctx.globalAlpha = this.modelAlpha;
|
|
25247
|
+
for (const m of scene.indicators.values()) {
|
|
25248
|
+
const off = scene.offsetOf(m.id);
|
|
25249
|
+
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, off);
|
|
25250
|
+
}
|
|
25251
|
+
for (const m of scene.indicators.values()) {
|
|
25252
|
+
const off = scene.offsetOf(m.id);
|
|
25253
|
+
for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
|
|
25254
|
+
}
|
|
25255
|
+
}
|
|
24858
25256
|
drawSlicesUpTo(Infinity);
|
|
24859
25257
|
ctx.globalAlpha = this.modelAlpha;
|
|
24860
25258
|
for (const m of models) {
|
|
@@ -25099,13 +25497,13 @@ void main() {
|
|
|
25099
25497
|
if (drawBody) {
|
|
25100
25498
|
const oY = coords.priceToY(b.open, pane.scale, pane.bounds);
|
|
25101
25499
|
const cY = coords.priceToY(b.close, pane.scale, pane.bounds);
|
|
25102
|
-
top = Math.min(oY, cY);
|
|
25103
|
-
bodyH = Math.max(1, Math.
|
|
25500
|
+
top = snapY(Math.min(oY, cY), coords.dpr);
|
|
25501
|
+
bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - top);
|
|
25104
25502
|
}
|
|
25105
25503
|
if (cs.wickVisible) {
|
|
25106
25504
|
const wick = (up ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : color);
|
|
25107
|
-
const hY = coords.priceToY(b.high, pane.scale, pane.bounds);
|
|
25108
|
-
const lY = coords.priceToY(b.low, pane.scale, pane.bounds);
|
|
25505
|
+
const hY = snapY(coords.priceToY(b.high, pane.scale, pane.bounds), coords.dpr);
|
|
25506
|
+
const lY = snapY(coords.priceToY(b.low, pane.scale, pane.bounds), coords.dpr);
|
|
25109
25507
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
25110
25508
|
ctx.strokeStyle = wick;
|
|
25111
25509
|
ctx.lineWidth = g.wickW;
|
|
@@ -25127,9 +25525,9 @@ void main() {
|
|
|
25127
25525
|
ctx.fillStyle = color;
|
|
25128
25526
|
ctx.fillRect(g.bodyX, top, g.bodyW, bodyH);
|
|
25129
25527
|
}
|
|
25130
|
-
if (cs.borderVisible ||
|
|
25528
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
25131
25529
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
25132
|
-
ctx.strokeStyle = cs.borderVisible
|
|
25530
|
+
ctx.strokeStyle = cs.borderVisible ? (up ? cs.borderUpColor : cs.borderDownColor) ?? color : color;
|
|
25133
25531
|
ctx.lineWidth = 1;
|
|
25134
25532
|
const bw = Math.max(0, g.bodyW - 1);
|
|
25135
25533
|
const bh = Math.max(0, bodyH - 1);
|
|
@@ -25552,6 +25950,19 @@ void main() {
|
|
|
25552
25950
|
|
|
25553
25951
|
// src/renderers/shared/DrawingSceneRenderer.ts
|
|
25554
25952
|
var EMPTY_DRAWING_SET = { lines: [], boxes: [], labels: [], polylines: [], linefills: [] };
|
|
25953
|
+
function modelDrawingSet(m, overlay) {
|
|
25954
|
+
const want = (d) => Boolean(d.overlay) === overlay;
|
|
25955
|
+
return {
|
|
25956
|
+
lines: (m.lines ?? []).filter(want),
|
|
25957
|
+
boxes: (m.boxes ?? []).filter(want),
|
|
25958
|
+
labels: (m.labels ?? []).filter(want),
|
|
25959
|
+
polylines: (m.polylines ?? []).filter(want),
|
|
25960
|
+
linefills: (m.linefills ?? []).filter(want)
|
|
25961
|
+
};
|
|
25962
|
+
}
|
|
25963
|
+
function drawingSetEmpty(s) {
|
|
25964
|
+
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
25965
|
+
}
|
|
25555
25966
|
function fontSizePx(size3) {
|
|
25556
25967
|
return size3 === "auto" ? 12 : namedFontSize(size3);
|
|
25557
25968
|
}
|
|
@@ -25561,6 +25972,8 @@ void main() {
|
|
|
25561
25972
|
this.set = set;
|
|
25562
25973
|
/** measureText width cache, keyed by `${font} ${text}`, persists across frames. */
|
|
25563
25974
|
this.widthCache = /* @__PURE__ */ new Map();
|
|
25975
|
+
/** Tooltip hit-rects captured while drawing the CURRENT set (rebuilt per render). */
|
|
25976
|
+
this.tipRegions = [];
|
|
25564
25977
|
/**
|
|
25565
25978
|
* Index offset of the CURRENT set's model: its `xloc:'bar_index'` coordinates count
|
|
25566
25979
|
* from the model's anchor bar, so they shift by this to land on chart logical indices.
|
|
@@ -25581,8 +25994,13 @@ void main() {
|
|
|
25581
25994
|
const s = this.set;
|
|
25582
25995
|
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
25583
25996
|
}
|
|
25997
|
+
/** Tooltip hit-rects of the labels drawn by the LAST `render` call (same coords as `ctx`). */
|
|
25998
|
+
labelTipRegions() {
|
|
25999
|
+
return this.tipRegions;
|
|
26000
|
+
}
|
|
25584
26001
|
/** Draw the whole set into `ctx` using the supplied coordinate closures. */
|
|
25585
26002
|
render(ctx, W, H, xOf, yOf) {
|
|
26003
|
+
this.tipRegions = [];
|
|
25586
26004
|
if (this.isEmpty()) return;
|
|
25587
26005
|
ctx.save();
|
|
25588
26006
|
this.drawLinefills(ctx, W, H, xOf, yOf);
|
|
@@ -25930,13 +26348,35 @@ void main() {
|
|
|
25930
26348
|
if (this.isPointShape(lb.style)) {
|
|
25931
26349
|
if (!lb.noFill) this.drawLabelShape(ctx, lb.style, px, py, fontPx, color);
|
|
25932
26350
|
if (lb.text) this.drawLabelText(ctx, lb, px, py + fontPx, fontPx);
|
|
25933
|
-
|
|
25934
|
-
|
|
26351
|
+
if (lb.tooltip) {
|
|
26352
|
+
const r = Math.max(4, fontPx * 0.6) + 3;
|
|
26353
|
+
this.tipRegions.push({ left: px - r, top: py - r, right: px + r, bottom: py + r, text: lb.tooltip });
|
|
26354
|
+
}
|
|
26355
|
+
} else if (lb.style === "none" || lb.style === "text_outline") {
|
|
26356
|
+
if (lb.text) {
|
|
26357
|
+
this.drawLabelText(ctx, lb, px, py, fontPx, lb.style === "text_outline");
|
|
26358
|
+
if (lb.tooltip) this.tipRegions.push(this.textRegion(ctx, lb, px, py, fontPx, lb.tooltip));
|
|
26359
|
+
}
|
|
25935
26360
|
} else {
|
|
25936
|
-
this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
26361
|
+
const r = this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
26362
|
+
if (lb.tooltip) this.tipRegions.push({ left: r.x, top: r.y, right: r.x + r.w, bottom: r.y + r.h, text: lb.tooltip });
|
|
25937
26363
|
}
|
|
25938
26364
|
}
|
|
25939
26365
|
}
|
|
26366
|
+
/** Canvas font for a label's text — family + Pine `text_formatting` (bold/italic). */
|
|
26367
|
+
labelFont(lb, fontPx) {
|
|
26368
|
+
const family = lb.fontFamily === "monospace" ? "monospace" : this.deps.theme.fontFamily || "sans-serif";
|
|
26369
|
+
return `${lb.italic ? "italic " : ""}${lb.bold ? "bold " : ""}${fontPx}px ${family}`;
|
|
26370
|
+
}
|
|
26371
|
+
/** Hover rect of a text-only label (style none/text_outline/noFill), centered like drawLabelText. */
|
|
26372
|
+
textRegion(ctx, lb, cx, cy, fontPx, text) {
|
|
26373
|
+
const font = this.labelFont(lb, fontPx);
|
|
26374
|
+
const lines = (lb.text ?? "").split("\n");
|
|
26375
|
+
const w = Math.max(1, ...lines.map((l) => this.measure(ctx, font, l)));
|
|
26376
|
+
const h = fontPx * 1.25 * lines.length;
|
|
26377
|
+
const left = lb.textAlign === "left" ? cx : lb.textAlign === "right" ? cx - w : cx - w / 2;
|
|
26378
|
+
return { left, top: cy - h / 2, right: left + w, bottom: cy + h / 2, text };
|
|
26379
|
+
}
|
|
25940
26380
|
isPointShape(style) {
|
|
25941
26381
|
switch (style) {
|
|
25942
26382
|
case "circle":
|
|
@@ -25955,8 +26395,7 @@ void main() {
|
|
|
25955
26395
|
}
|
|
25956
26396
|
}
|
|
25957
26397
|
drawLabelText(ctx, lb, cx, cy, fontPx, outline = false) {
|
|
25958
|
-
|
|
25959
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
26398
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
25960
26399
|
ctx.textAlign = lb.textAlign === "left" ? "left" : lb.textAlign === "right" ? "right" : "center";
|
|
25961
26400
|
ctx.textBaseline = "middle";
|
|
25962
26401
|
const lines = lb.text.split("\n");
|
|
@@ -26036,9 +26475,9 @@ void main() {
|
|
|
26036
26475
|
ctx.fill();
|
|
26037
26476
|
}
|
|
26038
26477
|
}
|
|
26478
|
+
/** Draw a bubble label; returns the bubble body rect (for tooltip hit-testing). */
|
|
26039
26479
|
drawBubble(ctx, lb, px, py, fontPx, color) {
|
|
26040
|
-
|
|
26041
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
26480
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
26042
26481
|
const lines = (lb.text ?? "").split("\n");
|
|
26043
26482
|
const padX = 6;
|
|
26044
26483
|
const padY = 4;
|
|
@@ -26096,38 +26535,51 @@ void main() {
|
|
|
26096
26535
|
by = py - h - ptr;
|
|
26097
26536
|
pointer = "down";
|
|
26098
26537
|
}
|
|
26099
|
-
|
|
26100
|
-
|
|
26101
|
-
|
|
26102
|
-
if (pointer !== "none") {
|
|
26103
|
-
ctx.beginPath();
|
|
26104
|
-
if (pointer === "down") {
|
|
26105
|
-
ctx.moveTo(px - ptr, by + h);
|
|
26106
|
-
ctx.lineTo(px + ptr, by + h);
|
|
26107
|
-
ctx.lineTo(px, py);
|
|
26108
|
-
} else if (pointer === "up") {
|
|
26109
|
-
ctx.moveTo(px - ptr, by);
|
|
26110
|
-
ctx.lineTo(px + ptr, by);
|
|
26111
|
-
ctx.lineTo(px, py);
|
|
26112
|
-
} else if (pointer === "left") {
|
|
26113
|
-
ctx.moveTo(bx, py - ptr);
|
|
26114
|
-
ctx.lineTo(bx, py + ptr);
|
|
26115
|
-
ctx.lineTo(px, py);
|
|
26116
|
-
} else {
|
|
26117
|
-
ctx.moveTo(bx + w, py - ptr);
|
|
26118
|
-
ctx.lineTo(bx + w, py + ptr);
|
|
26119
|
-
ctx.lineTo(px, py);
|
|
26120
|
-
}
|
|
26121
|
-
ctx.closePath();
|
|
26538
|
+
if (!lb.noFill) {
|
|
26539
|
+
ctx.fillStyle = color;
|
|
26540
|
+
this.roundRect(ctx, bx, by, w, h, 4);
|
|
26122
26541
|
ctx.fill();
|
|
26542
|
+
if (pointer !== "none") {
|
|
26543
|
+
ctx.beginPath();
|
|
26544
|
+
if (pointer === "down") {
|
|
26545
|
+
ctx.moveTo(px - ptr, by + h);
|
|
26546
|
+
ctx.lineTo(px + ptr, by + h);
|
|
26547
|
+
ctx.lineTo(px, py);
|
|
26548
|
+
} else if (pointer === "up") {
|
|
26549
|
+
ctx.moveTo(px - ptr, by);
|
|
26550
|
+
ctx.lineTo(px + ptr, by);
|
|
26551
|
+
ctx.lineTo(px, py);
|
|
26552
|
+
} else if (pointer === "left") {
|
|
26553
|
+
ctx.moveTo(bx, py - ptr);
|
|
26554
|
+
ctx.lineTo(bx, py + ptr);
|
|
26555
|
+
ctx.lineTo(px, py);
|
|
26556
|
+
} else {
|
|
26557
|
+
ctx.moveTo(bx + w, py - ptr);
|
|
26558
|
+
ctx.lineTo(bx + w, py + ptr);
|
|
26559
|
+
ctx.lineTo(px, py);
|
|
26560
|
+
}
|
|
26561
|
+
ctx.closePath();
|
|
26562
|
+
ctx.fill();
|
|
26563
|
+
}
|
|
26123
26564
|
}
|
|
26124
26565
|
if (lb.text) {
|
|
26125
|
-
ctx.fillStyle = lb.textColor ?? contrastColor(color);
|
|
26126
|
-
ctx.textAlign = "center";
|
|
26566
|
+
ctx.fillStyle = lb.textColor ?? (lb.noFill ? this.deps.theme.textColor : contrastColor(color));
|
|
26127
26567
|
ctx.textBaseline = "middle";
|
|
26568
|
+
let tx;
|
|
26569
|
+
if (lb.textAlign === "left") {
|
|
26570
|
+
ctx.textAlign = "left";
|
|
26571
|
+
tx = bx + padX;
|
|
26572
|
+
} else if (lb.textAlign === "right") {
|
|
26573
|
+
ctx.textAlign = "right";
|
|
26574
|
+
tx = bx + w - padX;
|
|
26575
|
+
} else {
|
|
26576
|
+
ctx.textAlign = "center";
|
|
26577
|
+
tx = bx + w / 2;
|
|
26578
|
+
}
|
|
26128
26579
|
const startY = by + padY + lineH / 2;
|
|
26129
|
-
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i],
|
|
26580
|
+
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i], tx, startY + i * lineH);
|
|
26130
26581
|
}
|
|
26582
|
+
return { x: bx, y: by, w, h };
|
|
26131
26583
|
}
|
|
26132
26584
|
roundRect(ctx, x, y, w, h, r) {
|
|
26133
26585
|
const rr = Math.min(r, w / 2, h / 2);
|
|
@@ -26363,7 +26815,7 @@ void main() {
|
|
|
26363
26815
|
this.ctx = null;
|
|
26364
26816
|
// The color for axis tick labels — the host-passed surface text, set each frame in render().
|
|
26365
26817
|
this.axisTextColor = DARK_THEME.textColor;
|
|
26366
|
-
// Shared Pine-drawing renderer
|
|
26818
|
+
// Shared Pine-drawing renderer, used here for autoscale geometry only; widthCache persists.
|
|
26367
26819
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
26368
26820
|
}
|
|
26369
26821
|
mount(canvas) {
|
|
@@ -26388,8 +26840,8 @@ void main() {
|
|
|
26388
26840
|
*/
|
|
26389
26841
|
paneDrawingsRange(ownModels, scene, isPricePane, vr) {
|
|
26390
26842
|
let dr = null;
|
|
26391
|
-
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(
|
|
26392
|
-
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(
|
|
26843
|
+
for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, false), vr, scene.offsetOf(m.id)));
|
|
26844
|
+
if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, true), vr, scene.offsetOf(m.id)));
|
|
26393
26845
|
return dr;
|
|
26394
26846
|
}
|
|
26395
26847
|
/** Clear the chrome canvas and draw drawings + axes + current-price line.
|
|
@@ -26421,17 +26873,6 @@ void main() {
|
|
|
26421
26873
|
return;
|
|
26422
26874
|
}
|
|
26423
26875
|
const pricePane = panes.find((p) => p.kind === "price") ?? null;
|
|
26424
|
-
for (const pane of panes) {
|
|
26425
|
-
if (pane.collapsed) continue;
|
|
26426
|
-
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
26427
|
-
const sc = scene.scaleFor(m, pane);
|
|
26428
|
-
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
26429
|
-
this.renderDrawings(ctx, coords, this.ownDrawings(m), mp, dataW, scene.offsetOf(m.id));
|
|
26430
|
-
}
|
|
26431
|
-
}
|
|
26432
|
-
if (pricePane) {
|
|
26433
|
-
for (const m of scene.indicators.values()) this.renderDrawings(ctx, coords, this.overlayDrawings(m), pricePane, dataW, scene.offsetOf(m.id));
|
|
26434
|
-
}
|
|
26435
26876
|
if (pricePane && !pricePane.collapsed && scene.tradeMarkers.visible) {
|
|
26436
26877
|
for (const m of scene.indicators.values()) {
|
|
26437
26878
|
if (m.trades?.length) this.renderTrades(ctx, coords, scene, theme, m.trades, pricePane, dataW);
|
|
@@ -26447,25 +26888,6 @@ void main() {
|
|
|
26447
26888
|
this.canvas = null;
|
|
26448
26889
|
this.ctx = null;
|
|
26449
26890
|
}
|
|
26450
|
-
// ── Pine-drawing helpers (own vs force_overlay routing) ──
|
|
26451
|
-
ownDrawings(m) {
|
|
26452
|
-
return {
|
|
26453
|
-
lines: (m.lines ?? []).filter((d) => !d.overlay),
|
|
26454
|
-
boxes: (m.boxes ?? []).filter((d) => !d.overlay),
|
|
26455
|
-
labels: (m.labels ?? []).filter((d) => !d.overlay),
|
|
26456
|
-
polylines: (m.polylines ?? []).filter((d) => !d.overlay),
|
|
26457
|
-
linefills: (m.linefills ?? []).filter((d) => !d.overlay)
|
|
26458
|
-
};
|
|
26459
|
-
}
|
|
26460
|
-
overlayDrawings(m) {
|
|
26461
|
-
return {
|
|
26462
|
-
lines: (m.lines ?? []).filter((d) => d.overlay),
|
|
26463
|
-
boxes: (m.boxes ?? []).filter((d) => d.overlay),
|
|
26464
|
-
labels: (m.labels ?? []).filter((d) => d.overlay),
|
|
26465
|
-
polylines: (m.polylines ?? []).filter((d) => d.overlay),
|
|
26466
|
-
linefills: (m.linefills ?? []).filter((d) => d.overlay)
|
|
26467
|
-
};
|
|
26468
|
-
}
|
|
26469
26891
|
drawingsRange(set, vr, indexOffset = 0) {
|
|
26470
26892
|
this.drawScene.setSet(set, indexOffset);
|
|
26471
26893
|
if (this.drawScene.isEmpty()) return null;
|
|
@@ -26499,23 +26921,6 @@ void main() {
|
|
|
26499
26921
|
);
|
|
26500
26922
|
ctx.restore();
|
|
26501
26923
|
}
|
|
26502
|
-
renderDrawings(ctx, coords, set, pane, dataW, indexOffset = 0) {
|
|
26503
|
-
this.drawScene.setSet(set, indexOffset);
|
|
26504
|
-
if (this.drawScene.isEmpty()) return;
|
|
26505
|
-
ctx.save();
|
|
26506
|
-
ctx.translate(0, pane.bounds.top);
|
|
26507
|
-
ctx.beginPath();
|
|
26508
|
-
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
26509
|
-
ctx.clip();
|
|
26510
|
-
this.drawScene.render(
|
|
26511
|
-
ctx,
|
|
26512
|
-
dataW,
|
|
26513
|
-
pane.bounds.height,
|
|
26514
|
-
(l) => coords.logicalToX(l),
|
|
26515
|
-
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
26516
|
-
);
|
|
26517
|
-
ctx.restore();
|
|
26518
|
-
}
|
|
26519
26924
|
// ── axes ──
|
|
26520
26925
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
26521
26926
|
ctx.strokeStyle = scene.style.borderColor ?? theme.borderColor;
|
|
@@ -26731,6 +27136,68 @@ void main() {
|
|
|
26731
27136
|
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
|
|
26732
27137
|
}
|
|
26733
27138
|
|
|
27139
|
+
// src/renderers/native/chrome/LabelTooltip.ts
|
|
27140
|
+
var HOVER_DELAY_MS = 350;
|
|
27141
|
+
var LabelTooltip = class {
|
|
27142
|
+
constructor(plot, deps) {
|
|
27143
|
+
this.plot = plot;
|
|
27144
|
+
this.deps = deps;
|
|
27145
|
+
this.tip = null;
|
|
27146
|
+
this.timer = null;
|
|
27147
|
+
/** Text of the currently open OR armed tip — dedupes moves inside one label. */
|
|
27148
|
+
this.current = null;
|
|
27149
|
+
this.onMove = (e) => {
|
|
27150
|
+
if (e.pointerType !== "mouse") return;
|
|
27151
|
+
const rect = this.plot.getBoundingClientRect();
|
|
27152
|
+
const x = e.clientX - rect.left;
|
|
27153
|
+
const y = e.clientY - rect.top;
|
|
27154
|
+
const text = this.deps.lookup(x, y);
|
|
27155
|
+
if (!text) {
|
|
27156
|
+
this.clear();
|
|
27157
|
+
return;
|
|
27158
|
+
}
|
|
27159
|
+
if (text === this.current) return;
|
|
27160
|
+
this.clear();
|
|
27161
|
+
this.current = text;
|
|
27162
|
+
this.timer = window.setTimeout(() => this.show(text, x, y), HOVER_DELAY_MS);
|
|
27163
|
+
};
|
|
27164
|
+
this.onLeave = () => {
|
|
27165
|
+
this.clear();
|
|
27166
|
+
};
|
|
27167
|
+
plot.addEventListener("pointermove", this.onMove);
|
|
27168
|
+
plot.addEventListener("pointerleave", this.onLeave);
|
|
27169
|
+
plot.addEventListener("pointerdown", this.onLeave);
|
|
27170
|
+
}
|
|
27171
|
+
destroy() {
|
|
27172
|
+
this.plot.removeEventListener("pointermove", this.onMove);
|
|
27173
|
+
this.plot.removeEventListener("pointerleave", this.onLeave);
|
|
27174
|
+
this.plot.removeEventListener("pointerdown", this.onLeave);
|
|
27175
|
+
this.clear();
|
|
27176
|
+
}
|
|
27177
|
+
show(text, x, y) {
|
|
27178
|
+
const doc = this.plot.ownerDocument;
|
|
27179
|
+
const tip = doc.createElement("div");
|
|
27180
|
+
tip.textContent = text;
|
|
27181
|
+
tip.style.cssText = "position:absolute;z-index:var(--vela-z-tooltip);pointer-events:none;background:var(--vela-bg);border:1px solid var(--vela-border);color:var(--vela-fg);border-radius:var(--vela-radius-md);padding:4px 9px;box-shadow:var(--vela-shadow);font:var(--vela-font-size-md) var(--vela-font);max-width:260px;";
|
|
27182
|
+
applyChromeTokens(tip, this.deps.theme());
|
|
27183
|
+
this.plot.appendChild(tip);
|
|
27184
|
+
const pw = this.plot.clientWidth;
|
|
27185
|
+
const ph = this.plot.clientHeight;
|
|
27186
|
+
tip.style.left = `${Math.max(0, Math.min(x + 12, pw - tip.offsetWidth - 4))}px`;
|
|
27187
|
+
tip.style.top = `${Math.max(0, Math.min(y + 16, ph - tip.offsetHeight - 4))}px`;
|
|
27188
|
+
this.tip = tip;
|
|
27189
|
+
}
|
|
27190
|
+
clear() {
|
|
27191
|
+
if (this.timer !== null) {
|
|
27192
|
+
clearTimeout(this.timer);
|
|
27193
|
+
this.timer = null;
|
|
27194
|
+
}
|
|
27195
|
+
this.tip?.remove();
|
|
27196
|
+
this.tip = null;
|
|
27197
|
+
this.current = null;
|
|
27198
|
+
}
|
|
27199
|
+
};
|
|
27200
|
+
|
|
26734
27201
|
// src/renderers/native/chrome/CrosshairRenderer.ts
|
|
26735
27202
|
var CrosshairRenderer = class {
|
|
26736
27203
|
constructor() {
|
|
@@ -30862,17 +31329,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
30862
31329
|
return textGlyph(String(glyph), 15);
|
|
30863
31330
|
}
|
|
30864
31331
|
function stampSizeIcon(size3) {
|
|
30865
|
-
return textGlyph("\u25CF", (
|
|
31332
|
+
return textGlyph("\u25CF", (SIZE_PX3[String(size3)] ?? 13) + 4);
|
|
30866
31333
|
}
|
|
30867
31334
|
function sizeIcon(size3) {
|
|
30868
31335
|
return textGlyph(String(size3).charAt(0).toUpperCase(), 15);
|
|
30869
31336
|
}
|
|
30870
|
-
var
|
|
31337
|
+
var SIZE_PX3 = { small: 10, normal: 13, large: 16, huge: 20 };
|
|
30871
31338
|
function numbersSizeIcon(size3) {
|
|
30872
|
-
return textGlyph("12", (
|
|
31339
|
+
return textGlyph("12", (SIZE_PX3[String(size3)] ?? 13) - 1, 16.5, 'font-weight="600"');
|
|
30873
31340
|
}
|
|
30874
31341
|
function labelSizeIcon(size3) {
|
|
30875
|
-
return textGlyph("T", (
|
|
31342
|
+
return textGlyph("T", (SIZE_PX3[String(size3)] ?? 13) + 2);
|
|
30876
31343
|
}
|
|
30877
31344
|
function capitalize(s) {
|
|
30878
31345
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
@@ -31036,7 +31503,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
31036
31503
|
this.root.replaceChildren();
|
|
31037
31504
|
this.groupCells.clear();
|
|
31038
31505
|
this.groupIcons.clear();
|
|
31039
|
-
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.
|
|
31506
|
+
this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.onCursorClick());
|
|
31040
31507
|
this.root.appendChild(this.cursorBtn);
|
|
31041
31508
|
if (this.def.groups.length > 0) this.root.appendChild(this.divider());
|
|
31042
31509
|
for (const g of this.def.groups) {
|
|
@@ -31140,6 +31607,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
31140
31607
|
this.magnetIcon = icon2;
|
|
31141
31608
|
return cell;
|
|
31142
31609
|
}
|
|
31610
|
+
/** Cursor returns to select/idle: an active measure/eraser mode exits through its own
|
|
31611
|
+
* toggle callback (disarming a tool via `onArm(null)` alone can't — the host treats a
|
|
31612
|
+
* null arm as a no-op side effect of entering those modes), then the tool disarms. */
|
|
31613
|
+
onCursorClick() {
|
|
31614
|
+
if (this.measureActive) this.onMeasure();
|
|
31615
|
+
if (this.eraserActive) this.onEraser();
|
|
31616
|
+
this.onArm(null);
|
|
31617
|
+
}
|
|
31143
31618
|
/** Clicking the icon arms the group's last-used tool (it does NOT open the flyout). */
|
|
31144
31619
|
onGroupIconClick(group) {
|
|
31145
31620
|
const type = this.lastUsed.get(group.id) ?? group.tools[0]?.type;
|
|
@@ -32471,6 +32946,315 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32471
32946
|
}
|
|
32472
32947
|
};
|
|
32473
32948
|
|
|
32949
|
+
// src/renderers/shared/TableOverlay.ts
|
|
32950
|
+
var SIZE_PX4 = {
|
|
32951
|
+
auto: 13,
|
|
32952
|
+
tiny: 10,
|
|
32953
|
+
small: 11,
|
|
32954
|
+
normal: 13,
|
|
32955
|
+
large: 16,
|
|
32956
|
+
huge: 20
|
|
32957
|
+
};
|
|
32958
|
+
function fontPxOf(size3) {
|
|
32959
|
+
if (typeof size3 === "number") return size3 > 0 ? size3 : SIZE_PX4.auto;
|
|
32960
|
+
return SIZE_PX4[size3] ?? SIZE_PX4.auto;
|
|
32961
|
+
}
|
|
32962
|
+
function tableHasContent(t) {
|
|
32963
|
+
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
32964
|
+
}
|
|
32965
|
+
function mergeRenderPlan(t) {
|
|
32966
|
+
const span = /* @__PURE__ */ new Map();
|
|
32967
|
+
const omit = /* @__PURE__ */ new Set();
|
|
32968
|
+
for (const m of t.merges) {
|
|
32969
|
+
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
32970
|
+
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
32971
|
+
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
32972
|
+
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
32973
|
+
}
|
|
32974
|
+
}
|
|
32975
|
+
}
|
|
32976
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
32977
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
32978
|
+
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
32979
|
+
}
|
|
32980
|
+
}
|
|
32981
|
+
for (const key of span.keys()) omit.delete(key);
|
|
32982
|
+
return { span, omit };
|
|
32983
|
+
}
|
|
32984
|
+
|
|
32985
|
+
// src/renderers/shared/TableCanvasRenderer.ts
|
|
32986
|
+
var PAD_X = 6;
|
|
32987
|
+
var PAD_Y = 2;
|
|
32988
|
+
var MARGIN = 6;
|
|
32989
|
+
var LINE_HEIGHT = 1.2;
|
|
32990
|
+
function paintTable(ctx, t, args, tips) {
|
|
32991
|
+
if (!tableHasContent(t)) return;
|
|
32992
|
+
const layout = layoutTable(ctx, t, args);
|
|
32993
|
+
if (!layout || layout.w <= 0 || layout.h <= 0) return;
|
|
32994
|
+
const fw = t.frameColor && t.frameWidth > 0 ? t.frameWidth : 0;
|
|
32995
|
+
const { x, y } = anchorOrigin(t.position, layout.w + 2 * fw, layout.h + 2 * fw, args);
|
|
32996
|
+
const x0 = x + fw;
|
|
32997
|
+
const y0 = y + fw;
|
|
32998
|
+
if (t.bgColor) {
|
|
32999
|
+
ctx.fillStyle = t.bgColor;
|
|
33000
|
+
ctx.fillRect(x0, y0, layout.w, layout.h);
|
|
33001
|
+
}
|
|
33002
|
+
if (fw > 0 && t.frameColor) {
|
|
33003
|
+
ctx.strokeStyle = t.frameColor;
|
|
33004
|
+
ctx.lineWidth = fw;
|
|
33005
|
+
ctx.strokeRect(x + fw / 2, y + fw / 2, layout.w + fw, layout.h + fw);
|
|
33006
|
+
}
|
|
33007
|
+
const colX = [0];
|
|
33008
|
+
for (const w of layout.colW) colX.push(colX[colX.length - 1] + w);
|
|
33009
|
+
const rowY = [0];
|
|
33010
|
+
for (const h of layout.rowH) rowY.push(rowY[rowY.length - 1] + h);
|
|
33011
|
+
const prevBaseline = ctx.textBaseline;
|
|
33012
|
+
const prevAlign = ctx.textAlign;
|
|
33013
|
+
ctx.textBaseline = "middle";
|
|
33014
|
+
for (const box of layout.boxes) {
|
|
33015
|
+
const rx = x0 + colX[box.c];
|
|
33016
|
+
const ry = y0 + rowY[box.r];
|
|
33017
|
+
const rw = colX[box.c + box.cs] - colX[box.c];
|
|
33018
|
+
const rh = rowY[box.r + box.rs] - rowY[box.r];
|
|
33019
|
+
const cell = box.cell;
|
|
33020
|
+
if (cell.bgColor) {
|
|
33021
|
+
ctx.fillStyle = cell.bgColor;
|
|
33022
|
+
ctx.fillRect(rx, ry, rw, rh);
|
|
33023
|
+
}
|
|
33024
|
+
const text = cell.text ?? "";
|
|
33025
|
+
if (text.length > 0) {
|
|
33026
|
+
const px = fontPxOf(cell.textSize);
|
|
33027
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
33028
|
+
ctx.fillStyle = cell.textColor ?? args.theme.textColor;
|
|
33029
|
+
const lines = text.split("\n");
|
|
33030
|
+
const blockH = lines.length * px * LINE_HEIGHT;
|
|
33031
|
+
const blockTop = cell.vAlign === "top" ? ry + PAD_Y : cell.vAlign === "bottom" ? ry + rh - PAD_Y - blockH : ry + (rh - blockH) / 2;
|
|
33032
|
+
const tx = cell.hAlign === "left" ? rx + PAD_X : cell.hAlign === "right" ? rx + rw - PAD_X : rx + rw / 2;
|
|
33033
|
+
ctx.textAlign = cell.hAlign;
|
|
33034
|
+
lines.forEach((line, i) => ctx.fillText(line, tx, blockTop + (i + 0.5) * px * LINE_HEIGHT));
|
|
33035
|
+
}
|
|
33036
|
+
if (cell.tooltip) tips.push({ left: rx, top: ry, right: rx + rw, bottom: ry + rh, text: cell.tooltip });
|
|
33037
|
+
}
|
|
33038
|
+
ctx.textBaseline = prevBaseline;
|
|
33039
|
+
ctx.textAlign = prevAlign;
|
|
33040
|
+
if (t.borderColor && t.borderWidth > 0) {
|
|
33041
|
+
ctx.strokeStyle = t.borderColor;
|
|
33042
|
+
ctx.lineWidth = t.borderWidth;
|
|
33043
|
+
const seen = /* @__PURE__ */ new Set();
|
|
33044
|
+
ctx.beginPath();
|
|
33045
|
+
const edge = (ax, ay, bx, by) => {
|
|
33046
|
+
const key = `${ax},${ay},${bx},${by}`;
|
|
33047
|
+
if (seen.has(key)) return;
|
|
33048
|
+
seen.add(key);
|
|
33049
|
+
ctx.moveTo(ax, ay);
|
|
33050
|
+
ctx.lineTo(bx, by);
|
|
33051
|
+
};
|
|
33052
|
+
for (const box of layout.boxes) {
|
|
33053
|
+
const l = Math.round(x0 + colX[box.c]);
|
|
33054
|
+
const r = Math.round(x0 + colX[box.c + box.cs]);
|
|
33055
|
+
const tp = Math.round(y0 + rowY[box.r]);
|
|
33056
|
+
const bt = Math.round(y0 + rowY[box.r + box.rs]);
|
|
33057
|
+
edge(l, tp, r, tp);
|
|
33058
|
+
edge(l, bt, r, bt);
|
|
33059
|
+
edge(l, tp, l, bt);
|
|
33060
|
+
edge(r, tp, r, bt);
|
|
33061
|
+
}
|
|
33062
|
+
ctx.stroke();
|
|
33063
|
+
}
|
|
33064
|
+
}
|
|
33065
|
+
function layoutTable(ctx, t, args) {
|
|
33066
|
+
const { span, omit } = mergeRenderPlan(t);
|
|
33067
|
+
const colW = new Array(t.columns).fill(0);
|
|
33068
|
+
const rowH = new Array(t.rows).fill(0);
|
|
33069
|
+
const boxes = [];
|
|
33070
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
33071
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
33072
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
33073
|
+
const cell = t.cells[r]?.[c];
|
|
33074
|
+
if (cell == null) continue;
|
|
33075
|
+
const sp = span.get(`${r}:${c}`);
|
|
33076
|
+
boxes.push({ cell, r, c, cs: Math.min(sp?.cs ?? 1, t.columns - c), rs: Math.min(sp?.rs ?? 1, t.rows - r) });
|
|
33077
|
+
}
|
|
33078
|
+
}
|
|
33079
|
+
if (boxes.length === 0) return null;
|
|
33080
|
+
const sizeOf = (cell) => {
|
|
33081
|
+
const px = fontPxOf(cell.textSize);
|
|
33082
|
+
ctx.font = cellFont(cell, px, args.theme);
|
|
33083
|
+
const lines = (cell.text ?? "").split("\n");
|
|
33084
|
+
let maxW = 0;
|
|
33085
|
+
for (const line of lines) maxW = Math.max(maxW, ctx.measureText(line).width);
|
|
33086
|
+
let w2 = Math.ceil(maxW) + 2 * PAD_X;
|
|
33087
|
+
let h2 = Math.ceil(lines.length * px * LINE_HEIGHT) + 2 * PAD_Y;
|
|
33088
|
+
if (cell.width) w2 = Math.max(w2, cell.width / 100 * args.plotWidth);
|
|
33089
|
+
if (cell.height) h2 = Math.max(h2, cell.height / 100 * args.paneHeight);
|
|
33090
|
+
return { w: w2, h: h2 };
|
|
33091
|
+
};
|
|
33092
|
+
const spanning = [];
|
|
33093
|
+
for (const box of boxes) {
|
|
33094
|
+
const { w: w2, h: h2 } = sizeOf(box.cell);
|
|
33095
|
+
if (box.cs === 1) colW[box.c] = Math.max(colW[box.c], w2);
|
|
33096
|
+
if (box.rs === 1) rowH[box.r] = Math.max(rowH[box.r], h2);
|
|
33097
|
+
if (box.cs > 1 || box.rs > 1) spanning.push({ box, w: w2, h: h2 });
|
|
33098
|
+
}
|
|
33099
|
+
for (const { box, w: w2, h: h2 } of spanning) {
|
|
33100
|
+
if (box.cs > 1) {
|
|
33101
|
+
let sum = 0;
|
|
33102
|
+
for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
|
|
33103
|
+
if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
|
|
33104
|
+
}
|
|
33105
|
+
if (box.rs > 1) {
|
|
33106
|
+
let sum = 0;
|
|
33107
|
+
for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
|
|
33108
|
+
if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
|
|
33109
|
+
}
|
|
33110
|
+
}
|
|
33111
|
+
let w = 0;
|
|
33112
|
+
for (const cw of colW) w += cw;
|
|
33113
|
+
let h = 0;
|
|
33114
|
+
for (const rh of rowH) h += rh;
|
|
33115
|
+
return { colW, rowH, w, h, boxes };
|
|
33116
|
+
}
|
|
33117
|
+
function anchorOrigin(position, totalW, totalH, args) {
|
|
33118
|
+
let y;
|
|
33119
|
+
if (position.startsWith("top")) y = MARGIN;
|
|
33120
|
+
else if (position.startsWith("bottom")) y = args.paneHeight - MARGIN - totalH;
|
|
33121
|
+
else y = args.paneHeight / 2 - totalH / 2;
|
|
33122
|
+
let x;
|
|
33123
|
+
if (position.endsWith("left")) x = MARGIN;
|
|
33124
|
+
else if (position.endsWith("right")) x = args.plotWidth - MARGIN - totalW;
|
|
33125
|
+
else x = args.plotWidth / 2 - totalW / 2;
|
|
33126
|
+
return { x, y };
|
|
33127
|
+
}
|
|
33128
|
+
function cellFont(cell, px, theme) {
|
|
33129
|
+
const family = cell.fontFamily === "monospace" ? "monospace" : theme.fontFamily || "sans-serif";
|
|
33130
|
+
return `${cell.italic ? "italic " : ""}${cell.bold ? "bold " : ""}${px}px ${family}`;
|
|
33131
|
+
}
|
|
33132
|
+
|
|
33133
|
+
// src/renderers/native/drawings/IndicatorDrawingSlices.ts
|
|
33134
|
+
function indicatorSliceKey(z, boundaries) {
|
|
33135
|
+
return boundaries.find((b) => b > z) ?? Infinity;
|
|
33136
|
+
}
|
|
33137
|
+
var IndicatorDrawingSlices = class {
|
|
33138
|
+
constructor() {
|
|
33139
|
+
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
33140
|
+
/** Slice canvas cache, keyed `paneId|beforeZ` — same lifecycle as the user-drawing cache. */
|
|
33141
|
+
this.sliceCache = /* @__PURE__ */ new Map();
|
|
33142
|
+
/** Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per prepare). */
|
|
33143
|
+
this.tips = [];
|
|
33144
|
+
}
|
|
33145
|
+
/**
|
|
33146
|
+
* Rebuild the per-indicator drawing slices for this data frame. `ref` is the data
|
|
33147
|
+
* canvas the slices must match pixel-for-pixel (the backend composites them 1:1).
|
|
33148
|
+
* Runs from the renderer's data paint, just before the backend composites the scene.
|
|
33149
|
+
*/
|
|
33150
|
+
prepare(scene, coords, theme, ref2) {
|
|
33151
|
+
this.tips = [];
|
|
33152
|
+
const out = /* @__PURE__ */ new Map();
|
|
33153
|
+
if (ref2.width === 0 || ref2.height === 0) {
|
|
33154
|
+
this.sliceCache.clear();
|
|
33155
|
+
return out;
|
|
33156
|
+
}
|
|
33157
|
+
this.drawScene.setDeps({
|
|
33158
|
+
timeToLogical: (ms) => coords.timeToLogical(ms),
|
|
33159
|
+
barAt: (logical) => {
|
|
33160
|
+
const b = scene.bars[Math.round(logical)];
|
|
33161
|
+
return b ? { high: b.high, low: b.low } : null;
|
|
33162
|
+
},
|
|
33163
|
+
theme
|
|
33164
|
+
});
|
|
33165
|
+
const dpr = coords.dpr;
|
|
33166
|
+
const dataW = coords.width;
|
|
33167
|
+
const buckets = /* @__PURE__ */ new Map();
|
|
33168
|
+
const add = (paneId, beforeZ, entry) => {
|
|
33169
|
+
const key = `${paneId}|${beforeZ}`;
|
|
33170
|
+
const bucket = buckets.get(key);
|
|
33171
|
+
if (bucket) bucket.entries.push(entry);
|
|
33172
|
+
else buckets.set(key, { paneId, beforeZ, entries: [entry] });
|
|
33173
|
+
};
|
|
33174
|
+
for (const pane of scene.orderedPanes()) {
|
|
33175
|
+
if (pane.collapsed) continue;
|
|
33176
|
+
const boundaries = scene.seriesBoundaries(pane.id);
|
|
33177
|
+
for (const m of scene.orderedIndicatorsForPane(pane.id)) {
|
|
33178
|
+
const set = modelDrawingSet(m, false);
|
|
33179
|
+
const tables = (m.tables ?? []).filter((t) => !t.overlay);
|
|
33180
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
33181
|
+
const sc = scene.scaleFor(m, pane);
|
|
33182
|
+
const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
33183
|
+
const beforeZ = indicatorSliceKey(scene.zOf(m.id), boundaries);
|
|
33184
|
+
add(pane.id, beforeZ, { set, tables, pane: mp, indexOffset: scene.offsetOf(m.id) });
|
|
33185
|
+
}
|
|
33186
|
+
if (pane.kind === "price") {
|
|
33187
|
+
for (const m of scene.indicators.values()) {
|
|
33188
|
+
const set = modelDrawingSet(m, true);
|
|
33189
|
+
const tables = (m.tables ?? []).filter((t) => t.overlay === true);
|
|
33190
|
+
if (drawingSetEmpty(set) && tables.length === 0) continue;
|
|
33191
|
+
add(pane.id, Infinity, { set, tables, pane, indexOffset: scene.offsetOf(m.id) });
|
|
33192
|
+
}
|
|
33193
|
+
}
|
|
33194
|
+
}
|
|
33195
|
+
for (const [key, { paneId, beforeZ, entries }] of buckets) {
|
|
33196
|
+
let canvas = this.sliceCache.get(key);
|
|
33197
|
+
if (!canvas) {
|
|
33198
|
+
canvas = document.createElement("canvas");
|
|
33199
|
+
this.sliceCache.set(key, canvas);
|
|
33200
|
+
}
|
|
33201
|
+
if (canvas.width !== ref2.width || canvas.height !== ref2.height) {
|
|
33202
|
+
canvas.width = ref2.width;
|
|
33203
|
+
canvas.height = ref2.height;
|
|
33204
|
+
}
|
|
33205
|
+
const ctx = canvas.getContext("2d");
|
|
33206
|
+
if (!ctx) continue;
|
|
33207
|
+
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
33208
|
+
ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
|
|
33209
|
+
for (const e of entries) this.paintEntry(ctx, e, coords, dataW, theme);
|
|
33210
|
+
const slices = out.get(paneId) ?? [];
|
|
33211
|
+
slices.push({ beforeZ, canvas });
|
|
33212
|
+
out.set(paneId, slices);
|
|
33213
|
+
}
|
|
33214
|
+
for (const key of [...this.sliceCache.keys()]) if (!buckets.has(key)) this.sliceCache.delete(key);
|
|
33215
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
33216
|
+
return out;
|
|
33217
|
+
}
|
|
33218
|
+
paintEntry(ctx, e, coords, dataW, theme) {
|
|
33219
|
+
const { pane } = e;
|
|
33220
|
+
const paneTips = [];
|
|
33221
|
+
ctx.save();
|
|
33222
|
+
ctx.translate(0, pane.bounds.top);
|
|
33223
|
+
ctx.beginPath();
|
|
33224
|
+
ctx.rect(0, 0, dataW, pane.bounds.height);
|
|
33225
|
+
ctx.clip();
|
|
33226
|
+
this.drawScene.setSet(e.set, e.indexOffset);
|
|
33227
|
+
this.drawScene.render(
|
|
33228
|
+
ctx,
|
|
33229
|
+
dataW,
|
|
33230
|
+
pane.bounds.height,
|
|
33231
|
+
(l) => coords.logicalToX(l),
|
|
33232
|
+
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
33233
|
+
);
|
|
33234
|
+
paneTips.push(...this.drawScene.labelTipRegions());
|
|
33235
|
+
for (const t of e.tables) paintTable(ctx, t, { paneHeight: pane.bounds.height, plotWidth: dataW, theme }, paneTips);
|
|
33236
|
+
ctx.restore();
|
|
33237
|
+
for (const r of paneTips) {
|
|
33238
|
+
this.tips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
33239
|
+
}
|
|
33240
|
+
}
|
|
33241
|
+
/** Tooltip of the topmost label or table cell under a plot-space point, or null. Fed by the last prepare. */
|
|
33242
|
+
labelTooltipAt(x, y) {
|
|
33243
|
+
for (let i = this.tips.length - 1; i >= 0; i -= 1) {
|
|
33244
|
+
const r = this.tips[i];
|
|
33245
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
33246
|
+
}
|
|
33247
|
+
return null;
|
|
33248
|
+
}
|
|
33249
|
+
};
|
|
33250
|
+
function mergeSlices(indicator, user) {
|
|
33251
|
+
const out = /* @__PURE__ */ new Map();
|
|
33252
|
+
for (const [paneId, slices] of indicator) out.set(paneId, [...slices]);
|
|
33253
|
+
for (const [paneId, slices] of user) out.set(paneId, [...out.get(paneId) ?? [], ...slices]);
|
|
33254
|
+
for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
|
|
33255
|
+
return out;
|
|
33256
|
+
}
|
|
33257
|
+
|
|
32474
33258
|
// src/renderers/native/drawings/Projector.ts
|
|
32475
33259
|
function createProjector(coords, paneOf, paneIdAtY, barsInRange) {
|
|
32476
33260
|
return {
|
|
@@ -32522,19 +33306,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32522
33306
|
for (const model of models) {
|
|
32523
33307
|
const off = offsetOf(model.id);
|
|
32524
33308
|
for (const s of model.series) {
|
|
32525
|
-
if (s.
|
|
32526
|
-
|
|
32527
|
-
const b = s.bars[i - off];
|
|
32528
|
-
if (b) {
|
|
32529
|
-
consider(b.high);
|
|
32530
|
-
consider(b.low);
|
|
32531
|
-
}
|
|
32532
|
-
}
|
|
32533
|
-
} else if (isLineLikeSeries(s)) {
|
|
32534
|
-
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
32535
|
-
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
32536
|
-
else if (s.style?.base != null) consider(s.style.base);
|
|
32537
|
-
}
|
|
33309
|
+
if (s.overlay === true) continue;
|
|
33310
|
+
considerSeries(s, i0, i1, off, consider);
|
|
32538
33311
|
}
|
|
32539
33312
|
for (const pl of model.priceLines) consider(pl.price);
|
|
32540
33313
|
}
|
|
@@ -32556,6 +33329,36 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32556
33329
|
const span = max2 - min2;
|
|
32557
33330
|
return { min: min2 - span * MARGIN_BOTTOM, max: max2 + span * MARGIN_TOP };
|
|
32558
33331
|
}
|
|
33332
|
+
function considerSeries(s, i0, i1, off, consider) {
|
|
33333
|
+
if (s.kind === "candle" || s.kind === "bar") {
|
|
33334
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
33335
|
+
const b = s.bars[i - off];
|
|
33336
|
+
if (b) {
|
|
33337
|
+
consider(b.high);
|
|
33338
|
+
consider(b.low);
|
|
33339
|
+
}
|
|
33340
|
+
}
|
|
33341
|
+
} else if (isLineLikeSeries(s)) {
|
|
33342
|
+
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
33343
|
+
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
33344
|
+
else if (s.style?.base != null) consider(s.style.base);
|
|
33345
|
+
}
|
|
33346
|
+
}
|
|
33347
|
+
function overlaySeriesRange(models, i0, i1, offsetOf = () => 0) {
|
|
33348
|
+
let min2 = Infinity;
|
|
33349
|
+
let max2 = -Infinity;
|
|
33350
|
+
const consider = (v) => {
|
|
33351
|
+
if (v != null && Number.isFinite(v)) {
|
|
33352
|
+
if (v < min2) min2 = v;
|
|
33353
|
+
if (v > max2) max2 = v;
|
|
33354
|
+
}
|
|
33355
|
+
};
|
|
33356
|
+
for (const model of models) {
|
|
33357
|
+
const off = offsetOf(model.id);
|
|
33358
|
+
for (const s of model.series) if (s.overlay === true) considerSeries(s, i0, i1, off, consider);
|
|
33359
|
+
}
|
|
33360
|
+
return min2 === Infinity ? null : { min: min2, max: max2 };
|
|
33361
|
+
}
|
|
32559
33362
|
function expandScaleByPixels(scale, heightPx, abovePx, belowPx) {
|
|
32560
33363
|
if (abovePx <= 0 && belowPx <= 0) return scale;
|
|
32561
33364
|
const content = heightPx - abovePx - belowPx;
|
|
@@ -33331,6 +34134,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33331
34134
|
this.vpvrRenderer = new VpvrRenderer();
|
|
33332
34135
|
this.resizeObserver = null;
|
|
33333
34136
|
this.dprMedia = null;
|
|
34137
|
+
/** Plot size in INTEGER device px, as last reported by the resize observer's
|
|
34138
|
+
* device-pixel-content-box — the browser's own statement of how many device pixels
|
|
34139
|
+
* it paints the plot into. `null` until the first report or where the box type is
|
|
34140
|
+
* unsupported (WebKit); syncSize then falls back to rounding the client rect. */
|
|
34141
|
+
this.plotDeviceSize = null;
|
|
33334
34142
|
this.coords = new CoordinateSystem();
|
|
33335
34143
|
this.scene = new SceneGraph();
|
|
33336
34144
|
// chosen at mount (WebGL2 if available, else canvas2d)
|
|
@@ -33338,6 +34146,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33338
34146
|
this.glowAmount = 0;
|
|
33339
34147
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
33340
34148
|
this.chrome = new ChromeRenderer();
|
|
34149
|
+
/** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
|
|
34150
|
+
this.indicatorSlices = new IndicatorDrawingSlices();
|
|
34151
|
+
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
34152
|
+
this.labelTooltip = null;
|
|
33341
34153
|
this.crosshairLayer = new CrosshairRenderer();
|
|
33342
34154
|
/** 1 Hz repaint pump so the price-axis countdown-to-bar-close ticks; null when off. */
|
|
33343
34155
|
this.countdownTimer = null;
|
|
@@ -33348,6 +34160,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33348
34160
|
this.indicatorValuesOn = true;
|
|
33349
34161
|
/** Host-contributed legend actions — held here so a rebuild of the legend re-wires them. */
|
|
33350
34162
|
this.legendActionsProvider = null;
|
|
34163
|
+
/** Host-contributed legend callouts — held here so a rebuild of the legend re-wires them. */
|
|
34164
|
+
this.legendCalloutsProvider = null;
|
|
33351
34165
|
/** Host override of the legend's fold toggle — held here so a remount re-applies it. */
|
|
33352
34166
|
this.legendOverviewAction = null;
|
|
33353
34167
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -33482,7 +34296,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33482
34296
|
this.toggleVisibleCbs = /* @__PURE__ */ new Set();
|
|
33483
34297
|
this.moveIndicatorCbs = /* @__PURE__ */ new Set();
|
|
33484
34298
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
33485
|
-
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
33486
34299
|
this.name = "native";
|
|
33487
34300
|
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
33488
34301
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
@@ -33796,7 +34609,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33796
34609
|
* hidden) on clear so a re-show picks up the current theme.
|
|
33797
34610
|
*/
|
|
33798
34611
|
setLoading(loading) {
|
|
33799
|
-
for (const overlay of this.tableOverlays.values()) overlay.setVisible(!loading);
|
|
33800
34612
|
if (!loading || !this.wrapper) {
|
|
33801
34613
|
this.loadingEl?.remove();
|
|
33802
34614
|
this.loadingEl = null;
|
|
@@ -34456,6 +35268,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34456
35268
|
this.plot.appendChild(this.scrollButton);
|
|
34457
35269
|
this.plot.addEventListener("pointermove", this.onScrollProximityMove);
|
|
34458
35270
|
this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
|
|
35271
|
+
this.labelTooltip = new LabelTooltip(this.plot, {
|
|
35272
|
+
theme: () => this.chromeTheme(),
|
|
35273
|
+
lookup: (x, y) => this.indicatorSlices.labelTooltipAt(x, y)
|
|
35274
|
+
});
|
|
34459
35275
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
34460
35276
|
projector: () => this.drawingProjector(),
|
|
34461
35277
|
dpr: () => this.coords.dpr,
|
|
@@ -34495,9 +35311,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34495
35311
|
this.inputsUI.setDialogHost(this.dialogHost);
|
|
34496
35312
|
this.inputsUI.setSymbolPicker(this.symbolPicker);
|
|
34497
35313
|
this.inputsUI.setLegendActions(this.legendActionsProvider);
|
|
35314
|
+
this.inputsUI.setLegendCallouts(this.legendCalloutsProvider);
|
|
34498
35315
|
this.inputsUI.setLegendOverviewAction(this.legendOverviewAction);
|
|
34499
35316
|
this.inputsUI.setOnChange((c) => {
|
|
34500
|
-
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value });
|
|
35317
|
+
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value, ...c.kind ? { kind: c.kind } : {} });
|
|
34501
35318
|
});
|
|
34502
35319
|
this.inputsUI.setOnRemove((id) => {
|
|
34503
35320
|
for (const cb of this.removeIndicatorCbs) cb(id);
|
|
@@ -34538,6 +35355,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34538
35355
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
34539
35356
|
}
|
|
34540
35357
|
});
|
|
35358
|
+
this.paneControls.setSuspended(this.layoutMode === "mobile");
|
|
34541
35359
|
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
34542
35360
|
panes: () => this.axisScaleViews(),
|
|
34543
35361
|
rightAxis: () => this.rightAxisW,
|
|
@@ -34547,8 +35365,19 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34547
35365
|
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
34548
35366
|
}
|
|
34549
35367
|
});
|
|
34550
|
-
this.resizeObserver = new ResizeObserver(() =>
|
|
35368
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
35369
|
+
for (const e of entries) {
|
|
35370
|
+
if (e.target !== this.plot) continue;
|
|
35371
|
+
const s = e.devicePixelContentBoxSize?.[0];
|
|
35372
|
+
if (s) this.plotDeviceSize = { width: s.inlineSize, height: s.blockSize };
|
|
35373
|
+
}
|
|
35374
|
+
this.resize();
|
|
35375
|
+
});
|
|
34551
35376
|
this.resizeObserver.observe(this.wrapper);
|
|
35377
|
+
try {
|
|
35378
|
+
this.resizeObserver.observe(this.plot, { box: "device-pixel-content-box" });
|
|
35379
|
+
} catch {
|
|
35380
|
+
}
|
|
34552
35381
|
this.watchDpr();
|
|
34553
35382
|
this.syncSize();
|
|
34554
35383
|
}
|
|
@@ -34673,8 +35502,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34673
35502
|
this.inputsUI?.destroy();
|
|
34674
35503
|
this.paneControls?.destroy();
|
|
34675
35504
|
this.axisScaleButtons?.destroy();
|
|
34676
|
-
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
34677
|
-
this.tableOverlays.clear();
|
|
34678
35505
|
this.resizeObserver?.disconnect();
|
|
34679
35506
|
this.resizeObserver = null;
|
|
34680
35507
|
this.dprMedia?.removeEventListener("change", this.onDprChange);
|
|
@@ -34687,6 +35514,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34687
35514
|
this.settingsButton = null;
|
|
34688
35515
|
this.plot?.removeEventListener("pointermove", this.onScrollProximityMove);
|
|
34689
35516
|
this.plot?.removeEventListener("pointerleave", this.onScrollProximityLeave);
|
|
35517
|
+
this.labelTooltip?.destroy();
|
|
35518
|
+
this.labelTooltip = null;
|
|
34690
35519
|
this.scrollButton?.remove();
|
|
34691
35520
|
this.scrollButton = null;
|
|
34692
35521
|
for (const l of this.extLayers) l.instance.destroy?.();
|
|
@@ -34701,6 +35530,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34701
35530
|
this.attributionEl = null;
|
|
34702
35531
|
this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
|
|
34703
35532
|
this.mountContainer?.style.removeProperty("--vela-scale-gutter");
|
|
35533
|
+
this.mountContainer?.style.removeProperty("--vela-bottom-gutter");
|
|
34704
35534
|
this.mountContainer?.style.removeProperty("--vela-price-pane-top");
|
|
34705
35535
|
this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
|
|
34706
35536
|
this.mountContainer = null;
|
|
@@ -34793,7 +35623,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34793
35623
|
ensurePane(pane) {
|
|
34794
35624
|
this.scene.ensurePane(pane.id, pane.kind, pane.order, pane.heightWeight ?? (pane.kind === "price" ? 3 : 1));
|
|
34795
35625
|
this.layoutPanes();
|
|
34796
|
-
this.repositionTables();
|
|
34797
35626
|
this.paneControls?.refresh();
|
|
34798
35627
|
this.scheduler.invalidate(4 /* Full */);
|
|
34799
35628
|
}
|
|
@@ -34815,17 +35644,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34815
35644
|
if (!model.ownScale) this.scene.dropIndicatorScale(handle.id);
|
|
34816
35645
|
this.inputsUI.setPane(handle.id, paneId);
|
|
34817
35646
|
this.refreshAnchorOffset(model);
|
|
34818
|
-
this.syncTables(model);
|
|
34819
35647
|
this.refreshAxisWidth();
|
|
34820
35648
|
this.layoutPanes();
|
|
34821
|
-
this.repositionTables();
|
|
34822
35649
|
this.paneControls?.refresh();
|
|
34823
35650
|
this.scheduler.invalidate(4 /* Full */);
|
|
34824
35651
|
}
|
|
34825
35652
|
orderPanes(orderedIds) {
|
|
34826
35653
|
this.scene.orderPanes(orderedIds);
|
|
34827
35654
|
this.layoutPanes();
|
|
34828
|
-
this.repositionTables();
|
|
34829
35655
|
this.paneControls?.refresh();
|
|
34830
35656
|
this.scheduler.invalidate(4 /* Full */);
|
|
34831
35657
|
}
|
|
@@ -34834,7 +35660,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34834
35660
|
if (!pane || pane.collapsed === collapsed) return;
|
|
34835
35661
|
pane.collapsed = collapsed;
|
|
34836
35662
|
this.layoutPanes();
|
|
34837
|
-
this.repositionTables();
|
|
34838
35663
|
this.paneControls?.refresh();
|
|
34839
35664
|
this.scheduler.invalidate(4 /* Full */);
|
|
34840
35665
|
}
|
|
@@ -34842,7 +35667,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34842
35667
|
if (paneId !== null && !this.scene.panes.has(paneId)) paneId = null;
|
|
34843
35668
|
this.maximizedPaneId = paneId;
|
|
34844
35669
|
this.layoutPanes();
|
|
34845
|
-
this.repositionTables();
|
|
34846
35670
|
this.paneControls?.refresh();
|
|
34847
35671
|
this.scheduler.invalidate(4 /* Full */);
|
|
34848
35672
|
}
|
|
@@ -34927,9 +35751,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34927
35751
|
else this.scene.assignIndicatorZ(model.id);
|
|
34928
35752
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
34929
35753
|
native: !!model.native,
|
|
34930
|
-
...model.
|
|
35754
|
+
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
34931
35755
|
});
|
|
34932
|
-
this.syncTables(model);
|
|
34933
35756
|
if (model.native?.type === "volume") {
|
|
34934
35757
|
this.volumeActive = true;
|
|
34935
35758
|
this.volumeHidden = false;
|
|
@@ -34953,7 +35776,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34953
35776
|
}
|
|
34954
35777
|
}
|
|
34955
35778
|
applyPatch(model, patch);
|
|
34956
|
-
this.syncTables(model);
|
|
34957
35779
|
this.scheduler.invalidate(3 /* Light */);
|
|
34958
35780
|
}
|
|
34959
35781
|
removeIndicator(handle) {
|
|
@@ -34972,14 +35794,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34972
35794
|
this.scene.forgetAnchorOffset(handle.id);
|
|
34973
35795
|
this.scene.dropIndicatorScale(handle.id);
|
|
34974
35796
|
this.inputsUI.remove(handle.id);
|
|
34975
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
34976
|
-
this.tableOverlays.delete(handle.id);
|
|
34977
35797
|
this.refreshAxisWidth();
|
|
34978
35798
|
this.paneControls?.refresh();
|
|
34979
35799
|
this.scheduler.invalidate(4 /* Full */);
|
|
34980
35800
|
}
|
|
34981
|
-
setIndicatorInputs(handle, values) {
|
|
34982
|
-
this.inputsUI.setValues(handle.id, values);
|
|
35801
|
+
setIndicatorInputs(handle, values, props) {
|
|
35802
|
+
this.inputsUI.setValues(handle.id, values, props);
|
|
34983
35803
|
}
|
|
34984
35804
|
setSymbolPicker(picker) {
|
|
34985
35805
|
this.symbolPicker = picker;
|
|
@@ -34989,6 +35809,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34989
35809
|
this.legendActionsProvider = provider;
|
|
34990
35810
|
this.inputsUI?.setLegendActions(provider);
|
|
34991
35811
|
}
|
|
35812
|
+
setLegendCallouts(provider) {
|
|
35813
|
+
this.legendCalloutsProvider = provider;
|
|
35814
|
+
this.inputsUI?.setLegendCallouts(provider);
|
|
35815
|
+
}
|
|
34992
35816
|
setLegendOverviewAction(action) {
|
|
34993
35817
|
this.legendOverviewAction = action;
|
|
34994
35818
|
this.inputsUI?.setLegendOverviewAction(action);
|
|
@@ -35013,8 +35837,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35013
35837
|
}
|
|
35014
35838
|
if (!visible) {
|
|
35015
35839
|
this.scene.indicators.delete(handle.id);
|
|
35016
|
-
this.tableOverlays.get(handle.id)?.destroy();
|
|
35017
|
-
this.tableOverlays.delete(handle.id);
|
|
35018
35840
|
}
|
|
35019
35841
|
this.inputsUI.setVisible(handle.id, visible);
|
|
35020
35842
|
this.scheduler.invalidate(4 /* Full */);
|
|
@@ -35060,6 +35882,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35060
35882
|
this.userDrawings?.setLayoutMode(mode);
|
|
35061
35883
|
this.settingsDialog?.setLayoutMode(mode);
|
|
35062
35884
|
this.inputsUI?.setLayoutMode(mode);
|
|
35885
|
+
this.paneControls?.setSuspended(mode === "mobile");
|
|
35063
35886
|
if (this.scrollButton) {
|
|
35064
35887
|
const px = mode === "mobile" ? SCROLL_BTN_SIZE_TOUCH : SCROLL_BTN_SIZE;
|
|
35065
35888
|
this.scrollButton.style.width = `${px}px`;
|
|
@@ -35455,7 +36278,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35455
36278
|
/** Relayout + repaint + refresh the hover buttons after a collapse/maximize/order change. */
|
|
35456
36279
|
afterPaneLayoutChange() {
|
|
35457
36280
|
this.layoutPanes();
|
|
35458
|
-
this.repositionTables();
|
|
35459
36281
|
this.paneControls?.refresh();
|
|
35460
36282
|
this.scheduler.invalidate(4 /* Full */);
|
|
35461
36283
|
}
|
|
@@ -35536,7 +36358,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35536
36358
|
above.heightWeight = next2.above;
|
|
35537
36359
|
below.heightWeight = next2.below;
|
|
35538
36360
|
this.layoutPanes();
|
|
35539
|
-
this.repositionTables();
|
|
35540
36361
|
this.scheduler.invalidate(4 /* Full */);
|
|
35541
36362
|
}
|
|
35542
36363
|
/** Double-click a separator → split the two adjacent panes evenly (each gets half of
|
|
@@ -35551,7 +36372,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35551
36372
|
above.heightWeight = half;
|
|
35552
36373
|
below.heightWeight = half;
|
|
35553
36374
|
this.layoutPanes();
|
|
35554
|
-
this.repositionTables();
|
|
35555
36375
|
this.scheduler.invalidate(4 /* Full */);
|
|
35556
36376
|
}
|
|
35557
36377
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -35830,7 +36650,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35830
36650
|
const liveActual = li >= 0 ? this.bars[li] : void 0;
|
|
35831
36651
|
const easeLive = !!liveActual && this.liveEaseTime === liveActual.time && (liveActual.high !== this.liveEaseHigh || liveActual.low !== this.liveEaseLow || liveActual.close !== this.liveEaseClose);
|
|
35832
36652
|
if (easeLive && liveActual) this.bars[li] = { ...liveActual, high: this.liveEaseHigh, low: this.liveEaseLow, close: this.liveEaseClose };
|
|
35833
|
-
this.scene.drawingSlices =
|
|
36653
|
+
this.scene.drawingSlices = mergeSlices(
|
|
36654
|
+
this.indicatorSlices.prepare(this.scene, this.coords, this.theme, this.dataCanvas),
|
|
36655
|
+
this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map()
|
|
36656
|
+
);
|
|
35834
36657
|
this.backdropRenderer.render(this.scene, this.coords, this.theme, gridAlpha);
|
|
35835
36658
|
this.backend.render(this.scene, this.coords, this.theme);
|
|
35836
36659
|
this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
|
|
@@ -35969,7 +36792,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35969
36792
|
}
|
|
35970
36793
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
35971
36794
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
35972
|
-
|
|
36795
|
+
let dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
36796
|
+
if (pane === pricePane) {
|
|
36797
|
+
const or2 = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
|
|
36798
|
+
if (or2) dr = dr ? { min: Math.min(dr.min, or2.min), max: Math.max(dr.max, or2.max) } : or2;
|
|
36799
|
+
}
|
|
35973
36800
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
35974
36801
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
35975
36802
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -36243,6 +37070,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36243
37070
|
for (const m of models) {
|
|
36244
37071
|
const off = this.scene.offsetOf(m.id);
|
|
36245
37072
|
for (const s of m.series) {
|
|
37073
|
+
if (s.overlay === true) continue;
|
|
36246
37074
|
if (isLineLikeSeries(s)) {
|
|
36247
37075
|
const v = s.points[i0 - off]?.value;
|
|
36248
37076
|
if (v != null && Number.isFinite(v)) return v;
|
|
@@ -36263,27 +37091,6 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36263
37091
|
}
|
|
36264
37092
|
return maxVol;
|
|
36265
37093
|
}
|
|
36266
|
-
/** Create/update/destroy an indicator's DOM table overlay (anchored off real pane geometry). */
|
|
36267
|
-
syncTables(model) {
|
|
36268
|
-
const tables = model.tables ?? [];
|
|
36269
|
-
let overlay = this.tableOverlays.get(model.id);
|
|
36270
|
-
if (tables.length === 0) {
|
|
36271
|
-
if (overlay) {
|
|
36272
|
-
overlay.destroy();
|
|
36273
|
-
this.tableOverlays.delete(model.id);
|
|
36274
|
-
}
|
|
36275
|
-
return;
|
|
36276
|
-
}
|
|
36277
|
-
if (!overlay) {
|
|
36278
|
-
overlay = new TableOverlay(this.plot, this.theme, (id) => this.paneBoundsFor(id));
|
|
36279
|
-
overlay.setVisible(this.loadingEl === null);
|
|
36280
|
-
this.tableOverlays.set(model.id, overlay);
|
|
36281
|
-
}
|
|
36282
|
-
overlay.update(tables);
|
|
36283
|
-
}
|
|
36284
|
-
repositionTables() {
|
|
36285
|
-
for (const overlay of this.tableOverlays.values()) overlay.reposition();
|
|
36286
|
-
}
|
|
36287
37094
|
layoutPanes() {
|
|
36288
37095
|
const panes = this.scene.orderedPanes();
|
|
36289
37096
|
const dataHeight = this.coords.height;
|
|
@@ -36334,6 +37141,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36334
37141
|
const visible = maxPane ? [maxPane] : this.scene.orderedPanes().filter((p) => !p.collapsed);
|
|
36335
37142
|
const paneBottom = visible.length ? Math.max(...visible.map((p) => p.bounds.top + p.bounds.height)) : dataHeight;
|
|
36336
37143
|
this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM + Math.max(0, dataHeight - paneBottom);
|
|
37144
|
+
this.mountContainer?.style.setProperty("--vela-bottom-gutter", `${TIME_AXIS_H + Math.max(0, dataHeight - paneBottom)}px`);
|
|
36337
37145
|
this.scrollBtnRightPx = this.rightAxisW + SCROLL_BTN_RIGHT_INSET;
|
|
36338
37146
|
if (this.scrollButton) {
|
|
36339
37147
|
this.scrollButton.style.bottom = `${this.scrollBtnBottomPx}px`;
|
|
@@ -36426,30 +37234,33 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36426
37234
|
if (w <= 0 || h <= 0) return;
|
|
36427
37235
|
const dpr = window.devicePixelRatio || 1;
|
|
36428
37236
|
this.plot.style.left = `${this.toolbarGutter}px`;
|
|
36429
|
-
const
|
|
36430
|
-
|
|
36431
|
-
|
|
36432
|
-
|
|
36433
|
-
|
|
36434
|
-
|
|
36435
|
-
|
|
36436
|
-
|
|
36437
|
-
|
|
36438
|
-
|
|
36439
|
-
|
|
36440
|
-
|
|
36441
|
-
|
|
36442
|
-
|
|
36443
|
-
|
|
36444
|
-
|
|
36445
|
-
this.
|
|
36446
|
-
this.
|
|
36447
|
-
this.
|
|
36448
|
-
|
|
37237
|
+
const rect = this.plot.getBoundingClientRect();
|
|
37238
|
+
let bw = Math.max(1, Math.round(rect.width * dpr));
|
|
37239
|
+
let bh = Math.max(1, Math.round(rect.height * dpr));
|
|
37240
|
+
const dev = this.plotDeviceSize;
|
|
37241
|
+
if (dev && Math.abs(dev.width - rect.width * dpr) <= 1 && Math.abs(dev.height - rect.height * dpr) <= 1) {
|
|
37242
|
+
bw = Math.max(1, dev.width);
|
|
37243
|
+
bh = Math.max(1, dev.height);
|
|
37244
|
+
}
|
|
37245
|
+
const pw = bw / dpr;
|
|
37246
|
+
const ph = bh / dpr;
|
|
37247
|
+
const size3 = (canvas) => {
|
|
37248
|
+
canvas.width = bw;
|
|
37249
|
+
canvas.height = bh;
|
|
37250
|
+
canvas.style.width = `${pw}px`;
|
|
37251
|
+
canvas.style.height = `${ph}px`;
|
|
37252
|
+
};
|
|
37253
|
+
size3(this.dataCanvas);
|
|
37254
|
+
size3(this.backdropCanvas);
|
|
37255
|
+
size3(this.volumeCanvas);
|
|
37256
|
+
for (const l of this.extLayers) size3(l.canvas);
|
|
37257
|
+
size3(this.vpvrCanvas);
|
|
37258
|
+
size3(this.chromeCanvas);
|
|
37259
|
+
size3(this.drawingsCanvas);
|
|
37260
|
+
size3(this.cursorCanvas);
|
|
36449
37261
|
this.coords.setSize(Math.max(1, pw - this.rightAxisW), Math.max(1, ph - TIME_AXIS_H), dpr);
|
|
36450
37262
|
this.scene.crosshair = null;
|
|
36451
37263
|
this.layoutPanes();
|
|
36452
|
-
this.repositionTables();
|
|
36453
37264
|
this.userDrawings?.onResize();
|
|
36454
37265
|
if (!this.didInitialFit && this.coords.barCount > 0) {
|
|
36455
37266
|
this.fitContent();
|
|
@@ -37148,6 +37959,19 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
37148
37959
|
function legendActions() {
|
|
37149
37960
|
return [...legendRegistry.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
37150
37961
|
}
|
|
37962
|
+
var calloutRegistry = /* @__PURE__ */ new Map();
|
|
37963
|
+
function registerLegendCallout(desc) {
|
|
37964
|
+
calloutRegistry.set(desc.id, desc);
|
|
37965
|
+
return () => {
|
|
37966
|
+
if (calloutRegistry.get(desc.id) === desc) calloutRegistry.delete(desc.id);
|
|
37967
|
+
};
|
|
37968
|
+
}
|
|
37969
|
+
function unregisterLegendCallout(id) {
|
|
37970
|
+
calloutRegistry.delete(id);
|
|
37971
|
+
}
|
|
37972
|
+
function legendCallouts() {
|
|
37973
|
+
return [...calloutRegistry.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
37974
|
+
}
|
|
37151
37975
|
var stateHandlers = /* @__PURE__ */ new Map();
|
|
37152
37976
|
function registerStatePersistence(handler) {
|
|
37153
37977
|
stateHandlers.set(handler.key, handler);
|
|
@@ -38491,6 +39315,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38491
39315
|
exports.iconMarkup = iconMarkup;
|
|
38492
39316
|
exports.inputVisible = inputVisible;
|
|
38493
39317
|
exports.legendActions = legendActions;
|
|
39318
|
+
exports.legendCallouts = legendCallouts;
|
|
38494
39319
|
exports.nativeIndicatorDescriptors = nativeIndicatorDescriptors;
|
|
38495
39320
|
exports.nativeIndicatorTypes = nativeIndicatorTypes;
|
|
38496
39321
|
exports.normalizeSettingsRow = normalizeSettingsRow;
|
|
@@ -38499,6 +39324,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38499
39324
|
exports.registerDrawingType = registerDrawingType;
|
|
38500
39325
|
exports.registerIcon = registerIcon;
|
|
38501
39326
|
exports.registerLegendAction = registerLegendAction;
|
|
39327
|
+
exports.registerLegendCallout = registerLegendCallout;
|
|
38502
39328
|
exports.registerNativeIndicator = registerNativeIndicator;
|
|
38503
39329
|
exports.registerRendererDefaults = registerRendererDefaults;
|
|
38504
39330
|
exports.registerRendererLayer = registerRendererLayer;
|
|
@@ -38524,6 +39350,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38524
39350
|
exports.unregisterChartType = unregisterChartType;
|
|
38525
39351
|
exports.unregisterDefaultEngine = unregisterDefaultEngine;
|
|
38526
39352
|
exports.unregisterLegendAction = unregisterLegendAction;
|
|
39353
|
+
exports.unregisterLegendCallout = unregisterLegendCallout;
|
|
38527
39354
|
exports.unregisterNativeIndicator = unregisterNativeIndicator;
|
|
38528
39355
|
exports.unregisterRendererDefaults = unregisterRendererDefaults;
|
|
38529
39356
|
exports.unregisterRendererLayer = unregisterRendererLayer;
|