@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.
Files changed (43) hide show
  1. package/dist/{DataProvider-JJq7M2it.d.ts → DataProvider-BSLlBpB9.d.ts} +10 -0
  2. package/dist/{DataProvider-DDUYw2qP.d.cts → DataProvider-BsQM2WNH.d.cts} +10 -0
  3. package/dist/{bottombar-COnL2Sk4.d.ts → bottombar-Br4rzx_R.d.ts} +52 -6
  4. package/dist/{bottombar-DeDXbF_K.d.cts → bottombar-Div5zibZ.d.cts} +52 -6
  5. package/dist/{chunk-JQFO2WMU.js → chunk-6WFKTAT4.js} +37 -1
  6. package/dist/{chunk-OEUGUXL7.js → chunk-L3I2CCYO.js} +49 -1
  7. package/dist/chunk-W4EJWLEO.js +12 -0
  8. package/dist/{chunk-TMXK2SJR.js → chunk-WVP4XV5A.js} +221 -65
  9. package/dist/{contributions-DNMqrAuw.d.cts → contributions-Hv4_cOJo.d.cts} +46 -3
  10. package/dist/{contributions-BqCIsbaP.d.ts → contributions-Z12BrWCy.d.ts} +46 -3
  11. package/dist/index.cjs +64 -0
  12. package/dist/index.d.cts +8 -4
  13. package/dist/index.d.ts +8 -4
  14. package/dist/index.js +2 -2
  15. package/dist/{plugin-WnvNNJOp.d.ts → plugin-9koBjc5H.d.ts} +2 -2
  16. package/dist/{plugin-CN8U2__Q.d.cts → plugin-BXhDHTYn.d.cts} +2 -2
  17. package/dist/plugin.cjs +28 -0
  18. package/dist/plugin.d.cts +3 -3
  19. package/dist/plugin.d.ts +3 -3
  20. package/dist/plugin.js +1 -1
  21. package/dist/providers/binance.cjs +16 -0
  22. package/dist/providers/binance.d.cts +4 -1
  23. package/dist/providers/binance.d.ts +4 -1
  24. package/dist/providers/binance.js +7 -0
  25. package/dist/providers/coinbase.cjs +16 -0
  26. package/dist/providers/coinbase.d.cts +4 -1
  27. package/dist/providers/coinbase.d.ts +4 -1
  28. package/dist/providers/coinbase.js +7 -0
  29. package/dist/providers/hyperliquid.cjs +16 -0
  30. package/dist/providers/hyperliquid.d.cts +4 -1
  31. package/dist/providers/hyperliquid.d.ts +4 -1
  32. package/dist/providers/hyperliquid.js +7 -0
  33. package/dist/vela.global.js +90 -0
  34. package/dist/vela.global.min.js +42 -42
  35. package/dist/widget.cjs +319 -63
  36. package/dist/widget.d.cts +52 -10
  37. package/dist/widget.d.ts +52 -10
  38. package/dist/widget.js +5 -4
  39. package/dist/workspace.cjs +265 -65
  40. package/dist/workspace.d.cts +13 -4
  41. package/dist/workspace.d.ts +13 -4
  42. package/dist/workspace.js +4 -3
  43. package/package.json +1 -1
package/dist/widget.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,33 @@ 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
+ function pinnedTopbarActionIds(comp) {
4610
+ const builtin = new Set(TOPBAR_BUILTIN_IDS);
4611
+ return [...comp.left, ...comp.right].filter((id) => !builtin.has(id));
4612
+ }
4613
+
4557
4614
  // src/widget/contributions.ts
4558
4615
  var registry3 = /* @__PURE__ */ new Map();
4559
4616
  var attachments = /* @__PURE__ */ new Map();
@@ -4583,7 +4640,18 @@ function unregisterSidePanel(id) {
4583
4640
  function sidePanels() {
4584
4641
  return [...panels.values()].sort((a, b) => (a.order ?? DEFAULT_PANEL_ORDER) - (b.order ?? DEFAULT_PANEL_ORDER));
4585
4642
  }
4643
+ var OVERRIDABLE_TOPBAR_IDS = ["indicators", "screenshot"];
4644
+ var overridableSet = new Set(OVERRIDABLE_TOPBAR_IDS);
4645
+ var builtinTopbarSet = new Set(TOPBAR_BUILTIN_IDS);
4646
+ function topbarActionOverride(id) {
4647
+ if (!overridableSet.has(id)) return void 0;
4648
+ return registry3.get(id);
4649
+ }
4586
4650
  function registerWidgetAction(desc) {
4651
+ if (builtinTopbarSet.has(desc.id) && !overridableSet.has(desc.id)) {
4652
+ console.warn(`[vela] widget action "${desc.id}": this built-in topbar slot is a stateful control and cannot be overridden \u2014 ignored. Overridable slots: ${OVERRIDABLE_TOPBAR_IDS.join(", ")}.`);
4653
+ return () => void 0;
4654
+ }
4587
4655
  registry3.set(desc.id, desc);
4588
4656
  return () => {
4589
4657
  if (registry3.get(desc.id) === desc) registry3.delete(desc.id);
@@ -4622,6 +4690,16 @@ function unregisterStatePersistence(key) {
4622
4690
  function statePersistenceHandlers(scope) {
4623
4691
  return [...stateHandlers.values()].filter((h) => h.scope === scope);
4624
4692
  }
4693
+ var symbolRankingHook;
4694
+ function registerSymbolRanking(hook) {
4695
+ symbolRankingHook = hook;
4696
+ return () => {
4697
+ if (symbolRankingHook === hook) symbolRankingHook = void 0;
4698
+ };
4699
+ }
4700
+ function symbolRanking() {
4701
+ return symbolRankingHook;
4702
+ }
4625
4703
  var defaultEngines = /* @__PURE__ */ new Map();
4626
4704
  function resolveEngines(overrides) {
4627
4705
  return { ...Object.fromEntries(defaultEngines), ...overrides };
@@ -5104,9 +5182,14 @@ var CSS3 = `
5104
5182
  align-items: center;
5105
5183
  justify-content: center;
5106
5184
  }
5107
- .vela-widget-actions { margin-left: auto; display: inline-flex; gap: var(--vela-space-1); }
5185
+ /* The right side of the bar \u2014 whatever the composition puts there rides this one
5186
+ auto-margin push (the flow-actions host used to carry it; composition can omit it). */
5187
+ .vela-topbar-right { margin-left: auto; display: inline-flex; align-items: center; gap: var(--vela-space-1); }
5188
+ .vela-widget-actions { display: inline-flex; gap: var(--vela-space-1); }
5108
5189
  /* Left-aligned contributed actions \u2014 the primary-chrome cluster after the dropdowns. */
5109
5190
  .vela-widget-actions-left { display: inline-flex; align-items: center; gap: var(--vela-space-1); }
5191
+ /* One PINNED contributed action's slot (a composition entry naming the action's id). */
5192
+ .vela-widget-action-pin { display: inline-flex; align-items: center; }
5110
5193
  /* The side-panel toggles, one per docked panel \u2014 a group so the dock can rebuild them
5111
5194
  without disturbing the tools around it. */
5112
5195
  .vela-widget-panels { display: inline-flex; align-items: center; gap: var(--vela-space-1); }
@@ -5165,6 +5248,12 @@ var Topbar = class {
5165
5248
  this.tooltips = [];
5166
5249
  this.panelBtns = /* @__PURE__ */ new Map();
5167
5250
  this.panelTooltips = [];
5251
+ /** Pinned contributed-action slots, by action id (composition entries that name one,
5252
+ * plus built-in slots taken over by an override). */
5253
+ this.pinned = /* @__PURE__ */ new Map();
5254
+ /** Overrides that LEFT their native slot (default side + a declared `order`) — they
5255
+ * render through the flow cluster like ordinary actions. */
5256
+ this.flowingOverrides = /* @__PURE__ */ new Set();
5168
5257
  this.onHairlineSync = () => {
5169
5258
  if (this.hairlineRaf) return;
5170
5259
  const win = this.el.ownerDocument.defaultView;
@@ -5179,6 +5268,8 @@ var Topbar = class {
5179
5268
  this.host = host;
5180
5269
  this.timeframe = opts.timeframe;
5181
5270
  this.priceStyle = opts.priceStyle;
5271
+ this.comp = resolveTopbarComposition(opts.composition);
5272
+ const vis = (id) => topbarHas(this.comp, id);
5182
5273
  const doc = host.ownerDocument;
5183
5274
  injectStyles(STYLE_ID2, CSS3, doc);
5184
5275
  this.el = doc.createElement("div");
@@ -5201,7 +5292,7 @@ var Topbar = class {
5201
5292
  this.styleButton.className = "vela-widget-style";
5202
5293
  this.renderStyleButton(doc);
5203
5294
  let indicatorsBtn = null;
5204
- if (opts.onIndicatorsClick) {
5295
+ if (opts.onIndicatorsClick && !topbarActionOverride("indicators")) {
5205
5296
  indicatorsBtn = doc.createElement("button");
5206
5297
  indicatorsBtn.className = "vela-widget-indicators";
5207
5298
  indicatorsBtn.append(iconEl("indicators", doc), doc.createTextNode("Indicators"));
@@ -5214,18 +5305,23 @@ var Topbar = class {
5214
5305
  this.panelsHost = doc.createElement("span");
5215
5306
  this.panelsHost.className = "vela-widget-panels";
5216
5307
  const tool = (cls, icon2, tip, onClick) => this.toolButton(cls, icon2, tip, onClick, this.tooltips);
5217
- this.undoBtn = tool("vela-widget-undo", "undo", "Undo", opts.onUndoClick);
5218
- this.redoBtn = tool("vela-widget-redo", "redo", "Redo", opts.onRedoClick);
5308
+ if (vis("undo-redo")) {
5309
+ this.undoBtn = tool("vela-widget-undo", "undo", "Undo", opts.onUndoClick);
5310
+ this.redoBtn = tool("vela-widget-redo", "redo", "Redo", opts.onRedoClick);
5311
+ } else {
5312
+ this.undoBtn = doc.createElement("button");
5313
+ this.redoBtn = doc.createElement("button");
5314
+ }
5219
5315
  this.setHistoryState(false, false);
5220
- const screenshotBtn = tool("vela-widget-screenshot", "camera", "Download screenshot", opts.onScreenshotClick);
5221
- this.alertsBtn = tool("vela-widget-alerts", "bell", "Alerts");
5316
+ const screenshotBtn = vis("screenshot") && !topbarActionOverride("screenshot") ? tool("vela-widget-screenshot", "camera", "Download screenshot", opts.onScreenshotClick) : null;
5317
+ this.alertsBtn = vis("alerts") ? tool("vela-widget-alerts", "bell", "Alerts") : doc.createElement("button");
5222
5318
  this.alertsBtn.style.position = "relative";
5223
5319
  if (opts.onAlertsClick) this.alertsBtn.addEventListener("click", () => opts.onAlertsClick(this.alertsBtn));
5224
5320
  this.alertsBadge = doc.createElement("span");
5225
5321
  this.alertsBadge.className = "vela-alerts-badge";
5226
5322
  this.alertsBadge.style.display = "none";
5227
5323
  this.alertsBtn.appendChild(this.alertsBadge);
5228
- if (opts.layout) {
5324
+ if (opts.layout && vis("layout")) {
5229
5325
  this.layoutId = opts.layout.current;
5230
5326
  this.layoutButton = doc.createElement("button");
5231
5327
  this.layoutButton.className = "vela-widget-style";
@@ -5236,12 +5332,65 @@ var Topbar = class {
5236
5332
  d.className = "vela-sep";
5237
5333
  return d;
5238
5334
  };
5239
- const leading = [this.symbolEl, sep(), tfGroup, sep(), this.styleButton, sep()];
5240
- if (this.layoutButton) leading.push(this.layoutButton, sep());
5241
- if (indicatorsBtn) leading.push(indicatorsBtn, sep());
5242
5335
  this.leftActionsSep = sep();
5243
5336
  this.leftActionsSep.hidden = true;
5244
- this.el.append(...leading, this.leftActionsHost, this.leftActionsSep, this.undoBtn, this.redoBtn, this.actionsHost, this.alertsBtn, this.panelsHost, screenshotBtn);
5337
+ const primaries = /* @__PURE__ */ new Set(["symbol", "timeframes", "style", "layout", "indicators"]);
5338
+ const pinSlot = (id, left) => {
5339
+ const slot = doc.createElement("span");
5340
+ slot.className = "vela-widget-action-pin";
5341
+ this.pinned.set(id, { host: slot, left });
5342
+ return [slot];
5343
+ };
5344
+ const overridden = (id, left) => {
5345
+ const ov = topbarActionOverride(id);
5346
+ if (!ov) return null;
5347
+ const sideDeclared = left ? opts.composition?.left != null : opts.composition?.right != null;
5348
+ if (!sideDeclared && ov.order !== void 0) {
5349
+ this.flowingOverrides.add(id);
5350
+ return [];
5351
+ }
5352
+ return pinSlot(id, left);
5353
+ };
5354
+ const elementsFor = (id, left) => {
5355
+ switch (id) {
5356
+ case "symbol":
5357
+ return [this.symbolEl];
5358
+ case "timeframes":
5359
+ return [tfGroup];
5360
+ case "style":
5361
+ return [this.styleButton];
5362
+ case "layout":
5363
+ return this.layoutButton ? [this.layoutButton] : [];
5364
+ case "indicators":
5365
+ return overridden(id, left) ?? (indicatorsBtn ? [indicatorsBtn] : []);
5366
+ case "actions":
5367
+ return left ? [this.leftActionsHost, this.leftActionsSep] : [this.actionsHost];
5368
+ case "undo-redo":
5369
+ return [this.undoBtn, this.redoBtn];
5370
+ case "alerts":
5371
+ return [this.alertsBtn];
5372
+ case "panels":
5373
+ return [this.panelsHost];
5374
+ case "screenshot":
5375
+ return overridden(id, left) ?? (screenshotBtn ? [screenshotBtn] : []);
5376
+ default:
5377
+ return pinSlot(id, left);
5378
+ }
5379
+ };
5380
+ const sideEls = (list, left) => {
5381
+ const out = [];
5382
+ for (const [i, id] of list.entries()) {
5383
+ const els = elementsFor(id, left);
5384
+ if (els.length === 0) continue;
5385
+ out.push(...els);
5386
+ if (primaries.has(id) && i < list.length - 1) out.push(sep());
5387
+ }
5388
+ return out;
5389
+ };
5390
+ const right = doc.createElement("span");
5391
+ right.className = "vela-topbar-right";
5392
+ right.append(...sideEls(this.comp.right, false));
5393
+ this.el.append(...sideEls(this.comp.left, true), right);
5245
5394
  host.appendChild(this.el);
5246
5395
  this.renderTfChips();
5247
5396
  this.onHairlineSync();
@@ -5358,14 +5507,24 @@ var Topbar = class {
5358
5507
  else this.styleButton.appendChild(doc.createTextNode(priceStyleLabel(this.priceStyle)));
5359
5508
  this.styleButton.setAttribute("aria-label", `Chart style \u2014 ${priceStyleLabel(this.priceStyle)}`);
5360
5509
  }
5361
- /** Re-project the contributed topbar actions (call after registrations change). */
5510
+ /** Re-project the contributed topbar actions (call after registrations change).
5511
+ * An action PINNED by the composition renders into its named slot (list position
5512
+ * wins over `align`/`order`); the rest flow into the side's `actions` slot — or
5513
+ * not at all when an explicit list omits it (the list is the side's contract). */
5362
5514
  renderActions() {
5363
5515
  const ctx = this.opts.getContext?.();
5364
5516
  this.actionsHost.replaceChildren();
5365
5517
  this.leftActionsHost.replaceChildren();
5518
+ for (const pin of this.pinned.values()) pin.host.replaceChildren();
5366
5519
  const doc = this.actionsHost.ownerDocument;
5520
+ const flowLeft = this.comp.left.includes("actions");
5521
+ const flowRight = this.comp.right.includes("actions");
5522
+ const builtin = new Set(TOPBAR_BUILTIN_IDS);
5367
5523
  for (const action of widgetActions("topbar", ctx)) {
5368
- const left = action.align === "left";
5524
+ const pin = this.pinned.get(action.id);
5525
+ if (!pin && builtin.has(action.id) && !this.flowingOverrides.has(action.id)) continue;
5526
+ const left = pin ? pin.left : action.align === "left";
5527
+ if (!pin && !(left ? flowLeft : flowRight)) continue;
5369
5528
  const b = doc.createElement("button");
5370
5529
  b.className = left ? "vela-widget-action-left" : "vela-widget-action";
5371
5530
  if (action.icon) b.appendChild(iconEl(action.icon, doc));
@@ -5374,7 +5533,7 @@ var Topbar = class {
5374
5533
  const c = this.opts.getContext?.();
5375
5534
  if (c) action.run(c);
5376
5535
  });
5377
- (left ? this.leftActionsHost : this.actionsHost).appendChild(b);
5536
+ (pin ? pin.host : left ? this.leftActionsHost : this.actionsHost).appendChild(b);
5378
5537
  }
5379
5538
  this.leftActionsSep.hidden = this.leftActionsHost.childElementCount === 0;
5380
5539
  this.onHairlineSync();
@@ -5767,41 +5926,39 @@ var Bottombar = class {
5767
5926
  }
5768
5927
  };
5769
5928
 
5770
- // src/widget/symbol-icon.ts
5771
- var iconFailed = /* @__PURE__ */ new Set();
5772
- function initialsOf(name) {
5773
- return (name || "?").replace(/[^A-Za-z0-9]/g, "").slice(0, 2).toUpperCase() || "?";
5774
- }
5775
- function cryptoIconUrl(base) {
5776
- return `https://crypto-icons.ledger.com/${encodeURIComponent(base.toUpperCase())}.png`;
5777
- }
5929
+ // src/data/symbol-base.ts
5778
5930
  function baseOf(d) {
5779
5931
  const fromDesc = d.description?.split("/")[0]?.trim();
5780
5932
  if (fromDesc) return fromDesc.replace(/\s+Perpetual$/i, "");
5781
5933
  return d.ticker.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || d.ticker;
5782
5934
  }
5783
- function tickerIconEl(doc, base, name, className) {
5935
+
5936
+ // src/widget/symbol-icon.ts
5937
+ var iconFailed = /* @__PURE__ */ new Set();
5938
+ function initialsOf(name) {
5939
+ return (name || "?").replace(/[^A-Za-z0-9]/g, "").slice(0, 2).toUpperCase() || "?";
5940
+ }
5941
+ function tickerIconEl(doc, base, name, className, iconUrl) {
5784
5942
  const wrap = doc.createElement("span");
5785
5943
  wrap.className = className;
5786
- const key = base.toUpperCase();
5787
5944
  const fallback = () => {
5788
5945
  wrap.replaceChildren();
5789
5946
  wrap.style.background = categoricalColor(name);
5790
5947
  wrap.textContent = initialsOf(base || name);
5791
5948
  };
5792
- if (!key || iconFailed.has(key)) {
5949
+ if (!iconUrl || iconFailed.has(iconUrl)) {
5793
5950
  fallback();
5794
5951
  return wrap;
5795
5952
  }
5796
5953
  const img = doc.createElement("img");
5797
5954
  img.alt = "";
5798
5955
  img.crossOrigin = "anonymous";
5799
- img.src = cryptoIconUrl(key);
5956
+ img.src = iconUrl;
5800
5957
  img.style.cssText = "width:100%;height:100%;border-radius:50%;display:block;object-fit:cover;";
5801
5958
  img.addEventListener(
5802
5959
  "error",
5803
5960
  () => {
5804
- iconFailed.add(key);
5961
+ iconFailed.add(iconUrl);
5805
5962
  fallback();
5806
5963
  },
5807
5964
  { once: true }
@@ -12054,11 +12211,14 @@ var MenuBuilder = class {
12054
12211
  }
12055
12212
  };
12056
12213
  var ObjectTree = class extends SidePanel {
12057
- constructor(host) {
12214
+ constructor(host, iconFor) {
12058
12215
  super(host, "Object tree", "vela-ot");
12216
+ this.iconFor = iconFor;
12059
12217
  this.chart = null;
12060
12218
  this.selectedDrawing = null;
12061
12219
  this.symbolName = "";
12220
+ /** The raw (possibly venue-prefixed) symbol — what icon resolution routes on. */
12221
+ this.symbolRaw = "";
12062
12222
  /** Drawing bundles — a view-side grouping, held for the panel's lifetime and never persisted.
12063
12223
  * Kept per chart because a workspace points this one panel at whichever chart is active, and
12064
12224
  * each chart's bundles have to survive the switch. */
@@ -12095,6 +12255,7 @@ var ObjectTree = class extends SidePanel {
12095
12255
  if (open2) this.refresh();
12096
12256
  }
12097
12257
  setSymbol(symbol) {
12258
+ this.symbolRaw = symbol;
12098
12259
  this.symbolName = parseSymbol(symbol).ticker;
12099
12260
  }
12100
12261
  /** (Re)bind to a chart instance — called after every widget rebuild. */
@@ -12295,7 +12456,7 @@ var ObjectTree = class extends SidePanel {
12295
12456
  const { chart } = pass;
12296
12457
  if (row.kind === "price") {
12297
12458
  const base = this.symbolName.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || this.symbolName;
12298
- const icon2 = tickerIconEl(doc, base || "P", this.symbolName || "Price", "vela-ot-avatar");
12459
+ const icon2 = tickerIconEl(doc, base || "P", this.symbolName || "Price", "vela-ot-avatar", this.symbolRaw ? this.iconFor?.(this.symbolRaw) : void 0);
12299
12460
  const el2 = this.row(icon2, row.label, row.visible, [
12300
12461
  {
12301
12462
  icon: row.visible ? "eye" : "eye-off",
@@ -13327,6 +13488,17 @@ var PanelDock = class {
13327
13488
  };
13328
13489
 
13329
13490
  // src/widget/symbol-picker.ts
13491
+ function dedupeSymbols(list) {
13492
+ const seen = /* @__PURE__ */ new Set();
13493
+ const out = [];
13494
+ for (const s of list) {
13495
+ const key = `${(s.prefix ?? s.provider ?? "").toLowerCase()}:${s.ticker.toUpperCase()}`;
13496
+ if (seen.has(key)) continue;
13497
+ seen.add(key);
13498
+ out.push(s);
13499
+ }
13500
+ return out;
13501
+ }
13330
13502
  var TOP_TICKERS = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT", "DOGEUSDT", "ADAUSDT", "LINKUSDT"];
13331
13503
  function parseQuery(raw, venues) {
13332
13504
  const m = raw.match(/^\s*([^\s:]+)\s*[:\s]\s*(.*)$/);
@@ -13341,17 +13513,18 @@ function onlyOne(matches) {
13341
13513
  return matches.length === 1 ? matches[0] : null;
13342
13514
  }
13343
13515
  var venueOf = (s) => s.prefix ?? s.provider;
13344
- function filterSymbols(list, query, limit = 100) {
13516
+ function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
13345
13517
  const venues = [...new Set(list.flatMap((s) => [venueOf(s)?.toLowerCase(), s.provider?.toLowerCase()]).filter((p) => !!p))];
13346
13518
  const { scope, term } = parseQuery(query, venues);
13347
13519
  const pool = scope ? list.filter((s) => venueOf(s)?.toLowerCase() === scope || s.provider?.toLowerCase() === scope) : list;
13348
13520
  const q = term.toUpperCase();
13349
13521
  if (!q) {
13350
13522
  if (scope) return [...pool].sort((a, b) => a.ticker.localeCompare(b.ticker)).slice(0, limit);
13523
+ if (top === false) return pool.slice(0, limit);
13351
13524
  const byTicker = new Map(pool.map((s) => [s.ticker.toUpperCase(), s]));
13352
- const top = TOP_TICKERS.map((t) => byTicker.get(t)).filter((s) => s !== void 0);
13353
- const rest = pool.filter((s) => !TOP_TICKERS.includes(s.ticker.toUpperCase()));
13354
- return [...top, ...rest].slice(0, limit);
13525
+ const pinned = top.map((t) => byTicker.get(t)).filter((s) => s !== void 0);
13526
+ const rest = pool.filter((s) => !top.includes(s.ticker.toUpperCase()));
13527
+ return [...pinned, ...rest].slice(0, limit);
13355
13528
  }
13356
13529
  const qLower = term.toLowerCase();
13357
13530
  const prefix = [];
@@ -13461,12 +13634,16 @@ var CSS8 = `
13461
13634
  var PAGE = 100;
13462
13635
  var SymbolPicker = class {
13463
13636
  constructor(opts) {
13637
+ this.opts = opts;
13464
13638
  this.source = () => [];
13465
13639
  this.rows = [];
13466
13640
  this.highlighted = 0;
13467
13641
  this.seed = "";
13468
13642
  this.activeTab = "All";
13469
13643
  this.visible = PAGE;
13644
+ /** The ranked pool cache — `key` fingerprints the raw pool the ranking ran on. */
13645
+ this.ranked = null;
13646
+ this.ranking = false;
13470
13647
  const doc = (opts.host ?? document.body).ownerDocument;
13471
13648
  injectStyles(STYLE_ID7, CSS8, doc);
13472
13649
  this.input = doc.createElement("input");
@@ -13564,10 +13741,34 @@ var SymbolPicker = class {
13564
13741
  } else delete el.dataset.highlighted;
13565
13742
  });
13566
13743
  }
13744
+ /** The picker's pool: the source, shaped by the registered symbol ranking. Cached —
13745
+ * the hook runs when the pool CHANGES (an index lands or refreshes), never per
13746
+ * keystroke; an async hook resolves onto the next repaint (stale-while-revalidate
13747
+ * in between, the raw pool before the first resolve). */
13748
+ pool() {
13749
+ const raw = this.source();
13750
+ const hook = symbolRanking();
13751
+ if (!hook) return raw;
13752
+ const key = `${raw.length}:${raw[0]?.ticker ?? ""}:${raw[raw.length - 1]?.ticker ?? ""}`;
13753
+ if (this.ranked?.key !== key && !this.ranking) {
13754
+ this.ranking = true;
13755
+ Promise.resolve([...raw]).then((copy) => hook(copy)).then((out) => {
13756
+ this.ranked = { key, result: dedupeSymbols(out) };
13757
+ }).catch((err) => {
13758
+ console.warn("[vela] symbol ranking failed \u2014 pool order kept:", err);
13759
+ this.ranked = { key, result: [...raw] };
13760
+ }).finally(() => {
13761
+ this.ranking = false;
13762
+ this.refresh();
13763
+ });
13764
+ }
13765
+ return this.ranked?.result ?? raw;
13766
+ }
13567
13767
  computeRows() {
13568
13768
  const TAB_TYPES = { Crypto: ["crypto"], Stocks: ["stock"], ETFs: ["etf"], Forex: ["forex"], Commodities: ["commodity"] };
13569
- const pool = this.activeTab === "All" ? this.source() : this.source().filter((s) => TAB_TYPES[this.activeTab]?.includes((s.type ?? "").toLowerCase()) || this.activeTab === "Crypto" && (s.type ?? "").toLowerCase() === "futures");
13570
- return filterSymbols(pool, this.input.value, this.visible);
13769
+ const all = this.pool();
13770
+ const pool = this.activeTab === "All" ? all : all.filter((s) => TAB_TYPES[this.activeTab]?.includes((s.type ?? "").toLowerCase()) || this.activeTab === "Crypto" && (s.type ?? "").toLowerCase() === "futures");
13771
+ return filterSymbols(pool, this.input.value, this.visible, symbolRanking() ? false : TOP_TICKERS);
13571
13772
  }
13572
13773
  refresh() {
13573
13774
  const doc = this.list.ownerDocument;
@@ -13598,7 +13799,7 @@ var SymbolPicker = class {
13598
13799
  row.dataset.ticker = s.ticker;
13599
13800
  const venue = s.prefix ?? s.provider;
13600
13801
  if (venue) row.dataset.venue = venue;
13601
- const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
13802
+ const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar", this.opts.iconFor?.(s));
13602
13803
  const main = doc.createElement("span");
13603
13804
  main.className = "vela-sp-main";
13604
13805
  const t = doc.createElement("span");
@@ -14279,7 +14480,8 @@ var MobileBar = class {
14279
14480
  if (!ctx) return;
14280
14481
  const doc = this.el.ownerDocument;
14281
14482
  this.actionsHost.replaceChildren();
14282
- for (const action of widgetActions("topbar", ctx).filter((a) => a.align === "left")) {
14483
+ const builtin = new Set(TOPBAR_BUILTIN_IDS);
14484
+ for (const action of widgetActions("topbar", ctx).filter((a) => a.align === "left" && !builtin.has(a.id))) {
14283
14485
  const b = doc.createElement("button");
14284
14486
  b.className = "vela-mb-item";
14285
14487
  b.setAttribute("aria-label", action.label);
@@ -14808,10 +15010,10 @@ var MoreDrawer = class {
14808
15010
  });
14809
15011
  actions.appendChild(b);
14810
15012
  };
14811
- action("undo", "Undo", this.opts.canUndo(), this.opts.onUndo);
14812
- action("redo", "Redo", this.opts.canRedo(), this.opts.onRedo);
14813
- action("camera", "Screenshot", true, this.opts.onScreenshot);
14814
- this.drawer.body.appendChild(actions);
15013
+ if (this.opts.onUndo) action("undo", "Undo", this.opts.canUndo(), this.opts.onUndo);
15014
+ if (this.opts.onRedo) action("redo", "Redo", this.opts.canRedo(), this.opts.onRedo);
15015
+ if (this.opts.onScreenshot) action("camera", "Screenshot", true, this.opts.onScreenshot);
15016
+ if (actions.childElementCount > 0) this.drawer.body.appendChild(actions);
14815
15017
  const list = doc.createElement("div");
14816
15018
  list.className = "vela-md-list";
14817
15019
  const current = this.opts.priceStyles().find((s) => s.id === this.opts.priceStyle());
@@ -14832,8 +15034,10 @@ var MoreDrawer = class {
14832
15034
  })
14833
15035
  );
14834
15036
  }
14835
- const alertCount = this.opts.alerts().length;
14836
- list.appendChild(this.row(doc, "Alerts", { icon: "bell", value: alertCount > 0 ? String(alertCount) : void 0, chevron: true, onClick: () => this.show("alerts") }));
15037
+ if (this.opts.alerts) {
15038
+ const alertCount = this.opts.alerts().length;
15039
+ list.appendChild(this.row(doc, "Alerts", { icon: "bell", value: alertCount > 0 ? String(alertCount) : void 0, chevron: true, onClick: () => this.show("alerts") }));
15040
+ }
14837
15041
  for (const act of this.opts.actions()) {
14838
15042
  list.appendChild(
14839
15043
  this.row(doc, act.label, {
@@ -14914,7 +15118,7 @@ var MoreDrawer = class {
14914
15118
  this.drawer.body.appendChild(list);
14915
15119
  }
14916
15120
  renderAlerts(doc) {
14917
- const alerts = this.opts.alerts();
15121
+ const alerts = this.opts.alerts?.() ?? [];
14918
15122
  if (alerts.length === 0) {
14919
15123
  const empty = doc.createElement("div");
14920
15124
  empty.className = "vela-md-empty";
@@ -19299,6 +19503,12 @@ var DataControl = class {
19299
19503
  symbols(provider) {
19300
19504
  return this.registry?.symbols(provider) ?? [];
19301
19505
  }
19506
+ /** The icon URL for `symbol` — its owning provider's `resolveSymbolIcon`, routed
19507
+ * through resolution. Undefined while unresolvable, when the provider declares no
19508
+ * resolver, or on a custom `deps.dataFeed` — the shells then show initials. */
19509
+ symbolIcon(symbol) {
19510
+ return this.registry?.symbolIcon(symbol);
19511
+ }
19302
19512
  /** Per-symbol metadata (Pine `syminfo.*`), resolved through the owning provider. */
19303
19513
  symbolInfo(symbol) {
19304
19514
  return this.registry?.symbolInfoFor(symbol) ?? Promise.resolve(void 0);
@@ -35647,8 +35857,9 @@ function statuslineInkOf(renderer, priceStyle) {
35647
35857
  }
35648
35858
  }
35649
35859
  var Statusline = class {
35650
- constructor(host, symbol) {
35860
+ constructor(host, symbol, iconFor) {
35651
35861
  this.host = host;
35862
+ this.iconFor = iconFor;
35652
35863
  this.parts = { name: true, market: true, ohlc: true, change: true };
35653
35864
  this.lastBar = null;
35654
35865
  this.hoverBar = null;
@@ -35672,7 +35883,7 @@ var Statusline = class {
35672
35883
  this.el.className = "vela-statusline";
35673
35884
  this.el.dataset.velaScreenshot = "1";
35674
35885
  const ticker = parseSymbol(symbol).ticker;
35675
- this.avatarEl = tickerIconEl(doc, baseOfTicker(ticker), ticker, "vela-sl-avatar");
35886
+ this.avatarEl = tickerIconEl(doc, baseOfTicker(ticker), ticker, "vela-sl-avatar", this.iconFor?.(symbol));
35676
35887
  this.symbolEl = doc.createElement("span");
35677
35888
  this.symbolEl.className = "vela-sl-symbol";
35678
35889
  this.symbolEl.textContent = ticker;
@@ -35693,7 +35904,7 @@ var Statusline = class {
35693
35904
  setSymbol(symbol) {
35694
35905
  const ticker = parseSymbol(symbol).ticker;
35695
35906
  this.symbolEl.textContent = ticker;
35696
- const fresh = tickerIconEl(this.el.ownerDocument, baseOfTicker(ticker), ticker, "vela-sl-avatar");
35907
+ const fresh = tickerIconEl(this.el.ownerDocument, baseOfTicker(ticker), ticker, "vela-sl-avatar", this.iconFor?.(symbol));
35697
35908
  this.avatarEl.replaceWith(fresh);
35698
35909
  this.avatarEl = fresh;
35699
35910
  this.fit();
@@ -36441,12 +36652,15 @@ var ChartCell = class {
36441
36652
  this.watermarkOn = seed.watermark ?? deps.watermark;
36442
36653
  this.watermark = deps.watermark ? new Watermark(this.host, symbol ?? "", seed.timeframe ?? "60") : null;
36443
36654
  if (!this.watermarkOn) this.watermark?.setVisible(false);
36444
- this.statusline = deps.statusline ? new Statusline(this.host, symbol ?? "") : null;
36655
+ this.statusline = deps.statusline ? new Statusline(this.host, symbol ?? "", (sym) => this.inner?.data.symbolIcon(sym)) : null;
36445
36656
  this.statusline?.setMeta(seed.timeframe ?? "60", this.state.provider ?? "");
36446
36657
  this.statusline?.onChart(this.inner);
36447
36658
  this.marketStatus = this.statusline ? new MarketStatusTracker((s) => this.statusline?.setMarketStatus(s)) : null;
36448
36659
  void this.inner.data.ready().then(() => {
36449
- if (this.inner && this.state.symbol) this.statusline?.setMeta(this.state.timeframe ?? "60", this.inner.data.displayPrefix(this.state.symbol) ?? this.state.provider ?? "");
36660
+ if (this.inner && this.state.symbol) {
36661
+ this.statusline?.setSymbol(this.state.symbol);
36662
+ this.statusline?.setMeta(this.state.timeframe ?? "60", this.inner.data.displayPrefix(this.state.symbol) ?? this.state.provider ?? "");
36663
+ }
36450
36664
  this.refreshSessionAvailable();
36451
36665
  if (this.inner && this.state.symbol) this.marketStatus?.track(this.inner.data, this.state.symbol);
36452
36666
  });
@@ -37529,6 +37743,9 @@ var VelaWorkspace = class {
37529
37743
  const optLayout = opts.layout === false || opts.layout === void 0 ? "4" : opts.layout;
37530
37744
  this.def = this.resolveLayout(this.monoLayout ? "1" : boot?.layout && ensureLayout(boot.layout) ? boot.layout : optLayout);
37531
37745
  this.alertCap = Math.max(1, opts.alertCap ?? ALERT_CAP);
37746
+ this.topbarComp = resolveTopbarComposition(opts.topbar);
37747
+ this.indicatorsOverride = topbarActionOverride("indicators");
37748
+ this.screenshotOverride = topbarActionOverride("screenshot");
37532
37749
  if (boot?.trackSizes) for (const [id, ts] of Object.entries(boot.trackSizes)) this.trackSizes.set(id, ts);
37533
37750
  if (boot?.ext) this.extState = { ...boot.ext };
37534
37751
  if (boot?.charts) for (const { id, ...cs } of boot.charts) this.pool.set(id, cs);
@@ -37545,6 +37762,8 @@ var VelaWorkspace = class {
37545
37762
  });
37546
37763
  this.symbolPicker = new SymbolPicker({
37547
37764
  host: this.root,
37765
+ // Row icons come from each descriptor's OWNING provider (resolveSymbolIcon).
37766
+ iconFor: (d) => this.feed.symbolIconOf(d),
37548
37767
  onSelect: (ticker) => this.active.setSymbol(ticker),
37549
37768
  onOpenChange: (open2) => {
37550
37769
  if (open2) for (const cell of this.cells()) cell.chart.renderer.closeDialogs();
@@ -37552,7 +37771,7 @@ var VelaWorkspace = class {
37552
37771
  }
37553
37772
  });
37554
37773
  this.symbolPicker.setSource(() => this.feed.symbols());
37555
- this.indicatorPicker = opts.indicatorPicker !== false ? new IndicatorPicker({
37774
+ this.indicatorPicker = opts.indicatorPicker !== false && !this.indicatorsOverride && topbarHas(this.topbarComp, "indicators") ? new IndicatorPicker({
37556
37775
  host: this.root,
37557
37776
  library: () => this.active.libraryRows(),
37558
37777
  onChart: () => this.active.onChartRows(),
@@ -37571,6 +37790,9 @@ var VelaWorkspace = class {
37571
37790
  });
37572
37791
  this.topbar = new Topbar(this.root, {
37573
37792
  symbol: "",
37793
+ // RAW option, not the resolved lists — the bar distinguishes a host-declared
37794
+ // side (list is law) from a default one (an override's `order` may flow).
37795
+ composition: opts.topbar,
37574
37796
  onSymbolClick: () => this.symbolPicker.open(),
37575
37797
  ...picker ? { onIndicatorsClick: () => picker.open() } : {},
37576
37798
  onUndoClick: () => this.active.history.undo(),
@@ -37628,7 +37850,7 @@ var VelaWorkspace = class {
37628
37850
  context: () => this.context(),
37629
37851
  changed: () => this.markStateDirty()
37630
37852
  });
37631
- this.objectTree = new ObjectTree(main);
37853
+ this.objectTree = new ObjectTree(main, (sym) => this.feed.symbolIcon(sym));
37632
37854
  this.dataWindow = new DataWindow(main);
37633
37855
  this.dock.addBuiltIn({ id: "dataWindow", title: "Data window", icon: "datawindow", order: 10, panel: this.dataWindow, onChart: (c) => this.dataWindow.onChart(c) });
37634
37856
  this.dock.addBuiltIn({ id: "objects", title: "Object tree", icon: "objects", order: 20, panel: this.objectTree, onChart: (c) => this.objectTree.onChart(c) });
@@ -37700,7 +37922,9 @@ var VelaWorkspace = class {
37700
37922
  timeframe: "60",
37701
37923
  onSymbolClick: () => this.symbolPicker.open(),
37702
37924
  onTimeframeClick: () => this.openTimeframeDrawer(),
37703
- ...picker ? { onIndicatorsClick: () => picker.open() } : {},
37925
+ // Same visibility truth as the desktop bar: composition-hidden
37926
+ // indicators lose their mobile stop too; an override takes it over.
37927
+ ...topbarHas(this.topbarComp, "indicators") && (picker || this.indicatorsOverride) ? { onIndicatorsClick: this.indicatorsOverride ? () => this.runOverride(this.indicatorsOverride) : () => picker.open() } : {},
37704
37928
  getContext: () => this.context(),
37705
37929
  ...this.drawingsEnabled ? { onDrawingsClick: () => this.openDrawingsDrawer() } : {},
37706
37930
  onMoreClick: () => this.openMoreDrawer(),
@@ -38596,27 +38820,32 @@ var VelaWorkspace = class {
38596
38820
  this.drawingsDrawer.open();
38597
38821
  }
38598
38822
  openMoreDrawer() {
38823
+ const has = (id) => topbarHas(this.topbarComp, id);
38599
38824
  this.moreDrawer ?? (this.moreDrawer = new MoreDrawer({
38600
38825
  host: this.root,
38601
- onUndo: () => this.active.history.undo(),
38602
- onRedo: () => this.active.history.redo(),
38603
- onScreenshot: () => this.active.downloadScreenshot(),
38826
+ ...has("undo-redo") ? { onUndo: () => this.active.history.undo(), onRedo: () => this.active.history.redo() } : {},
38827
+ ...has("screenshot") ? { onScreenshot: this.screenshotOverride ? () => this.runOverride(this.screenshotOverride) : () => this.active.downloadScreenshot() } : {},
38604
38828
  canUndo: () => this.active.history.canUndo,
38605
38829
  canRedo: () => this.active.history.canRedo,
38606
38830
  priceStyles: () => priceStyleIds().map((id) => ({ id, label: priceStyleLabel(id), icon: priceStyleIcon(id) })),
38607
38831
  priceStyle: () => this.active.priceStyle,
38608
38832
  onPriceStyle: (id) => this.active.setPriceStyle(id),
38609
- panels: () => [...this.dock.list()],
38833
+ panels: () => has("panels") ? [...this.dock.list()] : [],
38610
38834
  onTogglePanel: (id) => this.dock.toggle(id),
38611
- alerts: () => this.alerts.map((a) => ({ title: `${a.source} \xB7 ${a.title}`, message: a.message, time: a.time })),
38835
+ ...has("alerts") ? { alerts: () => this.alerts.map((a) => ({ title: `${a.source} \xB7 ${a.title}`, message: a.message, time: a.time })) } : {},
38612
38836
  // Left-aligned actions have their own bottom-bar stop — only the rest
38613
- // lands in the drawer, or every left action would appear twice.
38614
- actions: () => widgetActions("topbar", this.context()).filter((a) => a.align !== "left").map((a) => ({ label: a.label, icon: a.icon, run: () => a.run(this.context()) })),
38837
+ // lands in the drawer, or every left action would appear twice. Built-in-id
38838
+ // actions are slot OVERRIDES: they reach the drawer through the slot's own
38839
+ // routed button (screenshot) or stop (indicators), never as an extra row.
38840
+ actions: () => {
38841
+ const builtin = new Set(TOPBAR_BUILTIN_IDS);
38842
+ 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()) }));
38843
+ },
38615
38844
  // The desktop layout dropdown's whole surface — the grid canvas, the
38616
38845
  // non-canvas presets and the sync switches — relocated into the kebab
38617
38846
  // drawer (the topbar is hidden on mobile). Same reads as the topbar block;
38618
- // single-chart mode omits it here too.
38619
- layout: this.monoLayout ? void 0 : {
38847
+ // single-chart mode and a composition without 'layout' omit it here too.
38848
+ layout: this.monoLayout || !has("layout") ? void 0 : {
38620
38849
  shape: () => layoutShape(this.def),
38621
38850
  presets: () => layouts().filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label, checked: l.id === this.def.id })),
38622
38851
  onSelectGrid: (rows, cols) => this.setLayout(layoutForGrid(rows, cols)),
@@ -38683,9 +38912,23 @@ var VelaWorkspace = class {
38683
38912
  }
38684
38913
  }
38685
38914
  }
38915
+ /** Invoke a slot override the way its button would: fresh context, `when` respected. */
38916
+ runOverride(action) {
38917
+ const ctx = this.context();
38918
+ if (!action.when || action.when(ctx)) action.run(ctx);
38919
+ }
38686
38920
  /** The default shortcut set — every binding acts on the ACTIVE cell. */
38687
38921
  registerDefaultKeys() {
38688
- this.keymap.register({ id: "chart.screenshot", keys: "mod+alt+s", label: "Download a chart screenshot", category: "Chart", run: () => this.active.downloadScreenshot() });
38922
+ if (topbarHas(this.topbarComp, "screenshot")) {
38923
+ const ov = this.screenshotOverride;
38924
+ this.keymap.register({
38925
+ id: "chart.screenshot",
38926
+ keys: "mod+alt+s",
38927
+ label: ov ? ov.label : "Download a chart screenshot",
38928
+ category: "Chart",
38929
+ run: ov ? () => this.runOverride(ov) : () => this.active.downloadScreenshot()
38930
+ });
38931
+ }
38689
38932
  this.keymap.register({ id: "chart.reset-view", keys: "alt+r", label: "Reset view (all history)", category: "Chart", run: () => this.active.chart.setVisibleRangePreset("ALL") });
38690
38933
  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")) });
38691
38934
  this.keymap.register({
@@ -38725,7 +38968,10 @@ var VelaWorkspace = class {
38725
38968
  this.keymap.register({ id: "view.zoom-out", keys: "mod+arrowdown", label: "Zoom out", category: "Chart", run: () => this.glider.zoom(ZOOM_OUT) });
38726
38969
  this.keymap.register({ id: "view.pan-left", keys: "mod+arrowleft", label: "Pan toward history", category: "Chart", run: () => this.active.chart.panBy(-PAN_FAST) });
38727
38970
  this.keymap.register({ id: "view.pan-right", keys: "mod+arrowright", label: "Pan toward now", category: "Chart", run: () => this.active.chart.panBy(PAN_FAST) });
38728
- if (this.indicatorPicker) {
38971
+ const indOv = this.indicatorsOverride;
38972
+ if (indOv && topbarHas(this.topbarComp, "indicators")) {
38973
+ this.keymap.register({ id: "indicators.open", keys: "/", label: indOv.label, category: "Indicators", run: () => this.runOverride(indOv) });
38974
+ } else if (this.indicatorPicker) {
38729
38975
  this.keymap.register({ id: "indicators.open", keys: "/", label: "Open the indicator picker", category: "Indicators", run: () => this.indicatorPicker?.open() });
38730
38976
  }
38731
38977
  this.keymap.register({
@@ -38881,6 +39127,7 @@ exports.DEFAULT_PANEL_ORDER = DEFAULT_PANEL_ORDER;
38881
39127
  exports.DEFAULT_PANEL_WIDTH = DEFAULT_PANEL_WIDTH;
38882
39128
  exports.DataWindow = DataWindow;
38883
39129
  exports.IndicatorPicker = IndicatorPicker;
39130
+ exports.OVERRIDABLE_TOPBAR_IDS = OVERRIDABLE_TOPBAR_IDS;
38884
39131
  exports.ObjectTree = ObjectTree;
38885
39132
  exports.PanelDock = PanelDock;
38886
39133
  exports.RANGE_PRESETS = RANGE_PRESETS;
@@ -38889,6 +39136,9 @@ exports.SidePanel = SidePanel;
38889
39136
  exports.Statusline = Statusline;
38890
39137
  exports.SymbolPicker = SymbolPicker;
38891
39138
  exports.TIMEZONES = TIMEZONES;
39139
+ exports.TOPBAR_BUILTIN_IDS = TOPBAR_BUILTIN_IDS;
39140
+ exports.TOPBAR_DEFAULT_LEFT = TOPBAR_DEFAULT_LEFT;
39141
+ exports.TOPBAR_DEFAULT_RIGHT = TOPBAR_DEFAULT_RIGHT;
38892
39142
  exports.TimeframeQuick = TimeframeQuick;
38893
39143
  exports.Topbar = Topbar;
38894
39144
  exports.VelaWidget = VelaWidget;
@@ -38904,17 +39154,23 @@ exports.fmtPrice = fmtPrice;
38904
39154
  exports.localStorageAdapter = localStorageAdapter;
38905
39155
  exports.normalizeTimezone = normalizeTimezone;
38906
39156
  exports.parseTimeframe = parseTimeframe;
39157
+ exports.pinnedTopbarActionIds = pinnedTopbarActionIds;
38907
39158
  exports.priceStyleLabel = priceStyleLabel;
38908
39159
  exports.registerSidePanel = registerSidePanel;
38909
39160
  exports.registerStatePersistence = registerStatePersistence;
39161
+ exports.registerSymbolRanking = registerSymbolRanking;
38910
39162
  exports.registerWidgetAction = registerWidgetAction;
38911
39163
  exports.registerWidgetAttachment = registerWidgetAttachment;
38912
39164
  exports.resolveIndicators = resolveIndicators;
39165
+ exports.resolveTopbarComposition = resolveTopbarComposition;
38913
39166
  exports.sanitizeState = sanitizeState;
38914
39167
  exports.sidePanels = sidePanels;
38915
39168
  exports.statePersistenceHandlers = statePersistenceHandlers;
39169
+ exports.symbolRanking = symbolRanking;
38916
39170
  exports.timeframeLabel = timeframeLabel;
38917
39171
  exports.timeframeMs = timeframeMs;
39172
+ exports.topbarActionOverride = topbarActionOverride;
39173
+ exports.topbarHas = topbarHas;
38918
39174
  exports.tzButtonLabel = tzButtonLabel;
38919
39175
  exports.tzMenuLabel = tzMenuLabel;
38920
39176
  exports.tzOffset = tzOffset;