@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/index.cjs
CHANGED
|
@@ -138,6 +138,7 @@ var IndicatorHandleImpl = class {
|
|
|
138
138
|
constructor(id, title, controller, source) {
|
|
139
139
|
this.controller = controller;
|
|
140
140
|
this.schema = [];
|
|
141
|
+
this.propsSchema = [];
|
|
141
142
|
this.visibleState = true;
|
|
142
143
|
this.bus = new TypedEventBus();
|
|
143
144
|
this.id = id;
|
|
@@ -147,6 +148,9 @@ var IndicatorHandleImpl = class {
|
|
|
147
148
|
get inputs() {
|
|
148
149
|
return this.schema;
|
|
149
150
|
}
|
|
151
|
+
get props() {
|
|
152
|
+
return this.propsSchema;
|
|
153
|
+
}
|
|
150
154
|
get visible() {
|
|
151
155
|
return this.visibleState;
|
|
152
156
|
}
|
|
@@ -156,6 +160,12 @@ var IndicatorHandleImpl = class {
|
|
|
156
160
|
setInputs(values) {
|
|
157
161
|
this.controller.applyInputs(this.id, values);
|
|
158
162
|
}
|
|
163
|
+
setProp(key, value) {
|
|
164
|
+
this.controller.applyProps(this.id, { [key]: value });
|
|
165
|
+
}
|
|
166
|
+
setProps(values) {
|
|
167
|
+
this.controller.applyProps(this.id, values);
|
|
168
|
+
}
|
|
159
169
|
setVisible(visible) {
|
|
160
170
|
this.controller.setVisible(this.id, visible);
|
|
161
171
|
}
|
|
@@ -175,6 +185,9 @@ var IndicatorHandleImpl = class {
|
|
|
175
185
|
setSchema(schema) {
|
|
176
186
|
this.schema = schema;
|
|
177
187
|
}
|
|
188
|
+
setPropsSchema(schema) {
|
|
189
|
+
this.propsSchema = schema;
|
|
190
|
+
}
|
|
178
191
|
/** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
|
|
179
192
|
setVisibleState(visible) {
|
|
180
193
|
this.visibleState = visible;
|
|
@@ -188,6 +201,8 @@ var IndicatorHandleImpl = class {
|
|
|
188
201
|
function summarizeModel(model) {
|
|
189
202
|
const series = {};
|
|
190
203
|
for (const s of model.series) series[s.kind] = (series[s.kind] ?? 0) + 1;
|
|
204
|
+
const countOverlay = (items) => items?.filter((i) => i.overlay === true).length ?? 0;
|
|
205
|
+
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);
|
|
191
206
|
return {
|
|
192
207
|
id: model.id,
|
|
193
208
|
title: model.title,
|
|
@@ -207,7 +222,8 @@ function summarizeModel(model) {
|
|
|
207
222
|
tables: model.tables?.length ?? 0,
|
|
208
223
|
barColors: model.barColors?.length ?? 0,
|
|
209
224
|
trades: model.trades?.length ?? 0,
|
|
210
|
-
inputs: model.inputs.length
|
|
225
|
+
inputs: model.inputs.length,
|
|
226
|
+
forcedOverlay
|
|
211
227
|
};
|
|
212
228
|
}
|
|
213
229
|
function inspectModels(models) {
|
|
@@ -228,7 +244,8 @@ function inspectModels(models) {
|
|
|
228
244
|
linefills: sum((s) => s.linefills),
|
|
229
245
|
tables: sum((s) => s.tables),
|
|
230
246
|
barColors: sum((s) => s.barColors),
|
|
231
|
-
trades: sum((s) => s.trades)
|
|
247
|
+
trades: sum((s) => s.trades),
|
|
248
|
+
forcedOverlay: sum((s) => s.forcedOverlay)
|
|
232
249
|
}
|
|
233
250
|
};
|
|
234
251
|
}
|
|
@@ -498,11 +515,11 @@ registerIcon(
|
|
|
498
515
|
);
|
|
499
516
|
registerIcon(
|
|
500
517
|
"market-pre",
|
|
501
|
-
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="
|
|
518
|
+
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"/>')
|
|
502
519
|
);
|
|
503
520
|
registerIcon(
|
|
504
521
|
"market-post",
|
|
505
|
-
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
|
|
522
|
+
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"/>')
|
|
506
523
|
);
|
|
507
524
|
registerIcon("market-closed", svg24('<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>'));
|
|
508
525
|
registerIcon(
|
|
@@ -7342,7 +7359,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
7342
7359
|
for (const engine of engines) this.registerEngine(engine.language, engine);
|
|
7343
7360
|
this.defaultLanguage = config.defaultLanguage;
|
|
7344
7361
|
this.renderer.mount(container, config.theme);
|
|
7345
|
-
this.renderer.onInputChange(
|
|
7362
|
+
this.renderer.onInputChange(
|
|
7363
|
+
(e) => e.kind === "prop" ? this.applyProps(e.indicatorId, { [e.key]: e.value }) : this.applyInputs(e.indicatorId, { [e.key]: e.value })
|
|
7364
|
+
);
|
|
7346
7365
|
this.renderer.onRemoveIndicator((id) => this.removeIndicator(id));
|
|
7347
7366
|
this.renderer.onToggleIndicatorVisible?.((id, visible) => this.setVisible(id, visible));
|
|
7348
7367
|
this.paneActionUnsub = this.renderer.onPaneAction?.((a) => this.handlePaneAction(a)) ?? null;
|
|
@@ -8022,7 +8041,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8022
8041
|
const title = options.title ?? "Indicator";
|
|
8023
8042
|
const handle = new IndicatorHandleImpl(id, title, this, source);
|
|
8024
8043
|
this.handles.set(id, handle);
|
|
8025
|
-
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} } });
|
|
8044
|
+
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} }, propValues: { ...options.props ?? {} } });
|
|
8026
8045
|
void this.startIndicator(id, source, options, handle);
|
|
8027
8046
|
return handle;
|
|
8028
8047
|
}
|
|
@@ -8045,7 +8064,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8045
8064
|
return handle;
|
|
8046
8065
|
}
|
|
8047
8066
|
const inputValues = { ...descriptor.defaultInputs(), ...options.inputs ?? {} };
|
|
8048
|
-
this.registry.add({ id, title, source: type, inputValues, native: { type, instance: descriptor.create(), descriptor } });
|
|
8067
|
+
this.registry.add({ id, title, source: type, inputValues, propValues: {}, native: { type, instance: descriptor.create(), descriptor } });
|
|
8049
8068
|
handle.setSchema(descriptor.inputsSchema());
|
|
8050
8069
|
void this.startNativeIndicator(id, handle);
|
|
8051
8070
|
return handle;
|
|
@@ -8098,6 +8117,19 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8098
8117
|
if (record.session) record.session.update(record.inputValues);
|
|
8099
8118
|
else if (record.native && !record.hidden) record.native.instance.setInputs(record.inputValues);
|
|
8100
8119
|
}
|
|
8120
|
+
/** IndicatorController: re-run an indicator with merged declaration-prop overrides.
|
|
8121
|
+
* Same lifecycle as {@link applyInputs} — a prop change replays the whole script.
|
|
8122
|
+
* Script indicators only: natives have no declaration props. */
|
|
8123
|
+
applyProps(id, values) {
|
|
8124
|
+
const record = this.registry.get(id);
|
|
8125
|
+
if (!record?.session) return;
|
|
8126
|
+
record.propValues = { ...record.propValues, ...values };
|
|
8127
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
8128
|
+
record.pendingStructural = true;
|
|
8129
|
+
if (!record.hidden) this.setLoading(record, true);
|
|
8130
|
+
record.pendingCause = "inputs";
|
|
8131
|
+
record.session.update(record.inputValues, record.propValues);
|
|
8132
|
+
}
|
|
8101
8133
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
8102
8134
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
8103
8135
|
listIndicators() {
|
|
@@ -8246,6 +8278,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8246
8278
|
for (const input of prepared.inputs) defaults2[input.key] = input.defval;
|
|
8247
8279
|
record.inputValues = { ...defaults2, ...record.inputValues };
|
|
8248
8280
|
handle.setSchema(prepared.inputs);
|
|
8281
|
+
const propDefaults = {};
|
|
8282
|
+
for (const prop of prepared.props ?? []) propDefaults[prop.key] = prop.defval;
|
|
8283
|
+
record.propValues = { ...propDefaults, ...record.propValues };
|
|
8284
|
+
handle.setPropsSchema(prepared.props ?? []);
|
|
8249
8285
|
this.mountLoadingPlaceholder(id, record);
|
|
8250
8286
|
this.executeIndicator(id, handle);
|
|
8251
8287
|
} catch (err) {
|
|
@@ -8269,6 +8305,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8269
8305
|
getBars: () => this.bars,
|
|
8270
8306
|
fetchSeries: (sym, tf, range) => this.fetchSeries(sym, tf, range),
|
|
8271
8307
|
inputs: record.inputValues,
|
|
8308
|
+
props: record.propValues,
|
|
8272
8309
|
visibleRange: this.currentVisibleRange(),
|
|
8273
8310
|
mode,
|
|
8274
8311
|
// Mid-backfill session starts (add / re-show / price-style re-execute) let the
|
|
@@ -8362,6 +8399,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8362
8399
|
const meta = record.prepared.meta;
|
|
8363
8400
|
const model = {
|
|
8364
8401
|
id,
|
|
8402
|
+
// Deliberately the FULL title, never a shorttitle: while the script loads the
|
|
8403
|
+
// legend identifies it by its full name; the compact shorttitle arrives with
|
|
8404
|
+
// the first computed model and takes over from there.
|
|
8365
8405
|
title: record.options?.title ?? meta.title,
|
|
8366
8406
|
overlay: meta.overlay,
|
|
8367
8407
|
paneHint: meta.overlay ? "price" : "new",
|
|
@@ -8370,7 +8410,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8370
8410
|
backgrounds: [],
|
|
8371
8411
|
priceLines: [],
|
|
8372
8412
|
inputs: record.prepared.inputs,
|
|
8373
|
-
inputValues: record.inputValues
|
|
8413
|
+
inputValues: record.inputValues,
|
|
8414
|
+
...record.prepared.props ? { props: record.prepared.props, propValues: record.propValues } : {}
|
|
8374
8415
|
};
|
|
8375
8416
|
const paneId = this.routePane(id, model, record.options ?? {});
|
|
8376
8417
|
this.placeModel(model, id, paneId);
|
|
@@ -8603,7 +8644,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8603
8644
|
record.model = model;
|
|
8604
8645
|
if (record.pendingStructural) {
|
|
8605
8646
|
record.renderHandle = this.renderer.mountIndicator(model);
|
|
8606
|
-
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues);
|
|
8647
|
+
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
8607
8648
|
record.pendingStructural = false;
|
|
8608
8649
|
} else {
|
|
8609
8650
|
this.renderer.updateIndicator(record.renderHandle, modelToValuePatch(model));
|
|
@@ -8630,16 +8671,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
8630
8671
|
placeModel(model, id, paneId) {
|
|
8631
8672
|
model.id = id;
|
|
8632
8673
|
model.paneId = paneId;
|
|
8633
|
-
|
|
8634
|
-
for (const
|
|
8635
|
-
for (const
|
|
8674
|
+
const paneOf = (item) => item.overlay === true ? "price" : paneId;
|
|
8675
|
+
for (const series of model.series) series.paneId = paneOf(series);
|
|
8676
|
+
for (const fill of model.fills) fill.paneId = paneOf(fill);
|
|
8677
|
+
for (const bg of model.backgrounds) bg.paneId = paneOf(bg);
|
|
8636
8678
|
for (const line of model.priceLines) line.paneId = paneId;
|
|
8637
|
-
if (model.lines) for (const ln of model.lines) ln.paneId =
|
|
8638
|
-
if (model.boxes) for (const bx of model.boxes) bx.paneId =
|
|
8639
|
-
if (model.labels) for (const lb of model.labels) lb.paneId =
|
|
8640
|
-
if (model.polylines) for (const pl of model.polylines) pl.paneId =
|
|
8641
|
-
if (model.linefills) for (const lf of model.linefills) lf.paneId =
|
|
8642
|
-
if (model.tables) for (const tb of model.tables) tb.paneId =
|
|
8679
|
+
if (model.lines) for (const ln of model.lines) ln.paneId = paneOf(ln);
|
|
8680
|
+
if (model.boxes) for (const bx of model.boxes) bx.paneId = paneOf(bx);
|
|
8681
|
+
if (model.labels) for (const lb of model.labels) lb.paneId = paneOf(lb);
|
|
8682
|
+
if (model.polylines) for (const pl of model.polylines) pl.paneId = paneOf(pl);
|
|
8683
|
+
if (model.linefills) for (const lf of model.linefills) lf.paneId = paneOf(lf);
|
|
8684
|
+
if (model.tables) for (const tb of model.tables) tb.paneId = paneOf(tb);
|
|
8643
8685
|
}
|
|
8644
8686
|
emitContextChanged(id) {
|
|
8645
8687
|
if (!this.registry.get(id)?.session?.getContext) return;
|
|
@@ -8792,6 +8834,15 @@ var RendererControl = class {
|
|
|
8792
8834
|
setLegendActions(provider) {
|
|
8793
8835
|
this.renderer.setLegendActions?.(provider);
|
|
8794
8836
|
}
|
|
8837
|
+
/**
|
|
8838
|
+
* Wire the legend rows' HOST-CONTRIBUTED callout bubbles (the shells route the
|
|
8839
|
+
* plugin registry through this; see `registerLegendCallout`). Silent on a renderer
|
|
8840
|
+
* without the seam — contributed callouts simply never show there, same graceful
|
|
8841
|
+
* degradation as {@link setLegendActions}.
|
|
8842
|
+
*/
|
|
8843
|
+
setLegendCallouts(provider) {
|
|
8844
|
+
this.renderer.setLegendCallouts?.(provider);
|
|
8845
|
+
}
|
|
8795
8846
|
/**
|
|
8796
8847
|
* Replace the indicator legend's fold toggle with a host action (or restore it with
|
|
8797
8848
|
* `null`) — multi-chart shells point the chip at their indicator overview instead of
|
|
@@ -10379,13 +10430,6 @@ var Menu = class {
|
|
|
10379
10430
|
}
|
|
10380
10431
|
};
|
|
10381
10432
|
|
|
10382
|
-
// src/core/model/inputs.ts
|
|
10383
|
-
function inputVisible(when, values) {
|
|
10384
|
-
if (!when) return true;
|
|
10385
|
-
const conds = Array.isArray(when) ? when : [when];
|
|
10386
|
-
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
|
|
10387
|
-
}
|
|
10388
|
-
|
|
10389
10433
|
// src/ui/tokens.ts
|
|
10390
10434
|
var STATIC_ID = "vela-ui-tokens";
|
|
10391
10435
|
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
@@ -10631,6 +10675,199 @@ var Popover = class {
|
|
|
10631
10675
|
}
|
|
10632
10676
|
};
|
|
10633
10677
|
|
|
10678
|
+
// src/ui/components/callout-bubble/controller.ts
|
|
10679
|
+
function closesPanel(item) {
|
|
10680
|
+
return item.close !== false;
|
|
10681
|
+
}
|
|
10682
|
+
function calloutPanelRows(items) {
|
|
10683
|
+
const rows = [];
|
|
10684
|
+
for (const item of items) {
|
|
10685
|
+
if (item.type === "text") {
|
|
10686
|
+
rows.push({ type: "text", text: item.text });
|
|
10687
|
+
continue;
|
|
10688
|
+
}
|
|
10689
|
+
const last = rows[rows.length - 1];
|
|
10690
|
+
if (last && last.type === "buttons") last.buttons.push(item);
|
|
10691
|
+
else rows.push({ type: "buttons", buttons: [item] });
|
|
10692
|
+
}
|
|
10693
|
+
return rows;
|
|
10694
|
+
}
|
|
10695
|
+
|
|
10696
|
+
// src/ui/components/callout-bubble/styles.ts
|
|
10697
|
+
var CALLOUT_STYLE_ID = "vela-ui-callout-bubble";
|
|
10698
|
+
var CALLOUT_CSS = `
|
|
10699
|
+
.vela-callout {
|
|
10700
|
+
display: inline-grid;
|
|
10701
|
+
place-items: center;
|
|
10702
|
+
border-radius: 50%;
|
|
10703
|
+
flex: none;
|
|
10704
|
+
line-height: 0;
|
|
10705
|
+
box-sizing: border-box;
|
|
10706
|
+
cursor: default;
|
|
10707
|
+
user-select: none;
|
|
10708
|
+
-webkit-user-select: none;
|
|
10709
|
+
}
|
|
10710
|
+
.vela-callout[role='button'] { cursor: pointer; }
|
|
10711
|
+
.vela-callout svg { display: block; }
|
|
10712
|
+
/* The deployed panel \u2014 carries the kit's elevated-card look itself (the popover
|
|
10713
|
+
shell is bare positioning chrome). */
|
|
10714
|
+
.vela-callout-panel {
|
|
10715
|
+
display: flex;
|
|
10716
|
+
flex-direction: column;
|
|
10717
|
+
gap: 8px;
|
|
10718
|
+
padding: 10px 12px;
|
|
10719
|
+
max-width: 280px;
|
|
10720
|
+
box-sizing: border-box;
|
|
10721
|
+
background: var(--vela-surface-elev);
|
|
10722
|
+
border: 1px solid var(--vela-border-strong);
|
|
10723
|
+
border-radius: 6px;
|
|
10724
|
+
box-shadow: var(--vela-shadow);
|
|
10725
|
+
color: var(--vela-fg);
|
|
10726
|
+
font: var(--vela-font-size-md) var(--vela-font);
|
|
10727
|
+
}
|
|
10728
|
+
.vela-callout-title { font-weight: 600; color: var(--vela-fg-bright); }
|
|
10729
|
+
.vela-callout-text { color: var(--vela-fg-muted); line-height: 1.45; white-space: pre-line; }
|
|
10730
|
+
.vela-callout-actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
10731
|
+
.vela-callout-btn {
|
|
10732
|
+
cursor: pointer;
|
|
10733
|
+
height: 26px;
|
|
10734
|
+
padding: 0 10px;
|
|
10735
|
+
border-radius: 5px;
|
|
10736
|
+
border: 1px solid var(--vela-border);
|
|
10737
|
+
background: transparent;
|
|
10738
|
+
color: var(--vela-fg);
|
|
10739
|
+
font-size: var(--vela-font-size-md);
|
|
10740
|
+
font-family: inherit;
|
|
10741
|
+
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;
|
|
10742
|
+
}
|
|
10743
|
+
.vela-callout-btn:hover { background: var(--vela-hover); color: var(--vela-fg-bright); border-color: var(--vela-fg-muted); }
|
|
10744
|
+
.vela-callout-btn-primary { border-color: var(--vela-selected-bg); background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
|
|
10745
|
+
.vela-callout-btn-primary:hover { background: var(--vela-selected-bg); color: var(--vela-selected-fg); opacity: 0.85; border-color: var(--vela-selected-bg); }
|
|
10746
|
+
`;
|
|
10747
|
+
|
|
10748
|
+
// src/ui/components/callout-bubble/view.ts
|
|
10749
|
+
var CalloutBubble = class {
|
|
10750
|
+
constructor(opts) {
|
|
10751
|
+
this.pop = null;
|
|
10752
|
+
this.spec = { icon: opts.icon, background: opts.background, label: opts.label };
|
|
10753
|
+
if (opts.color !== void 0) this.spec.color = opts.color;
|
|
10754
|
+
if (opts.panel !== void 0) this.spec.panel = opts.panel;
|
|
10755
|
+
this.size = opts.size ?? 16;
|
|
10756
|
+
this.host = opts.host;
|
|
10757
|
+
this.theme = opts.theme;
|
|
10758
|
+
this.boundary = opts.boundary;
|
|
10759
|
+
const doc = (opts.host ?? document.body).ownerDocument;
|
|
10760
|
+
injectStyles(CALLOUT_STYLE_ID, CALLOUT_CSS, doc);
|
|
10761
|
+
this.el = doc.createElement("span");
|
|
10762
|
+
this.el.className = "vela-callout";
|
|
10763
|
+
this.el.style.width = `${this.size}px`;
|
|
10764
|
+
this.el.style.height = `${this.size}px`;
|
|
10765
|
+
this.el.addEventListener("click", (e) => {
|
|
10766
|
+
if (!this.spec.panel) return;
|
|
10767
|
+
e.stopPropagation();
|
|
10768
|
+
this.toggle();
|
|
10769
|
+
});
|
|
10770
|
+
this.el.addEventListener("dblclick", (e) => {
|
|
10771
|
+
if (this.spec.panel) e.stopPropagation();
|
|
10772
|
+
});
|
|
10773
|
+
this.el.addEventListener("keydown", (e) => {
|
|
10774
|
+
if (!this.spec.panel || e.key !== "Enter" && e.key !== " ") return;
|
|
10775
|
+
e.preventDefault();
|
|
10776
|
+
this.toggle();
|
|
10777
|
+
});
|
|
10778
|
+
this.dress();
|
|
10779
|
+
}
|
|
10780
|
+
/** Re-dress the bubble (a status change: new icon, tint, label, panel). */
|
|
10781
|
+
set(spec) {
|
|
10782
|
+
this.spec = { ...this.spec, ...spec };
|
|
10783
|
+
if ("panel" in spec) this.pop?.hide();
|
|
10784
|
+
this.dress();
|
|
10785
|
+
}
|
|
10786
|
+
/** Whether the deployed panel is currently open. */
|
|
10787
|
+
get open() {
|
|
10788
|
+
return this.pop?.open ?? false;
|
|
10789
|
+
}
|
|
10790
|
+
destroy() {
|
|
10791
|
+
this.pop?.destroy();
|
|
10792
|
+
this.pop = null;
|
|
10793
|
+
this.el.remove();
|
|
10794
|
+
}
|
|
10795
|
+
dress() {
|
|
10796
|
+
const clickable = this.spec.panel !== void 0;
|
|
10797
|
+
this.el.style.background = this.spec.background;
|
|
10798
|
+
this.el.style.color = this.spec.color ?? "";
|
|
10799
|
+
this.el.innerHTML = iconAt(this.spec.icon, this.size - 4);
|
|
10800
|
+
this.el.setAttribute("aria-label", this.spec.label);
|
|
10801
|
+
if (clickable) {
|
|
10802
|
+
this.el.setAttribute("role", "button");
|
|
10803
|
+
this.el.tabIndex = 0;
|
|
10804
|
+
} else {
|
|
10805
|
+
this.el.removeAttribute("role");
|
|
10806
|
+
this.el.removeAttribute("tabindex");
|
|
10807
|
+
}
|
|
10808
|
+
}
|
|
10809
|
+
toggle() {
|
|
10810
|
+
if (this.pop?.open) {
|
|
10811
|
+
this.pop.hide();
|
|
10812
|
+
return;
|
|
10813
|
+
}
|
|
10814
|
+
const panel = this.spec.panel;
|
|
10815
|
+
if (!panel) return;
|
|
10816
|
+
this.pop?.destroy();
|
|
10817
|
+
this.pop = new Popover({
|
|
10818
|
+
trigger: this.el,
|
|
10819
|
+
gap: 6,
|
|
10820
|
+
content: (body) => this.buildPanel(body, panel),
|
|
10821
|
+
...this.host ? { host: this.host } : {},
|
|
10822
|
+
...this.theme ? { theme: this.theme() } : {},
|
|
10823
|
+
...this.boundary ? { boundary: this.boundary } : {}
|
|
10824
|
+
});
|
|
10825
|
+
this.pop.show();
|
|
10826
|
+
}
|
|
10827
|
+
buildPanel(body, panel) {
|
|
10828
|
+
const doc = body.ownerDocument;
|
|
10829
|
+
const root = doc.createElement("div");
|
|
10830
|
+
root.className = "vela-callout-panel";
|
|
10831
|
+
if (panel.title) {
|
|
10832
|
+
const title = doc.createElement("div");
|
|
10833
|
+
title.className = "vela-callout-title";
|
|
10834
|
+
title.textContent = panel.title;
|
|
10835
|
+
root.appendChild(title);
|
|
10836
|
+
}
|
|
10837
|
+
for (const row of calloutPanelRows(panel.items)) {
|
|
10838
|
+
if (row.type === "text") {
|
|
10839
|
+
const text = doc.createElement("div");
|
|
10840
|
+
text.className = "vela-callout-text";
|
|
10841
|
+
text.textContent = row.text;
|
|
10842
|
+
root.appendChild(text);
|
|
10843
|
+
continue;
|
|
10844
|
+
}
|
|
10845
|
+
const actions = doc.createElement("div");
|
|
10846
|
+
actions.className = "vela-callout-actions";
|
|
10847
|
+
for (const item of row.buttons) {
|
|
10848
|
+
const btn2 = doc.createElement("button");
|
|
10849
|
+
btn2.type = "button";
|
|
10850
|
+
btn2.className = "vela-callout-btn" + (item.primary ? " vela-callout-btn-primary" : "");
|
|
10851
|
+
btn2.textContent = item.label;
|
|
10852
|
+
btn2.addEventListener("click", () => {
|
|
10853
|
+
item.run();
|
|
10854
|
+
if (closesPanel(item)) this.pop?.hide();
|
|
10855
|
+
});
|
|
10856
|
+
actions.appendChild(btn2);
|
|
10857
|
+
}
|
|
10858
|
+
root.appendChild(actions);
|
|
10859
|
+
}
|
|
10860
|
+
body.appendChild(root);
|
|
10861
|
+
}
|
|
10862
|
+
};
|
|
10863
|
+
|
|
10864
|
+
// src/core/model/inputs.ts
|
|
10865
|
+
function inputVisible(when, values) {
|
|
10866
|
+
if (!when) return true;
|
|
10867
|
+
const conds = Array.isArray(when) ? when : [when];
|
|
10868
|
+
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
|
|
10869
|
+
}
|
|
10870
|
+
|
|
10634
10871
|
// src/ui/components/select/controller.ts
|
|
10635
10872
|
function selectController(opts) {
|
|
10636
10873
|
const options = opts.options;
|
|
@@ -12545,6 +12782,10 @@ function buildFieldControl(desc) {
|
|
|
12545
12782
|
}
|
|
12546
12783
|
|
|
12547
12784
|
// src/renderers/shared/IndicatorInputsDialog.ts
|
|
12785
|
+
var PROPS_TAB = "Properties";
|
|
12786
|
+
function bagOf(row, decl) {
|
|
12787
|
+
return decl.prop ? row.propValues : row.values;
|
|
12788
|
+
}
|
|
12548
12789
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
12549
12790
|
var IndicatorInputsDialog = class {
|
|
12550
12791
|
constructor(host) {
|
|
@@ -12555,6 +12796,7 @@ var IndicatorInputsDialog = class {
|
|
|
12555
12796
|
this.openId = null;
|
|
12556
12797
|
this.row = null;
|
|
12557
12798
|
this.snapshot = null;
|
|
12799
|
+
this.propSnapshot = null;
|
|
12558
12800
|
this.dialogTips = [];
|
|
12559
12801
|
/** Re-applies every `when` gate against current values; set while the dialog is open. */
|
|
12560
12802
|
this.refreshVisibility = null;
|
|
@@ -12577,10 +12819,11 @@ var IndicatorInputsDialog = class {
|
|
|
12577
12819
|
}
|
|
12578
12820
|
open(row) {
|
|
12579
12821
|
this.close();
|
|
12580
|
-
if (row.inputs.length === 0) return;
|
|
12822
|
+
if (row.inputs.length === 0 && row.props.length === 0) return;
|
|
12581
12823
|
this.row = row;
|
|
12582
12824
|
this.openId = row.id;
|
|
12583
12825
|
this.snapshot = { ...row.values };
|
|
12826
|
+
this.propSnapshot = { ...row.propValues };
|
|
12584
12827
|
ensureDialogStyles();
|
|
12585
12828
|
const t = this.host.theme();
|
|
12586
12829
|
const border = "var(--vela-border)";
|
|
@@ -12588,7 +12831,7 @@ var IndicatorInputsDialog = class {
|
|
|
12588
12831
|
const host = this.host.dialogHost() ?? this.host.container;
|
|
12589
12832
|
const ui = new Dialog({
|
|
12590
12833
|
host,
|
|
12591
|
-
title: row.
|
|
12834
|
+
title: row.title,
|
|
12592
12835
|
// Non-modal: live-edit dialog — a modal machine locks pointer events on the
|
|
12593
12836
|
// whole body, killing the chart and the body-portaled popovers.
|
|
12594
12837
|
modal: false,
|
|
@@ -12620,6 +12863,16 @@ var IndicatorInputsDialog = class {
|
|
|
12620
12863
|
text: () => "Close"
|
|
12621
12864
|
}));
|
|
12622
12865
|
const tabDefs = tabInputs(row.inputs);
|
|
12866
|
+
if (row.props.length > 0) {
|
|
12867
|
+
const propDecls = row.props.map((p) => {
|
|
12868
|
+
const d = { ...p, prop: true };
|
|
12869
|
+
delete d.tab;
|
|
12870
|
+
return d;
|
|
12871
|
+
});
|
|
12872
|
+
const existing = tabDefs.find((t2) => t2.name === PROPS_TAB);
|
|
12873
|
+
if (existing) existing.inputs.push(...propDecls);
|
|
12874
|
+
else tabDefs.push({ name: PROPS_TAB, inputs: propDecls });
|
|
12875
|
+
}
|
|
12623
12876
|
const tabs = document.createElement("div");
|
|
12624
12877
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
12625
12878
|
const tabEls = [];
|
|
@@ -12722,9 +12975,10 @@ var IndicatorInputsDialog = class {
|
|
|
12722
12975
|
this.openId = null;
|
|
12723
12976
|
this.row = null;
|
|
12724
12977
|
this.snapshot = null;
|
|
12978
|
+
this.propSnapshot = null;
|
|
12725
12979
|
ui?.destroy();
|
|
12726
12980
|
}
|
|
12727
|
-
/** Restore every input to its open-time value (re-running the indicator), then close. */
|
|
12981
|
+
/** Restore every input and prop to its open-time value (re-running the indicator), then close. */
|
|
12728
12982
|
revertAndClose() {
|
|
12729
12983
|
const row = this.row;
|
|
12730
12984
|
const snap = this.snapshot;
|
|
@@ -12738,6 +12992,17 @@ var IndicatorInputsDialog = class {
|
|
|
12738
12992
|
}
|
|
12739
12993
|
}
|
|
12740
12994
|
}
|
|
12995
|
+
const propSnap = this.propSnapshot;
|
|
12996
|
+
if (row && propSnap) {
|
|
12997
|
+
for (const p of row.props) {
|
|
12998
|
+
const snapped = propSnap[p.key];
|
|
12999
|
+
const before = snapped !== void 0 ? snapped : p.defval;
|
|
13000
|
+
if (row.propValues[p.key] !== before) {
|
|
13001
|
+
row.propValues[p.key] = before;
|
|
13002
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: before, kind: "prop" });
|
|
13003
|
+
}
|
|
13004
|
+
}
|
|
13005
|
+
}
|
|
12741
13006
|
this.close();
|
|
12742
13007
|
}
|
|
12743
13008
|
/** The footer's reset button — same chip as Cancel, pinned to the LEFT edge
|
|
@@ -12755,19 +13020,27 @@ var IndicatorInputsDialog = class {
|
|
|
12755
13020
|
const row = this.row;
|
|
12756
13021
|
if (!row) return;
|
|
12757
13022
|
const snap = this.snapshot;
|
|
13023
|
+
const propSnap = this.propSnapshot;
|
|
12758
13024
|
for (const inp of row.inputs) {
|
|
12759
13025
|
if (row.values[inp.key] !== inp.defval) {
|
|
12760
13026
|
row.values[inp.key] = inp.defval;
|
|
12761
13027
|
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value: inp.defval });
|
|
12762
13028
|
}
|
|
12763
13029
|
}
|
|
13030
|
+
for (const p of row.props) {
|
|
13031
|
+
if (row.propValues[p.key] !== p.defval) {
|
|
13032
|
+
row.propValues[p.key] = p.defval;
|
|
13033
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: p.defval, kind: "prop" });
|
|
13034
|
+
}
|
|
13035
|
+
}
|
|
12764
13036
|
this.open(row);
|
|
12765
13037
|
if (snap) this.snapshot = snap;
|
|
13038
|
+
if (propSnap) this.propSnapshot = propSnap;
|
|
12766
13039
|
}
|
|
12767
13040
|
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
12768
|
-
commit(row,
|
|
12769
|
-
row.
|
|
12770
|
-
this.host.onChange?.({ indicatorId: row.id, key, value });
|
|
13041
|
+
commit(row, decl, value) {
|
|
13042
|
+
bagOf(row, decl)[decl.key] = value;
|
|
13043
|
+
this.host.onChange?.({ indicatorId: row.id, key: decl.key, value, ...decl.prop ? { kind: "prop" } : {} });
|
|
12771
13044
|
this.refreshVisibility?.();
|
|
12772
13045
|
}
|
|
12773
13046
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
@@ -12794,7 +13067,7 @@ var IndicatorInputsDialog = class {
|
|
|
12794
13067
|
const id = idOf(lead);
|
|
12795
13068
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
12796
13069
|
if (lead.type === "bool") {
|
|
12797
|
-
const current = Boolean(row
|
|
13070
|
+
const current = Boolean(bagOf(row, lead)[lead.key] ?? lead.defval);
|
|
12798
13071
|
return append(fieldRow({
|
|
12799
13072
|
label: nameOf(lead),
|
|
12800
13073
|
id,
|
|
@@ -12803,7 +13076,7 @@ var IndicatorInputsDialog = class {
|
|
|
12803
13076
|
toggle: {
|
|
12804
13077
|
id,
|
|
12805
13078
|
checked: current,
|
|
12806
|
-
onChange: (v) => this.commit(row, lead
|
|
13079
|
+
onChange: (v) => this.commit(row, lead, v)
|
|
12807
13080
|
}
|
|
12808
13081
|
}));
|
|
12809
13082
|
}
|
|
@@ -12828,8 +13101,8 @@ var IndicatorInputsDialog = class {
|
|
|
12828
13101
|
}
|
|
12829
13102
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
12830
13103
|
buildControl(row, inp, id) {
|
|
12831
|
-
const current = row
|
|
12832
|
-
const emit = (value) => this.commit(row, inp
|
|
13104
|
+
const current = bagOf(row, inp)[inp.key] ?? inp.defval;
|
|
13105
|
+
const emit = (value) => this.commit(row, inp, value);
|
|
12833
13106
|
if (inp.type === "bool") {
|
|
12834
13107
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
12835
13108
|
}
|
|
@@ -12840,7 +13113,7 @@ var IndicatorInputsDialog = class {
|
|
|
12840
13113
|
kind: "color",
|
|
12841
13114
|
id,
|
|
12842
13115
|
theme: this.host.theme(),
|
|
12843
|
-
get: () => String(row
|
|
13116
|
+
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
12844
13117
|
onChange: (v) => emit(v)
|
|
12845
13118
|
}).el;
|
|
12846
13119
|
}
|
|
@@ -13403,6 +13676,7 @@ function timeParts(ts) {
|
|
|
13403
13676
|
}
|
|
13404
13677
|
|
|
13405
13678
|
// src/renderers/shared/InputsUI.ts
|
|
13679
|
+
var LEGEND_AT_TOP_ATTR = "data-vela-pane-at-top";
|
|
13406
13680
|
var STATUS_KEYFRAMES = "@keyframes vela-ind-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.6)}}";
|
|
13407
13681
|
var LEGEND_ICON_PX2 = 16;
|
|
13408
13682
|
var LEGEND_CTL_PX = 18;
|
|
@@ -13441,10 +13715,14 @@ var InputsUI = class {
|
|
|
13441
13715
|
this.moveApi = null;
|
|
13442
13716
|
/** Host-contributed legend actions, resolved PER ROW at render time (see setLegendActions). */
|
|
13443
13717
|
this.legendActions = null;
|
|
13718
|
+
/** Host-contributed callout bubbles, resolved PER ROW at render time (see setLegendCallouts). */
|
|
13719
|
+
this.legendCallouts = null;
|
|
13444
13720
|
/** Chrome-tooltip disposers, per row id — a tip open at removal must not outlive its row. */
|
|
13445
13721
|
this.rowTips = /* @__PURE__ */ new Map();
|
|
13446
13722
|
/** Same, for the contributed extras only (rebuilt independently by setLegendActions). */
|
|
13447
13723
|
this.extrasTips = /* @__PURE__ */ new Map();
|
|
13724
|
+
/** Same, for the contributed callouts only (rebuilt independently by setLegendCallouts). */
|
|
13725
|
+
this.calloutTips = /* @__PURE__ */ new Map();
|
|
13448
13726
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
13449
13727
|
this.moveMenu = null;
|
|
13450
13728
|
this.moveTargets = [];
|
|
@@ -13602,6 +13880,42 @@ var InputsUI = class {
|
|
|
13602
13880
|
extrasEl.appendChild(btn2);
|
|
13603
13881
|
}
|
|
13604
13882
|
}
|
|
13883
|
+
/**
|
|
13884
|
+
* Wire the host-contributed callout bubbles. Same contract as
|
|
13885
|
+
* {@link setLegendActions}: re-calling replaces the provider and re-projects the
|
|
13886
|
+
* rows already on screen.
|
|
13887
|
+
*/
|
|
13888
|
+
setLegendCallouts(provider) {
|
|
13889
|
+
this.legendCallouts = provider;
|
|
13890
|
+
for (const row of this.rows.values()) this.renderCallouts(row);
|
|
13891
|
+
}
|
|
13892
|
+
/** (Re)build one row's callout bubbles — tinted icon circles beside the title whose
|
|
13893
|
+
* click (when the view carries content) deploys a panel of text and actions. */
|
|
13894
|
+
renderCallouts(row) {
|
|
13895
|
+
this.disposeTips(this.calloutTips, row.id);
|
|
13896
|
+
for (const bubble of row.callouts) bubble.destroy();
|
|
13897
|
+
row.callouts = [];
|
|
13898
|
+
row.calloutsEl.replaceChildren();
|
|
13899
|
+
const views = this.legendCallouts?.(row.id) ?? [];
|
|
13900
|
+
row.calloutsEl.style.display = views.length > 0 ? "inline-flex" : "none";
|
|
13901
|
+
for (const view of views) {
|
|
13902
|
+
const bubble = new CalloutBubble({
|
|
13903
|
+
icon: view.icon,
|
|
13904
|
+
background: view.background,
|
|
13905
|
+
...view.color !== void 0 ? { color: view.color } : {},
|
|
13906
|
+
label: view.tooltip,
|
|
13907
|
+
...view.content !== void 0 ? { panel: view.content } : {},
|
|
13908
|
+
// The panel portals into the plot host and self-tokens: it must work on
|
|
13909
|
+
// a bare chart, where no `.vela-ui` token ancestor exists.
|
|
13910
|
+
host: this.container,
|
|
13911
|
+
theme: () => this.theme
|
|
13912
|
+
});
|
|
13913
|
+
bubble.el.dataset.legendCallout = view.id;
|
|
13914
|
+
this.tip(this.calloutTips, row.id, bubble.el, view.tooltip);
|
|
13915
|
+
row.callouts.push(bubble);
|
|
13916
|
+
row.calloutsEl.appendChild(bubble.el);
|
|
13917
|
+
}
|
|
13918
|
+
}
|
|
13605
13919
|
/** Reposition the per-pane legend containers after a layout change. */
|
|
13606
13920
|
reposition() {
|
|
13607
13921
|
for (const [paneId, lg] of this.legends) this.positionLegend(lg, paneId);
|
|
@@ -13769,6 +14083,7 @@ var InputsUI = class {
|
|
|
13769
14083
|
}
|
|
13770
14084
|
positionLegend(lg, paneId) {
|
|
13771
14085
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
14086
|
+
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
13772
14087
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
13773
14088
|
const collapsed = this.paneCollapse.has(paneId);
|
|
13774
14089
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
@@ -13844,13 +14159,13 @@ var InputsUI = class {
|
|
|
13844
14159
|
}
|
|
13845
14160
|
/** Create or update an indicator's legend row (in the legend for its pane). */
|
|
13846
14161
|
upsert(id, title, inputs, values, paneId = "price", opts = {}) {
|
|
13847
|
-
const settingsTitle = opts.settingsTitle ?? title;
|
|
13848
14162
|
const existing = this.rows.get(id);
|
|
13849
14163
|
if (existing) {
|
|
13850
14164
|
existing.title = title;
|
|
13851
|
-
existing.settingsTitle = settingsTitle;
|
|
13852
14165
|
existing.inputs = inputs;
|
|
13853
14166
|
existing.values = { ...values };
|
|
14167
|
+
existing.props = opts.props ?? [];
|
|
14168
|
+
existing.propValues = { ...opts.propValues ?? {} };
|
|
13854
14169
|
existing.titleEl.textContent = title;
|
|
13855
14170
|
if (existing.paneId !== paneId) {
|
|
13856
14171
|
existing.paneId = paneId;
|
|
@@ -13902,6 +14217,9 @@ var InputsUI = class {
|
|
|
13902
14217
|
titleWrap.appendChild(beta);
|
|
13903
14218
|
}
|
|
13904
14219
|
el.appendChild(titleWrap);
|
|
14220
|
+
const calloutsEl = document.createElement("span");
|
|
14221
|
+
calloutsEl.style.cssText = `display:none;align-items:center;gap:4px;flex:none;margin-left:${LEGEND_TITLE_STATUS_GAP_PX}px;`;
|
|
14222
|
+
el.appendChild(calloutsEl);
|
|
13905
14223
|
el.appendChild(statusEl);
|
|
13906
14224
|
const valuesEl = document.createElement("span");
|
|
13907
14225
|
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;`;
|
|
@@ -13918,13 +14236,13 @@ var InputsUI = class {
|
|
|
13918
14236
|
eye.className = "vela-ind-ctl";
|
|
13919
14237
|
eye.style.cssText = LEGEND_CTL_CSS;
|
|
13920
14238
|
eye.addEventListener("click", () => {
|
|
13921
|
-
const
|
|
13922
|
-
this.onToggleVisible?.(id, Boolean(
|
|
14239
|
+
const row2 = this.rows.get(id);
|
|
14240
|
+
this.onToggleVisible?.(id, Boolean(row2?.hidden));
|
|
13923
14241
|
});
|
|
13924
14242
|
controlsEl.appendChild(eye);
|
|
13925
14243
|
eyeEl = eye;
|
|
13926
14244
|
}
|
|
13927
|
-
if (inputs.length > 0) {
|
|
14245
|
+
if (inputs.length > 0 || (opts.props ?? []).length > 0) {
|
|
13928
14246
|
const gear = document.createElement("button");
|
|
13929
14247
|
gear.type = "button";
|
|
13930
14248
|
gear.setAttribute("aria-label", "Settings");
|
|
@@ -13964,7 +14282,9 @@ var InputsUI = class {
|
|
|
13964
14282
|
controlsEl.appendChild(close);
|
|
13965
14283
|
el.appendChild(controlsEl);
|
|
13966
14284
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
13967
|
-
|
|
14285
|
+
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 };
|
|
14286
|
+
this.rows.set(id, row);
|
|
14287
|
+
this.renderCallouts(row);
|
|
13968
14288
|
this.syncFoldToggle();
|
|
13969
14289
|
}
|
|
13970
14290
|
/** Place a row in its pane's legend — native rows PREPEND (pinned to the top), Pine rows append. */
|
|
@@ -13973,9 +14293,11 @@ var InputsUI = class {
|
|
|
13973
14293
|
else container.appendChild(el);
|
|
13974
14294
|
}
|
|
13975
14295
|
/** Reflect programmatic input changes (so a re-opened dialog shows current values). */
|
|
13976
|
-
setValues(id, values) {
|
|
14296
|
+
setValues(id, values, props) {
|
|
13977
14297
|
const row = this.rows.get(id);
|
|
13978
|
-
if (row)
|
|
14298
|
+
if (!row) return;
|
|
14299
|
+
row.values = { ...row.values, ...values };
|
|
14300
|
+
if (props) row.propValues = { ...row.propValues, ...props };
|
|
13979
14301
|
}
|
|
13980
14302
|
/**
|
|
13981
14303
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
@@ -14057,8 +14379,13 @@ var InputsUI = class {
|
|
|
14057
14379
|
syncRowActions(row) {
|
|
14058
14380
|
const open2 = row.highlighted;
|
|
14059
14381
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
14060
|
-
if (open2)
|
|
14061
|
-
|
|
14382
|
+
if (open2) {
|
|
14383
|
+
row.el.appendChild(row.statusEl);
|
|
14384
|
+
row.el.appendChild(row.calloutsEl);
|
|
14385
|
+
} else {
|
|
14386
|
+
row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
14387
|
+
row.el.insertBefore(row.calloutsEl, row.statusEl);
|
|
14388
|
+
}
|
|
14062
14389
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
14063
14390
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
14064
14391
|
if (child === row.extrasEl) {
|
|
@@ -14076,10 +14403,12 @@ var InputsUI = class {
|
|
|
14076
14403
|
}
|
|
14077
14404
|
remove(id) {
|
|
14078
14405
|
const row = this.rows.get(id);
|
|
14406
|
+
for (const bubble of row?.callouts ?? []) bubble.destroy();
|
|
14079
14407
|
row?.el.remove();
|
|
14080
14408
|
this.rows.delete(id);
|
|
14081
14409
|
this.disposeTips(this.rowTips, id);
|
|
14082
14410
|
this.disposeTips(this.extrasTips, id);
|
|
14411
|
+
this.disposeTips(this.calloutTips, id);
|
|
14083
14412
|
if (this.selectedId === id) this.selectedId = null;
|
|
14084
14413
|
if (this.inputsDialog.openId === id) this.inputsDialog.close();
|
|
14085
14414
|
this.syncFoldToggle();
|
|
@@ -14099,6 +14428,8 @@ var InputsUI = class {
|
|
|
14099
14428
|
this.rowMenu = null;
|
|
14100
14429
|
for (const id of [...this.rowTips.keys()]) this.disposeTips(this.rowTips, id);
|
|
14101
14430
|
for (const id of [...this.extrasTips.keys()]) this.disposeTips(this.extrasTips, id);
|
|
14431
|
+
for (const id of [...this.calloutTips.keys()]) this.disposeTips(this.calloutTips, id);
|
|
14432
|
+
for (const row of this.rows.values()) for (const bubble of row.callouts) bubble.destroy();
|
|
14102
14433
|
for (const lg of this.legends.values()) lg.remove();
|
|
14103
14434
|
this.legends.clear();
|
|
14104
14435
|
this.rows.clear();
|
|
@@ -14453,6 +14784,32 @@ var SIZE_PX3 = {
|
|
|
14453
14784
|
large: 16,
|
|
14454
14785
|
huge: 20
|
|
14455
14786
|
};
|
|
14787
|
+
function fontPxOf(size) {
|
|
14788
|
+
if (typeof size === "number") return size > 0 ? size : SIZE_PX3.auto;
|
|
14789
|
+
return SIZE_PX3[size] ?? SIZE_PX3.auto;
|
|
14790
|
+
}
|
|
14791
|
+
function tableHasContent(t) {
|
|
14792
|
+
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
14793
|
+
}
|
|
14794
|
+
function mergeRenderPlan(t) {
|
|
14795
|
+
const span = /* @__PURE__ */ new Map();
|
|
14796
|
+
const omit = /* @__PURE__ */ new Set();
|
|
14797
|
+
for (const m of t.merges) {
|
|
14798
|
+
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
14799
|
+
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
14800
|
+
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
14801
|
+
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
14802
|
+
}
|
|
14803
|
+
}
|
|
14804
|
+
}
|
|
14805
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
14806
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
14807
|
+
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
14808
|
+
}
|
|
14809
|
+
}
|
|
14810
|
+
for (const key of span.keys()) omit.delete(key);
|
|
14811
|
+
return { span, omit };
|
|
14812
|
+
}
|
|
14456
14813
|
var TableOverlay = class {
|
|
14457
14814
|
constructor(container, theme, paneBounds) {
|
|
14458
14815
|
this.container = container;
|
|
@@ -14473,7 +14830,9 @@ var TableOverlay = class {
|
|
|
14473
14830
|
update(tables) {
|
|
14474
14831
|
this.lastTables = tables;
|
|
14475
14832
|
this.root.replaceChildren();
|
|
14476
|
-
for (const t of tables)
|
|
14833
|
+
for (const t of tables) {
|
|
14834
|
+
if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
|
|
14835
|
+
}
|
|
14477
14836
|
}
|
|
14478
14837
|
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
14479
14838
|
reposition() {
|
|
@@ -14488,39 +14847,36 @@ var TableOverlay = class {
|
|
|
14488
14847
|
this.root.remove();
|
|
14489
14848
|
}
|
|
14490
14849
|
renderTable(t) {
|
|
14850
|
+
const b = this.paneBounds(t.paneId);
|
|
14491
14851
|
const wrap = document.createElement("div");
|
|
14492
14852
|
wrap.style.position = "absolute";
|
|
14493
|
-
|
|
14853
|
+
if (t.frameColor && t.frameWidth > 0) wrap.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
|
|
14854
|
+
this.anchor(wrap, t.position, b);
|
|
14494
14855
|
const table = document.createElement("table");
|
|
14495
14856
|
Object.assign(table.style, {
|
|
14496
14857
|
borderCollapse: "collapse",
|
|
14497
14858
|
background: t.bgColor ?? "transparent",
|
|
14498
14859
|
fontFamily: this.theme.fontFamily || "sans-serif",
|
|
14499
|
-
// Frame as an inset shadow (not a `border`) so it stays independent of the
|
|
14500
|
-
// collapsed cell borders even when frame_width != border_width.
|
|
14501
|
-
boxShadow: t.frameColor && t.frameWidth > 0 ? `inset 0 0 0 ${t.frameWidth}px ${t.frameColor}` : "none",
|
|
14502
14860
|
border: "none",
|
|
14503
14861
|
tableLayout: "auto",
|
|
14504
14862
|
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
14505
14863
|
pointerEvents: "auto"
|
|
14506
14864
|
});
|
|
14507
|
-
const span
|
|
14508
|
-
const
|
|
14509
|
-
|
|
14510
|
-
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
14511
|
-
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
14512
|
-
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
14513
|
-
if (r !== m.startRow || c !== m.startCol) skip.add(`${r}:${c}`);
|
|
14514
|
-
}
|
|
14515
|
-
}
|
|
14516
|
-
}
|
|
14865
|
+
const { span, omit } = mergeRenderPlan(t);
|
|
14866
|
+
const plotW = Math.max(0, (this.root.clientWidth || this.container.clientWidth) - b.rightAxis);
|
|
14867
|
+
const paneH = b.height;
|
|
14517
14868
|
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
14518
14869
|
for (let r = 0; r < t.rows; r += 1) {
|
|
14519
14870
|
const tr = document.createElement("tr");
|
|
14520
14871
|
for (let c = 0; c < t.columns; c += 1) {
|
|
14872
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
14521
14873
|
const cell = t.cells[r]?.[c] ?? null;
|
|
14522
|
-
if (skip.has(`${r}:${c}`) || cell?.merged) continue;
|
|
14523
14874
|
const td = document.createElement("td");
|
|
14875
|
+
if (cell === null) {
|
|
14876
|
+
Object.assign(td.style, { padding: "0", border: "none" });
|
|
14877
|
+
tr.appendChild(td);
|
|
14878
|
+
continue;
|
|
14879
|
+
}
|
|
14524
14880
|
const sp = span.get(`${r}:${c}`);
|
|
14525
14881
|
if (sp) {
|
|
14526
14882
|
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
@@ -14529,18 +14885,22 @@ var TableOverlay = class {
|
|
|
14529
14885
|
Object.assign(td.style, {
|
|
14530
14886
|
border: cellBorder,
|
|
14531
14887
|
padding: "2px 6px",
|
|
14532
|
-
background: cell
|
|
14533
|
-
color: cell
|
|
14534
|
-
textAlign: cell
|
|
14535
|
-
verticalAlign: cell
|
|
14536
|
-
fontSize: `${
|
|
14537
|
-
fontFamily: cell
|
|
14538
|
-
fontWeight: cell
|
|
14539
|
-
fontStyle: cell
|
|
14540
|
-
|
|
14888
|
+
background: cell.bgColor ?? "transparent",
|
|
14889
|
+
color: cell.textColor ?? this.theme.textColor,
|
|
14890
|
+
textAlign: cell.hAlign,
|
|
14891
|
+
verticalAlign: cell.vAlign === "top" ? "top" : cell.vAlign === "bottom" ? "bottom" : "middle",
|
|
14892
|
+
fontSize: `${fontPxOf(cell.textSize)}px`,
|
|
14893
|
+
fontFamily: cell.fontFamily === "monospace" ? "monospace" : "inherit",
|
|
14894
|
+
fontWeight: cell.bold ? "bold" : "normal",
|
|
14895
|
+
fontStyle: cell.italic ? "italic" : "normal",
|
|
14896
|
+
// Pine cell text never wraps; `\n` still breaks lines. Wrapping
|
|
14897
|
+
// used to collapse unicode sparklines and ━━━ dividers.
|
|
14898
|
+
whiteSpace: "pre"
|
|
14541
14899
|
});
|
|
14542
|
-
if (cell
|
|
14543
|
-
td.
|
|
14900
|
+
if (cell.width) td.style.width = `${cell.width / 100 * plotW}px`;
|
|
14901
|
+
if (cell.height) td.style.height = `${cell.height / 100 * paneH}px`;
|
|
14902
|
+
if (cell.tooltip) td.title = cell.tooltip;
|
|
14903
|
+
td.textContent = cell.text ?? "";
|
|
14544
14904
|
tr.appendChild(td);
|
|
14545
14905
|
}
|
|
14546
14906
|
table.appendChild(tr);
|
|
@@ -15425,8 +15785,14 @@ var WebGL2Backend = class {
|
|
|
15425
15785
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
15426
15786
|
};
|
|
15427
15787
|
b.alpha = this.modelAlpha;
|
|
15428
|
-
for (const m of models) for (const bgSpan of m.backgrounds) this.emitBackground(b, bgSpan, pane, coords);
|
|
15429
|
-
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));
|
|
15788
|
+
for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
|
|
15789
|
+
for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
15790
|
+
if (isPrice) {
|
|
15791
|
+
for (const m of scene.indicators.values()) {
|
|
15792
|
+
for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
|
|
15793
|
+
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
15794
|
+
}
|
|
15795
|
+
}
|
|
15430
15796
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
15431
15797
|
let candleDrawn = false;
|
|
15432
15798
|
for (const m of models) {
|
|
@@ -15440,7 +15806,7 @@ var WebGL2Backend = class {
|
|
|
15440
15806
|
b.alpha = this.modelAlpha;
|
|
15441
15807
|
const off = scene.offsetOf(m.id);
|
|
15442
15808
|
const mp = effPane(m);
|
|
15443
|
-
for (const s of m.series) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
15809
|
+
for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
15444
15810
|
}
|
|
15445
15811
|
if (drawCandles && !candleDrawn) {
|
|
15446
15812
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -15449,6 +15815,12 @@ var WebGL2Backend = class {
|
|
|
15449
15815
|
}
|
|
15450
15816
|
drawSlicesUpTo(Infinity);
|
|
15451
15817
|
b.alpha = this.modelAlpha;
|
|
15818
|
+
if (isPrice) {
|
|
15819
|
+
for (const m of scene.indicators.values()) {
|
|
15820
|
+
const off = scene.offsetOf(m.id);
|
|
15821
|
+
for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
|
|
15822
|
+
}
|
|
15823
|
+
}
|
|
15452
15824
|
for (const m of models) {
|
|
15453
15825
|
const mp = effPane(m);
|
|
15454
15826
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -15495,7 +15867,7 @@ var WebGL2Backend = class {
|
|
|
15495
15867
|
for (const pane of scene.orderedPanes()) {
|
|
15496
15868
|
const b = this.glowBatch;
|
|
15497
15869
|
b.reset();
|
|
15498
|
-
this.emitGlowSources(b, scene
|
|
15870
|
+
this.emitGlowSources(b, scene, pane, coords, i0, i1);
|
|
15499
15871
|
if (b.vertexCount === 0) continue;
|
|
15500
15872
|
const topH = Math.round(pane.bounds.top * dpr * 0.5);
|
|
15501
15873
|
const botH = Math.round((pane.bounds.top + pane.bounds.height) * dpr * 0.5);
|
|
@@ -15564,15 +15936,23 @@ var WebGL2Backend = class {
|
|
|
15564
15936
|
gl.bindVertexArray(null);
|
|
15565
15937
|
return true;
|
|
15566
15938
|
}
|
|
15567
|
-
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
15568
|
-
|
|
15569
|
-
|
|
15570
|
-
|
|
15571
|
-
|
|
15572
|
-
|
|
15573
|
-
|
|
15574
|
-
|
|
15575
|
-
|
|
15939
|
+
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
15940
|
+
* Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
|
|
15941
|
+
emitGlowSources(b, scene, pane, coords, i0, i1) {
|
|
15942
|
+
const emitOne = (s, off) => {
|
|
15943
|
+
if (!isLineLikeSeries(s) || s.visible === false) return;
|
|
15944
|
+
if (s.kind === "histogram" || s.kind === "columns") return;
|
|
15945
|
+
if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
|
|
15946
|
+
else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
|
|
15947
|
+
};
|
|
15948
|
+
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
15949
|
+
const off = scene.offsetOf(m.id);
|
|
15950
|
+
for (const s of m.series) if (s.overlay !== true) emitOne(s, off);
|
|
15951
|
+
}
|
|
15952
|
+
if (pane.kind === "price") {
|
|
15953
|
+
for (const m of scene.indicators.values()) {
|
|
15954
|
+
const off = scene.offsetOf(m.id);
|
|
15955
|
+
for (const s of m.series) if (s.overlay === true) emitOne(s, off);
|
|
15576
15956
|
}
|
|
15577
15957
|
}
|
|
15578
15958
|
}
|
|
@@ -15855,9 +16235,9 @@ var WebGL2Backend = class {
|
|
|
15855
16235
|
b.alpha = this.candleBodyAlpha;
|
|
15856
16236
|
b.rect(g.bodyX, bodyTop, g.bodyW, bodyH, c);
|
|
15857
16237
|
}
|
|
15858
|
-
if (cs.borderVisible ||
|
|
16238
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
15859
16239
|
b.alpha = this.candleStructureAlpha;
|
|
15860
|
-
const bord = cs.borderVisible
|
|
16240
|
+
const bord = cs.borderVisible ? parseColor((isUp ? cs.borderUpColor : cs.borderDownColor) ?? bodyColorStr) : c;
|
|
15861
16241
|
const bw = Math.max(0, g.bodyW - 1);
|
|
15862
16242
|
const bh = Math.max(0, bodyH - 1);
|
|
15863
16243
|
b.rectStroke(g.bodyX + 0.5, bodyTop + 0.5, bw, bh, 1, bord);
|
|
@@ -17409,8 +17789,14 @@ var Canvas2dBackend = class {
|
|
|
17409
17789
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
17410
17790
|
};
|
|
17411
17791
|
ctx.globalAlpha = this.modelAlpha;
|
|
17412
|
-
for (const m of models) for (const bg of m.backgrounds) this.drawBackground(ctx, bg, pane, coords);
|
|
17413
|
-
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));
|
|
17792
|
+
for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
|
|
17793
|
+
for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
|
|
17794
|
+
if (isPrice) {
|
|
17795
|
+
for (const m of scene.indicators.values()) {
|
|
17796
|
+
for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
|
|
17797
|
+
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
17798
|
+
}
|
|
17799
|
+
}
|
|
17414
17800
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
17415
17801
|
let si = 0;
|
|
17416
17802
|
const drawSlicesUpTo = (z) => {
|
|
@@ -17432,7 +17818,7 @@ var Canvas2dBackend = class {
|
|
|
17432
17818
|
ctx.globalAlpha = this.modelAlpha;
|
|
17433
17819
|
const off = scene.offsetOf(m.id);
|
|
17434
17820
|
const mp = effPane(m);
|
|
17435
|
-
for (const s of m.series) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
17821
|
+
for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
17436
17822
|
}
|
|
17437
17823
|
if (drawCandles && !candleDrawn) {
|
|
17438
17824
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -17440,6 +17826,13 @@ var Canvas2dBackend = class {
|
|
|
17440
17826
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
17441
17827
|
}
|
|
17442
17828
|
drawSlicesUpTo(Infinity);
|
|
17829
|
+
if (isPrice) {
|
|
17830
|
+
ctx.globalAlpha = this.modelAlpha;
|
|
17831
|
+
for (const m of scene.indicators.values()) {
|
|
17832
|
+
const off = scene.offsetOf(m.id);
|
|
17833
|
+
for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
|
|
17834
|
+
}
|
|
17835
|
+
}
|
|
17443
17836
|
ctx.globalAlpha = this.modelAlpha;
|
|
17444
17837
|
for (const m of models) {
|
|
17445
17838
|
const mp = effPane(m);
|
|
@@ -17711,9 +18104,9 @@ var Canvas2dBackend = class {
|
|
|
17711
18104
|
ctx.fillStyle = color;
|
|
17712
18105
|
ctx.fillRect(g.bodyX, top, g.bodyW, bodyH);
|
|
17713
18106
|
}
|
|
17714
|
-
if (cs.borderVisible ||
|
|
18107
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
17715
18108
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
17716
|
-
ctx.strokeStyle = cs.borderVisible
|
|
18109
|
+
ctx.strokeStyle = cs.borderVisible ? (up ? cs.borderUpColor : cs.borderDownColor) ?? color : color;
|
|
17717
18110
|
ctx.lineWidth = 1;
|
|
17718
18111
|
const bw = Math.max(0, g.bodyW - 1);
|
|
17719
18112
|
const bh = Math.max(0, bodyH - 1);
|
|
@@ -18145,6 +18538,8 @@ var DrawingSceneRenderer = class {
|
|
|
18145
18538
|
this.set = set;
|
|
18146
18539
|
/** measureText width cache, keyed by `${font} ${text}`, persists across frames. */
|
|
18147
18540
|
this.widthCache = /* @__PURE__ */ new Map();
|
|
18541
|
+
/** Tooltip hit-rects captured while drawing the CURRENT set (rebuilt per render). */
|
|
18542
|
+
this.tipRegions = [];
|
|
18148
18543
|
/**
|
|
18149
18544
|
* Index offset of the CURRENT set's model: its `xloc:'bar_index'` coordinates count
|
|
18150
18545
|
* from the model's anchor bar, so they shift by this to land on chart logical indices.
|
|
@@ -18165,8 +18560,13 @@ var DrawingSceneRenderer = class {
|
|
|
18165
18560
|
const s = this.set;
|
|
18166
18561
|
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
18167
18562
|
}
|
|
18563
|
+
/** Tooltip hit-rects of the labels drawn by the LAST `render` call (same coords as `ctx`). */
|
|
18564
|
+
labelTipRegions() {
|
|
18565
|
+
return this.tipRegions;
|
|
18566
|
+
}
|
|
18168
18567
|
/** Draw the whole set into `ctx` using the supplied coordinate closures. */
|
|
18169
18568
|
render(ctx, W, H, xOf, yOf) {
|
|
18569
|
+
this.tipRegions = [];
|
|
18170
18570
|
if (this.isEmpty()) return;
|
|
18171
18571
|
ctx.save();
|
|
18172
18572
|
this.drawLinefills(ctx, W, H, xOf, yOf);
|
|
@@ -18514,13 +18914,35 @@ var DrawingSceneRenderer = class {
|
|
|
18514
18914
|
if (this.isPointShape(lb.style)) {
|
|
18515
18915
|
if (!lb.noFill) this.drawLabelShape(ctx, lb.style, px, py, fontPx, color);
|
|
18516
18916
|
if (lb.text) this.drawLabelText(ctx, lb, px, py + fontPx, fontPx);
|
|
18517
|
-
|
|
18518
|
-
|
|
18917
|
+
if (lb.tooltip) {
|
|
18918
|
+
const r = Math.max(4, fontPx * 0.6) + 3;
|
|
18919
|
+
this.tipRegions.push({ left: px - r, top: py - r, right: px + r, bottom: py + r, text: lb.tooltip });
|
|
18920
|
+
}
|
|
18921
|
+
} else if (lb.style === "none" || lb.style === "text_outline") {
|
|
18922
|
+
if (lb.text) {
|
|
18923
|
+
this.drawLabelText(ctx, lb, px, py, fontPx, lb.style === "text_outline");
|
|
18924
|
+
if (lb.tooltip) this.tipRegions.push(this.textRegion(ctx, lb, px, py, fontPx, lb.tooltip));
|
|
18925
|
+
}
|
|
18519
18926
|
} else {
|
|
18520
|
-
this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
18927
|
+
const r = this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
18928
|
+
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 });
|
|
18521
18929
|
}
|
|
18522
18930
|
}
|
|
18523
18931
|
}
|
|
18932
|
+
/** Canvas font for a label's text — family + Pine `text_formatting` (bold/italic). */
|
|
18933
|
+
labelFont(lb, fontPx) {
|
|
18934
|
+
const family = lb.fontFamily === "monospace" ? "monospace" : this.deps.theme.fontFamily || "sans-serif";
|
|
18935
|
+
return `${lb.italic ? "italic " : ""}${lb.bold ? "bold " : ""}${fontPx}px ${family}`;
|
|
18936
|
+
}
|
|
18937
|
+
/** Hover rect of a text-only label (style none/text_outline/noFill), centered like drawLabelText. */
|
|
18938
|
+
textRegion(ctx, lb, cx, cy, fontPx, text) {
|
|
18939
|
+
const font = this.labelFont(lb, fontPx);
|
|
18940
|
+
const lines = (lb.text ?? "").split("\n");
|
|
18941
|
+
const w = Math.max(1, ...lines.map((l) => this.measure(ctx, font, l)));
|
|
18942
|
+
const h = fontPx * 1.25 * lines.length;
|
|
18943
|
+
const left = lb.textAlign === "left" ? cx : lb.textAlign === "right" ? cx - w : cx - w / 2;
|
|
18944
|
+
return { left, top: cy - h / 2, right: left + w, bottom: cy + h / 2, text };
|
|
18945
|
+
}
|
|
18524
18946
|
isPointShape(style) {
|
|
18525
18947
|
switch (style) {
|
|
18526
18948
|
case "circle":
|
|
@@ -18539,8 +18961,7 @@ var DrawingSceneRenderer = class {
|
|
|
18539
18961
|
}
|
|
18540
18962
|
}
|
|
18541
18963
|
drawLabelText(ctx, lb, cx, cy, fontPx, outline = false) {
|
|
18542
|
-
|
|
18543
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
18964
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
18544
18965
|
ctx.textAlign = lb.textAlign === "left" ? "left" : lb.textAlign === "right" ? "right" : "center";
|
|
18545
18966
|
ctx.textBaseline = "middle";
|
|
18546
18967
|
const lines = lb.text.split("\n");
|
|
@@ -18620,9 +19041,9 @@ var DrawingSceneRenderer = class {
|
|
|
18620
19041
|
ctx.fill();
|
|
18621
19042
|
}
|
|
18622
19043
|
}
|
|
19044
|
+
/** Draw a bubble label; returns the bubble body rect (for tooltip hit-testing). */
|
|
18623
19045
|
drawBubble(ctx, lb, px, py, fontPx, color) {
|
|
18624
|
-
|
|
18625
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
19046
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
18626
19047
|
const lines = (lb.text ?? "").split("\n");
|
|
18627
19048
|
const padX = 6;
|
|
18628
19049
|
const padY = 4;
|
|
@@ -18680,38 +19101,51 @@ var DrawingSceneRenderer = class {
|
|
|
18680
19101
|
by = py - h - ptr;
|
|
18681
19102
|
pointer = "down";
|
|
18682
19103
|
}
|
|
18683
|
-
|
|
18684
|
-
|
|
18685
|
-
|
|
18686
|
-
if (pointer !== "none") {
|
|
18687
|
-
ctx.beginPath();
|
|
18688
|
-
if (pointer === "down") {
|
|
18689
|
-
ctx.moveTo(px - ptr, by + h);
|
|
18690
|
-
ctx.lineTo(px + ptr, by + h);
|
|
18691
|
-
ctx.lineTo(px, py);
|
|
18692
|
-
} else if (pointer === "up") {
|
|
18693
|
-
ctx.moveTo(px - ptr, by);
|
|
18694
|
-
ctx.lineTo(px + ptr, by);
|
|
18695
|
-
ctx.lineTo(px, py);
|
|
18696
|
-
} else if (pointer === "left") {
|
|
18697
|
-
ctx.moveTo(bx, py - ptr);
|
|
18698
|
-
ctx.lineTo(bx, py + ptr);
|
|
18699
|
-
ctx.lineTo(px, py);
|
|
18700
|
-
} else {
|
|
18701
|
-
ctx.moveTo(bx + w, py - ptr);
|
|
18702
|
-
ctx.lineTo(bx + w, py + ptr);
|
|
18703
|
-
ctx.lineTo(px, py);
|
|
18704
|
-
}
|
|
18705
|
-
ctx.closePath();
|
|
19104
|
+
if (!lb.noFill) {
|
|
19105
|
+
ctx.fillStyle = color;
|
|
19106
|
+
this.roundRect(ctx, bx, by, w, h, 4);
|
|
18706
19107
|
ctx.fill();
|
|
19108
|
+
if (pointer !== "none") {
|
|
19109
|
+
ctx.beginPath();
|
|
19110
|
+
if (pointer === "down") {
|
|
19111
|
+
ctx.moveTo(px - ptr, by + h);
|
|
19112
|
+
ctx.lineTo(px + ptr, by + h);
|
|
19113
|
+
ctx.lineTo(px, py);
|
|
19114
|
+
} else if (pointer === "up") {
|
|
19115
|
+
ctx.moveTo(px - ptr, by);
|
|
19116
|
+
ctx.lineTo(px + ptr, by);
|
|
19117
|
+
ctx.lineTo(px, py);
|
|
19118
|
+
} else if (pointer === "left") {
|
|
19119
|
+
ctx.moveTo(bx, py - ptr);
|
|
19120
|
+
ctx.lineTo(bx, py + ptr);
|
|
19121
|
+
ctx.lineTo(px, py);
|
|
19122
|
+
} else {
|
|
19123
|
+
ctx.moveTo(bx + w, py - ptr);
|
|
19124
|
+
ctx.lineTo(bx + w, py + ptr);
|
|
19125
|
+
ctx.lineTo(px, py);
|
|
19126
|
+
}
|
|
19127
|
+
ctx.closePath();
|
|
19128
|
+
ctx.fill();
|
|
19129
|
+
}
|
|
18707
19130
|
}
|
|
18708
19131
|
if (lb.text) {
|
|
18709
|
-
ctx.fillStyle = lb.textColor ?? contrastColor(color);
|
|
18710
|
-
ctx.textAlign = "center";
|
|
19132
|
+
ctx.fillStyle = lb.textColor ?? (lb.noFill ? this.deps.theme.textColor : contrastColor(color));
|
|
18711
19133
|
ctx.textBaseline = "middle";
|
|
19134
|
+
let tx;
|
|
19135
|
+
if (lb.textAlign === "left") {
|
|
19136
|
+
ctx.textAlign = "left";
|
|
19137
|
+
tx = bx + padX;
|
|
19138
|
+
} else if (lb.textAlign === "right") {
|
|
19139
|
+
ctx.textAlign = "right";
|
|
19140
|
+
tx = bx + w - padX;
|
|
19141
|
+
} else {
|
|
19142
|
+
ctx.textAlign = "center";
|
|
19143
|
+
tx = bx + w / 2;
|
|
19144
|
+
}
|
|
18712
19145
|
const startY = by + padY + lineH / 2;
|
|
18713
|
-
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i],
|
|
19146
|
+
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i], tx, startY + i * lineH);
|
|
18714
19147
|
}
|
|
19148
|
+
return { x: bx, y: by, w, h };
|
|
18715
19149
|
}
|
|
18716
19150
|
roundRect(ctx, x, y, w, h, r) {
|
|
18717
19151
|
const rr = Math.min(r, w / 2, h / 2);
|
|
@@ -18949,6 +19383,8 @@ var ChromeRenderer = class {
|
|
|
18949
19383
|
this.axisTextColor = DARK_THEME.textColor;
|
|
18950
19384
|
// Shared Pine-drawing renderer (line/box/label/polyline/linefill); widthCache persists.
|
|
18951
19385
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
19386
|
+
// Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per render).
|
|
19387
|
+
this.labelTips = [];
|
|
18952
19388
|
}
|
|
18953
19389
|
mount(canvas) {
|
|
18954
19390
|
this.canvas = canvas;
|
|
@@ -18990,6 +19426,7 @@ var ChromeRenderer = class {
|
|
|
18990
19426
|
const dataW = coords.width;
|
|
18991
19427
|
const dataH = coords.height;
|
|
18992
19428
|
this.axisTextColor = surface?.textColor ?? theme.textColor;
|
|
19429
|
+
this.labelTips = [];
|
|
18993
19430
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
18994
19431
|
ctx.clearRect(0, 0, fullW, fullH);
|
|
18995
19432
|
if (surface && (fullW > dataW || fullH > dataH)) {
|
|
@@ -19099,6 +19536,17 @@ var ChromeRenderer = class {
|
|
|
19099
19536
|
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
19100
19537
|
);
|
|
19101
19538
|
ctx.restore();
|
|
19539
|
+
for (const r of this.drawScene.labelTipRegions()) {
|
|
19540
|
+
this.labelTips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
19541
|
+
}
|
|
19542
|
+
}
|
|
19543
|
+
/** Tooltip of the topmost label under a plot-space point, or null. Fed by the last render. */
|
|
19544
|
+
labelTooltipAt(x, y) {
|
|
19545
|
+
for (let i = this.labelTips.length - 1; i >= 0; i -= 1) {
|
|
19546
|
+
const r = this.labelTips[i];
|
|
19547
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
19548
|
+
}
|
|
19549
|
+
return null;
|
|
19102
19550
|
}
|
|
19103
19551
|
// ── axes ──
|
|
19104
19552
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
@@ -19315,6 +19763,68 @@ function formatCountdown(ms) {
|
|
|
19315
19763
|
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
|
|
19316
19764
|
}
|
|
19317
19765
|
|
|
19766
|
+
// src/renderers/native/chrome/LabelTooltip.ts
|
|
19767
|
+
var HOVER_DELAY_MS = 350;
|
|
19768
|
+
var LabelTooltip = class {
|
|
19769
|
+
constructor(plot, deps) {
|
|
19770
|
+
this.plot = plot;
|
|
19771
|
+
this.deps = deps;
|
|
19772
|
+
this.tip = null;
|
|
19773
|
+
this.timer = null;
|
|
19774
|
+
/** Text of the currently open OR armed tip — dedupes moves inside one label. */
|
|
19775
|
+
this.current = null;
|
|
19776
|
+
this.onMove = (e) => {
|
|
19777
|
+
if (e.pointerType !== "mouse") return;
|
|
19778
|
+
const rect = this.plot.getBoundingClientRect();
|
|
19779
|
+
const x = e.clientX - rect.left;
|
|
19780
|
+
const y = e.clientY - rect.top;
|
|
19781
|
+
const text = this.deps.lookup(x, y);
|
|
19782
|
+
if (!text) {
|
|
19783
|
+
this.clear();
|
|
19784
|
+
return;
|
|
19785
|
+
}
|
|
19786
|
+
if (text === this.current) return;
|
|
19787
|
+
this.clear();
|
|
19788
|
+
this.current = text;
|
|
19789
|
+
this.timer = window.setTimeout(() => this.show(text, x, y), HOVER_DELAY_MS);
|
|
19790
|
+
};
|
|
19791
|
+
this.onLeave = () => {
|
|
19792
|
+
this.clear();
|
|
19793
|
+
};
|
|
19794
|
+
plot.addEventListener("pointermove", this.onMove);
|
|
19795
|
+
plot.addEventListener("pointerleave", this.onLeave);
|
|
19796
|
+
plot.addEventListener("pointerdown", this.onLeave);
|
|
19797
|
+
}
|
|
19798
|
+
destroy() {
|
|
19799
|
+
this.plot.removeEventListener("pointermove", this.onMove);
|
|
19800
|
+
this.plot.removeEventListener("pointerleave", this.onLeave);
|
|
19801
|
+
this.plot.removeEventListener("pointerdown", this.onLeave);
|
|
19802
|
+
this.clear();
|
|
19803
|
+
}
|
|
19804
|
+
show(text, x, y) {
|
|
19805
|
+
const doc = this.plot.ownerDocument;
|
|
19806
|
+
const tip = doc.createElement("div");
|
|
19807
|
+
tip.textContent = text;
|
|
19808
|
+
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;";
|
|
19809
|
+
applyChromeTokens(tip, this.deps.theme());
|
|
19810
|
+
this.plot.appendChild(tip);
|
|
19811
|
+
const pw = this.plot.clientWidth;
|
|
19812
|
+
const ph = this.plot.clientHeight;
|
|
19813
|
+
tip.style.left = `${Math.max(0, Math.min(x + 12, pw - tip.offsetWidth - 4))}px`;
|
|
19814
|
+
tip.style.top = `${Math.max(0, Math.min(y + 16, ph - tip.offsetHeight - 4))}px`;
|
|
19815
|
+
this.tip = tip;
|
|
19816
|
+
}
|
|
19817
|
+
clear() {
|
|
19818
|
+
if (this.timer !== null) {
|
|
19819
|
+
clearTimeout(this.timer);
|
|
19820
|
+
this.timer = null;
|
|
19821
|
+
}
|
|
19822
|
+
this.tip?.remove();
|
|
19823
|
+
this.tip = null;
|
|
19824
|
+
this.current = null;
|
|
19825
|
+
}
|
|
19826
|
+
};
|
|
19827
|
+
|
|
19318
19828
|
// src/renderers/native/chrome/CrosshairRenderer.ts
|
|
19319
19829
|
var CrosshairRenderer = class {
|
|
19320
19830
|
constructor() {
|
|
@@ -25106,19 +25616,8 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
25106
25616
|
for (const model of models) {
|
|
25107
25617
|
const off = offsetOf(model.id);
|
|
25108
25618
|
for (const s of model.series) {
|
|
25109
|
-
if (s.
|
|
25110
|
-
|
|
25111
|
-
const b = s.bars[i - off];
|
|
25112
|
-
if (b) {
|
|
25113
|
-
consider(b.high);
|
|
25114
|
-
consider(b.low);
|
|
25115
|
-
}
|
|
25116
|
-
}
|
|
25117
|
-
} else if (isLineLikeSeries(s)) {
|
|
25118
|
-
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
25119
|
-
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
25120
|
-
else if (s.style?.base != null) consider(s.style.base);
|
|
25121
|
-
}
|
|
25619
|
+
if (s.overlay === true) continue;
|
|
25620
|
+
considerSeries(s, i0, i1, off, consider);
|
|
25122
25621
|
}
|
|
25123
25622
|
for (const pl of model.priceLines) consider(pl.price);
|
|
25124
25623
|
}
|
|
@@ -25140,6 +25639,36 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
25140
25639
|
const span = max - min;
|
|
25141
25640
|
return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
|
|
25142
25641
|
}
|
|
25642
|
+
function considerSeries(s, i0, i1, off, consider) {
|
|
25643
|
+
if (s.kind === "candle" || s.kind === "bar") {
|
|
25644
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
25645
|
+
const b = s.bars[i - off];
|
|
25646
|
+
if (b) {
|
|
25647
|
+
consider(b.high);
|
|
25648
|
+
consider(b.low);
|
|
25649
|
+
}
|
|
25650
|
+
}
|
|
25651
|
+
} else if (isLineLikeSeries(s)) {
|
|
25652
|
+
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
25653
|
+
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
25654
|
+
else if (s.style?.base != null) consider(s.style.base);
|
|
25655
|
+
}
|
|
25656
|
+
}
|
|
25657
|
+
function overlaySeriesRange(models, i0, i1, offsetOf = () => 0) {
|
|
25658
|
+
let min = Infinity;
|
|
25659
|
+
let max = -Infinity;
|
|
25660
|
+
const consider = (v) => {
|
|
25661
|
+
if (v != null && Number.isFinite(v)) {
|
|
25662
|
+
if (v < min) min = v;
|
|
25663
|
+
if (v > max) max = v;
|
|
25664
|
+
}
|
|
25665
|
+
};
|
|
25666
|
+
for (const model of models) {
|
|
25667
|
+
const off = offsetOf(model.id);
|
|
25668
|
+
for (const s of model.series) if (s.overlay === true) considerSeries(s, i0, i1, off, consider);
|
|
25669
|
+
}
|
|
25670
|
+
return min === Infinity ? null : { min, max };
|
|
25671
|
+
}
|
|
25143
25672
|
function expandScaleByPixels(scale, heightPx, abovePx, belowPx) {
|
|
25144
25673
|
if (abovePx <= 0 && belowPx <= 0) return scale;
|
|
25145
25674
|
const content = heightPx - abovePx - belowPx;
|
|
@@ -25922,6 +26451,8 @@ var NativeRenderer = class {
|
|
|
25922
26451
|
this.glowAmount = 0;
|
|
25923
26452
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
25924
26453
|
this.chrome = new ChromeRenderer();
|
|
26454
|
+
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
26455
|
+
this.labelTooltip = null;
|
|
25925
26456
|
this.crosshairLayer = new CrosshairRenderer();
|
|
25926
26457
|
/** 1 Hz repaint pump so the price-axis countdown-to-bar-close ticks; null when off. */
|
|
25927
26458
|
this.countdownTimer = null;
|
|
@@ -25932,6 +26463,8 @@ var NativeRenderer = class {
|
|
|
25932
26463
|
this.indicatorValuesOn = true;
|
|
25933
26464
|
/** Host-contributed legend actions — held here so a rebuild of the legend re-wires them. */
|
|
25934
26465
|
this.legendActionsProvider = null;
|
|
26466
|
+
/** Host-contributed legend callouts — held here so a rebuild of the legend re-wires them. */
|
|
26467
|
+
this.legendCalloutsProvider = null;
|
|
25935
26468
|
/** Host override of the legend's fold toggle — held here so a remount re-applies it. */
|
|
25936
26469
|
this.legendOverviewAction = null;
|
|
25937
26470
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -27040,6 +27573,10 @@ var NativeRenderer = class {
|
|
|
27040
27573
|
this.plot.appendChild(this.scrollButton);
|
|
27041
27574
|
this.plot.addEventListener("pointermove", this.onScrollProximityMove);
|
|
27042
27575
|
this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
|
|
27576
|
+
this.labelTooltip = new LabelTooltip(this.plot, {
|
|
27577
|
+
theme: () => this.chromeTheme(),
|
|
27578
|
+
lookup: (x, y) => this.chrome.labelTooltipAt(x, y)
|
|
27579
|
+
});
|
|
27043
27580
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
27044
27581
|
projector: () => this.drawingProjector(),
|
|
27045
27582
|
dpr: () => this.coords.dpr,
|
|
@@ -27079,9 +27616,10 @@ var NativeRenderer = class {
|
|
|
27079
27616
|
this.inputsUI.setDialogHost(this.dialogHost);
|
|
27080
27617
|
this.inputsUI.setSymbolPicker(this.symbolPicker);
|
|
27081
27618
|
this.inputsUI.setLegendActions(this.legendActionsProvider);
|
|
27619
|
+
this.inputsUI.setLegendCallouts(this.legendCalloutsProvider);
|
|
27082
27620
|
this.inputsUI.setLegendOverviewAction(this.legendOverviewAction);
|
|
27083
27621
|
this.inputsUI.setOnChange((c) => {
|
|
27084
|
-
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value });
|
|
27622
|
+
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value, ...c.kind ? { kind: c.kind } : {} });
|
|
27085
27623
|
});
|
|
27086
27624
|
this.inputsUI.setOnRemove((id) => {
|
|
27087
27625
|
for (const cb of this.removeIndicatorCbs) cb(id);
|
|
@@ -27271,6 +27809,8 @@ var NativeRenderer = class {
|
|
|
27271
27809
|
this.settingsButton = null;
|
|
27272
27810
|
this.plot?.removeEventListener("pointermove", this.onScrollProximityMove);
|
|
27273
27811
|
this.plot?.removeEventListener("pointerleave", this.onScrollProximityLeave);
|
|
27812
|
+
this.labelTooltip?.destroy();
|
|
27813
|
+
this.labelTooltip = null;
|
|
27274
27814
|
this.scrollButton?.remove();
|
|
27275
27815
|
this.scrollButton = null;
|
|
27276
27816
|
for (const l of this.extLayers) l.instance.destroy?.();
|
|
@@ -27511,7 +28051,7 @@ var NativeRenderer = class {
|
|
|
27511
28051
|
else this.scene.assignIndicatorZ(model.id);
|
|
27512
28052
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
27513
28053
|
native: !!model.native,
|
|
27514
|
-
...model.
|
|
28054
|
+
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
27515
28055
|
});
|
|
27516
28056
|
this.syncTables(model);
|
|
27517
28057
|
if (model.native?.type === "volume") {
|
|
@@ -27562,8 +28102,8 @@ var NativeRenderer = class {
|
|
|
27562
28102
|
this.paneControls?.refresh();
|
|
27563
28103
|
this.scheduler.invalidate(4 /* Full */);
|
|
27564
28104
|
}
|
|
27565
|
-
setIndicatorInputs(handle, values) {
|
|
27566
|
-
this.inputsUI.setValues(handle.id, values);
|
|
28105
|
+
setIndicatorInputs(handle, values, props) {
|
|
28106
|
+
this.inputsUI.setValues(handle.id, values, props);
|
|
27567
28107
|
}
|
|
27568
28108
|
setSymbolPicker(picker) {
|
|
27569
28109
|
this.symbolPicker = picker;
|
|
@@ -27573,6 +28113,10 @@ var NativeRenderer = class {
|
|
|
27573
28113
|
this.legendActionsProvider = provider;
|
|
27574
28114
|
this.inputsUI?.setLegendActions(provider);
|
|
27575
28115
|
}
|
|
28116
|
+
setLegendCallouts(provider) {
|
|
28117
|
+
this.legendCalloutsProvider = provider;
|
|
28118
|
+
this.inputsUI?.setLegendCallouts(provider);
|
|
28119
|
+
}
|
|
27576
28120
|
setLegendOverviewAction(action) {
|
|
27577
28121
|
this.legendOverviewAction = action;
|
|
27578
28122
|
this.inputsUI?.setLegendOverviewAction(action);
|
|
@@ -28553,7 +29097,11 @@ var NativeRenderer = class {
|
|
|
28553
29097
|
}
|
|
28554
29098
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
28555
29099
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
28556
|
-
|
|
29100
|
+
let dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
29101
|
+
if (pane === pricePane) {
|
|
29102
|
+
const or = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
|
|
29103
|
+
if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
|
|
29104
|
+
}
|
|
28557
29105
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
28558
29106
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
28559
29107
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -28827,6 +29375,7 @@ var NativeRenderer = class {
|
|
|
28827
29375
|
for (const m of models) {
|
|
28828
29376
|
const off = this.scene.offsetOf(m.id);
|
|
28829
29377
|
for (const s of m.series) {
|
|
29378
|
+
if (s.overlay === true) continue;
|
|
28830
29379
|
if (isLineLikeSeries(s)) {
|
|
28831
29380
|
const v = s.points[i0 - off]?.value;
|
|
28832
29381
|
if (v != null && Number.isFinite(v)) return v;
|
|
@@ -29732,6 +30281,19 @@ function unregisterLegendAction(id) {
|
|
|
29732
30281
|
function legendActions() {
|
|
29733
30282
|
return [...legendRegistry.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
29734
30283
|
}
|
|
30284
|
+
var calloutRegistry = /* @__PURE__ */ new Map();
|
|
30285
|
+
function registerLegendCallout(desc) {
|
|
30286
|
+
calloutRegistry.set(desc.id, desc);
|
|
30287
|
+
return () => {
|
|
30288
|
+
if (calloutRegistry.get(desc.id) === desc) calloutRegistry.delete(desc.id);
|
|
30289
|
+
};
|
|
30290
|
+
}
|
|
30291
|
+
function unregisterLegendCallout(id) {
|
|
30292
|
+
calloutRegistry.delete(id);
|
|
30293
|
+
}
|
|
30294
|
+
function legendCallouts() {
|
|
30295
|
+
return [...calloutRegistry.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
30296
|
+
}
|
|
29735
30297
|
var stateHandlers = /* @__PURE__ */ new Map();
|
|
29736
30298
|
function registerStatePersistence(handler) {
|
|
29737
30299
|
stateHandlers.set(handler.key, handler);
|
|
@@ -29840,6 +30402,7 @@ exports.getNativeIndicator = getNativeIndicator;
|
|
|
29840
30402
|
exports.iconMarkup = iconMarkup;
|
|
29841
30403
|
exports.inputVisible = inputVisible;
|
|
29842
30404
|
exports.legendActions = legendActions;
|
|
30405
|
+
exports.legendCallouts = legendCallouts;
|
|
29843
30406
|
exports.nativeIndicatorDescriptors = nativeIndicatorDescriptors;
|
|
29844
30407
|
exports.nativeIndicatorTypes = nativeIndicatorTypes;
|
|
29845
30408
|
exports.normalizeSettingsRow = normalizeSettingsRow;
|
|
@@ -29848,6 +30411,7 @@ exports.registerDefaultEngine = registerDefaultEngine;
|
|
|
29848
30411
|
exports.registerDrawingType = registerDrawingType;
|
|
29849
30412
|
exports.registerIcon = registerIcon;
|
|
29850
30413
|
exports.registerLegendAction = registerLegendAction;
|
|
30414
|
+
exports.registerLegendCallout = registerLegendCallout;
|
|
29851
30415
|
exports.registerNativeIndicator = registerNativeIndicator;
|
|
29852
30416
|
exports.registerRendererDefaults = registerRendererDefaults;
|
|
29853
30417
|
exports.registerRendererLayer = registerRendererLayer;
|
|
@@ -29872,6 +30436,7 @@ exports.topbarActionOverride = topbarActionOverride;
|
|
|
29872
30436
|
exports.unregisterChartType = unregisterChartType;
|
|
29873
30437
|
exports.unregisterDefaultEngine = unregisterDefaultEngine;
|
|
29874
30438
|
exports.unregisterLegendAction = unregisterLegendAction;
|
|
30439
|
+
exports.unregisterLegendCallout = unregisterLegendCallout;
|
|
29875
30440
|
exports.unregisterNativeIndicator = unregisterNativeIndicator;
|
|
29876
30441
|
exports.unregisterRendererDefaults = unregisterRendererDefaults;
|
|
29877
30442
|
exports.unregisterRendererLayer = unregisterRendererLayer;
|