@luxalgo/vela 0.6.6 → 0.6.7
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-JJq7M2it.d.ts → DataProvider-BSLlBpB9.d.ts} +10 -0
- package/dist/{DataProvider-DDUYw2qP.d.cts → DataProvider-BsQM2WNH.d.cts} +10 -0
- package/dist/{bottombar-COnL2Sk4.d.ts → bottombar-Br4rzx_R.d.ts} +52 -6
- package/dist/{bottombar-DeDXbF_K.d.cts → bottombar-Div5zibZ.d.cts} +52 -6
- package/dist/{chunk-JQFO2WMU.js → chunk-6WFKTAT4.js} +37 -1
- package/dist/{chunk-OEUGUXL7.js → chunk-L3I2CCYO.js} +49 -1
- package/dist/chunk-W4EJWLEO.js +12 -0
- package/dist/{chunk-TMXK2SJR.js → chunk-WVP4XV5A.js} +221 -65
- package/dist/{contributions-DNMqrAuw.d.cts → contributions-Hv4_cOJo.d.cts} +46 -3
- package/dist/{contributions-BqCIsbaP.d.ts → contributions-Z12BrWCy.d.ts} +46 -3
- package/dist/index.cjs +64 -0
- package/dist/index.d.cts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +2 -2
- package/dist/{plugin-WnvNNJOp.d.ts → plugin-9koBjc5H.d.ts} +2 -2
- package/dist/{plugin-CN8U2__Q.d.cts → plugin-BXhDHTYn.d.cts} +2 -2
- package/dist/plugin.cjs +28 -0
- package/dist/plugin.d.cts +3 -3
- package/dist/plugin.d.ts +3 -3
- package/dist/plugin.js +1 -1
- package/dist/providers/binance.cjs +16 -0
- package/dist/providers/binance.d.cts +4 -1
- package/dist/providers/binance.d.ts +4 -1
- package/dist/providers/binance.js +7 -0
- package/dist/providers/coinbase.cjs +16 -0
- package/dist/providers/coinbase.d.cts +4 -1
- package/dist/providers/coinbase.d.ts +4 -1
- package/dist/providers/coinbase.js +7 -0
- package/dist/providers/hyperliquid.cjs +16 -0
- package/dist/providers/hyperliquid.d.cts +4 -1
- package/dist/providers/hyperliquid.d.ts +4 -1
- package/dist/providers/hyperliquid.js +7 -0
- package/dist/vela.global.js +90 -0
- package/dist/vela.global.min.js +42 -42
- package/dist/widget.cjs +319 -63
- package/dist/widget.d.cts +52 -10
- package/dist/widget.d.ts +52 -10
- package/dist/widget.js +5 -4
- package/dist/workspace.cjs +265 -65
- package/dist/workspace.d.cts +13 -4
- package/dist/workspace.d.ts +13 -4
- package/dist/workspace.js +4 -3
- package/package.json +1 -1
package/dist/workspace.cjs
CHANGED
|
@@ -303,6 +303,28 @@ var ProviderRegistry = class {
|
|
|
303
303
|
const d = this.entries.get(resolved.provider)?.byTicker?.get(normTicker(resolved.ticker));
|
|
304
304
|
return `${d?.prefix ?? resolved.provider}:${d?.ticker ?? resolved.ticker}`;
|
|
305
305
|
}
|
|
306
|
+
/** The icon URL for a DESCRIPTOR — routed to its OWNING provider's resolver (the
|
|
307
|
+
* provider owns the knowledge of where its asset class's icons live). A missing
|
|
308
|
+
* provider, an absent resolver, or a resolver that throws all mean "no icon" —
|
|
309
|
+
* the shells' initials badge takes over, never an error. */
|
|
310
|
+
symbolIconOf(d) {
|
|
311
|
+
const name = d.provider ? normName(d.provider) : [...this.entries.keys()][0];
|
|
312
|
+
const entry = name !== void 0 ? this.entries.get(name) : void 0;
|
|
313
|
+
try {
|
|
314
|
+
return entry?.provider.resolveSymbolIcon?.(d) ?? void 0;
|
|
315
|
+
} catch {
|
|
316
|
+
return void 0;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
/** The icon URL for a raw SYMBOL string (the statusline/object-tree path): resolve
|
|
320
|
+
* to the owning provider, find its descriptor, route to the resolver. Before the
|
|
321
|
+
* index settles (or for a lenient sole-provider resolution) a minimal synthetic
|
|
322
|
+
* descriptor is offered — crypto resolvers can answer from the ticker alone. */
|
|
323
|
+
symbolIcon(resolved) {
|
|
324
|
+
if (!resolved) return void 0;
|
|
325
|
+
const d = this.entries.get(resolved.provider)?.byTicker?.get(normTicker(resolved.ticker));
|
|
326
|
+
return this.symbolIconOf({ ...d ?? { ticker: resolved.ticker }, provider: d?.provider ?? resolved.provider });
|
|
327
|
+
}
|
|
306
328
|
/** Indexed symbols for one provider (or all, concatenated) — for autocomplete. */
|
|
307
329
|
symbolsOf(rawName) {
|
|
308
330
|
if (rawName != null) return this.entries.get(normName(rawName))?.descriptors ?? [];
|
|
@@ -655,6 +677,14 @@ var MultiProviderFeed = class {
|
|
|
655
677
|
providerInstance(name) {
|
|
656
678
|
return this.registry.get(name);
|
|
657
679
|
}
|
|
680
|
+
/** The icon URL for a DESCRIPTOR — its owning provider's `resolveSymbolIcon` (picker rows). */
|
|
681
|
+
symbolIconOf(d) {
|
|
682
|
+
return this.registry.symbolIconOf(d);
|
|
683
|
+
}
|
|
684
|
+
/** The icon URL for a raw SYMBOL string — resolve, then route (statusline, object tree). */
|
|
685
|
+
symbolIcon(raw) {
|
|
686
|
+
return this.registry.symbolIcon(this.resolveSymbol(raw));
|
|
687
|
+
}
|
|
658
688
|
symbols(name) {
|
|
659
689
|
return this.registry.symbolsOf(name);
|
|
660
690
|
}
|
|
@@ -4554,6 +4584,29 @@ function tickerModifierIds() {
|
|
|
4554
4584
|
return [...registry2.values()].filter((d) => d.tickerModifier ?? d.barTransform != null).map((d) => d.id);
|
|
4555
4585
|
}
|
|
4556
4586
|
|
|
4587
|
+
// src/widget/topbar-composition.ts
|
|
4588
|
+
var TOPBAR_BUILTIN_IDS = ["symbol", "timeframes", "style", "layout", "indicators", "actions", "undo-redo", "alerts", "panels", "screenshot"];
|
|
4589
|
+
var TOPBAR_DEFAULT_LEFT = ["symbol", "timeframes", "style", "layout", "indicators", "actions", "undo-redo"];
|
|
4590
|
+
var TOPBAR_DEFAULT_RIGHT = ["actions", "alerts", "panels", "screenshot"];
|
|
4591
|
+
function resolveTopbarComposition(opt) {
|
|
4592
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4593
|
+
const take = (list) => {
|
|
4594
|
+
const out = [];
|
|
4595
|
+
const local = /* @__PURE__ */ new Set();
|
|
4596
|
+
for (const id of list) {
|
|
4597
|
+
if (!id || local.has(id) || id !== "actions" && seen.has(id)) continue;
|
|
4598
|
+
local.add(id);
|
|
4599
|
+
seen.add(id);
|
|
4600
|
+
out.push(id);
|
|
4601
|
+
}
|
|
4602
|
+
return out;
|
|
4603
|
+
};
|
|
4604
|
+
return { left: take(opt?.left ?? TOPBAR_DEFAULT_LEFT), right: take(opt?.right ?? TOPBAR_DEFAULT_RIGHT) };
|
|
4605
|
+
}
|
|
4606
|
+
function topbarHas(comp, id) {
|
|
4607
|
+
return comp.left.includes(id) || comp.right.includes(id);
|
|
4608
|
+
}
|
|
4609
|
+
|
|
4557
4610
|
// src/widget/contributions.ts
|
|
4558
4611
|
var registry3 = /* @__PURE__ */ new Map();
|
|
4559
4612
|
var attachments = /* @__PURE__ */ new Map();
|
|
@@ -4565,6 +4618,13 @@ var DEFAULT_PANEL_ORDER = 100;
|
|
|
4565
4618
|
function sidePanels() {
|
|
4566
4619
|
return [...panels.values()].sort((a, b) => (a.order ?? DEFAULT_PANEL_ORDER) - (b.order ?? DEFAULT_PANEL_ORDER));
|
|
4567
4620
|
}
|
|
4621
|
+
var OVERRIDABLE_TOPBAR_IDS = ["indicators", "screenshot"];
|
|
4622
|
+
var overridableSet = new Set(OVERRIDABLE_TOPBAR_IDS);
|
|
4623
|
+
new Set(TOPBAR_BUILTIN_IDS);
|
|
4624
|
+
function topbarActionOverride(id) {
|
|
4625
|
+
if (!overridableSet.has(id)) return void 0;
|
|
4626
|
+
return registry3.get(id);
|
|
4627
|
+
}
|
|
4568
4628
|
function widgetActions(target, ctx) {
|
|
4569
4629
|
const list = [...registry3.values()].filter((d) => d.target === target);
|
|
4570
4630
|
list.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
@@ -5068,9 +5128,14 @@ var CSS3 = `
|
|
|
5068
5128
|
align-items: center;
|
|
5069
5129
|
justify-content: center;
|
|
5070
5130
|
}
|
|
5071
|
-
|
|
5131
|
+
/* The right side of the bar \u2014 whatever the composition puts there rides this one
|
|
5132
|
+
auto-margin push (the flow-actions host used to carry it; composition can omit it). */
|
|
5133
|
+
.vela-topbar-right { margin-left: auto; display: inline-flex; align-items: center; gap: var(--vela-space-1); }
|
|
5134
|
+
.vela-widget-actions { display: inline-flex; gap: var(--vela-space-1); }
|
|
5072
5135
|
/* Left-aligned contributed actions \u2014 the primary-chrome cluster after the dropdowns. */
|
|
5073
5136
|
.vela-widget-actions-left { display: inline-flex; align-items: center; gap: var(--vela-space-1); }
|
|
5137
|
+
/* One PINNED contributed action's slot (a composition entry naming the action's id). */
|
|
5138
|
+
.vela-widget-action-pin { display: inline-flex; align-items: center; }
|
|
5074
5139
|
/* The side-panel toggles, one per docked panel \u2014 a group so the dock can rebuild them
|
|
5075
5140
|
without disturbing the tools around it. */
|
|
5076
5141
|
.vela-widget-panels { display: inline-flex; align-items: center; gap: var(--vela-space-1); }
|
|
@@ -5129,6 +5194,12 @@ var Topbar = class {
|
|
|
5129
5194
|
this.tooltips = [];
|
|
5130
5195
|
this.panelBtns = /* @__PURE__ */ new Map();
|
|
5131
5196
|
this.panelTooltips = [];
|
|
5197
|
+
/** Pinned contributed-action slots, by action id (composition entries that name one,
|
|
5198
|
+
* plus built-in slots taken over by an override). */
|
|
5199
|
+
this.pinned = /* @__PURE__ */ new Map();
|
|
5200
|
+
/** Overrides that LEFT their native slot (default side + a declared `order`) — they
|
|
5201
|
+
* render through the flow cluster like ordinary actions. */
|
|
5202
|
+
this.flowingOverrides = /* @__PURE__ */ new Set();
|
|
5132
5203
|
this.onHairlineSync = () => {
|
|
5133
5204
|
if (this.hairlineRaf) return;
|
|
5134
5205
|
const win = this.el.ownerDocument.defaultView;
|
|
@@ -5143,6 +5214,8 @@ var Topbar = class {
|
|
|
5143
5214
|
this.host = host;
|
|
5144
5215
|
this.timeframe = opts.timeframe;
|
|
5145
5216
|
this.priceStyle = opts.priceStyle;
|
|
5217
|
+
this.comp = resolveTopbarComposition(opts.composition);
|
|
5218
|
+
const vis = (id) => topbarHas(this.comp, id);
|
|
5146
5219
|
const doc = host.ownerDocument;
|
|
5147
5220
|
injectStyles(STYLE_ID2, CSS3, doc);
|
|
5148
5221
|
this.el = doc.createElement("div");
|
|
@@ -5165,7 +5238,7 @@ var Topbar = class {
|
|
|
5165
5238
|
this.styleButton.className = "vela-widget-style";
|
|
5166
5239
|
this.renderStyleButton(doc);
|
|
5167
5240
|
let indicatorsBtn = null;
|
|
5168
|
-
if (opts.onIndicatorsClick) {
|
|
5241
|
+
if (opts.onIndicatorsClick && !topbarActionOverride("indicators")) {
|
|
5169
5242
|
indicatorsBtn = doc.createElement("button");
|
|
5170
5243
|
indicatorsBtn.className = "vela-widget-indicators";
|
|
5171
5244
|
indicatorsBtn.append(iconEl("indicators", doc), doc.createTextNode("Indicators"));
|
|
@@ -5178,18 +5251,23 @@ var Topbar = class {
|
|
|
5178
5251
|
this.panelsHost = doc.createElement("span");
|
|
5179
5252
|
this.panelsHost.className = "vela-widget-panels";
|
|
5180
5253
|
const tool = (cls, icon2, tip, onClick) => this.toolButton(cls, icon2, tip, onClick, this.tooltips);
|
|
5181
|
-
|
|
5182
|
-
|
|
5254
|
+
if (vis("undo-redo")) {
|
|
5255
|
+
this.undoBtn = tool("vela-widget-undo", "undo", "Undo", opts.onUndoClick);
|
|
5256
|
+
this.redoBtn = tool("vela-widget-redo", "redo", "Redo", opts.onRedoClick);
|
|
5257
|
+
} else {
|
|
5258
|
+
this.undoBtn = doc.createElement("button");
|
|
5259
|
+
this.redoBtn = doc.createElement("button");
|
|
5260
|
+
}
|
|
5183
5261
|
this.setHistoryState(false, false);
|
|
5184
|
-
const screenshotBtn = tool("vela-widget-screenshot", "camera", "Download screenshot", opts.onScreenshotClick);
|
|
5185
|
-
this.alertsBtn = tool("vela-widget-alerts", "bell", "Alerts");
|
|
5262
|
+
const screenshotBtn = vis("screenshot") && !topbarActionOverride("screenshot") ? tool("vela-widget-screenshot", "camera", "Download screenshot", opts.onScreenshotClick) : null;
|
|
5263
|
+
this.alertsBtn = vis("alerts") ? tool("vela-widget-alerts", "bell", "Alerts") : doc.createElement("button");
|
|
5186
5264
|
this.alertsBtn.style.position = "relative";
|
|
5187
5265
|
if (opts.onAlertsClick) this.alertsBtn.addEventListener("click", () => opts.onAlertsClick(this.alertsBtn));
|
|
5188
5266
|
this.alertsBadge = doc.createElement("span");
|
|
5189
5267
|
this.alertsBadge.className = "vela-alerts-badge";
|
|
5190
5268
|
this.alertsBadge.style.display = "none";
|
|
5191
5269
|
this.alertsBtn.appendChild(this.alertsBadge);
|
|
5192
|
-
if (opts.layout) {
|
|
5270
|
+
if (opts.layout && vis("layout")) {
|
|
5193
5271
|
this.layoutId = opts.layout.current;
|
|
5194
5272
|
this.layoutButton = doc.createElement("button");
|
|
5195
5273
|
this.layoutButton.className = "vela-widget-style";
|
|
@@ -5200,12 +5278,65 @@ var Topbar = class {
|
|
|
5200
5278
|
d.className = "vela-sep";
|
|
5201
5279
|
return d;
|
|
5202
5280
|
};
|
|
5203
|
-
const leading = [this.symbolEl, sep(), tfGroup, sep(), this.styleButton, sep()];
|
|
5204
|
-
if (this.layoutButton) leading.push(this.layoutButton, sep());
|
|
5205
|
-
if (indicatorsBtn) leading.push(indicatorsBtn, sep());
|
|
5206
5281
|
this.leftActionsSep = sep();
|
|
5207
5282
|
this.leftActionsSep.hidden = true;
|
|
5208
|
-
|
|
5283
|
+
const primaries = /* @__PURE__ */ new Set(["symbol", "timeframes", "style", "layout", "indicators"]);
|
|
5284
|
+
const pinSlot = (id, left) => {
|
|
5285
|
+
const slot = doc.createElement("span");
|
|
5286
|
+
slot.className = "vela-widget-action-pin";
|
|
5287
|
+
this.pinned.set(id, { host: slot, left });
|
|
5288
|
+
return [slot];
|
|
5289
|
+
};
|
|
5290
|
+
const overridden = (id, left) => {
|
|
5291
|
+
const ov = topbarActionOverride(id);
|
|
5292
|
+
if (!ov) return null;
|
|
5293
|
+
const sideDeclared = left ? opts.composition?.left != null : opts.composition?.right != null;
|
|
5294
|
+
if (!sideDeclared && ov.order !== void 0) {
|
|
5295
|
+
this.flowingOverrides.add(id);
|
|
5296
|
+
return [];
|
|
5297
|
+
}
|
|
5298
|
+
return pinSlot(id, left);
|
|
5299
|
+
};
|
|
5300
|
+
const elementsFor = (id, left) => {
|
|
5301
|
+
switch (id) {
|
|
5302
|
+
case "symbol":
|
|
5303
|
+
return [this.symbolEl];
|
|
5304
|
+
case "timeframes":
|
|
5305
|
+
return [tfGroup];
|
|
5306
|
+
case "style":
|
|
5307
|
+
return [this.styleButton];
|
|
5308
|
+
case "layout":
|
|
5309
|
+
return this.layoutButton ? [this.layoutButton] : [];
|
|
5310
|
+
case "indicators":
|
|
5311
|
+
return overridden(id, left) ?? (indicatorsBtn ? [indicatorsBtn] : []);
|
|
5312
|
+
case "actions":
|
|
5313
|
+
return left ? [this.leftActionsHost, this.leftActionsSep] : [this.actionsHost];
|
|
5314
|
+
case "undo-redo":
|
|
5315
|
+
return [this.undoBtn, this.redoBtn];
|
|
5316
|
+
case "alerts":
|
|
5317
|
+
return [this.alertsBtn];
|
|
5318
|
+
case "panels":
|
|
5319
|
+
return [this.panelsHost];
|
|
5320
|
+
case "screenshot":
|
|
5321
|
+
return overridden(id, left) ?? (screenshotBtn ? [screenshotBtn] : []);
|
|
5322
|
+
default:
|
|
5323
|
+
return pinSlot(id, left);
|
|
5324
|
+
}
|
|
5325
|
+
};
|
|
5326
|
+
const sideEls = (list, left) => {
|
|
5327
|
+
const out = [];
|
|
5328
|
+
for (const [i, id] of list.entries()) {
|
|
5329
|
+
const els = elementsFor(id, left);
|
|
5330
|
+
if (els.length === 0) continue;
|
|
5331
|
+
out.push(...els);
|
|
5332
|
+
if (primaries.has(id) && i < list.length - 1) out.push(sep());
|
|
5333
|
+
}
|
|
5334
|
+
return out;
|
|
5335
|
+
};
|
|
5336
|
+
const right = doc.createElement("span");
|
|
5337
|
+
right.className = "vela-topbar-right";
|
|
5338
|
+
right.append(...sideEls(this.comp.right, false));
|
|
5339
|
+
this.el.append(...sideEls(this.comp.left, true), right);
|
|
5209
5340
|
host.appendChild(this.el);
|
|
5210
5341
|
this.renderTfChips();
|
|
5211
5342
|
this.onHairlineSync();
|
|
@@ -5322,14 +5453,24 @@ var Topbar = class {
|
|
|
5322
5453
|
else this.styleButton.appendChild(doc.createTextNode(priceStyleLabel(this.priceStyle)));
|
|
5323
5454
|
this.styleButton.setAttribute("aria-label", `Chart style \u2014 ${priceStyleLabel(this.priceStyle)}`);
|
|
5324
5455
|
}
|
|
5325
|
-
/** Re-project the contributed topbar actions (call after registrations change).
|
|
5456
|
+
/** Re-project the contributed topbar actions (call after registrations change).
|
|
5457
|
+
* An action PINNED by the composition renders into its named slot (list position
|
|
5458
|
+
* wins over `align`/`order`); the rest flow into the side's `actions` slot — or
|
|
5459
|
+
* not at all when an explicit list omits it (the list is the side's contract). */
|
|
5326
5460
|
renderActions() {
|
|
5327
5461
|
const ctx = this.opts.getContext?.();
|
|
5328
5462
|
this.actionsHost.replaceChildren();
|
|
5329
5463
|
this.leftActionsHost.replaceChildren();
|
|
5464
|
+
for (const pin of this.pinned.values()) pin.host.replaceChildren();
|
|
5330
5465
|
const doc = this.actionsHost.ownerDocument;
|
|
5466
|
+
const flowLeft = this.comp.left.includes("actions");
|
|
5467
|
+
const flowRight = this.comp.right.includes("actions");
|
|
5468
|
+
const builtin = new Set(TOPBAR_BUILTIN_IDS);
|
|
5331
5469
|
for (const action of widgetActions("topbar", ctx)) {
|
|
5332
|
-
const
|
|
5470
|
+
const pin = this.pinned.get(action.id);
|
|
5471
|
+
if (!pin && builtin.has(action.id) && !this.flowingOverrides.has(action.id)) continue;
|
|
5472
|
+
const left = pin ? pin.left : action.align === "left";
|
|
5473
|
+
if (!pin && !(left ? flowLeft : flowRight)) continue;
|
|
5333
5474
|
const b = doc.createElement("button");
|
|
5334
5475
|
b.className = left ? "vela-widget-action-left" : "vela-widget-action";
|
|
5335
5476
|
if (action.icon) b.appendChild(iconEl(action.icon, doc));
|
|
@@ -5338,7 +5479,7 @@ var Topbar = class {
|
|
|
5338
5479
|
const c = this.opts.getContext?.();
|
|
5339
5480
|
if (c) action.run(c);
|
|
5340
5481
|
});
|
|
5341
|
-
(left ? this.leftActionsHost : this.actionsHost).appendChild(b);
|
|
5482
|
+
(pin ? pin.host : left ? this.leftActionsHost : this.actionsHost).appendChild(b);
|
|
5342
5483
|
}
|
|
5343
5484
|
this.leftActionsSep.hidden = this.leftActionsHost.childElementCount === 0;
|
|
5344
5485
|
this.onHairlineSync();
|
|
@@ -5731,41 +5872,39 @@ var Bottombar = class {
|
|
|
5731
5872
|
}
|
|
5732
5873
|
};
|
|
5733
5874
|
|
|
5734
|
-
// src/
|
|
5735
|
-
var iconFailed = /* @__PURE__ */ new Set();
|
|
5736
|
-
function initialsOf(name) {
|
|
5737
|
-
return (name || "?").replace(/[^A-Za-z0-9]/g, "").slice(0, 2).toUpperCase() || "?";
|
|
5738
|
-
}
|
|
5739
|
-
function cryptoIconUrl(base) {
|
|
5740
|
-
return `https://crypto-icons.ledger.com/${encodeURIComponent(base.toUpperCase())}.png`;
|
|
5741
|
-
}
|
|
5875
|
+
// src/data/symbol-base.ts
|
|
5742
5876
|
function baseOf(d) {
|
|
5743
5877
|
const fromDesc = d.description?.split("/")[0]?.trim();
|
|
5744
5878
|
if (fromDesc) return fromDesc.replace(/\s+Perpetual$/i, "");
|
|
5745
5879
|
return d.ticker.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || d.ticker;
|
|
5746
5880
|
}
|
|
5747
|
-
|
|
5881
|
+
|
|
5882
|
+
// src/widget/symbol-icon.ts
|
|
5883
|
+
var iconFailed = /* @__PURE__ */ new Set();
|
|
5884
|
+
function initialsOf(name) {
|
|
5885
|
+
return (name || "?").replace(/[^A-Za-z0-9]/g, "").slice(0, 2).toUpperCase() || "?";
|
|
5886
|
+
}
|
|
5887
|
+
function tickerIconEl(doc, base, name, className, iconUrl) {
|
|
5748
5888
|
const wrap = doc.createElement("span");
|
|
5749
5889
|
wrap.className = className;
|
|
5750
|
-
const key = base.toUpperCase();
|
|
5751
5890
|
const fallback = () => {
|
|
5752
5891
|
wrap.replaceChildren();
|
|
5753
5892
|
wrap.style.background = categoricalColor(name);
|
|
5754
5893
|
wrap.textContent = initialsOf(base || name);
|
|
5755
5894
|
};
|
|
5756
|
-
if (!
|
|
5895
|
+
if (!iconUrl || iconFailed.has(iconUrl)) {
|
|
5757
5896
|
fallback();
|
|
5758
5897
|
return wrap;
|
|
5759
5898
|
}
|
|
5760
5899
|
const img = doc.createElement("img");
|
|
5761
5900
|
img.alt = "";
|
|
5762
5901
|
img.crossOrigin = "anonymous";
|
|
5763
|
-
img.src =
|
|
5902
|
+
img.src = iconUrl;
|
|
5764
5903
|
img.style.cssText = "width:100%;height:100%;border-radius:50%;display:block;object-fit:cover;";
|
|
5765
5904
|
img.addEventListener(
|
|
5766
5905
|
"error",
|
|
5767
5906
|
() => {
|
|
5768
|
-
iconFailed.add(
|
|
5907
|
+
iconFailed.add(iconUrl);
|
|
5769
5908
|
fallback();
|
|
5770
5909
|
},
|
|
5771
5910
|
{ once: true }
|
|
@@ -12018,11 +12157,14 @@ var MenuBuilder = class {
|
|
|
12018
12157
|
}
|
|
12019
12158
|
};
|
|
12020
12159
|
var ObjectTree = class extends SidePanel {
|
|
12021
|
-
constructor(host) {
|
|
12160
|
+
constructor(host, iconFor) {
|
|
12022
12161
|
super(host, "Object tree", "vela-ot");
|
|
12162
|
+
this.iconFor = iconFor;
|
|
12023
12163
|
this.chart = null;
|
|
12024
12164
|
this.selectedDrawing = null;
|
|
12025
12165
|
this.symbolName = "";
|
|
12166
|
+
/** The raw (possibly venue-prefixed) symbol — what icon resolution routes on. */
|
|
12167
|
+
this.symbolRaw = "";
|
|
12026
12168
|
/** Drawing bundles — a view-side grouping, held for the panel's lifetime and never persisted.
|
|
12027
12169
|
* Kept per chart because a workspace points this one panel at whichever chart is active, and
|
|
12028
12170
|
* each chart's bundles have to survive the switch. */
|
|
@@ -12059,6 +12201,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
12059
12201
|
if (open2) this.refresh();
|
|
12060
12202
|
}
|
|
12061
12203
|
setSymbol(symbol) {
|
|
12204
|
+
this.symbolRaw = symbol;
|
|
12062
12205
|
this.symbolName = parseSymbol(symbol).ticker;
|
|
12063
12206
|
}
|
|
12064
12207
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
@@ -12259,7 +12402,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
12259
12402
|
const { chart } = pass;
|
|
12260
12403
|
if (row.kind === "price") {
|
|
12261
12404
|
const base = this.symbolName.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || this.symbolName;
|
|
12262
|
-
const icon2 = tickerIconEl(doc, base || "P", this.symbolName || "Price", "vela-ot-avatar");
|
|
12405
|
+
const icon2 = tickerIconEl(doc, base || "P", this.symbolName || "Price", "vela-ot-avatar", this.symbolRaw ? this.iconFor?.(this.symbolRaw) : void 0);
|
|
12263
12406
|
const el2 = this.row(icon2, row.label, row.visible, [
|
|
12264
12407
|
{
|
|
12265
12408
|
icon: row.visible ? "eye" : "eye-off",
|
|
@@ -13289,8 +13432,6 @@ var PanelDock = class {
|
|
|
13289
13432
|
for (const entry of this.entries) this.deps.chrome.setPanelActive(entry.id, entry.panel.open);
|
|
13290
13433
|
}
|
|
13291
13434
|
};
|
|
13292
|
-
|
|
13293
|
-
// src/widget/symbol-picker.ts
|
|
13294
13435
|
var TOP_TICKERS = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT", "DOGEUSDT", "ADAUSDT", "LINKUSDT"];
|
|
13295
13436
|
function parseQuery(raw, venues) {
|
|
13296
13437
|
const m = raw.match(/^\s*([^\s:]+)\s*[:\s]\s*(.*)$/);
|
|
@@ -13305,17 +13446,18 @@ function onlyOne(matches) {
|
|
|
13305
13446
|
return matches.length === 1 ? matches[0] : null;
|
|
13306
13447
|
}
|
|
13307
13448
|
var venueOf = (s) => s.prefix ?? s.provider;
|
|
13308
|
-
function filterSymbols(list, query, limit = 100) {
|
|
13449
|
+
function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
|
|
13309
13450
|
const venues = [...new Set(list.flatMap((s) => [venueOf(s)?.toLowerCase(), s.provider?.toLowerCase()]).filter((p) => !!p))];
|
|
13310
13451
|
const { scope, term } = parseQuery(query, venues);
|
|
13311
13452
|
const pool = scope ? list.filter((s) => venueOf(s)?.toLowerCase() === scope || s.provider?.toLowerCase() === scope) : list;
|
|
13312
13453
|
const q = term.toUpperCase();
|
|
13313
13454
|
if (!q) {
|
|
13314
13455
|
if (scope) return [...pool].sort((a, b) => a.ticker.localeCompare(b.ticker)).slice(0, limit);
|
|
13456
|
+
if (top === false) return pool.slice(0, limit);
|
|
13315
13457
|
const byTicker = new Map(pool.map((s) => [s.ticker.toUpperCase(), s]));
|
|
13316
|
-
const
|
|
13317
|
-
const rest = pool.filter((s) => !
|
|
13318
|
-
return [...
|
|
13458
|
+
const pinned = top.map((t) => byTicker.get(t)).filter((s) => s !== void 0);
|
|
13459
|
+
const rest = pool.filter((s) => !top.includes(s.ticker.toUpperCase()));
|
|
13460
|
+
return [...pinned, ...rest].slice(0, limit);
|
|
13319
13461
|
}
|
|
13320
13462
|
const qLower = term.toLowerCase();
|
|
13321
13463
|
const prefix = [];
|
|
@@ -13425,12 +13567,16 @@ var CSS8 = `
|
|
|
13425
13567
|
var PAGE = 100;
|
|
13426
13568
|
var SymbolPicker = class {
|
|
13427
13569
|
constructor(opts) {
|
|
13570
|
+
this.opts = opts;
|
|
13428
13571
|
this.source = () => [];
|
|
13429
13572
|
this.rows = [];
|
|
13430
13573
|
this.highlighted = 0;
|
|
13431
13574
|
this.seed = "";
|
|
13432
13575
|
this.activeTab = "All";
|
|
13433
13576
|
this.visible = PAGE;
|
|
13577
|
+
/** The ranked pool cache — `key` fingerprints the raw pool the ranking ran on. */
|
|
13578
|
+
this.ranked = null;
|
|
13579
|
+
this.ranking = false;
|
|
13434
13580
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
13435
13581
|
injectStyles(STYLE_ID7, CSS8, doc);
|
|
13436
13582
|
this.input = doc.createElement("input");
|
|
@@ -13528,10 +13674,19 @@ var SymbolPicker = class {
|
|
|
13528
13674
|
} else delete el.dataset.highlighted;
|
|
13529
13675
|
});
|
|
13530
13676
|
}
|
|
13677
|
+
/** The picker's pool: the source, shaped by the registered symbol ranking. Cached —
|
|
13678
|
+
* the hook runs when the pool CHANGES (an index lands or refreshes), never per
|
|
13679
|
+
* keystroke; an async hook resolves onto the next repaint (stale-while-revalidate
|
|
13680
|
+
* in between, the raw pool before the first resolve). */
|
|
13681
|
+
pool() {
|
|
13682
|
+
const raw = this.source();
|
|
13683
|
+
return raw;
|
|
13684
|
+
}
|
|
13531
13685
|
computeRows() {
|
|
13532
13686
|
const TAB_TYPES = { Crypto: ["crypto"], Stocks: ["stock"], ETFs: ["etf"], Forex: ["forex"], Commodities: ["commodity"] };
|
|
13533
|
-
const
|
|
13534
|
-
|
|
13687
|
+
const all = this.pool();
|
|
13688
|
+
const pool = this.activeTab === "All" ? all : all.filter((s) => TAB_TYPES[this.activeTab]?.includes((s.type ?? "").toLowerCase()) || this.activeTab === "Crypto" && (s.type ?? "").toLowerCase() === "futures");
|
|
13689
|
+
return filterSymbols(pool, this.input.value, this.visible, TOP_TICKERS);
|
|
13535
13690
|
}
|
|
13536
13691
|
refresh() {
|
|
13537
13692
|
const doc = this.list.ownerDocument;
|
|
@@ -13562,7 +13717,7 @@ var SymbolPicker = class {
|
|
|
13562
13717
|
row.dataset.ticker = s.ticker;
|
|
13563
13718
|
const venue = s.prefix ?? s.provider;
|
|
13564
13719
|
if (venue) row.dataset.venue = venue;
|
|
13565
|
-
const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
|
|
13720
|
+
const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar", this.opts.iconFor?.(s));
|
|
13566
13721
|
const main = doc.createElement("span");
|
|
13567
13722
|
main.className = "vela-sp-main";
|
|
13568
13723
|
const t = doc.createElement("span");
|
|
@@ -14243,7 +14398,8 @@ var MobileBar = class {
|
|
|
14243
14398
|
if (!ctx) return;
|
|
14244
14399
|
const doc = this.el.ownerDocument;
|
|
14245
14400
|
this.actionsHost.replaceChildren();
|
|
14246
|
-
|
|
14401
|
+
const builtin = new Set(TOPBAR_BUILTIN_IDS);
|
|
14402
|
+
for (const action of widgetActions("topbar", ctx).filter((a) => a.align === "left" && !builtin.has(a.id))) {
|
|
14247
14403
|
const b = doc.createElement("button");
|
|
14248
14404
|
b.className = "vela-mb-item";
|
|
14249
14405
|
b.setAttribute("aria-label", action.label);
|
|
@@ -14772,10 +14928,10 @@ var MoreDrawer = class {
|
|
|
14772
14928
|
});
|
|
14773
14929
|
actions.appendChild(b);
|
|
14774
14930
|
};
|
|
14775
|
-
action("undo", "Undo", this.opts.canUndo(), this.opts.onUndo);
|
|
14776
|
-
action("redo", "Redo", this.opts.canRedo(), this.opts.onRedo);
|
|
14777
|
-
action("camera", "Screenshot", true, this.opts.onScreenshot);
|
|
14778
|
-
this.drawer.body.appendChild(actions);
|
|
14931
|
+
if (this.opts.onUndo) action("undo", "Undo", this.opts.canUndo(), this.opts.onUndo);
|
|
14932
|
+
if (this.opts.onRedo) action("redo", "Redo", this.opts.canRedo(), this.opts.onRedo);
|
|
14933
|
+
if (this.opts.onScreenshot) action("camera", "Screenshot", true, this.opts.onScreenshot);
|
|
14934
|
+
if (actions.childElementCount > 0) this.drawer.body.appendChild(actions);
|
|
14779
14935
|
const list = doc.createElement("div");
|
|
14780
14936
|
list.className = "vela-md-list";
|
|
14781
14937
|
const current = this.opts.priceStyles().find((s) => s.id === this.opts.priceStyle());
|
|
@@ -14796,8 +14952,10 @@ var MoreDrawer = class {
|
|
|
14796
14952
|
})
|
|
14797
14953
|
);
|
|
14798
14954
|
}
|
|
14799
|
-
|
|
14800
|
-
|
|
14955
|
+
if (this.opts.alerts) {
|
|
14956
|
+
const alertCount = this.opts.alerts().length;
|
|
14957
|
+
list.appendChild(this.row(doc, "Alerts", { icon: "bell", value: alertCount > 0 ? String(alertCount) : void 0, chevron: true, onClick: () => this.show("alerts") }));
|
|
14958
|
+
}
|
|
14801
14959
|
for (const act of this.opts.actions()) {
|
|
14802
14960
|
list.appendChild(
|
|
14803
14961
|
this.row(doc, act.label, {
|
|
@@ -14878,7 +15036,7 @@ var MoreDrawer = class {
|
|
|
14878
15036
|
this.drawer.body.appendChild(list);
|
|
14879
15037
|
}
|
|
14880
15038
|
renderAlerts(doc) {
|
|
14881
|
-
const alerts = this.opts.alerts();
|
|
15039
|
+
const alerts = this.opts.alerts?.() ?? [];
|
|
14882
15040
|
if (alerts.length === 0) {
|
|
14883
15041
|
const empty = doc.createElement("div");
|
|
14884
15042
|
empty.className = "vela-md-empty";
|
|
@@ -19277,6 +19435,12 @@ var DataControl = class {
|
|
|
19277
19435
|
symbols(provider) {
|
|
19278
19436
|
return this.registry?.symbols(provider) ?? [];
|
|
19279
19437
|
}
|
|
19438
|
+
/** The icon URL for `symbol` — its owning provider's `resolveSymbolIcon`, routed
|
|
19439
|
+
* through resolution. Undefined while unresolvable, when the provider declares no
|
|
19440
|
+
* resolver, or on a custom `deps.dataFeed` — the shells then show initials. */
|
|
19441
|
+
symbolIcon(symbol) {
|
|
19442
|
+
return this.registry?.symbolIcon(symbol);
|
|
19443
|
+
}
|
|
19280
19444
|
/** Per-symbol metadata (Pine `syminfo.*`), resolved through the owning provider. */
|
|
19281
19445
|
symbolInfo(symbol) {
|
|
19282
19446
|
return this.registry?.symbolInfoFor(symbol) ?? Promise.resolve(void 0);
|
|
@@ -35625,8 +35789,9 @@ function statuslineInkOf(renderer, priceStyle) {
|
|
|
35625
35789
|
}
|
|
35626
35790
|
}
|
|
35627
35791
|
var Statusline = class {
|
|
35628
|
-
constructor(host, symbol) {
|
|
35792
|
+
constructor(host, symbol, iconFor) {
|
|
35629
35793
|
this.host = host;
|
|
35794
|
+
this.iconFor = iconFor;
|
|
35630
35795
|
this.parts = { name: true, market: true, ohlc: true, change: true };
|
|
35631
35796
|
this.lastBar = null;
|
|
35632
35797
|
this.hoverBar = null;
|
|
@@ -35650,7 +35815,7 @@ var Statusline = class {
|
|
|
35650
35815
|
this.el.className = "vela-statusline";
|
|
35651
35816
|
this.el.dataset.velaScreenshot = "1";
|
|
35652
35817
|
const ticker = parseSymbol(symbol).ticker;
|
|
35653
|
-
this.avatarEl = tickerIconEl(doc, baseOfTicker(ticker), ticker, "vela-sl-avatar");
|
|
35818
|
+
this.avatarEl = tickerIconEl(doc, baseOfTicker(ticker), ticker, "vela-sl-avatar", this.iconFor?.(symbol));
|
|
35654
35819
|
this.symbolEl = doc.createElement("span");
|
|
35655
35820
|
this.symbolEl.className = "vela-sl-symbol";
|
|
35656
35821
|
this.symbolEl.textContent = ticker;
|
|
@@ -35671,7 +35836,7 @@ var Statusline = class {
|
|
|
35671
35836
|
setSymbol(symbol) {
|
|
35672
35837
|
const ticker = parseSymbol(symbol).ticker;
|
|
35673
35838
|
this.symbolEl.textContent = ticker;
|
|
35674
|
-
const fresh = tickerIconEl(this.el.ownerDocument, baseOfTicker(ticker), ticker, "vela-sl-avatar");
|
|
35839
|
+
const fresh = tickerIconEl(this.el.ownerDocument, baseOfTicker(ticker), ticker, "vela-sl-avatar", this.iconFor?.(symbol));
|
|
35675
35840
|
this.avatarEl.replaceWith(fresh);
|
|
35676
35841
|
this.avatarEl = fresh;
|
|
35677
35842
|
this.fit();
|
|
@@ -36419,12 +36584,15 @@ var ChartCell = class {
|
|
|
36419
36584
|
this.watermarkOn = seed.watermark ?? deps.watermark;
|
|
36420
36585
|
this.watermark = deps.watermark ? new Watermark(this.host, symbol ?? "", seed.timeframe ?? "60") : null;
|
|
36421
36586
|
if (!this.watermarkOn) this.watermark?.setVisible(false);
|
|
36422
|
-
this.statusline = deps.statusline ? new Statusline(this.host, symbol ?? "") : null;
|
|
36587
|
+
this.statusline = deps.statusline ? new Statusline(this.host, symbol ?? "", (sym) => this.inner?.data.symbolIcon(sym)) : null;
|
|
36423
36588
|
this.statusline?.setMeta(seed.timeframe ?? "60", this.state.provider ?? "");
|
|
36424
36589
|
this.statusline?.onChart(this.inner);
|
|
36425
36590
|
this.marketStatus = this.statusline ? new MarketStatusTracker((s) => this.statusline?.setMarketStatus(s)) : null;
|
|
36426
36591
|
void this.inner.data.ready().then(() => {
|
|
36427
|
-
if (this.inner && this.state.symbol)
|
|
36592
|
+
if (this.inner && this.state.symbol) {
|
|
36593
|
+
this.statusline?.setSymbol(this.state.symbol);
|
|
36594
|
+
this.statusline?.setMeta(this.state.timeframe ?? "60", this.inner.data.displayPrefix(this.state.symbol) ?? this.state.provider ?? "");
|
|
36595
|
+
}
|
|
36428
36596
|
this.refreshSessionAvailable();
|
|
36429
36597
|
if (this.inner && this.state.symbol) this.marketStatus?.track(this.inner.data, this.state.symbol);
|
|
36430
36598
|
});
|
|
@@ -37513,6 +37681,9 @@ var VelaWorkspace = class {
|
|
|
37513
37681
|
const optLayout = opts.layout === false || opts.layout === void 0 ? "4" : opts.layout;
|
|
37514
37682
|
this.def = this.resolveLayout(this.monoLayout ? "1" : boot?.layout && ensureLayout(boot.layout) ? boot.layout : optLayout);
|
|
37515
37683
|
this.alertCap = Math.max(1, opts.alertCap ?? ALERT_CAP);
|
|
37684
|
+
this.topbarComp = resolveTopbarComposition(opts.topbar);
|
|
37685
|
+
this.indicatorsOverride = topbarActionOverride("indicators");
|
|
37686
|
+
this.screenshotOverride = topbarActionOverride("screenshot");
|
|
37516
37687
|
if (boot?.trackSizes) for (const [id, ts] of Object.entries(boot.trackSizes)) this.trackSizes.set(id, ts);
|
|
37517
37688
|
if (boot?.ext) this.extState = { ...boot.ext };
|
|
37518
37689
|
if (boot?.charts) for (const { id, ...cs } of boot.charts) this.pool.set(id, cs);
|
|
@@ -37529,6 +37700,8 @@ var VelaWorkspace = class {
|
|
|
37529
37700
|
});
|
|
37530
37701
|
this.symbolPicker = new SymbolPicker({
|
|
37531
37702
|
host: this.root,
|
|
37703
|
+
// Row icons come from each descriptor's OWNING provider (resolveSymbolIcon).
|
|
37704
|
+
iconFor: (d) => this.feed.symbolIconOf(d),
|
|
37532
37705
|
onSelect: (ticker) => this.active.setSymbol(ticker),
|
|
37533
37706
|
onOpenChange: (open2) => {
|
|
37534
37707
|
if (open2) for (const cell of this.cells()) cell.chart.renderer.closeDialogs();
|
|
@@ -37536,7 +37709,7 @@ var VelaWorkspace = class {
|
|
|
37536
37709
|
}
|
|
37537
37710
|
});
|
|
37538
37711
|
this.symbolPicker.setSource(() => this.feed.symbols());
|
|
37539
|
-
this.indicatorPicker = opts.indicatorPicker !== false ? new IndicatorPicker({
|
|
37712
|
+
this.indicatorPicker = opts.indicatorPicker !== false && !this.indicatorsOverride && topbarHas(this.topbarComp, "indicators") ? new IndicatorPicker({
|
|
37540
37713
|
host: this.root,
|
|
37541
37714
|
library: () => this.active.libraryRows(),
|
|
37542
37715
|
onChart: () => this.active.onChartRows(),
|
|
@@ -37555,6 +37728,9 @@ var VelaWorkspace = class {
|
|
|
37555
37728
|
});
|
|
37556
37729
|
this.topbar = new Topbar(this.root, {
|
|
37557
37730
|
symbol: "",
|
|
37731
|
+
// RAW option, not the resolved lists — the bar distinguishes a host-declared
|
|
37732
|
+
// side (list is law) from a default one (an override's `order` may flow).
|
|
37733
|
+
composition: opts.topbar,
|
|
37558
37734
|
onSymbolClick: () => this.symbolPicker.open(),
|
|
37559
37735
|
...picker ? { onIndicatorsClick: () => picker.open() } : {},
|
|
37560
37736
|
onUndoClick: () => this.active.history.undo(),
|
|
@@ -37612,7 +37788,7 @@ var VelaWorkspace = class {
|
|
|
37612
37788
|
context: () => this.context(),
|
|
37613
37789
|
changed: () => this.markStateDirty()
|
|
37614
37790
|
});
|
|
37615
|
-
this.objectTree = new ObjectTree(main);
|
|
37791
|
+
this.objectTree = new ObjectTree(main, (sym) => this.feed.symbolIcon(sym));
|
|
37616
37792
|
this.dataWindow = new DataWindow(main);
|
|
37617
37793
|
this.dock.addBuiltIn({ id: "dataWindow", title: "Data window", icon: "datawindow", order: 10, panel: this.dataWindow, onChart: (c) => this.dataWindow.onChart(c) });
|
|
37618
37794
|
this.dock.addBuiltIn({ id: "objects", title: "Object tree", icon: "objects", order: 20, panel: this.objectTree, onChart: (c) => this.objectTree.onChart(c) });
|
|
@@ -37684,7 +37860,9 @@ var VelaWorkspace = class {
|
|
|
37684
37860
|
timeframe: "60",
|
|
37685
37861
|
onSymbolClick: () => this.symbolPicker.open(),
|
|
37686
37862
|
onTimeframeClick: () => this.openTimeframeDrawer(),
|
|
37687
|
-
|
|
37863
|
+
// Same visibility truth as the desktop bar: composition-hidden
|
|
37864
|
+
// indicators lose their mobile stop too; an override takes it over.
|
|
37865
|
+
...topbarHas(this.topbarComp, "indicators") && (picker || this.indicatorsOverride) ? { onIndicatorsClick: this.indicatorsOverride ? () => this.runOverride(this.indicatorsOverride) : () => picker.open() } : {},
|
|
37688
37866
|
getContext: () => this.context(),
|
|
37689
37867
|
...this.drawingsEnabled ? { onDrawingsClick: () => this.openDrawingsDrawer() } : {},
|
|
37690
37868
|
onMoreClick: () => this.openMoreDrawer(),
|
|
@@ -38580,27 +38758,32 @@ var VelaWorkspace = class {
|
|
|
38580
38758
|
this.drawingsDrawer.open();
|
|
38581
38759
|
}
|
|
38582
38760
|
openMoreDrawer() {
|
|
38761
|
+
const has = (id) => topbarHas(this.topbarComp, id);
|
|
38583
38762
|
this.moreDrawer ?? (this.moreDrawer = new MoreDrawer({
|
|
38584
38763
|
host: this.root,
|
|
38585
|
-
onUndo: () => this.active.history.undo(),
|
|
38586
|
-
|
|
38587
|
-
onScreenshot: () => this.active.downloadScreenshot(),
|
|
38764
|
+
...has("undo-redo") ? { onUndo: () => this.active.history.undo(), onRedo: () => this.active.history.redo() } : {},
|
|
38765
|
+
...has("screenshot") ? { onScreenshot: this.screenshotOverride ? () => this.runOverride(this.screenshotOverride) : () => this.active.downloadScreenshot() } : {},
|
|
38588
38766
|
canUndo: () => this.active.history.canUndo,
|
|
38589
38767
|
canRedo: () => this.active.history.canRedo,
|
|
38590
38768
|
priceStyles: () => priceStyleIds().map((id) => ({ id, label: priceStyleLabel(id), icon: priceStyleIcon(id) })),
|
|
38591
38769
|
priceStyle: () => this.active.priceStyle,
|
|
38592
38770
|
onPriceStyle: (id) => this.active.setPriceStyle(id),
|
|
38593
|
-
panels: () => [...this.dock.list()],
|
|
38771
|
+
panels: () => has("panels") ? [...this.dock.list()] : [],
|
|
38594
38772
|
onTogglePanel: (id) => this.dock.toggle(id),
|
|
38595
|
-
alerts: () => this.alerts.map((a) => ({ title: `${a.source} \xB7 ${a.title}`, message: a.message, time: a.time })),
|
|
38773
|
+
...has("alerts") ? { alerts: () => this.alerts.map((a) => ({ title: `${a.source} \xB7 ${a.title}`, message: a.message, time: a.time })) } : {},
|
|
38596
38774
|
// Left-aligned actions have their own bottom-bar stop — only the rest
|
|
38597
|
-
// lands in the drawer, or every left action would appear twice.
|
|
38598
|
-
actions
|
|
38775
|
+
// lands in the drawer, or every left action would appear twice. Built-in-id
|
|
38776
|
+
// actions are slot OVERRIDES: they reach the drawer through the slot's own
|
|
38777
|
+
// routed button (screenshot) or stop (indicators), never as an extra row.
|
|
38778
|
+
actions: () => {
|
|
38779
|
+
const builtin = new Set(TOPBAR_BUILTIN_IDS);
|
|
38780
|
+
return widgetActions("topbar", this.context()).filter((a) => a.align !== "left" && !builtin.has(a.id)).map((a) => ({ label: a.label, icon: a.icon, run: () => a.run(this.context()) }));
|
|
38781
|
+
},
|
|
38599
38782
|
// The desktop layout dropdown's whole surface — the grid canvas, the
|
|
38600
38783
|
// non-canvas presets and the sync switches — relocated into the kebab
|
|
38601
38784
|
// drawer (the topbar is hidden on mobile). Same reads as the topbar block;
|
|
38602
|
-
// single-chart mode
|
|
38603
|
-
layout: this.monoLayout ? void 0 : {
|
|
38785
|
+
// single-chart mode and a composition without 'layout' omit it here too.
|
|
38786
|
+
layout: this.monoLayout || !has("layout") ? void 0 : {
|
|
38604
38787
|
shape: () => layoutShape(this.def),
|
|
38605
38788
|
presets: () => layouts().filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label, checked: l.id === this.def.id })),
|
|
38606
38789
|
onSelectGrid: (rows, cols) => this.setLayout(layoutForGrid(rows, cols)),
|
|
@@ -38667,9 +38850,23 @@ var VelaWorkspace = class {
|
|
|
38667
38850
|
}
|
|
38668
38851
|
}
|
|
38669
38852
|
}
|
|
38853
|
+
/** Invoke a slot override the way its button would: fresh context, `when` respected. */
|
|
38854
|
+
runOverride(action) {
|
|
38855
|
+
const ctx = this.context();
|
|
38856
|
+
if (!action.when || action.when(ctx)) action.run(ctx);
|
|
38857
|
+
}
|
|
38670
38858
|
/** The default shortcut set — every binding acts on the ACTIVE cell. */
|
|
38671
38859
|
registerDefaultKeys() {
|
|
38672
|
-
this.
|
|
38860
|
+
if (topbarHas(this.topbarComp, "screenshot")) {
|
|
38861
|
+
const ov = this.screenshotOverride;
|
|
38862
|
+
this.keymap.register({
|
|
38863
|
+
id: "chart.screenshot",
|
|
38864
|
+
keys: "mod+alt+s",
|
|
38865
|
+
label: ov ? ov.label : "Download a chart screenshot",
|
|
38866
|
+
category: "Chart",
|
|
38867
|
+
run: ov ? () => this.runOverride(ov) : () => this.active.downloadScreenshot()
|
|
38868
|
+
});
|
|
38869
|
+
}
|
|
38673
38870
|
this.keymap.register({ id: "chart.reset-view", keys: "alt+r", label: "Reset view (all history)", category: "Chart", run: () => this.active.chart.setVisibleRangePreset("ALL") });
|
|
38674
38871
|
this.keymap.register({ id: "chart.toggle-log", keys: "alt+l", label: "Toggle logarithmic scale", category: "Chart", run: () => this.active.chart.renderer.set("logScale", !this.active.chart.renderer.get("logScale")) });
|
|
38675
38872
|
this.keymap.register({
|
|
@@ -38709,7 +38906,10 @@ var VelaWorkspace = class {
|
|
|
38709
38906
|
this.keymap.register({ id: "view.zoom-out", keys: "mod+arrowdown", label: "Zoom out", category: "Chart", run: () => this.glider.zoom(ZOOM_OUT) });
|
|
38710
38907
|
this.keymap.register({ id: "view.pan-left", keys: "mod+arrowleft", label: "Pan toward history", category: "Chart", run: () => this.active.chart.panBy(-PAN_FAST) });
|
|
38711
38908
|
this.keymap.register({ id: "view.pan-right", keys: "mod+arrowright", label: "Pan toward now", category: "Chart", run: () => this.active.chart.panBy(PAN_FAST) });
|
|
38712
|
-
|
|
38909
|
+
const indOv = this.indicatorsOverride;
|
|
38910
|
+
if (indOv && topbarHas(this.topbarComp, "indicators")) {
|
|
38911
|
+
this.keymap.register({ id: "indicators.open", keys: "/", label: indOv.label, category: "Indicators", run: () => this.runOverride(indOv) });
|
|
38912
|
+
} else if (this.indicatorPicker) {
|
|
38713
38913
|
this.keymap.register({ id: "indicators.open", keys: "/", label: "Open the indicator picker", category: "Indicators", run: () => this.indicatorPicker?.open() });
|
|
38714
38914
|
}
|
|
38715
38915
|
this.keymap.register({
|