@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/widget.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 = `
|
|
@@ -4677,6 +4863,44 @@ function legendActionsProviderFor(chart, context) {
|
|
|
4677
4863
|
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) }));
|
|
4678
4864
|
};
|
|
4679
4865
|
}
|
|
4866
|
+
var calloutRegistry = /* @__PURE__ */ new Map();
|
|
4867
|
+
function legendCallouts() {
|
|
4868
|
+
return [...calloutRegistry.values()].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
4869
|
+
}
|
|
4870
|
+
function legendCalloutsProviderFor(chart, context) {
|
|
4871
|
+
return (indicatorId) => {
|
|
4872
|
+
const handle = chart.indicators().find((h) => h.id === indicatorId);
|
|
4873
|
+
if (!handle) return [];
|
|
4874
|
+
const info = { id: handle.id, title: handle.title, ...handle.source !== void 0 ? { source: handle.source } : {} };
|
|
4875
|
+
const views = [];
|
|
4876
|
+
for (const d of legendCallouts()) {
|
|
4877
|
+
const spec = d.callout(info);
|
|
4878
|
+
if (!spec) continue;
|
|
4879
|
+
views.push({
|
|
4880
|
+
id: d.id,
|
|
4881
|
+
icon: spec.icon,
|
|
4882
|
+
background: spec.background,
|
|
4883
|
+
...spec.color !== void 0 ? { color: spec.color } : {},
|
|
4884
|
+
tooltip: spec.tooltip,
|
|
4885
|
+
...spec.content !== void 0 ? {
|
|
4886
|
+
content: {
|
|
4887
|
+
...spec.content.title !== void 0 ? { title: spec.content.title } : {},
|
|
4888
|
+
items: spec.content.items.map(
|
|
4889
|
+
(item) => item.type === "text" ? item : {
|
|
4890
|
+
type: "button",
|
|
4891
|
+
label: item.label,
|
|
4892
|
+
...item.primary !== void 0 ? { primary: item.primary } : {},
|
|
4893
|
+
...item.close !== void 0 ? { close: item.close } : {},
|
|
4894
|
+
run: () => item.run(context(), info)
|
|
4895
|
+
}
|
|
4896
|
+
)
|
|
4897
|
+
}
|
|
4898
|
+
} : {}
|
|
4899
|
+
});
|
|
4900
|
+
}
|
|
4901
|
+
return views;
|
|
4902
|
+
};
|
|
4903
|
+
}
|
|
4680
4904
|
var stateHandlers = /* @__PURE__ */ new Map();
|
|
4681
4905
|
function registerStatePersistence(handler) {
|
|
4682
4906
|
stateHandlers.set(handler.key, handler);
|
|
@@ -17578,6 +17802,7 @@ var IndicatorHandleImpl = class {
|
|
|
17578
17802
|
constructor(id, title, controller, source) {
|
|
17579
17803
|
this.controller = controller;
|
|
17580
17804
|
this.schema = [];
|
|
17805
|
+
this.propsSchema = [];
|
|
17581
17806
|
this.visibleState = true;
|
|
17582
17807
|
this.bus = new TypedEventBus();
|
|
17583
17808
|
this.id = id;
|
|
@@ -17587,6 +17812,9 @@ var IndicatorHandleImpl = class {
|
|
|
17587
17812
|
get inputs() {
|
|
17588
17813
|
return this.schema;
|
|
17589
17814
|
}
|
|
17815
|
+
get props() {
|
|
17816
|
+
return this.propsSchema;
|
|
17817
|
+
}
|
|
17590
17818
|
get visible() {
|
|
17591
17819
|
return this.visibleState;
|
|
17592
17820
|
}
|
|
@@ -17596,6 +17824,12 @@ var IndicatorHandleImpl = class {
|
|
|
17596
17824
|
setInputs(values) {
|
|
17597
17825
|
this.controller.applyInputs(this.id, values);
|
|
17598
17826
|
}
|
|
17827
|
+
setProp(key, value) {
|
|
17828
|
+
this.controller.applyProps(this.id, { [key]: value });
|
|
17829
|
+
}
|
|
17830
|
+
setProps(values) {
|
|
17831
|
+
this.controller.applyProps(this.id, values);
|
|
17832
|
+
}
|
|
17599
17833
|
setVisible(visible) {
|
|
17600
17834
|
this.controller.setVisible(this.id, visible);
|
|
17601
17835
|
}
|
|
@@ -17615,6 +17849,9 @@ var IndicatorHandleImpl = class {
|
|
|
17615
17849
|
setSchema(schema) {
|
|
17616
17850
|
this.schema = schema;
|
|
17617
17851
|
}
|
|
17852
|
+
setPropsSchema(schema) {
|
|
17853
|
+
this.propsSchema = schema;
|
|
17854
|
+
}
|
|
17618
17855
|
/** Sync the public `visible` getter when the orchestrator changes visibility (API or legend eye). */
|
|
17619
17856
|
setVisibleState(visible) {
|
|
17620
17857
|
this.visibleState = visible;
|
|
@@ -17628,6 +17865,8 @@ var IndicatorHandleImpl = class {
|
|
|
17628
17865
|
function summarizeModel(model) {
|
|
17629
17866
|
const series = {};
|
|
17630
17867
|
for (const s of model.series) series[s.kind] = (series[s.kind] ?? 0) + 1;
|
|
17868
|
+
const countOverlay = (items) => items?.filter((i) => i.overlay === true).length ?? 0;
|
|
17869
|
+
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);
|
|
17631
17870
|
return {
|
|
17632
17871
|
id: model.id,
|
|
17633
17872
|
title: model.title,
|
|
@@ -17647,7 +17886,8 @@ function summarizeModel(model) {
|
|
|
17647
17886
|
tables: model.tables?.length ?? 0,
|
|
17648
17887
|
barColors: model.barColors?.length ?? 0,
|
|
17649
17888
|
trades: model.trades?.length ?? 0,
|
|
17650
|
-
inputs: model.inputs.length
|
|
17889
|
+
inputs: model.inputs.length,
|
|
17890
|
+
forcedOverlay
|
|
17651
17891
|
};
|
|
17652
17892
|
}
|
|
17653
17893
|
function inspectModels(models) {
|
|
@@ -17668,7 +17908,8 @@ function inspectModels(models) {
|
|
|
17668
17908
|
linefills: sum((s) => s.linefills),
|
|
17669
17909
|
tables: sum((s) => s.tables),
|
|
17670
17910
|
barColors: sum((s) => s.barColors),
|
|
17671
|
-
trades: sum((s) => s.trades)
|
|
17911
|
+
trades: sum((s) => s.trades),
|
|
17912
|
+
forcedOverlay: sum((s) => s.forcedOverlay)
|
|
17672
17913
|
}
|
|
17673
17914
|
};
|
|
17674
17915
|
}
|
|
@@ -17812,7 +18053,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
17812
18053
|
for (const engine of engines) this.registerEngine(engine.language, engine);
|
|
17813
18054
|
this.defaultLanguage = config.defaultLanguage;
|
|
17814
18055
|
this.renderer.mount(container, config.theme);
|
|
17815
|
-
this.renderer.onInputChange(
|
|
18056
|
+
this.renderer.onInputChange(
|
|
18057
|
+
(e) => e.kind === "prop" ? this.applyProps(e.indicatorId, { [e.key]: e.value }) : this.applyInputs(e.indicatorId, { [e.key]: e.value })
|
|
18058
|
+
);
|
|
17816
18059
|
this.renderer.onRemoveIndicator((id) => this.removeIndicator(id));
|
|
17817
18060
|
this.renderer.onToggleIndicatorVisible?.((id, visible) => this.setVisible(id, visible));
|
|
17818
18061
|
this.paneActionUnsub = this.renderer.onPaneAction?.((a) => this.handlePaneAction(a)) ?? null;
|
|
@@ -18492,7 +18735,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18492
18735
|
const title = options.title ?? "Indicator";
|
|
18493
18736
|
const handle = new IndicatorHandleImpl(id, title, this, source);
|
|
18494
18737
|
this.handles.set(id, handle);
|
|
18495
|
-
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} } });
|
|
18738
|
+
this.registry.add({ id, title, source, options, inputValues: { ...options.inputs ?? {} }, propValues: { ...options.props ?? {} } });
|
|
18496
18739
|
void this.startIndicator(id, source, options, handle);
|
|
18497
18740
|
return handle;
|
|
18498
18741
|
}
|
|
@@ -18515,7 +18758,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18515
18758
|
return handle;
|
|
18516
18759
|
}
|
|
18517
18760
|
const inputValues = { ...descriptor.defaultInputs(), ...options.inputs ?? {} };
|
|
18518
|
-
this.registry.add({ id, title, source: type, inputValues, native: { type, instance: descriptor.create(), descriptor } });
|
|
18761
|
+
this.registry.add({ id, title, source: type, inputValues, propValues: {}, native: { type, instance: descriptor.create(), descriptor } });
|
|
18519
18762
|
handle.setSchema(descriptor.inputsSchema());
|
|
18520
18763
|
void this.startNativeIndicator(id, handle);
|
|
18521
18764
|
return handle;
|
|
@@ -18568,6 +18811,19 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18568
18811
|
if (record.session) record.session.update(record.inputValues);
|
|
18569
18812
|
else if (record.native && !record.hidden) record.native.instance.setInputs(record.inputValues);
|
|
18570
18813
|
}
|
|
18814
|
+
/** IndicatorController: re-run an indicator with merged declaration-prop overrides.
|
|
18815
|
+
* Same lifecycle as {@link applyInputs} — a prop change replays the whole script.
|
|
18816
|
+
* Script indicators only: natives have no declaration props. */
|
|
18817
|
+
applyProps(id, values) {
|
|
18818
|
+
const record = this.registry.get(id);
|
|
18819
|
+
if (!record?.session) return;
|
|
18820
|
+
record.propValues = { ...record.propValues, ...values };
|
|
18821
|
+
if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
18822
|
+
record.pendingStructural = true;
|
|
18823
|
+
if (!record.hidden) this.setLoading(record, true);
|
|
18824
|
+
record.pendingCause = "inputs";
|
|
18825
|
+
record.session.update(record.inputValues, record.propValues);
|
|
18826
|
+
}
|
|
18571
18827
|
/** IndicatorController: tear down an indicator and (if now empty) its pane. */
|
|
18572
18828
|
/** Live handles of every indicator on the chart (script + native), insertion order. */
|
|
18573
18829
|
listIndicators() {
|
|
@@ -18716,6 +18972,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18716
18972
|
for (const input of prepared.inputs) defaults2[input.key] = input.defval;
|
|
18717
18973
|
record.inputValues = { ...defaults2, ...record.inputValues };
|
|
18718
18974
|
handle.setSchema(prepared.inputs);
|
|
18975
|
+
const propDefaults = {};
|
|
18976
|
+
for (const prop of prepared.props ?? []) propDefaults[prop.key] = prop.defval;
|
|
18977
|
+
record.propValues = { ...propDefaults, ...record.propValues };
|
|
18978
|
+
handle.setPropsSchema(prepared.props ?? []);
|
|
18719
18979
|
this.mountLoadingPlaceholder(id, record);
|
|
18720
18980
|
this.executeIndicator(id, handle);
|
|
18721
18981
|
} catch (err) {
|
|
@@ -18739,6 +18999,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18739
18999
|
getBars: () => this.bars,
|
|
18740
19000
|
fetchSeries: (sym, tf, range) => this.fetchSeries(sym, tf, range),
|
|
18741
19001
|
inputs: record.inputValues,
|
|
19002
|
+
props: record.propValues,
|
|
18742
19003
|
visibleRange: this.currentVisibleRange(),
|
|
18743
19004
|
mode,
|
|
18744
19005
|
// Mid-backfill session starts (add / re-show / price-style re-execute) let the
|
|
@@ -18832,6 +19093,9 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18832
19093
|
const meta = record.prepared.meta;
|
|
18833
19094
|
const model = {
|
|
18834
19095
|
id,
|
|
19096
|
+
// Deliberately the FULL title, never a shorttitle: while the script loads the
|
|
19097
|
+
// legend identifies it by its full name; the compact shorttitle arrives with
|
|
19098
|
+
// the first computed model and takes over from there.
|
|
18835
19099
|
title: record.options?.title ?? meta.title,
|
|
18836
19100
|
overlay: meta.overlay,
|
|
18837
19101
|
paneHint: meta.overlay ? "price" : "new",
|
|
@@ -18840,7 +19104,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
18840
19104
|
backgrounds: [],
|
|
18841
19105
|
priceLines: [],
|
|
18842
19106
|
inputs: record.prepared.inputs,
|
|
18843
|
-
inputValues: record.inputValues
|
|
19107
|
+
inputValues: record.inputValues,
|
|
19108
|
+
...record.prepared.props ? { props: record.prepared.props, propValues: record.propValues } : {}
|
|
18844
19109
|
};
|
|
18845
19110
|
const paneId = this.routePane(id, model, record.options ?? {});
|
|
18846
19111
|
this.placeModel(model, id, paneId);
|
|
@@ -19073,7 +19338,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19073
19338
|
record.model = model;
|
|
19074
19339
|
if (record.pendingStructural) {
|
|
19075
19340
|
record.renderHandle = this.renderer.mountIndicator(model);
|
|
19076
|
-
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues);
|
|
19341
|
+
this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
|
|
19077
19342
|
record.pendingStructural = false;
|
|
19078
19343
|
} else {
|
|
19079
19344
|
this.renderer.updateIndicator(record.renderHandle, modelToValuePatch(model));
|
|
@@ -19100,16 +19365,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
|
|
|
19100
19365
|
placeModel(model, id, paneId) {
|
|
19101
19366
|
model.id = id;
|
|
19102
19367
|
model.paneId = paneId;
|
|
19103
|
-
|
|
19104
|
-
for (const
|
|
19105
|
-
for (const
|
|
19368
|
+
const paneOf = (item) => item.overlay === true ? "price" : paneId;
|
|
19369
|
+
for (const series of model.series) series.paneId = paneOf(series);
|
|
19370
|
+
for (const fill of model.fills) fill.paneId = paneOf(fill);
|
|
19371
|
+
for (const bg of model.backgrounds) bg.paneId = paneOf(bg);
|
|
19106
19372
|
for (const line of model.priceLines) line.paneId = paneId;
|
|
19107
|
-
if (model.lines) for (const ln of model.lines) ln.paneId =
|
|
19108
|
-
if (model.boxes) for (const bx of model.boxes) bx.paneId =
|
|
19109
|
-
if (model.labels) for (const lb of model.labels) lb.paneId =
|
|
19110
|
-
if (model.polylines) for (const pl of model.polylines) pl.paneId =
|
|
19111
|
-
if (model.linefills) for (const lf of model.linefills) lf.paneId =
|
|
19112
|
-
if (model.tables) for (const tb of model.tables) tb.paneId =
|
|
19373
|
+
if (model.lines) for (const ln of model.lines) ln.paneId = paneOf(ln);
|
|
19374
|
+
if (model.boxes) for (const bx of model.boxes) bx.paneId = paneOf(bx);
|
|
19375
|
+
if (model.labels) for (const lb of model.labels) lb.paneId = paneOf(lb);
|
|
19376
|
+
if (model.polylines) for (const pl of model.polylines) pl.paneId = paneOf(pl);
|
|
19377
|
+
if (model.linefills) for (const lf of model.linefills) lf.paneId = paneOf(lf);
|
|
19378
|
+
if (model.tables) for (const tb of model.tables) tb.paneId = paneOf(tb);
|
|
19113
19379
|
}
|
|
19114
19380
|
emitContextChanged(id) {
|
|
19115
19381
|
if (!this.registry.get(id)?.session?.getContext) return;
|
|
@@ -19262,6 +19528,15 @@ var RendererControl = class {
|
|
|
19262
19528
|
setLegendActions(provider) {
|
|
19263
19529
|
this.renderer.setLegendActions?.(provider);
|
|
19264
19530
|
}
|
|
19531
|
+
/**
|
|
19532
|
+
* Wire the legend rows' HOST-CONTRIBUTED callout bubbles (the shells route the
|
|
19533
|
+
* plugin registry through this; see `registerLegendCallout`). Silent on a renderer
|
|
19534
|
+
* without the seam — contributed callouts simply never show there, same graceful
|
|
19535
|
+
* degradation as {@link setLegendActions}.
|
|
19536
|
+
*/
|
|
19537
|
+
setLegendCallouts(provider) {
|
|
19538
|
+
this.renderer.setLegendCallouts?.(provider);
|
|
19539
|
+
}
|
|
19265
19540
|
/**
|
|
19266
19541
|
* Replace the indicator legend's fold toggle with a host action (or restore it with
|
|
19267
19542
|
* `null`) — multi-chart shells point the chip at their indicator overview instead of
|
|
@@ -19771,6 +20046,10 @@ function inputVisible(when, values) {
|
|
|
19771
20046
|
}
|
|
19772
20047
|
|
|
19773
20048
|
// src/renderers/shared/IndicatorInputsDialog.ts
|
|
20049
|
+
var PROPS_TAB = "Properties";
|
|
20050
|
+
function bagOf(row, decl) {
|
|
20051
|
+
return decl.prop ? row.propValues : row.values;
|
|
20052
|
+
}
|
|
19774
20053
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
19775
20054
|
var IndicatorInputsDialog = class {
|
|
19776
20055
|
constructor(host) {
|
|
@@ -19781,6 +20060,7 @@ var IndicatorInputsDialog = class {
|
|
|
19781
20060
|
this.openId = null;
|
|
19782
20061
|
this.row = null;
|
|
19783
20062
|
this.snapshot = null;
|
|
20063
|
+
this.propSnapshot = null;
|
|
19784
20064
|
this.dialogTips = [];
|
|
19785
20065
|
/** Re-applies every `when` gate against current values; set while the dialog is open. */
|
|
19786
20066
|
this.refreshVisibility = null;
|
|
@@ -19803,10 +20083,11 @@ var IndicatorInputsDialog = class {
|
|
|
19803
20083
|
}
|
|
19804
20084
|
open(row) {
|
|
19805
20085
|
this.close();
|
|
19806
|
-
if (row.inputs.length === 0) return;
|
|
20086
|
+
if (row.inputs.length === 0 && row.props.length === 0) return;
|
|
19807
20087
|
this.row = row;
|
|
19808
20088
|
this.openId = row.id;
|
|
19809
20089
|
this.snapshot = { ...row.values };
|
|
20090
|
+
this.propSnapshot = { ...row.propValues };
|
|
19810
20091
|
ensureDialogStyles();
|
|
19811
20092
|
const t = this.host.theme();
|
|
19812
20093
|
const border = "var(--vela-border)";
|
|
@@ -19814,7 +20095,7 @@ var IndicatorInputsDialog = class {
|
|
|
19814
20095
|
const host = this.host.dialogHost() ?? this.host.container;
|
|
19815
20096
|
const ui = new Dialog({
|
|
19816
20097
|
host,
|
|
19817
|
-
title: row.
|
|
20098
|
+
title: row.title,
|
|
19818
20099
|
// Non-modal: live-edit dialog — a modal machine locks pointer events on the
|
|
19819
20100
|
// whole body, killing the chart and the body-portaled popovers.
|
|
19820
20101
|
modal: false,
|
|
@@ -19846,6 +20127,16 @@ var IndicatorInputsDialog = class {
|
|
|
19846
20127
|
text: () => "Close"
|
|
19847
20128
|
}));
|
|
19848
20129
|
const tabDefs = tabInputs(row.inputs);
|
|
20130
|
+
if (row.props.length > 0) {
|
|
20131
|
+
const propDecls = row.props.map((p) => {
|
|
20132
|
+
const d = { ...p, prop: true };
|
|
20133
|
+
delete d.tab;
|
|
20134
|
+
return d;
|
|
20135
|
+
});
|
|
20136
|
+
const existing = tabDefs.find((t2) => t2.name === PROPS_TAB);
|
|
20137
|
+
if (existing) existing.inputs.push(...propDecls);
|
|
20138
|
+
else tabDefs.push({ name: PROPS_TAB, inputs: propDecls });
|
|
20139
|
+
}
|
|
19849
20140
|
const tabs = document.createElement("div");
|
|
19850
20141
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
19851
20142
|
const tabEls = [];
|
|
@@ -19948,9 +20239,10 @@ var IndicatorInputsDialog = class {
|
|
|
19948
20239
|
this.openId = null;
|
|
19949
20240
|
this.row = null;
|
|
19950
20241
|
this.snapshot = null;
|
|
20242
|
+
this.propSnapshot = null;
|
|
19951
20243
|
ui?.destroy();
|
|
19952
20244
|
}
|
|
19953
|
-
/** Restore every input to its open-time value (re-running the indicator), then close. */
|
|
20245
|
+
/** Restore every input and prop to its open-time value (re-running the indicator), then close. */
|
|
19954
20246
|
revertAndClose() {
|
|
19955
20247
|
const row = this.row;
|
|
19956
20248
|
const snap = this.snapshot;
|
|
@@ -19964,6 +20256,17 @@ var IndicatorInputsDialog = class {
|
|
|
19964
20256
|
}
|
|
19965
20257
|
}
|
|
19966
20258
|
}
|
|
20259
|
+
const propSnap = this.propSnapshot;
|
|
20260
|
+
if (row && propSnap) {
|
|
20261
|
+
for (const p of row.props) {
|
|
20262
|
+
const snapped = propSnap[p.key];
|
|
20263
|
+
const before = snapped !== void 0 ? snapped : p.defval;
|
|
20264
|
+
if (row.propValues[p.key] !== before) {
|
|
20265
|
+
row.propValues[p.key] = before;
|
|
20266
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: before, kind: "prop" });
|
|
20267
|
+
}
|
|
20268
|
+
}
|
|
20269
|
+
}
|
|
19967
20270
|
this.close();
|
|
19968
20271
|
}
|
|
19969
20272
|
/** The footer's reset button — same chip as Cancel, pinned to the LEFT edge
|
|
@@ -19981,19 +20284,27 @@ var IndicatorInputsDialog = class {
|
|
|
19981
20284
|
const row = this.row;
|
|
19982
20285
|
if (!row) return;
|
|
19983
20286
|
const snap = this.snapshot;
|
|
20287
|
+
const propSnap = this.propSnapshot;
|
|
19984
20288
|
for (const inp of row.inputs) {
|
|
19985
20289
|
if (row.values[inp.key] !== inp.defval) {
|
|
19986
20290
|
row.values[inp.key] = inp.defval;
|
|
19987
20291
|
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value: inp.defval });
|
|
19988
20292
|
}
|
|
19989
20293
|
}
|
|
20294
|
+
for (const p of row.props) {
|
|
20295
|
+
if (row.propValues[p.key] !== p.defval) {
|
|
20296
|
+
row.propValues[p.key] = p.defval;
|
|
20297
|
+
this.host.onChange?.({ indicatorId: row.id, key: p.key, value: p.defval, kind: "prop" });
|
|
20298
|
+
}
|
|
20299
|
+
}
|
|
19990
20300
|
this.open(row);
|
|
19991
20301
|
if (snap) this.snapshot = snap;
|
|
20302
|
+
if (propSnap) this.propSnapshot = propSnap;
|
|
19992
20303
|
}
|
|
19993
20304
|
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
19994
|
-
commit(row,
|
|
19995
|
-
row.
|
|
19996
|
-
this.host.onChange?.({ indicatorId: row.id, key, value });
|
|
20305
|
+
commit(row, decl, value) {
|
|
20306
|
+
bagOf(row, decl)[decl.key] = value;
|
|
20307
|
+
this.host.onChange?.({ indicatorId: row.id, key: decl.key, value, ...decl.prop ? { kind: "prop" } : {} });
|
|
19997
20308
|
this.refreshVisibility?.();
|
|
19998
20309
|
}
|
|
19999
20310
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
@@ -20020,7 +20331,7 @@ var IndicatorInputsDialog = class {
|
|
|
20020
20331
|
const id = idOf(lead);
|
|
20021
20332
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
20022
20333
|
if (lead.type === "bool") {
|
|
20023
|
-
const current = Boolean(row
|
|
20334
|
+
const current = Boolean(bagOf(row, lead)[lead.key] ?? lead.defval);
|
|
20024
20335
|
return append(fieldRow({
|
|
20025
20336
|
label: nameOf(lead),
|
|
20026
20337
|
id,
|
|
@@ -20029,7 +20340,7 @@ var IndicatorInputsDialog = class {
|
|
|
20029
20340
|
toggle: {
|
|
20030
20341
|
id,
|
|
20031
20342
|
checked: current,
|
|
20032
|
-
onChange: (v) => this.commit(row, lead
|
|
20343
|
+
onChange: (v) => this.commit(row, lead, v)
|
|
20033
20344
|
}
|
|
20034
20345
|
}));
|
|
20035
20346
|
}
|
|
@@ -20054,8 +20365,8 @@ var IndicatorInputsDialog = class {
|
|
|
20054
20365
|
}
|
|
20055
20366
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
20056
20367
|
buildControl(row, inp, id) {
|
|
20057
|
-
const current = row
|
|
20058
|
-
const emit = (value) => this.commit(row, inp
|
|
20368
|
+
const current = bagOf(row, inp)[inp.key] ?? inp.defval;
|
|
20369
|
+
const emit = (value) => this.commit(row, inp, value);
|
|
20059
20370
|
if (inp.type === "bool") {
|
|
20060
20371
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
20061
20372
|
}
|
|
@@ -20066,7 +20377,7 @@ var IndicatorInputsDialog = class {
|
|
|
20066
20377
|
kind: "color",
|
|
20067
20378
|
id,
|
|
20068
20379
|
theme: this.host.theme(),
|
|
20069
|
-
get: () => String(row
|
|
20380
|
+
get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
|
|
20070
20381
|
onChange: (v) => emit(v)
|
|
20071
20382
|
}).el;
|
|
20072
20383
|
}
|
|
@@ -20629,6 +20940,7 @@ function timeParts(ts) {
|
|
|
20629
20940
|
}
|
|
20630
20941
|
|
|
20631
20942
|
// src/renderers/shared/InputsUI.ts
|
|
20943
|
+
var LEGEND_AT_TOP_ATTR = "data-vela-pane-at-top";
|
|
20632
20944
|
var STATUS_KEYFRAMES = "@keyframes vela-ind-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.3;transform:scale(.6)}}";
|
|
20633
20945
|
var LEGEND_ICON_PX2 = 16;
|
|
20634
20946
|
var LEGEND_CTL_PX = 18;
|
|
@@ -20667,10 +20979,14 @@ var InputsUI = class {
|
|
|
20667
20979
|
this.moveApi = null;
|
|
20668
20980
|
/** Host-contributed legend actions, resolved PER ROW at render time (see setLegendActions). */
|
|
20669
20981
|
this.legendActions = null;
|
|
20982
|
+
/** Host-contributed callout bubbles, resolved PER ROW at render time (see setLegendCallouts). */
|
|
20983
|
+
this.legendCallouts = null;
|
|
20670
20984
|
/** Chrome-tooltip disposers, per row id — a tip open at removal must not outlive its row. */
|
|
20671
20985
|
this.rowTips = /* @__PURE__ */ new Map();
|
|
20672
20986
|
/** Same, for the contributed extras only (rebuilt independently by setLegendActions). */
|
|
20673
20987
|
this.extrasTips = /* @__PURE__ */ new Map();
|
|
20988
|
+
/** Same, for the contributed callouts only (rebuilt independently by setLegendCallouts). */
|
|
20989
|
+
this.calloutTips = /* @__PURE__ */ new Map();
|
|
20674
20990
|
/** Open "Move to" menu (kept so it can be torn down). */
|
|
20675
20991
|
this.moveMenu = null;
|
|
20676
20992
|
this.moveTargets = [];
|
|
@@ -20828,6 +21144,42 @@ var InputsUI = class {
|
|
|
20828
21144
|
extrasEl.appendChild(btn2);
|
|
20829
21145
|
}
|
|
20830
21146
|
}
|
|
21147
|
+
/**
|
|
21148
|
+
* Wire the host-contributed callout bubbles. Same contract as
|
|
21149
|
+
* {@link setLegendActions}: re-calling replaces the provider and re-projects the
|
|
21150
|
+
* rows already on screen.
|
|
21151
|
+
*/
|
|
21152
|
+
setLegendCallouts(provider) {
|
|
21153
|
+
this.legendCallouts = provider;
|
|
21154
|
+
for (const row of this.rows.values()) this.renderCallouts(row);
|
|
21155
|
+
}
|
|
21156
|
+
/** (Re)build one row's callout bubbles — tinted icon circles beside the title whose
|
|
21157
|
+
* click (when the view carries content) deploys a panel of text and actions. */
|
|
21158
|
+
renderCallouts(row) {
|
|
21159
|
+
this.disposeTips(this.calloutTips, row.id);
|
|
21160
|
+
for (const bubble of row.callouts) bubble.destroy();
|
|
21161
|
+
row.callouts = [];
|
|
21162
|
+
row.calloutsEl.replaceChildren();
|
|
21163
|
+
const views = this.legendCallouts?.(row.id) ?? [];
|
|
21164
|
+
row.calloutsEl.style.display = views.length > 0 ? "inline-flex" : "none";
|
|
21165
|
+
for (const view of views) {
|
|
21166
|
+
const bubble = new CalloutBubble({
|
|
21167
|
+
icon: view.icon,
|
|
21168
|
+
background: view.background,
|
|
21169
|
+
...view.color !== void 0 ? { color: view.color } : {},
|
|
21170
|
+
label: view.tooltip,
|
|
21171
|
+
...view.content !== void 0 ? { panel: view.content } : {},
|
|
21172
|
+
// The panel portals into the plot host and self-tokens: it must work on
|
|
21173
|
+
// a bare chart, where no `.vela-ui` token ancestor exists.
|
|
21174
|
+
host: this.container,
|
|
21175
|
+
theme: () => this.theme
|
|
21176
|
+
});
|
|
21177
|
+
bubble.el.dataset.legendCallout = view.id;
|
|
21178
|
+
this.tip(this.calloutTips, row.id, bubble.el, view.tooltip);
|
|
21179
|
+
row.callouts.push(bubble);
|
|
21180
|
+
row.calloutsEl.appendChild(bubble.el);
|
|
21181
|
+
}
|
|
21182
|
+
}
|
|
20831
21183
|
/** Reposition the per-pane legend containers after a layout change. */
|
|
20832
21184
|
reposition() {
|
|
20833
21185
|
for (const [paneId, lg] of this.legends) this.positionLegend(lg, paneId);
|
|
@@ -20995,6 +21347,7 @@ var InputsUI = class {
|
|
|
20995
21347
|
}
|
|
20996
21348
|
positionLegend(lg, paneId) {
|
|
20997
21349
|
const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
|
|
21350
|
+
lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
|
|
20998
21351
|
lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
|
|
20999
21352
|
const collapsed = this.paneCollapse.has(paneId);
|
|
21000
21353
|
const masterId = this.paneCollapse.get(paneId) ?? null;
|
|
@@ -21070,13 +21423,13 @@ var InputsUI = class {
|
|
|
21070
21423
|
}
|
|
21071
21424
|
/** Create or update an indicator's legend row (in the legend for its pane). */
|
|
21072
21425
|
upsert(id, title, inputs, values, paneId = "price", opts = {}) {
|
|
21073
|
-
const settingsTitle = opts.settingsTitle ?? title;
|
|
21074
21426
|
const existing = this.rows.get(id);
|
|
21075
21427
|
if (existing) {
|
|
21076
21428
|
existing.title = title;
|
|
21077
|
-
existing.settingsTitle = settingsTitle;
|
|
21078
21429
|
existing.inputs = inputs;
|
|
21079
21430
|
existing.values = { ...values };
|
|
21431
|
+
existing.props = opts.props ?? [];
|
|
21432
|
+
existing.propValues = { ...opts.propValues ?? {} };
|
|
21080
21433
|
existing.titleEl.textContent = title;
|
|
21081
21434
|
if (existing.paneId !== paneId) {
|
|
21082
21435
|
existing.paneId = paneId;
|
|
@@ -21128,6 +21481,9 @@ var InputsUI = class {
|
|
|
21128
21481
|
titleWrap.appendChild(beta);
|
|
21129
21482
|
}
|
|
21130
21483
|
el.appendChild(titleWrap);
|
|
21484
|
+
const calloutsEl = document.createElement("span");
|
|
21485
|
+
calloutsEl.style.cssText = `display:none;align-items:center;gap:4px;flex:none;margin-left:${LEGEND_TITLE_STATUS_GAP_PX}px;`;
|
|
21486
|
+
el.appendChild(calloutsEl);
|
|
21131
21487
|
el.appendChild(statusEl);
|
|
21132
21488
|
const valuesEl = document.createElement("span");
|
|
21133
21489
|
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;`;
|
|
@@ -21144,13 +21500,13 @@ var InputsUI = class {
|
|
|
21144
21500
|
eye.className = "vela-ind-ctl";
|
|
21145
21501
|
eye.style.cssText = LEGEND_CTL_CSS;
|
|
21146
21502
|
eye.addEventListener("click", () => {
|
|
21147
|
-
const
|
|
21148
|
-
this.onToggleVisible?.(id, Boolean(
|
|
21503
|
+
const row2 = this.rows.get(id);
|
|
21504
|
+
this.onToggleVisible?.(id, Boolean(row2?.hidden));
|
|
21149
21505
|
});
|
|
21150
21506
|
controlsEl.appendChild(eye);
|
|
21151
21507
|
eyeEl = eye;
|
|
21152
21508
|
}
|
|
21153
|
-
if (inputs.length > 0) {
|
|
21509
|
+
if (inputs.length > 0 || (opts.props ?? []).length > 0) {
|
|
21154
21510
|
const gear = document.createElement("button");
|
|
21155
21511
|
gear.type = "button";
|
|
21156
21512
|
gear.setAttribute("aria-label", "Settings");
|
|
@@ -21190,7 +21546,9 @@ var InputsUI = class {
|
|
|
21190
21546
|
controlsEl.appendChild(close);
|
|
21191
21547
|
el.appendChild(controlsEl);
|
|
21192
21548
|
this.attach(this.legendFor(paneId), el, !!opts.native);
|
|
21193
|
-
|
|
21549
|
+
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 };
|
|
21550
|
+
this.rows.set(id, row);
|
|
21551
|
+
this.renderCallouts(row);
|
|
21194
21552
|
this.syncFoldToggle();
|
|
21195
21553
|
}
|
|
21196
21554
|
/** Place a row in its pane's legend — native rows PREPEND (pinned to the top), Pine rows append. */
|
|
@@ -21199,9 +21557,11 @@ var InputsUI = class {
|
|
|
21199
21557
|
else container.appendChild(el);
|
|
21200
21558
|
}
|
|
21201
21559
|
/** Reflect programmatic input changes (so a re-opened dialog shows current values). */
|
|
21202
|
-
setValues(id, values) {
|
|
21560
|
+
setValues(id, values, props) {
|
|
21203
21561
|
const row = this.rows.get(id);
|
|
21204
|
-
if (row)
|
|
21562
|
+
if (!row) return;
|
|
21563
|
+
row.values = { ...row.values, ...values };
|
|
21564
|
+
if (props) row.propValues = { ...row.propValues, ...props };
|
|
21205
21565
|
}
|
|
21206
21566
|
/**
|
|
21207
21567
|
* Reflect an indicator's live status in its legend row: `'loading'` shows three pulsing
|
|
@@ -21283,8 +21643,13 @@ var InputsUI = class {
|
|
|
21283
21643
|
syncRowActions(row) {
|
|
21284
21644
|
const open2 = row.highlighted;
|
|
21285
21645
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
21286
|
-
if (open2)
|
|
21287
|
-
|
|
21646
|
+
if (open2) {
|
|
21647
|
+
row.el.appendChild(row.statusEl);
|
|
21648
|
+
row.el.appendChild(row.calloutsEl);
|
|
21649
|
+
} else {
|
|
21650
|
+
row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
21651
|
+
row.el.insertBefore(row.calloutsEl, row.statusEl);
|
|
21652
|
+
}
|
|
21288
21653
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
21289
21654
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
21290
21655
|
if (child === row.extrasEl) {
|
|
@@ -21302,10 +21667,12 @@ var InputsUI = class {
|
|
|
21302
21667
|
}
|
|
21303
21668
|
remove(id) {
|
|
21304
21669
|
const row = this.rows.get(id);
|
|
21670
|
+
for (const bubble of row?.callouts ?? []) bubble.destroy();
|
|
21305
21671
|
row?.el.remove();
|
|
21306
21672
|
this.rows.delete(id);
|
|
21307
21673
|
this.disposeTips(this.rowTips, id);
|
|
21308
21674
|
this.disposeTips(this.extrasTips, id);
|
|
21675
|
+
this.disposeTips(this.calloutTips, id);
|
|
21309
21676
|
if (this.selectedId === id) this.selectedId = null;
|
|
21310
21677
|
if (this.inputsDialog.openId === id) this.inputsDialog.close();
|
|
21311
21678
|
this.syncFoldToggle();
|
|
@@ -21325,6 +21692,8 @@ var InputsUI = class {
|
|
|
21325
21692
|
this.rowMenu = null;
|
|
21326
21693
|
for (const id of [...this.rowTips.keys()]) this.disposeTips(this.rowTips, id);
|
|
21327
21694
|
for (const id of [...this.extrasTips.keys()]) this.disposeTips(this.extrasTips, id);
|
|
21695
|
+
for (const id of [...this.calloutTips.keys()]) this.disposeTips(this.calloutTips, id);
|
|
21696
|
+
for (const row of this.rows.values()) for (const bubble of row.callouts) bubble.destroy();
|
|
21328
21697
|
for (const lg of this.legends.values()) lg.remove();
|
|
21329
21698
|
this.legends.clear();
|
|
21330
21699
|
this.rows.clear();
|
|
@@ -21679,6 +22048,32 @@ var SIZE_PX3 = {
|
|
|
21679
22048
|
large: 16,
|
|
21680
22049
|
huge: 20
|
|
21681
22050
|
};
|
|
22051
|
+
function fontPxOf(size) {
|
|
22052
|
+
if (typeof size === "number") return size > 0 ? size : SIZE_PX3.auto;
|
|
22053
|
+
return SIZE_PX3[size] ?? SIZE_PX3.auto;
|
|
22054
|
+
}
|
|
22055
|
+
function tableHasContent(t) {
|
|
22056
|
+
return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
|
|
22057
|
+
}
|
|
22058
|
+
function mergeRenderPlan(t) {
|
|
22059
|
+
const span = /* @__PURE__ */ new Map();
|
|
22060
|
+
const omit = /* @__PURE__ */ new Set();
|
|
22061
|
+
for (const m of t.merges) {
|
|
22062
|
+
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
22063
|
+
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
22064
|
+
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
22065
|
+
if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
|
|
22066
|
+
}
|
|
22067
|
+
}
|
|
22068
|
+
}
|
|
22069
|
+
for (let r = 0; r < t.rows; r += 1) {
|
|
22070
|
+
for (let c = 0; c < t.columns; c += 1) {
|
|
22071
|
+
if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
|
|
22072
|
+
}
|
|
22073
|
+
}
|
|
22074
|
+
for (const key of span.keys()) omit.delete(key);
|
|
22075
|
+
return { span, omit };
|
|
22076
|
+
}
|
|
21682
22077
|
var TableOverlay = class {
|
|
21683
22078
|
constructor(container, theme, paneBounds) {
|
|
21684
22079
|
this.container = container;
|
|
@@ -21699,7 +22094,9 @@ var TableOverlay = class {
|
|
|
21699
22094
|
update(tables) {
|
|
21700
22095
|
this.lastTables = tables;
|
|
21701
22096
|
this.root.replaceChildren();
|
|
21702
|
-
for (const t of tables)
|
|
22097
|
+
for (const t of tables) {
|
|
22098
|
+
if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
|
|
22099
|
+
}
|
|
21703
22100
|
}
|
|
21704
22101
|
/** Re-render at the current pane geometry — after layout settles or on resize. */
|
|
21705
22102
|
reposition() {
|
|
@@ -21714,39 +22111,36 @@ var TableOverlay = class {
|
|
|
21714
22111
|
this.root.remove();
|
|
21715
22112
|
}
|
|
21716
22113
|
renderTable(t) {
|
|
22114
|
+
const b = this.paneBounds(t.paneId);
|
|
21717
22115
|
const wrap = document.createElement("div");
|
|
21718
22116
|
wrap.style.position = "absolute";
|
|
21719
|
-
|
|
22117
|
+
if (t.frameColor && t.frameWidth > 0) wrap.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
|
|
22118
|
+
this.anchor(wrap, t.position, b);
|
|
21720
22119
|
const table = document.createElement("table");
|
|
21721
22120
|
Object.assign(table.style, {
|
|
21722
22121
|
borderCollapse: "collapse",
|
|
21723
22122
|
background: t.bgColor ?? "transparent",
|
|
21724
22123
|
fontFamily: this.theme.fontFamily || "sans-serif",
|
|
21725
|
-
// Frame as an inset shadow (not a `border`) so it stays independent of the
|
|
21726
|
-
// collapsed cell borders even when frame_width != border_width.
|
|
21727
|
-
boxShadow: t.frameColor && t.frameWidth > 0 ? `inset 0 0 0 ${t.frameWidth}px ${t.frameColor}` : "none",
|
|
21728
22124
|
border: "none",
|
|
21729
22125
|
tableLayout: "auto",
|
|
21730
22126
|
// Re-enable pointer events on the table only (root is none) so cell tooltips work.
|
|
21731
22127
|
pointerEvents: "auto"
|
|
21732
22128
|
});
|
|
21733
|
-
const span
|
|
21734
|
-
const
|
|
21735
|
-
|
|
21736
|
-
span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
|
|
21737
|
-
for (let r = m.startRow; r <= m.endRow; r += 1) {
|
|
21738
|
-
for (let c = m.startCol; c <= m.endCol; c += 1) {
|
|
21739
|
-
if (r !== m.startRow || c !== m.startCol) skip.add(`${r}:${c}`);
|
|
21740
|
-
}
|
|
21741
|
-
}
|
|
21742
|
-
}
|
|
22129
|
+
const { span, omit } = mergeRenderPlan(t);
|
|
22130
|
+
const plotW = Math.max(0, (this.root.clientWidth || this.container.clientWidth) - b.rightAxis);
|
|
22131
|
+
const paneH = b.height;
|
|
21743
22132
|
const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
|
|
21744
22133
|
for (let r = 0; r < t.rows; r += 1) {
|
|
21745
22134
|
const tr = document.createElement("tr");
|
|
21746
22135
|
for (let c = 0; c < t.columns; c += 1) {
|
|
22136
|
+
if (omit.has(`${r}:${c}`)) continue;
|
|
21747
22137
|
const cell = t.cells[r]?.[c] ?? null;
|
|
21748
|
-
if (skip.has(`${r}:${c}`) || cell?.merged) continue;
|
|
21749
22138
|
const td = document.createElement("td");
|
|
22139
|
+
if (cell === null) {
|
|
22140
|
+
Object.assign(td.style, { padding: "0", border: "none" });
|
|
22141
|
+
tr.appendChild(td);
|
|
22142
|
+
continue;
|
|
22143
|
+
}
|
|
21750
22144
|
const sp = span.get(`${r}:${c}`);
|
|
21751
22145
|
if (sp) {
|
|
21752
22146
|
if (sp.cs > 1) td.colSpan = sp.cs;
|
|
@@ -21755,18 +22149,22 @@ var TableOverlay = class {
|
|
|
21755
22149
|
Object.assign(td.style, {
|
|
21756
22150
|
border: cellBorder,
|
|
21757
22151
|
padding: "2px 6px",
|
|
21758
|
-
background: cell
|
|
21759
|
-
color: cell
|
|
21760
|
-
textAlign: cell
|
|
21761
|
-
verticalAlign: cell
|
|
21762
|
-
fontSize: `${
|
|
21763
|
-
fontFamily: cell
|
|
21764
|
-
fontWeight: cell
|
|
21765
|
-
fontStyle: cell
|
|
21766
|
-
|
|
22152
|
+
background: cell.bgColor ?? "transparent",
|
|
22153
|
+
color: cell.textColor ?? this.theme.textColor,
|
|
22154
|
+
textAlign: cell.hAlign,
|
|
22155
|
+
verticalAlign: cell.vAlign === "top" ? "top" : cell.vAlign === "bottom" ? "bottom" : "middle",
|
|
22156
|
+
fontSize: `${fontPxOf(cell.textSize)}px`,
|
|
22157
|
+
fontFamily: cell.fontFamily === "monospace" ? "monospace" : "inherit",
|
|
22158
|
+
fontWeight: cell.bold ? "bold" : "normal",
|
|
22159
|
+
fontStyle: cell.italic ? "italic" : "normal",
|
|
22160
|
+
// Pine cell text never wraps; `\n` still breaks lines. Wrapping
|
|
22161
|
+
// used to collapse unicode sparklines and ━━━ dividers.
|
|
22162
|
+
whiteSpace: "pre"
|
|
21767
22163
|
});
|
|
21768
|
-
if (cell
|
|
21769
|
-
td.
|
|
22164
|
+
if (cell.width) td.style.width = `${cell.width / 100 * plotW}px`;
|
|
22165
|
+
if (cell.height) td.style.height = `${cell.height / 100 * paneH}px`;
|
|
22166
|
+
if (cell.tooltip) td.title = cell.tooltip;
|
|
22167
|
+
td.textContent = cell.text ?? "";
|
|
21770
22168
|
tr.appendChild(td);
|
|
21771
22169
|
}
|
|
21772
22170
|
table.appendChild(tr);
|
|
@@ -22368,8 +22766,14 @@ var WebGL2Backend = class {
|
|
|
22368
22766
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
22369
22767
|
};
|
|
22370
22768
|
b.alpha = this.modelAlpha;
|
|
22371
|
-
for (const m of models) for (const bgSpan of m.backgrounds) this.emitBackground(b, bgSpan, pane, coords);
|
|
22372
|
-
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));
|
|
22769
|
+
for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
|
|
22770
|
+
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));
|
|
22771
|
+
if (isPrice) {
|
|
22772
|
+
for (const m of scene.indicators.values()) {
|
|
22773
|
+
for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
|
|
22774
|
+
for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
22775
|
+
}
|
|
22776
|
+
}
|
|
22373
22777
|
const drawCandles = isPrice && !scene.candlesHidden;
|
|
22374
22778
|
let candleDrawn = false;
|
|
22375
22779
|
for (const m of models) {
|
|
@@ -22383,7 +22787,7 @@ var WebGL2Backend = class {
|
|
|
22383
22787
|
b.alpha = this.modelAlpha;
|
|
22384
22788
|
const off = scene.offsetOf(m.id);
|
|
22385
22789
|
const mp = effPane(m);
|
|
22386
|
-
for (const s of m.series) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
22790
|
+
for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
|
|
22387
22791
|
}
|
|
22388
22792
|
if (drawCandles && !candleDrawn) {
|
|
22389
22793
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -22392,6 +22796,12 @@ var WebGL2Backend = class {
|
|
|
22392
22796
|
}
|
|
22393
22797
|
drawSlicesUpTo(Infinity);
|
|
22394
22798
|
b.alpha = this.modelAlpha;
|
|
22799
|
+
if (isPrice) {
|
|
22800
|
+
for (const m of scene.indicators.values()) {
|
|
22801
|
+
const off = scene.offsetOf(m.id);
|
|
22802
|
+
for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
|
|
22803
|
+
}
|
|
22804
|
+
}
|
|
22395
22805
|
for (const m of models) {
|
|
22396
22806
|
const mp = effPane(m);
|
|
22397
22807
|
for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
|
|
@@ -22438,7 +22848,7 @@ var WebGL2Backend = class {
|
|
|
22438
22848
|
for (const pane of scene.orderedPanes()) {
|
|
22439
22849
|
const b = this.glowBatch;
|
|
22440
22850
|
b.reset();
|
|
22441
|
-
this.emitGlowSources(b, scene
|
|
22851
|
+
this.emitGlowSources(b, scene, pane, coords, i0, i1);
|
|
22442
22852
|
if (b.vertexCount === 0) continue;
|
|
22443
22853
|
const topH = Math.round(pane.bounds.top * dpr * 0.5);
|
|
22444
22854
|
const botH = Math.round((pane.bounds.top + pane.bounds.height) * dpr * 0.5);
|
|
@@ -22507,15 +22917,23 @@ var WebGL2Backend = class {
|
|
|
22507
22917
|
gl.bindVertexArray(null);
|
|
22508
22918
|
return true;
|
|
22509
22919
|
}
|
|
22510
|
-
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
22511
|
-
|
|
22512
|
-
|
|
22513
|
-
|
|
22514
|
-
|
|
22515
|
-
|
|
22516
|
-
|
|
22517
|
-
|
|
22518
|
-
|
|
22920
|
+
/** The "neon" elements that glow: line/area/step lines + point markers (not candles/fills/bars).
|
|
22921
|
+
* Routing mirrors the main pass: own series per pane, force_overlay series on the price pane. */
|
|
22922
|
+
emitGlowSources(b, scene, pane, coords, i0, i1) {
|
|
22923
|
+
const emitOne = (s, off) => {
|
|
22924
|
+
if (!isLineLikeSeries(s) || s.visible === false) return;
|
|
22925
|
+
if (s.kind === "histogram" || s.kind === "columns") return;
|
|
22926
|
+
if (s.kind === "circles" || s.kind === "cross") this.emitPointMarkers(b, s, pane, coords, i0, i1, off);
|
|
22927
|
+
else this.emitPolyline(b, s, pane, coords, i0, i1, s.kind === "step", off);
|
|
22928
|
+
};
|
|
22929
|
+
for (const m of scene.indicatorsForPane(pane.id)) {
|
|
22930
|
+
const off = scene.offsetOf(m.id);
|
|
22931
|
+
for (const s of m.series) if (s.overlay !== true) emitOne(s, off);
|
|
22932
|
+
}
|
|
22933
|
+
if (pane.kind === "price") {
|
|
22934
|
+
for (const m of scene.indicators.values()) {
|
|
22935
|
+
const off = scene.offsetOf(m.id);
|
|
22936
|
+
for (const s of m.series) if (s.overlay === true) emitOne(s, off);
|
|
22519
22937
|
}
|
|
22520
22938
|
}
|
|
22521
22939
|
}
|
|
@@ -22798,9 +23216,9 @@ var WebGL2Backend = class {
|
|
|
22798
23216
|
b.alpha = this.candleBodyAlpha;
|
|
22799
23217
|
b.rect(g.bodyX, bodyTop, g.bodyW, bodyH, c);
|
|
22800
23218
|
}
|
|
22801
|
-
if (cs.borderVisible ||
|
|
23219
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
22802
23220
|
b.alpha = this.candleStructureAlpha;
|
|
22803
|
-
const bord = cs.borderVisible
|
|
23221
|
+
const bord = cs.borderVisible ? parseColor((isUp ? cs.borderUpColor : cs.borderDownColor) ?? bodyColorStr) : c;
|
|
22804
23222
|
const bw = Math.max(0, g.bodyW - 1);
|
|
22805
23223
|
const bh = Math.max(0, bodyH - 1);
|
|
22806
23224
|
b.rectStroke(g.bodyX + 0.5, bodyTop + 0.5, bw, bh, 1, bord);
|
|
@@ -24352,8 +24770,14 @@ var Canvas2dBackend = class {
|
|
|
24352
24770
|
return sc === pane.scale ? pane : { ...pane, scale: sc };
|
|
24353
24771
|
};
|
|
24354
24772
|
ctx.globalAlpha = this.modelAlpha;
|
|
24355
|
-
for (const m of models) for (const bg of m.backgrounds) this.drawBackground(ctx, bg, pane, coords);
|
|
24356
|
-
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));
|
|
24773
|
+
for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
|
|
24774
|
+
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));
|
|
24775
|
+
if (isPrice) {
|
|
24776
|
+
for (const m of scene.indicators.values()) {
|
|
24777
|
+
for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
|
|
24778
|
+
for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
|
|
24779
|
+
}
|
|
24780
|
+
}
|
|
24357
24781
|
const slices = scene.drawingSlices.get(pane.id) ?? [];
|
|
24358
24782
|
let si = 0;
|
|
24359
24783
|
const drawSlicesUpTo = (z) => {
|
|
@@ -24375,7 +24799,7 @@ var Canvas2dBackend = class {
|
|
|
24375
24799
|
ctx.globalAlpha = this.modelAlpha;
|
|
24376
24800
|
const off = scene.offsetOf(m.id);
|
|
24377
24801
|
const mp = effPane(m);
|
|
24378
|
-
for (const s of m.series) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
24802
|
+
for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
|
|
24379
24803
|
}
|
|
24380
24804
|
if (drawCandles && !candleDrawn) {
|
|
24381
24805
|
drawSlicesUpTo(scene.candleZ);
|
|
@@ -24383,6 +24807,13 @@ var Canvas2dBackend = class {
|
|
|
24383
24807
|
this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
|
|
24384
24808
|
}
|
|
24385
24809
|
drawSlicesUpTo(Infinity);
|
|
24810
|
+
if (isPrice) {
|
|
24811
|
+
ctx.globalAlpha = this.modelAlpha;
|
|
24812
|
+
for (const m of scene.indicators.values()) {
|
|
24813
|
+
const off = scene.offsetOf(m.id);
|
|
24814
|
+
for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
|
|
24815
|
+
}
|
|
24816
|
+
}
|
|
24386
24817
|
ctx.globalAlpha = this.modelAlpha;
|
|
24387
24818
|
for (const m of models) {
|
|
24388
24819
|
const mp = effPane(m);
|
|
@@ -24654,9 +25085,9 @@ var Canvas2dBackend = class {
|
|
|
24654
25085
|
ctx.fillStyle = color;
|
|
24655
25086
|
ctx.fillRect(g.bodyX, top, g.bodyW, bodyH);
|
|
24656
25087
|
}
|
|
24657
|
-
if (cs.borderVisible ||
|
|
25088
|
+
if (cs.borderVisible || fading && cs.bodyVisible) {
|
|
24658
25089
|
ctx.globalAlpha = this.candleStructureAlpha;
|
|
24659
|
-
ctx.strokeStyle = cs.borderVisible
|
|
25090
|
+
ctx.strokeStyle = cs.borderVisible ? (up ? cs.borderUpColor : cs.borderDownColor) ?? color : color;
|
|
24660
25091
|
ctx.lineWidth = 1;
|
|
24661
25092
|
const bw = Math.max(0, g.bodyW - 1);
|
|
24662
25093
|
const bh = Math.max(0, bodyH - 1);
|
|
@@ -25088,6 +25519,8 @@ var DrawingSceneRenderer = class {
|
|
|
25088
25519
|
this.set = set;
|
|
25089
25520
|
/** measureText width cache, keyed by `${font} ${text}`, persists across frames. */
|
|
25090
25521
|
this.widthCache = /* @__PURE__ */ new Map();
|
|
25522
|
+
/** Tooltip hit-rects captured while drawing the CURRENT set (rebuilt per render). */
|
|
25523
|
+
this.tipRegions = [];
|
|
25091
25524
|
/**
|
|
25092
25525
|
* Index offset of the CURRENT set's model: its `xloc:'bar_index'` coordinates count
|
|
25093
25526
|
* from the model's anchor bar, so they shift by this to land on chart logical indices.
|
|
@@ -25108,8 +25541,13 @@ var DrawingSceneRenderer = class {
|
|
|
25108
25541
|
const s = this.set;
|
|
25109
25542
|
return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
|
|
25110
25543
|
}
|
|
25544
|
+
/** Tooltip hit-rects of the labels drawn by the LAST `render` call (same coords as `ctx`). */
|
|
25545
|
+
labelTipRegions() {
|
|
25546
|
+
return this.tipRegions;
|
|
25547
|
+
}
|
|
25111
25548
|
/** Draw the whole set into `ctx` using the supplied coordinate closures. */
|
|
25112
25549
|
render(ctx, W, H, xOf, yOf) {
|
|
25550
|
+
this.tipRegions = [];
|
|
25113
25551
|
if (this.isEmpty()) return;
|
|
25114
25552
|
ctx.save();
|
|
25115
25553
|
this.drawLinefills(ctx, W, H, xOf, yOf);
|
|
@@ -25457,13 +25895,35 @@ var DrawingSceneRenderer = class {
|
|
|
25457
25895
|
if (this.isPointShape(lb.style)) {
|
|
25458
25896
|
if (!lb.noFill) this.drawLabelShape(ctx, lb.style, px, py, fontPx, color);
|
|
25459
25897
|
if (lb.text) this.drawLabelText(ctx, lb, px, py + fontPx, fontPx);
|
|
25460
|
-
|
|
25461
|
-
|
|
25898
|
+
if (lb.tooltip) {
|
|
25899
|
+
const r = Math.max(4, fontPx * 0.6) + 3;
|
|
25900
|
+
this.tipRegions.push({ left: px - r, top: py - r, right: px + r, bottom: py + r, text: lb.tooltip });
|
|
25901
|
+
}
|
|
25902
|
+
} else if (lb.style === "none" || lb.style === "text_outline") {
|
|
25903
|
+
if (lb.text) {
|
|
25904
|
+
this.drawLabelText(ctx, lb, px, py, fontPx, lb.style === "text_outline");
|
|
25905
|
+
if (lb.tooltip) this.tipRegions.push(this.textRegion(ctx, lb, px, py, fontPx, lb.tooltip));
|
|
25906
|
+
}
|
|
25462
25907
|
} else {
|
|
25463
|
-
this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
25908
|
+
const r = this.drawBubble(ctx, lb, px, py, fontPx, color);
|
|
25909
|
+
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 });
|
|
25464
25910
|
}
|
|
25465
25911
|
}
|
|
25466
25912
|
}
|
|
25913
|
+
/** Canvas font for a label's text — family + Pine `text_formatting` (bold/italic). */
|
|
25914
|
+
labelFont(lb, fontPx) {
|
|
25915
|
+
const family = lb.fontFamily === "monospace" ? "monospace" : this.deps.theme.fontFamily || "sans-serif";
|
|
25916
|
+
return `${lb.italic ? "italic " : ""}${lb.bold ? "bold " : ""}${fontPx}px ${family}`;
|
|
25917
|
+
}
|
|
25918
|
+
/** Hover rect of a text-only label (style none/text_outline/noFill), centered like drawLabelText. */
|
|
25919
|
+
textRegion(ctx, lb, cx, cy, fontPx, text) {
|
|
25920
|
+
const font = this.labelFont(lb, fontPx);
|
|
25921
|
+
const lines = (lb.text ?? "").split("\n");
|
|
25922
|
+
const w = Math.max(1, ...lines.map((l) => this.measure(ctx, font, l)));
|
|
25923
|
+
const h = fontPx * 1.25 * lines.length;
|
|
25924
|
+
const left = lb.textAlign === "left" ? cx : lb.textAlign === "right" ? cx - w : cx - w / 2;
|
|
25925
|
+
return { left, top: cy - h / 2, right: left + w, bottom: cy + h / 2, text };
|
|
25926
|
+
}
|
|
25467
25927
|
isPointShape(style) {
|
|
25468
25928
|
switch (style) {
|
|
25469
25929
|
case "circle":
|
|
@@ -25482,8 +25942,7 @@ var DrawingSceneRenderer = class {
|
|
|
25482
25942
|
}
|
|
25483
25943
|
}
|
|
25484
25944
|
drawLabelText(ctx, lb, cx, cy, fontPx, outline = false) {
|
|
25485
|
-
|
|
25486
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
25945
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
25487
25946
|
ctx.textAlign = lb.textAlign === "left" ? "left" : lb.textAlign === "right" ? "right" : "center";
|
|
25488
25947
|
ctx.textBaseline = "middle";
|
|
25489
25948
|
const lines = lb.text.split("\n");
|
|
@@ -25563,9 +26022,9 @@ var DrawingSceneRenderer = class {
|
|
|
25563
26022
|
ctx.fill();
|
|
25564
26023
|
}
|
|
25565
26024
|
}
|
|
26025
|
+
/** Draw a bubble label; returns the bubble body rect (for tooltip hit-testing). */
|
|
25566
26026
|
drawBubble(ctx, lb, px, py, fontPx, color) {
|
|
25567
|
-
|
|
25568
|
-
ctx.font = `${fontPx}px ${family}`;
|
|
26027
|
+
ctx.font = this.labelFont(lb, fontPx);
|
|
25569
26028
|
const lines = (lb.text ?? "").split("\n");
|
|
25570
26029
|
const padX = 6;
|
|
25571
26030
|
const padY = 4;
|
|
@@ -25623,38 +26082,51 @@ var DrawingSceneRenderer = class {
|
|
|
25623
26082
|
by = py - h - ptr;
|
|
25624
26083
|
pointer = "down";
|
|
25625
26084
|
}
|
|
25626
|
-
|
|
25627
|
-
|
|
25628
|
-
|
|
25629
|
-
if (pointer !== "none") {
|
|
25630
|
-
ctx.beginPath();
|
|
25631
|
-
if (pointer === "down") {
|
|
25632
|
-
ctx.moveTo(px - ptr, by + h);
|
|
25633
|
-
ctx.lineTo(px + ptr, by + h);
|
|
25634
|
-
ctx.lineTo(px, py);
|
|
25635
|
-
} else if (pointer === "up") {
|
|
25636
|
-
ctx.moveTo(px - ptr, by);
|
|
25637
|
-
ctx.lineTo(px + ptr, by);
|
|
25638
|
-
ctx.lineTo(px, py);
|
|
25639
|
-
} else if (pointer === "left") {
|
|
25640
|
-
ctx.moveTo(bx, py - ptr);
|
|
25641
|
-
ctx.lineTo(bx, py + ptr);
|
|
25642
|
-
ctx.lineTo(px, py);
|
|
25643
|
-
} else {
|
|
25644
|
-
ctx.moveTo(bx + w, py - ptr);
|
|
25645
|
-
ctx.lineTo(bx + w, py + ptr);
|
|
25646
|
-
ctx.lineTo(px, py);
|
|
25647
|
-
}
|
|
25648
|
-
ctx.closePath();
|
|
26085
|
+
if (!lb.noFill) {
|
|
26086
|
+
ctx.fillStyle = color;
|
|
26087
|
+
this.roundRect(ctx, bx, by, w, h, 4);
|
|
25649
26088
|
ctx.fill();
|
|
26089
|
+
if (pointer !== "none") {
|
|
26090
|
+
ctx.beginPath();
|
|
26091
|
+
if (pointer === "down") {
|
|
26092
|
+
ctx.moveTo(px - ptr, by + h);
|
|
26093
|
+
ctx.lineTo(px + ptr, by + h);
|
|
26094
|
+
ctx.lineTo(px, py);
|
|
26095
|
+
} else if (pointer === "up") {
|
|
26096
|
+
ctx.moveTo(px - ptr, by);
|
|
26097
|
+
ctx.lineTo(px + ptr, by);
|
|
26098
|
+
ctx.lineTo(px, py);
|
|
26099
|
+
} else if (pointer === "left") {
|
|
26100
|
+
ctx.moveTo(bx, py - ptr);
|
|
26101
|
+
ctx.lineTo(bx, py + ptr);
|
|
26102
|
+
ctx.lineTo(px, py);
|
|
26103
|
+
} else {
|
|
26104
|
+
ctx.moveTo(bx + w, py - ptr);
|
|
26105
|
+
ctx.lineTo(bx + w, py + ptr);
|
|
26106
|
+
ctx.lineTo(px, py);
|
|
26107
|
+
}
|
|
26108
|
+
ctx.closePath();
|
|
26109
|
+
ctx.fill();
|
|
26110
|
+
}
|
|
25650
26111
|
}
|
|
25651
26112
|
if (lb.text) {
|
|
25652
|
-
ctx.fillStyle = lb.textColor ?? contrastColor(color);
|
|
25653
|
-
ctx.textAlign = "center";
|
|
26113
|
+
ctx.fillStyle = lb.textColor ?? (lb.noFill ? this.deps.theme.textColor : contrastColor(color));
|
|
25654
26114
|
ctx.textBaseline = "middle";
|
|
26115
|
+
let tx;
|
|
26116
|
+
if (lb.textAlign === "left") {
|
|
26117
|
+
ctx.textAlign = "left";
|
|
26118
|
+
tx = bx + padX;
|
|
26119
|
+
} else if (lb.textAlign === "right") {
|
|
26120
|
+
ctx.textAlign = "right";
|
|
26121
|
+
tx = bx + w - padX;
|
|
26122
|
+
} else {
|
|
26123
|
+
ctx.textAlign = "center";
|
|
26124
|
+
tx = bx + w / 2;
|
|
26125
|
+
}
|
|
25655
26126
|
const startY = by + padY + lineH / 2;
|
|
25656
|
-
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i],
|
|
26127
|
+
for (let i = 0; i < lines.length; i += 1) ctx.fillText(lines[i], tx, startY + i * lineH);
|
|
25657
26128
|
}
|
|
26129
|
+
return { x: bx, y: by, w, h };
|
|
25658
26130
|
}
|
|
25659
26131
|
roundRect(ctx, x, y, w, h, r) {
|
|
25660
26132
|
const rr = Math.min(r, w / 2, h / 2);
|
|
@@ -25892,6 +26364,8 @@ var ChromeRenderer = class {
|
|
|
25892
26364
|
this.axisTextColor = DARK_THEME.textColor;
|
|
25893
26365
|
// Shared Pine-drawing renderer (line/box/label/polyline/linefill); widthCache persists.
|
|
25894
26366
|
this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
|
|
26367
|
+
// Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per render).
|
|
26368
|
+
this.labelTips = [];
|
|
25895
26369
|
}
|
|
25896
26370
|
mount(canvas) {
|
|
25897
26371
|
this.canvas = canvas;
|
|
@@ -25933,6 +26407,7 @@ var ChromeRenderer = class {
|
|
|
25933
26407
|
const dataW = coords.width;
|
|
25934
26408
|
const dataH = coords.height;
|
|
25935
26409
|
this.axisTextColor = surface?.textColor ?? theme.textColor;
|
|
26410
|
+
this.labelTips = [];
|
|
25936
26411
|
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
|
|
25937
26412
|
ctx.clearRect(0, 0, fullW, fullH);
|
|
25938
26413
|
if (surface && (fullW > dataW || fullH > dataH)) {
|
|
@@ -26042,6 +26517,17 @@ var ChromeRenderer = class {
|
|
|
26042
26517
|
(price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
|
|
26043
26518
|
);
|
|
26044
26519
|
ctx.restore();
|
|
26520
|
+
for (const r of this.drawScene.labelTipRegions()) {
|
|
26521
|
+
this.labelTips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
|
|
26522
|
+
}
|
|
26523
|
+
}
|
|
26524
|
+
/** Tooltip of the topmost label under a plot-space point, or null. Fed by the last render. */
|
|
26525
|
+
labelTooltipAt(x, y) {
|
|
26526
|
+
for (let i = this.labelTips.length - 1; i >= 0; i -= 1) {
|
|
26527
|
+
const r = this.labelTips[i];
|
|
26528
|
+
if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
|
|
26529
|
+
}
|
|
26530
|
+
return null;
|
|
26045
26531
|
}
|
|
26046
26532
|
// ── axes ──
|
|
26047
26533
|
drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
|
|
@@ -26258,6 +26744,68 @@ function formatCountdown(ms) {
|
|
|
26258
26744
|
return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${pad(m)}:${pad(s)}`;
|
|
26259
26745
|
}
|
|
26260
26746
|
|
|
26747
|
+
// src/renderers/native/chrome/LabelTooltip.ts
|
|
26748
|
+
var HOVER_DELAY_MS = 350;
|
|
26749
|
+
var LabelTooltip = class {
|
|
26750
|
+
constructor(plot, deps) {
|
|
26751
|
+
this.plot = plot;
|
|
26752
|
+
this.deps = deps;
|
|
26753
|
+
this.tip = null;
|
|
26754
|
+
this.timer = null;
|
|
26755
|
+
/** Text of the currently open OR armed tip — dedupes moves inside one label. */
|
|
26756
|
+
this.current = null;
|
|
26757
|
+
this.onMove = (e) => {
|
|
26758
|
+
if (e.pointerType !== "mouse") return;
|
|
26759
|
+
const rect = this.plot.getBoundingClientRect();
|
|
26760
|
+
const x = e.clientX - rect.left;
|
|
26761
|
+
const y = e.clientY - rect.top;
|
|
26762
|
+
const text = this.deps.lookup(x, y);
|
|
26763
|
+
if (!text) {
|
|
26764
|
+
this.clear();
|
|
26765
|
+
return;
|
|
26766
|
+
}
|
|
26767
|
+
if (text === this.current) return;
|
|
26768
|
+
this.clear();
|
|
26769
|
+
this.current = text;
|
|
26770
|
+
this.timer = window.setTimeout(() => this.show(text, x, y), HOVER_DELAY_MS);
|
|
26771
|
+
};
|
|
26772
|
+
this.onLeave = () => {
|
|
26773
|
+
this.clear();
|
|
26774
|
+
};
|
|
26775
|
+
plot.addEventListener("pointermove", this.onMove);
|
|
26776
|
+
plot.addEventListener("pointerleave", this.onLeave);
|
|
26777
|
+
plot.addEventListener("pointerdown", this.onLeave);
|
|
26778
|
+
}
|
|
26779
|
+
destroy() {
|
|
26780
|
+
this.plot.removeEventListener("pointermove", this.onMove);
|
|
26781
|
+
this.plot.removeEventListener("pointerleave", this.onLeave);
|
|
26782
|
+
this.plot.removeEventListener("pointerdown", this.onLeave);
|
|
26783
|
+
this.clear();
|
|
26784
|
+
}
|
|
26785
|
+
show(text, x, y) {
|
|
26786
|
+
const doc = this.plot.ownerDocument;
|
|
26787
|
+
const tip = doc.createElement("div");
|
|
26788
|
+
tip.textContent = text;
|
|
26789
|
+
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;";
|
|
26790
|
+
applyChromeTokens(tip, this.deps.theme());
|
|
26791
|
+
this.plot.appendChild(tip);
|
|
26792
|
+
const pw = this.plot.clientWidth;
|
|
26793
|
+
const ph = this.plot.clientHeight;
|
|
26794
|
+
tip.style.left = `${Math.max(0, Math.min(x + 12, pw - tip.offsetWidth - 4))}px`;
|
|
26795
|
+
tip.style.top = `${Math.max(0, Math.min(y + 16, ph - tip.offsetHeight - 4))}px`;
|
|
26796
|
+
this.tip = tip;
|
|
26797
|
+
}
|
|
26798
|
+
clear() {
|
|
26799
|
+
if (this.timer !== null) {
|
|
26800
|
+
clearTimeout(this.timer);
|
|
26801
|
+
this.timer = null;
|
|
26802
|
+
}
|
|
26803
|
+
this.tip?.remove();
|
|
26804
|
+
this.tip = null;
|
|
26805
|
+
this.current = null;
|
|
26806
|
+
}
|
|
26807
|
+
};
|
|
26808
|
+
|
|
26261
26809
|
// src/renderers/native/chrome/CrosshairRenderer.ts
|
|
26262
26810
|
var CrosshairRenderer = class {
|
|
26263
26811
|
constructor() {
|
|
@@ -31335,19 +31883,8 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
31335
31883
|
for (const model of models) {
|
|
31336
31884
|
const off = offsetOf(model.id);
|
|
31337
31885
|
for (const s of model.series) {
|
|
31338
|
-
if (s.
|
|
31339
|
-
|
|
31340
|
-
const b = s.bars[i - off];
|
|
31341
|
-
if (b) {
|
|
31342
|
-
consider(b.high);
|
|
31343
|
-
consider(b.low);
|
|
31344
|
-
}
|
|
31345
|
-
}
|
|
31346
|
-
} else if (isLineLikeSeries(s)) {
|
|
31347
|
-
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
31348
|
-
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
31349
|
-
else if (s.style?.base != null) consider(s.style.base);
|
|
31350
|
-
}
|
|
31886
|
+
if (s.overlay === true) continue;
|
|
31887
|
+
considerSeries(s, i0, i1, off, consider);
|
|
31351
31888
|
}
|
|
31352
31889
|
for (const pl of model.priceLines) consider(pl.price);
|
|
31353
31890
|
}
|
|
@@ -31369,6 +31906,36 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
|
|
|
31369
31906
|
const span = max - min;
|
|
31370
31907
|
return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
|
|
31371
31908
|
}
|
|
31909
|
+
function considerSeries(s, i0, i1, off, consider) {
|
|
31910
|
+
if (s.kind === "candle" || s.kind === "bar") {
|
|
31911
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
31912
|
+
const b = s.bars[i - off];
|
|
31913
|
+
if (b) {
|
|
31914
|
+
consider(b.high);
|
|
31915
|
+
consider(b.low);
|
|
31916
|
+
}
|
|
31917
|
+
}
|
|
31918
|
+
} else if (isLineLikeSeries(s)) {
|
|
31919
|
+
for (let i = i0; i <= i1; i += 1) consider(s.points[i - off]?.value);
|
|
31920
|
+
if (s.kind === "histogram" || s.kind === "columns") consider(s.style?.base ?? 0);
|
|
31921
|
+
else if (s.style?.base != null) consider(s.style.base);
|
|
31922
|
+
}
|
|
31923
|
+
}
|
|
31924
|
+
function overlaySeriesRange(models, i0, i1, offsetOf = () => 0) {
|
|
31925
|
+
let min = Infinity;
|
|
31926
|
+
let max = -Infinity;
|
|
31927
|
+
const consider = (v) => {
|
|
31928
|
+
if (v != null && Number.isFinite(v)) {
|
|
31929
|
+
if (v < min) min = v;
|
|
31930
|
+
if (v > max) max = v;
|
|
31931
|
+
}
|
|
31932
|
+
};
|
|
31933
|
+
for (const model of models) {
|
|
31934
|
+
const off = offsetOf(model.id);
|
|
31935
|
+
for (const s of model.series) if (s.overlay === true) considerSeries(s, i0, i1, off, consider);
|
|
31936
|
+
}
|
|
31937
|
+
return min === Infinity ? null : { min, max };
|
|
31938
|
+
}
|
|
31372
31939
|
function expandScaleByPixels(scale, heightPx, abovePx, belowPx) {
|
|
31373
31940
|
if (abovePx <= 0 && belowPx <= 0) return scale;
|
|
31374
31941
|
const content = heightPx - abovePx - belowPx;
|
|
@@ -32017,6 +32584,8 @@ var NativeRenderer = class {
|
|
|
32017
32584
|
this.glowAmount = 0;
|
|
32018
32585
|
// WebGL2 neon-glow intensity (canvas2d ignores it)
|
|
32019
32586
|
this.chrome = new ChromeRenderer();
|
|
32587
|
+
/** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
|
|
32588
|
+
this.labelTooltip = null;
|
|
32020
32589
|
this.crosshairLayer = new CrosshairRenderer();
|
|
32021
32590
|
/** 1 Hz repaint pump so the price-axis countdown-to-bar-close ticks; null when off. */
|
|
32022
32591
|
this.countdownTimer = null;
|
|
@@ -32027,6 +32596,8 @@ var NativeRenderer = class {
|
|
|
32027
32596
|
this.indicatorValuesOn = true;
|
|
32028
32597
|
/** Host-contributed legend actions — held here so a rebuild of the legend re-wires them. */
|
|
32029
32598
|
this.legendActionsProvider = null;
|
|
32599
|
+
/** Host-contributed legend callouts — held here so a rebuild of the legend re-wires them. */
|
|
32600
|
+
this.legendCalloutsProvider = null;
|
|
32030
32601
|
/** Host override of the legend's fold toggle — held here so a remount re-applies it. */
|
|
32031
32602
|
this.legendOverviewAction = null;
|
|
32032
32603
|
// ── keyboard navigation / accessibility (item 11) ──
|
|
@@ -33135,6 +33706,10 @@ var NativeRenderer = class {
|
|
|
33135
33706
|
this.plot.appendChild(this.scrollButton);
|
|
33136
33707
|
this.plot.addEventListener("pointermove", this.onScrollProximityMove);
|
|
33137
33708
|
this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
|
|
33709
|
+
this.labelTooltip = new LabelTooltip(this.plot, {
|
|
33710
|
+
theme: () => this.chromeTheme(),
|
|
33711
|
+
lookup: (x, y) => this.chrome.labelTooltipAt(x, y)
|
|
33712
|
+
});
|
|
33138
33713
|
this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
|
|
33139
33714
|
projector: () => this.drawingProjector(),
|
|
33140
33715
|
dpr: () => this.coords.dpr,
|
|
@@ -33174,9 +33749,10 @@ var NativeRenderer = class {
|
|
|
33174
33749
|
this.inputsUI.setDialogHost(this.dialogHost);
|
|
33175
33750
|
this.inputsUI.setSymbolPicker(this.symbolPicker);
|
|
33176
33751
|
this.inputsUI.setLegendActions(this.legendActionsProvider);
|
|
33752
|
+
this.inputsUI.setLegendCallouts(this.legendCalloutsProvider);
|
|
33177
33753
|
this.inputsUI.setLegendOverviewAction(this.legendOverviewAction);
|
|
33178
33754
|
this.inputsUI.setOnChange((c) => {
|
|
33179
|
-
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value });
|
|
33755
|
+
for (const cb of this.inputChangeCbs) cb({ indicatorId: c.indicatorId, key: c.key, value: c.value, ...c.kind ? { kind: c.kind } : {} });
|
|
33180
33756
|
});
|
|
33181
33757
|
this.inputsUI.setOnRemove((id) => {
|
|
33182
33758
|
for (const cb of this.removeIndicatorCbs) cb(id);
|
|
@@ -33366,6 +33942,8 @@ var NativeRenderer = class {
|
|
|
33366
33942
|
this.settingsButton = null;
|
|
33367
33943
|
this.plot?.removeEventListener("pointermove", this.onScrollProximityMove);
|
|
33368
33944
|
this.plot?.removeEventListener("pointerleave", this.onScrollProximityLeave);
|
|
33945
|
+
this.labelTooltip?.destroy();
|
|
33946
|
+
this.labelTooltip = null;
|
|
33369
33947
|
this.scrollButton?.remove();
|
|
33370
33948
|
this.scrollButton = null;
|
|
33371
33949
|
for (const l of this.extLayers) l.instance.destroy?.();
|
|
@@ -33606,7 +34184,7 @@ var NativeRenderer = class {
|
|
|
33606
34184
|
else this.scene.assignIndicatorZ(model.id);
|
|
33607
34185
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
33608
34186
|
native: !!model.native,
|
|
33609
|
-
...model.
|
|
34187
|
+
...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
|
|
33610
34188
|
});
|
|
33611
34189
|
this.syncTables(model);
|
|
33612
34190
|
if (model.native?.type === "volume") {
|
|
@@ -33657,8 +34235,8 @@ var NativeRenderer = class {
|
|
|
33657
34235
|
this.paneControls?.refresh();
|
|
33658
34236
|
this.scheduler.invalidate(4 /* Full */);
|
|
33659
34237
|
}
|
|
33660
|
-
setIndicatorInputs(handle, values) {
|
|
33661
|
-
this.inputsUI.setValues(handle.id, values);
|
|
34238
|
+
setIndicatorInputs(handle, values, props) {
|
|
34239
|
+
this.inputsUI.setValues(handle.id, values, props);
|
|
33662
34240
|
}
|
|
33663
34241
|
setSymbolPicker(picker) {
|
|
33664
34242
|
this.symbolPicker = picker;
|
|
@@ -33668,6 +34246,10 @@ var NativeRenderer = class {
|
|
|
33668
34246
|
this.legendActionsProvider = provider;
|
|
33669
34247
|
this.inputsUI?.setLegendActions(provider);
|
|
33670
34248
|
}
|
|
34249
|
+
setLegendCallouts(provider) {
|
|
34250
|
+
this.legendCalloutsProvider = provider;
|
|
34251
|
+
this.inputsUI?.setLegendCallouts(provider);
|
|
34252
|
+
}
|
|
33671
34253
|
setLegendOverviewAction(action) {
|
|
33672
34254
|
this.legendOverviewAction = action;
|
|
33673
34255
|
this.inputsUI?.setLegendOverviewAction(action);
|
|
@@ -34648,7 +35230,11 @@ var NativeRenderer = class {
|
|
|
34648
35230
|
}
|
|
34649
35231
|
const models = this.scene.indicatorsForPane(pane.id);
|
|
34650
35232
|
const masterModels = models.filter((m) => m.ownScale !== true);
|
|
34651
|
-
|
|
35233
|
+
let dr = this.chrome.paneDrawingsRange(masterModels, this.scene, pane === pricePane, vr);
|
|
35234
|
+
if (pane === pricePane) {
|
|
35235
|
+
const or = overlaySeriesRange(this.scene.indicators.values(), i0, i1, (id) => this.scene.offsetOf(id));
|
|
35236
|
+
if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
|
|
35237
|
+
}
|
|
34652
35238
|
const includeCandles = pane.kind === "price" && !this.scene.candlesHidden;
|
|
34653
35239
|
pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
34654
35240
|
pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
|
|
@@ -34922,6 +35508,7 @@ var NativeRenderer = class {
|
|
|
34922
35508
|
for (const m of models) {
|
|
34923
35509
|
const off = this.scene.offsetOf(m.id);
|
|
34924
35510
|
for (const s of m.series) {
|
|
35511
|
+
if (s.overlay === true) continue;
|
|
34925
35512
|
if (isLineLikeSeries(s)) {
|
|
34926
35513
|
const v = s.points[i0 - off]?.value;
|
|
34927
35514
|
if (v != null && Number.isFinite(v)) return v;
|
|
@@ -35794,45 +36381,24 @@ var CSS21 = `
|
|
|
35794
36381
|
}
|
|
35795
36382
|
.vela-statusline .vela-sl-symbol { font-weight: 600; font-size: var(--vela-font-size-lg); }
|
|
35796
36383
|
.vela-statusline .vela-sl-meta { color: var(--vela-fg-muted); font-size: var(--vela-font-size-md); font-weight: 600; }
|
|
35797
|
-
/* Market status badge \u2014 icon-only 16px circle, label on hover
|
|
35798
|
-
|
|
35799
|
-
|
|
35800
|
-
place-items: center;
|
|
35801
|
-
width: 16px;
|
|
35802
|
-
height: 16px;
|
|
35803
|
-
border-radius: 50%;
|
|
35804
|
-
flex: none;
|
|
35805
|
-
align-self: center;
|
|
35806
|
-
line-height: 0;
|
|
35807
|
-
cursor: default;
|
|
35808
|
-
}
|
|
35809
|
-
.vela-statusline .vela-sl-market svg {
|
|
35810
|
-
width: 12px;
|
|
35811
|
-
height: 12px;
|
|
35812
|
-
display: block;
|
|
35813
|
-
}
|
|
35814
|
-
/* Open wears the theme's up color; the other sessions are meaning constants from the
|
|
35815
|
-
* palette (amber pre, sky post, gray closed/holiday). Same ink at 20% for the circle. */
|
|
35816
|
-
.vela-statusline .vela-sl-market[data-status='open'] {
|
|
35817
|
-
background: color-mix(in srgb, var(--vela-up) 20%, transparent);
|
|
35818
|
-
color: var(--vela-up);
|
|
35819
|
-
}
|
|
35820
|
-
.vela-statusline .vela-sl-market[data-status='pre'] { background: color-mix(in srgb, ${SESSION_PRE} 20%, transparent); color: ${SESSION_PRE}; }
|
|
35821
|
-
.vela-statusline .vela-sl-market[data-status='post'] { background: color-mix(in srgb, ${SESSION_POST} 20%, transparent); color: ${SESSION_POST}; }
|
|
35822
|
-
.vela-statusline .vela-sl-market[data-status='closed'],
|
|
35823
|
-
.vela-statusline .vela-sl-market[data-status='holiday'] { background: color-mix(in srgb, ${SESSION_OFF} 20%, transparent); color: ${SESSION_OFF}; }
|
|
36384
|
+
/* Market status badge \u2014 a kit callout bubble (icon-only 16px circle, label on hover
|
|
36385
|
+
* via the kit tooltip); the session tint is applied per status in setMarketStatus. */
|
|
36386
|
+
.vela-statusline .vela-sl-market { align-self: center; }
|
|
35824
36387
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
35825
36388
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
35826
36389
|
/* The change value wears the SAME ink as the OHLC values (set inline per render) \u2014
|
|
35827
36390
|
* these are the pre-ink fallbacks only. */
|
|
35828
36391
|
.vela-statusline .vela-sl-change[data-dir='up'] { color: var(--vela-up); }
|
|
35829
36392
|
.vela-statusline .vela-sl-change[data-dir='down'] { color: var(--vela-down); }
|
|
35830
|
-
/* Stack the
|
|
35831
|
-
* The renderer
|
|
35832
|
-
*
|
|
35833
|
-
*
|
|
35834
|
-
*
|
|
35835
|
-
|
|
36393
|
+
/* Stack the TOP pane's legend below the status line (lower study panes stay put).
|
|
36394
|
+
* The renderer marks whichever legend sits at the plot's top edge \u2014 the price pane
|
|
36395
|
+
* normally, or a maximized study pane filling the plot \u2014 so the legend never merges
|
|
36396
|
+
* with the status line whichever pane owns the top. The renderer sets the legend's
|
|
36397
|
+
* inline top \u2014 shift with a transform, don't fight it. Scoped to hosts that actually
|
|
36398
|
+
* CARRY a status line (the marker class set by the Statusline constructor) \u2014 the
|
|
36399
|
+
* stylesheet is document-global, so a bare attribute selector here would shift every
|
|
36400
|
+
* chart on the page, including statusline-less ones. */
|
|
36401
|
+
.vela-has-statusline [${LEGEND_AT_TOP_ATTR}] { transform: translateY(26px); }
|
|
35836
36402
|
/* Mobile: two-line chip \u2014 logo / symbol / meta / market status on one aligned row, the
|
|
35837
36403
|
* bar change on the next. Full O/H/L/C stays hidden (too dense on a phone-width plot).
|
|
35838
36404
|
* GRID, not a wrapping flexbox: an absolutely positioned wrapping flex container sizes
|
|
@@ -35868,7 +36434,7 @@ var CSS21 = `
|
|
|
35868
36434
|
font-size: var(--vela-font-size-sm);
|
|
35869
36435
|
line-height: 1.2;
|
|
35870
36436
|
}
|
|
35871
|
-
[data-layout='mobile'] .vela-has-statusline [
|
|
36437
|
+
[data-layout='mobile'] .vela-has-statusline [${LEGEND_AT_TOP_ATTR}] { transform: translateY(40px); }
|
|
35872
36438
|
/* FIT mode (multi-chart cells \u2014 see setFitMode): the line never wraps; segments that
|
|
35873
36439
|
* don't fit are HIDDEN by fit() (change first, then meta, then the market badge), so
|
|
35874
36440
|
* overflow:hidden only guards the transient between a resize and the next measure.
|
|
@@ -35885,7 +36451,7 @@ var CSS21 = `
|
|
|
35885
36451
|
padding-left: 0;
|
|
35886
36452
|
}
|
|
35887
36453
|
/* One row again \u2014 the mobile two-line shift doesn't apply in fit mode. */
|
|
35888
|
-
[data-layout='mobile'] .vela-sl-fit-host.vela-has-statusline [
|
|
36454
|
+
[data-layout='mobile'] .vela-sl-fit-host.vela-has-statusline [${LEGEND_AT_TOP_ATTR}] { transform: translateY(26px); }
|
|
35889
36455
|
`;
|
|
35890
36456
|
function baseOfTicker(ticker) {
|
|
35891
36457
|
return ticker.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || ticker;
|
|
@@ -35897,6 +36463,13 @@ var MARKET_LABELS = {
|
|
|
35897
36463
|
closed: "Market Closed",
|
|
35898
36464
|
holiday: "Market Holiday"
|
|
35899
36465
|
};
|
|
36466
|
+
var MARKET_INKS = {
|
|
36467
|
+
open: "var(--vela-up)",
|
|
36468
|
+
pre: SESSION_PRE,
|
|
36469
|
+
post: SESSION_POST,
|
|
36470
|
+
closed: SESSION_OFF,
|
|
36471
|
+
holiday: SESSION_OFF
|
|
36472
|
+
};
|
|
35900
36473
|
function statuslineInkOf(renderer, priceStyle) {
|
|
35901
36474
|
const cfg = renderer.getConfig();
|
|
35902
36475
|
const c = (v) => typeof v === "string" ? v : null;
|
|
@@ -35955,8 +36528,15 @@ var Statusline = class {
|
|
|
35955
36528
|
this.symbolEl.textContent = ticker;
|
|
35956
36529
|
this.metaEl = doc.createElement("span");
|
|
35957
36530
|
this.metaEl.className = "vela-sl-meta";
|
|
35958
|
-
this.
|
|
35959
|
-
|
|
36531
|
+
this.marketBubble = new CalloutBubble({
|
|
36532
|
+
icon: "market-open",
|
|
36533
|
+
background: `color-mix(in srgb, ${MARKET_INKS.open} 20%, transparent)`,
|
|
36534
|
+
color: MARKET_INKS.open,
|
|
36535
|
+
label: MARKET_LABELS.open,
|
|
36536
|
+
host
|
|
36537
|
+
});
|
|
36538
|
+
this.marketEl = this.marketBubble.el;
|
|
36539
|
+
this.marketEl.classList.add("vela-sl-market");
|
|
35960
36540
|
this.ohlcEl = doc.createElement("span");
|
|
35961
36541
|
this.ohlcEl.className = "vela-sl-ohlc";
|
|
35962
36542
|
this.changeEl = doc.createElement("span");
|
|
@@ -36044,8 +36624,13 @@ var Statusline = class {
|
|
|
36044
36624
|
* hover label. Callers with no session model leave the constructor's 'open'. */
|
|
36045
36625
|
setMarketStatus(status) {
|
|
36046
36626
|
this.marketEl.dataset.status = status;
|
|
36047
|
-
|
|
36048
|
-
this.
|
|
36627
|
+
const ink = MARKET_INKS[status];
|
|
36628
|
+
this.marketBubble.set({
|
|
36629
|
+
icon: `market-${status}`,
|
|
36630
|
+
background: `color-mix(in srgb, ${ink} 20%, transparent)`,
|
|
36631
|
+
color: ink,
|
|
36632
|
+
label: MARKET_LABELS[status]
|
|
36633
|
+
});
|
|
36049
36634
|
this.marketTip.setContent(MARKET_LABELS[status]);
|
|
36050
36635
|
}
|
|
36051
36636
|
setPartVisible(part, visible) {
|
|
@@ -36078,6 +36663,7 @@ var Statusline = class {
|
|
|
36078
36663
|
this.fitRO?.disconnect();
|
|
36079
36664
|
this.fitRO = null;
|
|
36080
36665
|
this.marketTip.destroy();
|
|
36666
|
+
this.marketBubble.destroy();
|
|
36081
36667
|
this.host.classList.remove("vela-has-statusline", "vela-sl-fit-host");
|
|
36082
36668
|
this.el.remove();
|
|
36083
36669
|
}
|
|
@@ -36304,6 +36890,8 @@ var SessionShadingTracker = class {
|
|
|
36304
36890
|
this.spec = null;
|
|
36305
36891
|
this.ready = false;
|
|
36306
36892
|
this.session = "regular";
|
|
36893
|
+
/** Whether one bar is shorter than a civil day — only then do pre/post bars exist. */
|
|
36894
|
+
this.intraday = false;
|
|
36307
36895
|
this.covered = null;
|
|
36308
36896
|
/** The newest range seen — viewport moves during metadata resolution (a load's fit
|
|
36309
36897
|
* animation) must not be lost, so the resolution always expands the LATEST range. */
|
|
@@ -36316,6 +36904,8 @@ var SessionShadingTracker = class {
|
|
|
36316
36904
|
this.spec = null;
|
|
36317
36905
|
this.covered = null;
|
|
36318
36906
|
this.session = opts.session;
|
|
36907
|
+
const tfMs = timeframeMs(opts.timeframe);
|
|
36908
|
+
this.intraday = Number.isFinite(tfMs) && tfMs < DAY_MS2;
|
|
36319
36909
|
this.lastRange = opts.range;
|
|
36320
36910
|
void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
|
|
36321
36911
|
if (my !== this.epoch) return;
|
|
@@ -36343,7 +36933,7 @@ var SessionShadingTracker = class {
|
|
|
36343
36933
|
if (force) this.onZones(null);
|
|
36344
36934
|
return;
|
|
36345
36935
|
}
|
|
36346
|
-
if (this.session !== "extended") {
|
|
36936
|
+
if (this.session !== "extended" || !this.intraday) {
|
|
36347
36937
|
if (force) this.onZones({ pre: [], post: [] });
|
|
36348
36938
|
return;
|
|
36349
36939
|
}
|
|
@@ -36758,6 +37348,7 @@ var ChartCell = class {
|
|
|
36758
37348
|
this.inner.renderer.set("attribution", false);
|
|
36759
37349
|
this.inner.renderer.set("dialogHost", deps.dialogHost);
|
|
36760
37350
|
this.inner.renderer.setLegendActions(legendActionsProviderFor(this.inner, () => deps.context()));
|
|
37351
|
+
this.inner.renderer.setLegendCallouts(legendCalloutsProviderFor(this.inner, () => deps.context()));
|
|
36761
37352
|
if (this.inner.renderer.supports("historyChords")) this.inner.renderer.set("historyChords", false);
|
|
36762
37353
|
this.history.onChart(this.inner);
|
|
36763
37354
|
this.inner.renderer.onConfigChanged(() => {
|
|
@@ -36874,20 +37465,30 @@ var ChartCell = class {
|
|
|
36874
37465
|
this.refreshNativeCatalog();
|
|
36875
37466
|
this.pushSettingsSections();
|
|
36876
37467
|
this.offMarket = this.inner.on("market:changed", ({ symbol: symbol2, timeframe }) => {
|
|
36877
|
-
this.
|
|
36878
|
-
this.state.provider = parseSymbol(symbol2).provider ?? void 0;
|
|
36879
|
-
this.state.timeframe = timeframe;
|
|
36880
|
-
this.state.session = normalizeSession(this.inner?.market.session);
|
|
36881
|
-
this.watermark?.update(symbol2, timeframe);
|
|
36882
|
-
this.statusline?.setSymbol(symbol2);
|
|
36883
|
-
this.statusline?.setMeta(timeframe, this.inner?.data.displayPrefix(symbol2) ?? this.state.provider ?? "");
|
|
36884
|
-
if (this.inner) this.statusline?.onChart(this.inner);
|
|
37468
|
+
this.projectMarket(symbol2, timeframe);
|
|
36885
37469
|
this.refreshNativeCatalog();
|
|
36886
37470
|
this.refreshSessionAvailable();
|
|
36887
37471
|
if (this.inner) this.marketStatus?.track(this.inner.data, symbol2);
|
|
36888
37472
|
this.deps.onMarketChanged(this.id);
|
|
36889
37473
|
});
|
|
36890
37474
|
}
|
|
37475
|
+
/**
|
|
37476
|
+
* Project a market identity into the cell state and its display overlays (watermark,
|
|
37477
|
+
* statusline), WITHOUT the data-dependent bookkeeping. Runs twice per user pick: once
|
|
37478
|
+
* optimistically from the cell setters — the labels reflect the pick immediately, not
|
|
37479
|
+
* after the bars load — and again from `market:changed` (the committed pass, and the
|
|
37480
|
+
* only pass for host `chart.setMarket` calls). Idempotent, so the double run converges.
|
|
37481
|
+
*/
|
|
37482
|
+
projectMarket(symbol, timeframe) {
|
|
37483
|
+
this.state.symbol = symbol;
|
|
37484
|
+
this.state.provider = parseSymbol(symbol).provider ?? void 0;
|
|
37485
|
+
this.state.timeframe = timeframe;
|
|
37486
|
+
this.state.session = normalizeSession(this.inner?.market.session);
|
|
37487
|
+
this.watermark?.update(symbol, timeframe);
|
|
37488
|
+
this.statusline?.setSymbol(symbol);
|
|
37489
|
+
this.statusline?.setMeta(timeframe, this.inner?.data.displayPrefix(symbol) ?? this.state.provider ?? "");
|
|
37490
|
+
if (this.inner) this.statusline?.onChart(this.inner);
|
|
37491
|
+
}
|
|
36891
37492
|
/**
|
|
36892
37493
|
* Does this cell's market HAVE sessions (RTH/ETH meaningful)? Derived from the
|
|
36893
37494
|
* symbol's own metadata (`syminfo.session !== '24x7'`), asynchronously — the
|
|
@@ -36933,7 +37534,7 @@ var ChartCell = class {
|
|
|
36933
37534
|
const requestedSpan = Math.max(this.state.bars ?? 1e3, this.rangeBars) * timeframeMs(this.state.timeframe ?? "60");
|
|
36934
37535
|
const fallbackSpan = Number.isFinite(requestedSpan) ? Math.max(3 * 864e5, requestedSpan) : 3 * 864e5;
|
|
36935
37536
|
const range = chart.getVisibleRange() ?? { from: now - fallbackSpan, to: now };
|
|
36936
|
-
this.sessionShading.track(chart.data, symbol, { session: this.session, range });
|
|
37537
|
+
this.sessionShading.track(chart.data, symbol, { session: this.session, timeframe: this.timeframe, range });
|
|
36937
37538
|
}
|
|
36938
37539
|
/** The session-shade colors live in the renderer CONFIG (persisted with it, edited
|
|
36939
37540
|
* live by the dialog swatch) — the cell only proxies them into its settings rows. */
|
|
@@ -37122,17 +37723,23 @@ var ChartCell = class {
|
|
|
37122
37723
|
get indicatorCount() {
|
|
37123
37724
|
return this.instances.length + this.nativeCatalog.filter((n) => n.present).length;
|
|
37124
37725
|
}
|
|
37125
|
-
/** Switch this cell's market in place (the chart instance survives).
|
|
37726
|
+
/** Switch this cell's market in place (the chart instance survives). The projection
|
|
37727
|
+
* is OPTIMISTIC — labels and chrome show the pick before the bars load; it follows
|
|
37728
|
+
* the setMarket call so the statusline reads the already-blanked chart. */
|
|
37126
37729
|
setSymbol(symbol) {
|
|
37127
37730
|
if (!this.inner || symbol === this.symbol) return;
|
|
37128
37731
|
this.unresolvedToasted = null;
|
|
37129
37732
|
void this.inner.setMarket({ symbol });
|
|
37733
|
+
this.projectMarket(symbol, this.timeframe);
|
|
37734
|
+
this.deps.onMarketChanged(this.id);
|
|
37130
37735
|
}
|
|
37131
37736
|
setTimeframe(timeframe) {
|
|
37132
37737
|
if (!this.inner || timeframe === this.timeframe) return;
|
|
37133
37738
|
this.activeRangeId = null;
|
|
37134
37739
|
this.rangeBars = 0;
|
|
37135
37740
|
void this.inner.setMarket({ timeframe, bars: this.state.bars });
|
|
37741
|
+
this.projectMarket(this.symbol, timeframe);
|
|
37742
|
+
this.deps.onMarketChanged(this.id);
|
|
37136
37743
|
}
|
|
37137
37744
|
/** Applied live (renderer feature) — no reload. */
|
|
37138
37745
|
setPriceStyle(style) {
|
|
@@ -38238,7 +38845,10 @@ var VelaWorkspace = class {
|
|
|
38238
38845
|
this.topbar.renderActions();
|
|
38239
38846
|
this.mobileBar?.renderActions();
|
|
38240
38847
|
this.dock.refresh();
|
|
38241
|
-
for (const cell of this.cells())
|
|
38848
|
+
for (const cell of this.cells()) {
|
|
38849
|
+
cell.chart.renderer.setLegendActions(legendActionsProviderFor(cell.chart, () => this.context()));
|
|
38850
|
+
cell.chart.renderer.setLegendCallouts(legendCalloutsProviderFor(cell.chart, () => this.context()));
|
|
38851
|
+
}
|
|
38242
38852
|
}
|
|
38243
38853
|
// ── state surface (the SDK's read/restore of the whole grid's config + content) ──
|
|
38244
38854
|
/**
|