@luxalgo/vela 0.6.6 → 0.6.8
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/{chunk-OEUGUXL7.js → chunk-L3I2CCYO.js} +49 -1
- package/dist/{chunk-TMXK2SJR.js → chunk-N7LGMKCE.js} +523 -142
- package/dist/{chunk-JQFO2WMU.js → chunk-SQETIBFU.js} +83 -9
- package/dist/chunk-W4EJWLEO.js +12 -0
- package/dist/{contributions-BqCIsbaP.d.ts → contributions-DLLdV9jD.d.ts} +59 -3
- package/dist/{contributions-DNMqrAuw.d.cts → contributions-Vw-Hm58R.d.cts} +59 -3
- package/dist/index.cjs +110 -8
- package/dist/index.d.cts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +2 -2
- package/dist/{plugin-CN8U2__Q.d.cts → plugin-BnJgjLAy.d.cts} +2 -2
- package/dist/{plugin-WnvNNJOp.d.ts → plugin-CC7rBrOY.d.ts} +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/{bottombar-COnL2Sk4.d.ts → statusline-CGkB2EOo.d.ts} +137 -8
- package/dist/{bottombar-DeDXbF_K.d.cts → statusline-DqzBqy42.d.cts} +137 -8
- package/dist/vela.global.js +136 -8
- package/dist/vela.global.min.js +42 -42
- package/dist/widget.cjs +667 -148
- package/dist/widget.d.cts +50 -81
- package/dist/widget.d.ts +50 -81
- package/dist/widget.js +5 -4
- package/dist/workspace.cjs +613 -150
- package/dist/workspace.d.cts +62 -9
- package/dist/workspace.d.ts +62 -9
- 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,17 @@ 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();
|
|
5257
|
+
/** Tooltips of the CURRENT icon-only action buttons — rebuilt with every
|
|
5258
|
+
* renderActions pass (contributed buttons are replaceChildren'd away). */
|
|
5259
|
+
this.actionTooltips = [];
|
|
5260
|
+
/** `iconOnly` misuse warned once per action id (renderActions re-runs freely). */
|
|
5261
|
+
this.warnedIconless = /* @__PURE__ */ new Set();
|
|
5168
5262
|
this.onHairlineSync = () => {
|
|
5169
5263
|
if (this.hairlineRaf) return;
|
|
5170
5264
|
const win = this.el.ownerDocument.defaultView;
|
|
@@ -5179,6 +5273,8 @@ var Topbar = class {
|
|
|
5179
5273
|
this.host = host;
|
|
5180
5274
|
this.timeframe = opts.timeframe;
|
|
5181
5275
|
this.priceStyle = opts.priceStyle;
|
|
5276
|
+
this.comp = resolveTopbarComposition(opts.composition);
|
|
5277
|
+
const vis = (id) => topbarHas(this.comp, id);
|
|
5182
5278
|
const doc = host.ownerDocument;
|
|
5183
5279
|
injectStyles(STYLE_ID2, CSS3, doc);
|
|
5184
5280
|
this.el = doc.createElement("div");
|
|
@@ -5201,7 +5297,7 @@ var Topbar = class {
|
|
|
5201
5297
|
this.styleButton.className = "vela-widget-style";
|
|
5202
5298
|
this.renderStyleButton(doc);
|
|
5203
5299
|
let indicatorsBtn = null;
|
|
5204
|
-
if (opts.onIndicatorsClick) {
|
|
5300
|
+
if (opts.onIndicatorsClick && !topbarActionOverride("indicators")) {
|
|
5205
5301
|
indicatorsBtn = doc.createElement("button");
|
|
5206
5302
|
indicatorsBtn.className = "vela-widget-indicators";
|
|
5207
5303
|
indicatorsBtn.append(iconEl("indicators", doc), doc.createTextNode("Indicators"));
|
|
@@ -5214,18 +5310,23 @@ var Topbar = class {
|
|
|
5214
5310
|
this.panelsHost = doc.createElement("span");
|
|
5215
5311
|
this.panelsHost.className = "vela-widget-panels";
|
|
5216
5312
|
const tool = (cls, icon2, tip, onClick) => this.toolButton(cls, icon2, tip, onClick, this.tooltips);
|
|
5217
|
-
|
|
5218
|
-
|
|
5313
|
+
if (vis("undo-redo")) {
|
|
5314
|
+
this.undoBtn = tool("vela-widget-undo", "undo", "Undo", opts.onUndoClick);
|
|
5315
|
+
this.redoBtn = tool("vela-widget-redo", "redo", "Redo", opts.onRedoClick);
|
|
5316
|
+
} else {
|
|
5317
|
+
this.undoBtn = doc.createElement("button");
|
|
5318
|
+
this.redoBtn = doc.createElement("button");
|
|
5319
|
+
}
|
|
5219
5320
|
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");
|
|
5321
|
+
const screenshotBtn = vis("screenshot") && !topbarActionOverride("screenshot") ? tool("vela-widget-screenshot", "camera", "Download screenshot", opts.onScreenshotClick) : null;
|
|
5322
|
+
this.alertsBtn = vis("alerts") ? tool("vela-widget-alerts", "bell", "Alerts") : doc.createElement("button");
|
|
5222
5323
|
this.alertsBtn.style.position = "relative";
|
|
5223
5324
|
if (opts.onAlertsClick) this.alertsBtn.addEventListener("click", () => opts.onAlertsClick(this.alertsBtn));
|
|
5224
5325
|
this.alertsBadge = doc.createElement("span");
|
|
5225
5326
|
this.alertsBadge.className = "vela-alerts-badge";
|
|
5226
5327
|
this.alertsBadge.style.display = "none";
|
|
5227
5328
|
this.alertsBtn.appendChild(this.alertsBadge);
|
|
5228
|
-
if (opts.layout) {
|
|
5329
|
+
if (opts.layout && vis("layout")) {
|
|
5229
5330
|
this.layoutId = opts.layout.current;
|
|
5230
5331
|
this.layoutButton = doc.createElement("button");
|
|
5231
5332
|
this.layoutButton.className = "vela-widget-style";
|
|
@@ -5236,12 +5337,65 @@ var Topbar = class {
|
|
|
5236
5337
|
d.className = "vela-sep";
|
|
5237
5338
|
return d;
|
|
5238
5339
|
};
|
|
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
5340
|
this.leftActionsSep = sep();
|
|
5243
5341
|
this.leftActionsSep.hidden = true;
|
|
5244
|
-
|
|
5342
|
+
const primaries = /* @__PURE__ */ new Set(["symbol", "timeframes", "style", "layout", "indicators"]);
|
|
5343
|
+
const pinSlot = (id, left) => {
|
|
5344
|
+
const slot = doc.createElement("span");
|
|
5345
|
+
slot.className = "vela-widget-action-pin";
|
|
5346
|
+
this.pinned.set(id, { host: slot, left });
|
|
5347
|
+
return [slot];
|
|
5348
|
+
};
|
|
5349
|
+
const overridden = (id, left) => {
|
|
5350
|
+
const ov = topbarActionOverride(id);
|
|
5351
|
+
if (!ov) return null;
|
|
5352
|
+
const sideDeclared = left ? opts.composition?.left != null : opts.composition?.right != null;
|
|
5353
|
+
if (!sideDeclared && ov.order !== void 0) {
|
|
5354
|
+
this.flowingOverrides.add(id);
|
|
5355
|
+
return [];
|
|
5356
|
+
}
|
|
5357
|
+
return pinSlot(id, left);
|
|
5358
|
+
};
|
|
5359
|
+
const elementsFor = (id, left) => {
|
|
5360
|
+
switch (id) {
|
|
5361
|
+
case "symbol":
|
|
5362
|
+
return [this.symbolEl];
|
|
5363
|
+
case "timeframes":
|
|
5364
|
+
return [tfGroup];
|
|
5365
|
+
case "style":
|
|
5366
|
+
return [this.styleButton];
|
|
5367
|
+
case "layout":
|
|
5368
|
+
return this.layoutButton ? [this.layoutButton] : [];
|
|
5369
|
+
case "indicators":
|
|
5370
|
+
return overridden(id, left) ?? (indicatorsBtn ? [indicatorsBtn] : []);
|
|
5371
|
+
case "actions":
|
|
5372
|
+
return left ? [this.leftActionsHost, this.leftActionsSep] : [this.actionsHost];
|
|
5373
|
+
case "undo-redo":
|
|
5374
|
+
return [this.undoBtn, this.redoBtn];
|
|
5375
|
+
case "alerts":
|
|
5376
|
+
return [this.alertsBtn];
|
|
5377
|
+
case "panels":
|
|
5378
|
+
return [this.panelsHost];
|
|
5379
|
+
case "screenshot":
|
|
5380
|
+
return overridden(id, left) ?? (screenshotBtn ? [screenshotBtn] : []);
|
|
5381
|
+
default:
|
|
5382
|
+
return pinSlot(id, left);
|
|
5383
|
+
}
|
|
5384
|
+
};
|
|
5385
|
+
const sideEls = (list, left) => {
|
|
5386
|
+
const out = [];
|
|
5387
|
+
for (const [i, id] of list.entries()) {
|
|
5388
|
+
const els = elementsFor(id, left);
|
|
5389
|
+
if (els.length === 0) continue;
|
|
5390
|
+
out.push(...els);
|
|
5391
|
+
if (primaries.has(id) && i < list.length - 1) out.push(sep());
|
|
5392
|
+
}
|
|
5393
|
+
return out;
|
|
5394
|
+
};
|
|
5395
|
+
const right = doc.createElement("span");
|
|
5396
|
+
right.className = "vela-topbar-right";
|
|
5397
|
+
right.append(...sideEls(this.comp.right, false));
|
|
5398
|
+
this.el.append(...sideEls(this.comp.left, true), right);
|
|
5245
5399
|
host.appendChild(this.el);
|
|
5246
5400
|
this.renderTfChips();
|
|
5247
5401
|
this.onHairlineSync();
|
|
@@ -5358,23 +5512,45 @@ var Topbar = class {
|
|
|
5358
5512
|
else this.styleButton.appendChild(doc.createTextNode(priceStyleLabel(this.priceStyle)));
|
|
5359
5513
|
this.styleButton.setAttribute("aria-label", `Chart style \u2014 ${priceStyleLabel(this.priceStyle)}`);
|
|
5360
5514
|
}
|
|
5361
|
-
/** Re-project the contributed topbar actions (call after registrations change).
|
|
5515
|
+
/** Re-project the contributed topbar actions (call after registrations change).
|
|
5516
|
+
* An action PINNED by the composition renders into its named slot (list position
|
|
5517
|
+
* wins over `align`/`order`); the rest flow into the side's `actions` slot — or
|
|
5518
|
+
* not at all when an explicit list omits it (the list is the side's contract). */
|
|
5362
5519
|
renderActions() {
|
|
5363
5520
|
const ctx = this.opts.getContext?.();
|
|
5364
5521
|
this.actionsHost.replaceChildren();
|
|
5365
5522
|
this.leftActionsHost.replaceChildren();
|
|
5523
|
+
for (const pin of this.pinned.values()) pin.host.replaceChildren();
|
|
5524
|
+
for (const t of this.actionTooltips) t.destroy();
|
|
5525
|
+
this.actionTooltips = [];
|
|
5366
5526
|
const doc = this.actionsHost.ownerDocument;
|
|
5527
|
+
const flowLeft = this.comp.left.includes("actions");
|
|
5528
|
+
const flowRight = this.comp.right.includes("actions");
|
|
5529
|
+
const builtin = new Set(TOPBAR_BUILTIN_IDS);
|
|
5367
5530
|
for (const action of widgetActions("topbar", ctx)) {
|
|
5368
|
-
const
|
|
5531
|
+
const pin = this.pinned.get(action.id);
|
|
5532
|
+
if (!pin && builtin.has(action.id) && !this.flowingOverrides.has(action.id)) continue;
|
|
5533
|
+
const left = pin ? pin.left : action.align === "left";
|
|
5534
|
+
if (!pin && !(left ? flowLeft : flowRight)) continue;
|
|
5535
|
+
const iconOnly = action.iconOnly === true && !!action.icon;
|
|
5536
|
+
if (action.iconOnly === true && !action.icon && !this.warnedIconless.has(action.id)) {
|
|
5537
|
+
this.warnedIconless.add(action.id);
|
|
5538
|
+
console.warn(`[vela] widget action "${action.id}": iconOnly needs an \`icon\` \u2014 rendering the label instead.`);
|
|
5539
|
+
}
|
|
5369
5540
|
const b = doc.createElement("button");
|
|
5370
|
-
b.className = left ? "vela-widget-action-left" : "vela-widget-action";
|
|
5541
|
+
b.className = left ? "vela-widget-action-left" : iconOnly ? "vela-widget-tool" : "vela-widget-action";
|
|
5371
5542
|
if (action.icon) b.appendChild(iconEl(action.icon, doc));
|
|
5372
|
-
|
|
5543
|
+
if (iconOnly) {
|
|
5544
|
+
b.setAttribute("aria-label", action.label);
|
|
5545
|
+
this.actionTooltips.push(new Tooltip(b, { content: action.label, triggerId: `vela-action-${action.id}`, host: this.host }));
|
|
5546
|
+
} else {
|
|
5547
|
+
b.appendChild(doc.createTextNode(action.label));
|
|
5548
|
+
}
|
|
5373
5549
|
b.addEventListener("click", () => {
|
|
5374
5550
|
const c = this.opts.getContext?.();
|
|
5375
5551
|
if (c) action.run(c);
|
|
5376
5552
|
});
|
|
5377
|
-
(left ? this.leftActionsHost : this.actionsHost).appendChild(b);
|
|
5553
|
+
(pin ? pin.host : left ? this.leftActionsHost : this.actionsHost).appendChild(b);
|
|
5378
5554
|
}
|
|
5379
5555
|
this.leftActionsSep.hidden = this.leftActionsHost.childElementCount === 0;
|
|
5380
5556
|
this.onHairlineSync();
|
|
@@ -5420,7 +5596,7 @@ var Topbar = class {
|
|
|
5420
5596
|
this.tfMenu.destroy();
|
|
5421
5597
|
this.styleMenu.destroy();
|
|
5422
5598
|
this.layoutPicker?.destroy();
|
|
5423
|
-
for (const t of [...this.tooltips, ...this.panelTooltips]) t.destroy();
|
|
5599
|
+
for (const t of [...this.tooltips, ...this.panelTooltips, ...this.actionTooltips]) t.destroy();
|
|
5424
5600
|
this.el.remove();
|
|
5425
5601
|
}
|
|
5426
5602
|
/** One icon-only tool button with its kit tooltip, parked in `sink` for disposal. */
|
|
@@ -5767,41 +5943,39 @@ var Bottombar = class {
|
|
|
5767
5943
|
}
|
|
5768
5944
|
};
|
|
5769
5945
|
|
|
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
|
-
}
|
|
5946
|
+
// src/data/symbol-base.ts
|
|
5778
5947
|
function baseOf(d) {
|
|
5779
5948
|
const fromDesc = d.description?.split("/")[0]?.trim();
|
|
5780
5949
|
if (fromDesc) return fromDesc.replace(/\s+Perpetual$/i, "");
|
|
5781
5950
|
return d.ticker.replace(/[-_/]?(USDT|USDC|USD1|USDS|BUSD|USD|EUR|PERP)$/i, "") || d.ticker;
|
|
5782
5951
|
}
|
|
5783
|
-
|
|
5952
|
+
|
|
5953
|
+
// src/widget/symbol-icon.ts
|
|
5954
|
+
var iconFailed = /* @__PURE__ */ new Set();
|
|
5955
|
+
function initialsOf(name) {
|
|
5956
|
+
return (name || "?").replace(/[^A-Za-z0-9]/g, "").slice(0, 2).toUpperCase() || "?";
|
|
5957
|
+
}
|
|
5958
|
+
function tickerIconEl(doc, base, name, className, iconUrl) {
|
|
5784
5959
|
const wrap = doc.createElement("span");
|
|
5785
5960
|
wrap.className = className;
|
|
5786
|
-
const key = base.toUpperCase();
|
|
5787
5961
|
const fallback = () => {
|
|
5788
5962
|
wrap.replaceChildren();
|
|
5789
5963
|
wrap.style.background = categoricalColor(name);
|
|
5790
5964
|
wrap.textContent = initialsOf(base || name);
|
|
5791
5965
|
};
|
|
5792
|
-
if (!
|
|
5966
|
+
if (!iconUrl || iconFailed.has(iconUrl)) {
|
|
5793
5967
|
fallback();
|
|
5794
5968
|
return wrap;
|
|
5795
5969
|
}
|
|
5796
5970
|
const img = doc.createElement("img");
|
|
5797
5971
|
img.alt = "";
|
|
5798
5972
|
img.crossOrigin = "anonymous";
|
|
5799
|
-
img.src =
|
|
5973
|
+
img.src = iconUrl;
|
|
5800
5974
|
img.style.cssText = "width:100%;height:100%;border-radius:50%;display:block;object-fit:cover;";
|
|
5801
5975
|
img.addEventListener(
|
|
5802
5976
|
"error",
|
|
5803
5977
|
() => {
|
|
5804
|
-
iconFailed.add(
|
|
5978
|
+
iconFailed.add(iconUrl);
|
|
5805
5979
|
fallback();
|
|
5806
5980
|
},
|
|
5807
5981
|
{ once: true }
|
|
@@ -12054,11 +12228,14 @@ var MenuBuilder = class {
|
|
|
12054
12228
|
}
|
|
12055
12229
|
};
|
|
12056
12230
|
var ObjectTree = class extends SidePanel {
|
|
12057
|
-
constructor(host) {
|
|
12231
|
+
constructor(host, iconFor) {
|
|
12058
12232
|
super(host, "Object tree", "vela-ot");
|
|
12233
|
+
this.iconFor = iconFor;
|
|
12059
12234
|
this.chart = null;
|
|
12060
12235
|
this.selectedDrawing = null;
|
|
12061
12236
|
this.symbolName = "";
|
|
12237
|
+
/** The raw (possibly venue-prefixed) symbol — what icon resolution routes on. */
|
|
12238
|
+
this.symbolRaw = "";
|
|
12062
12239
|
/** Drawing bundles — a view-side grouping, held for the panel's lifetime and never persisted.
|
|
12063
12240
|
* Kept per chart because a workspace points this one panel at whichever chart is active, and
|
|
12064
12241
|
* each chart's bundles have to survive the switch. */
|
|
@@ -12095,6 +12272,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
12095
12272
|
if (open2) this.refresh();
|
|
12096
12273
|
}
|
|
12097
12274
|
setSymbol(symbol) {
|
|
12275
|
+
this.symbolRaw = symbol;
|
|
12098
12276
|
this.symbolName = parseSymbol(symbol).ticker;
|
|
12099
12277
|
}
|
|
12100
12278
|
/** (Re)bind to a chart instance — called after every widget rebuild. */
|
|
@@ -12295,7 +12473,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
12295
12473
|
const { chart } = pass;
|
|
12296
12474
|
if (row.kind === "price") {
|
|
12297
12475
|
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");
|
|
12476
|
+
const icon2 = tickerIconEl(doc, base || "P", this.symbolName || "Price", "vela-ot-avatar", this.symbolRaw ? this.iconFor?.(this.symbolRaw) : void 0);
|
|
12299
12477
|
const el2 = this.row(icon2, row.label, row.visible, [
|
|
12300
12478
|
{
|
|
12301
12479
|
icon: row.visible ? "eye" : "eye-off",
|
|
@@ -13327,6 +13505,17 @@ var PanelDock = class {
|
|
|
13327
13505
|
};
|
|
13328
13506
|
|
|
13329
13507
|
// src/widget/symbol-picker.ts
|
|
13508
|
+
function dedupeSymbols(list) {
|
|
13509
|
+
const seen = /* @__PURE__ */ new Set();
|
|
13510
|
+
const out = [];
|
|
13511
|
+
for (const s of list) {
|
|
13512
|
+
const key = `${(s.prefix ?? s.provider ?? "").toLowerCase()}:${s.ticker.toUpperCase()}`;
|
|
13513
|
+
if (seen.has(key)) continue;
|
|
13514
|
+
seen.add(key);
|
|
13515
|
+
out.push(s);
|
|
13516
|
+
}
|
|
13517
|
+
return out;
|
|
13518
|
+
}
|
|
13330
13519
|
var TOP_TICKERS = ["BTCUSDT", "ETHUSDT", "SOLUSDT", "BNBUSDT", "XRPUSDT", "DOGEUSDT", "ADAUSDT", "LINKUSDT"];
|
|
13331
13520
|
function parseQuery(raw, venues) {
|
|
13332
13521
|
const m = raw.match(/^\s*([^\s:]+)\s*[:\s]\s*(.*)$/);
|
|
@@ -13341,17 +13530,18 @@ function onlyOne(matches) {
|
|
|
13341
13530
|
return matches.length === 1 ? matches[0] : null;
|
|
13342
13531
|
}
|
|
13343
13532
|
var venueOf = (s) => s.prefix ?? s.provider;
|
|
13344
|
-
function filterSymbols(list, query, limit = 100) {
|
|
13533
|
+
function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
|
|
13345
13534
|
const venues = [...new Set(list.flatMap((s) => [venueOf(s)?.toLowerCase(), s.provider?.toLowerCase()]).filter((p) => !!p))];
|
|
13346
13535
|
const { scope, term } = parseQuery(query, venues);
|
|
13347
13536
|
const pool = scope ? list.filter((s) => venueOf(s)?.toLowerCase() === scope || s.provider?.toLowerCase() === scope) : list;
|
|
13348
13537
|
const q = term.toUpperCase();
|
|
13349
13538
|
if (!q) {
|
|
13350
13539
|
if (scope) return [...pool].sort((a, b) => a.ticker.localeCompare(b.ticker)).slice(0, limit);
|
|
13540
|
+
if (top === false) return pool.slice(0, limit);
|
|
13351
13541
|
const byTicker = new Map(pool.map((s) => [s.ticker.toUpperCase(), s]));
|
|
13352
|
-
const
|
|
13353
|
-
const rest = pool.filter((s) => !
|
|
13354
|
-
return [...
|
|
13542
|
+
const pinned = top.map((t) => byTicker.get(t)).filter((s) => s !== void 0);
|
|
13543
|
+
const rest = pool.filter((s) => !top.includes(s.ticker.toUpperCase()));
|
|
13544
|
+
return [...pinned, ...rest].slice(0, limit);
|
|
13355
13545
|
}
|
|
13356
13546
|
const qLower = term.toLowerCase();
|
|
13357
13547
|
const prefix = [];
|
|
@@ -13461,12 +13651,16 @@ var CSS8 = `
|
|
|
13461
13651
|
var PAGE = 100;
|
|
13462
13652
|
var SymbolPicker = class {
|
|
13463
13653
|
constructor(opts) {
|
|
13654
|
+
this.opts = opts;
|
|
13464
13655
|
this.source = () => [];
|
|
13465
13656
|
this.rows = [];
|
|
13466
13657
|
this.highlighted = 0;
|
|
13467
13658
|
this.seed = "";
|
|
13468
13659
|
this.activeTab = "All";
|
|
13469
13660
|
this.visible = PAGE;
|
|
13661
|
+
/** The ranked pool cache — `key` fingerprints the raw pool the ranking ran on. */
|
|
13662
|
+
this.ranked = null;
|
|
13663
|
+
this.ranking = false;
|
|
13470
13664
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
13471
13665
|
injectStyles(STYLE_ID7, CSS8, doc);
|
|
13472
13666
|
this.input = doc.createElement("input");
|
|
@@ -13564,10 +13758,34 @@ var SymbolPicker = class {
|
|
|
13564
13758
|
} else delete el.dataset.highlighted;
|
|
13565
13759
|
});
|
|
13566
13760
|
}
|
|
13761
|
+
/** The picker's pool: the source, shaped by the registered symbol ranking. Cached —
|
|
13762
|
+
* the hook runs when the pool CHANGES (an index lands or refreshes), never per
|
|
13763
|
+
* keystroke; an async hook resolves onto the next repaint (stale-while-revalidate
|
|
13764
|
+
* in between, the raw pool before the first resolve). */
|
|
13765
|
+
pool() {
|
|
13766
|
+
const raw = this.source();
|
|
13767
|
+
const hook = symbolRanking();
|
|
13768
|
+
if (!hook) return raw;
|
|
13769
|
+
const key = `${raw.length}:${raw[0]?.ticker ?? ""}:${raw[raw.length - 1]?.ticker ?? ""}`;
|
|
13770
|
+
if (this.ranked?.key !== key && !this.ranking) {
|
|
13771
|
+
this.ranking = true;
|
|
13772
|
+
Promise.resolve([...raw]).then((copy) => hook(copy)).then((out) => {
|
|
13773
|
+
this.ranked = { key, result: dedupeSymbols(out) };
|
|
13774
|
+
}).catch((err) => {
|
|
13775
|
+
console.warn("[vela] symbol ranking failed \u2014 pool order kept:", err);
|
|
13776
|
+
this.ranked = { key, result: [...raw] };
|
|
13777
|
+
}).finally(() => {
|
|
13778
|
+
this.ranking = false;
|
|
13779
|
+
this.refresh();
|
|
13780
|
+
});
|
|
13781
|
+
}
|
|
13782
|
+
return this.ranked?.result ?? raw;
|
|
13783
|
+
}
|
|
13567
13784
|
computeRows() {
|
|
13568
13785
|
const TAB_TYPES = { Crypto: ["crypto"], Stocks: ["stock"], ETFs: ["etf"], Forex: ["forex"], Commodities: ["commodity"] };
|
|
13569
|
-
const
|
|
13570
|
-
|
|
13786
|
+
const all = this.pool();
|
|
13787
|
+
const pool = this.activeTab === "All" ? all : all.filter((s) => TAB_TYPES[this.activeTab]?.includes((s.type ?? "").toLowerCase()) || this.activeTab === "Crypto" && (s.type ?? "").toLowerCase() === "futures");
|
|
13788
|
+
return filterSymbols(pool, this.input.value, this.visible, symbolRanking() ? false : TOP_TICKERS);
|
|
13571
13789
|
}
|
|
13572
13790
|
refresh() {
|
|
13573
13791
|
const doc = this.list.ownerDocument;
|
|
@@ -13598,7 +13816,7 @@ var SymbolPicker = class {
|
|
|
13598
13816
|
row.dataset.ticker = s.ticker;
|
|
13599
13817
|
const venue = s.prefix ?? s.provider;
|
|
13600
13818
|
if (venue) row.dataset.venue = venue;
|
|
13601
|
-
const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
|
|
13819
|
+
const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar", this.opts.iconFor?.(s));
|
|
13602
13820
|
const main = doc.createElement("span");
|
|
13603
13821
|
main.className = "vela-sp-main";
|
|
13604
13822
|
const t = doc.createElement("span");
|
|
@@ -14279,7 +14497,8 @@ var MobileBar = class {
|
|
|
14279
14497
|
if (!ctx) return;
|
|
14280
14498
|
const doc = this.el.ownerDocument;
|
|
14281
14499
|
this.actionsHost.replaceChildren();
|
|
14282
|
-
|
|
14500
|
+
const builtin = new Set(TOPBAR_BUILTIN_IDS);
|
|
14501
|
+
for (const action of widgetActions("topbar", ctx).filter((a) => a.align === "left" && !builtin.has(a.id))) {
|
|
14283
14502
|
const b = doc.createElement("button");
|
|
14284
14503
|
b.className = "vela-mb-item";
|
|
14285
14504
|
b.setAttribute("aria-label", action.label);
|
|
@@ -14808,10 +15027,10 @@ var MoreDrawer = class {
|
|
|
14808
15027
|
});
|
|
14809
15028
|
actions.appendChild(b);
|
|
14810
15029
|
};
|
|
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);
|
|
15030
|
+
if (this.opts.onUndo) action("undo", "Undo", this.opts.canUndo(), this.opts.onUndo);
|
|
15031
|
+
if (this.opts.onRedo) action("redo", "Redo", this.opts.canRedo(), this.opts.onRedo);
|
|
15032
|
+
if (this.opts.onScreenshot) action("camera", "Screenshot", true, this.opts.onScreenshot);
|
|
15033
|
+
if (actions.childElementCount > 0) this.drawer.body.appendChild(actions);
|
|
14815
15034
|
const list = doc.createElement("div");
|
|
14816
15035
|
list.className = "vela-md-list";
|
|
14817
15036
|
const current = this.opts.priceStyles().find((s) => s.id === this.opts.priceStyle());
|
|
@@ -14832,8 +15051,10 @@ var MoreDrawer = class {
|
|
|
14832
15051
|
})
|
|
14833
15052
|
);
|
|
14834
15053
|
}
|
|
14835
|
-
|
|
14836
|
-
|
|
15054
|
+
if (this.opts.alerts) {
|
|
15055
|
+
const alertCount = this.opts.alerts().length;
|
|
15056
|
+
list.appendChild(this.row(doc, "Alerts", { icon: "bell", value: alertCount > 0 ? String(alertCount) : void 0, chevron: true, onClick: () => this.show("alerts") }));
|
|
15057
|
+
}
|
|
14837
15058
|
for (const act of this.opts.actions()) {
|
|
14838
15059
|
list.appendChild(
|
|
14839
15060
|
this.row(doc, act.label, {
|
|
@@ -14914,7 +15135,7 @@ var MoreDrawer = class {
|
|
|
14914
15135
|
this.drawer.body.appendChild(list);
|
|
14915
15136
|
}
|
|
14916
15137
|
renderAlerts(doc) {
|
|
14917
|
-
const alerts = this.opts.alerts();
|
|
15138
|
+
const alerts = this.opts.alerts?.() ?? [];
|
|
14918
15139
|
if (alerts.length === 0) {
|
|
14919
15140
|
const empty = doc.createElement("div");
|
|
14920
15141
|
empty.className = "vela-md-empty";
|
|
@@ -17111,19 +17332,8 @@ function registerBuiltinChartTypes() {
|
|
|
17111
17332
|
registerChartType({ id: "heikinashi", label: "Heikin Ashi", barTransform: HEIKIN_ASHI });
|
|
17112
17333
|
}
|
|
17113
17334
|
|
|
17114
|
-
// src/workspace/sync.ts
|
|
17115
|
-
function syncTargets(originId, setting, cellIds) {
|
|
17116
|
-
if (setting == null || setting === false) return [];
|
|
17117
|
-
if (setting === true) return cellIds.filter((id) => id !== originId);
|
|
17118
|
-
const group = setting[originId];
|
|
17119
|
-
if (group == null) return [];
|
|
17120
|
-
return cellIds.filter((id) => id !== originId && setting[id] === group);
|
|
17121
|
-
}
|
|
17122
|
-
function rangesWithin(a, b, epsMs) {
|
|
17123
|
-
return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
|
|
17124
|
-
}
|
|
17125
|
-
|
|
17126
17335
|
// src/state/document.ts
|
|
17336
|
+
var SYNC_KINDS = ["viewport", "symbol", "timeframe", "crosshair", "drawings", "style"];
|
|
17127
17337
|
function prefixedSymbol(cell) {
|
|
17128
17338
|
if (!cell?.symbol) return void 0;
|
|
17129
17339
|
if (cell.symbol.includes(":") || !cell.provider) return cell.symbol;
|
|
@@ -17211,7 +17421,7 @@ function sanitizeSync(raw) {
|
|
|
17211
17421
|
if (raw == null || typeof raw !== "object") return null;
|
|
17212
17422
|
const s = raw;
|
|
17213
17423
|
const out = {};
|
|
17214
|
-
for (const kind of
|
|
17424
|
+
for (const kind of SYNC_KINDS) {
|
|
17215
17425
|
const v = s[kind];
|
|
17216
17426
|
if (v === true) out[kind] = true;
|
|
17217
17427
|
else if (v != null && typeof v === "object") {
|
|
@@ -17254,6 +17464,28 @@ function sanitizeTrackSizes(raw) {
|
|
|
17254
17464
|
return Object.keys(out).length > 0 ? out : null;
|
|
17255
17465
|
}
|
|
17256
17466
|
|
|
17467
|
+
// src/workspace/sync.ts
|
|
17468
|
+
function syncTargets(originId, setting, cellIds) {
|
|
17469
|
+
if (setting == null || setting === false) return [];
|
|
17470
|
+
if (setting === true) return cellIds.filter((id) => id !== originId);
|
|
17471
|
+
const group = setting[originId];
|
|
17472
|
+
if (group == null) return [];
|
|
17473
|
+
return cellIds.filter((id) => id !== originId && setting[id] === group);
|
|
17474
|
+
}
|
|
17475
|
+
function rangesWithin(a, b, epsMs) {
|
|
17476
|
+
return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
|
|
17477
|
+
}
|
|
17478
|
+
var STYLE_SYNC_CONFIG_KEYS = ["layout", "panes", "grid", "priceScale", "crosshair"];
|
|
17479
|
+
function styleConfigSlice(config) {
|
|
17480
|
+
if (config == null || typeof config !== "object") return null;
|
|
17481
|
+
const doc = config;
|
|
17482
|
+
const out = {};
|
|
17483
|
+
for (const key of STYLE_SYNC_CONFIG_KEYS) {
|
|
17484
|
+
if (doc[key] != null && typeof doc[key] === "object") out[key] = doc[key];
|
|
17485
|
+
}
|
|
17486
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
17487
|
+
}
|
|
17488
|
+
|
|
17257
17489
|
// src/widget/persist.ts
|
|
17258
17490
|
function localStorageAdapter(storageKey) {
|
|
17259
17491
|
return {
|
|
@@ -19299,6 +19531,12 @@ var DataControl = class {
|
|
|
19299
19531
|
symbols(provider) {
|
|
19300
19532
|
return this.registry?.symbols(provider) ?? [];
|
|
19301
19533
|
}
|
|
19534
|
+
/** The icon URL for `symbol` — its owning provider's `resolveSymbolIcon`, routed
|
|
19535
|
+
* through resolution. Undefined while unresolvable, when the provider declares no
|
|
19536
|
+
* resolver, or on a custom `deps.dataFeed` — the shells then show initials. */
|
|
19537
|
+
symbolIcon(symbol) {
|
|
19538
|
+
return this.registry?.symbolIcon(symbol);
|
|
19539
|
+
}
|
|
19302
19540
|
/** Per-symbol metadata (Pine `syminfo.*`), resolved through the owning provider. */
|
|
19303
19541
|
symbolInfo(symbol) {
|
|
19304
19542
|
return this.registry?.symbolInfoFor(symbol) ?? Promise.resolve(void 0);
|
|
@@ -19588,12 +19826,16 @@ var IndicatorInputsDialog = class {
|
|
|
19588
19826
|
closeOnEscape: false,
|
|
19589
19827
|
footer: (foot) => {
|
|
19590
19828
|
foot.append(
|
|
19829
|
+
this.resetAction(),
|
|
19591
19830
|
this.dialogButton("Cancel", false, () => this.revertAndClose()),
|
|
19592
19831
|
this.dialogButton("Ok", true, () => this.close())
|
|
19593
19832
|
);
|
|
19594
19833
|
},
|
|
19834
|
+
// Stale-guard: destroying a dialog fires its machine's onOpenChange(false)
|
|
19835
|
+
// asynchronously — after a reset rebuild that notification must not close
|
|
19836
|
+
// the replacement dialog.
|
|
19595
19837
|
onOpenChange: (open2) => {
|
|
19596
|
-
if (!open2) this.close();
|
|
19838
|
+
if (!open2 && this.uiDialog === ui) this.close();
|
|
19597
19839
|
}
|
|
19598
19840
|
});
|
|
19599
19841
|
applyChromeTokens(ui.panel, t);
|
|
@@ -19724,6 +19966,30 @@ var IndicatorInputsDialog = class {
|
|
|
19724
19966
|
}
|
|
19725
19967
|
this.close();
|
|
19726
19968
|
}
|
|
19969
|
+
/** The footer's reset button — same chip as Cancel, pinned to the LEFT edge
|
|
19970
|
+
* (`margin-right:auto` against the footer's flex-end keeps Cancel/Ok right). */
|
|
19971
|
+
resetAction() {
|
|
19972
|
+
const b = this.dialogButton("Reset defaults", false, () => this.resetToDefaults());
|
|
19973
|
+
b.style.marginRight = "auto";
|
|
19974
|
+
return b;
|
|
19975
|
+
}
|
|
19976
|
+
/** Restore every input to its declared default (re-running the indicator), then
|
|
19977
|
+
* re-open the form so each control re-reads the restored values — the same
|
|
19978
|
+
* rebuild-after-reset move as the chart-settings dialog. The open-time snapshot
|
|
19979
|
+
* survives the rebuild, so Cancel after a reset still reverts the whole session. */
|
|
19980
|
+
resetToDefaults() {
|
|
19981
|
+
const row = this.row;
|
|
19982
|
+
if (!row) return;
|
|
19983
|
+
const snap = this.snapshot;
|
|
19984
|
+
for (const inp of row.inputs) {
|
|
19985
|
+
if (row.values[inp.key] !== inp.defval) {
|
|
19986
|
+
row.values[inp.key] = inp.defval;
|
|
19987
|
+
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value: inp.defval });
|
|
19988
|
+
}
|
|
19989
|
+
}
|
|
19990
|
+
this.open(row);
|
|
19991
|
+
if (snap) this.snapshot = snap;
|
|
19992
|
+
}
|
|
19727
19993
|
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
19728
19994
|
commit(row, key, value) {
|
|
19729
19995
|
row.values[key] = value;
|
|
@@ -31157,6 +31423,11 @@ function resizeSplit(split, dyTotal, minPx = MIN_PANE_PX) {
|
|
|
31157
31423
|
}
|
|
31158
31424
|
|
|
31159
31425
|
// src/renderers/native/backdrop/BackdropRenderer.ts
|
|
31426
|
+
function clipHighlightRect(x1, x2, left, right) {
|
|
31427
|
+
const x = Math.max(left, x1);
|
|
31428
|
+
const end = Math.min(right, x2);
|
|
31429
|
+
return end > x ? { x, width: end - x } : null;
|
|
31430
|
+
}
|
|
31160
31431
|
var BackdropRenderer = class {
|
|
31161
31432
|
constructor() {
|
|
31162
31433
|
this.canvas = null;
|
|
@@ -31189,17 +31460,22 @@ var BackdropRenderer = class {
|
|
|
31189
31460
|
/** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
|
|
31190
31461
|
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
31191
31462
|
drawHighlights(ctx, scene, coords) {
|
|
31192
|
-
const
|
|
31193
|
-
if (
|
|
31463
|
+
const sessions = scene.sessionHighlightBands();
|
|
31464
|
+
if (sessions.length > 0) {
|
|
31465
|
+
const left = Math.max(0, coords.logicalToX(-0.5));
|
|
31466
|
+
const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
|
|
31467
|
+
this.drawHighlightSet(ctx, sessions, coords, left, right);
|
|
31468
|
+
}
|
|
31469
|
+
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
|
|
31470
|
+
}
|
|
31471
|
+
drawHighlightSet(ctx, bands, coords, left, right) {
|
|
31194
31472
|
for (const band of bands) {
|
|
31195
31473
|
const x1 = coords.timeToX(band.from);
|
|
31196
31474
|
const x2 = coords.timeToX(band.to);
|
|
31197
|
-
|
|
31198
|
-
|
|
31199
|
-
const cw = Math.min(coords.width, x2) - cx;
|
|
31200
|
-
if (cw <= 0) continue;
|
|
31475
|
+
const rect = clipHighlightRect(x1, x2, left, right);
|
|
31476
|
+
if (!rect) continue;
|
|
31201
31477
|
ctx.fillStyle = band.color;
|
|
31202
|
-
ctx.fillRect(
|
|
31478
|
+
ctx.fillRect(rect.x, 0, rect.width, coords.height);
|
|
31203
31479
|
}
|
|
31204
31480
|
}
|
|
31205
31481
|
// ── grid ── vert/horz gate on `scene.showGrid` AND their own per-axis visibility
|
|
@@ -35647,8 +35923,9 @@ function statuslineInkOf(renderer, priceStyle) {
|
|
|
35647
35923
|
}
|
|
35648
35924
|
}
|
|
35649
35925
|
var Statusline = class {
|
|
35650
|
-
constructor(host, symbol) {
|
|
35926
|
+
constructor(host, symbol, iconFor) {
|
|
35651
35927
|
this.host = host;
|
|
35928
|
+
this.iconFor = iconFor;
|
|
35652
35929
|
this.parts = { name: true, market: true, ohlc: true, change: true };
|
|
35653
35930
|
this.lastBar = null;
|
|
35654
35931
|
this.hoverBar = null;
|
|
@@ -35672,7 +35949,7 @@ var Statusline = class {
|
|
|
35672
35949
|
this.el.className = "vela-statusline";
|
|
35673
35950
|
this.el.dataset.velaScreenshot = "1";
|
|
35674
35951
|
const ticker = parseSymbol(symbol).ticker;
|
|
35675
|
-
this.avatarEl = tickerIconEl(doc, baseOfTicker(ticker), ticker, "vela-sl-avatar");
|
|
35952
|
+
this.avatarEl = tickerIconEl(doc, baseOfTicker(ticker), ticker, "vela-sl-avatar", this.iconFor?.(symbol));
|
|
35676
35953
|
this.symbolEl = doc.createElement("span");
|
|
35677
35954
|
this.symbolEl.className = "vela-sl-symbol";
|
|
35678
35955
|
this.symbolEl.textContent = ticker;
|
|
@@ -35693,7 +35970,7 @@ var Statusline = class {
|
|
|
35693
35970
|
setSymbol(symbol) {
|
|
35694
35971
|
const ticker = parseSymbol(symbol).ticker;
|
|
35695
35972
|
this.symbolEl.textContent = ticker;
|
|
35696
|
-
const fresh = tickerIconEl(this.el.ownerDocument, baseOfTicker(ticker), ticker, "vela-sl-avatar");
|
|
35973
|
+
const fresh = tickerIconEl(this.el.ownerDocument, baseOfTicker(ticker), ticker, "vela-sl-avatar", this.iconFor?.(symbol));
|
|
35697
35974
|
this.avatarEl.replaceWith(fresh);
|
|
35698
35975
|
this.avatarEl = fresh;
|
|
35699
35976
|
this.fit();
|
|
@@ -35940,61 +36217,144 @@ var MarketStatusTracker = class {
|
|
|
35940
36217
|
};
|
|
35941
36218
|
|
|
35942
36219
|
// src/widget/session-shading.ts
|
|
35943
|
-
|
|
36220
|
+
var DAY_MS2 = 864e5;
|
|
36221
|
+
var MINUTE_MS = 6e4;
|
|
36222
|
+
function parseWindow(text) {
|
|
36223
|
+
if (typeof text !== "string") return null;
|
|
36224
|
+
const m = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/.exec(text);
|
|
36225
|
+
if (!m) return null;
|
|
36226
|
+
const start = Number(m[1]) * 60 + Number(m[2]);
|
|
36227
|
+
const end = Number(m[3]) * 60 + Number(m[4]);
|
|
36228
|
+
if (start >= end || end > 1440) return null;
|
|
36229
|
+
return { start, end };
|
|
36230
|
+
}
|
|
36231
|
+
function parseSessionSpec(si) {
|
|
36232
|
+
const session = si?.["session"];
|
|
36233
|
+
if (typeof session !== "string" || session === "" || session === "24x7") return null;
|
|
36234
|
+
const regular = parseWindow(session);
|
|
36235
|
+
if (!regular) return null;
|
|
36236
|
+
const tz = si?.["timezone"];
|
|
36237
|
+
const timezone = typeof tz === "string" && tz !== "" ? tz : "Etc/UTC";
|
|
36238
|
+
const ext = parseWindow(si?.["session_extended"]);
|
|
36239
|
+
const extended = ext && ext.start <= regular.start && ext.end >= regular.end ? ext : { start: 0, end: 1440 };
|
|
36240
|
+
return { regular, extended, timezone };
|
|
36241
|
+
}
|
|
36242
|
+
var dtfCache = /* @__PURE__ */ new Map();
|
|
36243
|
+
function civilFormatter(tz) {
|
|
36244
|
+
const cached = dtfCache.get(tz);
|
|
36245
|
+
if (cached !== void 0) return cached;
|
|
36246
|
+
let dtf = null;
|
|
36247
|
+
try {
|
|
36248
|
+
dtf = new Intl.DateTimeFormat("en-US", {
|
|
36249
|
+
timeZone: tz,
|
|
36250
|
+
hourCycle: "h23",
|
|
36251
|
+
weekday: "short",
|
|
36252
|
+
year: "numeric",
|
|
36253
|
+
month: "2-digit",
|
|
36254
|
+
day: "2-digit",
|
|
36255
|
+
hour: "2-digit",
|
|
36256
|
+
minute: "2-digit"
|
|
36257
|
+
});
|
|
36258
|
+
} catch {
|
|
36259
|
+
dtf = null;
|
|
36260
|
+
}
|
|
36261
|
+
dtfCache.set(tz, dtf);
|
|
36262
|
+
return dtf;
|
|
36263
|
+
}
|
|
36264
|
+
function civilParts(dtf, ms) {
|
|
36265
|
+
const parts = dtf.formatToParts(ms);
|
|
36266
|
+
const get = (t) => parts.find((p) => p.type === t)?.value ?? "";
|
|
36267
|
+
return {
|
|
36268
|
+
weekday: get("weekday"),
|
|
36269
|
+
year: Number(get("year")),
|
|
36270
|
+
month: Number(get("month")),
|
|
36271
|
+
day: Number(get("day")),
|
|
36272
|
+
hour: Number(get("hour")) % 24,
|
|
36273
|
+
minute: Number(get("minute"))
|
|
36274
|
+
};
|
|
36275
|
+
}
|
|
36276
|
+
function expandSessionZones(spec, from, to) {
|
|
35944
36277
|
const pre = [];
|
|
35945
36278
|
const post = [];
|
|
35946
|
-
|
|
35947
|
-
|
|
35948
|
-
|
|
35949
|
-
|
|
35950
|
-
|
|
35951
|
-
|
|
35952
|
-
|
|
35953
|
-
|
|
36279
|
+
const dtf = civilFormatter(spec.timezone);
|
|
36280
|
+
if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post };
|
|
36281
|
+
const seen = /* @__PURE__ */ new Set();
|
|
36282
|
+
for (let cursor = from - DAY_MS2; cursor < to + DAY_MS2; cursor += DAY_MS2) {
|
|
36283
|
+
const civil = civilParts(dtf, cursor);
|
|
36284
|
+
const key = civil.year * 1e4 + civil.month * 100 + civil.day;
|
|
36285
|
+
if (!Number.isFinite(key) || seen.has(key)) continue;
|
|
36286
|
+
seen.add(key);
|
|
36287
|
+
if (civil.weekday === "Sat" || civil.weekday === "Sun") continue;
|
|
36288
|
+
const naive = Date.UTC(civil.year, civil.month - 1, civil.day);
|
|
36289
|
+
const noonGuess = naive + 720 * MINUTE_MS;
|
|
36290
|
+
const atNoon = civilParts(dtf, noonGuess);
|
|
36291
|
+
const offset = Date.UTC(atNoon.year, atNoon.month - 1, atNoon.day, atNoon.hour, atNoon.minute) - noonGuess;
|
|
36292
|
+
const at = (minutes) => naive + minutes * MINUTE_MS - offset;
|
|
36293
|
+
if (spec.extended.start < spec.regular.start) pre.push([at(spec.extended.start), at(spec.regular.start)]);
|
|
36294
|
+
if (spec.regular.end < spec.extended.end) post.push([at(spec.regular.end), at(spec.extended.end)]);
|
|
35954
36295
|
}
|
|
35955
36296
|
return { pre, post };
|
|
35956
36297
|
}
|
|
35957
|
-
var
|
|
35958
|
-
var MIN_LOOKBACK_MS = 3 * 864e5;
|
|
35959
|
-
var MAX_LOOKBACK_MS = 120 * 864e5;
|
|
36298
|
+
var COVER_PAD_MIN_MS = 2 * DAY_MS2;
|
|
35960
36299
|
var SessionShadingTracker = class {
|
|
35961
36300
|
constructor(onZones) {
|
|
35962
36301
|
this.onZones = onZones;
|
|
35963
|
-
/** Invalidates
|
|
36302
|
+
/** Invalidates the one async step (metadata resolution) — bumped by track()/stop(). */
|
|
35964
36303
|
this.epoch = 0;
|
|
35965
|
-
|
|
35966
|
-
|
|
36304
|
+
this.spec = null;
|
|
36305
|
+
this.ready = false;
|
|
36306
|
+
this.session = "regular";
|
|
36307
|
+
this.covered = null;
|
|
36308
|
+
/** The newest range seen — viewport moves during metadata resolution (a load's fit
|
|
36309
|
+
* animation) must not be lost, so the resolution always expands the LATEST range. */
|
|
36310
|
+
this.lastRange = null;
|
|
36311
|
+
}
|
|
36312
|
+
/** (Re)bind to a chart's data surface + market and expand once metadata lands. */
|
|
35967
36313
|
track(data, symbol, opts) {
|
|
35968
36314
|
const my = ++this.epoch;
|
|
35969
|
-
|
|
36315
|
+
this.ready = false;
|
|
36316
|
+
this.spec = null;
|
|
36317
|
+
this.covered = null;
|
|
36318
|
+
this.session = opts.session;
|
|
36319
|
+
this.lastRange = opts.range;
|
|
36320
|
+
void data.symbolInfo(symbol).catch(() => void 0).then((si) => {
|
|
36321
|
+
if (my !== this.epoch) return;
|
|
36322
|
+
this.ready = true;
|
|
36323
|
+
this.spec = parseSessionSpec(si);
|
|
36324
|
+
this.emit(this.lastRange ?? opts.range, true);
|
|
36325
|
+
});
|
|
36326
|
+
}
|
|
36327
|
+
/** Follow a pan/zoom synchronously: bands are epoch-anchored, so only a range that
|
|
36328
|
+
* leaves the last expansion's coverage needs a recompute — no fetch, no debounce. */
|
|
36329
|
+
updateRange(range) {
|
|
36330
|
+
this.lastRange = range;
|
|
36331
|
+
if (!this.ready) return;
|
|
36332
|
+
this.emit(range, false);
|
|
35970
36333
|
}
|
|
35971
36334
|
stop() {
|
|
35972
36335
|
this.epoch += 1;
|
|
35973
|
-
|
|
35974
|
-
|
|
35975
|
-
|
|
35976
|
-
|
|
35977
|
-
|
|
35978
|
-
|
|
35979
|
-
|
|
35980
|
-
|
|
35981
|
-
this.onZones(null);
|
|
36336
|
+
this.ready = false;
|
|
36337
|
+
this.spec = null;
|
|
36338
|
+
this.covered = null;
|
|
36339
|
+
this.lastRange = null;
|
|
36340
|
+
}
|
|
36341
|
+
emit(range, force) {
|
|
36342
|
+
if (!this.spec) {
|
|
36343
|
+
if (force) this.onZones(null);
|
|
35982
36344
|
return;
|
|
35983
36345
|
}
|
|
35984
|
-
if (
|
|
35985
|
-
this.onZones({ pre: [], post: [] });
|
|
36346
|
+
if (this.session !== "extended") {
|
|
36347
|
+
if (force) this.onZones({ pre: [], post: [] });
|
|
35986
36348
|
return;
|
|
35987
36349
|
}
|
|
35988
|
-
const
|
|
35989
|
-
const
|
|
35990
|
-
|
|
35991
|
-
|
|
35992
|
-
|
|
35993
|
-
|
|
35994
|
-
|
|
35995
|
-
|
|
35996
|
-
if (!regular || !extended) return;
|
|
35997
|
-
this.onZones(deriveSessionZones(regular, extended));
|
|
36350
|
+
const from = Math.min(range.from, range.to);
|
|
36351
|
+
const to = Math.max(range.from, range.to);
|
|
36352
|
+
if (!Number.isFinite(from) || !Number.isFinite(to)) return;
|
|
36353
|
+
if (!force && this.covered && from >= this.covered.from && to <= this.covered.to) return;
|
|
36354
|
+
const pad = Math.max(to - from, COVER_PAD_MIN_MS);
|
|
36355
|
+
const covered = { from: from - pad, to: to + pad };
|
|
36356
|
+
this.covered = covered;
|
|
36357
|
+
this.onZones(expandSessionZones(this.spec, covered.from, covered.to));
|
|
35998
36358
|
}
|
|
35999
36359
|
};
|
|
36000
36360
|
|
|
@@ -36431,7 +36791,11 @@ var ChartCell = class {
|
|
|
36431
36791
|
this.deps.toast(`No registered provider serves "${symbol2}" (registered: ${list})`, "error", 6e3);
|
|
36432
36792
|
});
|
|
36433
36793
|
this.inner.on("load:start", () => this.watermark?.setLoading(true));
|
|
36434
|
-
this.inner.on("load:end", () =>
|
|
36794
|
+
this.inner.on("load:end", () => {
|
|
36795
|
+
this.watermark?.setLoading(false);
|
|
36796
|
+
this.refreshSessionShading();
|
|
36797
|
+
});
|
|
36798
|
+
this.inner.on("viewport:changed", (range) => this.sessionShading.updateRange(range));
|
|
36435
36799
|
const tz = deps.timezone();
|
|
36436
36800
|
if (tz !== "Etc/UTC") this.inner.renderer.set("timezone", tz);
|
|
36437
36801
|
this.indicatorTitlesOn = seed.indicatorTitles ?? true;
|
|
@@ -36441,12 +36805,15 @@ var ChartCell = class {
|
|
|
36441
36805
|
this.watermarkOn = seed.watermark ?? deps.watermark;
|
|
36442
36806
|
this.watermark = deps.watermark ? new Watermark(this.host, symbol ?? "", seed.timeframe ?? "60") : null;
|
|
36443
36807
|
if (!this.watermarkOn) this.watermark?.setVisible(false);
|
|
36444
|
-
this.statusline = deps.statusline ? new Statusline(this.host, symbol ?? "") : null;
|
|
36808
|
+
this.statusline = deps.statusline ? new Statusline(this.host, symbol ?? "", (sym) => this.inner?.data.symbolIcon(sym)) : null;
|
|
36445
36809
|
this.statusline?.setMeta(seed.timeframe ?? "60", this.state.provider ?? "");
|
|
36446
36810
|
this.statusline?.onChart(this.inner);
|
|
36447
36811
|
this.marketStatus = this.statusline ? new MarketStatusTracker((s) => this.statusline?.setMarketStatus(s)) : null;
|
|
36448
36812
|
void this.inner.data.ready().then(() => {
|
|
36449
|
-
if (this.inner && this.state.symbol)
|
|
36813
|
+
if (this.inner && this.state.symbol) {
|
|
36814
|
+
this.statusline?.setSymbol(this.state.symbol);
|
|
36815
|
+
this.statusline?.setMeta(this.state.timeframe ?? "60", this.inner.data.displayPrefix(this.state.symbol) ?? this.state.provider ?? "");
|
|
36816
|
+
}
|
|
36450
36817
|
this.refreshSessionAvailable();
|
|
36451
36818
|
if (this.inner && this.state.symbol) this.marketStatus?.track(this.inner.data, this.state.symbol);
|
|
36452
36819
|
});
|
|
@@ -36555,14 +36922,18 @@ var ChartCell = class {
|
|
|
36555
36922
|
this.refreshSessionShading();
|
|
36556
36923
|
});
|
|
36557
36924
|
}
|
|
36558
|
-
/** (Re)derive the pre/post-market shading bands for this cell's market. The
|
|
36559
|
-
*
|
|
36925
|
+
/** (Re)derive the pre/post-market shading bands for this cell's market. The bands
|
|
36926
|
+
* expand locally from the symbol's session vocabulary, so they paint as soon as
|
|
36927
|
+
* metadata is known and follow any pan depth without provider round trips. */
|
|
36560
36928
|
refreshSessionShading() {
|
|
36561
36929
|
const chart = this.inner;
|
|
36562
36930
|
const symbol = this.state.symbol;
|
|
36563
36931
|
if (!chart || !symbol) return;
|
|
36564
|
-
const
|
|
36565
|
-
this.
|
|
36932
|
+
const now = Date.now();
|
|
36933
|
+
const requestedSpan = Math.max(this.state.bars ?? 1e3, this.rangeBars) * timeframeMs(this.state.timeframe ?? "60");
|
|
36934
|
+
const fallbackSpan = Number.isFinite(requestedSpan) ? Math.max(3 * 864e5, requestedSpan) : 3 * 864e5;
|
|
36935
|
+
const range = chart.getVisibleRange() ?? { from: now - fallbackSpan, to: now };
|
|
36936
|
+
this.sessionShading.track(chart.data, symbol, { session: this.session, range });
|
|
36566
36937
|
}
|
|
36567
36938
|
/** The session-shade colors live in the renderer CONFIG (persisted with it, edited
|
|
36568
36939
|
* live by the dialog swatch) — the cell only proxies them into its settings rows. */
|
|
@@ -36658,10 +37029,10 @@ var ChartCell = class {
|
|
|
36658
37029
|
id: "status-line",
|
|
36659
37030
|
rows: [
|
|
36660
37031
|
{ kind: "heading", label: "Status line", id: "parts" },
|
|
36661
|
-
{ kind: "toggle", label: "Symbol name", id: "name", get: () => sl.partVisible("name"), set: (v) =>
|
|
36662
|
-
{ kind: "toggle", label: "Market status", id: "market", get: () => sl.partVisible("market"), set: (v) =>
|
|
36663
|
-
{ kind: "toggle", label: "OHLC values", id: "ohlc", get: () => sl.partVisible("ohlc"), set: (v) =>
|
|
36664
|
-
{ kind: "toggle", label: "Bar change values", id: "change", get: () => sl.partVisible("change"), set: (v) =>
|
|
37032
|
+
{ kind: "toggle", label: "Symbol name", id: "name", get: () => sl.partVisible("name"), set: (v) => this.setStatuslinePart("name", v) },
|
|
37033
|
+
{ kind: "toggle", label: "Market status", id: "market", get: () => sl.partVisible("market"), set: (v) => this.setStatuslinePart("market", v) },
|
|
37034
|
+
{ kind: "toggle", label: "OHLC values", id: "ohlc", get: () => sl.partVisible("ohlc"), set: (v) => this.setStatuslinePart("ohlc", v) },
|
|
37035
|
+
{ kind: "toggle", label: "Bar change values", id: "change", get: () => sl.partVisible("change"), set: (v) => this.setStatuslinePart("change", v) },
|
|
36665
37036
|
{ kind: "heading", label: "Indicators", id: "indicators" },
|
|
36666
37037
|
{
|
|
36667
37038
|
kind: "toggle",
|
|
@@ -36696,12 +37067,40 @@ var ChartCell = class {
|
|
|
36696
37067
|
this.indicatorTitlesOn = visible;
|
|
36697
37068
|
this.inner?.renderer.set("indicatorTitles", visible);
|
|
36698
37069
|
this.deps.onStateDirty();
|
|
37070
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
36699
37071
|
}
|
|
36700
37072
|
/** Show/hide the plot values beside this cell's legend titles (persisted per cell). */
|
|
36701
37073
|
setIndicatorValuesVisible(visible) {
|
|
36702
37074
|
this.indicatorValuesOn = visible;
|
|
36703
37075
|
this.inner?.renderer.set("indicatorValues", visible);
|
|
36704
37076
|
this.deps.onStateDirty();
|
|
37077
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
37078
|
+
}
|
|
37079
|
+
/** Show/hide one status-line segment (the settings dialog's Status line tab). */
|
|
37080
|
+
setStatuslinePart(part, visible) {
|
|
37081
|
+
this.statusline?.setPartVisible(part, visible);
|
|
37082
|
+
this.deps.onStatusPrefsChanged(this.id);
|
|
37083
|
+
}
|
|
37084
|
+
/** This cell's Status line tab prefs as one bundle (see {@link CellStatusPrefs}). */
|
|
37085
|
+
statusPrefs() {
|
|
37086
|
+
const sl = this.statusline;
|
|
37087
|
+
return {
|
|
37088
|
+
parts: sl ? { name: sl.partVisible("name"), market: sl.partVisible("market"), ohlc: sl.partVisible("ohlc"), change: sl.partVisible("change") } : null,
|
|
37089
|
+
indicatorTitles: this.indicatorTitlesOn,
|
|
37090
|
+
indicatorValues: this.indicatorValuesOn
|
|
37091
|
+
};
|
|
37092
|
+
}
|
|
37093
|
+
/** Converge this cell's Status line tab prefs to `prefs` — the follower half of
|
|
37094
|
+
* the workspace's style link. Idempotent: matching values change nothing, so a
|
|
37095
|
+
* propagated echo dies on its own. */
|
|
37096
|
+
applyStatusPrefs(prefs) {
|
|
37097
|
+
if (prefs.parts && this.statusline) {
|
|
37098
|
+
for (const part of Object.keys(prefs.parts)) {
|
|
37099
|
+
if (this.statusline.partVisible(part) !== prefs.parts[part]) this.statusline.setPartVisible(part, prefs.parts[part]);
|
|
37100
|
+
}
|
|
37101
|
+
}
|
|
37102
|
+
if (prefs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(prefs.indicatorTitles);
|
|
37103
|
+
if (prefs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(prefs.indicatorValues);
|
|
36705
37104
|
}
|
|
36706
37105
|
/** The LIVE chart of this cell — never cache it across a layout change (the cell's
|
|
36707
37106
|
* identity is what endures; the chart dies with the cell). */
|
|
@@ -37108,16 +37507,21 @@ var ChartCell = class {
|
|
|
37108
37507
|
|
|
37109
37508
|
// src/workspace/context.ts
|
|
37110
37509
|
function buildContext(host) {
|
|
37111
|
-
const active = host.active();
|
|
37112
37510
|
return {
|
|
37113
37511
|
get chart() {
|
|
37114
37512
|
const cell = host.active();
|
|
37115
37513
|
if (!cell) throw new Error("VelaWorkspace has no active cell yet");
|
|
37116
37514
|
return cell.chart;
|
|
37117
37515
|
},
|
|
37118
|
-
|
|
37119
|
-
|
|
37120
|
-
|
|
37516
|
+
get symbol() {
|
|
37517
|
+
return host.active()?.symbol ?? "";
|
|
37518
|
+
},
|
|
37519
|
+
get timeframe() {
|
|
37520
|
+
return host.active()?.timeframe ?? "60";
|
|
37521
|
+
},
|
|
37522
|
+
get priceStyle() {
|
|
37523
|
+
return host.active()?.priceStyle ?? "candles";
|
|
37524
|
+
},
|
|
37121
37525
|
setSymbol: (symbol) => host.active()?.setSymbol(symbol),
|
|
37122
37526
|
setTimeframe: (tf) => host.active()?.setTimeframe(tf),
|
|
37123
37527
|
setPriceStyle: (style) => host.active()?.setPriceStyle(style),
|
|
@@ -37128,8 +37532,12 @@ function buildContext(host) {
|
|
|
37128
37532
|
addIndicator: (entry) => host.active()?.addExternalIndicator(entry),
|
|
37129
37533
|
addNativeIndicator: (type) => host.active()?.addNative(type),
|
|
37130
37534
|
stateChanged: () => host.stateDirty(),
|
|
37131
|
-
|
|
37132
|
-
|
|
37535
|
+
get cells() {
|
|
37536
|
+
return host.cells().map((c) => ({ id: c.id, chart: c.chart, symbol: c.symbol, timeframe: c.timeframe }));
|
|
37537
|
+
},
|
|
37538
|
+
get activeCellId() {
|
|
37539
|
+
return host.active()?.id ?? "";
|
|
37540
|
+
},
|
|
37133
37541
|
setActiveCell: (id) => host.setActiveCell(id)
|
|
37134
37542
|
};
|
|
37135
37543
|
}
|
|
@@ -37462,6 +37870,10 @@ var VelaWorkspace = class {
|
|
|
37462
37870
|
/** Same guard for the drawings link: the propagated mutations' own `drawing:*`
|
|
37463
37871
|
* events fire synchronously inside the propagation loop and must not fan out again. */
|
|
37464
37872
|
this.drawingSyncBusy = false;
|
|
37873
|
+
/** Same guard for the style link: a follower's `applyConfig` re-fires its
|
|
37874
|
+
* `onConfigChanged` in the same tick, and a state restore applies per-cell
|
|
37875
|
+
* configs that legitimately differ — neither must propagate. */
|
|
37876
|
+
this.styleSyncBusy = false;
|
|
37465
37877
|
/** LINKED drawings (the drawings sync): one map per synced set (cellId → that
|
|
37466
37878
|
* cell's drawing id), reachable from every member under its `cellId\0drawingId`
|
|
37467
37879
|
* key — any member finds its peers to push edits/removals onto. Survives a
|
|
@@ -37522,13 +37934,14 @@ var VelaWorkspace = class {
|
|
|
37522
37934
|
if (boot?.favorites) this.favs = [...boot.favorites];
|
|
37523
37935
|
if (boot?.timeframeFavorites) this.tfFavs = [...boot.timeframeFavorites];
|
|
37524
37936
|
const sync = boot?.sync ?? opts.sync;
|
|
37525
|
-
for (const kind of
|
|
37526
|
-
this.applySyncSetting(kind, sync?.[kind]);
|
|
37527
|
-
}
|
|
37937
|
+
for (const kind of SYNC_KINDS) this.applySyncSetting(kind, sync?.[kind]);
|
|
37528
37938
|
this.monoLayout = opts.layout === false;
|
|
37529
37939
|
const optLayout = opts.layout === false || opts.layout === void 0 ? "4" : opts.layout;
|
|
37530
37940
|
this.def = this.resolveLayout(this.monoLayout ? "1" : boot?.layout && ensureLayout(boot.layout) ? boot.layout : optLayout);
|
|
37531
37941
|
this.alertCap = Math.max(1, opts.alertCap ?? ALERT_CAP);
|
|
37942
|
+
this.topbarComp = resolveTopbarComposition(opts.topbar);
|
|
37943
|
+
this.indicatorsOverride = topbarActionOverride("indicators");
|
|
37944
|
+
this.screenshotOverride = topbarActionOverride("screenshot");
|
|
37532
37945
|
if (boot?.trackSizes) for (const [id, ts] of Object.entries(boot.trackSizes)) this.trackSizes.set(id, ts);
|
|
37533
37946
|
if (boot?.ext) this.extState = { ...boot.ext };
|
|
37534
37947
|
if (boot?.charts) for (const { id, ...cs } of boot.charts) this.pool.set(id, cs);
|
|
@@ -37545,6 +37958,8 @@ var VelaWorkspace = class {
|
|
|
37545
37958
|
});
|
|
37546
37959
|
this.symbolPicker = new SymbolPicker({
|
|
37547
37960
|
host: this.root,
|
|
37961
|
+
// Row icons come from each descriptor's OWNING provider (resolveSymbolIcon).
|
|
37962
|
+
iconFor: (d) => this.feed.symbolIconOf(d),
|
|
37548
37963
|
onSelect: (ticker) => this.active.setSymbol(ticker),
|
|
37549
37964
|
onOpenChange: (open2) => {
|
|
37550
37965
|
if (open2) for (const cell of this.cells()) cell.chart.renderer.closeDialogs();
|
|
@@ -37552,7 +37967,7 @@ var VelaWorkspace = class {
|
|
|
37552
37967
|
}
|
|
37553
37968
|
});
|
|
37554
37969
|
this.symbolPicker.setSource(() => this.feed.symbols());
|
|
37555
|
-
this.indicatorPicker = opts.indicatorPicker !== false ? new IndicatorPicker({
|
|
37970
|
+
this.indicatorPicker = opts.indicatorPicker !== false && !this.indicatorsOverride && topbarHas(this.topbarComp, "indicators") ? new IndicatorPicker({
|
|
37556
37971
|
host: this.root,
|
|
37557
37972
|
library: () => this.active.libraryRows(),
|
|
37558
37973
|
onChart: () => this.active.onChartRows(),
|
|
@@ -37571,6 +37986,9 @@ var VelaWorkspace = class {
|
|
|
37571
37986
|
});
|
|
37572
37987
|
this.topbar = new Topbar(this.root, {
|
|
37573
37988
|
symbol: "",
|
|
37989
|
+
// RAW option, not the resolved lists — the bar distinguishes a host-declared
|
|
37990
|
+
// side (list is law) from a default one (an override's `order` may flow).
|
|
37991
|
+
composition: opts.topbar,
|
|
37574
37992
|
onSymbolClick: () => this.symbolPicker.open(),
|
|
37575
37993
|
...picker ? { onIndicatorsClick: () => picker.open() } : {},
|
|
37576
37994
|
onUndoClick: () => this.active.history.undo(),
|
|
@@ -37600,7 +38018,8 @@ var VelaWorkspace = class {
|
|
|
37600
38018
|
syncs: () => [
|
|
37601
38019
|
{ id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
|
|
37602
38020
|
{ id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
|
|
37603
|
-
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true }
|
|
38021
|
+
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true },
|
|
38022
|
+
{ id: "style", label: "Style", checked: this.syncOpts.style === true }
|
|
37604
38023
|
],
|
|
37605
38024
|
onToggleSync: (id) => {
|
|
37606
38025
|
const kind = id;
|
|
@@ -37628,7 +38047,7 @@ var VelaWorkspace = class {
|
|
|
37628
38047
|
context: () => this.context(),
|
|
37629
38048
|
changed: () => this.markStateDirty()
|
|
37630
38049
|
});
|
|
37631
|
-
this.objectTree = new ObjectTree(main);
|
|
38050
|
+
this.objectTree = new ObjectTree(main, (sym) => this.feed.symbolIcon(sym));
|
|
37632
38051
|
this.dataWindow = new DataWindow(main);
|
|
37633
38052
|
this.dock.addBuiltIn({ id: "dataWindow", title: "Data window", icon: "datawindow", order: 10, panel: this.dataWindow, onChart: (c) => this.dataWindow.onChart(c) });
|
|
37634
38053
|
this.dock.addBuiltIn({ id: "objects", title: "Object tree", icon: "objects", order: 20, panel: this.objectTree, onChart: (c) => this.objectTree.onChart(c) });
|
|
@@ -37700,7 +38119,9 @@ var VelaWorkspace = class {
|
|
|
37700
38119
|
timeframe: "60",
|
|
37701
38120
|
onSymbolClick: () => this.symbolPicker.open(),
|
|
37702
38121
|
onTimeframeClick: () => this.openTimeframeDrawer(),
|
|
37703
|
-
|
|
38122
|
+
// Same visibility truth as the desktop bar: composition-hidden
|
|
38123
|
+
// indicators lose their mobile stop too; an override takes it over.
|
|
38124
|
+
...topbarHas(this.topbarComp, "indicators") && (picker || this.indicatorsOverride) ? { onIndicatorsClick: this.indicatorsOverride ? () => this.runOverride(this.indicatorsOverride) : () => picker.open() } : {},
|
|
37704
38125
|
getContext: () => this.context(),
|
|
37705
38126
|
...this.drawingsEnabled ? { onDrawingsClick: () => this.openDrawingsDrawer() } : {},
|
|
37706
38127
|
onMoreClick: () => this.openMoreDrawer(),
|
|
@@ -37882,7 +38303,7 @@ var VelaWorkspace = class {
|
|
|
37882
38303
|
this.topbar.setTimeframeFavorites(this.tfFavs);
|
|
37883
38304
|
}
|
|
37884
38305
|
this.dock.applyState(st.panels);
|
|
37885
|
-
for (const kind of
|
|
38306
|
+
for (const kind of SYNC_KINDS) this.applySyncSetting(kind, st.sync?.[kind]);
|
|
37886
38307
|
this.trackSizes.clear();
|
|
37887
38308
|
if (st.trackSizes) for (const [id, ts] of Object.entries(st.trackSizes)) this.trackSizes.set(id, ts);
|
|
37888
38309
|
const targetDef = this.monoLayout ? this.def : ensureLayout(st.layout) ?? this.def;
|
|
@@ -37893,9 +38314,14 @@ var VelaWorkspace = class {
|
|
|
37893
38314
|
for (const cell of this.cellsById.values()) cell.chart.drawings.setFavorites(this.favs);
|
|
37894
38315
|
}
|
|
37895
38316
|
this.drawingLinks.clear();
|
|
37896
|
-
|
|
37897
|
-
|
|
37898
|
-
this.
|
|
38317
|
+
this.styleSyncBusy = true;
|
|
38318
|
+
try {
|
|
38319
|
+
for (const [i] of this.def.cells.entries()) {
|
|
38320
|
+
const { id, ...cs } = st.charts[i];
|
|
38321
|
+
this.cellsById.get(id)?.rehydrate(cs);
|
|
38322
|
+
}
|
|
38323
|
+
} finally {
|
|
38324
|
+
this.styleSyncBusy = false;
|
|
37899
38325
|
}
|
|
37900
38326
|
if (st.timezone) this.setTimezone(st.timezone);
|
|
37901
38327
|
this.pool.clear();
|
|
@@ -37995,6 +38421,7 @@ var VelaWorkspace = class {
|
|
|
37995
38421
|
const rebuildAll = nextBackend !== this.cellBackend;
|
|
37996
38422
|
this.order = orderAfterLayout(this.order, next.cells.length, this.activeId);
|
|
37997
38423
|
const keep = new Set(this.order.slice(0, next.cells.length));
|
|
38424
|
+
const preexisting = new Set(this.cellsById.keys());
|
|
37998
38425
|
for (const [id, cell] of [...this.cellsById]) {
|
|
37999
38426
|
if (!keep.has(id) || rebuildAll) {
|
|
38000
38427
|
this.poolSet(id, cell.dehydrate());
|
|
@@ -38007,6 +38434,7 @@ var VelaWorkspace = class {
|
|
|
38007
38434
|
this.cellBackend = nextBackend;
|
|
38008
38435
|
this.applyGrid();
|
|
38009
38436
|
this.buildCells();
|
|
38437
|
+
this.alignNewCellStyles(preexisting);
|
|
38010
38438
|
this.syncCellPresentation();
|
|
38011
38439
|
this.topbar.setLayout(next.id);
|
|
38012
38440
|
const nextActive = activeAfterLayout(this.activeId, this.order.slice(0, next.cells.length));
|
|
@@ -38210,6 +38638,7 @@ var VelaWorkspace = class {
|
|
|
38210
38638
|
onMarketChanged: (id2) => this.onCellMarketChanged(id2),
|
|
38211
38639
|
onPriceStyleChanged: (id2) => this.onCellPriceStyleChanged(id2),
|
|
38212
38640
|
onIndicatorsChanged: (id2) => this.onCellIndicatorsChanged(id2),
|
|
38641
|
+
onStatusPrefsChanged: (id2) => this.propagateStylePrefs(id2),
|
|
38213
38642
|
onStateDirty: () => this.markStateDirty(),
|
|
38214
38643
|
manifestSettled: () => this.manifestSettled,
|
|
38215
38644
|
toast: (message, kind, durationMs) => this.toastHost.show(message, kind, durationMs)
|
|
@@ -38285,6 +38714,7 @@ var VelaWorkspace = class {
|
|
|
38285
38714
|
this.drawToolbar?.setEraserActive(mode === "eraser");
|
|
38286
38715
|
});
|
|
38287
38716
|
chart.on("viewport:changed", (range) => this.propagateViewport(cell.id, range));
|
|
38717
|
+
chart.renderer.onConfigChanged(() => this.propagateStylePrefs(cell.id));
|
|
38288
38718
|
chart.on("theme:changed", (t) => this.setTheme(t));
|
|
38289
38719
|
chart.renderer.onAxisLongPress((e) => {
|
|
38290
38720
|
if (this.layoutCtl.current !== "mobile") return;
|
|
@@ -38317,11 +38747,67 @@ var VelaWorkspace = class {
|
|
|
38317
38747
|
if (kind === "viewport") {
|
|
38318
38748
|
const range = this.cellsById.get(this.activeId)?.chart.getVisibleRange();
|
|
38319
38749
|
if (range) this.propagateViewport(this.activeId, range);
|
|
38750
|
+
} else if (kind === "style") {
|
|
38751
|
+
this.propagateStylePrefs(this.activeId);
|
|
38320
38752
|
} else {
|
|
38321
38753
|
this.propagateMarket(this.activeId);
|
|
38322
38754
|
}
|
|
38323
38755
|
}
|
|
38324
38756
|
}
|
|
38757
|
+
/**
|
|
38758
|
+
* Align cells minted by a layout change to their style group: with the link on, a
|
|
38759
|
+
* NEW cell (fresh slot or one returning from the pool, which missed edits while
|
|
38760
|
+
* dormant) inherits the presentation of a pre-existing group peer — the active
|
|
38761
|
+
* cell when it is one — instead of sitting on its own state beside a styled
|
|
38762
|
+
* group. Propagation runs FROM the peer, so a newborn's defaults never overwrite
|
|
38763
|
+
* the group, and the equality short-circuits keep converged peers untouched.
|
|
38764
|
+
*/
|
|
38765
|
+
alignNewCellStyles(preexisting) {
|
|
38766
|
+
const setting = this.syncOpts.style;
|
|
38767
|
+
if (!setting) return;
|
|
38768
|
+
const ids = [...this.cellsById.keys()];
|
|
38769
|
+
const propagated = /* @__PURE__ */ new Set();
|
|
38770
|
+
for (const id of ids) {
|
|
38771
|
+
if (preexisting.has(id)) continue;
|
|
38772
|
+
const peers = syncTargets(id, setting, ids).filter((p) => preexisting.has(p));
|
|
38773
|
+
if (peers.length === 0) continue;
|
|
38774
|
+
const source = this.activeId && peers.includes(this.activeId) ? this.activeId : peers[0];
|
|
38775
|
+
if (propagated.has(source)) continue;
|
|
38776
|
+
propagated.add(source);
|
|
38777
|
+
this.propagateStylePrefs(source);
|
|
38778
|
+
}
|
|
38779
|
+
}
|
|
38780
|
+
/**
|
|
38781
|
+
* Mirror an origin cell's presentation — the Canvas + Scales-and-lines slice of
|
|
38782
|
+
* its renderer config plus its Status line tab prefs — onto its same-group
|
|
38783
|
+
* followers (the style link). Loop-safe two ways: the busy guard eats the
|
|
38784
|
+
* followers' SYNCHRONOUS echoes (their `applyConfig` re-fires `onConfigChanged`
|
|
38785
|
+
* in the same tick), and the equality short-circuits leave already-converged
|
|
38786
|
+
* followers untouched, so nothing re-emits once the group agrees.
|
|
38787
|
+
*/
|
|
38788
|
+
propagateStylePrefs(originId) {
|
|
38789
|
+
if (this.styleSyncBusy || this.destroyed) return;
|
|
38790
|
+
const targets = syncTargets(originId, this.syncOpts.style, [...this.cellsById.keys()]);
|
|
38791
|
+
if (targets.length === 0) return;
|
|
38792
|
+
const origin = this.cellsById.get(originId);
|
|
38793
|
+
if (!origin) return;
|
|
38794
|
+
const slice = styleConfigSlice(origin.chart.renderer.getConfig());
|
|
38795
|
+
const sliceJson = slice ? JSON.stringify(slice) : null;
|
|
38796
|
+
const prefs = origin.statusPrefs();
|
|
38797
|
+
this.styleSyncBusy = true;
|
|
38798
|
+
try {
|
|
38799
|
+
for (const id of targets) {
|
|
38800
|
+
const cell = this.cellsById.get(id);
|
|
38801
|
+
if (!cell) continue;
|
|
38802
|
+
if (slice && sliceJson !== JSON.stringify(styleConfigSlice(cell.chart.renderer.getConfig()))) {
|
|
38803
|
+
cell.chart.renderer.applyConfig(slice);
|
|
38804
|
+
}
|
|
38805
|
+
cell.applyStatusPrefs(prefs);
|
|
38806
|
+
}
|
|
38807
|
+
} finally {
|
|
38808
|
+
this.styleSyncBusy = false;
|
|
38809
|
+
}
|
|
38810
|
+
}
|
|
38325
38811
|
/**
|
|
38326
38812
|
* Mirror an origin cell's pointer time onto its same-group followers as GHOST
|
|
38327
38813
|
* crosshairs (`renderer.setExternalCrosshair`). The horizontal price level rides
|
|
@@ -38596,27 +39082,32 @@ var VelaWorkspace = class {
|
|
|
38596
39082
|
this.drawingsDrawer.open();
|
|
38597
39083
|
}
|
|
38598
39084
|
openMoreDrawer() {
|
|
39085
|
+
const has = (id) => topbarHas(this.topbarComp, id);
|
|
38599
39086
|
this.moreDrawer ?? (this.moreDrawer = new MoreDrawer({
|
|
38600
39087
|
host: this.root,
|
|
38601
|
-
onUndo: () => this.active.history.undo(),
|
|
38602
|
-
|
|
38603
|
-
onScreenshot: () => this.active.downloadScreenshot(),
|
|
39088
|
+
...has("undo-redo") ? { onUndo: () => this.active.history.undo(), onRedo: () => this.active.history.redo() } : {},
|
|
39089
|
+
...has("screenshot") ? { onScreenshot: this.screenshotOverride ? () => this.runOverride(this.screenshotOverride) : () => this.active.downloadScreenshot() } : {},
|
|
38604
39090
|
canUndo: () => this.active.history.canUndo,
|
|
38605
39091
|
canRedo: () => this.active.history.canRedo,
|
|
38606
39092
|
priceStyles: () => priceStyleIds().map((id) => ({ id, label: priceStyleLabel(id), icon: priceStyleIcon(id) })),
|
|
38607
39093
|
priceStyle: () => this.active.priceStyle,
|
|
38608
39094
|
onPriceStyle: (id) => this.active.setPriceStyle(id),
|
|
38609
|
-
panels: () => [...this.dock.list()],
|
|
39095
|
+
panels: () => has("panels") ? [...this.dock.list()] : [],
|
|
38610
39096
|
onTogglePanel: (id) => this.dock.toggle(id),
|
|
38611
|
-
alerts: () => this.alerts.map((a) => ({ title: `${a.source} \xB7 ${a.title}`, message: a.message, time: a.time })),
|
|
39097
|
+
...has("alerts") ? { alerts: () => this.alerts.map((a) => ({ title: `${a.source} \xB7 ${a.title}`, message: a.message, time: a.time })) } : {},
|
|
38612
39098
|
// 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
|
|
39099
|
+
// lands in the drawer, or every left action would appear twice. Built-in-id
|
|
39100
|
+
// actions are slot OVERRIDES: they reach the drawer through the slot's own
|
|
39101
|
+
// routed button (screenshot) or stop (indicators), never as an extra row.
|
|
39102
|
+
actions: () => {
|
|
39103
|
+
const builtin = new Set(TOPBAR_BUILTIN_IDS);
|
|
39104
|
+
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()) }));
|
|
39105
|
+
},
|
|
38615
39106
|
// The desktop layout dropdown's whole surface — the grid canvas, the
|
|
38616
39107
|
// non-canvas presets and the sync switches — relocated into the kebab
|
|
38617
39108
|
// drawer (the topbar is hidden on mobile). Same reads as the topbar block;
|
|
38618
|
-
// single-chart mode
|
|
38619
|
-
layout: this.monoLayout ? void 0 : {
|
|
39109
|
+
// single-chart mode and a composition without 'layout' omit it here too.
|
|
39110
|
+
layout: this.monoLayout || !has("layout") ? void 0 : {
|
|
38620
39111
|
shape: () => layoutShape(this.def),
|
|
38621
39112
|
presets: () => layouts().filter((l) => layoutShape(l) === null).map((l) => ({ id: l.id, label: l.label, checked: l.id === this.def.id })),
|
|
38622
39113
|
onSelectGrid: (rows, cols) => this.setLayout(layoutForGrid(rows, cols)),
|
|
@@ -38624,7 +39115,8 @@ var VelaWorkspace = class {
|
|
|
38624
39115
|
syncs: () => [
|
|
38625
39116
|
{ id: "symbol", label: "Symbol", checked: this.syncOpts.symbol === true },
|
|
38626
39117
|
{ id: "timeframe", label: "Interval", checked: this.syncOpts.timeframe === true },
|
|
38627
|
-
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true }
|
|
39118
|
+
{ id: "crosshair", label: "Crosshair", checked: this.syncOpts.crosshair === true },
|
|
39119
|
+
{ id: "style", label: "Style", checked: this.syncOpts.style === true }
|
|
38628
39120
|
],
|
|
38629
39121
|
onToggleSync: (id) => {
|
|
38630
39122
|
const kind = id;
|
|
@@ -38683,9 +39175,23 @@ var VelaWorkspace = class {
|
|
|
38683
39175
|
}
|
|
38684
39176
|
}
|
|
38685
39177
|
}
|
|
39178
|
+
/** Invoke a slot override the way its button would: fresh context, `when` respected. */
|
|
39179
|
+
runOverride(action) {
|
|
39180
|
+
const ctx = this.context();
|
|
39181
|
+
if (!action.when || action.when(ctx)) action.run(ctx);
|
|
39182
|
+
}
|
|
38686
39183
|
/** The default shortcut set — every binding acts on the ACTIVE cell. */
|
|
38687
39184
|
registerDefaultKeys() {
|
|
38688
|
-
this.
|
|
39185
|
+
if (topbarHas(this.topbarComp, "screenshot")) {
|
|
39186
|
+
const ov = this.screenshotOverride;
|
|
39187
|
+
this.keymap.register({
|
|
39188
|
+
id: "chart.screenshot",
|
|
39189
|
+
keys: "mod+alt+s",
|
|
39190
|
+
label: ov ? ov.label : "Download a chart screenshot",
|
|
39191
|
+
category: "Chart",
|
|
39192
|
+
run: ov ? () => this.runOverride(ov) : () => this.active.downloadScreenshot()
|
|
39193
|
+
});
|
|
39194
|
+
}
|
|
38689
39195
|
this.keymap.register({ id: "chart.reset-view", keys: "alt+r", label: "Reset view (all history)", category: "Chart", run: () => this.active.chart.setVisibleRangePreset("ALL") });
|
|
38690
39196
|
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
39197
|
this.keymap.register({
|
|
@@ -38725,7 +39231,10 @@ var VelaWorkspace = class {
|
|
|
38725
39231
|
this.keymap.register({ id: "view.zoom-out", keys: "mod+arrowdown", label: "Zoom out", category: "Chart", run: () => this.glider.zoom(ZOOM_OUT) });
|
|
38726
39232
|
this.keymap.register({ id: "view.pan-left", keys: "mod+arrowleft", label: "Pan toward history", category: "Chart", run: () => this.active.chart.panBy(-PAN_FAST) });
|
|
38727
39233
|
this.keymap.register({ id: "view.pan-right", keys: "mod+arrowright", label: "Pan toward now", category: "Chart", run: () => this.active.chart.panBy(PAN_FAST) });
|
|
38728
|
-
|
|
39234
|
+
const indOv = this.indicatorsOverride;
|
|
39235
|
+
if (indOv && topbarHas(this.topbarComp, "indicators")) {
|
|
39236
|
+
this.keymap.register({ id: "indicators.open", keys: "/", label: indOv.label, category: "Indicators", run: () => this.runOverride(indOv) });
|
|
39237
|
+
} else if (this.indicatorPicker) {
|
|
38729
39238
|
this.keymap.register({ id: "indicators.open", keys: "/", label: "Open the indicator picker", category: "Indicators", run: () => this.indicatorPicker?.open() });
|
|
38730
39239
|
}
|
|
38731
39240
|
this.keymap.register({
|
|
@@ -38881,6 +39390,7 @@ exports.DEFAULT_PANEL_ORDER = DEFAULT_PANEL_ORDER;
|
|
|
38881
39390
|
exports.DEFAULT_PANEL_WIDTH = DEFAULT_PANEL_WIDTH;
|
|
38882
39391
|
exports.DataWindow = DataWindow;
|
|
38883
39392
|
exports.IndicatorPicker = IndicatorPicker;
|
|
39393
|
+
exports.OVERRIDABLE_TOPBAR_IDS = OVERRIDABLE_TOPBAR_IDS;
|
|
38884
39394
|
exports.ObjectTree = ObjectTree;
|
|
38885
39395
|
exports.PanelDock = PanelDock;
|
|
38886
39396
|
exports.RANGE_PRESETS = RANGE_PRESETS;
|
|
@@ -38889,6 +39399,9 @@ exports.SidePanel = SidePanel;
|
|
|
38889
39399
|
exports.Statusline = Statusline;
|
|
38890
39400
|
exports.SymbolPicker = SymbolPicker;
|
|
38891
39401
|
exports.TIMEZONES = TIMEZONES;
|
|
39402
|
+
exports.TOPBAR_BUILTIN_IDS = TOPBAR_BUILTIN_IDS;
|
|
39403
|
+
exports.TOPBAR_DEFAULT_LEFT = TOPBAR_DEFAULT_LEFT;
|
|
39404
|
+
exports.TOPBAR_DEFAULT_RIGHT = TOPBAR_DEFAULT_RIGHT;
|
|
38892
39405
|
exports.TimeframeQuick = TimeframeQuick;
|
|
38893
39406
|
exports.Topbar = Topbar;
|
|
38894
39407
|
exports.VelaWidget = VelaWidget;
|
|
@@ -38904,17 +39417,23 @@ exports.fmtPrice = fmtPrice;
|
|
|
38904
39417
|
exports.localStorageAdapter = localStorageAdapter;
|
|
38905
39418
|
exports.normalizeTimezone = normalizeTimezone;
|
|
38906
39419
|
exports.parseTimeframe = parseTimeframe;
|
|
39420
|
+
exports.pinnedTopbarActionIds = pinnedTopbarActionIds;
|
|
38907
39421
|
exports.priceStyleLabel = priceStyleLabel;
|
|
38908
39422
|
exports.registerSidePanel = registerSidePanel;
|
|
38909
39423
|
exports.registerStatePersistence = registerStatePersistence;
|
|
39424
|
+
exports.registerSymbolRanking = registerSymbolRanking;
|
|
38910
39425
|
exports.registerWidgetAction = registerWidgetAction;
|
|
38911
39426
|
exports.registerWidgetAttachment = registerWidgetAttachment;
|
|
38912
39427
|
exports.resolveIndicators = resolveIndicators;
|
|
39428
|
+
exports.resolveTopbarComposition = resolveTopbarComposition;
|
|
38913
39429
|
exports.sanitizeState = sanitizeState;
|
|
38914
39430
|
exports.sidePanels = sidePanels;
|
|
38915
39431
|
exports.statePersistenceHandlers = statePersistenceHandlers;
|
|
39432
|
+
exports.symbolRanking = symbolRanking;
|
|
38916
39433
|
exports.timeframeLabel = timeframeLabel;
|
|
38917
39434
|
exports.timeframeMs = timeframeMs;
|
|
39435
|
+
exports.topbarActionOverride = topbarActionOverride;
|
|
39436
|
+
exports.topbarHas = topbarHas;
|
|
38918
39437
|
exports.tzButtonLabel = tzButtonLabel;
|
|
38919
39438
|
exports.tzMenuLabel = tzMenuLabel;
|
|
38920
39439
|
exports.tzOffset = tzOffset;
|