@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/workspace.cjs
CHANGED
|
@@ -1097,11 +1097,11 @@ registerIcon(
|
|
|
1097
1097
|
);
|
|
1098
1098
|
registerIcon(
|
|
1099
1099
|
"market-pre",
|
|
1100
|
-
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="
|
|
1100
|
+
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"/>')
|
|
1101
1101
|
);
|
|
1102
1102
|
registerIcon(
|
|
1103
1103
|
"market-post",
|
|
1104
|
-
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
|
|
1104
|
+
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"/>')
|
|
1105
1105
|
);
|
|
1106
1106
|
registerIcon("market-closed", svg24('<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>'));
|
|
1107
1107
|
registerIcon(
|
|
@@ -4195,6 +4195,192 @@ function buildFieldControl(desc) {
|
|
|
4195
4195
|
return { el };
|
|
4196
4196
|
}
|
|
4197
4197
|
|
|
4198
|
+
// src/ui/components/callout-bubble/controller.ts
|
|
4199
|
+
function closesPanel(item) {
|
|
4200
|
+
return item.close !== false;
|
|
4201
|
+
}
|
|
4202
|
+
function calloutPanelRows(items) {
|
|
4203
|
+
const rows = [];
|
|
4204
|
+
for (const item of items) {
|
|
4205
|
+
if (item.type === "text") {
|
|
4206
|
+
rows.push({ type: "text", text: item.text });
|
|
4207
|
+
continue;
|
|
4208
|
+
}
|
|
4209
|
+
const last = rows[rows.length - 1];
|
|
4210
|
+
if (last && last.type === "buttons") last.buttons.push(item);
|
|
4211
|
+
else rows.push({ type: "buttons", buttons: [item] });
|
|
4212
|
+
}
|
|
4213
|
+
return rows;
|
|
4214
|
+
}
|
|
4215
|
+
|
|
4216
|
+
// src/ui/components/callout-bubble/styles.ts
|
|
4217
|
+
var CALLOUT_STYLE_ID = "vela-ui-callout-bubble";
|
|
4218
|
+
var CALLOUT_CSS = `
|
|
4219
|
+
.vela-callout {
|
|
4220
|
+
display: inline-grid;
|
|
4221
|
+
place-items: center;
|
|
4222
|
+
border-radius: 50%;
|
|
4223
|
+
flex: none;
|
|
4224
|
+
line-height: 0;
|
|
4225
|
+
box-sizing: border-box;
|
|
4226
|
+
cursor: default;
|
|
4227
|
+
user-select: none;
|
|
4228
|
+
-webkit-user-select: none;
|
|
4229
|
+
}
|
|
4230
|
+
.vela-callout[role='button'] { cursor: pointer; }
|
|
4231
|
+
.vela-callout svg { display: block; }
|
|
4232
|
+
/* The deployed panel \u2014 carries the kit's elevated-card look itself (the popover
|
|
4233
|
+
shell is bare positioning chrome). */
|
|
4234
|
+
.vela-callout-panel {
|
|
4235
|
+
display: flex;
|
|
4236
|
+
flex-direction: column;
|
|
4237
|
+
gap: 8px;
|
|
4238
|
+
padding: 10px 12px;
|
|
4239
|
+
max-width: 280px;
|
|
4240
|
+
box-sizing: border-box;
|
|
4241
|
+
background: var(--vela-surface-elev);
|
|
4242
|
+
border: 1px solid var(--vela-border-strong);
|
|
4243
|
+
border-radius: 6px;
|
|
4244
|
+
box-shadow: var(--vela-shadow);
|
|
4245
|
+
color: var(--vela-fg);
|
|
4246
|
+
font: var(--vela-font-size-md) var(--vela-font);
|
|
4247
|
+
}
|
|
4248
|
+
.vela-callout-title { font-weight: 600; color: var(--vela-fg-bright); }
|
|
4249
|
+
.vela-callout-text { color: var(--vela-fg-muted); line-height: 1.45; white-space: pre-line; }
|
|
4250
|
+
.vela-callout-actions { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
4251
|
+
.vela-callout-btn {
|
|
4252
|
+
cursor: pointer;
|
|
4253
|
+
height: 26px;
|
|
4254
|
+
padding: 0 10px;
|
|
4255
|
+
border-radius: 5px;
|
|
4256
|
+
border: 1px solid var(--vela-border);
|
|
4257
|
+
background: transparent;
|
|
4258
|
+
color: var(--vela-fg);
|
|
4259
|
+
font-size: var(--vela-font-size-md);
|
|
4260
|
+
font-family: inherit;
|
|
4261
|
+
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;
|
|
4262
|
+
}
|
|
4263
|
+
.vela-callout-btn:hover { background: var(--vela-hover); color: var(--vela-fg-bright); border-color: var(--vela-fg-muted); }
|
|
4264
|
+
.vela-callout-btn-primary { border-color: var(--vela-selected-bg); background: var(--vela-selected-bg); color: var(--vela-selected-fg); }
|
|
4265
|
+
.vela-callout-btn-primary:hover { background: var(--vela-selected-bg); color: var(--vela-selected-fg); opacity: 0.85; border-color: var(--vela-selected-bg); }
|
|
4266
|
+
`;
|
|
4267
|
+
|
|
4268
|
+
// src/ui/components/callout-bubble/view.ts
|
|
4269
|
+
var CalloutBubble = class {
|
|
4270
|
+
constructor(opts) {
|
|
4271
|
+
this.pop = null;
|
|
4272
|
+
this.spec = { icon: opts.icon, background: opts.background, label: opts.label };
|
|
4273
|
+
if (opts.color !== void 0) this.spec.color = opts.color;
|
|
4274
|
+
if (opts.panel !== void 0) this.spec.panel = opts.panel;
|
|
4275
|
+
this.size = opts.size ?? 16;
|
|
4276
|
+
this.host = opts.host;
|
|
4277
|
+
this.theme = opts.theme;
|
|
4278
|
+
this.boundary = opts.boundary;
|
|
4279
|
+
const doc = (opts.host ?? document.body).ownerDocument;
|
|
4280
|
+
injectStyles(CALLOUT_STYLE_ID, CALLOUT_CSS, doc);
|
|
4281
|
+
this.el = doc.createElement("span");
|
|
4282
|
+
this.el.className = "vela-callout";
|
|
4283
|
+
this.el.style.width = `${this.size}px`;
|
|
4284
|
+
this.el.style.height = `${this.size}px`;
|
|
4285
|
+
this.el.addEventListener("click", (e) => {
|
|
4286
|
+
if (!this.spec.panel) return;
|
|
4287
|
+
e.stopPropagation();
|
|
4288
|
+
this.toggle();
|
|
4289
|
+
});
|
|
4290
|
+
this.el.addEventListener("dblclick", (e) => {
|
|
4291
|
+
if (this.spec.panel) e.stopPropagation();
|
|
4292
|
+
});
|
|
4293
|
+
this.el.addEventListener("keydown", (e) => {
|
|
4294
|
+
if (!this.spec.panel || e.key !== "Enter" && e.key !== " ") return;
|
|
4295
|
+
e.preventDefault();
|
|
4296
|
+
this.toggle();
|
|
4297
|
+
});
|
|
4298
|
+
this.dress();
|
|
4299
|
+
}
|
|
4300
|
+
/** Re-dress the bubble (a status change: new icon, tint, label, panel). */
|
|
4301
|
+
set(spec) {
|
|
4302
|
+
this.spec = { ...this.spec, ...spec };
|
|
4303
|
+
if ("panel" in spec) this.pop?.hide();
|
|
4304
|
+
this.dress();
|
|
4305
|
+
}
|
|
4306
|
+
/** Whether the deployed panel is currently open. */
|
|
4307
|
+
get open() {
|
|
4308
|
+
return this.pop?.open ?? false;
|
|
4309
|
+
}
|
|
4310
|
+
destroy() {
|
|
4311
|
+
this.pop?.destroy();
|
|
4312
|
+
this.pop = null;
|
|
4313
|
+
this.el.remove();
|
|
4314
|
+
}
|
|
4315
|
+
dress() {
|
|
4316
|
+
const clickable = this.spec.panel !== void 0;
|
|
4317
|
+
this.el.style.background = this.spec.background;
|
|
4318
|
+
this.el.style.color = this.spec.color ?? "";
|
|
4319
|
+
this.el.innerHTML = iconAt(this.spec.icon, this.size - 4);
|
|
4320
|
+
this.el.setAttribute("aria-label", this.spec.label);
|
|
4321
|
+
if (clickable) {
|
|
4322
|
+
this.el.setAttribute("role", "button");
|
|
4323
|
+
this.el.tabIndex = 0;
|
|
4324
|
+
} else {
|
|
4325
|
+
this.el.removeAttribute("role");
|
|
4326
|
+
this.el.removeAttribute("tabindex");
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
toggle() {
|
|
4330
|
+
if (this.pop?.open) {
|
|
4331
|
+
this.pop.hide();
|
|
4332
|
+
return;
|
|
4333
|
+
}
|
|
4334
|
+
const panel = this.spec.panel;
|
|
4335
|
+
if (!panel) return;
|
|
4336
|
+
this.pop?.destroy();
|
|
4337
|
+
this.pop = new Popover({
|
|
4338
|
+
trigger: this.el,
|
|
4339
|
+
gap: 6,
|
|
4340
|
+
content: (body) => this.buildPanel(body, panel),
|
|
4341
|
+
...this.host ? { host: this.host } : {},
|
|
4342
|
+
...this.theme ? { theme: this.theme() } : {},
|
|
4343
|
+
...this.boundary ? { boundary: this.boundary } : {}
|
|
4344
|
+
});
|
|
4345
|
+
this.pop.show();
|
|
4346
|
+
}
|
|
4347
|
+
buildPanel(body, panel) {
|
|
4348
|
+
const doc = body.ownerDocument;
|
|
4349
|
+
const root = doc.createElement("div");
|
|
4350
|
+
root.className = "vela-callout-panel";
|
|
4351
|
+
if (panel.title) {
|
|
4352
|
+
const title = doc.createElement("div");
|
|
4353
|
+
title.className = "vela-callout-title";
|
|
4354
|
+
title.textContent = panel.title;
|
|
4355
|
+
root.appendChild(title);
|
|
4356
|
+
}
|
|
4357
|
+
for (const row of calloutPanelRows(panel.items)) {
|
|
4358
|
+
if (row.type === "text") {
|
|
4359
|
+
const text = doc.createElement("div");
|
|
4360
|
+
text.className = "vela-callout-text";
|
|
4361
|
+
text.textContent = row.text;
|
|
4362
|
+
root.appendChild(text);
|
|
4363
|
+
continue;
|
|
4364
|
+
}
|
|
4365
|
+
const actions = doc.createElement("div");
|
|
4366
|
+
actions.className = "vela-callout-actions";
|
|
4367
|
+
for (const item of row.buttons) {
|
|
4368
|
+
const btn2 = doc.createElement("button");
|
|
4369
|
+
btn2.type = "button";
|
|
4370
|
+
btn2.className = "vela-callout-btn" + (item.primary ? " vela-callout-btn-primary" : "");
|
|
4371
|
+
btn2.textContent = item.label;
|
|
4372
|
+
btn2.addEventListener("click", () => {
|
|
4373
|
+
item.run();
|
|
4374
|
+
if (closesPanel(item)) this.pop?.hide();
|
|
4375
|
+
});
|
|
4376
|
+
actions.appendChild(btn2);
|
|
4377
|
+
}
|
|
4378
|
+
root.appendChild(actions);
|
|
4379
|
+
}
|
|
4380
|
+
body.appendChild(root);
|
|
4381
|
+
}
|
|
4382
|
+
};
|
|
4383
|
+
|
|
4198
4384
|
// src/widget/layout-picker.ts
|
|
4199
4385
|
var STYLE_ID = "vela-widget-layout-picker-v14";
|
|
4200
4386
|
var CSS2 = `
|
|
@@ -4642,6 +4828,44 @@ function legendActionsProviderFor(chart, context) {
|
|
|
4642
4828
|
return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(), info) }));
|
|
4643
4829
|
};
|
|
4644
4830
|
}
|
|
4831
|
+
var calloutRegistry = /* @__PURE__ */ new Map();
|
|
4832
|
+
function legendCallouts() {
|
|
4833
|
+
return [...calloutRegistry.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
4834
|
+
}
|
|
4835
|
+
function legendCalloutsProviderFor(chart, context) {
|
|
4836
|
+
return (indicatorId) => {
|
|
4837
|
+
const handle = chart.indicators().find((h) => h.id === indicatorId);
|
|
4838
|
+
if (!handle) return [];
|
|
4839
|
+
const info = { id: handle.id, title: handle.title, ...handle.source !== void 0 ? { source: handle.source } : {} };
|
|
4840
|
+
const views = [];
|
|
4841
|
+
for (const d of legendCallouts()) {
|
|
4842
|
+
const spec = d.callout(info);
|
|
4843
|
+
if (!spec) continue;
|
|
4844
|
+
views.push({
|
|
4845
|
+
id: d.id,
|
|
4846
|
+
icon: spec.icon,
|
|
4847
|
+
background: spec.background,
|
|
4848
|
+
...spec.color !== void 0 ? { color: spec.color } : {},
|
|
4849
|
+
tooltip: spec.tooltip,
|
|
4850
|
+
...spec.content !== void 0 ? {
|
|
4851
|
+
content: {
|
|
4852
|
+
...spec.content.title !== void 0 ? { title: spec.content.title } : {},
|
|
4853
|
+
items: spec.content.items.map(
|
|
4854
|
+
(item) => item.type === "text" ? item : {
|
|
4855
|
+
type: "button",
|
|
4856
|
+
label: item.label,
|
|
4857
|
+
...item.primary !== void 0 ? { primary: item.primary } : {},
|
|
4858
|
+
...item.close !== void 0 ? { close: item.close } : {},
|
|
4859
|
+
run: () => item.run(context(), info)
|
|
4860
|
+
}
|
|
4861
|
+
)
|
|
4862
|
+
}
|
|
4863
|
+
} : {}
|
|
4864
|
+
});
|
|
4865
|
+
}
|
|
4866
|
+
return views;
|
|
4867
|
+
};
|
|
4868
|
+
}
|
|
4645
4869
|
var stateHandlers = /* @__PURE__ */ new Map();
|
|
4646
4870
|
function statePersistenceHandlers(scope) {
|
|
4647
4871
|
return [...stateHandlers.values()].filter((h) => h.scope === scope);
|
|
@@ -17510,6 +17734,7 @@ var IndicatorHandleImpl = class {
|
|
|
17510
17734
|
constructor(id, title, controller, source) {
|
|
17511
17735
|
this.controller = controller;
|
|
17512
17736
|
this.schema = [];
|
|
17737
|
+
this.propsSchema = [];
|
|
17513
17738
|
this.visibleState = true;
|
|
17514
17739
|
this.bus = new TypedEventBus();
|
|
17515
17740
|
this.id = id;
|
|
@@ -17519,6 +17744,9 @@ var IndicatorHandleImpl = class {
|
|
|
17519
17744
|
get inputs() {
|
|
17520
17745
|
return this.schema;
|
|
17521
17746
|
}
|
|
17747
|
+
get props() {
|
|
17748
|
+
return this.propsSchema;
|
|
17749
|
+
}
|
|
17522
17750
|
get visible() {
|
|
17523
17751
|
return this.visibleState;
|
|
17524
17752
|
}
|
|
@@ -17528,6 +17756,12 @@ var IndicatorHandleImpl = class {
|
|
|
17528
17756
|
setInputs(values) {
|
|
17529
17757
|
this.controller.applyInputs(this.id, values);
|
|
17530
17758
|
}
|
|
17759
|
+
setProp(key, value) {
|
|
17760
|
+
this.controller.applyProps(this.id, { [key]: value });
|
|
17761
|
+
}
|
|
17762
|
+
setProps(values) {
|
|
17763
|
+
this.controller.applyProps(this.id, values);
|
|
17764
|
+
}
|
|
17531
17765
|
setVisible(visible) {
|
|
17532
17766
|
this.controller.setVisible(this.id, visible);
|
|
17533
17767
|
}
|
|
@@ -17547,6 +17781,9 @@ var IndicatorHandleImpl = class {
|
|
|
17547
17781
|
setSchema(schema) {
|
|
17548
17782
|
this.schema = schema;
|
|
17549
17783
|
}
|
|
17784
|
+
setPropsSchema(schema) {
|
|
17785
|
+
this.propsSchema = schema;
|
|
17786
|
+
}
|
|
17550
17787
|
/** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
|
|
17551
17788
|
setVisibleState(visible) {
|
|
17552
17789
|
this.visibleState = visible;
|
|
@@ -17560,6 +17797,8 @@ var IndicatorHandleImpl = class {
|
|
|
17560
17797
|
function summarizeModel(model) {
|
|
17561
17798
|
const series = {};
|
|
17562
17799
|
for (const s of model.series) series[s.kind] = (series[s.kind] ?? 0) + 1;
|
|
17800
|
+
const countOverlay = (items) => items?.filter((i) => i.overlay === true).length ?? 0;
|
|
17801
|
+
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);
|
|
17563
17802
|
return {
|
|
17564
17803
|
id: model.id,
|
|
17565
17804
|
title: model.title,
|
|
@@ -17579,7 +17818,8 @@ function summarizeModel(model) {
|
|
|
17579
17818
|
tables: model.tables?.length ?? 0,
|
|
17580
17819
|
barColors: model.barColors?.length ?? 0,
|
|
17581
17820
|
trades: model.trades?.length ?? 0,
|
|
17582
|
-
inputs: model.inputs.length
|
|
17821
|
+
inputs: model.inputs.length,
|
|
17822
|
+
forcedOverlay
|
|
17583
17823
|
};
|
|
17584
17824
|
}
|
|
17585
17825
|
function inspectModels(models) {
|
|
@@ -17600,7 +17840,8 @@ function inspectModels(models) {
|
|
|
17600
17840
|
linefills: sum((s) => s.linefills),
|
|
17601
17841
|
tables: sum((s) => s.tables),
|
|
17602
17842
|
barColors: sum((s) => s.barColors),
|
|
17603
|
-
trades: sum((s) => s.trades)
|
|
17843
|
+
trades: sum((s) => s.trades),
|
|
17844
|
+
forcedOverlay: sum((s) => s.forcedOverlay)
|
|
17604
17845
|
}
|
|
17605
17846
|
};
|
|
17606
17847
|
}
|
|
@@ -17744,7 +17985,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
17744
17985
|
for (const engine of engines) this.registerEngine(engine.language, engine);
|
|
17745
17986
|
this.defaultLanguage = config.defaultLanguage;
|
|
17746
17987
|
this.renderer.mount(container, config.theme);
|
|
17747
|
-
this.renderer.onInputChange(
|
|
17988
|
+
this.renderer.onInputChange(
|
|
17989
|
+
(e) => e.kind === "prop" ? this.applyProps(e.indicatorId, { [e.key]: e.value }) : this.applyInputs(e.indicatorId, { [e.key]: e.value })
|
|
17990
|
+
);
|
|
17748
17991
|
this.renderer.onRemoveIndicator((id) => this.removeIndicator(id));
|
|
17749
17992
|
this.renderer.onToggleIndicatorVisible?.((id, visible) => this.setVisible(id, visible));
|
|
17750
17993
|
this.paneActionUnsub = this.renderer.onPaneAction?.((a) => this.handlePaneAction(a)) ?? null;
|
|
@@ -18424,7 +18667,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18424
18667
|
const title = options.title ?? "Indicator";
|
|
18425
18668
|
const handle = new IndicatorHandleImpl(id, title, this, source);
|
|
18426
18669
|
this.handles.set(id, handle);
|
|
18427
|
-
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} } });
|
|
18670
|
+
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} }, propValues: { ...options.props ?? {} } });
|
|
18428
18671
|
void this.startIndicator(id, source, options, handle);
|
|
18429
18672
|
return handle;
|
|
18430
18673
|
}
|
|
@@ -18447,7 +18690,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18447
18690
|
return handle;
|
|
18448
18691
|
}
|
|
18449
18692
|
const inputValues = { ...descriptor.defaultInputs(), ...options.inputs ?? {} };
|
|
18450
|
-
this.registry.add({ id, title, source: type, inputValues, native: { type, instance: descriptor.create(), descriptor } });
|
|
18693
|
+
this.registry.add({ id, title, source: type, inputValues, propValues: {}, native: { type, instance: descriptor.create(), descriptor } });
|
|
18451
18694
|
handle.setSchema(descriptor.inputsSchema());
|
|
18452
18695
|
void this.startNativeIndicator(id, handle);
|
|
18453
18696
|
return handle;
|
|
@@ -18500,6 +18743,19 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18500
18743
|
if (record.session) record.session.update(record.inputValues);
|
|
18501
18744
|
else if (record.native && !record.hidden) record.native.instance.setInputs(record.inputValues);
|
|
18502
18745
|
}
|
|
18746
|
+
/** IndicatorController: re-run an indicator with merged declaration-prop overrides.
|
|
18747
|
+
* Same lifecycle as {@link applyInputs} — a prop change replays the whole script.
|
|
18748
|
+
* Script indicators only: natives have no declaration props. */
|
|
18749
|
+
applyProps(id, values) {
|
|
18750
|
+
const record = this.registry.get(id);
|
|
18751
|
+
if (!record?.session) return;
|
|
18752
|
+
record.propValues = { ...record.propValues, ...values };
|
|
18753
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
18754
|
+
record.pendingStructural = true;
|
|
18755
|
+
if (!record.hidden) this.setLoading(record, true);
|
|
18756
|
+
record.pendingCause = "inputs";
|
|
18757
|
+
record.session.update(record.inputValues, record.propValues);
|
|
18758
|
+
}
|
|
18503
18759
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
18504
18760
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
18505
18761
|
listIndicators() {
|
|
@@ -18648,6 +18904,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18648
18904
|
for (const input of prepared.inputs) defaults2[input.key] = input.defval;
|
|
18649
18905
|
record.inputValues = { ...defaults2, ...record.inputValues };
|
|
18650
18906
|
handle.setSchema(prepared.inputs);
|
|
18907
|
+
const propDefaults = {};
|
|
18908
|
+
for (const prop of prepared.props ?? []) propDefaults[prop.key] = prop.defval;
|
|
18909
|
+
record.propValues = { ...propDefaults, ...record.propValues };
|
|
18910
|
+
handle.setPropsSchema(prepared.props ?? []);
|
|
18651
18911
|
this.mountLoadingPlaceholder(id, record);
|
|
18652
18912
|
this.executeIndicator(id, handle);
|
|
18653
18913
|
} catch (err) {
|
|
@@ -18671,6 +18931,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18671
18931
|
getBars: () => this.bars,
|
|
18672
18932
|
fetchSeries: (sym, tf, range) => this.fetchSeries(sym, tf, range),
|
|
18673
18933
|
inputs: record.inputValues,
|
|
18934
|
+
props: record.propValues,
|
|
18674
18935
|
visibleRange: this.currentVisibleRange(),
|
|
18675
18936
|
mode,
|
|
18676
18937
|
// Mid-backfill session starts (add / re-show / price-style re-execute) let the
|
|
@@ -18764,6 +19025,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18764
19025
|
const meta = record.prepared.meta;
|
|
18765
19026
|
const model = {
|
|
18766
19027
|
id,
|
|
19028
|
+
// Deliberately the FULL title, never a shorttitle: while the script loads the
|
|
19029
|
+
// legend identifies it by its full name; the compact shorttitle arrives with
|
|
19030
|
+
// the first computed model and takes over from there.
|
|
18767
19031
|
title: record.options?.title ?? meta.title,
|
|
18768
19032
|
overlay: meta.overlay,
|
|
18769
19033
|
paneHint: meta.overlay ? "price" : "new",
|
|
@@ -18772,7 +19036,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18772
19036
|
backgrounds: [],
|
|
18773
19037
|
priceLines: [],
|
|
18774
19038
|
inputs: record.prepared.inputs,
|
|
18775
|
-
inputValues: record.inputValues
|
|
19039
|
+
inputValues: record.inputValues,
|
|
19040
|
+
...record.prepared.props ? { props: record.prepared.props, propValues: record.propValues } : {}
|
|
18776
19041
|
};
|
|
18777
19042
|
const paneId = this.routePane(id, model, record.options ?? {});
|
|
18778
19043
|
this.placeModel(model, id, paneId);
|
|
@@ -19005,7 +19270,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19005
19270
|
record.model = model;
|
|
19006
19271
|
if (record.pendingStructural) {
|
|
19007
19272
|
record.renderHandle = this.renderer.mountIndicator(model);
|
|
19008
|
-
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues);
|
|
19273
|
+
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
19009
19274
|
record.pendingStructural = false;
|
|
19010
19275
|
} else {
|
|
19011
19276
|
this.renderer.updateIndicator(record.renderHandle, modelToValuePatch(model));
|
|
@@ -19032,16 +19297,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19032
19297
|
placeModel(model, id, paneId) {
|
|
19033
19298
|
model.id = id;
|
|
19034
19299
|
model.paneId = paneId;
|
|
19035
|
-
|
|
19036
|
-
for (const
|
|
19037
|
-
for (const
|
|
19300
|
+
const paneOf = (item) => item.overlay === true ? "price" : paneId;
|
|
19301
|
+
for (const series of model.series) series.paneId = paneOf(series);
|
|
19302
|
+
for (const fill of model.fills) fill.paneId = paneOf(fill);
|
|
19303
|
+
for (const bg of model.backgrounds) bg.paneId = paneOf(bg);
|
|
19038
19304
|
for (const line of model.priceLines) line.paneId = paneId;
|
|
19039
|
-
if (model.lines) for (const ln of model.lines) ln.paneId =
|
|
19040
|
-
if (model.boxes) for (const bx of model.boxes) bx.paneId =
|
|
19041
|
-
if (model.labels) for (const lb of model.labels) lb.paneId =
|
|
19042
|
-
if (model.polylines) for (const pl of model.polylines) pl.paneId =
|
|
19043
|
-
if (model.linefills) for (const lf of model.linefills) lf.paneId =
|
|
19044
|
-
if (model.tables) for (const tb of model.tables) tb.paneId =
|
|
19305
|
+
if (model.lines) for (const ln of model.lines) ln.paneId = paneOf(ln);
|
|
19306
|
+
if (model.boxes) for (const bx of model.boxes) bx.paneId = paneOf(bx);
|
|
19307
|
+
if (model.labels) for (const lb of model.labels) lb.paneId = paneOf(lb);
|
|
19308
|
+
if (model.polylines) for (const pl of model.polylines) pl.paneId = paneOf(pl);
|
|
19309
|
+
if (model.linefills) for (const lf of model.linefills) lf.paneId = paneOf(lf);
|
|
19310
|
+
if (model.tables) for (const tb of model.tables) tb.paneId = paneOf(tb);
|
|
19045
19311
|
}
|
|
19046
19312
|
emitContextChanged(id) {
|
|
19047
19313
|
if (!this.registry.get(id)?.session?.getContext) return;
|
|
@@ -19194,6 +19460,15 @@ var RendererControl = class {
|
|
|
19194
19460
|
setLegendActions(provider) {
|
|
19195
19461
|
this.renderer.setLegendActions?.(provider);
|
|
19196
19462
|
}
|
|
19463
|
+
/**
|
|
19464
|
+
* Wire the legend rows' HOST-CONTRIBUTED callout bubbles (the shells route the
|
|
19465
|
+
* plugin registry through this; see `registerLegendCallout`). Silent on a renderer
|
|
19466
|
+
* without the seam — contributed callouts simply never show there, same graceful
|
|
19467
|
+
* degradation as {@link setLegendActions}.
|
|
19468
|
+
*/
|
|
19469
|
+
setLegendCallouts(provider) {
|
|
19470
|
+
this.renderer.setLegendCallouts?.(provider);
|
|
19471
|
+
}
|
|
19197
19472
|
/**
|
|
19198
19473
|
* Replace the indicator legend's fold toggle with a host action (or restore it with
|
|
19199
19474
|
* `null`) — multi-chart shells point the chip at their indicator overview instead of
|
|
@@ -19703,6 +19978,10 @@ function inputVisible(when, values) {
|
|
|
19703
19978
|
}
|
|
19704
19979
|
|
|
19705
19980
|
// src/renderers/shared/IndicatorInputsDialog.ts
|
|
19981
|
+
var PROPS_TAB = "Properties";
|
|
19982
|
+
function bagOf(row, decl) {
|
|
19983
|
+
return decl.prop ? row.propValues : row.values;
|
|
19984
|
+
}
|
|
19706
19985
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
19707
19986
|
var IndicatorInputsDialog = class {
|
|
19708
19987
|
constructor(host) {
|
|
@@ -19713,6 +19992,7 @@ var IndicatorInputsDialog = class {
|
|
|
19713
19992
|
this.openId = null;
|
|
19714
19993
|
this.row = null;
|
|
19715
19994
|
this.snapshot = null;
|
|
19995
|
+
this.propSnapshot = null;
|
|
19716
19996
|
this.dialogTips = [];
|
|
19717
19997
|
/** Re-applies every `when` gate against current values; set while the dialog is open. */
|
|
19718
19998
|
this.refreshVisibility = null;
|
|
@@ -19735,10 +20015,11 @@ var IndicatorInputsDialog = class {
|
|
|
19735
20015
|
}
|
|
19736
20016
|
open(row) {
|
|
19737
20017
|
this.close();
|
|
19738
|
-
if (row.inputs.length === 0) return;
|
|
20018
|
+
if (row.inputs.length === 0 && row.props.length === 0) return;
|
|
19739
20019
|
this.row = row;
|
|
19740
20020
|
this.openId = row.id;
|
|
19741
20021
|
this.snapshot = { ...row.values };
|
|
20022
|
+
this.propSnapshot = { ...row.propValues };
|
|
19742
20023
|
ensureDialogStyles();
|
|
19743
20024
|
const t = this.host.theme();
|
|
19744
20025
|
const border = "var(--vela-border)";
|
|
@@ -19746,7 +20027,7 @@ var IndicatorInputsDialog = class {
|
|
|
19746
20027
|
const host = this.host.dialogHost() ?? this.host.container;
|
|
19747
20028
|
const ui = new Dialog({
|
|
19748
20029
|
host,
|
|
19749
|
-
title: row.
|
|
20030
|
+
title: row.title,
|
|
19750
20031
|
// Non-modal: live-edit dialog — a modal machine locks pointer events on the
|
|
19751
20032
|
// whole body, killing the chart and the body-portaled popovers.
|
|
19752
20033
|
modal: false,
|
|
@@ -19778,6 +20059,16 @@ var IndicatorInputsDialog = class {
|
|
|
19778
20059
|
text: () => "Close"
|
|
19779
20060
|
}));
|
|
19780
20061
|
const tabDefs = tabInputs(row.inputs);
|
|
20062
|
+
if (row.props.length > 0) {
|
|
20063
|
+
const propDecls = row.props.map((p) => {
|
|
20064
|
+
const d = { ...p, prop: true };
|
|
20065
|
+
delete d.tab;
|
|
20066
|
+
return d;
|
|
20067
|
+
});
|
|
20068
|
+
const existing = tabDefs.find((t2) => t2.name === PROPS_TAB);
|
|
20069
|
+
if (existing) existing.inputs.push(...propDecls);
|
|
20070
|
+
else tabDefs.push({ name: PROPS_TAB, inputs: propDecls });
|
|
20071
|
+
}
|
|
19781
20072
|
const tabs = document.createElement("div");
|
|
19782
20073
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
19783
20074
|
const tabEls = [];
|
|
@@ -19880,9 +20171,10 @@ var IndicatorInputsDialog = class {
|
|
|
19880
20171
|
this.openId = null;
|
|
19881
20172
|
this.row = null;
|
|
19882
20173
|
this.snapshot = null;
|
|
20174
|
+
this.propSnapshot = null;
|
|
19883
20175
|
ui?.destroy();
|
|
19884
20176
|
}
|
|
19885
|
-
/** Restore every input to its open-time value (re-running the indicator), then close. */
|
|
20177
|
+
/** Restore every input and prop to its open-time value (re-running the indicator), then close. */
|
|
19886
20178
|
revertAndClose() {
|
|
19887
20179
|
const row = this.row;
|
|
19888
20180
|
const snap = this.snapshot;
|
|
@@ -19896,6 +20188,17 @@ var IndicatorInputsDialog = class {
|
|
|
19896
20188
|
}
|
|
19897
20189
|
}
|
|
19898
20190
|
}
|
|
20191
|
+
const propSnap = this.propSnapshot;
|
|
20192
|
+
if (row && propSnap) {
|
|
20193
|
+
for (const p of row.props) {
|
|
20194
|
+
const snapped = propSnap[p.key];
|
|
20195
|
+
const before = snapped !== void 0 ? snapped : p.defval;
|
|
20196
|
+
if (row.propValues[p.key] !== before) {
|
|
20197
|
+
row.propValues[p.key] = before;
|
|
20198
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: before, kind: "prop" });
|
|
20199
|
+
}
|
|
20200
|
+
}
|
|
20201
|
+
}
|
|
19899
20202
|
this.close();
|
|
19900
20203
|
}
|
|
19901
20204
|
/** The footer's reset button — same chip as Cancel, pinned to the LEFT edge
|
|
@@ -19913,19 +20216,27 @@ var IndicatorInputsDialog = class {
|
|
|
19913
20216
|
const row = this.row;
|
|
19914
20217
|
if (!row) return;
|
|
19915
20218
|
const snap = this.snapshot;
|
|
20219
|
+
const propSnap = this.propSnapshot;
|
|
19916
20220
|
for (const inp of row.inputs) {
|
|
19917
20221
|
if (row.values[inp.key] !== inp.defval) {
|
|
19918
20222
|
row.values[inp.key] = inp.defval;
|
|
19919
20223
|
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value: inp.defval });
|
|
19920
20224
|
}
|
|
19921
20225
|
}
|
|
20226
|
+
for (const p of row.props) {
|
|
20227
|
+
if (row.propValues[p.key] !== p.defval) {
|
|
20228
|
+
row.propValues[p.key] = p.defval;
|
|
20229
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: p.defval, kind: "prop" });
|
|
20230
|
+
}
|
|
20231
|
+
}
|
|
19922
20232
|
this.open(row);
|
|
19923
20233
|
if (snap) this.snapshot = snap;
|
|
20234
|
+
if (propSnap) this.propSnapshot = propSnap;
|
|
19924
20235
|
}
|
|
19925
20236
|
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
19926
|
-
commit(row,
|
|
19927
|
-
row.
|
|
19928
|
-
this.host.onChange?.({ indicatorId: row.id, key, value });
|
|
20237
|
+
commit(row, decl, value) {
|
|
20238
|
+
bagOf(row, decl)[decl.key] = value;
|
|
20239
|
+
this.host.onChange?.({ indicatorId: row.id, key: decl.key, value, ...decl.prop ? { kind: "prop" } : {} });
|
|
19929
20240
|
this.refreshVisibility?.();
|
|
19930
20241
|
}
|
|
19931
20242
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
@@ -19952,7 +20263,7 @@ var IndicatorInputsDialog = class {
|
|
|
19952
20263
|
const id = idOf(lead);
|
|
19953
20264
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
19954
20265
|
if (lead.type === "bool") {
|
|
19955
|
-
const current = Boolean(row
|
|
20266
|
+
const current = Boolean(bagOf(row, lead)[lead.key] ?? lead.defval);
|
|
19956
20267
|
return append(fieldRow({
|
|
19957
20268
|
label: nameOf(lead),
|
|
19958
20269
|
id,
|
|
@@ -19961,7 +20272,7 @@ var IndicatorInputsDialog = class {
|
|
|
19961
20272
|
toggle: {
|
|
19962
20273
|
id,
|
|
19963
20274
|
checked: current,
|
|
19964
|
-
onChange: (v) => this.commit(row, lead
|
|
20275
|
+
onChange: (v) => this.commit(row, lead, v)
|
|
19965
20276
|
}
|
|
19966
20277
|
}));
|
|
19967
20278
|
}
|
|
@@ -19986,8 +20297,8 @@ var IndicatorInputsDialog = class {
|
|
|
19986
20297
|
}
|
|
19987
20298
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
19988
20299
|
buildControl(row, inp, id) {
|
|
19989
|
-
const current = row
|
|
19990
|
-
const emit = (value) => this.commit(row, inp
|
|
20300
|
+
const current = bagOf(row, inp)[inp.key] ?? inp.defval;
|
|
20301
|
+
const emit = (value) => this.commit(row, inp, value);
|
|
19991
20302
|
if (inp.type === "bool") {
|
|
19992
20303
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
19993
20304
|
}
|
|
@@ -19998,7 +20309,7 @@ var IndicatorInputsDialog = class {
|
|
|
19998
20309
|
kind: "color",
|
|
19999
20310
|
id,
|
|
20000
20311
|
theme: this.host.theme(),
|
|
20001
|
-
get: () => String(row
|
|
20312
|
+
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
20002
20313
|
onChange: (v) => emit(v)
|
|
20003
20314
|
}).el;
|
|
20004
20315
|
}
|
|
@@ -20561,6 +20872,7 @@ function timeParts(ts) {
|
|
|
20561
20872
|
}
|
|
20562
20873
|
|
|
20563
20874
|
// src/renderers/shared/InputsUI.ts
|
|
20875
|
+
var LEGEND_AT_TOP_ATTR = "data-vela-pane-at-top";
|
|
20564
20876
|
var STATUS_KEYFRAMES = "@keyframes vela-ind-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.6)}}";
|
|
20565
20877
|
var LEGEND_ICON_PX2 = 16;
|
|
20566
20878
|
var LEGEND_CTL_PX = 18;
|
|
@@ -20599,10 +20911,14 @@ var InputsUI = class {
|
|
|
20599
20911
|
this.moveApi = null;
|
|
20600
20912
|
/** Host-contributed legend actions, resolved PER ROW at render time (see setLegendActions). */
|
|
20601
20913
|
this.legendActions = null;
|
|
20914
|
+
/** Host-contributed callout bubbles, resolved PER ROW at render time (see setLegendCallouts). */
|
|
20915
|
+
this.legendCallouts = null;
|
|
20602
20916
|
/** Chrome-tooltip disposers, per row id — a tip open at removal must not outlive its row. */
|
|
20603
20917
|
this.rowTips = /* @__PURE__ */ new Map();
|
|
20604
20918
|
/** Same, for the contributed extras only (rebuilt independently by setLegendActions). */
|
|
20605
20919
|
this.extrasTips = /* @__PURE__ */ new Map();
|
|
20920
|
+
/** Same, for the contributed callouts only (rebuilt independently by setLegendCallouts). */
|
|
20921
|
+
this.calloutTips = /* @__PURE__ */ new Map();
|
|
20606
20922
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
20607
20923
|
this.moveMenu = null;
|
|
20608
20924
|
this.moveTargets = [];
|
|
@@ -20760,6 +21076,42 @@ var InputsUI = class {
|
|
|
20760
21076
|
extrasEl.appendChild(btn2);
|
|
20761
21077
|
}
|
|
20762
21078
|
}
|
|
21079
|
+
/**
|
|
21080
|
+
* Wire the host-contributed callout bubbles. Same contract as
|
|
21081
|
+
* {@link setLegendActions}: re-calling replaces the provider and re-projects the
|
|
21082
|
+
* rows already on screen.
|
|
21083
|
+
*/
|
|
21084
|
+
setLegendCallouts(provider) {
|
|
21085
|
+
this.legendCallouts = provider;
|
|
21086
|
+
for (const row of this.rows.values()) this.renderCallouts(row);
|
|
21087
|
+
}
|
|
21088
|
+
/** (Re)build one row's callout bubbles — tinted icon circles beside the title whose
|
|
21089
|
+
* click (when the view carries content) deploys a panel of text and actions. */
|
|
21090
|
+
renderCallouts(row) {
|
|
21091
|
+
this.disposeTips(this.calloutTips, row.id);
|
|
21092
|
+
for (const bubble of row.callouts) bubble.destroy();
|
|
21093
|
+
row.callouts = [];
|
|
21094
|
+
row.calloutsEl.replaceChildren();
|
|
21095
|
+
const views = this.legendCallouts?.(row.id) ?? [];
|
|
21096
|
+
row.calloutsEl.style.display = views.length > 0 ? "inline-flex" : "none";
|
|
21097
|
+
for (const view of views) {
|
|
21098
|
+
const bubble = new CalloutBubble({
|
|
21099
|
+
icon: view.icon,
|
|
21100
|
+
background: view.background,
|
|
21101
|
+
...view.color !== void 0 ? { color: view.color } : {},
|
|
21102
|
+
label: view.tooltip,
|
|
21103
|
+
...view.content !== void 0 ? { panel: view.content } : {},
|
|
21104
|
+
// The panel portals into the plot host and self-tokens: it must work on
|
|
21105
|
+
// a bare chart, where no `.vela-ui` token ancestor exists.
|
|
21106
|
+
host: this.container,
|
|
21107
|
+
theme: () => this.theme
|
|
21108
|
+
});
|
|
21109
|
+
bubble.el.dataset.legendCallout = view.id;
|
|
21110
|
+
this.tip(this.calloutTips, row.id, bubble.el, view.tooltip);
|
|
21111
|
+
row.callouts.push(bubble);
|
|
21112
|
+
row.calloutsEl.appendChild(bubble.el);
|
|
21113
|
+
}
|
|
21114
|
+
}
|
|
20763
21115
|
/** Reposition the per-pane legend containers after a layout change. */
|
|
20764
21116
|
reposition() {
|
|
20765
21117
|
for (const [paneId, lg] of this.legends) this.positionLegend(lg, paneId);
|
|
@@ -20927,6 +21279,7 @@ var InputsUI = class {
|
|
|
20927
21279
|
}
|
|
20928
21280
|
positionLegend(lg, paneId) {
|
|
20929
21281
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
21282
|
+
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
20930
21283
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
20931
21284
|
const collapsed = this.paneCollapse.has(paneId);
|
|
20932
21285
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
@@ -21002,13 +21355,13 @@ var InputsUI = class {
|
|
|
21002
21355
|
}
|
|
21003
21356
|
/** Create or update an indicator's legend row (in the legend for its pane). */
|
|
21004
21357
|
upsert(id, title, inputs, values, paneId = "price", opts = {}) {
|
|
21005
|
-
const settingsTitle = opts.settingsTitle ?? title;
|
|
21006
21358
|
const existing = this.rows.get(id);
|
|
21007
21359
|
if (existing) {
|
|
21008
21360
|
existing.title = title;
|
|
21009
|
-
existing.settingsTitle = settingsTitle;
|
|
21010
21361
|
existing.inputs = inputs;
|
|
21011
21362
|
existing.values = { ...values };
|
|
21363
|
+
existing.props = opts.props ?? [];
|
|
21364
|
+
existing.propValues = { ...opts.propValues ?? {} };
|
|
21012
21365
|
existing.titleEl.textContent = title;
|
|
21013
21366
|
if (existing.paneId !== paneId) {
|
|
21014
21367
|
existing.paneId = paneId;
|
|
@@ -21060,6 +21413,9 @@ var InputsUI = class {
|
|
|
21060
21413
|
titleWrap.appendChild(beta);
|
|
21061
21414
|
}
|
|
21062
21415
|
el.appendChild(titleWrap);
|
|
21416
|
+
const calloutsEl = document.createElement("span");
|
|
21417
|
+
calloutsEl.style.cssText = `display:none;align-items:center;gap:4px;flex:none;margin-left:${LEGEND_TITLE_STATUS_GAP_PX}px;`;
|
|
21418
|
+
el.appendChild(calloutsEl);
|
|
21063
21419
|
el.appendChild(statusEl);
|
|
21064
21420
|
const valuesEl = document.createElement("span");
|
|
21065
21421
|
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;`;
|
|
@@ -21076,13 +21432,13 @@ var InputsUI = class {
|
|
|
21076
21432
|
eye.className = "vela-ind-ctl";
|
|
21077
21433
|
eye.style.cssText = LEGEND_CTL_CSS;
|
|
21078
21434
|
eye.addEventListener("click", () => {
|
|
21079
|
-
const
|
|
21080
|
-
this.onToggleVisible?.(id, Boolean(
|
|
21435
|
+
const row2 = this.rows.get(id);
|
|
21436
|
+
this.onToggleVisible?.(id, Boolean(row2?.hidden));
|
|
21081
21437
|
});
|
|
21082
21438
|
controlsEl.appendChild(eye);
|
|
21083
21439
|
eyeEl = eye;
|
|
21084
21440
|
}
|
|
21085
|
-
if (inputs.length > 0) {
|
|
21441
|
+
if (inputs.length > 0 || (opts.props ?? []).length > 0) {
|
|
21086
21442
|
const gear = document.createElement("button");
|
|
21087
21443
|
gear.type = "button";
|
|
21088
21444
|
gear.setAttribute("aria-label", "Settings");
|
|
@@ -21122,7 +21478,9 @@ var InputsUI = class {
|
|
|
21122
21478
|
controlsEl.appendChild(close);
|
|
21123
21479
|
el.appendChild(controlsEl);
|
|
21124
21480
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
21125
|
-
|
|
21481
|
+
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 };
|
|
21482
|
+
this.rows.set(id, row);
|
|
21483
|
+
this.renderCallouts(row);
|
|
21126
21484
|
this.syncFoldToggle();
|
|
21127
21485
|
}
|
|
21128
21486
|
/** Place a row in its pane's legend — native rows PREPEND (pinned to the top), Pine rows append. */
|
|
@@ -21131,9 +21489,11 @@ var InputsUI = class {
|
|
|
21131
21489
|
else container.appendChild(el);
|
|
21132
21490
|
}
|
|
21133
21491
|
/** Reflect programmatic input changes (so a re-opened dialog shows current values). */
|
|
21134
|
-
setValues(id, values) {
|
|
21492
|
+
setValues(id, values, props) {
|
|
21135
21493
|
const row = this.rows.get(id);
|
|
21136
|
-
if (row)
|
|
21494
|
+
if (!row) return;
|
|
21495
|
+
row.values = { ...row.values, ...values };
|
|
21496
|
+
if (props) row.propValues = { ...row.propValues, ...props };
|
|
21137
21497
|
}
|
|
21138
21498
|
/**
|
|
21139
21499
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
@@ -21215,8 +21575,13 @@ var InputsUI = class {
|
|
|
21215
21575
|
syncRowActions(row) {
|
|
21216
21576
|
const open2 = row.highlighted;
|
|
21217
21577
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
21218
|
-
if (open2)
|
|
21219
|
-
|
|
21578
|
+
if (open2) {
|
|
21579
|
+
row.el.appendChild(row.statusEl);
|
|
21580
|
+
row.el.appendChild(row.calloutsEl);
|
|
21581
|
+
} else {
|
|
21582
|
+
row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
21583
|
+
row.el.insertBefore(row.calloutsEl, row.statusEl);
|
|
21584
|
+
}
|
|
21220
21585
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
21221
21586
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
21222
21587
|
if (child === row.extrasEl) {
|
|
@@ -21234,10 +21599,12 @@ var InputsUI = class {
|
|
|
21234
21599
|
}
|
|
21235
21600
|
remove(id) {
|
|
21236
21601
|
const row = this.rows.get(id);
|
|
21602
|
+
for (const bubble of row?.callouts ?? []) bubble.destroy();
|
|
21237
21603
|
row?.el.remove();
|
|
21238
21604
|
this.rows.delete(id);
|
|
21239
21605
|
this.disposeTips(this.rowTips, id);
|
|
21240
21606
|
this.disposeTips(this.extrasTips, id);
|
|
21607
|
+
this.disposeTips(this.calloutTips, id);
|
|
21241
21608
|
if (this.selectedId === id) this.selectedId = null;
|
|
21242
21609
|
if (this.inputsDialog.openId === id) this.inputsDialog.close();
|
|
21243
21610
|
this.syncFoldToggle();
|
|
@@ -21257,6 +21624,8 @@ var InputsUI = class {
|
|
|
21257
21624
|
this.rowMenu = null;
|
|
21258
21625
|
for (const id of [...this.rowTips.keys()]) this.disposeTips(this.rowTips, id);
|
|
21259
21626
|
for (const id of [...this.extrasTips.keys()]) this.disposeTips(this.extrasTips, id);
|
|
21627
|
+
for (const id of [...this.calloutTips.keys()]) this.disposeTips(this.calloutTips, id);
|
|
21628
|
+
for (const row of this.rows.values()) for (const bubble of row.callouts) bubble.destroy();
|
|
21260
21629
|
for (const lg of this.legends.values()) lg.remove();
|
|
21261
21630
|
this.legends.clear();
|
|
21262
21631
|
this.rows.clear();
|
|
@@ -21611,6 +21980,32 @@ var SIZE_PX3 = {
|
|
|
21611
21980
|
large: 16,
|
|
21612
21981
|
huge: 20
|
|
21613
21982
|
};
|
|
21983
|
+
function fontPxOf(size) {
|
|
21984
|
+
if (typeof size === "number") return size > 0 ? size : SIZE_PX3.auto;
|
|
21985
|
+
return SIZE_PX3[size] ?? SIZE_PX3.auto;
|
|
21986
|
+
}
|
|
21987
|
+
function tableHasContent(t) {
|
|
21988
|
+
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
21989
|
+
}
|
|
21990
|
+
function mergeRenderPlan(t) {
|
|
21991
|
+
const span = /* @__PURE__ */ new Map();
|
|
21992
|
+
const omit = /* @__PURE__ */ new Set();
|
|
21993
|
+
for (const m of t.merges) {
|
|
21994
|
+
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
21995
|
+
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
21996
|
+
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
21997
|
+
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
21998
|
+
}
|
|
21999
|
+
}
|
|
22000
|
+
}
|
|
22001
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
22002
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
22003
|
+
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
22004
|
+
}
|
|
22005
|
+
}
|
|
22006
|
+
for (const key of span.keys()) omit.delete(key);
|
|
22007
|
+
return { span, omit };
|
|
22008
|
+
}
|
|
21614
22009
|
var TableOverlay = class {
|
|
21615
22010
|
constructor(container, theme, paneBounds) {
|
|
21616
22011
|
this.container = container;
|
|
@@ -21631,7 +22026,9 @@ var TableOverlay = class {
|
|
|
21631
22026
|
update(tables) {
|
|
21632
22027
|
this.lastTables = tables;
|
|
21633
22028
|
this.root.replaceChildren();
|
|
21634
|
-
for (const t of tables)
|
|
22029
|
+
for (const t of tables) {
|
|
22030
|
+
if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
|
|
22031
|
+
}
|
|
21635
22032
|
}
|
|
21636
22033
|
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
21637
22034
|
reposition() {
|
|
@@ -21646,39 +22043,36 @@ var TableOverlay = class {
|
|
|
21646
22043
|
this.root.remove();
|
|
21647
22044
|
}
|
|
21648
22045
|
renderTable(t) {
|
|
22046
|
+
const b = this.paneBounds(t.paneId);
|
|
21649
22047
|
const wrap = document.createElement("div");
|
|
21650
22048
|
wrap.style.position = "absolute";
|
|
21651
|
-
|
|
22049
|
+
if (t.frameColor && t.frameWidth > 0) wrap.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
|
|
22050
|
+
this.anchor(wrap, t.position, b);
|
|
21652
22051
|
const table = document.createElement("table");
|
|
21653
22052
|
Object.assign(table.style, {
|
|
21654
22053
|
borderCollapse: "collapse",
|
|
21655
22054
|
background: t.bgColor ?? "transparent",
|
|
21656
22055
|
fontFamily: this.theme.fontFamily || "sans-serif",
|
|
21657
|
-
// Frame as an inset shadow (not a `border`) so it stays independent of the
|
|
21658
|
-
// collapsed cell borders even when frame_width != border_width.
|
|
21659
|
-
boxShadow: t.frameColor && t.frameWidth > 0 ? `inset 0 0 0 ${t.frameWidth}px ${t.frameColor}` : "none",
|
|
21660
22056
|
border: "none",
|
|
21661
22057
|
tableLayout: "auto",
|
|
21662
22058
|
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
21663
22059
|
pointerEvents: "auto"
|
|
21664
22060
|
});
|
|
21665
|
-
const span
|
|
21666
|
-
const
|
|
21667
|
-
|
|
21668
|
-
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
21669
|
-
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
21670
|
-
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
21671
|
-
if (r !== m.startRow || c !== m.startCol) skip.add(`${r}:${c}`);
|
|
21672
|
-
}
|
|
21673
|
-
}
|
|
21674
|
-
}
|
|
22061
|
+
const { span, omit } = mergeRenderPlan(t);
|
|
22062
|
+
const plotW = Math.max(0, (this.root.clientWidth || this.container.clientWidth) - b.rightAxis);
|
|
22063
|
+
const paneH = b.height;
|
|
21675
22064
|
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
21676
22065
|
for (let r = 0; r < t.rows; r += 1) {
|
|
21677
22066
|
const tr = document.createElement("tr");
|
|
21678
22067
|
for (let c = 0; c < t.columns; c += 1) {
|
|
22068
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
21679
22069
|
const cell = t.cells[r]?.[c] ?? null;
|
|
21680
|
-
if (skip.has(`${r}:${c}`) || cell?.merged) continue;
|
|
21681
22070
|
const td = document.createElement("td");
|
|
22071
|
+
if (cell === null) {
|
|
22072
|
+
Object.assign(td.style, { padding: "0", border: "none" });
|
|
22073
|
+
tr.appendChild(td);
|
|
22074
|
+
continue;
|
|
22075
|
+
}
|
|
21682
22076
|
const sp = span.get(`${r}:${c}`);
|
|
21683
22077
|
if (sp) {
|
|
21684
22078
|
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
@@ -21687,18 +22081,22 @@ var TableOverlay = class {
|
|
|
21687
22081
|
Object.assign(td.style, {
|
|
21688
22082
|
border: cellBorder,
|
|
21689
22083
|
padding: "2px 6px",
|
|
21690
|
-
background: cell
|
|
21691
|
-
color: cell
|
|
21692
|
-
textAlign: cell
|
|
21693
|
-
verticalAlign: cell
|
|
21694
|
-
fontSize: `${
|
|
21695
|
-
fontFamily: cell
|
|
21696
|
-
fontWeight: cell
|
|
21697
|
-
fontStyle: cell
|
|
21698
|
-
|
|
22084
|
+
background: cell.bgColor ?? "transparent",
|
|
22085
|
+
color: cell.textColor ?? this.theme.textColor,
|
|
22086
|
+
textAlign: cell.hAlign,
|
|
22087
|
+
verticalAlign: cell.vAlign === "top" ? "top" : cell.vAlign === "bottom" ? "bottom" : "middle",
|
|
22088
|
+
fontSize: `${fontPxOf(cell.textSize)}px`,
|
|
22089
|
+
fontFamily: cell.fontFamily === "monospace" ? "monospace" : "inherit",
|
|
22090
|
+
fontWeight: cell.bold ? "bold" : "normal",
|
|
22091
|
+
fontStyle: cell.italic ? "italic" : "normal",
|
|
22092
|
+
// Pine cell text never wraps; `\n` still breaks lines. Wrapping
|
|
22093
|
+
// used to collapse unicode sparklines and ━━━ dividers.
|
|
22094
|
+
whiteSpace: "pre"
|
|
21699
22095
|
});
|
|
21700
|
-
if (cell
|
|
21701
|
-
td.
|
|
22096
|
+
if (cell.width) td.style.width = `${cell.width / 100 * plotW}px`;
|
|
22097
|
+
if (cell.height) td.style.height = `${cell.height / 100 * paneH}px`;
|
|
22098
|
+
if (cell.tooltip) td.title = cell.tooltip;
|
|
22099
|
+
td.textContent = cell.text ?? "";
|
|
21702
22100
|
tr.appendChild(td);
|
|
21703
22101
|
}
|
|
21704
22102
|
table.appendChild(tr);
|
|
@@ -22300,8 +22698,14 @@ var WebGL2Backend = class {
|
|
|
22300
22698
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
22301
22699
|
};
|
|
22302
22700
|
b.alpha = this.modelAlpha;
|
|
22303
|
-
for (const m of models) for (const bgSpan of m.backgrounds) this.emitBackground(b, bgSpan, pane, coords);
|
|
22304
|
-
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));
|
|
22701
|
+
for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
|
|
22702
|
+
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));
|
|
22703
|
+
if (isPrice) {
|
|
22704
|
+
for (const m of scene.indicators.values()) {
|
|
22705
|
+
for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
|
|
22706
|
+
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
22707
|
+
}
|
|
22708
|
+
}
|
|
22305
22709
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
22306
22710
|
let candleDrawn = false;
|
|
22307
22711
|
for (const m of models) {
|
|
@@ -22315,7 +22719,7 @@ var WebGL2Backend = class {
|
|
|
22315
22719
|
b.alpha = this.modelAlpha;
|
|
22316
22720
|
const off = scene.offsetOf(m.id);
|
|
22317
22721
|
const mp = effPane(m);
|
|
22318
|
-
for (const s of m.series) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
22722
|
+
for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
22319
22723
|
}
|
|
22320
22724
|
if (drawCandles && !candleDrawn) {
|
|
22321
22725
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -22324,6 +22728,12 @@ var WebGL2Backend = class {
|
|
|
22324
22728
|
}
|
|
22325
22729
|
drawSlicesUpTo(Infinity);
|
|
22326
22730
|
b.alpha = this.modelAlpha;
|
|
22731
|
+
if (isPrice) {
|
|
22732
|
+
for (const m of scene.indicators.values()) {
|
|
22733
|
+
const off = scene.offsetOf(m.id);
|
|
22734
|
+
for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
|
|
22735
|
+
}
|
|
22736
|
+
}
|
|
22327
22737
|
for (const m of models) {
|
|
22328
22738
|
const mp = effPane(m);
|
|
22329
22739
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -22370,7 +22780,7 @@ var WebGL2Backend = class {
|
|
|
22370
22780
|
for (const pane of scene.orderedPanes()) {
|
|
22371
22781
|
const b = this.glowBatch;
|
|
22372
22782
|
b.reset();
|
|
22373
|
-
this.emitGlowSources(b, scene
|
|
22783
|
+
this.emitGlowSources(b, scene, pane, coords, i0, i1);
|
|
22374
22784
|
if (b.vertexCount === 0) continue;
|
|
22375
22785
|
const topH = Math.round(pane.bounds.top * dpr * 0.5);
|
|
22376
22786
|
const botH = Math.round((pane.bounds.top + pane.bounds.height) * dpr * 0.5);
|
|
@@ -22439,15 +22849,23 @@ var WebGL2Backend = class {
|
|
|
22439
22849
|
gl.bindVertexArray(null);
|
|
22440
22850
|
return true;
|
|
22441
22851
|
}
|
|
22442
|
-
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
22443
|
-
|
|
22444
|
-
|
|
22445
|
-
|
|
22446
|
-
|
|
22447
|
-
|
|
22448
|
-
|
|
22449
|
-
|
|
22450
|
-
|
|
22852
|
+
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
22853
|
+
* Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
|
|
22854
|
+
emitGlowSources(b, scene, pane, coords, i0, i1) {
|
|
22855
|
+
const emitOne = (s, off) => {
|
|
22856
|
+
if (!isLineLikeSeries(s) || s.visible === false) return;
|
|
22857
|
+
if (s.kind === "histogram" || s.kind === "columns") return;
|
|
22858
|
+
if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
|
|
22859
|
+
else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
|
|
22860
|
+
};
|
|
22861
|
+
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
22862
|
+
const off = scene.offsetOf(m.id);
|
|
22863
|
+
for (const s of m.series) if (s.overlay !== true) emitOne(s, off);
|
|
22864
|
+
}
|
|
22865
|
+
if (pane.kind === "price") {
|
|
22866
|
+
for (const m of scene.indicators.values()) {
|
|
22867
|
+
const off = scene.offsetOf(m.id);
|
|
22868
|
+
for (const s of m.series) if (s.overlay === true) emitOne(s, off);
|
|
22451
22869
|
}
|
|
22452
22870
|
}
|
|
22453
22871
|
}
|
|
@@ -22730,9 +23148,9 @@ var WebGL2Backend = class {
|
|
|
22730
23148
|
b.alpha = this.candleBodyAlpha;
|
|
22731
23149
|
b.rect(g.bodyX, bodyTop, g.bodyW, bodyH, c);
|
|
22732
23150
|
}
|
|
22733
|
-
if (cs.borderVisible ||
|
|
23151
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
22734
23152
|
b.alpha = this.candleStructureAlpha;
|
|
22735
|
-
const bord = cs.borderVisible
|
|
23153
|
+
const bord = cs.borderVisible ? parseColor((isUp ? cs.borderUpColor : cs.borderDownColor) ?? bodyColorStr) : c;
|
|
22736
23154
|
const bw = Math.max(0, g.bodyW - 1);
|
|
22737
23155
|
const bh = Math.max(0, bodyH - 1);
|
|
22738
23156
|
b.rectStroke(g.bodyX + 0.5, bodyTop + 0.5, bw, bh, 1, bord);
|
|
@@ -24284,8 +24702,14 @@ var Canvas2dBackend = class {
|
|
|
24284
24702
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
24285
24703
|
};
|
|
24286
24704
|
ctx.globalAlpha = this.modelAlpha;
|
|
24287
|
-
for (const m of models) for (const bg of m.backgrounds) this.drawBackground(ctx, bg, pane, coords);
|
|
24288
|
-
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));
|
|
24705
|
+
for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
|
|
24706
|
+
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));
|
|
24707
|
+
if (isPrice) {
|
|
24708
|
+
for (const m of scene.indicators.values()) {
|
|
24709
|
+
for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
|
|
24710
|
+
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
24711
|
+
}
|
|
24712
|
+
}
|
|
24289
24713
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
24290
24714
|
let si = 0;
|
|
24291
24715
|
const drawSlicesUpTo = (z) => {
|
|
@@ -24307,7 +24731,7 @@ var Canvas2dBackend = class {
|
|
|
24307
24731
|
ctx.globalAlpha = this.modelAlpha;
|
|
24308
24732
|
const off = scene.offsetOf(m.id);
|
|
24309
24733
|
const mp = effPane(m);
|
|
24310
|
-
for (const s of m.series) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
24734
|
+
for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
24311
24735
|
}
|
|
24312
24736
|
if (drawCandles && !candleDrawn) {
|
|
24313
24737
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -24315,6 +24739,13 @@ var Canvas2dBackend = class {
|
|
|
24315
24739
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
24316
24740
|
}
|
|
24317
24741
|
drawSlicesUpTo(Infinity);
|
|
24742
|
+
if (isPrice) {
|
|
24743
|
+
ctx.globalAlpha = this.modelAlpha;
|
|
24744
|
+
for (const m of scene.indicators.values()) {
|
|
24745
|
+
const off = scene.offsetOf(m.id);
|
|
24746
|
+
for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
|
|
24747
|
+
}
|
|
24748
|
+
}
|
|
24318
24749
|
ctx.globalAlpha = this.modelAlpha;
|
|
24319
24750
|
for (const m of models) {
|
|
24320
24751
|
const mp = effPane(m);
|
|
@@ -24586,9 +25017,9 @@ var Canvas2dBackend = class {
|
|
|
24586
25017
|
ctx.fillStyle = color;
|
|
24587
25018
|
ctx.fillRect(g.bodyX, top, g.bodyW, bodyH);
|
|
24588
25019
|
}
|
|
24589
|
-
if (cs.borderVisible ||
|
|
25020
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
24590
25021
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
24591
|
-
ctx.strokeStyle = cs.borderVisible
|
|
25022
|
+
ctx.strokeStyle = cs.borderVisible ? (up ? cs.borderUpColor : cs.borderDownColor) ?? color : color;
|
|
24592
25023
|
ctx.lineWidth = 1;
|
|
24593
25024
|
const bw = Math.max(0, g.bodyW - 1);
|
|
24594
25025
|
const bh = Math.max(0, bodyH - 1);
|
|
@@ -25020,6 +25451,8 @@ var DrawingSceneRenderer = class {
|
|
|
25020
25451
|
this.set = set;
|
|
25021
25452
|
/** measureText width cache, keyed by `${font} ${text}`, persists across frames. */
|
|
25022
25453
|
this.widthCache = /* @__PURE__ */ new Map();
|
|
25454
|
+
/** Tooltip hit-rects captured while drawing the CURRENT set (rebuilt per render). */
|
|
25455
|
+
this.tipRegions = [];
|
|
25023
25456
|
/**
|
|
25024
25457
|
* Index offset of the CURRENT set's model: its `xloc:'bar_index'` coordinates count
|
|
25025
25458
|
* from the model's anchor bar, so they shift by this to land on chart logical indices.
|
|
@@ -25040,8 +25473,13 @@ var DrawingSceneRenderer = class {
|
|
|
25040
25473
|
const s = this.set;
|
|
25041
25474
|
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
25042
25475
|
}
|
|
25476
|
+
/** Tooltip hit-rects of the labels drawn by the LAST `render` call (same coords as `ctx`). */
|
|
25477
|
+
labelTipRegions() {
|
|
25478
|
+
return this.tipRegions;
|
|
25479
|
+
}
|
|
25043
25480
|
/** Draw the whole set into `ctx` using the supplied coordinate closures. */
|
|
25044
25481
|
render(ctx, W, H, xOf, yOf) {
|
|
25482
|
+
this.tipRegions = [];
|
|
25045
25483
|
if (this.isEmpty()) return;
|
|
25046
25484
|
ctx.save();
|
|
25047
25485
|
this.drawLinefills(ctx, W, H, xOf, yOf);
|
|
@@ -25389,13 +25827,35 @@ var DrawingSceneRenderer = class {
|
|
|
25389
25827
|
if (this.isPointShape(lb.style)) {
|
|
25390
25828
|
if (!lb.noFill) this.drawLabelShape(ctx, lb.style, px, py, fontPx, color);
|
|
25391
25829
|
if (lb.text) this.drawLabelText(ctx, lb, px, py + fontPx, fontPx);
|
|
25392
|
-
|
|
25393
|
-
|
|
25830
|
+
if (lb.tooltip) {
|
|
25831
|
+
const r = Math.max(4, fontPx * 0.6) + 3;
|
|
25832
|
+
this.tipRegions.push({ left: px - r, top: py - r, right: px + r, bottom: py + r, text: lb.tooltip });
|
|
25833
|
+
}
|
|
25834
|
+
} else if (lb.style === "none" || lb.style === "text_outline") {
|
|
25835
|
+
if (lb.text) {
|
|
25836
|
+
this.drawLabelText(ctx, lb, px, py, fontPx, lb.style === "text_outline");
|
|
25837
|
+
if (lb.tooltip) this.tipRegions.push(this.textRegion(ctx, lb, px, py, fontPx, lb.tooltip));
|
|
25838
|
+
}
|
|
25394
25839
|
} else {
|
|
25395
|
-
this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
25840
|
+
const r = this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
25841
|
+
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 });
|
|
25396
25842
|
}
|
|
25397
25843
|
}
|
|
25398
25844
|
}
|
|
25845
|
+
/** Canvas font for a label's text — family + Pine `text_formatting` (bold/italic). */
|
|
25846
|
+
labelFont(lb, fontPx) {
|
|
25847
|
+
const family = lb.fontFamily === "monospace" ? "monospace" : this.deps.theme.fontFamily || "sans-serif";
|
|
25848
|
+
return `${lb.italic ? "italic " : ""}${lb.bold ? "bold " : ""}${fontPx}px ${family}`;
|
|
25849
|
+
}
|
|
25850
|
+
/** Hover rect of a text-only label (style none/text_outline/noFill), centered like drawLabelText. */
|
|
25851
|
+
textRegion(ctx, lb, cx, cy, fontPx, text) {
|
|
25852
|
+
const font = this.labelFont(lb, fontPx);
|
|
25853
|
+
const lines = (lb.text ?? "").split("\n");
|
|
25854
|
+
const w = Math.max(1, ...lines.map((l) => this.measure(ctx, font, l)));
|
|
25855
|
+
const h = fontPx * 1.25 * lines.length;
|
|
25856
|
+
const left = lb.textAlign === "left" ? cx : lb.textAlign === "right" ? cx - w : cx - w / 2;
|
|
25857
|
+
return { left, top: cy - h / 2, right: left + w, bottom: cy + h / 2, text };
|
|
25858
|
+
}
|
|
25399
25859
|
isPointShape(style) {
|
|
25400
25860
|
switch (style) {
|
|
25401
25861
|
case "circle":
|
|
@@ -25414,8 +25874,7 @@ var DrawingSceneRenderer = class {
|
|
|
25414
25874
|
}
|
|
25415
25875
|
}
|
|
25416
25876
|
drawLabelText(ctx, lb, cx, cy, fontPx, outline = false) {
|
|
25417
|
-
|
|
25418
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
25877
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
25419
25878
|
ctx.textAlign = lb.textAlign === "left" ? "left" : lb.textAlign === "right" ? "right" : "center";
|
|
25420
25879
|
ctx.textBaseline = "middle";
|
|
25421
25880
|
const lines = lb.text.split("\n");
|
|
@@ -25495,9 +25954,9 @@ var DrawingSceneRenderer = class {
|
|
|
25495
25954
|
ctx.fill();
|
|
25496
25955
|
}
|
|
25497
25956
|
}
|
|
25957
|
+
/** Draw a bubble label; returns the bubble body rect (for tooltip hit-testing). */
|
|
25498
25958
|
drawBubble(ctx, lb, px, py, fontPx, color) {
|
|
25499
|
-
|
|
25500
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
25959
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
25501
25960
|
const lines = (lb.text ?? "").split("\n");
|
|
25502
25961
|
const padX = 6;
|
|
25503
25962
|
const padY = 4;
|
|
@@ -25555,38 +26014,51 @@ var DrawingSceneRenderer = class {
|
|
|
25555
26014
|
by = py - h - ptr;
|
|
25556
26015
|
pointer = "down";
|
|
25557
26016
|
}
|
|
25558
|
-
|
|
25559
|
-
|
|
25560
|
-
|
|
25561
|
-
if (pointer !== "none") {
|
|
25562
|
-
ctx.beginPath();
|
|
25563
|
-
if (pointer === "down") {
|
|
25564
|
-
ctx.moveTo(px - ptr, by + h);
|
|
25565
|
-
ctx.lineTo(px + ptr, by + h);
|
|
25566
|
-
ctx.lineTo(px, py);
|
|
25567
|
-
} else if (pointer === "up") {
|
|
25568
|
-
ctx.moveTo(px - ptr, by);
|
|
25569
|
-
ctx.lineTo(px + ptr, by);
|
|
25570
|
-
ctx.lineTo(px, py);
|
|
25571
|
-
} else if (pointer === "left") {
|
|
25572
|
-
ctx.moveTo(bx, py - ptr);
|
|
25573
|
-
ctx.lineTo(bx, py + ptr);
|
|
25574
|
-
ctx.lineTo(px, py);
|
|
25575
|
-
} else {
|
|
25576
|
-
ctx.moveTo(bx + w, py - ptr);
|
|
25577
|
-
ctx.lineTo(bx + w, py + ptr);
|
|
25578
|
-
ctx.lineTo(px, py);
|
|
25579
|
-
}
|
|
25580
|
-
ctx.closePath();
|
|
26017
|
+
if (!lb.noFill) {
|
|
26018
|
+
ctx.fillStyle = color;
|
|
26019
|
+
this.roundRect(ctx, bx, by, w, h, 4);
|
|
25581
26020
|
ctx.fill();
|
|
26021
|
+
if (pointer !== "none") {
|
|
26022
|
+
ctx.beginPath();
|
|
26023
|
+
if (pointer === "down") {
|
|
26024
|
+
ctx.moveTo(px - ptr, by + h);
|
|
26025
|
+
ctx.lineTo(px + ptr, by + h);
|
|
26026
|
+
ctx.lineTo(px, py);
|
|
26027
|
+
} else if (pointer === "up") {
|
|
26028
|
+
ctx.moveTo(px - ptr, by);
|
|
26029
|
+
ctx.lineTo(px + ptr, by);
|
|
26030
|
+
ctx.lineTo(px, py);
|
|
26031
|
+
} else if (pointer === "left") {
|
|
26032
|
+
ctx.moveTo(bx, py - ptr);
|
|
26033
|
+
ctx.lineTo(bx, py + ptr);
|
|
26034
|
+
ctx.lineTo(px, py);
|
|
26035
|
+
} else {
|
|
26036
|
+
ctx.moveTo(bx + w, py - ptr);
|
|
26037
|
+
ctx.lineTo(bx + w, py + ptr);
|
|
26038
|
+
ctx.lineTo(px, py);
|
|
26039
|
+
}
|
|
26040
|
+
ctx.closePath();
|
|
26041
|
+
ctx.fill();
|
|
26042
|
+
}
|
|
25582
26043
|
}
|
|
25583
26044
|
if (lb.text) {
|
|
25584
|
-
ctx.fillStyle = lb.textColor ?? contrastColor(color);
|
|
25585
|
-
ctx.textAlign = "center";
|
|
26045
|
+
ctx.fillStyle = lb.textColor ?? (lb.noFill ? this.deps.theme.textColor : contrastColor(color));
|
|
25586
26046
|
ctx.textBaseline = "middle";
|
|
26047
|
+
let tx;
|
|
26048
|
+
if (lb.textAlign === "left") {
|
|
26049
|
+
ctx.textAlign = "left";
|
|
26050
|
+
tx = bx + padX;
|
|
26051
|
+
} else if (lb.textAlign === "right") {
|
|
26052
|
+
ctx.textAlign = "right";
|
|
26053
|
+
tx = bx + w - padX;
|
|
26054
|
+
} else {
|
|
26055
|
+
ctx.textAlign = "center";
|
|
26056
|
+
tx = bx + w / 2;
|
|
26057
|
+
}
|
|
25587
26058
|
const startY = by + padY + lineH / 2;
|
|
25588
|
-
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i],
|
|
26059
|
+
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i], tx, startY + i * lineH);
|
|
25589
26060
|
}
|
|
26061
|
+
return { x: bx, y: by, w, h };
|
|
25590
26062
|
}
|
|
25591
26063
|
roundRect(ctx, x, y, w, h, r) {
|
|
25592
26064
|
const rr = Math.min(r, w / 2, h / 2);
|
|
@@ -25824,6 +26296,8 @@ var ChromeRenderer = class {
|
|
|
25824
26296
|
this.axisTextColor = DARK_THEME.textColor;
|
|
25825
26297
|
// Shared Pine-drawing renderer (line/box/label/polyline/linefill); widthCache persists.
|
|
25826
26298
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
26299
|
+
// Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per render).
|
|
26300
|
+
this.labelTips = [];
|
|
25827
26301
|
}
|
|
25828
26302
|
mount(canvas) {
|
|
25829
26303
|
this.canvas = canvas;
|
|
@@ -25865,6 +26339,7 @@ var ChromeRenderer = class {
|
|
|
25865
26339
|
const dataW = coords.width;
|
|
25866
26340
|
const dataH = coords.height;
|
|
25867
26341
|
this.axisTextColor = surface?.textColor ?? theme.textColor;
|
|
26342
|
+
this.labelTips = [];
|
|
25868
26343
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
25869
26344
|
ctx.clearRect(0, 0, fullW, fullH);
|
|
25870
26345
|
if (surface && (fullW > dataW || fullH > dataH)) {
|
|
@@ -25974,6 +26449,17 @@ var ChromeRenderer = class {
|
|
|
25974
26449
|
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
25975
26450
|
);
|
|
25976
26451
|
ctx.restore();
|
|
26452
|
+
for (const r of this.drawScene.labelTipRegions()) {
|
|
26453
|
+
this.labelTips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
26454
|
+
}
|
|
26455
|
+
}
|
|
26456
|
+
/** Tooltip of the topmost label under a plot-space point, or null. Fed by the last render. */
|
|
26457
|
+
labelTooltipAt(x, y) {
|
|
26458
|
+
for (let i = this.labelTips.length - 1; i >= 0; i -= 1) {
|
|
26459
|
+
const r = this.labelTips[i];
|
|
26460
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
26461
|
+
}
|
|
26462
|
+
return null;
|
|
25977
26463
|
}
|
|
25978
26464
|
// ── axes ──
|
|
25979
26465
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
@@ -26190,6 +26676,68 @@ function formatCountdown(ms) {
|
|
|
26190
26676
|
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
|
|
26191
26677
|
}
|
|
26192
26678
|
|
|
26679
|
+
// src/renderers/native/chrome/LabelTooltip.ts
|
|
26680
|
+
var HOVER_DELAY_MS = 350;
|
|
26681
|
+
var LabelTooltip = class {
|
|
26682
|
+
constructor(plot, deps) {
|
|
26683
|
+
this.plot = plot;
|
|
26684
|
+
this.deps = deps;
|
|
26685
|
+
this.tip = null;
|
|
26686
|
+
this.timer = null;
|
|
26687
|
+
/** Text of the currently open OR armed tip — dedupes moves inside one label. */
|
|
26688
|
+
this.current = null;
|
|
26689
|
+
this.onMove = (e) => {
|
|
26690
|
+
if (e.pointerType !== "mouse") return;
|
|
26691
|
+
const rect = this.plot.getBoundingClientRect();
|
|
26692
|
+
const x = e.clientX - rect.left;
|
|
26693
|
+
const y = e.clientY - rect.top;
|
|
26694
|
+
const text = this.deps.lookup(x, y);
|
|
26695
|
+
if (!text) {
|
|
26696
|
+
this.clear();
|
|
26697
|
+
return;
|
|
26698
|
+
}
|
|
26699
|
+
if (text === this.current) return;
|
|
26700
|
+
this.clear();
|
|
26701
|
+
this.current = text;
|
|
26702
|
+
this.timer = window.setTimeout(() => this.show(text, x, y), HOVER_DELAY_MS);
|
|
26703
|
+
};
|
|
26704
|
+
this.onLeave = () => {
|
|
26705
|
+
this.clear();
|
|
26706
|
+
};
|
|
26707
|
+
plot.addEventListener("pointermove", this.onMove);
|
|
26708
|
+
plot.addEventListener("pointerleave", this.onLeave);
|
|
26709
|
+
plot.addEventListener("pointerdown", this.onLeave);
|
|
26710
|
+
}
|
|
26711
|
+
destroy() {
|
|
26712
|
+
this.plot.removeEventListener("pointermove", this.onMove);
|
|
26713
|
+
this.plot.removeEventListener("pointerleave", this.onLeave);
|
|
26714
|
+
this.plot.removeEventListener("pointerdown", this.onLeave);
|
|
26715
|
+
this.clear();
|
|
26716
|
+
}
|
|
26717
|
+
show(text, x, y) {
|
|
26718
|
+
const doc = this.plot.ownerDocument;
|
|
26719
|
+
const tip = doc.createElement("div");
|
|
26720
|
+
tip.textContent = text;
|
|
26721
|
+
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;";
|
|
26722
|
+
applyChromeTokens(tip, this.deps.theme());
|
|
26723
|
+
this.plot.appendChild(tip);
|
|
26724
|
+
const pw = this.plot.clientWidth;
|
|
26725
|
+
const ph = this.plot.clientHeight;
|
|
26726
|
+
tip.style.left = `${Math.max(0, Math.min(x + 12, pw - tip.offsetWidth - 4))}px`;
|
|
26727
|
+
tip.style.top = `${Math.max(0, Math.min(y + 16, ph - tip.offsetHeight - 4))}px`;
|
|
26728
|
+
this.tip = tip;
|
|
26729
|
+
}
|
|
26730
|
+
clear() {
|
|
26731
|
+
if (this.timer !== null) {
|
|
26732
|
+
clearTimeout(this.timer);
|
|
26733
|
+
this.timer = null;
|
|
26734
|
+
}
|
|
26735
|
+
this.tip?.remove();
|
|
26736
|
+
this.tip = null;
|
|
26737
|
+
this.current = null;
|
|
26738
|
+
}
|
|
26739
|
+
};
|
|
26740
|
+
|
|
26193
26741
|
// src/renderers/native/chrome/CrosshairRenderer.ts
|
|
26194
26742
|
var CrosshairRenderer = class {
|
|
26195
26743
|
constructor() {
|
|
@@ -31267,19 +31815,8 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
31267
31815
|
for (const model of models) {
|
|
31268
31816
|
const off = offsetOf(model.id);
|
|
31269
31817
|
for (const s of model.series) {
|
|
31270
|
-
if (s.
|
|
31271
|
-
|
|
31272
|
-
const b = s.bars[i - off];
|
|
31273
|
-
if (b) {
|
|
31274
|
-
consider(b.high);
|
|
31275
|
-
consider(b.low);
|
|
31276
|
-
}
|
|
31277
|
-
}
|
|
31278
|
-
} else if (isLineLikeSeries(s)) {
|
|
31279
|
-
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
31280
|
-
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
31281
|
-
else if (s.style?.base != null) consider(s.style.base);
|
|
31282
|
-
}
|
|
31818
|
+
if (s.overlay === true) continue;
|
|
31819
|
+
considerSeries(s, i0, i1, off, consider);
|
|
31283
31820
|
}
|
|
31284
31821
|
for (const pl of model.priceLines) consider(pl.price);
|
|
31285
31822
|
}
|
|
@@ -31301,6 +31838,36 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
31301
31838
|
const span = max - min;
|
|
31302
31839
|
return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
|
|
31303
31840
|
}
|
|
31841
|
+
function considerSeries(s, i0, i1, off, consider) {
|
|
31842
|
+
if (s.kind === "candle" || s.kind === "bar") {
|
|
31843
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
31844
|
+
const b = s.bars[i - off];
|
|
31845
|
+
if (b) {
|
|
31846
|
+
consider(b.high);
|
|
31847
|
+
consider(b.low);
|
|
31848
|
+
}
|
|
31849
|
+
}
|
|
31850
|
+
} else if (isLineLikeSeries(s)) {
|
|
31851
|
+
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
31852
|
+
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
31853
|
+
else if (s.style?.base != null) consider(s.style.base);
|
|
31854
|
+
}
|
|
31855
|
+
}
|
|
31856
|
+
function overlaySeriesRange(models, i0, i1, offsetOf = () => 0) {
|
|
31857
|
+
let min = Infinity;
|
|
31858
|
+
let max = -Infinity;
|
|
31859
|
+
const consider = (v) => {
|
|
31860
|
+
if (v != null && Number.isFinite(v)) {
|
|
31861
|
+
if (v < min) min = v;
|
|
31862
|
+
if (v > max) max = v;
|
|
31863
|
+
}
|
|
31864
|
+
};
|
|
31865
|
+
for (const model of models) {
|
|
31866
|
+
const off = offsetOf(model.id);
|
|
31867
|
+
for (const s of model.series) if (s.overlay === true) considerSeries(s, i0, i1, off, consider);
|
|
31868
|
+
}
|
|
31869
|
+
return min === Infinity ? null : { min, max };
|
|
31870
|
+
}
|
|
31304
31871
|
function expandScaleByPixels(scale, heightPx, abovePx, belowPx) {
|
|
31305
31872
|
if (abovePx <= 0 && belowPx <= 0) return scale;
|
|
31306
31873
|
const content = heightPx - abovePx - belowPx;
|
|
@@ -31949,6 +32516,8 @@ var NativeRenderer = class {
|
|
|
31949
32516
|
this.glowAmount = 0;
|
|
31950
32517
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
31951
32518
|
this.chrome = new ChromeRenderer();
|
|
32519
|
+
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
32520
|
+
this.labelTooltip = null;
|
|
31952
32521
|
this.crosshairLayer = new CrosshairRenderer();
|
|
31953
32522
|
/** 1 Hz repaint pump so the price-axis countdown-to-bar-close ticks; null when off. */
|
|
31954
32523
|
this.countdownTimer = null;
|
|
@@ -31959,6 +32528,8 @@ var NativeRenderer = class {
|
|
|
31959
32528
|
this.indicatorValuesOn = true;
|
|
31960
32529
|
/** Host-contributed legend actions — held here so a rebuild of the legend re-wires them. */
|
|
31961
32530
|
this.legendActionsProvider = null;
|
|
32531
|
+
/** Host-contributed legend callouts — held here so a rebuild of the legend re-wires them. */
|
|
32532
|
+
this.legendCalloutsProvider = null;
|
|
31962
32533
|
/** Host override of the legend's fold toggle — held here so a remount re-applies it. */
|
|
31963
32534
|
this.legendOverviewAction = null;
|
|
31964
32535
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -33067,6 +33638,10 @@ var NativeRenderer = class {
|
|
|
33067
33638
|
this.plot.appendChild(this.scrollButton);
|
|
33068
33639
|
this.plot.addEventListener("pointermove", this.onScrollProximityMove);
|
|
33069
33640
|
this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
|
|
33641
|
+
this.labelTooltip = new LabelTooltip(this.plot, {
|
|
33642
|
+
theme: () => this.chromeTheme(),
|
|
33643
|
+
lookup: (x, y) => this.chrome.labelTooltipAt(x, y)
|
|
33644
|
+
});
|
|
33070
33645
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
33071
33646
|
projector: () => this.drawingProjector(),
|
|
33072
33647
|
dpr: () => this.coords.dpr,
|
|
@@ -33106,9 +33681,10 @@ var NativeRenderer = class {
|
|
|
33106
33681
|
this.inputsUI.setDialogHost(this.dialogHost);
|
|
33107
33682
|
this.inputsUI.setSymbolPicker(this.symbolPicker);
|
|
33108
33683
|
this.inputsUI.setLegendActions(this.legendActionsProvider);
|
|
33684
|
+
this.inputsUI.setLegendCallouts(this.legendCalloutsProvider);
|
|
33109
33685
|
this.inputsUI.setLegendOverviewAction(this.legendOverviewAction);
|
|
33110
33686
|
this.inputsUI.setOnChange((c) => {
|
|
33111
|
-
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value });
|
|
33687
|
+
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value, ...c.kind ? { kind: c.kind } : {} });
|
|
33112
33688
|
});
|
|
33113
33689
|
this.inputsUI.setOnRemove((id) => {
|
|
33114
33690
|
for (const cb of this.removeIndicatorCbs) cb(id);
|
|
@@ -33298,6 +33874,8 @@ var NativeRenderer = class {
|
|
|
33298
33874
|
this.settingsButton = null;
|
|
33299
33875
|
this.plot?.removeEventListener("pointermove", this.onScrollProximityMove);
|
|
33300
33876
|
this.plot?.removeEventListener("pointerleave", this.onScrollProximityLeave);
|
|
33877
|
+
this.labelTooltip?.destroy();
|
|
33878
|
+
this.labelTooltip = null;
|
|
33301
33879
|
this.scrollButton?.remove();
|
|
33302
33880
|
this.scrollButton = null;
|
|
33303
33881
|
for (const l of this.extLayers) l.instance.destroy?.();
|
|
@@ -33538,7 +34116,7 @@ var NativeRenderer = class {
|
|
|
33538
34116
|
else this.scene.assignIndicatorZ(model.id);
|
|
33539
34117
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
33540
34118
|
native: !!model.native,
|
|
33541
|
-
...model.
|
|
34119
|
+
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
33542
34120
|
});
|
|
33543
34121
|
this.syncTables(model);
|
|
33544
34122
|
if (model.native?.type === "volume") {
|
|
@@ -33589,8 +34167,8 @@ var NativeRenderer = class {
|
|
|
33589
34167
|
this.paneControls?.refresh();
|
|
33590
34168
|
this.scheduler.invalidate(4 /* Full */);
|
|
33591
34169
|
}
|
|
33592
|
-
setIndicatorInputs(handle, values) {
|
|
33593
|
-
this.inputsUI.setValues(handle.id, values);
|
|
34170
|
+
setIndicatorInputs(handle, values, props) {
|
|
34171
|
+
this.inputsUI.setValues(handle.id, values, props);
|
|
33594
34172
|
}
|
|
33595
34173
|
setSymbolPicker(picker) {
|
|
33596
34174
|
this.symbolPicker = picker;
|
|
@@ -33600,6 +34178,10 @@ var NativeRenderer = class {
|
|
|
33600
34178
|
this.legendActionsProvider = provider;
|
|
33601
34179
|
this.inputsUI?.setLegendActions(provider);
|
|
33602
34180
|
}
|
|
34181
|
+
setLegendCallouts(provider) {
|
|
34182
|
+
this.legendCalloutsProvider = provider;
|
|
34183
|
+
this.inputsUI?.setLegendCallouts(provider);
|
|
34184
|
+
}
|
|
33603
34185
|
setLegendOverviewAction(action) {
|
|
33604
34186
|
this.legendOverviewAction = action;
|
|
33605
34187
|
this.inputsUI?.setLegendOverviewAction(action);
|
|
@@ -34580,7 +35162,11 @@ var NativeRenderer = class {
|
|
|
34580
35162
|
}
|
|
34581
35163
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
34582
35164
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
34583
|
-
|
|
35165
|
+
let dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
35166
|
+
if (pane === pricePane) {
|
|
35167
|
+
const or = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
|
|
35168
|
+
if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
|
|
35169
|
+
}
|
|
34584
35170
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
34585
35171
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
34586
35172
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -34854,6 +35440,7 @@ var NativeRenderer = class {
|
|
|
34854
35440
|
for (const m of models) {
|
|
34855
35441
|
const off = this.scene.offsetOf(m.id);
|
|
34856
35442
|
for (const s of m.series) {
|
|
35443
|
+
if (s.overlay === true) continue;
|
|
34857
35444
|
if (isLineLikeSeries(s)) {
|
|
34858
35445
|
const v = s.points[i0 - off]?.value;
|
|
34859
35446
|
if (v != null && Number.isFinite(v)) return v;
|
|
@@ -35726,45 +36313,24 @@ var CSS21 = `
|
|
|
35726
36313
|
}
|
|
35727
36314
|
.vela-statusline .vela-sl-symbol { font-weight: 600; font-size: var(--vela-font-size-lg); }
|
|
35728
36315
|
.vela-statusline .vela-sl-meta { color: var(--vela-fg-muted); font-size: var(--vela-font-size-md); font-weight: 600; }
|
|
35729
|
-
/* Market status badge \u2014 icon-only 16px circle, label on hover
|
|
35730
|
-
|
|
35731
|
-
|
|
35732
|
-
place-items: center;
|
|
35733
|
-
width: 16px;
|
|
35734
|
-
height: 16px;
|
|
35735
|
-
border-radius: 50%;
|
|
35736
|
-
flex: none;
|
|
35737
|
-
align-self: center;
|
|
35738
|
-
line-height: 0;
|
|
35739
|
-
cursor: default;
|
|
35740
|
-
}
|
|
35741
|
-
.vela-statusline .vela-sl-market svg {
|
|
35742
|
-
width: 12px;
|
|
35743
|
-
height: 12px;
|
|
35744
|
-
display: block;
|
|
35745
|
-
}
|
|
35746
|
-
/* Open wears the theme's up color; the other sessions are meaning constants from the
|
|
35747
|
-
* palette (amber pre, sky post, gray closed/holiday). Same ink at 20% for the circle. */
|
|
35748
|
-
.vela-statusline .vela-sl-market[data-status='open'] {
|
|
35749
|
-
background: color-mix(in srgb, var(--vela-up) 20%, transparent);
|
|
35750
|
-
color: var(--vela-up);
|
|
35751
|
-
}
|
|
35752
|
-
.vela-statusline .vela-sl-market[data-status='pre'] { background: color-mix(in srgb, ${SESSION_PRE} 20%, transparent); color: ${SESSION_PRE}; }
|
|
35753
|
-
.vela-statusline .vela-sl-market[data-status='post'] { background: color-mix(in srgb, ${SESSION_POST} 20%, transparent); color: ${SESSION_POST}; }
|
|
35754
|
-
.vela-statusline .vela-sl-market[data-status='closed'],
|
|
35755
|
-
.vela-statusline .vela-sl-market[data-status='holiday'] { background: color-mix(in srgb, ${SESSION_OFF} 20%, transparent); color: ${SESSION_OFF}; }
|
|
36316
|
+
/* Market status badge \u2014 a kit callout bubble (icon-only 16px circle, label on hover
|
|
36317
|
+
* via the kit tooltip); the session tint is applied per status in setMarketStatus. */
|
|
36318
|
+
.vela-statusline .vela-sl-market { align-self: center; }
|
|
35756
36319
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
35757
36320
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
35758
36321
|
/* The change value wears the SAME ink as the OHLC values (set inline per render) \u2014
|
|
35759
36322
|
* these are the pre-ink fallbacks only. */
|
|
35760
36323
|
.vela-statusline .vela-sl-change[data-dir='up'] { color: var(--vela-up); }
|
|
35761
36324
|
.vela-statusline .vela-sl-change[data-dir='down'] { color: var(--vela-down); }
|
|
35762
|
-
/* Stack the
|
|
35763
|
-
* The renderer
|
|
35764
|
-
*
|
|
35765
|
-
*
|
|
35766
|
-
*
|
|
35767
|
-
|
|
36325
|
+
/* Stack the TOP pane's legend below the status line (lower study panes stay put).
|
|
36326
|
+
* The renderer marks whichever legend sits at the plot's top edge \u2014 the price pane
|
|
36327
|
+
* normally, or a maximized study pane filling the plot \u2014 so the legend never merges
|
|
36328
|
+
* with the status line whichever pane owns the top. The renderer sets the legend's
|
|
36329
|
+
* inline top \u2014 shift with a transform, don't fight it. Scoped to hosts that actually
|
|
36330
|
+
* CARRY a status line (the marker class set by the Statusline constructor) \u2014 the
|
|
36331
|
+
* stylesheet is document-global, so a bare attribute selector here would shift every
|
|
36332
|
+
* chart on the page, including statusline-less ones. */
|
|
36333
|
+
.vela-has-statusline [${LEGEND_AT_TOP_ATTR}] { transform: translateY(26px); }
|
|
35768
36334
|
/* Mobile: two-line chip \u2014 logo / symbol / meta / market status on one aligned row, the
|
|
35769
36335
|
* bar change on the next. Full O/H/L/C stays hidden (too dense on a phone-width plot).
|
|
35770
36336
|
* GRID, not a wrapping flexbox: an absolutely positioned wrapping flex container sizes
|
|
@@ -35800,7 +36366,7 @@ var CSS21 = `
|
|
|
35800
36366
|
font-size: var(--vela-font-size-sm);
|
|
35801
36367
|
line-height: 1.2;
|
|
35802
36368
|
}
|
|
35803
|
-
[data-layout='mobile'] .vela-has-statusline [
|
|
36369
|
+
[data-layout='mobile'] .vela-has-statusline [${LEGEND_AT_TOP_ATTR}] { transform: translateY(40px); }
|
|
35804
36370
|
/* FIT mode (multi-chart cells \u2014 see setFitMode): the line never wraps; segments that
|
|
35805
36371
|
* don't fit are HIDDEN by fit() (change first, then meta, then the market badge), so
|
|
35806
36372
|
* overflow:hidden only guards the transient between a resize and the next measure.
|
|
@@ -35817,7 +36383,7 @@ var CSS21 = `
|
|
|
35817
36383
|
padding-left: 0;
|
|
35818
36384
|
}
|
|
35819
36385
|
/* One row again \u2014 the mobile two-line shift doesn't apply in fit mode. */
|
|
35820
|
-
[data-layout='mobile'] .vela-sl-fit-host.vela-has-statusline [
|
|
36386
|
+
[data-layout='mobile'] .vela-sl-fit-host.vela-has-statusline [${LEGEND_AT_TOP_ATTR}] { transform: translateY(26px); }
|
|
35821
36387
|
`;
|
|
35822
36388
|
function baseOfTicker(ticker) {
|
|
35823
36389
|
return ticker.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || ticker;
|
|
@@ -35829,6 +36395,13 @@ var MARKET_LABELS = {
|
|
|
35829
36395
|
closed: "Market Closed",
|
|
35830
36396
|
holiday: "Market Holiday"
|
|
35831
36397
|
};
|
|
36398
|
+
var MARKET_INKS = {
|
|
36399
|
+
open: "var(--vela-up)",
|
|
36400
|
+
pre: SESSION_PRE,
|
|
36401
|
+
post: SESSION_POST,
|
|
36402
|
+
closed: SESSION_OFF,
|
|
36403
|
+
holiday: SESSION_OFF
|
|
36404
|
+
};
|
|
35832
36405
|
function statuslineInkOf(renderer, priceStyle) {
|
|
35833
36406
|
const cfg = renderer.getConfig();
|
|
35834
36407
|
const c = (v) => typeof v === "string" ? v : null;
|
|
@@ -35887,8 +36460,15 @@ var Statusline = class {
|
|
|
35887
36460
|
this.symbolEl.textContent = ticker;
|
|
35888
36461
|
this.metaEl = doc.createElement("span");
|
|
35889
36462
|
this.metaEl.className = "vela-sl-meta";
|
|
35890
|
-
this.
|
|
35891
|
-
|
|
36463
|
+
this.marketBubble = new CalloutBubble({
|
|
36464
|
+
icon: "market-open",
|
|
36465
|
+
background: `color-mix(in srgb, ${MARKET_INKS.open} 20%, transparent)`,
|
|
36466
|
+
color: MARKET_INKS.open,
|
|
36467
|
+
label: MARKET_LABELS.open,
|
|
36468
|
+
host
|
|
36469
|
+
});
|
|
36470
|
+
this.marketEl = this.marketBubble.el;
|
|
36471
|
+
this.marketEl.classList.add("vela-sl-market");
|
|
35892
36472
|
this.ohlcEl = doc.createElement("span");
|
|
35893
36473
|
this.ohlcEl.className = "vela-sl-ohlc";
|
|
35894
36474
|
this.changeEl = doc.createElement("span");
|
|
@@ -35976,8 +36556,13 @@ var Statusline = class {
|
|
|
35976
36556
|
* hover label. Callers with no session model leave the constructor's 'open'. */
|
|
35977
36557
|
setMarketStatus(status) {
|
|
35978
36558
|
this.marketEl.dataset.status = status;
|
|
35979
|
-
|
|
35980
|
-
this.
|
|
36559
|
+
const ink = MARKET_INKS[status];
|
|
36560
|
+
this.marketBubble.set({
|
|
36561
|
+
icon: `market-${status}`,
|
|
36562
|
+
background: `color-mix(in srgb, ${ink} 20%, transparent)`,
|
|
36563
|
+
color: ink,
|
|
36564
|
+
label: MARKET_LABELS[status]
|
|
36565
|
+
});
|
|
35981
36566
|
this.marketTip.setContent(MARKET_LABELS[status]);
|
|
35982
36567
|
}
|
|
35983
36568
|
setPartVisible(part, visible) {
|
|
@@ -36010,6 +36595,7 @@ var Statusline = class {
|
|
|
36010
36595
|
this.fitRO?.disconnect();
|
|
36011
36596
|
this.fitRO = null;
|
|
36012
36597
|
this.marketTip.destroy();
|
|
36598
|
+
this.marketBubble.destroy();
|
|
36013
36599
|
this.host.classList.remove("vela-has-statusline", "vela-sl-fit-host");
|
|
36014
36600
|
this.el.remove();
|
|
36015
36601
|
}
|
|
@@ -36236,6 +36822,8 @@ var SessionShadingTracker = class {
|
|
|
36236
36822
|
this.spec = null;
|
|
36237
36823
|
this.ready = false;
|
|
36238
36824
|
this.session = "regular";
|
|
36825
|
+
/** Whether one bar is shorter than a civil day — only then do pre/post bars exist. */
|
|
36826
|
+
this.intraday = false;
|
|
36239
36827
|
this.covered = null;
|
|
36240
36828
|
/** The newest range seen — viewport moves during metadata resolution (a load's fit
|
|
36241
36829
|
* animation) must not be lost, so the resolution always expands the LATEST range. */
|
|
@@ -36248,6 +36836,8 @@ var SessionShadingTracker = class {
|
|
|
36248
36836
|
this.spec = null;
|
|
36249
36837
|
this.covered = null;
|
|
36250
36838
|
this.session = opts.session;
|
|
36839
|
+
const tfMs = timeframeMs(opts.timeframe);
|
|
36840
|
+
this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS2;
|
|
36251
36841
|
this.lastRange = opts.range;
|
|
36252
36842
|
void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
|
|
36253
36843
|
if (my !== this.epoch) return;
|
|
@@ -36275,7 +36865,7 @@ var SessionShadingTracker = class {
|
|
|
36275
36865
|
if (force) this.onZones(null);
|
|
36276
36866
|
return;
|
|
36277
36867
|
}
|
|
36278
|
-
if (this.session !== "extended") {
|
|
36868
|
+
if (this.session !== "extended" || !this.intraday) {
|
|
36279
36869
|
if (force) this.onZones({ pre: [], post: [] });
|
|
36280
36870
|
return;
|
|
36281
36871
|
}
|
|
@@ -36690,6 +37280,7 @@ var ChartCell = class {
|
|
|
36690
37280
|
this.inner.renderer.set("attribution", false);
|
|
36691
37281
|
this.inner.renderer.set("dialogHost", deps.dialogHost);
|
|
36692
37282
|
this.inner.renderer.setLegendActions(legendActionsProviderFor(this.inner, () => deps.context()));
|
|
37283
|
+
this.inner.renderer.setLegendCallouts(legendCalloutsProviderFor(this.inner, () => deps.context()));
|
|
36693
37284
|
if (this.inner.renderer.supports("historyChords")) this.inner.renderer.set("historyChords", false);
|
|
36694
37285
|
this.history.onChart(this.inner);
|
|
36695
37286
|
this.inner.renderer.onConfigChanged(() => {
|
|
@@ -36806,20 +37397,30 @@ var ChartCell = class {
|
|
|
36806
37397
|
this.refreshNativeCatalog();
|
|
36807
37398
|
this.pushSettingsSections();
|
|
36808
37399
|
this.offMarket = this.inner.on("market:changed", ({ symbol: symbol2, timeframe }) => {
|
|
36809
|
-
this.
|
|
36810
|
-
this.state.provider = parseSymbol(symbol2).provider ?? void 0;
|
|
36811
|
-
this.state.timeframe = timeframe;
|
|
36812
|
-
this.state.session = normalizeSession(this.inner?.market.session);
|
|
36813
|
-
this.watermark?.update(symbol2, timeframe);
|
|
36814
|
-
this.statusline?.setSymbol(symbol2);
|
|
36815
|
-
this.statusline?.setMeta(timeframe, this.inner?.data.displayPrefix(symbol2) ?? this.state.provider ?? "");
|
|
36816
|
-
if (this.inner) this.statusline?.onChart(this.inner);
|
|
37400
|
+
this.projectMarket(symbol2, timeframe);
|
|
36817
37401
|
this.refreshNativeCatalog();
|
|
36818
37402
|
this.refreshSessionAvailable();
|
|
36819
37403
|
if (this.inner) this.marketStatus?.track(this.inner.data, symbol2);
|
|
36820
37404
|
this.deps.onMarketChanged(this.id);
|
|
36821
37405
|
});
|
|
36822
37406
|
}
|
|
37407
|
+
/**
|
|
37408
|
+
* Project a market identity into the cell state and its display overlays (watermark,
|
|
37409
|
+
* statusline), WITHOUT the data-dependent bookkeeping. Runs twice per user pick: once
|
|
37410
|
+
* optimistically from the cell setters — the labels reflect the pick immediately, not
|
|
37411
|
+
* after the bars load — and again from `market:changed` (the committed pass, and the
|
|
37412
|
+
* only pass for host `chart.setMarket` calls). Idempotent, so the double run converges.
|
|
37413
|
+
*/
|
|
37414
|
+
projectMarket(symbol, timeframe) {
|
|
37415
|
+
this.state.symbol = symbol;
|
|
37416
|
+
this.state.provider = parseSymbol(symbol).provider ?? void 0;
|
|
37417
|
+
this.state.timeframe = timeframe;
|
|
37418
|
+
this.state.session = normalizeSession(this.inner?.market.session);
|
|
37419
|
+
this.watermark?.update(symbol, timeframe);
|
|
37420
|
+
this.statusline?.setSymbol(symbol);
|
|
37421
|
+
this.statusline?.setMeta(timeframe, this.inner?.data.displayPrefix(symbol) ?? this.state.provider ?? "");
|
|
37422
|
+
if (this.inner) this.statusline?.onChart(this.inner);
|
|
37423
|
+
}
|
|
36823
37424
|
/**
|
|
36824
37425
|
* Does this cell's market HAVE sessions (RTH/ETH meaningful)? Derived from the
|
|
36825
37426
|
* symbol's own metadata (`syminfo.session !== '24x7'`), asynchronously — the
|
|
@@ -36865,7 +37466,7 @@ var ChartCell = class {
|
|
|
36865
37466
|
const requestedSpan = Math.max(this.state.bars ?? 1e3, this.rangeBars) * timeframeMs(this.state.timeframe ?? "60");
|
|
36866
37467
|
const fallbackSpan = Number.isFinite(requestedSpan) ? Math.max(3 * 864e5, requestedSpan) : 3 * 864e5;
|
|
36867
37468
|
const range = chart.getVisibleRange() ?? { from: now - fallbackSpan, to: now };
|
|
36868
|
-
this.sessionShading.track(chart.data, symbol, { session: this.session, range });
|
|
37469
|
+
this.sessionShading.track(chart.data, symbol, { session: this.session, timeframe: this.timeframe, range });
|
|
36869
37470
|
}
|
|
36870
37471
|
/** The session-shade colors live in the renderer CONFIG (persisted with it, edited
|
|
36871
37472
|
* live by the dialog swatch) — the cell only proxies them into its settings rows. */
|
|
@@ -37054,17 +37655,23 @@ var ChartCell = class {
|
|
|
37054
37655
|
get indicatorCount() {
|
|
37055
37656
|
return this.instances.length + this.nativeCatalog.filter((n) => n.present).length;
|
|
37056
37657
|
}
|
|
37057
|
-
/** Switch this cell's market in place (the chart instance survives).
|
|
37658
|
+
/** Switch this cell's market in place (the chart instance survives). The projection
|
|
37659
|
+
* is OPTIMISTIC — labels and chrome show the pick before the bars load; it follows
|
|
37660
|
+
* the setMarket call so the statusline reads the already-blanked chart. */
|
|
37058
37661
|
setSymbol(symbol) {
|
|
37059
37662
|
if (!this.inner || symbol === this.symbol) return;
|
|
37060
37663
|
this.unresolvedToasted = null;
|
|
37061
37664
|
void this.inner.setMarket({ symbol });
|
|
37665
|
+
this.projectMarket(symbol, this.timeframe);
|
|
37666
|
+
this.deps.onMarketChanged(this.id);
|
|
37062
37667
|
}
|
|
37063
37668
|
setTimeframe(timeframe) {
|
|
37064
37669
|
if (!this.inner || timeframe === this.timeframe) return;
|
|
37065
37670
|
this.activeRangeId = null;
|
|
37066
37671
|
this.rangeBars = 0;
|
|
37067
37672
|
void this.inner.setMarket({ timeframe, bars: this.state.bars });
|
|
37673
|
+
this.projectMarket(this.symbol, timeframe);
|
|
37674
|
+
this.deps.onMarketChanged(this.id);
|
|
37068
37675
|
}
|
|
37069
37676
|
/** Applied live (renderer feature) — no reload. */
|
|
37070
37677
|
setPriceStyle(style) {
|
|
@@ -38176,7 +38783,10 @@ var VelaWorkspace = class {
|
|
|
38176
38783
|
this.topbar.renderActions();
|
|
38177
38784
|
this.mobileBar?.renderActions();
|
|
38178
38785
|
this.dock.refresh();
|
|
38179
|
-
for (const cell of this.cells())
|
|
38786
|
+
for (const cell of this.cells()) {
|
|
38787
|
+
cell.chart.renderer.setLegendActions(legendActionsProviderFor(cell.chart, () => this.context()));
|
|
38788
|
+
cell.chart.renderer.setLegendCallouts(legendCalloutsProviderFor(cell.chart, () => this.context()));
|
|
38789
|
+
}
|
|
38180
38790
|
}
|
|
38181
38791
|
// ── state surface (the SDK's read/restore of the whole grid's config + content) ──
|
|
38182
38792
|
/**
|