@luxalgo/vela 0.6.8 → 0.6.9
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-BsQM2WNH.d.cts → DataProvider-8Z95Q-RJ.d.cts} +1 -1
- package/dist/{DataProvider-BSLlBpB9.d.ts → DataProvider-DExJrfut.d.ts} +1 -1
- package/dist/{chunk-2R7BANEM.js → chunk-62SVGONC.js} +2 -2
- package/dist/{chunk-SQETIBFU.js → chunk-6WDDVMBJ.js} +502 -139
- package/dist/{chunk-L3I2CCYO.js → chunk-EQCHJZOT.js} +49 -2
- package/dist/{chunk-OICPLNU7.js → chunk-FCVIG7JG.js} +188 -2
- package/dist/{chunk-N7LGMKCE.js → chunk-STHSKXOR.js} +107 -84
- package/dist/{chunk-PN5KWFZ4.js → chunk-WFSZBX3R.js} +2 -2
- package/dist/{contributions-Vw-Hm58R.d.cts → contributions-C1U2Krwg.d.cts} +99 -7
- package/dist/{contributions-DLLdV9jD.d.ts → contributions-D7PVZO2i.d.ts} +99 -7
- package/dist/index.cjs +709 -144
- package/dist/index.d.cts +12 -7
- package/dist/index.d.ts +12 -7
- package/dist/index.js +4 -4
- package/dist/{options-BaVTMXaO.d.ts → options-FM0peknS.d.cts} +84 -7
- package/dist/{options-BaVTMXaO.d.cts → options-FM0peknS.d.ts} +84 -7
- package/dist/{plugin-CC7rBrOY.d.ts → plugin-7bkF32Rk.d.ts} +3 -3
- package/dist/{plugin-BnJgjLAy.d.cts → plugin-DfVqBz9p.d.cts} +3 -3
- package/dist/plugin.cjs +18 -2
- 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-DqzBqy42.d.cts → statusline-DOPiT6I6.d.cts} +5 -3
- package/dist/{statusline-CGkB2EOo.d.ts → statusline-zdF4eZLr.d.ts} +5 -3
- package/dist/ui.cjs +201 -4
- package/dist/ui.d.cts +89 -2
- package/dist/ui.d.ts +89 -2
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +709 -144
- package/dist/vela.global.min.js +93 -44
- package/dist/widget.cjs +798 -188
- package/dist/widget.d.cts +6 -6
- package/dist/widget.d.ts +6 -6
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +798 -188
- package/dist/workspace.d.cts +16 -6
- package/dist/workspace.d.ts +16 -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(
|
|
@@ -7318,7 +7335,9 @@ var Vela = (function (exports) {
|
|
|
7318
7335
|
for (const engine of engines) this.registerEngine(engine.language, engine);
|
|
7319
7336
|
this.defaultLanguage = config.defaultLanguage;
|
|
7320
7337
|
this.renderer.mount(container, config.theme);
|
|
7321
|
-
this.renderer.onInputChange(
|
|
7338
|
+
this.renderer.onInputChange(
|
|
7339
|
+
(e) => e.kind === "prop" ? this.applyProps(e.indicatorId, { [e.key]: e.value }) : this.applyInputs(e.indicatorId, { [e.key]: e.value })
|
|
7340
|
+
);
|
|
7322
7341
|
this.renderer.onRemoveIndicator((id) => this.removeIndicator(id));
|
|
7323
7342
|
this.renderer.onToggleIndicatorVisible?.((id, visible) => this.setVisible(id, visible));
|
|
7324
7343
|
this.paneActionUnsub = this.renderer.onPaneAction?.((a) => this.handlePaneAction(a)) ?? null;
|
|
@@ -7998,7 +8017,7 @@ var Vela = (function (exports) {
|
|
|
7998
8017
|
const title = options.title ?? "Indicator";
|
|
7999
8018
|
const handle = new IndicatorHandleImpl(id, title, this, source);
|
|
8000
8019
|
this.handles.set(id, handle);
|
|
8001
|
-
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} } });
|
|
8020
|
+
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} }, propValues: { ...options.props ?? {} } });
|
|
8002
8021
|
void this.startIndicator(id, source, options, handle);
|
|
8003
8022
|
return handle;
|
|
8004
8023
|
}
|
|
@@ -8021,7 +8040,7 @@ var Vela = (function (exports) {
|
|
|
8021
8040
|
return handle;
|
|
8022
8041
|
}
|
|
8023
8042
|
const inputValues = { ...descriptor.defaultInputs(), ...options.inputs ?? {} };
|
|
8024
|
-
this.registry.add({ id, title, source: type, inputValues, native: { type, instance: descriptor.create(), descriptor } });
|
|
8043
|
+
this.registry.add({ id, title, source: type, inputValues, propValues: {}, native: { type, instance: descriptor.create(), descriptor } });
|
|
8025
8044
|
handle.setSchema(descriptor.inputsSchema());
|
|
8026
8045
|
void this.startNativeIndicator(id, handle);
|
|
8027
8046
|
return handle;
|
|
@@ -8074,6 +8093,19 @@ var Vela = (function (exports) {
|
|
|
8074
8093
|
if (record.session) record.session.update(record.inputValues);
|
|
8075
8094
|
else if (record.native && !record.hidden) record.native.instance.setInputs(record.inputValues);
|
|
8076
8095
|
}
|
|
8096
|
+
/** IndicatorController: re-run an indicator with merged declaration-prop overrides.
|
|
8097
|
+
* Same lifecycle as {@link applyInputs} — a prop change replays the whole script.
|
|
8098
|
+
* Script indicators only: natives have no declaration props. */
|
|
8099
|
+
applyProps(id, values) {
|
|
8100
|
+
const record = this.registry.get(id);
|
|
8101
|
+
if (!record?.session) return;
|
|
8102
|
+
record.propValues = { ...record.propValues, ...values };
|
|
8103
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
8104
|
+
record.pendingStructural = true;
|
|
8105
|
+
if (!record.hidden) this.setLoading(record, true);
|
|
8106
|
+
record.pendingCause = "inputs";
|
|
8107
|
+
record.session.update(record.inputValues, record.propValues);
|
|
8108
|
+
}
|
|
8077
8109
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
8078
8110
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
8079
8111
|
listIndicators() {
|
|
@@ -8222,6 +8254,10 @@ var Vela = (function (exports) {
|
|
|
8222
8254
|
for (const input of prepared.inputs) defaults2[input.key] = input.defval;
|
|
8223
8255
|
record.inputValues = { ...defaults2, ...record.inputValues };
|
|
8224
8256
|
handle.setSchema(prepared.inputs);
|
|
8257
|
+
const propDefaults = {};
|
|
8258
|
+
for (const prop of prepared.props ?? []) propDefaults[prop.key] = prop.defval;
|
|
8259
|
+
record.propValues = { ...propDefaults, ...record.propValues };
|
|
8260
|
+
handle.setPropsSchema(prepared.props ?? []);
|
|
8225
8261
|
this.mountLoadingPlaceholder(id, record);
|
|
8226
8262
|
this.executeIndicator(id, handle);
|
|
8227
8263
|
} catch (err) {
|
|
@@ -8245,6 +8281,7 @@ var Vela = (function (exports) {
|
|
|
8245
8281
|
getBars: () => this.bars,
|
|
8246
8282
|
fetchSeries: (sym, tf, range) => this.fetchSeries(sym, tf, range),
|
|
8247
8283
|
inputs: record.inputValues,
|
|
8284
|
+
props: record.propValues,
|
|
8248
8285
|
visibleRange: this.currentVisibleRange(),
|
|
8249
8286
|
mode,
|
|
8250
8287
|
// Mid-backfill session starts (add / re-show / price-style re-execute) let the
|
|
@@ -8338,6 +8375,9 @@ var Vela = (function (exports) {
|
|
|
8338
8375
|
const meta = record.prepared.meta;
|
|
8339
8376
|
const model = {
|
|
8340
8377
|
id,
|
|
8378
|
+
// Deliberately the FULL title, never a shorttitle: while the script loads the
|
|
8379
|
+
// legend identifies it by its full name; the compact shorttitle arrives with
|
|
8380
|
+
// the first computed model and takes over from there.
|
|
8341
8381
|
title: record.options?.title ?? meta.title,
|
|
8342
8382
|
overlay: meta.overlay,
|
|
8343
8383
|
paneHint: meta.overlay ? "price" : "new",
|
|
@@ -8346,7 +8386,8 @@ var Vela = (function (exports) {
|
|
|
8346
8386
|
backgrounds: [],
|
|
8347
8387
|
priceLines: [],
|
|
8348
8388
|
inputs: record.prepared.inputs,
|
|
8349
|
-
inputValues: record.inputValues
|
|
8389
|
+
inputValues: record.inputValues,
|
|
8390
|
+
...record.prepared.props ? { props: record.prepared.props, propValues: record.propValues } : {}
|
|
8350
8391
|
};
|
|
8351
8392
|
const paneId = this.routePane(id, model, record.options ?? {});
|
|
8352
8393
|
this.placeModel(model, id, paneId);
|
|
@@ -8579,7 +8620,7 @@ var Vela = (function (exports) {
|
|
|
8579
8620
|
record.model = model;
|
|
8580
8621
|
if (record.pendingStructural) {
|
|
8581
8622
|
record.renderHandle = this.renderer.mountIndicator(model);
|
|
8582
|
-
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues);
|
|
8623
|
+
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
8583
8624
|
record.pendingStructural = false;
|
|
8584
8625
|
} else {
|
|
8585
8626
|
this.renderer.updateIndicator(record.renderHandle, modelToValuePatch(model));
|
|
@@ -8606,16 +8647,17 @@ var Vela = (function (exports) {
|
|
|
8606
8647
|
placeModel(model, id, paneId) {
|
|
8607
8648
|
model.id = id;
|
|
8608
8649
|
model.paneId = paneId;
|
|
8609
|
-
|
|
8610
|
-
for (const
|
|
8611
|
-
for (const
|
|
8650
|
+
const paneOf = (item) => item.overlay === true ? "price" : paneId;
|
|
8651
|
+
for (const series of model.series) series.paneId = paneOf(series);
|
|
8652
|
+
for (const fill of model.fills) fill.paneId = paneOf(fill);
|
|
8653
|
+
for (const bg of model.backgrounds) bg.paneId = paneOf(bg);
|
|
8612
8654
|
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 =
|
|
8655
|
+
if (model.lines) for (const ln of model.lines) ln.paneId = paneOf(ln);
|
|
8656
|
+
if (model.boxes) for (const bx of model.boxes) bx.paneId = paneOf(bx);
|
|
8657
|
+
if (model.labels) for (const lb of model.labels) lb.paneId = paneOf(lb);
|
|
8658
|
+
if (model.polylines) for (const pl of model.polylines) pl.paneId = paneOf(pl);
|
|
8659
|
+
if (model.linefills) for (const lf of model.linefills) lf.paneId = paneOf(lf);
|
|
8660
|
+
if (model.tables) for (const tb of model.tables) tb.paneId = paneOf(tb);
|
|
8619
8661
|
}
|
|
8620
8662
|
emitContextChanged(id) {
|
|
8621
8663
|
if (!this.registry.get(id)?.session?.getContext) return;
|
|
@@ -8768,6 +8810,15 @@ var Vela = (function (exports) {
|
|
|
8768
8810
|
setLegendActions(provider) {
|
|
8769
8811
|
this.renderer.setLegendActions?.(provider);
|
|
8770
8812
|
}
|
|
8813
|
+
/**
|
|
8814
|
+
* Wire the legend rows' HOST-CONTRIBUTED callout bubbles (the shells route the
|
|
8815
|
+
* plugin registry through this; see `registerLegendCallout`). Silent on a renderer
|
|
8816
|
+
* without the seam — contributed callouts simply never show there, same graceful
|
|
8817
|
+
* degradation as {@link setLegendActions}.
|
|
8818
|
+
*/
|
|
8819
|
+
setLegendCallouts(provider) {
|
|
8820
|
+
this.renderer.setLegendCallouts?.(provider);
|
|
8821
|
+
}
|
|
8771
8822
|
/**
|
|
8772
8823
|
* Replace the indicator legend's fold toggle with a host action (or restore it with
|
|
8773
8824
|
* `null`) — multi-chart shells point the chip at their indicator overview instead of
|
|
@@ -16606,13 +16657,6 @@ var Vela = (function (exports) {
|
|
|
16606
16657
|
}
|
|
16607
16658
|
};
|
|
16608
16659
|
|
|
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
16660
|
// src/ui/tokens.ts
|
|
16617
16661
|
var STATIC_ID = "vela-ui-tokens";
|
|
16618
16662
|
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
@@ -16858,6 +16902,199 @@ ${STATIC_DECLS}
|
|
|
16858
16902
|
}
|
|
16859
16903
|
};
|
|
16860
16904
|
|
|
16905
|
+
// src/ui/components/callout-bubble/controller.ts
|
|
16906
|
+
function closesPanel(item) {
|
|
16907
|
+
return item.close !== false;
|
|
16908
|
+
}
|
|
16909
|
+
function calloutPanelRows(items) {
|
|
16910
|
+
const rows = [];
|
|
16911
|
+
for (const item of items) {
|
|
16912
|
+
if (item.type === "text") {
|
|
16913
|
+
rows.push({ type: "text", text: item.text });
|
|
16914
|
+
continue;
|
|
16915
|
+
}
|
|
16916
|
+
const last2 = rows[rows.length - 1];
|
|
16917
|
+
if (last2 && last2.type === "buttons") last2.buttons.push(item);
|
|
16918
|
+
else rows.push({ type: "buttons", buttons: [item] });
|
|
16919
|
+
}
|
|
16920
|
+
return rows;
|
|
16921
|
+
}
|
|
16922
|
+
|
|
16923
|
+
// src/ui/components/callout-bubble/styles.ts
|
|
16924
|
+
var CALLOUT_STYLE_ID = "vela-ui-callout-bubble";
|
|
16925
|
+
var CALLOUT_CSS = `
|
|
16926
|
+
.vela-callout {
|
|
16927
|
+
display: inline-grid;
|
|
16928
|
+
place-items: center;
|
|
16929
|
+
border-radius: 50%;
|
|
16930
|
+
flex: none;
|
|
16931
|
+
line-height: 0;
|
|
16932
|
+
box-sizing: border-box;
|
|
16933
|
+
cursor: default;
|
|
16934
|
+
user-select: none;
|
|
16935
|
+
-webkit-user-select: none;
|
|
16936
|
+
}
|
|
16937
|
+
.vela-callout[role='button'] { cursor: pointer; }
|
|
16938
|
+
.vela-callout svg { display: block; }
|
|
16939
|
+
/* The deployed panel \u2014 carries the kit's elevated-card look itself (the popover
|
|
16940
|
+
shell is bare positioning chrome). */
|
|
16941
|
+
.vela-callout-panel {
|
|
16942
|
+
display: flex;
|
|
16943
|
+
flex-direction: column;
|
|
16944
|
+
gap: 8px;
|
|
16945
|
+
padding: 10px 12px;
|
|
16946
|
+
max-width: 280px;
|
|
16947
|
+
box-sizing: border-box;
|
|
16948
|
+
background: var(--vela-surface-elev);
|
|
16949
|
+
border: 1px solid var(--vela-border-strong);
|
|
16950
|
+
border-radius: 6px;
|
|
16951
|
+
box-shadow: var(--vela-shadow);
|
|
16952
|
+
color: var(--vela-fg);
|
|
16953
|
+
font: var(--vela-font-size-md) var(--vela-font);
|
|
16954
|
+
}
|
|
16955
|
+
.vela-callout-title { font-weight: 600; color: var(--vela-fg-bright); }
|
|
16956
|
+
.vela-callout-text { color: var(--vela-fg-muted); line-height: 1.45; white-space: pre-line; }
|
|
16957
|
+
.vela-callout-actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
16958
|
+
.vela-callout-btn {
|
|
16959
|
+
cursor: pointer;
|
|
16960
|
+
height: 26px;
|
|
16961
|
+
padding: 0 10px;
|
|
16962
|
+
border-radius: 5px;
|
|
16963
|
+
border: 1px solid var(--vela-border);
|
|
16964
|
+
background: transparent;
|
|
16965
|
+
color: var(--vela-fg);
|
|
16966
|
+
font-size: var(--vela-font-size-md);
|
|
16967
|
+
font-family: inherit;
|
|
16968
|
+
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;
|
|
16969
|
+
}
|
|
16970
|
+
.vela-callout-btn:hover { background: var(--vela-hover); color: var(--vela-fg-bright); border-color: var(--vela-fg-muted); }
|
|
16971
|
+
.vela-callout-btn-primary { border-color: var(--vela-selected-bg); background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
|
|
16972
|
+
.vela-callout-btn-primary:hover { background: var(--vela-selected-bg); color: var(--vela-selected-fg); opacity: 0.85; border-color: var(--vela-selected-bg); }
|
|
16973
|
+
`;
|
|
16974
|
+
|
|
16975
|
+
// src/ui/components/callout-bubble/view.ts
|
|
16976
|
+
var CalloutBubble = class {
|
|
16977
|
+
constructor(opts) {
|
|
16978
|
+
this.pop = null;
|
|
16979
|
+
this.spec = { icon: opts.icon, background: opts.background, label: opts.label };
|
|
16980
|
+
if (opts.color !== void 0) this.spec.color = opts.color;
|
|
16981
|
+
if (opts.panel !== void 0) this.spec.panel = opts.panel;
|
|
16982
|
+
this.size = opts.size ?? 16;
|
|
16983
|
+
this.host = opts.host;
|
|
16984
|
+
this.theme = opts.theme;
|
|
16985
|
+
this.boundary = opts.boundary;
|
|
16986
|
+
const doc = (opts.host ?? document.body).ownerDocument;
|
|
16987
|
+
injectStyles(CALLOUT_STYLE_ID, CALLOUT_CSS, doc);
|
|
16988
|
+
this.el = doc.createElement("span");
|
|
16989
|
+
this.el.className = "vela-callout";
|
|
16990
|
+
this.el.style.width = `${this.size}px`;
|
|
16991
|
+
this.el.style.height = `${this.size}px`;
|
|
16992
|
+
this.el.addEventListener("click", (e) => {
|
|
16993
|
+
if (!this.spec.panel) return;
|
|
16994
|
+
e.stopPropagation();
|
|
16995
|
+
this.toggle();
|
|
16996
|
+
});
|
|
16997
|
+
this.el.addEventListener("dblclick", (e) => {
|
|
16998
|
+
if (this.spec.panel) e.stopPropagation();
|
|
16999
|
+
});
|
|
17000
|
+
this.el.addEventListener("keydown", (e) => {
|
|
17001
|
+
if (!this.spec.panel || e.key !== "Enter" && e.key !== " ") return;
|
|
17002
|
+
e.preventDefault();
|
|
17003
|
+
this.toggle();
|
|
17004
|
+
});
|
|
17005
|
+
this.dress();
|
|
17006
|
+
}
|
|
17007
|
+
/** Re-dress the bubble (a status change: new icon, tint, label, panel). */
|
|
17008
|
+
set(spec) {
|
|
17009
|
+
this.spec = { ...this.spec, ...spec };
|
|
17010
|
+
if ("panel" in spec) this.pop?.hide();
|
|
17011
|
+
this.dress();
|
|
17012
|
+
}
|
|
17013
|
+
/** Whether the deployed panel is currently open. */
|
|
17014
|
+
get open() {
|
|
17015
|
+
return this.pop?.open ?? false;
|
|
17016
|
+
}
|
|
17017
|
+
destroy() {
|
|
17018
|
+
this.pop?.destroy();
|
|
17019
|
+
this.pop = null;
|
|
17020
|
+
this.el.remove();
|
|
17021
|
+
}
|
|
17022
|
+
dress() {
|
|
17023
|
+
const clickable = this.spec.panel !== void 0;
|
|
17024
|
+
this.el.style.background = this.spec.background;
|
|
17025
|
+
this.el.style.color = this.spec.color ?? "";
|
|
17026
|
+
this.el.innerHTML = iconAt(this.spec.icon, this.size - 4);
|
|
17027
|
+
this.el.setAttribute("aria-label", this.spec.label);
|
|
17028
|
+
if (clickable) {
|
|
17029
|
+
this.el.setAttribute("role", "button");
|
|
17030
|
+
this.el.tabIndex = 0;
|
|
17031
|
+
} else {
|
|
17032
|
+
this.el.removeAttribute("role");
|
|
17033
|
+
this.el.removeAttribute("tabindex");
|
|
17034
|
+
}
|
|
17035
|
+
}
|
|
17036
|
+
toggle() {
|
|
17037
|
+
if (this.pop?.open) {
|
|
17038
|
+
this.pop.hide();
|
|
17039
|
+
return;
|
|
17040
|
+
}
|
|
17041
|
+
const panel = this.spec.panel;
|
|
17042
|
+
if (!panel) return;
|
|
17043
|
+
this.pop?.destroy();
|
|
17044
|
+
this.pop = new Popover({
|
|
17045
|
+
trigger: this.el,
|
|
17046
|
+
gap: 6,
|
|
17047
|
+
content: (body) => this.buildPanel(body, panel),
|
|
17048
|
+
...this.host ? { host: this.host } : {},
|
|
17049
|
+
...this.theme ? { theme: this.theme() } : {},
|
|
17050
|
+
...this.boundary ? { boundary: this.boundary } : {}
|
|
17051
|
+
});
|
|
17052
|
+
this.pop.show();
|
|
17053
|
+
}
|
|
17054
|
+
buildPanel(body, panel) {
|
|
17055
|
+
const doc = body.ownerDocument;
|
|
17056
|
+
const root = doc.createElement("div");
|
|
17057
|
+
root.className = "vela-callout-panel";
|
|
17058
|
+
if (panel.title) {
|
|
17059
|
+
const title = doc.createElement("div");
|
|
17060
|
+
title.className = "vela-callout-title";
|
|
17061
|
+
title.textContent = panel.title;
|
|
17062
|
+
root.appendChild(title);
|
|
17063
|
+
}
|
|
17064
|
+
for (const row of calloutPanelRows(panel.items)) {
|
|
17065
|
+
if (row.type === "text") {
|
|
17066
|
+
const text = doc.createElement("div");
|
|
17067
|
+
text.className = "vela-callout-text";
|
|
17068
|
+
text.textContent = row.text;
|
|
17069
|
+
root.appendChild(text);
|
|
17070
|
+
continue;
|
|
17071
|
+
}
|
|
17072
|
+
const actions = doc.createElement("div");
|
|
17073
|
+
actions.className = "vela-callout-actions";
|
|
17074
|
+
for (const item of row.buttons) {
|
|
17075
|
+
const btn2 = doc.createElement("button");
|
|
17076
|
+
btn2.type = "button";
|
|
17077
|
+
btn2.className = "vela-callout-btn" + (item.primary ? " vela-callout-btn-primary" : "");
|
|
17078
|
+
btn2.textContent = item.label;
|
|
17079
|
+
btn2.addEventListener("click", () => {
|
|
17080
|
+
item.run();
|
|
17081
|
+
if (closesPanel(item)) this.pop?.hide();
|
|
17082
|
+
});
|
|
17083
|
+
actions.appendChild(btn2);
|
|
17084
|
+
}
|
|
17085
|
+
root.appendChild(actions);
|
|
17086
|
+
}
|
|
17087
|
+
body.appendChild(root);
|
|
17088
|
+
}
|
|
17089
|
+
};
|
|
17090
|
+
|
|
17091
|
+
// src/core/model/inputs.ts
|
|
17092
|
+
function inputVisible(when, values) {
|
|
17093
|
+
if (!when) return true;
|
|
17094
|
+
const conds = Array.isArray(when) ? when : [when];
|
|
17095
|
+
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
|
|
17096
|
+
}
|
|
17097
|
+
|
|
16861
17098
|
// src/ui/components/select/controller.ts
|
|
16862
17099
|
function selectController(opts) {
|
|
16863
17100
|
const options = opts.options;
|
|
@@ -19961,6 +20198,10 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
19961
20198
|
}
|
|
19962
20199
|
|
|
19963
20200
|
// src/renderers/shared/IndicatorInputsDialog.ts
|
|
20201
|
+
var PROPS_TAB = "Properties";
|
|
20202
|
+
function bagOf(row, decl) {
|
|
20203
|
+
return decl.prop ? row.propValues : row.values;
|
|
20204
|
+
}
|
|
19964
20205
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
19965
20206
|
var IndicatorInputsDialog = class {
|
|
19966
20207
|
constructor(host) {
|
|
@@ -19971,6 +20212,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
19971
20212
|
this.openId = null;
|
|
19972
20213
|
this.row = null;
|
|
19973
20214
|
this.snapshot = null;
|
|
20215
|
+
this.propSnapshot = null;
|
|
19974
20216
|
this.dialogTips = [];
|
|
19975
20217
|
/** Re-applies every `when` gate against current values; set while the dialog is open. */
|
|
19976
20218
|
this.refreshVisibility = null;
|
|
@@ -19993,10 +20235,11 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
19993
20235
|
}
|
|
19994
20236
|
open(row) {
|
|
19995
20237
|
this.close();
|
|
19996
|
-
if (row.inputs.length === 0) return;
|
|
20238
|
+
if (row.inputs.length === 0 && row.props.length === 0) return;
|
|
19997
20239
|
this.row = row;
|
|
19998
20240
|
this.openId = row.id;
|
|
19999
20241
|
this.snapshot = { ...row.values };
|
|
20242
|
+
this.propSnapshot = { ...row.propValues };
|
|
20000
20243
|
ensureDialogStyles();
|
|
20001
20244
|
const t = this.host.theme();
|
|
20002
20245
|
const border = "var(--vela-border)";
|
|
@@ -20004,7 +20247,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20004
20247
|
const host = this.host.dialogHost() ?? this.host.container;
|
|
20005
20248
|
const ui = new Dialog({
|
|
20006
20249
|
host,
|
|
20007
|
-
title: row.
|
|
20250
|
+
title: row.title,
|
|
20008
20251
|
// Non-modal: live-edit dialog — a modal machine locks pointer events on the
|
|
20009
20252
|
// whole body, killing the chart and the body-portaled popovers.
|
|
20010
20253
|
modal: false,
|
|
@@ -20036,6 +20279,16 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20036
20279
|
text: () => "Close"
|
|
20037
20280
|
}));
|
|
20038
20281
|
const tabDefs = tabInputs(row.inputs);
|
|
20282
|
+
if (row.props.length > 0) {
|
|
20283
|
+
const propDecls = row.props.map((p) => {
|
|
20284
|
+
const d = { ...p, prop: true };
|
|
20285
|
+
delete d.tab;
|
|
20286
|
+
return d;
|
|
20287
|
+
});
|
|
20288
|
+
const existing = tabDefs.find((t2) => t2.name === PROPS_TAB);
|
|
20289
|
+
if (existing) existing.inputs.push(...propDecls);
|
|
20290
|
+
else tabDefs.push({ name: PROPS_TAB, inputs: propDecls });
|
|
20291
|
+
}
|
|
20039
20292
|
const tabs = document.createElement("div");
|
|
20040
20293
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
20041
20294
|
const tabEls = [];
|
|
@@ -20138,9 +20391,10 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20138
20391
|
this.openId = null;
|
|
20139
20392
|
this.row = null;
|
|
20140
20393
|
this.snapshot = null;
|
|
20394
|
+
this.propSnapshot = null;
|
|
20141
20395
|
ui?.destroy();
|
|
20142
20396
|
}
|
|
20143
|
-
/** Restore every input to its open-time value (re-running the indicator), then close. */
|
|
20397
|
+
/** Restore every input and prop to its open-time value (re-running the indicator), then close. */
|
|
20144
20398
|
revertAndClose() {
|
|
20145
20399
|
const row = this.row;
|
|
20146
20400
|
const snap = this.snapshot;
|
|
@@ -20154,6 +20408,17 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20154
20408
|
}
|
|
20155
20409
|
}
|
|
20156
20410
|
}
|
|
20411
|
+
const propSnap = this.propSnapshot;
|
|
20412
|
+
if (row && propSnap) {
|
|
20413
|
+
for (const p of row.props) {
|
|
20414
|
+
const snapped = propSnap[p.key];
|
|
20415
|
+
const before = snapped !== void 0 ? snapped : p.defval;
|
|
20416
|
+
if (row.propValues[p.key] !== before) {
|
|
20417
|
+
row.propValues[p.key] = before;
|
|
20418
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: before, kind: "prop" });
|
|
20419
|
+
}
|
|
20420
|
+
}
|
|
20421
|
+
}
|
|
20157
20422
|
this.close();
|
|
20158
20423
|
}
|
|
20159
20424
|
/** The footer's reset button — same chip as Cancel, pinned to the LEFT edge
|
|
@@ -20171,19 +20436,27 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20171
20436
|
const row = this.row;
|
|
20172
20437
|
if (!row) return;
|
|
20173
20438
|
const snap = this.snapshot;
|
|
20439
|
+
const propSnap = this.propSnapshot;
|
|
20174
20440
|
for (const inp of row.inputs) {
|
|
20175
20441
|
if (row.values[inp.key] !== inp.defval) {
|
|
20176
20442
|
row.values[inp.key] = inp.defval;
|
|
20177
20443
|
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value: inp.defval });
|
|
20178
20444
|
}
|
|
20179
20445
|
}
|
|
20446
|
+
for (const p of row.props) {
|
|
20447
|
+
if (row.propValues[p.key] !== p.defval) {
|
|
20448
|
+
row.propValues[p.key] = p.defval;
|
|
20449
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: p.defval, kind: "prop" });
|
|
20450
|
+
}
|
|
20451
|
+
}
|
|
20180
20452
|
this.open(row);
|
|
20181
20453
|
if (snap) this.snapshot = snap;
|
|
20454
|
+
if (propSnap) this.propSnapshot = propSnap;
|
|
20182
20455
|
}
|
|
20183
20456
|
/** 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 });
|
|
20457
|
+
commit(row, decl, value) {
|
|
20458
|
+
bagOf(row, decl)[decl.key] = value;
|
|
20459
|
+
this.host.onChange?.({ indicatorId: row.id, key: decl.key, value, ...decl.prop ? { kind: "prop" } : {} });
|
|
20187
20460
|
this.refreshVisibility?.();
|
|
20188
20461
|
}
|
|
20189
20462
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
@@ -20210,7 +20483,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20210
20483
|
const id = idOf(lead);
|
|
20211
20484
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
20212
20485
|
if (lead.type === "bool") {
|
|
20213
|
-
const current = Boolean(row
|
|
20486
|
+
const current = Boolean(bagOf(row, lead)[lead.key] ?? lead.defval);
|
|
20214
20487
|
return append(fieldRow({
|
|
20215
20488
|
label: nameOf(lead),
|
|
20216
20489
|
id,
|
|
@@ -20219,7 +20492,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20219
20492
|
toggle: {
|
|
20220
20493
|
id,
|
|
20221
20494
|
checked: current,
|
|
20222
|
-
onChange: (v) => this.commit(row, lead
|
|
20495
|
+
onChange: (v) => this.commit(row, lead, v)
|
|
20223
20496
|
}
|
|
20224
20497
|
}));
|
|
20225
20498
|
}
|
|
@@ -20244,8 +20517,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20244
20517
|
}
|
|
20245
20518
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
20246
20519
|
buildControl(row, inp, id) {
|
|
20247
|
-
const current = row
|
|
20248
|
-
const emit = (value) => this.commit(row, inp
|
|
20520
|
+
const current = bagOf(row, inp)[inp.key] ?? inp.defval;
|
|
20521
|
+
const emit = (value) => this.commit(row, inp, value);
|
|
20249
20522
|
if (inp.type === "bool") {
|
|
20250
20523
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
20251
20524
|
}
|
|
@@ -20256,7 +20529,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20256
20529
|
kind: "color",
|
|
20257
20530
|
id,
|
|
20258
20531
|
theme: this.host.theme(),
|
|
20259
|
-
get: () => String(row
|
|
20532
|
+
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
20260
20533
|
onChange: (v) => emit(v)
|
|
20261
20534
|
}).el;
|
|
20262
20535
|
}
|
|
@@ -20819,6 +21092,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
20819
21092
|
}
|
|
20820
21093
|
|
|
20821
21094
|
// src/renderers/shared/InputsUI.ts
|
|
21095
|
+
var LEGEND_AT_TOP_ATTR = "data-vela-pane-at-top";
|
|
20822
21096
|
var STATUS_KEYFRAMES = "@keyframes vela-ind-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.6)}}";
|
|
20823
21097
|
var LEGEND_ICON_PX2 = 16;
|
|
20824
21098
|
var LEGEND_CTL_PX = 18;
|
|
@@ -20857,10 +21131,14 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
20857
21131
|
this.moveApi = null;
|
|
20858
21132
|
/** Host-contributed legend actions, resolved PER ROW at render time (see setLegendActions). */
|
|
20859
21133
|
this.legendActions = null;
|
|
21134
|
+
/** Host-contributed callout bubbles, resolved PER ROW at render time (see setLegendCallouts). */
|
|
21135
|
+
this.legendCallouts = null;
|
|
20860
21136
|
/** Chrome-tooltip disposers, per row id — a tip open at removal must not outlive its row. */
|
|
20861
21137
|
this.rowTips = /* @__PURE__ */ new Map();
|
|
20862
21138
|
/** Same, for the contributed extras only (rebuilt independently by setLegendActions). */
|
|
20863
21139
|
this.extrasTips = /* @__PURE__ */ new Map();
|
|
21140
|
+
/** Same, for the contributed callouts only (rebuilt independently by setLegendCallouts). */
|
|
21141
|
+
this.calloutTips = /* @__PURE__ */ new Map();
|
|
20864
21142
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
20865
21143
|
this.moveMenu = null;
|
|
20866
21144
|
this.moveTargets = [];
|
|
@@ -21018,6 +21296,42 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21018
21296
|
extrasEl.appendChild(btn2);
|
|
21019
21297
|
}
|
|
21020
21298
|
}
|
|
21299
|
+
/**
|
|
21300
|
+
* Wire the host-contributed callout bubbles. Same contract as
|
|
21301
|
+
* {@link setLegendActions}: re-calling replaces the provider and re-projects the
|
|
21302
|
+
* rows already on screen.
|
|
21303
|
+
*/
|
|
21304
|
+
setLegendCallouts(provider) {
|
|
21305
|
+
this.legendCallouts = provider;
|
|
21306
|
+
for (const row of this.rows.values()) this.renderCallouts(row);
|
|
21307
|
+
}
|
|
21308
|
+
/** (Re)build one row's callout bubbles — tinted icon circles beside the title whose
|
|
21309
|
+
* click (when the view carries content) deploys a panel of text and actions. */
|
|
21310
|
+
renderCallouts(row) {
|
|
21311
|
+
this.disposeTips(this.calloutTips, row.id);
|
|
21312
|
+
for (const bubble of row.callouts) bubble.destroy();
|
|
21313
|
+
row.callouts = [];
|
|
21314
|
+
row.calloutsEl.replaceChildren();
|
|
21315
|
+
const views = this.legendCallouts?.(row.id) ?? [];
|
|
21316
|
+
row.calloutsEl.style.display = views.length > 0 ? "inline-flex" : "none";
|
|
21317
|
+
for (const view of views) {
|
|
21318
|
+
const bubble = new CalloutBubble({
|
|
21319
|
+
icon: view.icon,
|
|
21320
|
+
background: view.background,
|
|
21321
|
+
...view.color !== void 0 ? { color: view.color } : {},
|
|
21322
|
+
label: view.tooltip,
|
|
21323
|
+
...view.content !== void 0 ? { panel: view.content } : {},
|
|
21324
|
+
// The panel portals into the plot host and self-tokens: it must work on
|
|
21325
|
+
// a bare chart, where no `.vela-ui` token ancestor exists.
|
|
21326
|
+
host: this.container,
|
|
21327
|
+
theme: () => this.theme
|
|
21328
|
+
});
|
|
21329
|
+
bubble.el.dataset.legendCallout = view.id;
|
|
21330
|
+
this.tip(this.calloutTips, row.id, bubble.el, view.tooltip);
|
|
21331
|
+
row.callouts.push(bubble);
|
|
21332
|
+
row.calloutsEl.appendChild(bubble.el);
|
|
21333
|
+
}
|
|
21334
|
+
}
|
|
21021
21335
|
/** Reposition the per-pane legend containers after a layout change. */
|
|
21022
21336
|
reposition() {
|
|
21023
21337
|
for (const [paneId, lg] of this.legends) this.positionLegend(lg, paneId);
|
|
@@ -21185,6 +21499,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21185
21499
|
}
|
|
21186
21500
|
positionLegend(lg, paneId) {
|
|
21187
21501
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
21502
|
+
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
21188
21503
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
21189
21504
|
const collapsed = this.paneCollapse.has(paneId);
|
|
21190
21505
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
@@ -21260,13 +21575,13 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21260
21575
|
}
|
|
21261
21576
|
/** Create or update an indicator's legend row (in the legend for its pane). */
|
|
21262
21577
|
upsert(id, title, inputs, values, paneId = "price", opts = {}) {
|
|
21263
|
-
const settingsTitle = opts.settingsTitle ?? title;
|
|
21264
21578
|
const existing = this.rows.get(id);
|
|
21265
21579
|
if (existing) {
|
|
21266
21580
|
existing.title = title;
|
|
21267
|
-
existing.settingsTitle = settingsTitle;
|
|
21268
21581
|
existing.inputs = inputs;
|
|
21269
21582
|
existing.values = { ...values };
|
|
21583
|
+
existing.props = opts.props ?? [];
|
|
21584
|
+
existing.propValues = { ...opts.propValues ?? {} };
|
|
21270
21585
|
existing.titleEl.textContent = title;
|
|
21271
21586
|
if (existing.paneId !== paneId) {
|
|
21272
21587
|
existing.paneId = paneId;
|
|
@@ -21318,6 +21633,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21318
21633
|
titleWrap.appendChild(beta);
|
|
21319
21634
|
}
|
|
21320
21635
|
el.appendChild(titleWrap);
|
|
21636
|
+
const calloutsEl = document.createElement("span");
|
|
21637
|
+
calloutsEl.style.cssText = `display:none;align-items:center;gap:4px;flex:none;margin-left:${LEGEND_TITLE_STATUS_GAP_PX}px;`;
|
|
21638
|
+
el.appendChild(calloutsEl);
|
|
21321
21639
|
el.appendChild(statusEl);
|
|
21322
21640
|
const valuesEl = document.createElement("span");
|
|
21323
21641
|
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 +21652,13 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21334
21652
|
eye.className = "vela-ind-ctl";
|
|
21335
21653
|
eye.style.cssText = LEGEND_CTL_CSS;
|
|
21336
21654
|
eye.addEventListener("click", () => {
|
|
21337
|
-
const
|
|
21338
|
-
this.onToggleVisible?.(id, Boolean(
|
|
21655
|
+
const row2 = this.rows.get(id);
|
|
21656
|
+
this.onToggleVisible?.(id, Boolean(row2?.hidden));
|
|
21339
21657
|
});
|
|
21340
21658
|
controlsEl.appendChild(eye);
|
|
21341
21659
|
eyeEl = eye;
|
|
21342
21660
|
}
|
|
21343
|
-
if (inputs.length > 0) {
|
|
21661
|
+
if (inputs.length > 0 || (opts.props ?? []).length > 0) {
|
|
21344
21662
|
const gear = document.createElement("button");
|
|
21345
21663
|
gear.type = "button";
|
|
21346
21664
|
gear.setAttribute("aria-label", "Settings");
|
|
@@ -21380,7 +21698,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21380
21698
|
controlsEl.appendChild(close);
|
|
21381
21699
|
el.appendChild(controlsEl);
|
|
21382
21700
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
21383
|
-
|
|
21701
|
+
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 };
|
|
21702
|
+
this.rows.set(id, row);
|
|
21703
|
+
this.renderCallouts(row);
|
|
21384
21704
|
this.syncFoldToggle();
|
|
21385
21705
|
}
|
|
21386
21706
|
/** Place a row in its pane's legend — native rows PREPEND (pinned to the top), Pine rows append. */
|
|
@@ -21389,9 +21709,11 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21389
21709
|
else container.appendChild(el);
|
|
21390
21710
|
}
|
|
21391
21711
|
/** Reflect programmatic input changes (so a re-opened dialog shows current values). */
|
|
21392
|
-
setValues(id, values) {
|
|
21712
|
+
setValues(id, values, props) {
|
|
21393
21713
|
const row = this.rows.get(id);
|
|
21394
|
-
if (row)
|
|
21714
|
+
if (!row) return;
|
|
21715
|
+
row.values = { ...row.values, ...values };
|
|
21716
|
+
if (props) row.propValues = { ...row.propValues, ...props };
|
|
21395
21717
|
}
|
|
21396
21718
|
/**
|
|
21397
21719
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
@@ -21473,8 +21795,13 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21473
21795
|
syncRowActions(row) {
|
|
21474
21796
|
const open2 = row.highlighted;
|
|
21475
21797
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
21476
|
-
if (open2)
|
|
21477
|
-
|
|
21798
|
+
if (open2) {
|
|
21799
|
+
row.el.appendChild(row.statusEl);
|
|
21800
|
+
row.el.appendChild(row.calloutsEl);
|
|
21801
|
+
} else {
|
|
21802
|
+
row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
21803
|
+
row.el.insertBefore(row.calloutsEl, row.statusEl);
|
|
21804
|
+
}
|
|
21478
21805
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
21479
21806
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
21480
21807
|
if (child === row.extrasEl) {
|
|
@@ -21492,10 +21819,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21492
21819
|
}
|
|
21493
21820
|
remove(id) {
|
|
21494
21821
|
const row = this.rows.get(id);
|
|
21822
|
+
for (const bubble of row?.callouts ?? []) bubble.destroy();
|
|
21495
21823
|
row?.el.remove();
|
|
21496
21824
|
this.rows.delete(id);
|
|
21497
21825
|
this.disposeTips(this.rowTips, id);
|
|
21498
21826
|
this.disposeTips(this.extrasTips, id);
|
|
21827
|
+
this.disposeTips(this.calloutTips, id);
|
|
21499
21828
|
if (this.selectedId === id) this.selectedId = null;
|
|
21500
21829
|
if (this.inputsDialog.openId === id) this.inputsDialog.close();
|
|
21501
21830
|
this.syncFoldToggle();
|
|
@@ -21515,6 +21844,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21515
21844
|
this.rowMenu = null;
|
|
21516
21845
|
for (const id of [...this.rowTips.keys()]) this.disposeTips(this.rowTips, id);
|
|
21517
21846
|
for (const id of [...this.extrasTips.keys()]) this.disposeTips(this.extrasTips, id);
|
|
21847
|
+
for (const id of [...this.calloutTips.keys()]) this.disposeTips(this.calloutTips, id);
|
|
21848
|
+
for (const row of this.rows.values()) for (const bubble of row.callouts) bubble.destroy();
|
|
21518
21849
|
for (const lg of this.legends.values()) lg.remove();
|
|
21519
21850
|
this.legends.clear();
|
|
21520
21851
|
this.rows.clear();
|
|
@@ -21869,6 +22200,32 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21869
22200
|
large: 16,
|
|
21870
22201
|
huge: 20
|
|
21871
22202
|
};
|
|
22203
|
+
function fontPxOf(size3) {
|
|
22204
|
+
if (typeof size3 === "number") return size3 > 0 ? size3 : SIZE_PX3.auto;
|
|
22205
|
+
return SIZE_PX3[size3] ?? SIZE_PX3.auto;
|
|
22206
|
+
}
|
|
22207
|
+
function tableHasContent(t) {
|
|
22208
|
+
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
22209
|
+
}
|
|
22210
|
+
function mergeRenderPlan(t) {
|
|
22211
|
+
const span = /* @__PURE__ */ new Map();
|
|
22212
|
+
const omit = /* @__PURE__ */ new Set();
|
|
22213
|
+
for (const m of t.merges) {
|
|
22214
|
+
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
22215
|
+
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
22216
|
+
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
22217
|
+
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
22218
|
+
}
|
|
22219
|
+
}
|
|
22220
|
+
}
|
|
22221
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
22222
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
22223
|
+
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
22224
|
+
}
|
|
22225
|
+
}
|
|
22226
|
+
for (const key of span.keys()) omit.delete(key);
|
|
22227
|
+
return { span, omit };
|
|
22228
|
+
}
|
|
21872
22229
|
var TableOverlay = class {
|
|
21873
22230
|
constructor(container, theme, paneBounds) {
|
|
21874
22231
|
this.container = container;
|
|
@@ -21889,7 +22246,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21889
22246
|
update(tables) {
|
|
21890
22247
|
this.lastTables = tables;
|
|
21891
22248
|
this.root.replaceChildren();
|
|
21892
|
-
for (const t of tables)
|
|
22249
|
+
for (const t of tables) {
|
|
22250
|
+
if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
|
|
22251
|
+
}
|
|
21893
22252
|
}
|
|
21894
22253
|
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
21895
22254
|
reposition() {
|
|
@@ -21904,39 +22263,36 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21904
22263
|
this.root.remove();
|
|
21905
22264
|
}
|
|
21906
22265
|
renderTable(t) {
|
|
22266
|
+
const b = this.paneBounds(t.paneId);
|
|
21907
22267
|
const wrap2 = document.createElement("div");
|
|
21908
22268
|
wrap2.style.position = "absolute";
|
|
21909
|
-
|
|
22269
|
+
if (t.frameColor && t.frameWidth > 0) wrap2.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
|
|
22270
|
+
this.anchor(wrap2, t.position, b);
|
|
21910
22271
|
const table = document.createElement("table");
|
|
21911
22272
|
Object.assign(table.style, {
|
|
21912
22273
|
borderCollapse: "collapse",
|
|
21913
22274
|
background: t.bgColor ?? "transparent",
|
|
21914
22275
|
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
22276
|
border: "none",
|
|
21919
22277
|
tableLayout: "auto",
|
|
21920
22278
|
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
21921
22279
|
pointerEvents: "auto"
|
|
21922
22280
|
});
|
|
21923
|
-
const span
|
|
21924
|
-
const
|
|
21925
|
-
|
|
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
|
-
}
|
|
22281
|
+
const { span, omit } = mergeRenderPlan(t);
|
|
22282
|
+
const plotW = Math.max(0, (this.root.clientWidth || this.container.clientWidth) - b.rightAxis);
|
|
22283
|
+
const paneH = b.height;
|
|
21933
22284
|
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
21934
22285
|
for (let r = 0; r < t.rows; r += 1) {
|
|
21935
22286
|
const tr = document.createElement("tr");
|
|
21936
22287
|
for (let c = 0; c < t.columns; c += 1) {
|
|
22288
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
21937
22289
|
const cell = t.cells[r]?.[c] ?? null;
|
|
21938
|
-
if (skip.has(`${r}:${c}`) || cell?.merged) continue;
|
|
21939
22290
|
const td = document.createElement("td");
|
|
22291
|
+
if (cell === null) {
|
|
22292
|
+
Object.assign(td.style, { padding: "0", border: "none" });
|
|
22293
|
+
tr.appendChild(td);
|
|
22294
|
+
continue;
|
|
22295
|
+
}
|
|
21940
22296
|
const sp = span.get(`${r}:${c}`);
|
|
21941
22297
|
if (sp) {
|
|
21942
22298
|
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
@@ -21945,18 +22301,22 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21945
22301
|
Object.assign(td.style, {
|
|
21946
22302
|
border: cellBorder,
|
|
21947
22303
|
padding: "2px 6px",
|
|
21948
|
-
background: cell
|
|
21949
|
-
color: cell
|
|
21950
|
-
textAlign: cell
|
|
21951
|
-
verticalAlign: cell
|
|
21952
|
-
fontSize: `${
|
|
21953
|
-
fontFamily: cell
|
|
21954
|
-
fontWeight: cell
|
|
21955
|
-
fontStyle: cell
|
|
21956
|
-
|
|
22304
|
+
background: cell.bgColor ?? "transparent",
|
|
22305
|
+
color: cell.textColor ?? this.theme.textColor,
|
|
22306
|
+
textAlign: cell.hAlign,
|
|
22307
|
+
verticalAlign: cell.vAlign === "top" ? "top" : cell.vAlign === "bottom" ? "bottom" : "middle",
|
|
22308
|
+
fontSize: `${fontPxOf(cell.textSize)}px`,
|
|
22309
|
+
fontFamily: cell.fontFamily === "monospace" ? "monospace" : "inherit",
|
|
22310
|
+
fontWeight: cell.bold ? "bold" : "normal",
|
|
22311
|
+
fontStyle: cell.italic ? "italic" : "normal",
|
|
22312
|
+
// Pine cell text never wraps; `\n` still breaks lines. Wrapping
|
|
22313
|
+
// used to collapse unicode sparklines and ━━━ dividers.
|
|
22314
|
+
whiteSpace: "pre"
|
|
21957
22315
|
});
|
|
21958
|
-
if (cell
|
|
21959
|
-
td.
|
|
22316
|
+
if (cell.width) td.style.width = `${cell.width / 100 * plotW}px`;
|
|
22317
|
+
if (cell.height) td.style.height = `${cell.height / 100 * paneH}px`;
|
|
22318
|
+
if (cell.tooltip) td.title = cell.tooltip;
|
|
22319
|
+
td.textContent = cell.text ?? "";
|
|
21960
22320
|
tr.appendChild(td);
|
|
21961
22321
|
}
|
|
21962
22322
|
table.appendChild(tr);
|
|
@@ -22841,8 +23201,14 @@ void main() {
|
|
|
22841
23201
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
22842
23202
|
};
|
|
22843
23203
|
b.alpha = this.modelAlpha;
|
|
22844
|
-
for (const m of models) for (const bgSpan of m.backgrounds) this.emitBackground(b, bgSpan, pane, coords);
|
|
22845
|
-
for (const m of models) for (const f of m.fills) this.emitFill(b, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
23204
|
+
for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
|
|
23205
|
+
for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
23206
|
+
if (isPrice) {
|
|
23207
|
+
for (const m of scene.indicators.values()) {
|
|
23208
|
+
for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
|
|
23209
|
+
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
23210
|
+
}
|
|
23211
|
+
}
|
|
22846
23212
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
22847
23213
|
let candleDrawn = false;
|
|
22848
23214
|
for (const m of models) {
|
|
@@ -22856,7 +23222,7 @@ void main() {
|
|
|
22856
23222
|
b.alpha = this.modelAlpha;
|
|
22857
23223
|
const off = scene.offsetOf(m.id);
|
|
22858
23224
|
const mp = effPane(m);
|
|
22859
|
-
for (const s of m.series) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
23225
|
+
for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
22860
23226
|
}
|
|
22861
23227
|
if (drawCandles && !candleDrawn) {
|
|
22862
23228
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -22865,6 +23231,12 @@ void main() {
|
|
|
22865
23231
|
}
|
|
22866
23232
|
drawSlicesUpTo(Infinity);
|
|
22867
23233
|
b.alpha = this.modelAlpha;
|
|
23234
|
+
if (isPrice) {
|
|
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
|
+
}
|
|
22868
23240
|
for (const m of models) {
|
|
22869
23241
|
const mp = effPane(m);
|
|
22870
23242
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -22911,7 +23283,7 @@ void main() {
|
|
|
22911
23283
|
for (const pane of scene.orderedPanes()) {
|
|
22912
23284
|
const b = this.glowBatch;
|
|
22913
23285
|
b.reset();
|
|
22914
|
-
this.emitGlowSources(b, scene
|
|
23286
|
+
this.emitGlowSources(b, scene, pane, coords, i0, i1);
|
|
22915
23287
|
if (b.vertexCount === 0) continue;
|
|
22916
23288
|
const topH = Math.round(pane.bounds.top * dpr * 0.5);
|
|
22917
23289
|
const botH = Math.round((pane.bounds.top + pane.bounds.height) * dpr * 0.5);
|
|
@@ -22980,15 +23352,23 @@ void main() {
|
|
|
22980
23352
|
gl.bindVertexArray(null);
|
|
22981
23353
|
return true;
|
|
22982
23354
|
}
|
|
22983
|
-
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
22984
|
-
|
|
22985
|
-
|
|
22986
|
-
|
|
22987
|
-
|
|
22988
|
-
|
|
22989
|
-
|
|
22990
|
-
|
|
22991
|
-
|
|
23355
|
+
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
23356
|
+
* Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
|
|
23357
|
+
emitGlowSources(b, scene, pane, coords, i0, i1) {
|
|
23358
|
+
const emitOne = (s, off) => {
|
|
23359
|
+
if (!isLineLikeSeries(s) || s.visible === false) return;
|
|
23360
|
+
if (s.kind === "histogram" || s.kind === "columns") return;
|
|
23361
|
+
if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
|
|
23362
|
+
else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
|
|
23363
|
+
};
|
|
23364
|
+
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
23365
|
+
const off = scene.offsetOf(m.id);
|
|
23366
|
+
for (const s of m.series) if (s.overlay !== true) emitOne(s, off);
|
|
23367
|
+
}
|
|
23368
|
+
if (pane.kind === "price") {
|
|
23369
|
+
for (const m of scene.indicators.values()) {
|
|
23370
|
+
const off = scene.offsetOf(m.id);
|
|
23371
|
+
for (const s of m.series) if (s.overlay === true) emitOne(s, off);
|
|
22992
23372
|
}
|
|
22993
23373
|
}
|
|
22994
23374
|
}
|
|
@@ -23271,9 +23651,9 @@ void main() {
|
|
|
23271
23651
|
b.alpha = this.candleBodyAlpha;
|
|
23272
23652
|
b.rect(g.bodyX, bodyTop, g.bodyW, bodyH, c);
|
|
23273
23653
|
}
|
|
23274
|
-
if (cs.borderVisible ||
|
|
23654
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
23275
23655
|
b.alpha = this.candleStructureAlpha;
|
|
23276
|
-
const bord = cs.borderVisible
|
|
23656
|
+
const bord = cs.borderVisible ? parseColor((isUp ? cs.borderUpColor : cs.borderDownColor) ?? bodyColorStr) : c;
|
|
23277
23657
|
const bw = Math.max(0, g.bodyW - 1);
|
|
23278
23658
|
const bh = Math.max(0, bodyH - 1);
|
|
23279
23659
|
b.rectStroke(g.bodyX + 0.5, bodyTop + 0.5, bw, bh, 1, bord);
|
|
@@ -24825,8 +25205,14 @@ void main() {
|
|
|
24825
25205
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
24826
25206
|
};
|
|
24827
25207
|
ctx.globalAlpha = this.modelAlpha;
|
|
24828
|
-
for (const m of models) for (const bg of m.backgrounds) this.drawBackground(ctx, bg, pane, coords);
|
|
24829
|
-
for (const m of models) for (const f of m.fills) this.drawFill(ctx, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
25208
|
+
for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
|
|
25209
|
+
for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
25210
|
+
if (isPrice) {
|
|
25211
|
+
for (const m of scene.indicators.values()) {
|
|
25212
|
+
for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
|
|
25213
|
+
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
25214
|
+
}
|
|
25215
|
+
}
|
|
24830
25216
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
24831
25217
|
let si = 0;
|
|
24832
25218
|
const drawSlicesUpTo = (z) => {
|
|
@@ -24848,7 +25234,7 @@ 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 s of m.series) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
25237
|
+
for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
24852
25238
|
}
|
|
24853
25239
|
if (drawCandles && !candleDrawn) {
|
|
24854
25240
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -24856,6 +25242,13 @@ void main() {
|
|
|
24856
25242
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
24857
25243
|
}
|
|
24858
25244
|
drawSlicesUpTo(Infinity);
|
|
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 s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
|
|
25250
|
+
}
|
|
25251
|
+
}
|
|
24859
25252
|
ctx.globalAlpha = this.modelAlpha;
|
|
24860
25253
|
for (const m of models) {
|
|
24861
25254
|
const mp = effPane(m);
|
|
@@ -25127,9 +25520,9 @@ void main() {
|
|
|
25127
25520
|
ctx.fillStyle = color;
|
|
25128
25521
|
ctx.fillRect(g.bodyX, top, g.bodyW, bodyH);
|
|
25129
25522
|
}
|
|
25130
|
-
if (cs.borderVisible ||
|
|
25523
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
25131
25524
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
25132
|
-
ctx.strokeStyle = cs.borderVisible
|
|
25525
|
+
ctx.strokeStyle = cs.borderVisible ? (up ? cs.borderUpColor : cs.borderDownColor) ?? color : color;
|
|
25133
25526
|
ctx.lineWidth = 1;
|
|
25134
25527
|
const bw = Math.max(0, g.bodyW - 1);
|
|
25135
25528
|
const bh = Math.max(0, bodyH - 1);
|
|
@@ -25561,6 +25954,8 @@ void main() {
|
|
|
25561
25954
|
this.set = set;
|
|
25562
25955
|
/** measureText width cache, keyed by `${font} ${text}`, persists across frames. */
|
|
25563
25956
|
this.widthCache = /* @__PURE__ */ new Map();
|
|
25957
|
+
/** Tooltip hit-rects captured while drawing the CURRENT set (rebuilt per render). */
|
|
25958
|
+
this.tipRegions = [];
|
|
25564
25959
|
/**
|
|
25565
25960
|
* Index offset of the CURRENT set's model: its `xloc:'bar_index'` coordinates count
|
|
25566
25961
|
* from the model's anchor bar, so they shift by this to land on chart logical indices.
|
|
@@ -25581,8 +25976,13 @@ void main() {
|
|
|
25581
25976
|
const s = this.set;
|
|
25582
25977
|
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
25583
25978
|
}
|
|
25979
|
+
/** Tooltip hit-rects of the labels drawn by the LAST `render` call (same coords as `ctx`). */
|
|
25980
|
+
labelTipRegions() {
|
|
25981
|
+
return this.tipRegions;
|
|
25982
|
+
}
|
|
25584
25983
|
/** Draw the whole set into `ctx` using the supplied coordinate closures. */
|
|
25585
25984
|
render(ctx, W, H, xOf, yOf) {
|
|
25985
|
+
this.tipRegions = [];
|
|
25586
25986
|
if (this.isEmpty()) return;
|
|
25587
25987
|
ctx.save();
|
|
25588
25988
|
this.drawLinefills(ctx, W, H, xOf, yOf);
|
|
@@ -25930,13 +26330,35 @@ void main() {
|
|
|
25930
26330
|
if (this.isPointShape(lb.style)) {
|
|
25931
26331
|
if (!lb.noFill) this.drawLabelShape(ctx, lb.style, px, py, fontPx, color);
|
|
25932
26332
|
if (lb.text) this.drawLabelText(ctx, lb, px, py + fontPx, fontPx);
|
|
25933
|
-
|
|
25934
|
-
|
|
26333
|
+
if (lb.tooltip) {
|
|
26334
|
+
const r = Math.max(4, fontPx * 0.6) + 3;
|
|
26335
|
+
this.tipRegions.push({ left: px - r, top: py - r, right: px + r, bottom: py + r, text: lb.tooltip });
|
|
26336
|
+
}
|
|
26337
|
+
} else if (lb.style === "none" || lb.style === "text_outline") {
|
|
26338
|
+
if (lb.text) {
|
|
26339
|
+
this.drawLabelText(ctx, lb, px, py, fontPx, lb.style === "text_outline");
|
|
26340
|
+
if (lb.tooltip) this.tipRegions.push(this.textRegion(ctx, lb, px, py, fontPx, lb.tooltip));
|
|
26341
|
+
}
|
|
25935
26342
|
} else {
|
|
25936
|
-
this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
26343
|
+
const r = this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
26344
|
+
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
26345
|
}
|
|
25938
26346
|
}
|
|
25939
26347
|
}
|
|
26348
|
+
/** Canvas font for a label's text — family + Pine `text_formatting` (bold/italic). */
|
|
26349
|
+
labelFont(lb, fontPx) {
|
|
26350
|
+
const family = lb.fontFamily === "monospace" ? "monospace" : this.deps.theme.fontFamily || "sans-serif";
|
|
26351
|
+
return `${lb.italic ? "italic " : ""}${lb.bold ? "bold " : ""}${fontPx}px ${family}`;
|
|
26352
|
+
}
|
|
26353
|
+
/** Hover rect of a text-only label (style none/text_outline/noFill), centered like drawLabelText. */
|
|
26354
|
+
textRegion(ctx, lb, cx, cy, fontPx, text) {
|
|
26355
|
+
const font = this.labelFont(lb, fontPx);
|
|
26356
|
+
const lines = (lb.text ?? "").split("\n");
|
|
26357
|
+
const w = Math.max(1, ...lines.map((l) => this.measure(ctx, font, l)));
|
|
26358
|
+
const h = fontPx * 1.25 * lines.length;
|
|
26359
|
+
const left = lb.textAlign === "left" ? cx : lb.textAlign === "right" ? cx - w : cx - w / 2;
|
|
26360
|
+
return { left, top: cy - h / 2, right: left + w, bottom: cy + h / 2, text };
|
|
26361
|
+
}
|
|
25940
26362
|
isPointShape(style) {
|
|
25941
26363
|
switch (style) {
|
|
25942
26364
|
case "circle":
|
|
@@ -25955,8 +26377,7 @@ void main() {
|
|
|
25955
26377
|
}
|
|
25956
26378
|
}
|
|
25957
26379
|
drawLabelText(ctx, lb, cx, cy, fontPx, outline = false) {
|
|
25958
|
-
|
|
25959
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
26380
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
25960
26381
|
ctx.textAlign = lb.textAlign === "left" ? "left" : lb.textAlign === "right" ? "right" : "center";
|
|
25961
26382
|
ctx.textBaseline = "middle";
|
|
25962
26383
|
const lines = lb.text.split("\n");
|
|
@@ -26036,9 +26457,9 @@ void main() {
|
|
|
26036
26457
|
ctx.fill();
|
|
26037
26458
|
}
|
|
26038
26459
|
}
|
|
26460
|
+
/** Draw a bubble label; returns the bubble body rect (for tooltip hit-testing). */
|
|
26039
26461
|
drawBubble(ctx, lb, px, py, fontPx, color) {
|
|
26040
|
-
|
|
26041
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
26462
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
26042
26463
|
const lines = (lb.text ?? "").split("\n");
|
|
26043
26464
|
const padX = 6;
|
|
26044
26465
|
const padY = 4;
|
|
@@ -26096,38 +26517,51 @@ void main() {
|
|
|
26096
26517
|
by = py - h - ptr;
|
|
26097
26518
|
pointer = "down";
|
|
26098
26519
|
}
|
|
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();
|
|
26520
|
+
if (!lb.noFill) {
|
|
26521
|
+
ctx.fillStyle = color;
|
|
26522
|
+
this.roundRect(ctx, bx, by, w, h, 4);
|
|
26122
26523
|
ctx.fill();
|
|
26524
|
+
if (pointer !== "none") {
|
|
26525
|
+
ctx.beginPath();
|
|
26526
|
+
if (pointer === "down") {
|
|
26527
|
+
ctx.moveTo(px - ptr, by + h);
|
|
26528
|
+
ctx.lineTo(px + ptr, by + h);
|
|
26529
|
+
ctx.lineTo(px, py);
|
|
26530
|
+
} else if (pointer === "up") {
|
|
26531
|
+
ctx.moveTo(px - ptr, by);
|
|
26532
|
+
ctx.lineTo(px + ptr, by);
|
|
26533
|
+
ctx.lineTo(px, py);
|
|
26534
|
+
} else if (pointer === "left") {
|
|
26535
|
+
ctx.moveTo(bx, py - ptr);
|
|
26536
|
+
ctx.lineTo(bx, py + ptr);
|
|
26537
|
+
ctx.lineTo(px, py);
|
|
26538
|
+
} else {
|
|
26539
|
+
ctx.moveTo(bx + w, py - ptr);
|
|
26540
|
+
ctx.lineTo(bx + w, py + ptr);
|
|
26541
|
+
ctx.lineTo(px, py);
|
|
26542
|
+
}
|
|
26543
|
+
ctx.closePath();
|
|
26544
|
+
ctx.fill();
|
|
26545
|
+
}
|
|
26123
26546
|
}
|
|
26124
26547
|
if (lb.text) {
|
|
26125
|
-
ctx.fillStyle = lb.textColor ?? contrastColor(color);
|
|
26126
|
-
ctx.textAlign = "center";
|
|
26548
|
+
ctx.fillStyle = lb.textColor ?? (lb.noFill ? this.deps.theme.textColor : contrastColor(color));
|
|
26127
26549
|
ctx.textBaseline = "middle";
|
|
26550
|
+
let tx;
|
|
26551
|
+
if (lb.textAlign === "left") {
|
|
26552
|
+
ctx.textAlign = "left";
|
|
26553
|
+
tx = bx + padX;
|
|
26554
|
+
} else if (lb.textAlign === "right") {
|
|
26555
|
+
ctx.textAlign = "right";
|
|
26556
|
+
tx = bx + w - padX;
|
|
26557
|
+
} else {
|
|
26558
|
+
ctx.textAlign = "center";
|
|
26559
|
+
tx = bx + w / 2;
|
|
26560
|
+
}
|
|
26128
26561
|
const startY = by + padY + lineH / 2;
|
|
26129
|
-
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i],
|
|
26562
|
+
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i], tx, startY + i * lineH);
|
|
26130
26563
|
}
|
|
26564
|
+
return { x: bx, y: by, w, h };
|
|
26131
26565
|
}
|
|
26132
26566
|
roundRect(ctx, x, y, w, h, r) {
|
|
26133
26567
|
const rr = Math.min(r, w / 2, h / 2);
|
|
@@ -26365,6 +26799,8 @@ void main() {
|
|
|
26365
26799
|
this.axisTextColor = DARK_THEME.textColor;
|
|
26366
26800
|
// Shared Pine-drawing renderer (line/box/label/polyline/linefill); widthCache persists.
|
|
26367
26801
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
26802
|
+
// Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per render).
|
|
26803
|
+
this.labelTips = [];
|
|
26368
26804
|
}
|
|
26369
26805
|
mount(canvas) {
|
|
26370
26806
|
this.canvas = canvas;
|
|
@@ -26406,6 +26842,7 @@ void main() {
|
|
|
26406
26842
|
const dataW = coords.width;
|
|
26407
26843
|
const dataH = coords.height;
|
|
26408
26844
|
this.axisTextColor = surface?.textColor ?? theme.textColor;
|
|
26845
|
+
this.labelTips = [];
|
|
26409
26846
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
26410
26847
|
ctx.clearRect(0, 0, fullW, fullH);
|
|
26411
26848
|
if (surface && (fullW > dataW || fullH > dataH)) {
|
|
@@ -26515,6 +26952,17 @@ void main() {
|
|
|
26515
26952
|
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
26516
26953
|
);
|
|
26517
26954
|
ctx.restore();
|
|
26955
|
+
for (const r of this.drawScene.labelTipRegions()) {
|
|
26956
|
+
this.labelTips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
26957
|
+
}
|
|
26958
|
+
}
|
|
26959
|
+
/** Tooltip of the topmost label under a plot-space point, or null. Fed by the last render. */
|
|
26960
|
+
labelTooltipAt(x, y) {
|
|
26961
|
+
for (let i = this.labelTips.length - 1; i >= 0; i -= 1) {
|
|
26962
|
+
const r = this.labelTips[i];
|
|
26963
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
26964
|
+
}
|
|
26965
|
+
return null;
|
|
26518
26966
|
}
|
|
26519
26967
|
// ── axes ──
|
|
26520
26968
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
@@ -26731,6 +27179,68 @@ void main() {
|
|
|
26731
27179
|
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
|
|
26732
27180
|
}
|
|
26733
27181
|
|
|
27182
|
+
// src/renderers/native/chrome/LabelTooltip.ts
|
|
27183
|
+
var HOVER_DELAY_MS = 350;
|
|
27184
|
+
var LabelTooltip = class {
|
|
27185
|
+
constructor(plot, deps) {
|
|
27186
|
+
this.plot = plot;
|
|
27187
|
+
this.deps = deps;
|
|
27188
|
+
this.tip = null;
|
|
27189
|
+
this.timer = null;
|
|
27190
|
+
/** Text of the currently open OR armed tip — dedupes moves inside one label. */
|
|
27191
|
+
this.current = null;
|
|
27192
|
+
this.onMove = (e) => {
|
|
27193
|
+
if (e.pointerType !== "mouse") return;
|
|
27194
|
+
const rect = this.plot.getBoundingClientRect();
|
|
27195
|
+
const x = e.clientX - rect.left;
|
|
27196
|
+
const y = e.clientY - rect.top;
|
|
27197
|
+
const text = this.deps.lookup(x, y);
|
|
27198
|
+
if (!text) {
|
|
27199
|
+
this.clear();
|
|
27200
|
+
return;
|
|
27201
|
+
}
|
|
27202
|
+
if (text === this.current) return;
|
|
27203
|
+
this.clear();
|
|
27204
|
+
this.current = text;
|
|
27205
|
+
this.timer = window.setTimeout(() => this.show(text, x, y), HOVER_DELAY_MS);
|
|
27206
|
+
};
|
|
27207
|
+
this.onLeave = () => {
|
|
27208
|
+
this.clear();
|
|
27209
|
+
};
|
|
27210
|
+
plot.addEventListener("pointermove", this.onMove);
|
|
27211
|
+
plot.addEventListener("pointerleave", this.onLeave);
|
|
27212
|
+
plot.addEventListener("pointerdown", this.onLeave);
|
|
27213
|
+
}
|
|
27214
|
+
destroy() {
|
|
27215
|
+
this.plot.removeEventListener("pointermove", this.onMove);
|
|
27216
|
+
this.plot.removeEventListener("pointerleave", this.onLeave);
|
|
27217
|
+
this.plot.removeEventListener("pointerdown", this.onLeave);
|
|
27218
|
+
this.clear();
|
|
27219
|
+
}
|
|
27220
|
+
show(text, x, y) {
|
|
27221
|
+
const doc = this.plot.ownerDocument;
|
|
27222
|
+
const tip = doc.createElement("div");
|
|
27223
|
+
tip.textContent = text;
|
|
27224
|
+
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;";
|
|
27225
|
+
applyChromeTokens(tip, this.deps.theme());
|
|
27226
|
+
this.plot.appendChild(tip);
|
|
27227
|
+
const pw = this.plot.clientWidth;
|
|
27228
|
+
const ph = this.plot.clientHeight;
|
|
27229
|
+
tip.style.left = `${Math.max(0, Math.min(x + 12, pw - tip.offsetWidth - 4))}px`;
|
|
27230
|
+
tip.style.top = `${Math.max(0, Math.min(y + 16, ph - tip.offsetHeight - 4))}px`;
|
|
27231
|
+
this.tip = tip;
|
|
27232
|
+
}
|
|
27233
|
+
clear() {
|
|
27234
|
+
if (this.timer !== null) {
|
|
27235
|
+
clearTimeout(this.timer);
|
|
27236
|
+
this.timer = null;
|
|
27237
|
+
}
|
|
27238
|
+
this.tip?.remove();
|
|
27239
|
+
this.tip = null;
|
|
27240
|
+
this.current = null;
|
|
27241
|
+
}
|
|
27242
|
+
};
|
|
27243
|
+
|
|
26734
27244
|
// src/renderers/native/chrome/CrosshairRenderer.ts
|
|
26735
27245
|
var CrosshairRenderer = class {
|
|
26736
27246
|
constructor() {
|
|
@@ -32522,19 +33032,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32522
33032
|
for (const model of models) {
|
|
32523
33033
|
const off = offsetOf(model.id);
|
|
32524
33034
|
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
|
-
}
|
|
33035
|
+
if (s.overlay === true) continue;
|
|
33036
|
+
considerSeries(s, i0, i1, off, consider);
|
|
32538
33037
|
}
|
|
32539
33038
|
for (const pl of model.priceLines) consider(pl.price);
|
|
32540
33039
|
}
|
|
@@ -32556,6 +33055,36 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32556
33055
|
const span = max2 - min2;
|
|
32557
33056
|
return { min: min2 - span * MARGIN_BOTTOM, max: max2 + span * MARGIN_TOP };
|
|
32558
33057
|
}
|
|
33058
|
+
function considerSeries(s, i0, i1, off, consider) {
|
|
33059
|
+
if (s.kind === "candle" || s.kind === "bar") {
|
|
33060
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
33061
|
+
const b = s.bars[i - off];
|
|
33062
|
+
if (b) {
|
|
33063
|
+
consider(b.high);
|
|
33064
|
+
consider(b.low);
|
|
33065
|
+
}
|
|
33066
|
+
}
|
|
33067
|
+
} else if (isLineLikeSeries(s)) {
|
|
33068
|
+
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
33069
|
+
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
33070
|
+
else if (s.style?.base != null) consider(s.style.base);
|
|
33071
|
+
}
|
|
33072
|
+
}
|
|
33073
|
+
function overlaySeriesRange(models, i0, i1, offsetOf = () => 0) {
|
|
33074
|
+
let min2 = Infinity;
|
|
33075
|
+
let max2 = -Infinity;
|
|
33076
|
+
const consider = (v) => {
|
|
33077
|
+
if (v != null && Number.isFinite(v)) {
|
|
33078
|
+
if (v < min2) min2 = v;
|
|
33079
|
+
if (v > max2) max2 = v;
|
|
33080
|
+
}
|
|
33081
|
+
};
|
|
33082
|
+
for (const model of models) {
|
|
33083
|
+
const off = offsetOf(model.id);
|
|
33084
|
+
for (const s of model.series) if (s.overlay === true) considerSeries(s, i0, i1, off, consider);
|
|
33085
|
+
}
|
|
33086
|
+
return min2 === Infinity ? null : { min: min2, max: max2 };
|
|
33087
|
+
}
|
|
32559
33088
|
function expandScaleByPixels(scale, heightPx, abovePx, belowPx) {
|
|
32560
33089
|
if (abovePx <= 0 && belowPx <= 0) return scale;
|
|
32561
33090
|
const content = heightPx - abovePx - belowPx;
|
|
@@ -33338,6 +33867,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33338
33867
|
this.glowAmount = 0;
|
|
33339
33868
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
33340
33869
|
this.chrome = new ChromeRenderer();
|
|
33870
|
+
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
33871
|
+
this.labelTooltip = null;
|
|
33341
33872
|
this.crosshairLayer = new CrosshairRenderer();
|
|
33342
33873
|
/** 1 Hz repaint pump so the price-axis countdown-to-bar-close ticks; null when off. */
|
|
33343
33874
|
this.countdownTimer = null;
|
|
@@ -33348,6 +33879,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33348
33879
|
this.indicatorValuesOn = true;
|
|
33349
33880
|
/** Host-contributed legend actions — held here so a rebuild of the legend re-wires them. */
|
|
33350
33881
|
this.legendActionsProvider = null;
|
|
33882
|
+
/** Host-contributed legend callouts — held here so a rebuild of the legend re-wires them. */
|
|
33883
|
+
this.legendCalloutsProvider = null;
|
|
33351
33884
|
/** Host override of the legend's fold toggle — held here so a remount re-applies it. */
|
|
33352
33885
|
this.legendOverviewAction = null;
|
|
33353
33886
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -34456,6 +34989,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34456
34989
|
this.plot.appendChild(this.scrollButton);
|
|
34457
34990
|
this.plot.addEventListener("pointermove", this.onScrollProximityMove);
|
|
34458
34991
|
this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
|
|
34992
|
+
this.labelTooltip = new LabelTooltip(this.plot, {
|
|
34993
|
+
theme: () => this.chromeTheme(),
|
|
34994
|
+
lookup: (x, y) => this.chrome.labelTooltipAt(x, y)
|
|
34995
|
+
});
|
|
34459
34996
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
34460
34997
|
projector: () => this.drawingProjector(),
|
|
34461
34998
|
dpr: () => this.coords.dpr,
|
|
@@ -34495,9 +35032,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34495
35032
|
this.inputsUI.setDialogHost(this.dialogHost);
|
|
34496
35033
|
this.inputsUI.setSymbolPicker(this.symbolPicker);
|
|
34497
35034
|
this.inputsUI.setLegendActions(this.legendActionsProvider);
|
|
35035
|
+
this.inputsUI.setLegendCallouts(this.legendCalloutsProvider);
|
|
34498
35036
|
this.inputsUI.setLegendOverviewAction(this.legendOverviewAction);
|
|
34499
35037
|
this.inputsUI.setOnChange((c) => {
|
|
34500
|
-
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value });
|
|
35038
|
+
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value, ...c.kind ? { kind: c.kind } : {} });
|
|
34501
35039
|
});
|
|
34502
35040
|
this.inputsUI.setOnRemove((id) => {
|
|
34503
35041
|
for (const cb of this.removeIndicatorCbs) cb(id);
|
|
@@ -34687,6 +35225,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34687
35225
|
this.settingsButton = null;
|
|
34688
35226
|
this.plot?.removeEventListener("pointermove", this.onScrollProximityMove);
|
|
34689
35227
|
this.plot?.removeEventListener("pointerleave", this.onScrollProximityLeave);
|
|
35228
|
+
this.labelTooltip?.destroy();
|
|
35229
|
+
this.labelTooltip = null;
|
|
34690
35230
|
this.scrollButton?.remove();
|
|
34691
35231
|
this.scrollButton = null;
|
|
34692
35232
|
for (const l of this.extLayers) l.instance.destroy?.();
|
|
@@ -34927,7 +35467,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34927
35467
|
else this.scene.assignIndicatorZ(model.id);
|
|
34928
35468
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
34929
35469
|
native: !!model.native,
|
|
34930
|
-
...model.
|
|
35470
|
+
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
34931
35471
|
});
|
|
34932
35472
|
this.syncTables(model);
|
|
34933
35473
|
if (model.native?.type === "volume") {
|
|
@@ -34978,8 +35518,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34978
35518
|
this.paneControls?.refresh();
|
|
34979
35519
|
this.scheduler.invalidate(4 /* Full */);
|
|
34980
35520
|
}
|
|
34981
|
-
setIndicatorInputs(handle, values) {
|
|
34982
|
-
this.inputsUI.setValues(handle.id, values);
|
|
35521
|
+
setIndicatorInputs(handle, values, props) {
|
|
35522
|
+
this.inputsUI.setValues(handle.id, values, props);
|
|
34983
35523
|
}
|
|
34984
35524
|
setSymbolPicker(picker) {
|
|
34985
35525
|
this.symbolPicker = picker;
|
|
@@ -34989,6 +35529,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34989
35529
|
this.legendActionsProvider = provider;
|
|
34990
35530
|
this.inputsUI?.setLegendActions(provider);
|
|
34991
35531
|
}
|
|
35532
|
+
setLegendCallouts(provider) {
|
|
35533
|
+
this.legendCalloutsProvider = provider;
|
|
35534
|
+
this.inputsUI?.setLegendCallouts(provider);
|
|
35535
|
+
}
|
|
34992
35536
|
setLegendOverviewAction(action) {
|
|
34993
35537
|
this.legendOverviewAction = action;
|
|
34994
35538
|
this.inputsUI?.setLegendOverviewAction(action);
|
|
@@ -35969,7 +36513,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35969
36513
|
}
|
|
35970
36514
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
35971
36515
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
35972
|
-
|
|
36516
|
+
let dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
36517
|
+
if (pane === pricePane) {
|
|
36518
|
+
const or2 = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
|
|
36519
|
+
if (or2) dr = dr ? { min: Math.min(dr.min, or2.min), max: Math.max(dr.max, or2.max) } : or2;
|
|
36520
|
+
}
|
|
35973
36521
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
35974
36522
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
35975
36523
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -36243,6 +36791,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36243
36791
|
for (const m of models) {
|
|
36244
36792
|
const off = this.scene.offsetOf(m.id);
|
|
36245
36793
|
for (const s of m.series) {
|
|
36794
|
+
if (s.overlay === true) continue;
|
|
36246
36795
|
if (isLineLikeSeries(s)) {
|
|
36247
36796
|
const v = s.points[i0 - off]?.value;
|
|
36248
36797
|
if (v != null && Number.isFinite(v)) return v;
|
|
@@ -37148,6 +37697,19 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
37148
37697
|
function legendActions() {
|
|
37149
37698
|
return [...legendRegistry.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
37150
37699
|
}
|
|
37700
|
+
var calloutRegistry = /* @__PURE__ */ new Map();
|
|
37701
|
+
function registerLegendCallout(desc) {
|
|
37702
|
+
calloutRegistry.set(desc.id, desc);
|
|
37703
|
+
return () => {
|
|
37704
|
+
if (calloutRegistry.get(desc.id) === desc) calloutRegistry.delete(desc.id);
|
|
37705
|
+
};
|
|
37706
|
+
}
|
|
37707
|
+
function unregisterLegendCallout(id) {
|
|
37708
|
+
calloutRegistry.delete(id);
|
|
37709
|
+
}
|
|
37710
|
+
function legendCallouts() {
|
|
37711
|
+
return [...calloutRegistry.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
37712
|
+
}
|
|
37151
37713
|
var stateHandlers = /* @__PURE__ */ new Map();
|
|
37152
37714
|
function registerStatePersistence(handler) {
|
|
37153
37715
|
stateHandlers.set(handler.key, handler);
|
|
@@ -38491,6 +39053,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38491
39053
|
exports.iconMarkup = iconMarkup;
|
|
38492
39054
|
exports.inputVisible = inputVisible;
|
|
38493
39055
|
exports.legendActions = legendActions;
|
|
39056
|
+
exports.legendCallouts = legendCallouts;
|
|
38494
39057
|
exports.nativeIndicatorDescriptors = nativeIndicatorDescriptors;
|
|
38495
39058
|
exports.nativeIndicatorTypes = nativeIndicatorTypes;
|
|
38496
39059
|
exports.normalizeSettingsRow = normalizeSettingsRow;
|
|
@@ -38499,6 +39062,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38499
39062
|
exports.registerDrawingType = registerDrawingType;
|
|
38500
39063
|
exports.registerIcon = registerIcon;
|
|
38501
39064
|
exports.registerLegendAction = registerLegendAction;
|
|
39065
|
+
exports.registerLegendCallout = registerLegendCallout;
|
|
38502
39066
|
exports.registerNativeIndicator = registerNativeIndicator;
|
|
38503
39067
|
exports.registerRendererDefaults = registerRendererDefaults;
|
|
38504
39068
|
exports.registerRendererLayer = registerRendererLayer;
|
|
@@ -38524,6 +39088,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38524
39088
|
exports.unregisterChartType = unregisterChartType;
|
|
38525
39089
|
exports.unregisterDefaultEngine = unregisterDefaultEngine;
|
|
38526
39090
|
exports.unregisterLegendAction = unregisterLegendAction;
|
|
39091
|
+
exports.unregisterLegendCallout = unregisterLegendCallout;
|
|
38527
39092
|
exports.unregisterNativeIndicator = unregisterNativeIndicator;
|
|
38528
39093
|
exports.unregisterRendererDefaults = unregisterRendererDefaults;
|
|
38529
39094
|
exports.unregisterRendererLayer = unregisterRendererLayer;
|