@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/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
|
-
|
|
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
|
-
|
|
5218
|
-
|
|
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
|
-
|
|
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
|
|
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/
|
|
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
|
-
|
|
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 (!
|
|
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 =
|
|
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(
|
|
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
|
|
13353
|
-
const rest = pool.filter((s) => !
|
|
13354
|
-
return [...
|
|
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
|
|
13570
|
-
|
|
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
|
-
|
|
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
|
-
|
|
14836
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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;
|