@luxalgo/vela 0.6.7 → 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-6WFKTAT4.js → chunk-6WDDVMBJ.js} +548 -147
- package/dist/{chunk-L3I2CCYO.js → chunk-EQCHJZOT.js} +49 -2
- package/dist/{chunk-OICPLNU7.js → chunk-FCVIG7JG.js} +188 -2
- package/dist/{chunk-WVP4XV5A.js → chunk-STHSKXOR.js} +407 -159
- package/dist/{chunk-PN5KWFZ4.js → chunk-WFSZBX3R.js} +2 -2
- package/dist/{contributions-Z12BrWCy.d.ts → contributions-C1U2Krwg.d.cts} +112 -7
- package/dist/{contributions-Hv4_cOJo.d.cts → contributions-D7PVZO2i.d.ts} +112 -7
- package/dist/index.cjs +755 -152
- 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-9koBjc5H.d.ts → plugin-7bkF32Rk.d.ts} +3 -3
- package/dist/{plugin-BXhDHTYn.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/{bottombar-Br4rzx_R.d.ts → statusline-DOPiT6I6.d.cts} +91 -6
- package/dist/{bottombar-Div5zibZ.d.cts → statusline-zdF4eZLr.d.ts} +91 -6
- 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 +755 -152
- package/dist/vela.global.min.js +93 -44
- package/dist/widget.cjs +1144 -271
- package/dist/widget.d.cts +12 -85
- package/dist/widget.d.ts +12 -85
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +1144 -271
- package/dist/workspace.d.cts +65 -11
- package/dist/workspace.d.ts +65 -11
- 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,
|
|
@@ -20016,12 +20259,16 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20016
20259
|
closeOnEscape: false,
|
|
20017
20260
|
footer: (foot) => {
|
|
20018
20261
|
foot.append(
|
|
20262
|
+
this.resetAction(),
|
|
20019
20263
|
this.dialogButton("Cancel", false, () => this.revertAndClose()),
|
|
20020
20264
|
this.dialogButton("Ok", true, () => this.close())
|
|
20021
20265
|
);
|
|
20022
20266
|
},
|
|
20267
|
+
// Stale-guard: destroying a dialog fires its machine's onOpenChange(false)
|
|
20268
|
+
// asynchronously — after a reset rebuild that notification must not close
|
|
20269
|
+
// the replacement dialog.
|
|
20023
20270
|
onOpenChange: (open2) => {
|
|
20024
|
-
if (!open2) this.close();
|
|
20271
|
+
if (!open2 && this.uiDialog === ui) this.close();
|
|
20025
20272
|
}
|
|
20026
20273
|
});
|
|
20027
20274
|
applyChromeTokens(ui.panel, t);
|
|
@@ -20032,6 +20279,16 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20032
20279
|
text: () => "Close"
|
|
20033
20280
|
}));
|
|
20034
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
|
+
}
|
|
20035
20292
|
const tabs = document.createElement("div");
|
|
20036
20293
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
20037
20294
|
const tabEls = [];
|
|
@@ -20134,9 +20391,10 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20134
20391
|
this.openId = null;
|
|
20135
20392
|
this.row = null;
|
|
20136
20393
|
this.snapshot = null;
|
|
20394
|
+
this.propSnapshot = null;
|
|
20137
20395
|
ui?.destroy();
|
|
20138
20396
|
}
|
|
20139
|
-
/** 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. */
|
|
20140
20398
|
revertAndClose() {
|
|
20141
20399
|
const row = this.row;
|
|
20142
20400
|
const snap = this.snapshot;
|
|
@@ -20150,12 +20408,55 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20150
20408
|
}
|
|
20151
20409
|
}
|
|
20152
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
|
+
}
|
|
20153
20422
|
this.close();
|
|
20154
20423
|
}
|
|
20424
|
+
/** The footer's reset button — same chip as Cancel, pinned to the LEFT edge
|
|
20425
|
+
* (`margin-right:auto` against the footer's flex-end keeps Cancel/Ok right). */
|
|
20426
|
+
resetAction() {
|
|
20427
|
+
const b = this.dialogButton("Reset defaults", false, () => this.resetToDefaults());
|
|
20428
|
+
b.style.marginRight = "auto";
|
|
20429
|
+
return b;
|
|
20430
|
+
}
|
|
20431
|
+
/** Restore every input to its declared default (re-running the indicator), then
|
|
20432
|
+
* re-open the form so each control re-reads the restored values — the same
|
|
20433
|
+
* rebuild-after-reset move as the chart-settings dialog. The open-time snapshot
|
|
20434
|
+
* survives the rebuild, so Cancel after a reset still reverts the whole session. */
|
|
20435
|
+
resetToDefaults() {
|
|
20436
|
+
const row = this.row;
|
|
20437
|
+
if (!row) return;
|
|
20438
|
+
const snap = this.snapshot;
|
|
20439
|
+
const propSnap = this.propSnapshot;
|
|
20440
|
+
for (const inp of row.inputs) {
|
|
20441
|
+
if (row.values[inp.key] !== inp.defval) {
|
|
20442
|
+
row.values[inp.key] = inp.defval;
|
|
20443
|
+
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value: inp.defval });
|
|
20444
|
+
}
|
|
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
|
+
}
|
|
20452
|
+
this.open(row);
|
|
20453
|
+
if (snap) this.snapshot = snap;
|
|
20454
|
+
if (propSnap) this.propSnapshot = propSnap;
|
|
20455
|
+
}
|
|
20155
20456
|
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
20156
|
-
commit(row,
|
|
20157
|
-
row.
|
|
20158
|
-
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" } : {} });
|
|
20159
20460
|
this.refreshVisibility?.();
|
|
20160
20461
|
}
|
|
20161
20462
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
@@ -20182,7 +20483,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20182
20483
|
const id = idOf(lead);
|
|
20183
20484
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
20184
20485
|
if (lead.type === "bool") {
|
|
20185
|
-
const current = Boolean(row
|
|
20486
|
+
const current = Boolean(bagOf(row, lead)[lead.key] ?? lead.defval);
|
|
20186
20487
|
return append(fieldRow({
|
|
20187
20488
|
label: nameOf(lead),
|
|
20188
20489
|
id,
|
|
@@ -20191,7 +20492,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20191
20492
|
toggle: {
|
|
20192
20493
|
id,
|
|
20193
20494
|
checked: current,
|
|
20194
|
-
onChange: (v) => this.commit(row, lead
|
|
20495
|
+
onChange: (v) => this.commit(row, lead, v)
|
|
20195
20496
|
}
|
|
20196
20497
|
}));
|
|
20197
20498
|
}
|
|
@@ -20216,8 +20517,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20216
20517
|
}
|
|
20217
20518
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
20218
20519
|
buildControl(row, inp, id) {
|
|
20219
|
-
const current = row
|
|
20220
|
-
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);
|
|
20221
20522
|
if (inp.type === "bool") {
|
|
20222
20523
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
20223
20524
|
}
|
|
@@ -20228,7 +20529,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
|
|
|
20228
20529
|
kind: "color",
|
|
20229
20530
|
id,
|
|
20230
20531
|
theme: this.host.theme(),
|
|
20231
|
-
get: () => String(row
|
|
20532
|
+
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
20232
20533
|
onChange: (v) => emit(v)
|
|
20233
20534
|
}).el;
|
|
20234
20535
|
}
|
|
@@ -20791,6 +21092,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
20791
21092
|
}
|
|
20792
21093
|
|
|
20793
21094
|
// src/renderers/shared/InputsUI.ts
|
|
21095
|
+
var LEGEND_AT_TOP_ATTR = "data-vela-pane-at-top";
|
|
20794
21096
|
var STATUS_KEYFRAMES = "@keyframes vela-ind-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.6)}}";
|
|
20795
21097
|
var LEGEND_ICON_PX2 = 16;
|
|
20796
21098
|
var LEGEND_CTL_PX = 18;
|
|
@@ -20829,10 +21131,14 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
20829
21131
|
this.moveApi = null;
|
|
20830
21132
|
/** Host-contributed legend actions, resolved PER ROW at render time (see setLegendActions). */
|
|
20831
21133
|
this.legendActions = null;
|
|
21134
|
+
/** Host-contributed callout bubbles, resolved PER ROW at render time (see setLegendCallouts). */
|
|
21135
|
+
this.legendCallouts = null;
|
|
20832
21136
|
/** Chrome-tooltip disposers, per row id — a tip open at removal must not outlive its row. */
|
|
20833
21137
|
this.rowTips = /* @__PURE__ */ new Map();
|
|
20834
21138
|
/** Same, for the contributed extras only (rebuilt independently by setLegendActions). */
|
|
20835
21139
|
this.extrasTips = /* @__PURE__ */ new Map();
|
|
21140
|
+
/** Same, for the contributed callouts only (rebuilt independently by setLegendCallouts). */
|
|
21141
|
+
this.calloutTips = /* @__PURE__ */ new Map();
|
|
20836
21142
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
20837
21143
|
this.moveMenu = null;
|
|
20838
21144
|
this.moveTargets = [];
|
|
@@ -20990,6 +21296,42 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
20990
21296
|
extrasEl.appendChild(btn2);
|
|
20991
21297
|
}
|
|
20992
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
|
+
}
|
|
20993
21335
|
/** Reposition the per-pane legend containers after a layout change. */
|
|
20994
21336
|
reposition() {
|
|
20995
21337
|
for (const [paneId, lg] of this.legends) this.positionLegend(lg, paneId);
|
|
@@ -21157,6 +21499,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21157
21499
|
}
|
|
21158
21500
|
positionLegend(lg, paneId) {
|
|
21159
21501
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
21502
|
+
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
21160
21503
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
21161
21504
|
const collapsed = this.paneCollapse.has(paneId);
|
|
21162
21505
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
@@ -21232,13 +21575,13 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21232
21575
|
}
|
|
21233
21576
|
/** Create or update an indicator's legend row (in the legend for its pane). */
|
|
21234
21577
|
upsert(id, title, inputs, values, paneId = "price", opts = {}) {
|
|
21235
|
-
const settingsTitle = opts.settingsTitle ?? title;
|
|
21236
21578
|
const existing = this.rows.get(id);
|
|
21237
21579
|
if (existing) {
|
|
21238
21580
|
existing.title = title;
|
|
21239
|
-
existing.settingsTitle = settingsTitle;
|
|
21240
21581
|
existing.inputs = inputs;
|
|
21241
21582
|
existing.values = { ...values };
|
|
21583
|
+
existing.props = opts.props ?? [];
|
|
21584
|
+
existing.propValues = { ...opts.propValues ?? {} };
|
|
21242
21585
|
existing.titleEl.textContent = title;
|
|
21243
21586
|
if (existing.paneId !== paneId) {
|
|
21244
21587
|
existing.paneId = paneId;
|
|
@@ -21290,6 +21633,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21290
21633
|
titleWrap.appendChild(beta);
|
|
21291
21634
|
}
|
|
21292
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);
|
|
21293
21639
|
el.appendChild(statusEl);
|
|
21294
21640
|
const valuesEl = document.createElement("span");
|
|
21295
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;`;
|
|
@@ -21306,13 +21652,13 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21306
21652
|
eye.className = "vela-ind-ctl";
|
|
21307
21653
|
eye.style.cssText = LEGEND_CTL_CSS;
|
|
21308
21654
|
eye.addEventListener("click", () => {
|
|
21309
|
-
const
|
|
21310
|
-
this.onToggleVisible?.(id, Boolean(
|
|
21655
|
+
const row2 = this.rows.get(id);
|
|
21656
|
+
this.onToggleVisible?.(id, Boolean(row2?.hidden));
|
|
21311
21657
|
});
|
|
21312
21658
|
controlsEl.appendChild(eye);
|
|
21313
21659
|
eyeEl = eye;
|
|
21314
21660
|
}
|
|
21315
|
-
if (inputs.length > 0) {
|
|
21661
|
+
if (inputs.length > 0 || (opts.props ?? []).length > 0) {
|
|
21316
21662
|
const gear = document.createElement("button");
|
|
21317
21663
|
gear.type = "button";
|
|
21318
21664
|
gear.setAttribute("aria-label", "Settings");
|
|
@@ -21352,7 +21698,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21352
21698
|
controlsEl.appendChild(close);
|
|
21353
21699
|
el.appendChild(controlsEl);
|
|
21354
21700
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
21355
|
-
|
|
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);
|
|
21356
21704
|
this.syncFoldToggle();
|
|
21357
21705
|
}
|
|
21358
21706
|
/** Place a row in its pane's legend — native rows PREPEND (pinned to the top), Pine rows append. */
|
|
@@ -21361,9 +21709,11 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21361
21709
|
else container.appendChild(el);
|
|
21362
21710
|
}
|
|
21363
21711
|
/** Reflect programmatic input changes (so a re-opened dialog shows current values). */
|
|
21364
|
-
setValues(id, values) {
|
|
21712
|
+
setValues(id, values, props) {
|
|
21365
21713
|
const row = this.rows.get(id);
|
|
21366
|
-
if (row)
|
|
21714
|
+
if (!row) return;
|
|
21715
|
+
row.values = { ...row.values, ...values };
|
|
21716
|
+
if (props) row.propValues = { ...row.propValues, ...props };
|
|
21367
21717
|
}
|
|
21368
21718
|
/**
|
|
21369
21719
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
@@ -21445,8 +21795,13 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21445
21795
|
syncRowActions(row) {
|
|
21446
21796
|
const open2 = row.highlighted;
|
|
21447
21797
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
21448
|
-
if (open2)
|
|
21449
|
-
|
|
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
|
+
}
|
|
21450
21805
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
21451
21806
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
21452
21807
|
if (child === row.extrasEl) {
|
|
@@ -21464,10 +21819,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21464
21819
|
}
|
|
21465
21820
|
remove(id) {
|
|
21466
21821
|
const row = this.rows.get(id);
|
|
21822
|
+
for (const bubble of row?.callouts ?? []) bubble.destroy();
|
|
21467
21823
|
row?.el.remove();
|
|
21468
21824
|
this.rows.delete(id);
|
|
21469
21825
|
this.disposeTips(this.rowTips, id);
|
|
21470
21826
|
this.disposeTips(this.extrasTips, id);
|
|
21827
|
+
this.disposeTips(this.calloutTips, id);
|
|
21471
21828
|
if (this.selectedId === id) this.selectedId = null;
|
|
21472
21829
|
if (this.inputsDialog.openId === id) this.inputsDialog.close();
|
|
21473
21830
|
this.syncFoldToggle();
|
|
@@ -21487,6 +21844,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21487
21844
|
this.rowMenu = null;
|
|
21488
21845
|
for (const id of [...this.rowTips.keys()]) this.disposeTips(this.rowTips, id);
|
|
21489
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();
|
|
21490
21849
|
for (const lg of this.legends.values()) lg.remove();
|
|
21491
21850
|
this.legends.clear();
|
|
21492
21851
|
this.rows.clear();
|
|
@@ -21841,6 +22200,32 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21841
22200
|
large: 16,
|
|
21842
22201
|
huge: 20
|
|
21843
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
|
+
}
|
|
21844
22229
|
var TableOverlay = class {
|
|
21845
22230
|
constructor(container, theme, paneBounds) {
|
|
21846
22231
|
this.container = container;
|
|
@@ -21861,7 +22246,9 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21861
22246
|
update(tables) {
|
|
21862
22247
|
this.lastTables = tables;
|
|
21863
22248
|
this.root.replaceChildren();
|
|
21864
|
-
for (const t of tables)
|
|
22249
|
+
for (const t of tables) {
|
|
22250
|
+
if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
|
|
22251
|
+
}
|
|
21865
22252
|
}
|
|
21866
22253
|
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
21867
22254
|
reposition() {
|
|
@@ -21876,39 +22263,36 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21876
22263
|
this.root.remove();
|
|
21877
22264
|
}
|
|
21878
22265
|
renderTable(t) {
|
|
22266
|
+
const b = this.paneBounds(t.paneId);
|
|
21879
22267
|
const wrap2 = document.createElement("div");
|
|
21880
22268
|
wrap2.style.position = "absolute";
|
|
21881
|
-
|
|
22269
|
+
if (t.frameColor && t.frameWidth > 0) wrap2.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
|
|
22270
|
+
this.anchor(wrap2, t.position, b);
|
|
21882
22271
|
const table = document.createElement("table");
|
|
21883
22272
|
Object.assign(table.style, {
|
|
21884
22273
|
borderCollapse: "collapse",
|
|
21885
22274
|
background: t.bgColor ?? "transparent",
|
|
21886
22275
|
fontFamily: this.theme.fontFamily || "sans-serif",
|
|
21887
|
-
// Frame as an inset shadow (not a `border`) so it stays independent of the
|
|
21888
|
-
// collapsed cell borders even when frame_width != border_width.
|
|
21889
|
-
boxShadow: t.frameColor && t.frameWidth > 0 ? `inset 0 0 0 ${t.frameWidth}px ${t.frameColor}` : "none",
|
|
21890
22276
|
border: "none",
|
|
21891
22277
|
tableLayout: "auto",
|
|
21892
22278
|
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
21893
22279
|
pointerEvents: "auto"
|
|
21894
22280
|
});
|
|
21895
|
-
const span
|
|
21896
|
-
const
|
|
21897
|
-
|
|
21898
|
-
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
21899
|
-
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
21900
|
-
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
21901
|
-
if (r !== m.startRow || c !== m.startCol) skip.add(`${r}:${c}`);
|
|
21902
|
-
}
|
|
21903
|
-
}
|
|
21904
|
-
}
|
|
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;
|
|
21905
22284
|
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
21906
22285
|
for (let r = 0; r < t.rows; r += 1) {
|
|
21907
22286
|
const tr = document.createElement("tr");
|
|
21908
22287
|
for (let c = 0; c < t.columns; c += 1) {
|
|
22288
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
21909
22289
|
const cell = t.cells[r]?.[c] ?? null;
|
|
21910
|
-
if (skip.has(`${r}:${c}`) || cell?.merged) continue;
|
|
21911
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
|
+
}
|
|
21912
22296
|
const sp = span.get(`${r}:${c}`);
|
|
21913
22297
|
if (sp) {
|
|
21914
22298
|
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
@@ -21917,18 +22301,22 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
|
|
|
21917
22301
|
Object.assign(td.style, {
|
|
21918
22302
|
border: cellBorder,
|
|
21919
22303
|
padding: "2px 6px",
|
|
21920
|
-
background: cell
|
|
21921
|
-
color: cell
|
|
21922
|
-
textAlign: cell
|
|
21923
|
-
verticalAlign: cell
|
|
21924
|
-
fontSize: `${
|
|
21925
|
-
fontFamily: cell
|
|
21926
|
-
fontWeight: cell
|
|
21927
|
-
fontStyle: cell
|
|
21928
|
-
|
|
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"
|
|
21929
22315
|
});
|
|
21930
|
-
if (cell
|
|
21931
|
-
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 ?? "";
|
|
21932
22320
|
tr.appendChild(td);
|
|
21933
22321
|
}
|
|
21934
22322
|
table.appendChild(tr);
|
|
@@ -22813,8 +23201,14 @@ void main() {
|
|
|
22813
23201
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
22814
23202
|
};
|
|
22815
23203
|
b.alpha = this.modelAlpha;
|
|
22816
|
-
for (const m of models) for (const bgSpan of m.backgrounds) this.emitBackground(b, bgSpan, pane, coords);
|
|
22817
|
-
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
|
+
}
|
|
22818
23212
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
22819
23213
|
let candleDrawn = false;
|
|
22820
23214
|
for (const m of models) {
|
|
@@ -22828,7 +23222,7 @@ void main() {
|
|
|
22828
23222
|
b.alpha = this.modelAlpha;
|
|
22829
23223
|
const off = scene.offsetOf(m.id);
|
|
22830
23224
|
const mp = effPane(m);
|
|
22831
|
-
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);
|
|
22832
23226
|
}
|
|
22833
23227
|
if (drawCandles && !candleDrawn) {
|
|
22834
23228
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -22837,6 +23231,12 @@ void main() {
|
|
|
22837
23231
|
}
|
|
22838
23232
|
drawSlicesUpTo(Infinity);
|
|
22839
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
|
+
}
|
|
22840
23240
|
for (const m of models) {
|
|
22841
23241
|
const mp = effPane(m);
|
|
22842
23242
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -22883,7 +23283,7 @@ void main() {
|
|
|
22883
23283
|
for (const pane of scene.orderedPanes()) {
|
|
22884
23284
|
const b = this.glowBatch;
|
|
22885
23285
|
b.reset();
|
|
22886
|
-
this.emitGlowSources(b, scene
|
|
23286
|
+
this.emitGlowSources(b, scene, pane, coords, i0, i1);
|
|
22887
23287
|
if (b.vertexCount === 0) continue;
|
|
22888
23288
|
const topH = Math.round(pane.bounds.top * dpr * 0.5);
|
|
22889
23289
|
const botH = Math.round((pane.bounds.top + pane.bounds.height) * dpr * 0.5);
|
|
@@ -22952,15 +23352,23 @@ void main() {
|
|
|
22952
23352
|
gl.bindVertexArray(null);
|
|
22953
23353
|
return true;
|
|
22954
23354
|
}
|
|
22955
|
-
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
22956
|
-
|
|
22957
|
-
|
|
22958
|
-
|
|
22959
|
-
|
|
22960
|
-
|
|
22961
|
-
|
|
22962
|
-
|
|
22963
|
-
|
|
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);
|
|
22964
23372
|
}
|
|
22965
23373
|
}
|
|
22966
23374
|
}
|
|
@@ -23243,9 +23651,9 @@ void main() {
|
|
|
23243
23651
|
b.alpha = this.candleBodyAlpha;
|
|
23244
23652
|
b.rect(g.bodyX, bodyTop, g.bodyW, bodyH, c);
|
|
23245
23653
|
}
|
|
23246
|
-
if (cs.borderVisible ||
|
|
23654
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
23247
23655
|
b.alpha = this.candleStructureAlpha;
|
|
23248
|
-
const bord = cs.borderVisible
|
|
23656
|
+
const bord = cs.borderVisible ? parseColor((isUp ? cs.borderUpColor : cs.borderDownColor) ?? bodyColorStr) : c;
|
|
23249
23657
|
const bw = Math.max(0, g.bodyW - 1);
|
|
23250
23658
|
const bh = Math.max(0, bodyH - 1);
|
|
23251
23659
|
b.rectStroke(g.bodyX + 0.5, bodyTop + 0.5, bw, bh, 1, bord);
|
|
@@ -24797,8 +25205,14 @@ void main() {
|
|
|
24797
25205
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
24798
25206
|
};
|
|
24799
25207
|
ctx.globalAlpha = this.modelAlpha;
|
|
24800
|
-
for (const m of models) for (const bg of m.backgrounds) this.drawBackground(ctx, bg, pane, coords);
|
|
24801
|
-
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
|
+
}
|
|
24802
25216
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
24803
25217
|
let si = 0;
|
|
24804
25218
|
const drawSlicesUpTo = (z) => {
|
|
@@ -24820,7 +25234,7 @@ void main() {
|
|
|
24820
25234
|
ctx.globalAlpha = this.modelAlpha;
|
|
24821
25235
|
const off = scene.offsetOf(m.id);
|
|
24822
25236
|
const mp = effPane(m);
|
|
24823
|
-
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);
|
|
24824
25238
|
}
|
|
24825
25239
|
if (drawCandles && !candleDrawn) {
|
|
24826
25240
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -24828,6 +25242,13 @@ void main() {
|
|
|
24828
25242
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
24829
25243
|
}
|
|
24830
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
|
+
}
|
|
24831
25252
|
ctx.globalAlpha = this.modelAlpha;
|
|
24832
25253
|
for (const m of models) {
|
|
24833
25254
|
const mp = effPane(m);
|
|
@@ -25099,9 +25520,9 @@ void main() {
|
|
|
25099
25520
|
ctx.fillStyle = color;
|
|
25100
25521
|
ctx.fillRect(g.bodyX, top, g.bodyW, bodyH);
|
|
25101
25522
|
}
|
|
25102
|
-
if (cs.borderVisible ||
|
|
25523
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
25103
25524
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
25104
|
-
ctx.strokeStyle = cs.borderVisible
|
|
25525
|
+
ctx.strokeStyle = cs.borderVisible ? (up ? cs.borderUpColor : cs.borderDownColor) ?? color : color;
|
|
25105
25526
|
ctx.lineWidth = 1;
|
|
25106
25527
|
const bw = Math.max(0, g.bodyW - 1);
|
|
25107
25528
|
const bh = Math.max(0, bodyH - 1);
|
|
@@ -25533,6 +25954,8 @@ void main() {
|
|
|
25533
25954
|
this.set = set;
|
|
25534
25955
|
/** measureText width cache, keyed by `${font} ${text}`, persists across frames. */
|
|
25535
25956
|
this.widthCache = /* @__PURE__ */ new Map();
|
|
25957
|
+
/** Tooltip hit-rects captured while drawing the CURRENT set (rebuilt per render). */
|
|
25958
|
+
this.tipRegions = [];
|
|
25536
25959
|
/**
|
|
25537
25960
|
* Index offset of the CURRENT set's model: its `xloc:'bar_index'` coordinates count
|
|
25538
25961
|
* from the model's anchor bar, so they shift by this to land on chart logical indices.
|
|
@@ -25553,8 +25976,13 @@ void main() {
|
|
|
25553
25976
|
const s = this.set;
|
|
25554
25977
|
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
25555
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
|
+
}
|
|
25556
25983
|
/** Draw the whole set into `ctx` using the supplied coordinate closures. */
|
|
25557
25984
|
render(ctx, W, H, xOf, yOf) {
|
|
25985
|
+
this.tipRegions = [];
|
|
25558
25986
|
if (this.isEmpty()) return;
|
|
25559
25987
|
ctx.save();
|
|
25560
25988
|
this.drawLinefills(ctx, W, H, xOf, yOf);
|
|
@@ -25902,13 +26330,35 @@ void main() {
|
|
|
25902
26330
|
if (this.isPointShape(lb.style)) {
|
|
25903
26331
|
if (!lb.noFill) this.drawLabelShape(ctx, lb.style, px, py, fontPx, color);
|
|
25904
26332
|
if (lb.text) this.drawLabelText(ctx, lb, px, py + fontPx, fontPx);
|
|
25905
|
-
|
|
25906
|
-
|
|
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
|
+
}
|
|
25907
26342
|
} else {
|
|
25908
|
-
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 });
|
|
25909
26345
|
}
|
|
25910
26346
|
}
|
|
25911
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
|
+
}
|
|
25912
26362
|
isPointShape(style) {
|
|
25913
26363
|
switch (style) {
|
|
25914
26364
|
case "circle":
|
|
@@ -25927,8 +26377,7 @@ void main() {
|
|
|
25927
26377
|
}
|
|
25928
26378
|
}
|
|
25929
26379
|
drawLabelText(ctx, lb, cx, cy, fontPx, outline = false) {
|
|
25930
|
-
|
|
25931
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
26380
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
25932
26381
|
ctx.textAlign = lb.textAlign === "left" ? "left" : lb.textAlign === "right" ? "right" : "center";
|
|
25933
26382
|
ctx.textBaseline = "middle";
|
|
25934
26383
|
const lines = lb.text.split("\n");
|
|
@@ -26008,9 +26457,9 @@ void main() {
|
|
|
26008
26457
|
ctx.fill();
|
|
26009
26458
|
}
|
|
26010
26459
|
}
|
|
26460
|
+
/** Draw a bubble label; returns the bubble body rect (for tooltip hit-testing). */
|
|
26011
26461
|
drawBubble(ctx, lb, px, py, fontPx, color) {
|
|
26012
|
-
|
|
26013
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
26462
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
26014
26463
|
const lines = (lb.text ?? "").split("\n");
|
|
26015
26464
|
const padX = 6;
|
|
26016
26465
|
const padY = 4;
|
|
@@ -26068,38 +26517,51 @@ void main() {
|
|
|
26068
26517
|
by = py - h - ptr;
|
|
26069
26518
|
pointer = "down";
|
|
26070
26519
|
}
|
|
26071
|
-
|
|
26072
|
-
|
|
26073
|
-
|
|
26074
|
-
if (pointer !== "none") {
|
|
26075
|
-
ctx.beginPath();
|
|
26076
|
-
if (pointer === "down") {
|
|
26077
|
-
ctx.moveTo(px - ptr, by + h);
|
|
26078
|
-
ctx.lineTo(px + ptr, by + h);
|
|
26079
|
-
ctx.lineTo(px, py);
|
|
26080
|
-
} else if (pointer === "up") {
|
|
26081
|
-
ctx.moveTo(px - ptr, by);
|
|
26082
|
-
ctx.lineTo(px + ptr, by);
|
|
26083
|
-
ctx.lineTo(px, py);
|
|
26084
|
-
} else if (pointer === "left") {
|
|
26085
|
-
ctx.moveTo(bx, py - ptr);
|
|
26086
|
-
ctx.lineTo(bx, py + ptr);
|
|
26087
|
-
ctx.lineTo(px, py);
|
|
26088
|
-
} else {
|
|
26089
|
-
ctx.moveTo(bx + w, py - ptr);
|
|
26090
|
-
ctx.lineTo(bx + w, py + ptr);
|
|
26091
|
-
ctx.lineTo(px, py);
|
|
26092
|
-
}
|
|
26093
|
-
ctx.closePath();
|
|
26520
|
+
if (!lb.noFill) {
|
|
26521
|
+
ctx.fillStyle = color;
|
|
26522
|
+
this.roundRect(ctx, bx, by, w, h, 4);
|
|
26094
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
|
+
}
|
|
26095
26546
|
}
|
|
26096
26547
|
if (lb.text) {
|
|
26097
|
-
ctx.fillStyle = lb.textColor ?? contrastColor(color);
|
|
26098
|
-
ctx.textAlign = "center";
|
|
26548
|
+
ctx.fillStyle = lb.textColor ?? (lb.noFill ? this.deps.theme.textColor : contrastColor(color));
|
|
26099
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
|
+
}
|
|
26100
26561
|
const startY = by + padY + lineH / 2;
|
|
26101
|
-
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);
|
|
26102
26563
|
}
|
|
26564
|
+
return { x: bx, y: by, w, h };
|
|
26103
26565
|
}
|
|
26104
26566
|
roundRect(ctx, x, y, w, h, r) {
|
|
26105
26567
|
const rr = Math.min(r, w / 2, h / 2);
|
|
@@ -26337,6 +26799,8 @@ void main() {
|
|
|
26337
26799
|
this.axisTextColor = DARK_THEME.textColor;
|
|
26338
26800
|
// Shared Pine-drawing renderer (line/box/label/polyline/linefill); widthCache persists.
|
|
26339
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 = [];
|
|
26340
26804
|
}
|
|
26341
26805
|
mount(canvas) {
|
|
26342
26806
|
this.canvas = canvas;
|
|
@@ -26378,6 +26842,7 @@ void main() {
|
|
|
26378
26842
|
const dataW = coords.width;
|
|
26379
26843
|
const dataH = coords.height;
|
|
26380
26844
|
this.axisTextColor = surface?.textColor ?? theme.textColor;
|
|
26845
|
+
this.labelTips = [];
|
|
26381
26846
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
26382
26847
|
ctx.clearRect(0, 0, fullW, fullH);
|
|
26383
26848
|
if (surface && (fullW > dataW || fullH > dataH)) {
|
|
@@ -26487,6 +26952,17 @@ void main() {
|
|
|
26487
26952
|
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
26488
26953
|
);
|
|
26489
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;
|
|
26490
26966
|
}
|
|
26491
26967
|
// ── axes ──
|
|
26492
26968
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
@@ -26703,6 +27179,68 @@ void main() {
|
|
|
26703
27179
|
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
|
|
26704
27180
|
}
|
|
26705
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
|
+
|
|
26706
27244
|
// src/renderers/native/chrome/CrosshairRenderer.ts
|
|
26707
27245
|
var CrosshairRenderer = class {
|
|
26708
27246
|
constructor() {
|
|
@@ -32494,19 +33032,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32494
33032
|
for (const model of models) {
|
|
32495
33033
|
const off = offsetOf(model.id);
|
|
32496
33034
|
for (const s of model.series) {
|
|
32497
|
-
if (s.
|
|
32498
|
-
|
|
32499
|
-
const b = s.bars[i - off];
|
|
32500
|
-
if (b) {
|
|
32501
|
-
consider(b.high);
|
|
32502
|
-
consider(b.low);
|
|
32503
|
-
}
|
|
32504
|
-
}
|
|
32505
|
-
} else if (isLineLikeSeries(s)) {
|
|
32506
|
-
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
32507
|
-
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
32508
|
-
else if (s.style?.base != null) consider(s.style.base);
|
|
32509
|
-
}
|
|
33035
|
+
if (s.overlay === true) continue;
|
|
33036
|
+
considerSeries(s, i0, i1, off, consider);
|
|
32510
33037
|
}
|
|
32511
33038
|
for (const pl of model.priceLines) consider(pl.price);
|
|
32512
33039
|
}
|
|
@@ -32528,6 +33055,36 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32528
33055
|
const span = max2 - min2;
|
|
32529
33056
|
return { min: min2 - span * MARGIN_BOTTOM, max: max2 + span * MARGIN_TOP };
|
|
32530
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
|
+
}
|
|
32531
33088
|
function expandScaleByPixels(scale, heightPx, abovePx, belowPx) {
|
|
32532
33089
|
if (abovePx <= 0 && belowPx <= 0) return scale;
|
|
32533
33090
|
const content = heightPx - abovePx - belowPx;
|
|
@@ -32582,6 +33139,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32582
33139
|
}
|
|
32583
33140
|
|
|
32584
33141
|
// src/renderers/native/backdrop/BackdropRenderer.ts
|
|
33142
|
+
function clipHighlightRect(x1, x2, left, right) {
|
|
33143
|
+
const x = Math.max(left, x1);
|
|
33144
|
+
const end = Math.min(right, x2);
|
|
33145
|
+
return end > x ? { x, width: end - x } : null;
|
|
33146
|
+
}
|
|
32585
33147
|
var BackdropRenderer = class {
|
|
32586
33148
|
constructor() {
|
|
32587
33149
|
this.canvas = null;
|
|
@@ -32614,17 +33176,22 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
32614
33176
|
/** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
|
|
32615
33177
|
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
32616
33178
|
drawHighlights(ctx, scene, coords) {
|
|
32617
|
-
const
|
|
32618
|
-
if (
|
|
33179
|
+
const sessions = scene.sessionHighlightBands();
|
|
33180
|
+
if (sessions.length > 0) {
|
|
33181
|
+
const left = Math.max(0, coords.logicalToX(-0.5));
|
|
33182
|
+
const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
|
|
33183
|
+
this.drawHighlightSet(ctx, sessions, coords, left, right);
|
|
33184
|
+
}
|
|
33185
|
+
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
|
|
33186
|
+
}
|
|
33187
|
+
drawHighlightSet(ctx, bands, coords, left, right) {
|
|
32619
33188
|
for (const band of bands) {
|
|
32620
33189
|
const x1 = coords.timeToX(band.from);
|
|
32621
33190
|
const x2 = coords.timeToX(band.to);
|
|
32622
|
-
|
|
32623
|
-
|
|
32624
|
-
const cw = Math.min(coords.width, x2) - cx;
|
|
32625
|
-
if (cw <= 0) continue;
|
|
33191
|
+
const rect = clipHighlightRect(x1, x2, left, right);
|
|
33192
|
+
if (!rect) continue;
|
|
32626
33193
|
ctx.fillStyle = band.color;
|
|
32627
|
-
ctx.fillRect(
|
|
33194
|
+
ctx.fillRect(rect.x, 0, rect.width, coords.height);
|
|
32628
33195
|
}
|
|
32629
33196
|
}
|
|
32630
33197
|
// ── grid ── vert/horz gate on `scene.showGrid` AND their own per-axis visibility
|
|
@@ -33300,6 +33867,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33300
33867
|
this.glowAmount = 0;
|
|
33301
33868
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
33302
33869
|
this.chrome = new ChromeRenderer();
|
|
33870
|
+
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
33871
|
+
this.labelTooltip = null;
|
|
33303
33872
|
this.crosshairLayer = new CrosshairRenderer();
|
|
33304
33873
|
/** 1 Hz repaint pump so the price-axis countdown-to-bar-close ticks; null when off. */
|
|
33305
33874
|
this.countdownTimer = null;
|
|
@@ -33310,6 +33879,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
33310
33879
|
this.indicatorValuesOn = true;
|
|
33311
33880
|
/** Host-contributed legend actions — held here so a rebuild of the legend re-wires them. */
|
|
33312
33881
|
this.legendActionsProvider = null;
|
|
33882
|
+
/** Host-contributed legend callouts — held here so a rebuild of the legend re-wires them. */
|
|
33883
|
+
this.legendCalloutsProvider = null;
|
|
33313
33884
|
/** Host override of the legend's fold toggle — held here so a remount re-applies it. */
|
|
33314
33885
|
this.legendOverviewAction = null;
|
|
33315
33886
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -34418,6 +34989,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34418
34989
|
this.plot.appendChild(this.scrollButton);
|
|
34419
34990
|
this.plot.addEventListener("pointermove", this.onScrollProximityMove);
|
|
34420
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
|
+
});
|
|
34421
34996
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
34422
34997
|
projector: () => this.drawingProjector(),
|
|
34423
34998
|
dpr: () => this.coords.dpr,
|
|
@@ -34457,9 +35032,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34457
35032
|
this.inputsUI.setDialogHost(this.dialogHost);
|
|
34458
35033
|
this.inputsUI.setSymbolPicker(this.symbolPicker);
|
|
34459
35034
|
this.inputsUI.setLegendActions(this.legendActionsProvider);
|
|
35035
|
+
this.inputsUI.setLegendCallouts(this.legendCalloutsProvider);
|
|
34460
35036
|
this.inputsUI.setLegendOverviewAction(this.legendOverviewAction);
|
|
34461
35037
|
this.inputsUI.setOnChange((c) => {
|
|
34462
|
-
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 } : {} });
|
|
34463
35039
|
});
|
|
34464
35040
|
this.inputsUI.setOnRemove((id) => {
|
|
34465
35041
|
for (const cb of this.removeIndicatorCbs) cb(id);
|
|
@@ -34649,6 +35225,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34649
35225
|
this.settingsButton = null;
|
|
34650
35226
|
this.plot?.removeEventListener("pointermove", this.onScrollProximityMove);
|
|
34651
35227
|
this.plot?.removeEventListener("pointerleave", this.onScrollProximityLeave);
|
|
35228
|
+
this.labelTooltip?.destroy();
|
|
35229
|
+
this.labelTooltip = null;
|
|
34652
35230
|
this.scrollButton?.remove();
|
|
34653
35231
|
this.scrollButton = null;
|
|
34654
35232
|
for (const l of this.extLayers) l.instance.destroy?.();
|
|
@@ -34889,7 +35467,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34889
35467
|
else this.scene.assignIndicatorZ(model.id);
|
|
34890
35468
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
34891
35469
|
native: !!model.native,
|
|
34892
|
-
...model.
|
|
35470
|
+
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
34893
35471
|
});
|
|
34894
35472
|
this.syncTables(model);
|
|
34895
35473
|
if (model.native?.type === "volume") {
|
|
@@ -34940,8 +35518,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34940
35518
|
this.paneControls?.refresh();
|
|
34941
35519
|
this.scheduler.invalidate(4 /* Full */);
|
|
34942
35520
|
}
|
|
34943
|
-
setIndicatorInputs(handle, values) {
|
|
34944
|
-
this.inputsUI.setValues(handle.id, values);
|
|
35521
|
+
setIndicatorInputs(handle, values, props) {
|
|
35522
|
+
this.inputsUI.setValues(handle.id, values, props);
|
|
34945
35523
|
}
|
|
34946
35524
|
setSymbolPicker(picker) {
|
|
34947
35525
|
this.symbolPicker = picker;
|
|
@@ -34951,6 +35529,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
34951
35529
|
this.legendActionsProvider = provider;
|
|
34952
35530
|
this.inputsUI?.setLegendActions(provider);
|
|
34953
35531
|
}
|
|
35532
|
+
setLegendCallouts(provider) {
|
|
35533
|
+
this.legendCalloutsProvider = provider;
|
|
35534
|
+
this.inputsUI?.setLegendCallouts(provider);
|
|
35535
|
+
}
|
|
34954
35536
|
setLegendOverviewAction(action) {
|
|
34955
35537
|
this.legendOverviewAction = action;
|
|
34956
35538
|
this.inputsUI?.setLegendOverviewAction(action);
|
|
@@ -35931,7 +36513,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
35931
36513
|
}
|
|
35932
36514
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
35933
36515
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
35934
|
-
|
|
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
|
+
}
|
|
35935
36521
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
35936
36522
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
35937
36523
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -36205,6 +36791,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
36205
36791
|
for (const m of models) {
|
|
36206
36792
|
const off = this.scene.offsetOf(m.id);
|
|
36207
36793
|
for (const s of m.series) {
|
|
36794
|
+
if (s.overlay === true) continue;
|
|
36208
36795
|
if (isLineLikeSeries(s)) {
|
|
36209
36796
|
const v = s.points[i0 - off]?.value;
|
|
36210
36797
|
if (v != null && Number.isFinite(v)) return v;
|
|
@@ -37110,6 +37697,19 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
37110
37697
|
function legendActions() {
|
|
37111
37698
|
return [...legendRegistry.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
37112
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
|
+
}
|
|
37113
37713
|
var stateHandlers = /* @__PURE__ */ new Map();
|
|
37114
37714
|
function registerStatePersistence(handler) {
|
|
37115
37715
|
stateHandlers.set(handler.key, handler);
|
|
@@ -38453,6 +39053,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38453
39053
|
exports.iconMarkup = iconMarkup;
|
|
38454
39054
|
exports.inputVisible = inputVisible;
|
|
38455
39055
|
exports.legendActions = legendActions;
|
|
39056
|
+
exports.legendCallouts = legendCallouts;
|
|
38456
39057
|
exports.nativeIndicatorDescriptors = nativeIndicatorDescriptors;
|
|
38457
39058
|
exports.nativeIndicatorTypes = nativeIndicatorTypes;
|
|
38458
39059
|
exports.normalizeSettingsRow = normalizeSettingsRow;
|
|
@@ -38461,6 +39062,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38461
39062
|
exports.registerDrawingType = registerDrawingType;
|
|
38462
39063
|
exports.registerIcon = registerIcon;
|
|
38463
39064
|
exports.registerLegendAction = registerLegendAction;
|
|
39065
|
+
exports.registerLegendCallout = registerLegendCallout;
|
|
38464
39066
|
exports.registerNativeIndicator = registerNativeIndicator;
|
|
38465
39067
|
exports.registerRendererDefaults = registerRendererDefaults;
|
|
38466
39068
|
exports.registerRendererLayer = registerRendererLayer;
|
|
@@ -38486,6 +39088,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
|
|
|
38486
39088
|
exports.unregisterChartType = unregisterChartType;
|
|
38487
39089
|
exports.unregisterDefaultEngine = unregisterDefaultEngine;
|
|
38488
39090
|
exports.unregisterLegendAction = unregisterLegendAction;
|
|
39091
|
+
exports.unregisterLegendCallout = unregisterLegendCallout;
|
|
38489
39092
|
exports.unregisterNativeIndicator = unregisterNativeIndicator;
|
|
38490
39093
|
exports.unregisterRendererDefaults = unregisterRendererDefaults;
|
|
38491
39094
|
exports.unregisterRendererLayer = unregisterRendererLayer;
|