@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/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,
|
|
@@ -12600,12 +12843,16 @@ var IndicatorInputsDialog = class {
|
|
|
12600
12843
|
closeOnEscape: false,
|
|
12601
12844
|
footer: (foot) => {
|
|
12602
12845
|
foot.append(
|
|
12846
|
+
this.resetAction(),
|
|
12603
12847
|
this.dialogButton("Cancel", false, () => this.revertAndClose()),
|
|
12604
12848
|
this.dialogButton("Ok", true, () => this.close())
|
|
12605
12849
|
);
|
|
12606
12850
|
},
|
|
12851
|
+
// Stale-guard: destroying a dialog fires its machine's onOpenChange(false)
|
|
12852
|
+
// asynchronously — after a reset rebuild that notification must not close
|
|
12853
|
+
// the replacement dialog.
|
|
12607
12854
|
onOpenChange: (open2) => {
|
|
12608
|
-
if (!open2) this.close();
|
|
12855
|
+
if (!open2 && this.uiDialog === ui) this.close();
|
|
12609
12856
|
}
|
|
12610
12857
|
});
|
|
12611
12858
|
applyChromeTokens(ui.panel, t);
|
|
@@ -12616,6 +12863,16 @@ var IndicatorInputsDialog = class {
|
|
|
12616
12863
|
text: () => "Close"
|
|
12617
12864
|
}));
|
|
12618
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
|
+
}
|
|
12619
12876
|
const tabs = document.createElement("div");
|
|
12620
12877
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
12621
12878
|
const tabEls = [];
|
|
@@ -12718,9 +12975,10 @@ var IndicatorInputsDialog = class {
|
|
|
12718
12975
|
this.openId = null;
|
|
12719
12976
|
this.row = null;
|
|
12720
12977
|
this.snapshot = null;
|
|
12978
|
+
this.propSnapshot = null;
|
|
12721
12979
|
ui?.destroy();
|
|
12722
12980
|
}
|
|
12723
|
-
/** 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. */
|
|
12724
12982
|
revertAndClose() {
|
|
12725
12983
|
const row = this.row;
|
|
12726
12984
|
const snap = this.snapshot;
|
|
@@ -12734,12 +12992,55 @@ var IndicatorInputsDialog = class {
|
|
|
12734
12992
|
}
|
|
12735
12993
|
}
|
|
12736
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
|
+
}
|
|
12737
13006
|
this.close();
|
|
12738
13007
|
}
|
|
13008
|
+
/** The footer's reset button — same chip as Cancel, pinned to the LEFT edge
|
|
13009
|
+
* (`margin-right:auto` against the footer's flex-end keeps Cancel/Ok right). */
|
|
13010
|
+
resetAction() {
|
|
13011
|
+
const b = this.dialogButton("Reset defaults", false, () => this.resetToDefaults());
|
|
13012
|
+
b.style.marginRight = "auto";
|
|
13013
|
+
return b;
|
|
13014
|
+
}
|
|
13015
|
+
/** Restore every input to its declared default (re-running the indicator), then
|
|
13016
|
+
* re-open the form so each control re-reads the restored values — the same
|
|
13017
|
+
* rebuild-after-reset move as the chart-settings dialog. The open-time snapshot
|
|
13018
|
+
* survives the rebuild, so Cancel after a reset still reverts the whole session. */
|
|
13019
|
+
resetToDefaults() {
|
|
13020
|
+
const row = this.row;
|
|
13021
|
+
if (!row) return;
|
|
13022
|
+
const snap = this.snapshot;
|
|
13023
|
+
const propSnap = this.propSnapshot;
|
|
13024
|
+
for (const inp of row.inputs) {
|
|
13025
|
+
if (row.values[inp.key] !== inp.defval) {
|
|
13026
|
+
row.values[inp.key] = inp.defval;
|
|
13027
|
+
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value: inp.defval });
|
|
13028
|
+
}
|
|
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
|
+
}
|
|
13036
|
+
this.open(row);
|
|
13037
|
+
if (snap) this.snapshot = snap;
|
|
13038
|
+
if (propSnap) this.propSnapshot = propSnap;
|
|
13039
|
+
}
|
|
12739
13040
|
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
12740
|
-
commit(row,
|
|
12741
|
-
row.
|
|
12742
|
-
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" } : {} });
|
|
12743
13044
|
this.refreshVisibility?.();
|
|
12744
13045
|
}
|
|
12745
13046
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
@@ -12766,7 +13067,7 @@ var IndicatorInputsDialog = class {
|
|
|
12766
13067
|
const id = idOf(lead);
|
|
12767
13068
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
12768
13069
|
if (lead.type === "bool") {
|
|
12769
|
-
const current = Boolean(row
|
|
13070
|
+
const current = Boolean(bagOf(row, lead)[lead.key] ?? lead.defval);
|
|
12770
13071
|
return append(fieldRow({
|
|
12771
13072
|
label: nameOf(lead),
|
|
12772
13073
|
id,
|
|
@@ -12775,7 +13076,7 @@ var IndicatorInputsDialog = class {
|
|
|
12775
13076
|
toggle: {
|
|
12776
13077
|
id,
|
|
12777
13078
|
checked: current,
|
|
12778
|
-
onChange: (v) => this.commit(row, lead
|
|
13079
|
+
onChange: (v) => this.commit(row, lead, v)
|
|
12779
13080
|
}
|
|
12780
13081
|
}));
|
|
12781
13082
|
}
|
|
@@ -12800,8 +13101,8 @@ var IndicatorInputsDialog = class {
|
|
|
12800
13101
|
}
|
|
12801
13102
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
12802
13103
|
buildControl(row, inp, id) {
|
|
12803
|
-
const current = row
|
|
12804
|
-
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);
|
|
12805
13106
|
if (inp.type === "bool") {
|
|
12806
13107
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
12807
13108
|
}
|
|
@@ -12812,7 +13113,7 @@ var IndicatorInputsDialog = class {
|
|
|
12812
13113
|
kind: "color",
|
|
12813
13114
|
id,
|
|
12814
13115
|
theme: this.host.theme(),
|
|
12815
|
-
get: () => String(row
|
|
13116
|
+
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
12816
13117
|
onChange: (v) => emit(v)
|
|
12817
13118
|
}).el;
|
|
12818
13119
|
}
|
|
@@ -13375,6 +13676,7 @@ function timeParts(ts) {
|
|
|
13375
13676
|
}
|
|
13376
13677
|
|
|
13377
13678
|
// src/renderers/shared/InputsUI.ts
|
|
13679
|
+
var LEGEND_AT_TOP_ATTR = "data-vela-pane-at-top";
|
|
13378
13680
|
var STATUS_KEYFRAMES = "@keyframes vela-ind-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.6)}}";
|
|
13379
13681
|
var LEGEND_ICON_PX2 = 16;
|
|
13380
13682
|
var LEGEND_CTL_PX = 18;
|
|
@@ -13413,10 +13715,14 @@ var InputsUI = class {
|
|
|
13413
13715
|
this.moveApi = null;
|
|
13414
13716
|
/** Host-contributed legend actions, resolved PER ROW at render time (see setLegendActions). */
|
|
13415
13717
|
this.legendActions = null;
|
|
13718
|
+
/** Host-contributed callout bubbles, resolved PER ROW at render time (see setLegendCallouts). */
|
|
13719
|
+
this.legendCallouts = null;
|
|
13416
13720
|
/** Chrome-tooltip disposers, per row id — a tip open at removal must not outlive its row. */
|
|
13417
13721
|
this.rowTips = /* @__PURE__ */ new Map();
|
|
13418
13722
|
/** Same, for the contributed extras only (rebuilt independently by setLegendActions). */
|
|
13419
13723
|
this.extrasTips = /* @__PURE__ */ new Map();
|
|
13724
|
+
/** Same, for the contributed callouts only (rebuilt independently by setLegendCallouts). */
|
|
13725
|
+
this.calloutTips = /* @__PURE__ */ new Map();
|
|
13420
13726
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
13421
13727
|
this.moveMenu = null;
|
|
13422
13728
|
this.moveTargets = [];
|
|
@@ -13574,6 +13880,42 @@ var InputsUI = class {
|
|
|
13574
13880
|
extrasEl.appendChild(btn2);
|
|
13575
13881
|
}
|
|
13576
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
|
+
}
|
|
13577
13919
|
/** Reposition the per-pane legend containers after a layout change. */
|
|
13578
13920
|
reposition() {
|
|
13579
13921
|
for (const [paneId, lg] of this.legends) this.positionLegend(lg, paneId);
|
|
@@ -13741,6 +14083,7 @@ var InputsUI = class {
|
|
|
13741
14083
|
}
|
|
13742
14084
|
positionLegend(lg, paneId) {
|
|
13743
14085
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
14086
|
+
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
13744
14087
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
13745
14088
|
const collapsed = this.paneCollapse.has(paneId);
|
|
13746
14089
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
@@ -13816,13 +14159,13 @@ var InputsUI = class {
|
|
|
13816
14159
|
}
|
|
13817
14160
|
/** Create or update an indicator's legend row (in the legend for its pane). */
|
|
13818
14161
|
upsert(id, title, inputs, values, paneId = "price", opts = {}) {
|
|
13819
|
-
const settingsTitle = opts.settingsTitle ?? title;
|
|
13820
14162
|
const existing = this.rows.get(id);
|
|
13821
14163
|
if (existing) {
|
|
13822
14164
|
existing.title = title;
|
|
13823
|
-
existing.settingsTitle = settingsTitle;
|
|
13824
14165
|
existing.inputs = inputs;
|
|
13825
14166
|
existing.values = { ...values };
|
|
14167
|
+
existing.props = opts.props ?? [];
|
|
14168
|
+
existing.propValues = { ...opts.propValues ?? {} };
|
|
13826
14169
|
existing.titleEl.textContent = title;
|
|
13827
14170
|
if (existing.paneId !== paneId) {
|
|
13828
14171
|
existing.paneId = paneId;
|
|
@@ -13874,6 +14217,9 @@ var InputsUI = class {
|
|
|
13874
14217
|
titleWrap.appendChild(beta);
|
|
13875
14218
|
}
|
|
13876
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);
|
|
13877
14223
|
el.appendChild(statusEl);
|
|
13878
14224
|
const valuesEl = document.createElement("span");
|
|
13879
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;`;
|
|
@@ -13890,13 +14236,13 @@ var InputsUI = class {
|
|
|
13890
14236
|
eye.className = "vela-ind-ctl";
|
|
13891
14237
|
eye.style.cssText = LEGEND_CTL_CSS;
|
|
13892
14238
|
eye.addEventListener("click", () => {
|
|
13893
|
-
const
|
|
13894
|
-
this.onToggleVisible?.(id, Boolean(
|
|
14239
|
+
const row2 = this.rows.get(id);
|
|
14240
|
+
this.onToggleVisible?.(id, Boolean(row2?.hidden));
|
|
13895
14241
|
});
|
|
13896
14242
|
controlsEl.appendChild(eye);
|
|
13897
14243
|
eyeEl = eye;
|
|
13898
14244
|
}
|
|
13899
|
-
if (inputs.length > 0) {
|
|
14245
|
+
if (inputs.length > 0 || (opts.props ?? []).length > 0) {
|
|
13900
14246
|
const gear = document.createElement("button");
|
|
13901
14247
|
gear.type = "button";
|
|
13902
14248
|
gear.setAttribute("aria-label", "Settings");
|
|
@@ -13936,7 +14282,9 @@ var InputsUI = class {
|
|
|
13936
14282
|
controlsEl.appendChild(close);
|
|
13937
14283
|
el.appendChild(controlsEl);
|
|
13938
14284
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
13939
|
-
|
|
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);
|
|
13940
14288
|
this.syncFoldToggle();
|
|
13941
14289
|
}
|
|
13942
14290
|
/** Place a row in its pane's legend — native rows PREPEND (pinned to the top), Pine rows append. */
|
|
@@ -13945,9 +14293,11 @@ var InputsUI = class {
|
|
|
13945
14293
|
else container.appendChild(el);
|
|
13946
14294
|
}
|
|
13947
14295
|
/** Reflect programmatic input changes (so a re-opened dialog shows current values). */
|
|
13948
|
-
setValues(id, values) {
|
|
14296
|
+
setValues(id, values, props) {
|
|
13949
14297
|
const row = this.rows.get(id);
|
|
13950
|
-
if (row)
|
|
14298
|
+
if (!row) return;
|
|
14299
|
+
row.values = { ...row.values, ...values };
|
|
14300
|
+
if (props) row.propValues = { ...row.propValues, ...props };
|
|
13951
14301
|
}
|
|
13952
14302
|
/**
|
|
13953
14303
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
@@ -14029,8 +14379,13 @@ var InputsUI = class {
|
|
|
14029
14379
|
syncRowActions(row) {
|
|
14030
14380
|
const open2 = row.highlighted;
|
|
14031
14381
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
14032
|
-
if (open2)
|
|
14033
|
-
|
|
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
|
+
}
|
|
14034
14389
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
14035
14390
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
14036
14391
|
if (child === row.extrasEl) {
|
|
@@ -14048,10 +14403,12 @@ var InputsUI = class {
|
|
|
14048
14403
|
}
|
|
14049
14404
|
remove(id) {
|
|
14050
14405
|
const row = this.rows.get(id);
|
|
14406
|
+
for (const bubble of row?.callouts ?? []) bubble.destroy();
|
|
14051
14407
|
row?.el.remove();
|
|
14052
14408
|
this.rows.delete(id);
|
|
14053
14409
|
this.disposeTips(this.rowTips, id);
|
|
14054
14410
|
this.disposeTips(this.extrasTips, id);
|
|
14411
|
+
this.disposeTips(this.calloutTips, id);
|
|
14055
14412
|
if (this.selectedId === id) this.selectedId = null;
|
|
14056
14413
|
if (this.inputsDialog.openId === id) this.inputsDialog.close();
|
|
14057
14414
|
this.syncFoldToggle();
|
|
@@ -14071,6 +14428,8 @@ var InputsUI = class {
|
|
|
14071
14428
|
this.rowMenu = null;
|
|
14072
14429
|
for (const id of [...this.rowTips.keys()]) this.disposeTips(this.rowTips, id);
|
|
14073
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();
|
|
14074
14433
|
for (const lg of this.legends.values()) lg.remove();
|
|
14075
14434
|
this.legends.clear();
|
|
14076
14435
|
this.rows.clear();
|
|
@@ -14425,6 +14784,32 @@ var SIZE_PX3 = {
|
|
|
14425
14784
|
large: 16,
|
|
14426
14785
|
huge: 20
|
|
14427
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
|
+
}
|
|
14428
14813
|
var TableOverlay = class {
|
|
14429
14814
|
constructor(container, theme, paneBounds) {
|
|
14430
14815
|
this.container = container;
|
|
@@ -14445,7 +14830,9 @@ var TableOverlay = class {
|
|
|
14445
14830
|
update(tables) {
|
|
14446
14831
|
this.lastTables = tables;
|
|
14447
14832
|
this.root.replaceChildren();
|
|
14448
|
-
for (const t of tables)
|
|
14833
|
+
for (const t of tables) {
|
|
14834
|
+
if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
|
|
14835
|
+
}
|
|
14449
14836
|
}
|
|
14450
14837
|
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
14451
14838
|
reposition() {
|
|
@@ -14460,39 +14847,36 @@ var TableOverlay = class {
|
|
|
14460
14847
|
this.root.remove();
|
|
14461
14848
|
}
|
|
14462
14849
|
renderTable(t) {
|
|
14850
|
+
const b = this.paneBounds(t.paneId);
|
|
14463
14851
|
const wrap = document.createElement("div");
|
|
14464
14852
|
wrap.style.position = "absolute";
|
|
14465
|
-
|
|
14853
|
+
if (t.frameColor && t.frameWidth > 0) wrap.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
|
|
14854
|
+
this.anchor(wrap, t.position, b);
|
|
14466
14855
|
const table = document.createElement("table");
|
|
14467
14856
|
Object.assign(table.style, {
|
|
14468
14857
|
borderCollapse: "collapse",
|
|
14469
14858
|
background: t.bgColor ?? "transparent",
|
|
14470
14859
|
fontFamily: this.theme.fontFamily || "sans-serif",
|
|
14471
|
-
// Frame as an inset shadow (not a `border`) so it stays independent of the
|
|
14472
|
-
// collapsed cell borders even when frame_width != border_width.
|
|
14473
|
-
boxShadow: t.frameColor && t.frameWidth > 0 ? `inset 0 0 0 ${t.frameWidth}px ${t.frameColor}` : "none",
|
|
14474
14860
|
border: "none",
|
|
14475
14861
|
tableLayout: "auto",
|
|
14476
14862
|
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
14477
14863
|
pointerEvents: "auto"
|
|
14478
14864
|
});
|
|
14479
|
-
const span
|
|
14480
|
-
const
|
|
14481
|
-
|
|
14482
|
-
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
14483
|
-
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
14484
|
-
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
14485
|
-
if (r !== m.startRow || c !== m.startCol) skip.add(`${r}:${c}`);
|
|
14486
|
-
}
|
|
14487
|
-
}
|
|
14488
|
-
}
|
|
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;
|
|
14489
14868
|
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
14490
14869
|
for (let r = 0; r < t.rows; r += 1) {
|
|
14491
14870
|
const tr = document.createElement("tr");
|
|
14492
14871
|
for (let c = 0; c < t.columns; c += 1) {
|
|
14872
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
14493
14873
|
const cell = t.cells[r]?.[c] ?? null;
|
|
14494
|
-
if (skip.has(`${r}:${c}`) || cell?.merged) continue;
|
|
14495
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
|
+
}
|
|
14496
14880
|
const sp = span.get(`${r}:${c}`);
|
|
14497
14881
|
if (sp) {
|
|
14498
14882
|
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
@@ -14501,18 +14885,22 @@ var TableOverlay = class {
|
|
|
14501
14885
|
Object.assign(td.style, {
|
|
14502
14886
|
border: cellBorder,
|
|
14503
14887
|
padding: "2px 6px",
|
|
14504
|
-
background: cell
|
|
14505
|
-
color: cell
|
|
14506
|
-
textAlign: cell
|
|
14507
|
-
verticalAlign: cell
|
|
14508
|
-
fontSize: `${
|
|
14509
|
-
fontFamily: cell
|
|
14510
|
-
fontWeight: cell
|
|
14511
|
-
fontStyle: cell
|
|
14512
|
-
|
|
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"
|
|
14513
14899
|
});
|
|
14514
|
-
if (cell
|
|
14515
|
-
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 ?? "";
|
|
14516
14904
|
tr.appendChild(td);
|
|
14517
14905
|
}
|
|
14518
14906
|
table.appendChild(tr);
|
|
@@ -15397,8 +15785,14 @@ var WebGL2Backend = class {
|
|
|
15397
15785
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
15398
15786
|
};
|
|
15399
15787
|
b.alpha = this.modelAlpha;
|
|
15400
|
-
for (const m of models) for (const bgSpan of m.backgrounds) this.emitBackground(b, bgSpan, pane, coords);
|
|
15401
|
-
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
|
+
}
|
|
15402
15796
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
15403
15797
|
let candleDrawn = false;
|
|
15404
15798
|
for (const m of models) {
|
|
@@ -15412,7 +15806,7 @@ var WebGL2Backend = class {
|
|
|
15412
15806
|
b.alpha = this.modelAlpha;
|
|
15413
15807
|
const off = scene.offsetOf(m.id);
|
|
15414
15808
|
const mp = effPane(m);
|
|
15415
|
-
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);
|
|
15416
15810
|
}
|
|
15417
15811
|
if (drawCandles && !candleDrawn) {
|
|
15418
15812
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -15421,6 +15815,12 @@ var WebGL2Backend = class {
|
|
|
15421
15815
|
}
|
|
15422
15816
|
drawSlicesUpTo(Infinity);
|
|
15423
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
|
+
}
|
|
15424
15824
|
for (const m of models) {
|
|
15425
15825
|
const mp = effPane(m);
|
|
15426
15826
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -15467,7 +15867,7 @@ var WebGL2Backend = class {
|
|
|
15467
15867
|
for (const pane of scene.orderedPanes()) {
|
|
15468
15868
|
const b = this.glowBatch;
|
|
15469
15869
|
b.reset();
|
|
15470
|
-
this.emitGlowSources(b, scene
|
|
15870
|
+
this.emitGlowSources(b, scene, pane, coords, i0, i1);
|
|
15471
15871
|
if (b.vertexCount === 0) continue;
|
|
15472
15872
|
const topH = Math.round(pane.bounds.top * dpr * 0.5);
|
|
15473
15873
|
const botH = Math.round((pane.bounds.top + pane.bounds.height) * dpr * 0.5);
|
|
@@ -15536,15 +15936,23 @@ var WebGL2Backend = class {
|
|
|
15536
15936
|
gl.bindVertexArray(null);
|
|
15537
15937
|
return true;
|
|
15538
15938
|
}
|
|
15539
|
-
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
15540
|
-
|
|
15541
|
-
|
|
15542
|
-
|
|
15543
|
-
|
|
15544
|
-
|
|
15545
|
-
|
|
15546
|
-
|
|
15547
|
-
|
|
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);
|
|
15548
15956
|
}
|
|
15549
15957
|
}
|
|
15550
15958
|
}
|
|
@@ -15827,9 +16235,9 @@ var WebGL2Backend = class {
|
|
|
15827
16235
|
b.alpha = this.candleBodyAlpha;
|
|
15828
16236
|
b.rect(g.bodyX, bodyTop, g.bodyW, bodyH, c);
|
|
15829
16237
|
}
|
|
15830
|
-
if (cs.borderVisible ||
|
|
16238
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
15831
16239
|
b.alpha = this.candleStructureAlpha;
|
|
15832
|
-
const bord = cs.borderVisible
|
|
16240
|
+
const bord = cs.borderVisible ? parseColor((isUp ? cs.borderUpColor : cs.borderDownColor) ?? bodyColorStr) : c;
|
|
15833
16241
|
const bw = Math.max(0, g.bodyW - 1);
|
|
15834
16242
|
const bh = Math.max(0, bodyH - 1);
|
|
15835
16243
|
b.rectStroke(g.bodyX + 0.5, bodyTop + 0.5, bw, bh, 1, bord);
|
|
@@ -17381,8 +17789,14 @@ var Canvas2dBackend = class {
|
|
|
17381
17789
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
17382
17790
|
};
|
|
17383
17791
|
ctx.globalAlpha = this.modelAlpha;
|
|
17384
|
-
for (const m of models) for (const bg of m.backgrounds) this.drawBackground(ctx, bg, pane, coords);
|
|
17385
|
-
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
|
+
}
|
|
17386
17800
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
17387
17801
|
let si = 0;
|
|
17388
17802
|
const drawSlicesUpTo = (z) => {
|
|
@@ -17404,7 +17818,7 @@ var Canvas2dBackend = class {
|
|
|
17404
17818
|
ctx.globalAlpha = this.modelAlpha;
|
|
17405
17819
|
const off = scene.offsetOf(m.id);
|
|
17406
17820
|
const mp = effPane(m);
|
|
17407
|
-
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);
|
|
17408
17822
|
}
|
|
17409
17823
|
if (drawCandles && !candleDrawn) {
|
|
17410
17824
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -17412,6 +17826,13 @@ var Canvas2dBackend = class {
|
|
|
17412
17826
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
17413
17827
|
}
|
|
17414
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
|
+
}
|
|
17415
17836
|
ctx.globalAlpha = this.modelAlpha;
|
|
17416
17837
|
for (const m of models) {
|
|
17417
17838
|
const mp = effPane(m);
|
|
@@ -17683,9 +18104,9 @@ var Canvas2dBackend = class {
|
|
|
17683
18104
|
ctx.fillStyle = color;
|
|
17684
18105
|
ctx.fillRect(g.bodyX, top, g.bodyW, bodyH);
|
|
17685
18106
|
}
|
|
17686
|
-
if (cs.borderVisible ||
|
|
18107
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
17687
18108
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
17688
|
-
ctx.strokeStyle = cs.borderVisible
|
|
18109
|
+
ctx.strokeStyle = cs.borderVisible ? (up ? cs.borderUpColor : cs.borderDownColor) ?? color : color;
|
|
17689
18110
|
ctx.lineWidth = 1;
|
|
17690
18111
|
const bw = Math.max(0, g.bodyW - 1);
|
|
17691
18112
|
const bh = Math.max(0, bodyH - 1);
|
|
@@ -18117,6 +18538,8 @@ var DrawingSceneRenderer = class {
|
|
|
18117
18538
|
this.set = set;
|
|
18118
18539
|
/** measureText width cache, keyed by `${font} ${text}`, persists across frames. */
|
|
18119
18540
|
this.widthCache = /* @__PURE__ */ new Map();
|
|
18541
|
+
/** Tooltip hit-rects captured while drawing the CURRENT set (rebuilt per render). */
|
|
18542
|
+
this.tipRegions = [];
|
|
18120
18543
|
/**
|
|
18121
18544
|
* Index offset of the CURRENT set's model: its `xloc:'bar_index'` coordinates count
|
|
18122
18545
|
* from the model's anchor bar, so they shift by this to land on chart logical indices.
|
|
@@ -18137,8 +18560,13 @@ var DrawingSceneRenderer = class {
|
|
|
18137
18560
|
const s = this.set;
|
|
18138
18561
|
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
18139
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
|
+
}
|
|
18140
18567
|
/** Draw the whole set into `ctx` using the supplied coordinate closures. */
|
|
18141
18568
|
render(ctx, W, H, xOf, yOf) {
|
|
18569
|
+
this.tipRegions = [];
|
|
18142
18570
|
if (this.isEmpty()) return;
|
|
18143
18571
|
ctx.save();
|
|
18144
18572
|
this.drawLinefills(ctx, W, H, xOf, yOf);
|
|
@@ -18486,13 +18914,35 @@ var DrawingSceneRenderer = class {
|
|
|
18486
18914
|
if (this.isPointShape(lb.style)) {
|
|
18487
18915
|
if (!lb.noFill) this.drawLabelShape(ctx, lb.style, px, py, fontPx, color);
|
|
18488
18916
|
if (lb.text) this.drawLabelText(ctx, lb, px, py + fontPx, fontPx);
|
|
18489
|
-
|
|
18490
|
-
|
|
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
|
+
}
|
|
18491
18926
|
} else {
|
|
18492
|
-
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 });
|
|
18493
18929
|
}
|
|
18494
18930
|
}
|
|
18495
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
|
+
}
|
|
18496
18946
|
isPointShape(style) {
|
|
18497
18947
|
switch (style) {
|
|
18498
18948
|
case "circle":
|
|
@@ -18511,8 +18961,7 @@ var DrawingSceneRenderer = class {
|
|
|
18511
18961
|
}
|
|
18512
18962
|
}
|
|
18513
18963
|
drawLabelText(ctx, lb, cx, cy, fontPx, outline = false) {
|
|
18514
|
-
|
|
18515
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
18964
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
18516
18965
|
ctx.textAlign = lb.textAlign === "left" ? "left" : lb.textAlign === "right" ? "right" : "center";
|
|
18517
18966
|
ctx.textBaseline = "middle";
|
|
18518
18967
|
const lines = lb.text.split("\n");
|
|
@@ -18592,9 +19041,9 @@ var DrawingSceneRenderer = class {
|
|
|
18592
19041
|
ctx.fill();
|
|
18593
19042
|
}
|
|
18594
19043
|
}
|
|
19044
|
+
/** Draw a bubble label; returns the bubble body rect (for tooltip hit-testing). */
|
|
18595
19045
|
drawBubble(ctx, lb, px, py, fontPx, color) {
|
|
18596
|
-
|
|
18597
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
19046
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
18598
19047
|
const lines = (lb.text ?? "").split("\n");
|
|
18599
19048
|
const padX = 6;
|
|
18600
19049
|
const padY = 4;
|
|
@@ -18652,38 +19101,51 @@ var DrawingSceneRenderer = class {
|
|
|
18652
19101
|
by = py - h - ptr;
|
|
18653
19102
|
pointer = "down";
|
|
18654
19103
|
}
|
|
18655
|
-
|
|
18656
|
-
|
|
18657
|
-
|
|
18658
|
-
if (pointer !== "none") {
|
|
18659
|
-
ctx.beginPath();
|
|
18660
|
-
if (pointer === "down") {
|
|
18661
|
-
ctx.moveTo(px - ptr, by + h);
|
|
18662
|
-
ctx.lineTo(px + ptr, by + h);
|
|
18663
|
-
ctx.lineTo(px, py);
|
|
18664
|
-
} else if (pointer === "up") {
|
|
18665
|
-
ctx.moveTo(px - ptr, by);
|
|
18666
|
-
ctx.lineTo(px + ptr, by);
|
|
18667
|
-
ctx.lineTo(px, py);
|
|
18668
|
-
} else if (pointer === "left") {
|
|
18669
|
-
ctx.moveTo(bx, py - ptr);
|
|
18670
|
-
ctx.lineTo(bx, py + ptr);
|
|
18671
|
-
ctx.lineTo(px, py);
|
|
18672
|
-
} else {
|
|
18673
|
-
ctx.moveTo(bx + w, py - ptr);
|
|
18674
|
-
ctx.lineTo(bx + w, py + ptr);
|
|
18675
|
-
ctx.lineTo(px, py);
|
|
18676
|
-
}
|
|
18677
|
-
ctx.closePath();
|
|
19104
|
+
if (!lb.noFill) {
|
|
19105
|
+
ctx.fillStyle = color;
|
|
19106
|
+
this.roundRect(ctx, bx, by, w, h, 4);
|
|
18678
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
|
+
}
|
|
18679
19130
|
}
|
|
18680
19131
|
if (lb.text) {
|
|
18681
|
-
ctx.fillStyle = lb.textColor ?? contrastColor(color);
|
|
18682
|
-
ctx.textAlign = "center";
|
|
19132
|
+
ctx.fillStyle = lb.textColor ?? (lb.noFill ? this.deps.theme.textColor : contrastColor(color));
|
|
18683
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
|
+
}
|
|
18684
19145
|
const startY = by + padY + lineH / 2;
|
|
18685
|
-
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);
|
|
18686
19147
|
}
|
|
19148
|
+
return { x: bx, y: by, w, h };
|
|
18687
19149
|
}
|
|
18688
19150
|
roundRect(ctx, x, y, w, h, r) {
|
|
18689
19151
|
const rr = Math.min(r, w / 2, h / 2);
|
|
@@ -18921,6 +19383,8 @@ var ChromeRenderer = class {
|
|
|
18921
19383
|
this.axisTextColor = DARK_THEME.textColor;
|
|
18922
19384
|
// Shared Pine-drawing renderer (line/box/label/polyline/linefill); widthCache persists.
|
|
18923
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 = [];
|
|
18924
19388
|
}
|
|
18925
19389
|
mount(canvas) {
|
|
18926
19390
|
this.canvas = canvas;
|
|
@@ -18962,6 +19426,7 @@ var ChromeRenderer = class {
|
|
|
18962
19426
|
const dataW = coords.width;
|
|
18963
19427
|
const dataH = coords.height;
|
|
18964
19428
|
this.axisTextColor = surface?.textColor ?? theme.textColor;
|
|
19429
|
+
this.labelTips = [];
|
|
18965
19430
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
18966
19431
|
ctx.clearRect(0, 0, fullW, fullH);
|
|
18967
19432
|
if (surface && (fullW > dataW || fullH > dataH)) {
|
|
@@ -19071,6 +19536,17 @@ var ChromeRenderer = class {
|
|
|
19071
19536
|
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
19072
19537
|
);
|
|
19073
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;
|
|
19074
19550
|
}
|
|
19075
19551
|
// ── axes ──
|
|
19076
19552
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
@@ -19287,6 +19763,68 @@ function formatCountdown(ms) {
|
|
|
19287
19763
|
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
|
|
19288
19764
|
}
|
|
19289
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
|
+
|
|
19290
19828
|
// src/renderers/native/chrome/CrosshairRenderer.ts
|
|
19291
19829
|
var CrosshairRenderer = class {
|
|
19292
19830
|
constructor() {
|
|
@@ -25078,19 +25616,8 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
25078
25616
|
for (const model of models) {
|
|
25079
25617
|
const off = offsetOf(model.id);
|
|
25080
25618
|
for (const s of model.series) {
|
|
25081
|
-
if (s.
|
|
25082
|
-
|
|
25083
|
-
const b = s.bars[i - off];
|
|
25084
|
-
if (b) {
|
|
25085
|
-
consider(b.high);
|
|
25086
|
-
consider(b.low);
|
|
25087
|
-
}
|
|
25088
|
-
}
|
|
25089
|
-
} else if (isLineLikeSeries(s)) {
|
|
25090
|
-
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
25091
|
-
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
25092
|
-
else if (s.style?.base != null) consider(s.style.base);
|
|
25093
|
-
}
|
|
25619
|
+
if (s.overlay === true) continue;
|
|
25620
|
+
considerSeries(s, i0, i1, off, consider);
|
|
25094
25621
|
}
|
|
25095
25622
|
for (const pl of model.priceLines) consider(pl.price);
|
|
25096
25623
|
}
|
|
@@ -25112,6 +25639,36 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
25112
25639
|
const span = max - min;
|
|
25113
25640
|
return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
|
|
25114
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
|
+
}
|
|
25115
25672
|
function expandScaleByPixels(scale, heightPx, abovePx, belowPx) {
|
|
25116
25673
|
if (abovePx <= 0 && belowPx <= 0) return scale;
|
|
25117
25674
|
const content = heightPx - abovePx - belowPx;
|
|
@@ -25166,6 +25723,11 @@ function resizeSplit(split, dyTotal, minPx = MIN_PANE_PX) {
|
|
|
25166
25723
|
}
|
|
25167
25724
|
|
|
25168
25725
|
// src/renderers/native/backdrop/BackdropRenderer.ts
|
|
25726
|
+
function clipHighlightRect(x1, x2, left, right) {
|
|
25727
|
+
const x = Math.max(left, x1);
|
|
25728
|
+
const end = Math.min(right, x2);
|
|
25729
|
+
return end > x ? { x, width: end - x } : null;
|
|
25730
|
+
}
|
|
25169
25731
|
var BackdropRenderer = class {
|
|
25170
25732
|
constructor() {
|
|
25171
25733
|
this.canvas = null;
|
|
@@ -25198,17 +25760,22 @@ var BackdropRenderer = class {
|
|
|
25198
25760
|
/** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
|
|
25199
25761
|
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
25200
25762
|
drawHighlights(ctx, scene, coords) {
|
|
25201
|
-
const
|
|
25202
|
-
if (
|
|
25763
|
+
const sessions = scene.sessionHighlightBands();
|
|
25764
|
+
if (sessions.length > 0) {
|
|
25765
|
+
const left = Math.max(0, coords.logicalToX(-0.5));
|
|
25766
|
+
const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
|
|
25767
|
+
this.drawHighlightSet(ctx, sessions, coords, left, right);
|
|
25768
|
+
}
|
|
25769
|
+
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
|
|
25770
|
+
}
|
|
25771
|
+
drawHighlightSet(ctx, bands, coords, left, right) {
|
|
25203
25772
|
for (const band of bands) {
|
|
25204
25773
|
const x1 = coords.timeToX(band.from);
|
|
25205
25774
|
const x2 = coords.timeToX(band.to);
|
|
25206
|
-
|
|
25207
|
-
|
|
25208
|
-
const cw = Math.min(coords.width, x2) - cx;
|
|
25209
|
-
if (cw <= 0) continue;
|
|
25775
|
+
const rect = clipHighlightRect(x1, x2, left, right);
|
|
25776
|
+
if (!rect) continue;
|
|
25210
25777
|
ctx.fillStyle = band.color;
|
|
25211
|
-
ctx.fillRect(
|
|
25778
|
+
ctx.fillRect(rect.x, 0, rect.width, coords.height);
|
|
25212
25779
|
}
|
|
25213
25780
|
}
|
|
25214
25781
|
// ── grid ── vert/horz gate on `scene.showGrid` AND their own per-axis visibility
|
|
@@ -25884,6 +26451,8 @@ var NativeRenderer = class {
|
|
|
25884
26451
|
this.glowAmount = 0;
|
|
25885
26452
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
25886
26453
|
this.chrome = new ChromeRenderer();
|
|
26454
|
+
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
26455
|
+
this.labelTooltip = null;
|
|
25887
26456
|
this.crosshairLayer = new CrosshairRenderer();
|
|
25888
26457
|
/** 1 Hz repaint pump so the price-axis countdown-to-bar-close ticks; null when off. */
|
|
25889
26458
|
this.countdownTimer = null;
|
|
@@ -25894,6 +26463,8 @@ var NativeRenderer = class {
|
|
|
25894
26463
|
this.indicatorValuesOn = true;
|
|
25895
26464
|
/** Host-contributed legend actions — held here so a rebuild of the legend re-wires them. */
|
|
25896
26465
|
this.legendActionsProvider = null;
|
|
26466
|
+
/** Host-contributed legend callouts — held here so a rebuild of the legend re-wires them. */
|
|
26467
|
+
this.legendCalloutsProvider = null;
|
|
25897
26468
|
/** Host override of the legend's fold toggle — held here so a remount re-applies it. */
|
|
25898
26469
|
this.legendOverviewAction = null;
|
|
25899
26470
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -27002,6 +27573,10 @@ var NativeRenderer = class {
|
|
|
27002
27573
|
this.plot.appendChild(this.scrollButton);
|
|
27003
27574
|
this.plot.addEventListener("pointermove", this.onScrollProximityMove);
|
|
27004
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
|
+
});
|
|
27005
27580
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
27006
27581
|
projector: () => this.drawingProjector(),
|
|
27007
27582
|
dpr: () => this.coords.dpr,
|
|
@@ -27041,9 +27616,10 @@ var NativeRenderer = class {
|
|
|
27041
27616
|
this.inputsUI.setDialogHost(this.dialogHost);
|
|
27042
27617
|
this.inputsUI.setSymbolPicker(this.symbolPicker);
|
|
27043
27618
|
this.inputsUI.setLegendActions(this.legendActionsProvider);
|
|
27619
|
+
this.inputsUI.setLegendCallouts(this.legendCalloutsProvider);
|
|
27044
27620
|
this.inputsUI.setLegendOverviewAction(this.legendOverviewAction);
|
|
27045
27621
|
this.inputsUI.setOnChange((c) => {
|
|
27046
|
-
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 } : {} });
|
|
27047
27623
|
});
|
|
27048
27624
|
this.inputsUI.setOnRemove((id) => {
|
|
27049
27625
|
for (const cb of this.removeIndicatorCbs) cb(id);
|
|
@@ -27233,6 +27809,8 @@ var NativeRenderer = class {
|
|
|
27233
27809
|
this.settingsButton = null;
|
|
27234
27810
|
this.plot?.removeEventListener("pointermove", this.onScrollProximityMove);
|
|
27235
27811
|
this.plot?.removeEventListener("pointerleave", this.onScrollProximityLeave);
|
|
27812
|
+
this.labelTooltip?.destroy();
|
|
27813
|
+
this.labelTooltip = null;
|
|
27236
27814
|
this.scrollButton?.remove();
|
|
27237
27815
|
this.scrollButton = null;
|
|
27238
27816
|
for (const l of this.extLayers) l.instance.destroy?.();
|
|
@@ -27473,7 +28051,7 @@ var NativeRenderer = class {
|
|
|
27473
28051
|
else this.scene.assignIndicatorZ(model.id);
|
|
27474
28052
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
27475
28053
|
native: !!model.native,
|
|
27476
|
-
...model.
|
|
28054
|
+
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
27477
28055
|
});
|
|
27478
28056
|
this.syncTables(model);
|
|
27479
28057
|
if (model.native?.type === "volume") {
|
|
@@ -27524,8 +28102,8 @@ var NativeRenderer = class {
|
|
|
27524
28102
|
this.paneControls?.refresh();
|
|
27525
28103
|
this.scheduler.invalidate(4 /* Full */);
|
|
27526
28104
|
}
|
|
27527
|
-
setIndicatorInputs(handle, values) {
|
|
27528
|
-
this.inputsUI.setValues(handle.id, values);
|
|
28105
|
+
setIndicatorInputs(handle, values, props) {
|
|
28106
|
+
this.inputsUI.setValues(handle.id, values, props);
|
|
27529
28107
|
}
|
|
27530
28108
|
setSymbolPicker(picker) {
|
|
27531
28109
|
this.symbolPicker = picker;
|
|
@@ -27535,6 +28113,10 @@ var NativeRenderer = class {
|
|
|
27535
28113
|
this.legendActionsProvider = provider;
|
|
27536
28114
|
this.inputsUI?.setLegendActions(provider);
|
|
27537
28115
|
}
|
|
28116
|
+
setLegendCallouts(provider) {
|
|
28117
|
+
this.legendCalloutsProvider = provider;
|
|
28118
|
+
this.inputsUI?.setLegendCallouts(provider);
|
|
28119
|
+
}
|
|
27538
28120
|
setLegendOverviewAction(action) {
|
|
27539
28121
|
this.legendOverviewAction = action;
|
|
27540
28122
|
this.inputsUI?.setLegendOverviewAction(action);
|
|
@@ -28515,7 +29097,11 @@ var NativeRenderer = class {
|
|
|
28515
29097
|
}
|
|
28516
29098
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
28517
29099
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
28518
|
-
|
|
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
|
+
}
|
|
28519
29105
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
28520
29106
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
28521
29107
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -28789,6 +29375,7 @@ var NativeRenderer = class {
|
|
|
28789
29375
|
for (const m of models) {
|
|
28790
29376
|
const off = this.scene.offsetOf(m.id);
|
|
28791
29377
|
for (const s of m.series) {
|
|
29378
|
+
if (s.overlay === true) continue;
|
|
28792
29379
|
if (isLineLikeSeries(s)) {
|
|
28793
29380
|
const v = s.points[i0 - off]?.value;
|
|
28794
29381
|
if (v != null && Number.isFinite(v)) return v;
|
|
@@ -29694,6 +30281,19 @@ function unregisterLegendAction(id) {
|
|
|
29694
30281
|
function legendActions() {
|
|
29695
30282
|
return [...legendRegistry.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
29696
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
|
+
}
|
|
29697
30297
|
var stateHandlers = /* @__PURE__ */ new Map();
|
|
29698
30298
|
function registerStatePersistence(handler) {
|
|
29699
30299
|
stateHandlers.set(handler.key, handler);
|
|
@@ -29802,6 +30402,7 @@ exports.getNativeIndicator = getNativeIndicator;
|
|
|
29802
30402
|
exports.iconMarkup = iconMarkup;
|
|
29803
30403
|
exports.inputVisible = inputVisible;
|
|
29804
30404
|
exports.legendActions = legendActions;
|
|
30405
|
+
exports.legendCallouts = legendCallouts;
|
|
29805
30406
|
exports.nativeIndicatorDescriptors = nativeIndicatorDescriptors;
|
|
29806
30407
|
exports.nativeIndicatorTypes = nativeIndicatorTypes;
|
|
29807
30408
|
exports.normalizeSettingsRow = normalizeSettingsRow;
|
|
@@ -29810,6 +30411,7 @@ exports.registerDefaultEngine = registerDefaultEngine;
|
|
|
29810
30411
|
exports.registerDrawingType = registerDrawingType;
|
|
29811
30412
|
exports.registerIcon = registerIcon;
|
|
29812
30413
|
exports.registerLegendAction = registerLegendAction;
|
|
30414
|
+
exports.registerLegendCallout = registerLegendCallout;
|
|
29813
30415
|
exports.registerNativeIndicator = registerNativeIndicator;
|
|
29814
30416
|
exports.registerRendererDefaults = registerRendererDefaults;
|
|
29815
30417
|
exports.registerRendererLayer = registerRendererLayer;
|
|
@@ -29834,6 +30436,7 @@ exports.topbarActionOverride = topbarActionOverride;
|
|
|
29834
30436
|
exports.unregisterChartType = unregisterChartType;
|
|
29835
30437
|
exports.unregisterDefaultEngine = unregisterDefaultEngine;
|
|
29836
30438
|
exports.unregisterLegendAction = unregisterLegendAction;
|
|
30439
|
+
exports.unregisterLegendCallout = unregisterLegendCallout;
|
|
29837
30440
|
exports.unregisterNativeIndicator = unregisterNativeIndicator;
|
|
29838
30441
|
exports.unregisterRendererDefaults = unregisterRendererDefaults;
|
|
29839
30442
|
exports.unregisterRendererLayer = unregisterRendererLayer;
|