@luxalgo/vela 0.7.8 → 0.8.1
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/NOTICE +28 -28
- package/README.md +15 -7
- package/dist/{DataProvider-CGa4KHRa.d.cts → DataProvider-BrlxntPN.d.cts} +1 -1
- package/dist/{DataProvider-CmWUtDVL.d.ts → DataProvider-DwFIoV4r.d.ts} +1 -1
- package/dist/{chunk-N4ZZYLMP.js → chunk-ATGTCWHJ.js} +726 -273
- package/dist/{chunk-CAFCLMPF.js → chunk-BZQM2XO7.js} +1 -0
- package/dist/{chunk-M6GP7DAX.js → chunk-DBDXKZUF.js} +474 -47
- package/dist/{chunk-BFA32GOU.js → chunk-EZ5FWVLA.js} +5 -2
- package/dist/{chunk-PVGWNF7V.js → chunk-LMBYEGUR.js} +2 -2
- package/dist/{chunk-WTWGEB7K.js → chunk-NELQJCGK.js} +298 -5
- package/dist/{contributions-DF_Ea07m.d.cts → contributions-CtlptxR8.d.cts} +187 -5
- package/dist/{contributions-nKOSeLy8.d.ts → contributions-tjNExo1o.d.ts} +187 -5
- package/dist/index.cjs +1044 -272
- package/dist/index.d.cts +24 -8
- package/dist/index.d.ts +24 -8
- package/dist/index.js +28 -4
- package/dist/{options-D1AJKsn_.d.ts → options-BnEPUuVV.d.cts} +1 -1
- package/dist/{options-D1AJKsn_.d.cts → options-BnEPUuVV.d.ts} +1 -1
- package/dist/{plugin-6F04gjQe.d.cts → plugin-BWxJRKzS.d.cts} +10 -4
- package/dist/{plugin-BLsNipkc.d.ts → plugin-e2WUEtiI.d.ts} +10 -4
- package/dist/plugin.cjs +1 -0
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/{statusline-model-DAFyqVmR.d.ts → statusline-model-DFY7tuHj.d.cts} +5 -3
- package/dist/{statusline-model-BoWJ3cWQ.d.cts → statusline-model-zKXtGIjj.d.ts} +5 -3
- package/dist/ui.cjs +301 -3
- package/dist/ui.d.cts +75 -2
- package/dist/ui.d.ts +75 -2
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +1046 -274
- package/dist/vela.global.min.js +82 -87
- package/dist/widget.cjs +1504 -327
- package/dist/widget.d.cts +26 -7
- package/dist/widget.d.ts +26 -7
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +1507 -327
- package/dist/workspace.d.cts +125 -7
- package/dist/workspace.d.ts +125 -7
- package/dist/workspace.js +6 -6
- package/package.json +2 -2
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, SecondClock, resolveTimezone, timezoneMenuRows, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, normalizeTimezone, isExchangeTimezone, TypedEventBus, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore
|
|
2
|
-
import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, statePersistenceHandlers, topbarHas, rendererDefaults, widgetAttachments, getDrawingType, inputDeltas } from './chunk-
|
|
3
|
-
import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-
|
|
4
|
-
import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-
|
|
5
|
-
import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, iconAt, SESSION_OFF, SESSION_POST, SESSION_PRE, icon, categoricalColor } from './chunk-
|
|
1
|
+
import { parseSymbol, priceStyleIds, BUILTIN_PRICE_STYLES, tzButtonLabel, SecondClock, resolveTimezone, timezoneMenuRows, isGroupRow, defaultMemberOf, groupKeyOf, groupMembers, LEGEND_AT_TOP_ATTR, normalizeSession, Vela, normalizeTimezone, isExchangeTimezone, TypedEventBus, timeframeToMs, MultiProviderFeed, registerBuiltinChartTypes, resolveTheme, buildToolbar, createCustomMark, createAttributionMark, DrawingToolbar, applyAttributionMarkTheme, sharedBarStore } from './chunk-ATGTCWHJ.js';
|
|
2
|
+
import { chartType, resolveTopbarComposition, topbarActionOverride, TOPBAR_BUILTIN_IDS, widgetActions, SidePanel, sidePanels, DEFAULT_PANEL_ORDER, symbolRanking, resolveEngines, legendActionsProviderFor, legendCalloutsProviderFor, statePersistenceHandlers, topbarHas, rendererDefaults, mobilePlacement, widgetAttachments, getDrawingType, inputDeltas } from './chunk-EZ5FWVLA.js';
|
|
3
|
+
import { Tooltip, KeymapManager, isEditableTarget, Drawer } from './chunk-LMBYEGUR.js';
|
|
4
|
+
import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-NELQJCGK.js';
|
|
5
|
+
import { registerIcon, svg16, injectStyles, iconEl, iconMarkup, iconAt, SESSION_OFF, SESSION_POST, SESSION_PRE, icon, categoricalColor } from './chunk-BZQM2XO7.js';
|
|
6
6
|
import { baseOf } from './chunk-W4EJWLEO.js';
|
|
7
7
|
|
|
8
8
|
// src/widget/timeframe.ts
|
|
@@ -3652,7 +3652,12 @@ var TimeframeQuick = class {
|
|
|
3652
3652
|
content: (body) => body.append(this.input, this.hint),
|
|
3653
3653
|
onOpenChange: (open) => opts.onOpenChange?.(open)
|
|
3654
3654
|
});
|
|
3655
|
-
this.input.addEventListener("input", () =>
|
|
3655
|
+
this.input.addEventListener("input", () => {
|
|
3656
|
+
const { selectionStart, selectionEnd } = this.input;
|
|
3657
|
+
this.input.value = this.input.value.toUpperCase();
|
|
3658
|
+
this.input.setSelectionRange(selectionStart, selectionEnd);
|
|
3659
|
+
this.renderHint();
|
|
3660
|
+
});
|
|
3656
3661
|
this.input.addEventListener("keydown", (e) => {
|
|
3657
3662
|
if (e.key !== "Enter") return;
|
|
3658
3663
|
const parsed = parseTimeframe(this.input.value);
|
|
@@ -3664,7 +3669,7 @@ var TimeframeQuick = class {
|
|
|
3664
3669
|
}
|
|
3665
3670
|
open(seed = "") {
|
|
3666
3671
|
this.dialog.show();
|
|
3667
|
-
this.input.value = seed;
|
|
3672
|
+
this.input.value = seed.toUpperCase();
|
|
3668
3673
|
this.renderHint();
|
|
3669
3674
|
setTimeout(() => {
|
|
3670
3675
|
this.input.focus();
|
|
@@ -3682,7 +3687,7 @@ var TimeframeQuick = class {
|
|
|
3682
3687
|
this.open(text);
|
|
3683
3688
|
return;
|
|
3684
3689
|
}
|
|
3685
|
-
this.input.value += text;
|
|
3690
|
+
this.input.value += text.toUpperCase();
|
|
3686
3691
|
this.renderHint();
|
|
3687
3692
|
}
|
|
3688
3693
|
close() {
|
|
@@ -3695,7 +3700,7 @@ var TimeframeQuick = class {
|
|
|
3695
3700
|
const raw = this.input.value.trim();
|
|
3696
3701
|
const parsed = parseTimeframe(raw);
|
|
3697
3702
|
if (!raw) {
|
|
3698
|
-
this.hint.textContent = "e.g. 15,
|
|
3703
|
+
this.hint.textContent = "e.g. 15, 4H, D, 3M";
|
|
3699
3704
|
delete this.hint.dataset.invalid;
|
|
3700
3705
|
} else if (parsed.valid) {
|
|
3701
3706
|
this.hint.textContent = parsed.label ?? "";
|
|
@@ -3798,7 +3803,7 @@ var ShortcutsHelp = class {
|
|
|
3798
3803
|
addRow("Delete the drawing under the cursor", ["Middle-click"]);
|
|
3799
3804
|
const s = doc.createElement("div");
|
|
3800
3805
|
s.className = "vela-sh-static";
|
|
3801
|
-
s.textContent = "Typing a letter opens the symbol search; typing a digit opens the timeframe entry.";
|
|
3806
|
+
s.textContent = "Typing a letter or 0 opens the symbol search; typing a digit from 1 to 9 opens the timeframe entry.";
|
|
3802
3807
|
this.list.appendChild(s);
|
|
3803
3808
|
}
|
|
3804
3809
|
};
|
|
@@ -3925,6 +3930,7 @@ function sanitizeCell(raw) {
|
|
|
3925
3930
|
if (typeof c.bars === "number" && Number.isFinite(c.bars) && c.bars > 0) out.bars = c.bars;
|
|
3926
3931
|
if (c.session === "regular" || c.session === "extended") out.session = c.session;
|
|
3927
3932
|
if (typeof c.watermark === "boolean") out.watermark = c.watermark;
|
|
3933
|
+
if (typeof c.replayWatermark === "boolean") out.replayWatermark = c.replayWatermark;
|
|
3928
3934
|
if (typeof c.indicatorTitles === "boolean") out.indicatorTitles = c.indicatorTitles;
|
|
3929
3935
|
if (typeof c.indicatorValues === "boolean") out.indicatorValues = c.indicatorValues;
|
|
3930
3936
|
if (c.rendererConfig != null && typeof c.rendererConfig === "object") out.rendererConfig = c.rendererConfig;
|
|
@@ -4208,9 +4214,15 @@ var CSS10 = `
|
|
|
4208
4214
|
}
|
|
4209
4215
|
.vela-statusline .vela-sl-symbol { font-weight: 600; font-size: var(--vela-font-size-lg); }
|
|
4210
4216
|
.vela-statusline .vela-sl-meta { color: var(--vela-fg-muted); font-size: var(--vela-font-size-md); font-weight: 600; }
|
|
4217
|
+
/* The meta opens with "\xB7 " \u2014 sit its dot one space-width after the ticker, as far as the
|
|
4218
|
+
* venue sits after it, not a full row gap away. */
|
|
4219
|
+
.vela-statusline .vela-sl-symbol + .vela-sl-meta { margin-left: calc(var(--vela-space-1) - var(--vela-space-2)); }
|
|
4211
4220
|
/* Market status badge \u2014 a kit callout bubble (icon-only 16px circle, label on hover
|
|
4212
|
-
* via the kit tooltip); the session tint is applied per status in setMarketStatus.
|
|
4213
|
-
|
|
4221
|
+
* via the kit tooltip); the session tint is applied per status in setMarketStatus. While
|
|
4222
|
+
* the chart replays past bars it wears the replay badge instead (the inverse chip). */
|
|
4223
|
+
.vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
|
|
4224
|
+
.vela-statusline .vela-sl-market > [hidden] { display: none !important; }
|
|
4225
|
+
.vela-statusline .vela-sl-replay-badge, .vela-statusline .vela-sl-replay-badge svg { display: block; width: 16px; height: 16px; }
|
|
4214
4226
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
4215
4227
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
4216
4228
|
/* The change value wears the SAME ink as the OHLC values (set inline per render) \u2014
|
|
@@ -4263,6 +4275,8 @@ var MARKET_LABELS = {
|
|
|
4263
4275
|
closed: "Market Closed",
|
|
4264
4276
|
holiday: "Market Holiday"
|
|
4265
4277
|
};
|
|
4278
|
+
var REPLAY_LABEL = "Replay Mode";
|
|
4279
|
+
var REPLAY_BADGE_SVG = '<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true"><circle cx="8" cy="8" r="8" style="fill: var(--vela-selected-bg)"/><path d="M11.7 4.75v6.5L7.2 8zM7.2 4.75v6.5L2.7 8z" style="fill: var(--vela-selected-fg); stroke: var(--vela-selected-fg); stroke-width: 0.8; stroke-linejoin: round"/></svg>';
|
|
4266
4280
|
var MARKET_INKS = {
|
|
4267
4281
|
open: "var(--vela-up)",
|
|
4268
4282
|
pre: SESSION_PRE,
|
|
@@ -4301,6 +4315,9 @@ var Statusline = class {
|
|
|
4301
4315
|
this.host = host;
|
|
4302
4316
|
this.iconFor = iconFor;
|
|
4303
4317
|
this.parts = { logo: true, name: true, market: true, ohlc: true, change: true };
|
|
4318
|
+
this.marketStatus = "open";
|
|
4319
|
+
/** The chart replays past bars — the badge shows the replay mode (see {@link setReplaying}). */
|
|
4320
|
+
this.replaying = false;
|
|
4304
4321
|
/** The right-click action menu — present once a host wires it via {@link attachMenu}. */
|
|
4305
4322
|
this.menu = null;
|
|
4306
4323
|
this.menuHooks = null;
|
|
@@ -4352,8 +4369,13 @@ var Statusline = class {
|
|
|
4352
4369
|
label: MARKET_LABELS.open,
|
|
4353
4370
|
host
|
|
4354
4371
|
});
|
|
4355
|
-
this.
|
|
4356
|
-
this.
|
|
4372
|
+
this.replayBadge = doc.createElement("span");
|
|
4373
|
+
this.replayBadge.className = "vela-sl-replay-badge";
|
|
4374
|
+
this.replayBadge.innerHTML = REPLAY_BADGE_SVG;
|
|
4375
|
+
this.replayBadge.hidden = true;
|
|
4376
|
+
this.marketEl = doc.createElement("span");
|
|
4377
|
+
this.marketEl.className = "vela-sl-market";
|
|
4378
|
+
this.marketEl.append(this.marketBubble.el, this.replayBadge);
|
|
4357
4379
|
this.ohlcEl = doc.createElement("span");
|
|
4358
4380
|
this.ohlcEl.className = "vela-sl-ohlc";
|
|
4359
4381
|
this.changeEl = doc.createElement("span");
|
|
@@ -4475,6 +4497,25 @@ var Statusline = class {
|
|
|
4475
4497
|
/** Dress the market badge for a session state: its icon, tinted circle, and the
|
|
4476
4498
|
* hover label. Callers with no session model leave the constructor's 'open'. */
|
|
4477
4499
|
setMarketStatus(status) {
|
|
4500
|
+
this.marketStatus = status;
|
|
4501
|
+
this.dressBadge();
|
|
4502
|
+
}
|
|
4503
|
+
/** The chart entered or left a bar replay: the badge shows the replay mode meanwhile
|
|
4504
|
+
* (the session state of a past bar says nothing about the market now). */
|
|
4505
|
+
setReplaying(replaying) {
|
|
4506
|
+
if (replaying === this.replaying) return;
|
|
4507
|
+
this.replaying = replaying;
|
|
4508
|
+
this.dressBadge();
|
|
4509
|
+
}
|
|
4510
|
+
dressBadge() {
|
|
4511
|
+
this.marketBubble.el.hidden = this.replaying;
|
|
4512
|
+
this.replayBadge.hidden = !this.replaying;
|
|
4513
|
+
if (this.replaying) {
|
|
4514
|
+
this.marketEl.dataset.status = "replay";
|
|
4515
|
+
this.marketTip.setContent(REPLAY_LABEL);
|
|
4516
|
+
return;
|
|
4517
|
+
}
|
|
4518
|
+
const status = this.marketStatus;
|
|
4478
4519
|
this.marketEl.dataset.status = status;
|
|
4479
4520
|
const ink = MARKET_INKS[status];
|
|
4480
4521
|
this.marketBubble.set({
|
|
@@ -4547,11 +4588,23 @@ var Statusline = class {
|
|
|
4547
4588
|
this.lastBar = b;
|
|
4548
4589
|
if (!this.hoverBar) this.render();
|
|
4549
4590
|
}),
|
|
4591
|
+
// A replay cut or restore replaces the newest bar without a `bar` event.
|
|
4592
|
+
chart.on("replay:start", () => {
|
|
4593
|
+
this.lastBar = null;
|
|
4594
|
+
this.setReplaying(true);
|
|
4595
|
+
this.render();
|
|
4596
|
+
}),
|
|
4597
|
+
chart.on("replay:end", () => {
|
|
4598
|
+
this.lastBar = null;
|
|
4599
|
+
this.setReplaying(false);
|
|
4600
|
+
this.render();
|
|
4601
|
+
}),
|
|
4550
4602
|
chart.renderer.onCrosshairMove((e) => {
|
|
4551
4603
|
this.hoverBar = e.ohlc;
|
|
4552
4604
|
this.render();
|
|
4553
4605
|
})
|
|
4554
4606
|
);
|
|
4607
|
+
this.setReplaying(chart.replay.state.active);
|
|
4555
4608
|
this.render();
|
|
4556
4609
|
}
|
|
4557
4610
|
destroy() {
|
|
@@ -4620,6 +4673,7 @@ var CSS11 = `
|
|
|
4620
4673
|
left: var(--vela-toolbar-gutter, 0px);
|
|
4621
4674
|
right: var(--vela-scale-gutter, 0px);
|
|
4622
4675
|
display: flex;
|
|
4676
|
+
flex-direction: column;
|
|
4623
4677
|
align-items: center;
|
|
4624
4678
|
justify-content: center;
|
|
4625
4679
|
overflow: hidden;
|
|
@@ -4633,6 +4687,14 @@ var CSS11 = `
|
|
|
4633
4687
|
user-select: none;
|
|
4634
4688
|
white-space: nowrap;
|
|
4635
4689
|
}
|
|
4690
|
+
.vela-watermark-replay {
|
|
4691
|
+
display: inline-flex;
|
|
4692
|
+
align-items: center;
|
|
4693
|
+
gap: 0.25em;
|
|
4694
|
+
margin-top: 0.1em;
|
|
4695
|
+
font-size: 0.8em;
|
|
4696
|
+
}
|
|
4697
|
+
.vela-watermark [hidden] { display: none !important; }
|
|
4636
4698
|
`;
|
|
4637
4699
|
function watermarkFontPx(availPx, textPxAtMax) {
|
|
4638
4700
|
if (textPxAtMax <= 0) return MAX_FONT_PX;
|
|
@@ -4643,6 +4705,10 @@ var Watermark = class {
|
|
|
4643
4705
|
this.resizeObserver = null;
|
|
4644
4706
|
/** The host's visibility preference (the persisted watermark toggle). */
|
|
4645
4707
|
this.shown = true;
|
|
4708
|
+
/** The host's preference for the replay line (its own persisted toggle). */
|
|
4709
|
+
this.replayShown = true;
|
|
4710
|
+
/** The chart is replaying past bars. */
|
|
4711
|
+
this.replaying = false;
|
|
4646
4712
|
/** A bar load is in flight with nothing painted — the loading affordance owns the
|
|
4647
4713
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
4648
4714
|
* fires during chart construction, before any subscriber can see it. */
|
|
@@ -4652,7 +4718,10 @@ var Watermark = class {
|
|
|
4652
4718
|
this.el.className = "vela-watermark";
|
|
4653
4719
|
this.el.dataset.velaScreenshot = "under";
|
|
4654
4720
|
this.text = host.ownerDocument.createElement("span");
|
|
4655
|
-
this.
|
|
4721
|
+
this.replayLine = host.ownerDocument.createElement("span");
|
|
4722
|
+
this.replayLine.className = "vela-watermark-replay";
|
|
4723
|
+
this.replayLine.append(iconEl("replay", host.ownerDocument), host.ownerDocument.createTextNode("Replay"));
|
|
4724
|
+
this.el.append(this.text, this.replayLine);
|
|
4656
4725
|
host.appendChild(this.el);
|
|
4657
4726
|
if (typeof ResizeObserver !== "undefined") {
|
|
4658
4727
|
this.resizeObserver = new ResizeObserver(() => this.fit());
|
|
@@ -4665,13 +4734,26 @@ var Watermark = class {
|
|
|
4665
4734
|
this.shown = visible;
|
|
4666
4735
|
this.sync();
|
|
4667
4736
|
}
|
|
4737
|
+
/** Show/hide the replay line (the host's toggle); it only ever shows while replaying. */
|
|
4738
|
+
setReplayVisible(visible) {
|
|
4739
|
+
this.replayShown = visible;
|
|
4740
|
+
this.sync();
|
|
4741
|
+
}
|
|
4742
|
+
/** The chart entered or left a bar replay. */
|
|
4743
|
+
setReplaying(replaying) {
|
|
4744
|
+
this.replaying = replaying;
|
|
4745
|
+
this.sync();
|
|
4746
|
+
}
|
|
4668
4747
|
/** Loading and the watermark never share the canvas — hidden while a load is up. */
|
|
4669
4748
|
setLoading(loading) {
|
|
4670
4749
|
this.loading = loading;
|
|
4671
4750
|
this.sync();
|
|
4672
4751
|
}
|
|
4673
4752
|
sync() {
|
|
4674
|
-
|
|
4753
|
+
const replay = this.replayShown && this.replaying;
|
|
4754
|
+
this.text.hidden = !this.shown;
|
|
4755
|
+
this.replayLine.hidden = !replay;
|
|
4756
|
+
this.el.style.display = (this.shown || replay) && !this.loading ? "" : "none";
|
|
4675
4757
|
}
|
|
4676
4758
|
update(symbol, timeframe) {
|
|
4677
4759
|
this.text.textContent = symbol ? `${parseSymbol(symbol).ticker} \xB7 ${timeframeLabel(timeframe)}` : "";
|
|
@@ -5707,8 +5789,12 @@ var ChartCell = class {
|
|
|
5707
5789
|
this.indicatorValuesOn = seed.indicatorValues ?? true;
|
|
5708
5790
|
if (!this.indicatorValuesOn) this.inner.renderer.set("indicatorValues", false);
|
|
5709
5791
|
this.watermarkOn = seed.watermark ?? deps.watermark;
|
|
5792
|
+
this.replayWatermarkOn = seed.replayWatermark ?? true;
|
|
5710
5793
|
this.watermark = deps.watermark ? new Watermark(this.host, symbol ?? "", seed.timeframe ?? "60") : null;
|
|
5711
5794
|
if (!this.watermarkOn) this.watermark?.setVisible(false);
|
|
5795
|
+
if (!this.replayWatermarkOn) this.watermark?.setReplayVisible(false);
|
|
5796
|
+
this.inner.on("replay:start", () => this.watermark?.setReplaying(true));
|
|
5797
|
+
this.inner.on("replay:end", () => this.watermark?.setReplaying(false));
|
|
5712
5798
|
this.statusline = deps.statusline ? new Statusline(this.host, symbol ?? "", (sym) => this.inner?.data.symbolIcon(sym)) : null;
|
|
5713
5799
|
this.statusline?.setMeta(seed.timeframe ?? "60", this.state.provider ?? "");
|
|
5714
5800
|
this.statusline?.onChart(this.inner);
|
|
@@ -6022,6 +6108,13 @@ var ChartCell = class {
|
|
|
6022
6108
|
id: "visible",
|
|
6023
6109
|
get: () => this.watermarkOn,
|
|
6024
6110
|
set: (v) => this.setWatermarkVisible(v)
|
|
6111
|
+
},
|
|
6112
|
+
{
|
|
6113
|
+
kind: "toggle",
|
|
6114
|
+
label: "Replay watermark",
|
|
6115
|
+
id: "replay",
|
|
6116
|
+
get: () => this.replayWatermarkOn,
|
|
6117
|
+
set: (v) => this.setReplayWatermarkVisible(v)
|
|
6025
6118
|
}
|
|
6026
6119
|
]
|
|
6027
6120
|
};
|
|
@@ -6090,6 +6183,12 @@ var ChartCell = class {
|
|
|
6090
6183
|
this.watermark?.setVisible(visible);
|
|
6091
6184
|
this.deps.onStateDirty();
|
|
6092
6185
|
}
|
|
6186
|
+
/** Show/hide the "Replay" line under this cell's watermark while it replays (persisted per cell). */
|
|
6187
|
+
setReplayWatermarkVisible(visible) {
|
|
6188
|
+
this.replayWatermarkOn = visible;
|
|
6189
|
+
this.watermark?.setReplayVisible(visible);
|
|
6190
|
+
this.deps.onStateDirty();
|
|
6191
|
+
}
|
|
6093
6192
|
/** Show/hide this cell's indicator titles — the in-chart legend rows (persisted per cell). */
|
|
6094
6193
|
setIndicatorTitlesVisible(visible) {
|
|
6095
6194
|
this.indicatorTitlesOn = visible;
|
|
@@ -6330,7 +6429,7 @@ var ChartCell = class {
|
|
|
6330
6429
|
* then the manifest in the host's own order. */
|
|
6331
6430
|
libraryRows() {
|
|
6332
6431
|
return [
|
|
6333
|
-
...this.supportedNatives().map((n) => ({ name: n.title, category: "
|
|
6432
|
+
...this.supportedNatives().map((n) => ({ name: n.title, category: "Built-in", native: true, nativeType: n.type, beta: n.beta })),
|
|
6334
6433
|
...this.manifest.map((e) => ({ name: e.name, language: e.language, category: e.category }))
|
|
6335
6434
|
];
|
|
6336
6435
|
}
|
|
@@ -6563,6 +6662,7 @@ var ChartCell = class {
|
|
|
6563
6662
|
if (!this.inner || this.destroyed) return;
|
|
6564
6663
|
if (cs.priceStyle && cs.priceStyle !== this.priceStyle) this.setPriceStyle(cs.priceStyle);
|
|
6565
6664
|
if (cs.watermark !== void 0 && cs.watermark !== this.watermarkOn) this.setWatermarkVisible(cs.watermark);
|
|
6665
|
+
if (cs.replayWatermark !== void 0 && cs.replayWatermark !== this.replayWatermarkOn) this.setReplayWatermarkVisible(cs.replayWatermark);
|
|
6566
6666
|
if (cs.indicatorTitles !== void 0 && cs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(cs.indicatorTitles);
|
|
6567
6667
|
if (cs.indicatorValues !== void 0 && cs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(cs.indicatorValues);
|
|
6568
6668
|
if (cs.rendererConfig != null) this.inner.renderer.applyConfig(cs.rendererConfig);
|
|
@@ -6597,6 +6697,7 @@ var ChartCell = class {
|
|
|
6597
6697
|
...live ? { symbol: live.symbol, provider: live.provider, timeframe: live.timeframe } : {},
|
|
6598
6698
|
priceStyle: this.priceStyle,
|
|
6599
6699
|
watermark: this.watermarkOn,
|
|
6700
|
+
replayWatermark: this.replayWatermarkOn,
|
|
6600
6701
|
indicatorTitles: this.indicatorTitlesOn,
|
|
6601
6702
|
indicatorValues: this.indicatorValuesOn,
|
|
6602
6703
|
rendererConfig: this.inner?.renderer.getConfig() ?? void 0,
|
|
@@ -6639,6 +6740,298 @@ var ChartCell = class {
|
|
|
6639
6740
|
}
|
|
6640
6741
|
};
|
|
6641
6742
|
|
|
6743
|
+
// src/workspace/WorkspaceReplay.ts
|
|
6744
|
+
var MAX_BARS_PER_STEP = 1e5;
|
|
6745
|
+
var WorkspaceReplay = class {
|
|
6746
|
+
constructor(host) {
|
|
6747
|
+
this.host = host;
|
|
6748
|
+
this.bus = new TypedEventBus();
|
|
6749
|
+
/** The running session: the shared replay time (every chart shows the bars closed by it). */
|
|
6750
|
+
this.session = null;
|
|
6751
|
+
this.playing = false;
|
|
6752
|
+
this.intervalMs = 1e3;
|
|
6753
|
+
this.timer = null;
|
|
6754
|
+
/** Cells whose replay a symbol switch ended — restarted at the shared time once their bars land. */
|
|
6755
|
+
this.rejoining = /* @__PURE__ */ new Set();
|
|
6756
|
+
this.cellSubs = /* @__PURE__ */ new Map();
|
|
6757
|
+
/** Inside a group step: chart events are the controller's own doing. */
|
|
6758
|
+
this.stepping = false;
|
|
6759
|
+
/** Inside `finish`: the charts' `replay:end` events are the controller's own doing. */
|
|
6760
|
+
this.stopping = false;
|
|
6761
|
+
/** Driving a chart's own timer: its play/pause events are not the user's. */
|
|
6762
|
+
this.muted = 0;
|
|
6763
|
+
/** A chart finished during a group step — the session ends right after it. */
|
|
6764
|
+
this.finishedDuringStep = false;
|
|
6765
|
+
for (const cell of host.cells()) this.watch(cell);
|
|
6766
|
+
this.offHost = host.onCells((e) => {
|
|
6767
|
+
if (e.kind === "created") this.onCellCreated(e.id);
|
|
6768
|
+
else if (e.kind === "destroyed") this.onCellDestroyed(e.id);
|
|
6769
|
+
});
|
|
6770
|
+
}
|
|
6771
|
+
/**
|
|
6772
|
+
* Enter replay on every cell (or seek, when already replaying) — paused. Resolves once
|
|
6773
|
+
* every chart shows its rewound history (a chart whose history does not reach that far
|
|
6774
|
+
* loads older bars first). A chart with nothing to replay after the shared time stays
|
|
6775
|
+
* as it is.
|
|
6776
|
+
*/
|
|
6777
|
+
async start(opts) {
|
|
6778
|
+
const cells = this.host.cells();
|
|
6779
|
+
const ref = cells.find((c) => c.id === (opts.cell ?? this.host.activeId())) ?? cells[0];
|
|
6780
|
+
if (!ref) return;
|
|
6781
|
+
if (this.playing) this.pause();
|
|
6782
|
+
this.rejoining.clear();
|
|
6783
|
+
await ref.chart.replay.start({ from: opts.from });
|
|
6784
|
+
const cursor = ref.chart.replay.state.cursorTime;
|
|
6785
|
+
if (cursor == null) return;
|
|
6786
|
+
const session = { clock: barClose(cursor, ref.chart.market.timeframe) };
|
|
6787
|
+
this.session = session;
|
|
6788
|
+
await Promise.all(this.host.cells().filter((c) => c.id !== ref.id).map((c) => this.startCell(c, session.clock)));
|
|
6789
|
+
if (this.session !== session) return;
|
|
6790
|
+
this.syncPlayback();
|
|
6791
|
+
const s = this.activeChart()?.replay.state;
|
|
6792
|
+
this.bus.emit("replay:start", { cursorTime: s?.cursorTime ?? cursor, remaining: s?.remaining ?? 0 });
|
|
6793
|
+
}
|
|
6794
|
+
/** Reveal the next bar(s): the clock moves to the next bar close on any chart. Returns false when nothing is left. */
|
|
6795
|
+
step() {
|
|
6796
|
+
const solo = this.solo();
|
|
6797
|
+
return solo ? solo.replay.step() : this.stepGroup();
|
|
6798
|
+
}
|
|
6799
|
+
/** Reveal the next update — in a single-chart layout, the chart's `stepUpdate()` (tick replay); otherwise {@link step}. */
|
|
6800
|
+
stepUpdate() {
|
|
6801
|
+
const solo = this.solo();
|
|
6802
|
+
return solo ? solo.replay.stepUpdate() : this.stepGroup();
|
|
6803
|
+
}
|
|
6804
|
+
/** Advance one update every `intervalMs` (default: the last pace, initially 1000). Calling it again while playing changes the pace. */
|
|
6805
|
+
play(intervalMs) {
|
|
6806
|
+
if (!this.session) return this;
|
|
6807
|
+
if (intervalMs !== void 0) {
|
|
6808
|
+
if (!(Number.isFinite(intervalMs) && intervalMs > 0)) {
|
|
6809
|
+
console.warn(`[vela] workspace.replay.play(${intervalMs}) ignored \u2014 the interval must be a positive number of ms`);
|
|
6810
|
+
return this;
|
|
6811
|
+
}
|
|
6812
|
+
this.intervalMs = intervalMs;
|
|
6813
|
+
}
|
|
6814
|
+
this.playing = true;
|
|
6815
|
+
this.clearTimer();
|
|
6816
|
+
this.syncPlayback();
|
|
6817
|
+
this.bus.emit("replay:play", { intervalMs: this.intervalMs });
|
|
6818
|
+
return this;
|
|
6819
|
+
}
|
|
6820
|
+
pause() {
|
|
6821
|
+
if (!this.playing) return this;
|
|
6822
|
+
this.playing = false;
|
|
6823
|
+
this.syncPlayback();
|
|
6824
|
+
this.bus.emit("replay:pause", void 0);
|
|
6825
|
+
return this;
|
|
6826
|
+
}
|
|
6827
|
+
/** Leave replay on every chart: full history back, live updates resumed. */
|
|
6828
|
+
stop() {
|
|
6829
|
+
this.finish("stopped");
|
|
6830
|
+
return this;
|
|
6831
|
+
}
|
|
6832
|
+
/** `active`/`playing`/`intervalMs` for the whole workspace; the cursor fields read the ACTIVE cell's chart. */
|
|
6833
|
+
get state() {
|
|
6834
|
+
const s = this.session ? this.activeChart()?.replay.state : void 0;
|
|
6835
|
+
return {
|
|
6836
|
+
active: this.session !== null,
|
|
6837
|
+
playing: this.playing,
|
|
6838
|
+
cursorTime: s?.cursorTime ?? null,
|
|
6839
|
+
remaining: s?.remaining ?? 0,
|
|
6840
|
+
nextTime: s?.nextTime ?? null,
|
|
6841
|
+
intervalMs: this.intervalMs
|
|
6842
|
+
};
|
|
6843
|
+
}
|
|
6844
|
+
/** The active cell's replayable history (`chart.replay.bounds`). */
|
|
6845
|
+
get bounds() {
|
|
6846
|
+
return this.activeChart()?.replay.bounds ?? null;
|
|
6847
|
+
}
|
|
6848
|
+
on(event, handler) {
|
|
6849
|
+
return this.bus.on(event, handler);
|
|
6850
|
+
}
|
|
6851
|
+
destroy() {
|
|
6852
|
+
this.session = null;
|
|
6853
|
+
this.playing = false;
|
|
6854
|
+
this.clearTimer();
|
|
6855
|
+
this.offHost();
|
|
6856
|
+
for (const off of this.cellSubs.values()) off();
|
|
6857
|
+
this.cellSubs.clear();
|
|
6858
|
+
this.bus.clear();
|
|
6859
|
+
}
|
|
6860
|
+
// ── internals ──
|
|
6861
|
+
/** The one chart of a single-chart layout — it replays on its own, tick replay included. */
|
|
6862
|
+
solo() {
|
|
6863
|
+
const cells = this.host.cells();
|
|
6864
|
+
return cells.length === 1 ? cells[0].chart : null;
|
|
6865
|
+
}
|
|
6866
|
+
activeChart() {
|
|
6867
|
+
const cells = this.host.cells();
|
|
6868
|
+
return (cells.find((c) => c.id === this.host.activeId()) ?? cells[0])?.chart ?? null;
|
|
6869
|
+
}
|
|
6870
|
+
/** Rewind one chart to the shared time: every bar that closed by `clock`, none after. */
|
|
6871
|
+
async startCell(cell, clock) {
|
|
6872
|
+
const replay = cell.chart.replay;
|
|
6873
|
+
const from = lastOpenClosedBy(clock, cell.chart.market.timeframe);
|
|
6874
|
+
const bounds = replay.bounds;
|
|
6875
|
+
if (!bounds || bounds.last <= from) return;
|
|
6876
|
+
await replay.start({ from });
|
|
6877
|
+
}
|
|
6878
|
+
/** Move the clock to the next bar close on any chart; each chart reveals what closed by then. */
|
|
6879
|
+
stepGroup() {
|
|
6880
|
+
const session = this.session;
|
|
6881
|
+
if (!session) return false;
|
|
6882
|
+
const cells = this.host.cells();
|
|
6883
|
+
let next = Infinity;
|
|
6884
|
+
for (const c of cells) {
|
|
6885
|
+
const t = c.chart.replay.state.nextTime;
|
|
6886
|
+
if (t != null) next = Math.min(next, barClose(t, c.chart.market.timeframe));
|
|
6887
|
+
}
|
|
6888
|
+
if (!Number.isFinite(next)) return false;
|
|
6889
|
+
this.stepping = true;
|
|
6890
|
+
try {
|
|
6891
|
+
for (const c of cells) {
|
|
6892
|
+
const tf = c.chart.market.timeframe;
|
|
6893
|
+
for (let n = 0; n < MAX_BARS_PER_STEP; n += 1) {
|
|
6894
|
+
const t = c.chart.replay.state.nextTime;
|
|
6895
|
+
if (t == null || barClose(t, tf) > next) break;
|
|
6896
|
+
if (!c.chart.replay.step()) break;
|
|
6897
|
+
}
|
|
6898
|
+
}
|
|
6899
|
+
} finally {
|
|
6900
|
+
this.stepping = false;
|
|
6901
|
+
}
|
|
6902
|
+
session.clock = next;
|
|
6903
|
+
if (this.finishedDuringStep) {
|
|
6904
|
+
this.finishedDuringStep = false;
|
|
6905
|
+
this.finish("finished");
|
|
6906
|
+
return true;
|
|
6907
|
+
}
|
|
6908
|
+
const s = this.activeChart()?.replay.state;
|
|
6909
|
+
if (s?.cursorTime != null) this.bus.emit("replay:step", { cursorTime: s.cursorTime, remaining: s.remaining });
|
|
6910
|
+
return true;
|
|
6911
|
+
}
|
|
6912
|
+
/** Put the timers in the state `playing` asks for: a single chart runs its own (it paces
|
|
6913
|
+
* ticks), several run on the controller's clock. */
|
|
6914
|
+
syncPlayback() {
|
|
6915
|
+
const solo = this.solo();
|
|
6916
|
+
const run = this.playing && this.session !== null;
|
|
6917
|
+
this.muted += 1;
|
|
6918
|
+
try {
|
|
6919
|
+
for (const c of this.host.cells()) {
|
|
6920
|
+
if (c.chart === solo && run) {
|
|
6921
|
+
if (!c.chart.replay.state.playing || c.chart.replay.state.intervalMs !== this.intervalMs) c.chart.replay.play(this.intervalMs);
|
|
6922
|
+
} else if (c.chart.replay.state.playing) {
|
|
6923
|
+
c.chart.replay.pause();
|
|
6924
|
+
}
|
|
6925
|
+
}
|
|
6926
|
+
} finally {
|
|
6927
|
+
this.muted -= 1;
|
|
6928
|
+
}
|
|
6929
|
+
if (!run || solo) this.clearTimer();
|
|
6930
|
+
else if (this.timer == null) this.schedule();
|
|
6931
|
+
}
|
|
6932
|
+
schedule() {
|
|
6933
|
+
this.timer = setTimeout(() => {
|
|
6934
|
+
this.timer = null;
|
|
6935
|
+
if (!this.playing || !this.session || this.solo()) return;
|
|
6936
|
+
if (this.stepGroup() || this.rejoining.size > 0) {
|
|
6937
|
+
if (this.playing && this.session && this.timer == null) this.schedule();
|
|
6938
|
+
} else {
|
|
6939
|
+
this.pause();
|
|
6940
|
+
}
|
|
6941
|
+
}, this.intervalMs);
|
|
6942
|
+
}
|
|
6943
|
+
clearTimer() {
|
|
6944
|
+
if (this.timer != null) clearTimeout(this.timer);
|
|
6945
|
+
this.timer = null;
|
|
6946
|
+
}
|
|
6947
|
+
/** End the session on every chart (idempotent). */
|
|
6948
|
+
finish(reason) {
|
|
6949
|
+
const had = this.session !== null;
|
|
6950
|
+
this.session = null;
|
|
6951
|
+
this.playing = false;
|
|
6952
|
+
this.clearTimer();
|
|
6953
|
+
this.rejoining.clear();
|
|
6954
|
+
this.stopping = true;
|
|
6955
|
+
try {
|
|
6956
|
+
for (const c of this.host.cells()) c.chart.replay.stop();
|
|
6957
|
+
} finally {
|
|
6958
|
+
this.stopping = false;
|
|
6959
|
+
}
|
|
6960
|
+
if (had) this.bus.emit("replay:end", { reason });
|
|
6961
|
+
}
|
|
6962
|
+
watch(cell) {
|
|
6963
|
+
const { id, chart } = cell;
|
|
6964
|
+
const isActive = () => (this.host.activeId() ?? this.host.cells()[0]?.id) === id;
|
|
6965
|
+
const offs = [
|
|
6966
|
+
chart.on("replay:end", ({ reason }) => {
|
|
6967
|
+
if (!this.session || this.stopping) return;
|
|
6968
|
+
if (reason === "market") this.rejoining.add(id);
|
|
6969
|
+
else if (this.stepping) this.finishedDuringStep || (this.finishedDuringStep = reason === "finished");
|
|
6970
|
+
else this.finish(reason);
|
|
6971
|
+
}),
|
|
6972
|
+
chart.on("market:changed", () => {
|
|
6973
|
+
if (!this.rejoining.delete(id) || !this.session) return;
|
|
6974
|
+
void this.startCell(cell, this.session.clock).then(() => this.syncPlayback());
|
|
6975
|
+
}),
|
|
6976
|
+
chart.on("replay:step", (e) => {
|
|
6977
|
+
if (this.session && !this.stepping && isActive()) this.bus.emit("replay:step", e);
|
|
6978
|
+
}),
|
|
6979
|
+
chart.on("replay:tick", (e) => {
|
|
6980
|
+
if (this.session && isActive()) this.bus.emit("replay:tick", e);
|
|
6981
|
+
}),
|
|
6982
|
+
// A single chart runs its own timer: a pause it takes on its own (a seek) is the session's.
|
|
6983
|
+
chart.on("replay:pause", () => {
|
|
6984
|
+
if (!this.session || this.muted > 0 || !this.playing || chart !== this.solo()) return;
|
|
6985
|
+
this.playing = false;
|
|
6986
|
+
this.bus.emit("replay:pause", void 0);
|
|
6987
|
+
})
|
|
6988
|
+
];
|
|
6989
|
+
this.cellSubs.set(id, () => {
|
|
6990
|
+
for (const off of offs) off();
|
|
6991
|
+
});
|
|
6992
|
+
}
|
|
6993
|
+
onCellCreated(id) {
|
|
6994
|
+
const cell = this.host.cells().find((c) => c.id === id);
|
|
6995
|
+
if (!cell) return;
|
|
6996
|
+
this.watch(cell);
|
|
6997
|
+
const session = this.session;
|
|
6998
|
+
if (!session) return;
|
|
6999
|
+
this.rejoining.add(id);
|
|
7000
|
+
void cell.chart.ready().then(async () => {
|
|
7001
|
+
if (this.session !== session || !this.rejoining.delete(id)) return;
|
|
7002
|
+
await this.startCell(cell, session.clock);
|
|
7003
|
+
this.syncPlayback();
|
|
7004
|
+
});
|
|
7005
|
+
this.syncPlayback();
|
|
7006
|
+
}
|
|
7007
|
+
onCellDestroyed(id) {
|
|
7008
|
+
this.cellSubs.get(id)?.();
|
|
7009
|
+
this.cellSubs.delete(id);
|
|
7010
|
+
this.rejoining.delete(id);
|
|
7011
|
+
this.syncPlayback();
|
|
7012
|
+
}
|
|
7013
|
+
};
|
|
7014
|
+
function barClose(open, timeframe) {
|
|
7015
|
+
const months = monthSpan(timeframe);
|
|
7016
|
+
if (months > 0) {
|
|
7017
|
+
const d = new Date(open);
|
|
7018
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + months, d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes());
|
|
7019
|
+
}
|
|
7020
|
+
return open + timeframeToMs(timeframe ?? "60");
|
|
7021
|
+
}
|
|
7022
|
+
function lastOpenClosedBy(time, timeframe) {
|
|
7023
|
+
const months = monthSpan(timeframe);
|
|
7024
|
+
if (months > 0) {
|
|
7025
|
+
const d = new Date(time);
|
|
7026
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth() - months, d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes());
|
|
7027
|
+
}
|
|
7028
|
+
return time - timeframeToMs(timeframe ?? "60");
|
|
7029
|
+
}
|
|
7030
|
+
function monthSpan(timeframe) {
|
|
7031
|
+
const m = /^(\d*)M$/.exec((timeframe ?? "").trim());
|
|
7032
|
+
return m ? Number(m[1] || 1) : 0;
|
|
7033
|
+
}
|
|
7034
|
+
|
|
6642
7035
|
// src/workspace/layouts.ts
|
|
6643
7036
|
var registry = /* @__PURE__ */ new Map();
|
|
6644
7037
|
function registerLayout(def) {
|
|
@@ -7095,16 +7488,16 @@ var MobileBar = class {
|
|
|
7095
7488
|
host.appendChild(this.el);
|
|
7096
7489
|
this.renderActions();
|
|
7097
7490
|
}
|
|
7098
|
-
/** Re-project the
|
|
7099
|
-
* indicators slot (call after
|
|
7100
|
-
*
|
|
7491
|
+
/** Re-project the contributed actions placed on the bar (left-aligned by default —
|
|
7492
|
+
* see `mobilePlacement`) as icon-only stops in the indicators slot (call after
|
|
7493
|
+
* registrations change). The rest are three-dots drawer rows. */
|
|
7101
7494
|
renderActions() {
|
|
7102
7495
|
const ctx = this.opts.getContext?.();
|
|
7103
7496
|
if (!ctx) return;
|
|
7104
7497
|
const doc = this.el.ownerDocument;
|
|
7105
7498
|
this.actionsHost.replaceChildren();
|
|
7106
7499
|
const builtin = new Set(TOPBAR_BUILTIN_IDS);
|
|
7107
|
-
for (const action of widgetActions("topbar", ctx).filter((a) => a
|
|
7500
|
+
for (const action of widgetActions("topbar", ctx).filter((a) => mobilePlacement(a) === "bar" && !builtin.has(a.id))) {
|
|
7108
7501
|
const b = doc.createElement("button");
|
|
7109
7502
|
b.className = "vela-mb-item";
|
|
7110
7503
|
b.setAttribute("aria-label", action.label);
|
|
@@ -7660,6 +8053,7 @@ var MoreDrawer = class {
|
|
|
7660
8053
|
const value = shape ? `${shape.cols} \xD7 ${shape.rows}` : this.opts.layout.presets().find((p) => p.checked)?.label;
|
|
7661
8054
|
list.appendChild(this.row(doc, "Layout", { icon: "layout", value, chevron: true, onClick: () => this.show("layout") }));
|
|
7662
8055
|
}
|
|
8056
|
+
for (const act of this.opts.primaryActions?.() ?? []) list.appendChild(this.actionRow(doc, act));
|
|
7663
8057
|
for (const panel of this.opts.panels()) {
|
|
7664
8058
|
list.appendChild(
|
|
7665
8059
|
this.row(doc, panel.title, {
|
|
@@ -7675,19 +8069,19 @@ var MoreDrawer = class {
|
|
|
7675
8069
|
const alertCount = this.opts.alerts().length;
|
|
7676
8070
|
list.appendChild(this.row(doc, "Alerts", { icon: "bell", value: alertCount > 0 ? String(alertCount) : void 0, chevron: true, onClick: () => this.show("alerts") }));
|
|
7677
8071
|
}
|
|
7678
|
-
for (const act of this.opts.actions())
|
|
7679
|
-
list.appendChild(
|
|
7680
|
-
this.row(doc, act.label, {
|
|
7681
|
-
icon: act.icon,
|
|
7682
|
-
onClick: () => {
|
|
7683
|
-
act.run();
|
|
7684
|
-
this.drawer.hide();
|
|
7685
|
-
}
|
|
7686
|
-
})
|
|
7687
|
-
);
|
|
7688
|
-
}
|
|
8072
|
+
for (const act of this.opts.actions()) list.appendChild(this.actionRow(doc, act));
|
|
7689
8073
|
this.drawer.body.appendChild(list);
|
|
7690
8074
|
}
|
|
8075
|
+
/** A contributed action's row: runs it and closes the drawer. */
|
|
8076
|
+
actionRow(doc, act) {
|
|
8077
|
+
return this.row(doc, act.label, {
|
|
8078
|
+
icon: act.icon,
|
|
8079
|
+
onClick: () => {
|
|
8080
|
+
act.run();
|
|
8081
|
+
this.drawer.hide();
|
|
8082
|
+
}
|
|
8083
|
+
});
|
|
8084
|
+
}
|
|
7691
8085
|
renderStyle(doc) {
|
|
7692
8086
|
const list = doc.createElement("div");
|
|
7693
8087
|
list.className = "vela-md-list";
|
|
@@ -8151,6 +8545,7 @@ function buildContext(host) {
|
|
|
8151
8545
|
setPriceStyle: (style) => host.active()?.setPriceStyle(style),
|
|
8152
8546
|
openSymbolSearch: (query) => host.openSymbolSearch(query),
|
|
8153
8547
|
togglePanel: (id, open) => host.togglePanel(id, open),
|
|
8548
|
+
dockStrip: (el) => host.dockStrip(el),
|
|
8154
8549
|
host: host.root,
|
|
8155
8550
|
toast: (message, kind) => host.toast(message, kind),
|
|
8156
8551
|
addIndicator: (entry) => host.active()?.addExternalIndicator(entry),
|
|
@@ -8162,7 +8557,8 @@ function buildContext(host) {
|
|
|
8162
8557
|
get activeCellId() {
|
|
8163
8558
|
return host.active()?.id ?? "";
|
|
8164
8559
|
},
|
|
8165
|
-
setActiveCell: (id) => host.setActiveCell(id)
|
|
8560
|
+
setActiveCell: (id) => host.setActiveCell(id),
|
|
8561
|
+
replay: host.replay
|
|
8166
8562
|
};
|
|
8167
8563
|
}
|
|
8168
8564
|
|
|
@@ -8225,6 +8621,8 @@ var CSS21 = `
|
|
|
8225
8621
|
.vela-workspace { position: relative; width: 100%; height: 100%; display: flex; flex-direction: column; background: var(--vela-bg); }
|
|
8226
8622
|
.vela-ws-main { position: relative; display: flex; flex-direction: row; flex: 1 1 auto; min-height: 0; }
|
|
8227
8623
|
.vela-ws-toolbar { position: relative; flex: none; }
|
|
8624
|
+
.vela-ws-strips { position: relative; flex: none; display: flex; flex-direction: column; min-width: 0; }
|
|
8625
|
+
.vela-ws-strips:empty { display: none; }
|
|
8228
8626
|
.vela-ws-grid { position: relative; flex: 1 1 auto; min-width: 0; display: grid; gap: ${GAP_PX}px; background: var(--vela-border-soft); }
|
|
8229
8627
|
.vela-cell { background: var(--vela-bg); position: relative; }
|
|
8230
8628
|
/* Active-cell highlight: an overlay ring ABOVE the chart's own canvas stack (a plain
|
|
@@ -8283,11 +8681,11 @@ function resolveTyping(key, open) {
|
|
|
8283
8681
|
if (open.dialogs > 0) {
|
|
8284
8682
|
if (key.length !== 1) return null;
|
|
8285
8683
|
if (open.symbolSearch) return { target: "symbol", text: key.toUpperCase() };
|
|
8286
|
-
if (open.timeframeEntry) return { target: "timeframe", text: key };
|
|
8684
|
+
if (open.timeframeEntry) return { target: "timeframe", text: key.toUpperCase() };
|
|
8287
8685
|
return null;
|
|
8288
8686
|
}
|
|
8289
|
-
if (/^[a-zA-
|
|
8290
|
-
if (/^[
|
|
8687
|
+
if (/^[a-zA-Z0]$/.test(key)) return { target: "symbol", text: key.toUpperCase() };
|
|
8688
|
+
if (/^[1-9]$/.test(key)) return { target: "timeframe", text: key };
|
|
8291
8689
|
return null;
|
|
8292
8690
|
}
|
|
8293
8691
|
var VelaWorkspace = class {
|
|
@@ -8396,6 +8794,20 @@ var VelaWorkspace = class {
|
|
|
8396
8794
|
const hostEl = typeof container === "string" ? document.querySelector(container) : container;
|
|
8397
8795
|
if (!hostEl) throw new Error(`VelaWorkspace: container not found: ${String(container)}`);
|
|
8398
8796
|
this.opts = opts;
|
|
8797
|
+
this.replay = new WorkspaceReplay({
|
|
8798
|
+
cells: () => this.cells(),
|
|
8799
|
+
activeId: () => this.activeId,
|
|
8800
|
+
onCells: (handler) => {
|
|
8801
|
+
const offs = [
|
|
8802
|
+
this.events.on("cell:created", ({ id }) => handler({ kind: "created", id })),
|
|
8803
|
+
this.events.on("cell:destroyed", ({ id }) => handler({ kind: "destroyed", id })),
|
|
8804
|
+
this.events.on("cell:active", ({ id }) => handler({ kind: "active", id }))
|
|
8805
|
+
];
|
|
8806
|
+
return () => {
|
|
8807
|
+
for (const off of offs) off();
|
|
8808
|
+
};
|
|
8809
|
+
}
|
|
8810
|
+
});
|
|
8399
8811
|
this.persistKey = opts.persist === void 0 || opts.persist === false ? null : opts.persist === true ? "vela-workspace" : opts.persist;
|
|
8400
8812
|
this.storage = opts.storage ?? localStorageAdapter();
|
|
8401
8813
|
let boot = null;
|
|
@@ -8533,6 +8945,9 @@ var VelaWorkspace = class {
|
|
|
8533
8945
|
this.dock.refresh();
|
|
8534
8946
|
if (boot?.panels) this.dock.applyState(boot.panels);
|
|
8535
8947
|
this.root.appendChild(main);
|
|
8948
|
+
this.stripsEl = doc.createElement("div");
|
|
8949
|
+
this.stripsEl.className = "vela-ws-strips";
|
|
8950
|
+
this.root.appendChild(this.stripsEl);
|
|
8536
8951
|
this.toastHost = new Toast(this.gridEl);
|
|
8537
8952
|
const attribution = rendererDefaults().attribution;
|
|
8538
8953
|
if (attribution !== false) {
|
|
@@ -8767,9 +9182,16 @@ var VelaWorkspace = class {
|
|
|
8767
9182
|
setActiveCell: (id) => this.setActiveCell(id),
|
|
8768
9183
|
openSymbolSearch: (query) => this.symbolPicker.open(query ?? ""),
|
|
8769
9184
|
togglePanel: (id, open) => this.dock.toggle(id, open),
|
|
9185
|
+
dockStrip: (el) => {
|
|
9186
|
+
this.stripsEl.appendChild(el);
|
|
9187
|
+
return () => {
|
|
9188
|
+
if (el.parentElement === this.stripsEl) el.remove();
|
|
9189
|
+
};
|
|
9190
|
+
},
|
|
8770
9191
|
root: this.root,
|
|
8771
9192
|
toast: (message, kind) => this.toastHost.show(message, kind),
|
|
8772
|
-
stateDirty: () => this.markStateDirty()
|
|
9193
|
+
stateDirty: () => this.markStateDirty(),
|
|
9194
|
+
replay: this.replay
|
|
8773
9195
|
});
|
|
8774
9196
|
}
|
|
8775
9197
|
/** Re-project contributed topbar actions + side panels, and mount late-registered attachments. */
|
|
@@ -9077,6 +9499,7 @@ var VelaWorkspace = class {
|
|
|
9077
9499
|
if (this.destroyed) return;
|
|
9078
9500
|
this.flushPendingState();
|
|
9079
9501
|
this.destroyed = true;
|
|
9502
|
+
this.replay.destroy();
|
|
9080
9503
|
if (this.persistKey !== null && typeof window !== "undefined") window.removeEventListener("beforeunload", this.onUnload);
|
|
9081
9504
|
this.resizeObserver?.disconnect();
|
|
9082
9505
|
this.splitters.destroy();
|
|
@@ -9843,14 +10266,13 @@ var VelaWorkspace = class {
|
|
|
9843
10266
|
panels: () => has("panels") ? [...this.dock.list()] : [],
|
|
9844
10267
|
onTogglePanel: (id) => this.dock.toggle(id),
|
|
9845
10268
|
...has("alerts") ? { alerts: () => this.alerts.map((a) => ({ title: `${a.source} \xB7 ${a.title}`, message: a.message, time: a.time })) } : {},
|
|
9846
|
-
//
|
|
9847
|
-
//
|
|
9848
|
-
//
|
|
9849
|
-
//
|
|
9850
|
-
|
|
9851
|
-
|
|
9852
|
-
|
|
9853
|
-
},
|
|
10269
|
+
// Actions placed on the bottom bar (left-aligned by default) have their stop
|
|
10270
|
+
// there — only the menu-placed ones land in the drawer, or they would appear
|
|
10271
|
+
// twice: left (primary) ones with the primary rows, right ones at the end.
|
|
10272
|
+
// Built-in-id actions are slot OVERRIDES: they reach the drawer through the
|
|
10273
|
+
// slot's own routed button (screenshot) or stop (indicators), never as a row.
|
|
10274
|
+
primaryActions: () => this.drawerActions("left"),
|
|
10275
|
+
actions: () => this.drawerActions("right"),
|
|
9854
10276
|
// The desktop layout dropdown's whole surface — the grid canvas, the
|
|
9855
10277
|
// non-canvas presets and the sync switches — relocated into the kebab
|
|
9856
10278
|
// drawer (the topbar is hidden on mobile). Same reads as the topbar block;
|
|
@@ -9875,6 +10297,11 @@ var VelaWorkspace = class {
|
|
|
9875
10297
|
}));
|
|
9876
10298
|
this.moreDrawer.open();
|
|
9877
10299
|
}
|
|
10300
|
+
/** The contributed topbar actions the mobile menu lists, from one cluster. */
|
|
10301
|
+
drawerActions(cluster) {
|
|
10302
|
+
const builtin = new Set(TOPBAR_BUILTIN_IDS);
|
|
10303
|
+
return widgetActions("topbar", this.context()).filter((a) => mobilePlacement(a) === "menu" && a.align === "left" === (cluster === "left") && !builtin.has(a.id)).map((a) => ({ label: a.label, icon: a.icon, run: () => a.run(this.context()) }));
|
|
10304
|
+
}
|
|
9878
10305
|
openTimezoneDrawer() {
|
|
9879
10306
|
this.timezoneDrawer ?? (this.timezoneDrawer = new TimezoneDrawer({
|
|
9880
10307
|
host: this.root,
|
|
@@ -9996,7 +10423,7 @@ var VelaWorkspace = class {
|
|
|
9996
10423
|
}
|
|
9997
10424
|
});
|
|
9998
10425
|
}
|
|
9999
|
-
/** Bare-typing router: letters → symbol search (seeded), digits → timeframe entry. */
|
|
10426
|
+
/** Bare-typing router: letters and `0` → symbol search (seeded), digits 1–9 → timeframe entry. */
|
|
10000
10427
|
routeTyping(ev) {
|
|
10001
10428
|
if (this.destroyed) return;
|
|
10002
10429
|
if (ev.ctrlKey || ev.metaKey || ev.altKey) return;
|
|
@@ -10038,4 +10465,4 @@ var VelaWorkspace = class {
|
|
|
10038
10465
|
}
|
|
10039
10466
|
};
|
|
10040
10467
|
|
|
10041
|
-
export { Bottombar, ChartCell, ChartContextMenu, DataWindow, GRID_PICKER_MAX, IndicatorPicker, ObjectTree, PanelDock, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeQuick, Topbar, VelaWorkspace, Watermark, activeAfterLayout, dataWindowSections, decimalsFor, decodeState, encodeState, ensureLayout, evenTracks, filterSymbols, fmtChange, fmtPrice, gridStyles, layoutDefinition, layoutForGrid, layoutShape, layouts, localStorageAdapter, memoryStorageAdapter, parseTimeframe, priceStyleLabel, rangesWithin, registerBuiltinLayouts, registerLayout, resizeTracks, resolveIndicators, sanitizeState, syncTargets, timeframeLabel, timeframeMs, trackOffsets, unregisterLayout };
|
|
10468
|
+
export { Bottombar, ChartCell, ChartContextMenu, DataWindow, GRID_PICKER_MAX, IndicatorPicker, ObjectTree, PanelDock, RANGE_PRESETS, ShortcutsHelp, Statusline, SymbolPicker, TimeframeQuick, Topbar, VelaWorkspace, Watermark, WorkspaceReplay, activeAfterLayout, barClose, dataWindowSections, decimalsFor, decodeState, encodeState, ensureLayout, evenTracks, filterSymbols, fmtChange, fmtPrice, gridStyles, lastOpenClosedBy, layoutDefinition, layoutForGrid, layoutShape, layouts, localStorageAdapter, memoryStorageAdapter, parseTimeframe, priceStyleLabel, rangesWithin, registerBuiltinLayouts, registerLayout, resizeTracks, resolveIndicators, sanitizeState, syncTargets, timeframeLabel, timeframeMs, trackOffsets, unregisterLayout };
|