@luxalgo/vela 0.7.8 → 0.8.0
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/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-WTWGEB7K.js → chunk-2IAETM2I.js} +260 -2
- package/dist/{chunk-CAFCLMPF.js → chunk-BZQM2XO7.js} +1 -0
- package/dist/{chunk-M6GP7DAX.js → chunk-E3GU6AUI.js} +459 -37
- package/dist/{chunk-BFA32GOU.js → chunk-EZ5FWVLA.js} +5 -2
- package/dist/{chunk-PVGWNF7V.js → chunk-KA77UTRF.js} +2 -2
- package/dist/{chunk-N4ZZYLMP.js → chunk-QBPUT5SU.js} +686 -251
- 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 +966 -247
- 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 +263 -0
- 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 +968 -249
- package/dist/vela.global.min.js +73 -71
- package/dist/widget.cjs +1411 -292
- package/dist/widget.d.cts +26 -7
- package/dist/widget.d.ts +26 -7
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +1414 -292
- package/dist/workspace.d.cts +124 -6
- package/dist/workspace.d.ts +124 -6
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
|
@@ -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-QBPUT5SU.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-KA77UTRF.js';
|
|
4
|
+
import { Menu, Dialog, CalloutBubble, applyPlotOverlayTokens, ensureUIHost } from './chunk-2IAETM2I.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
|
|
@@ -3925,6 +3925,7 @@ function sanitizeCell(raw) {
|
|
|
3925
3925
|
if (typeof c.bars === "number" && Number.isFinite(c.bars) && c.bars > 0) out.bars = c.bars;
|
|
3926
3926
|
if (c.session === "regular" || c.session === "extended") out.session = c.session;
|
|
3927
3927
|
if (typeof c.watermark === "boolean") out.watermark = c.watermark;
|
|
3928
|
+
if (typeof c.replayWatermark === "boolean") out.replayWatermark = c.replayWatermark;
|
|
3928
3929
|
if (typeof c.indicatorTitles === "boolean") out.indicatorTitles = c.indicatorTitles;
|
|
3929
3930
|
if (typeof c.indicatorValues === "boolean") out.indicatorValues = c.indicatorValues;
|
|
3930
3931
|
if (c.rendererConfig != null && typeof c.rendererConfig === "object") out.rendererConfig = c.rendererConfig;
|
|
@@ -4208,9 +4209,15 @@ var CSS10 = `
|
|
|
4208
4209
|
}
|
|
4209
4210
|
.vela-statusline .vela-sl-symbol { font-weight: 600; font-size: var(--vela-font-size-lg); }
|
|
4210
4211
|
.vela-statusline .vela-sl-meta { color: var(--vela-fg-muted); font-size: var(--vela-font-size-md); font-weight: 600; }
|
|
4212
|
+
/* The meta opens with "\xB7 " \u2014 sit its dot one space-width after the ticker, as far as the
|
|
4213
|
+
* venue sits after it, not a full row gap away. */
|
|
4214
|
+
.vela-statusline .vela-sl-symbol + .vela-sl-meta { margin-left: calc(var(--vela-space-1) - var(--vela-space-2)); }
|
|
4211
4215
|
/* 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
|
-
|
|
4216
|
+
* via the kit tooltip); the session tint is applied per status in setMarketStatus. While
|
|
4217
|
+
* the chart replays past bars it wears the replay badge instead (the inverse chip). */
|
|
4218
|
+
.vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
|
|
4219
|
+
.vela-statusline .vela-sl-market > [hidden] { display: none !important; }
|
|
4220
|
+
.vela-statusline .vela-sl-replay-badge, .vela-statusline .vela-sl-replay-badge svg { display: block; width: 16px; height: 16px; }
|
|
4214
4221
|
.vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
|
|
4215
4222
|
.vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
|
|
4216
4223
|
/* The change value wears the SAME ink as the OHLC values (set inline per render) \u2014
|
|
@@ -4263,6 +4270,8 @@ var MARKET_LABELS = {
|
|
|
4263
4270
|
closed: "Market Closed",
|
|
4264
4271
|
holiday: "Market Holiday"
|
|
4265
4272
|
};
|
|
4273
|
+
var REPLAY_LABEL = "Replay Mode";
|
|
4274
|
+
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
4275
|
var MARKET_INKS = {
|
|
4267
4276
|
open: "var(--vela-up)",
|
|
4268
4277
|
pre: SESSION_PRE,
|
|
@@ -4301,6 +4310,9 @@ var Statusline = class {
|
|
|
4301
4310
|
this.host = host;
|
|
4302
4311
|
this.iconFor = iconFor;
|
|
4303
4312
|
this.parts = { logo: true, name: true, market: true, ohlc: true, change: true };
|
|
4313
|
+
this.marketStatus = "open";
|
|
4314
|
+
/** The chart replays past bars — the badge shows the replay mode (see {@link setReplaying}). */
|
|
4315
|
+
this.replaying = false;
|
|
4304
4316
|
/** The right-click action menu — present once a host wires it via {@link attachMenu}. */
|
|
4305
4317
|
this.menu = null;
|
|
4306
4318
|
this.menuHooks = null;
|
|
@@ -4352,8 +4364,13 @@ var Statusline = class {
|
|
|
4352
4364
|
label: MARKET_LABELS.open,
|
|
4353
4365
|
host
|
|
4354
4366
|
});
|
|
4355
|
-
this.
|
|
4356
|
-
this.
|
|
4367
|
+
this.replayBadge = doc.createElement("span");
|
|
4368
|
+
this.replayBadge.className = "vela-sl-replay-badge";
|
|
4369
|
+
this.replayBadge.innerHTML = REPLAY_BADGE_SVG;
|
|
4370
|
+
this.replayBadge.hidden = true;
|
|
4371
|
+
this.marketEl = doc.createElement("span");
|
|
4372
|
+
this.marketEl.className = "vela-sl-market";
|
|
4373
|
+
this.marketEl.append(this.marketBubble.el, this.replayBadge);
|
|
4357
4374
|
this.ohlcEl = doc.createElement("span");
|
|
4358
4375
|
this.ohlcEl.className = "vela-sl-ohlc";
|
|
4359
4376
|
this.changeEl = doc.createElement("span");
|
|
@@ -4475,6 +4492,25 @@ var Statusline = class {
|
|
|
4475
4492
|
/** Dress the market badge for a session state: its icon, tinted circle, and the
|
|
4476
4493
|
* hover label. Callers with no session model leave the constructor's 'open'. */
|
|
4477
4494
|
setMarketStatus(status) {
|
|
4495
|
+
this.marketStatus = status;
|
|
4496
|
+
this.dressBadge();
|
|
4497
|
+
}
|
|
4498
|
+
/** The chart entered or left a bar replay: the badge shows the replay mode meanwhile
|
|
4499
|
+
* (the session state of a past bar says nothing about the market now). */
|
|
4500
|
+
setReplaying(replaying) {
|
|
4501
|
+
if (replaying === this.replaying) return;
|
|
4502
|
+
this.replaying = replaying;
|
|
4503
|
+
this.dressBadge();
|
|
4504
|
+
}
|
|
4505
|
+
dressBadge() {
|
|
4506
|
+
this.marketBubble.el.hidden = this.replaying;
|
|
4507
|
+
this.replayBadge.hidden = !this.replaying;
|
|
4508
|
+
if (this.replaying) {
|
|
4509
|
+
this.marketEl.dataset.status = "replay";
|
|
4510
|
+
this.marketTip.setContent(REPLAY_LABEL);
|
|
4511
|
+
return;
|
|
4512
|
+
}
|
|
4513
|
+
const status = this.marketStatus;
|
|
4478
4514
|
this.marketEl.dataset.status = status;
|
|
4479
4515
|
const ink = MARKET_INKS[status];
|
|
4480
4516
|
this.marketBubble.set({
|
|
@@ -4547,11 +4583,23 @@ var Statusline = class {
|
|
|
4547
4583
|
this.lastBar = b;
|
|
4548
4584
|
if (!this.hoverBar) this.render();
|
|
4549
4585
|
}),
|
|
4586
|
+
// A replay cut or restore replaces the newest bar without a `bar` event.
|
|
4587
|
+
chart.on("replay:start", () => {
|
|
4588
|
+
this.lastBar = null;
|
|
4589
|
+
this.setReplaying(true);
|
|
4590
|
+
this.render();
|
|
4591
|
+
}),
|
|
4592
|
+
chart.on("replay:end", () => {
|
|
4593
|
+
this.lastBar = null;
|
|
4594
|
+
this.setReplaying(false);
|
|
4595
|
+
this.render();
|
|
4596
|
+
}),
|
|
4550
4597
|
chart.renderer.onCrosshairMove((e) => {
|
|
4551
4598
|
this.hoverBar = e.ohlc;
|
|
4552
4599
|
this.render();
|
|
4553
4600
|
})
|
|
4554
4601
|
);
|
|
4602
|
+
this.setReplaying(chart.replay.state.active);
|
|
4555
4603
|
this.render();
|
|
4556
4604
|
}
|
|
4557
4605
|
destroy() {
|
|
@@ -4620,6 +4668,7 @@ var CSS11 = `
|
|
|
4620
4668
|
left: var(--vela-toolbar-gutter, 0px);
|
|
4621
4669
|
right: var(--vela-scale-gutter, 0px);
|
|
4622
4670
|
display: flex;
|
|
4671
|
+
flex-direction: column;
|
|
4623
4672
|
align-items: center;
|
|
4624
4673
|
justify-content: center;
|
|
4625
4674
|
overflow: hidden;
|
|
@@ -4633,6 +4682,14 @@ var CSS11 = `
|
|
|
4633
4682
|
user-select: none;
|
|
4634
4683
|
white-space: nowrap;
|
|
4635
4684
|
}
|
|
4685
|
+
.vela-watermark-replay {
|
|
4686
|
+
display: inline-flex;
|
|
4687
|
+
align-items: center;
|
|
4688
|
+
gap: 0.25em;
|
|
4689
|
+
margin-top: 0.1em;
|
|
4690
|
+
font-size: 0.8em;
|
|
4691
|
+
}
|
|
4692
|
+
.vela-watermark [hidden] { display: none !important; }
|
|
4636
4693
|
`;
|
|
4637
4694
|
function watermarkFontPx(availPx, textPxAtMax) {
|
|
4638
4695
|
if (textPxAtMax <= 0) return MAX_FONT_PX;
|
|
@@ -4643,6 +4700,10 @@ var Watermark = class {
|
|
|
4643
4700
|
this.resizeObserver = null;
|
|
4644
4701
|
/** The host's visibility preference (the persisted watermark toggle). */
|
|
4645
4702
|
this.shown = true;
|
|
4703
|
+
/** The host's preference for the replay line (its own persisted toggle). */
|
|
4704
|
+
this.replayShown = true;
|
|
4705
|
+
/** The chart is replaying past bars. */
|
|
4706
|
+
this.replaying = false;
|
|
4646
4707
|
/** A bar load is in flight with nothing painted — the loading affordance owns the
|
|
4647
4708
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
4648
4709
|
* fires during chart construction, before any subscriber can see it. */
|
|
@@ -4652,7 +4713,10 @@ var Watermark = class {
|
|
|
4652
4713
|
this.el.className = "vela-watermark";
|
|
4653
4714
|
this.el.dataset.velaScreenshot = "under";
|
|
4654
4715
|
this.text = host.ownerDocument.createElement("span");
|
|
4655
|
-
this.
|
|
4716
|
+
this.replayLine = host.ownerDocument.createElement("span");
|
|
4717
|
+
this.replayLine.className = "vela-watermark-replay";
|
|
4718
|
+
this.replayLine.append(iconEl("replay", host.ownerDocument), host.ownerDocument.createTextNode("Replay"));
|
|
4719
|
+
this.el.append(this.text, this.replayLine);
|
|
4656
4720
|
host.appendChild(this.el);
|
|
4657
4721
|
if (typeof ResizeObserver !== "undefined") {
|
|
4658
4722
|
this.resizeObserver = new ResizeObserver(() => this.fit());
|
|
@@ -4665,13 +4729,26 @@ var Watermark = class {
|
|
|
4665
4729
|
this.shown = visible;
|
|
4666
4730
|
this.sync();
|
|
4667
4731
|
}
|
|
4732
|
+
/** Show/hide the replay line (the host's toggle); it only ever shows while replaying. */
|
|
4733
|
+
setReplayVisible(visible) {
|
|
4734
|
+
this.replayShown = visible;
|
|
4735
|
+
this.sync();
|
|
4736
|
+
}
|
|
4737
|
+
/** The chart entered or left a bar replay. */
|
|
4738
|
+
setReplaying(replaying) {
|
|
4739
|
+
this.replaying = replaying;
|
|
4740
|
+
this.sync();
|
|
4741
|
+
}
|
|
4668
4742
|
/** Loading and the watermark never share the canvas — hidden while a load is up. */
|
|
4669
4743
|
setLoading(loading) {
|
|
4670
4744
|
this.loading = loading;
|
|
4671
4745
|
this.sync();
|
|
4672
4746
|
}
|
|
4673
4747
|
sync() {
|
|
4674
|
-
|
|
4748
|
+
const replay = this.replayShown && this.replaying;
|
|
4749
|
+
this.text.hidden = !this.shown;
|
|
4750
|
+
this.replayLine.hidden = !replay;
|
|
4751
|
+
this.el.style.display = (this.shown || replay) && !this.loading ? "" : "none";
|
|
4675
4752
|
}
|
|
4676
4753
|
update(symbol, timeframe) {
|
|
4677
4754
|
this.text.textContent = symbol ? `${parseSymbol(symbol).ticker} \xB7 ${timeframeLabel(timeframe)}` : "";
|
|
@@ -5707,8 +5784,12 @@ var ChartCell = class {
|
|
|
5707
5784
|
this.indicatorValuesOn = seed.indicatorValues ?? true;
|
|
5708
5785
|
if (!this.indicatorValuesOn) this.inner.renderer.set("indicatorValues", false);
|
|
5709
5786
|
this.watermarkOn = seed.watermark ?? deps.watermark;
|
|
5787
|
+
this.replayWatermarkOn = seed.replayWatermark ?? true;
|
|
5710
5788
|
this.watermark = deps.watermark ? new Watermark(this.host, symbol ?? "", seed.timeframe ?? "60") : null;
|
|
5711
5789
|
if (!this.watermarkOn) this.watermark?.setVisible(false);
|
|
5790
|
+
if (!this.replayWatermarkOn) this.watermark?.setReplayVisible(false);
|
|
5791
|
+
this.inner.on("replay:start", () => this.watermark?.setReplaying(true));
|
|
5792
|
+
this.inner.on("replay:end", () => this.watermark?.setReplaying(false));
|
|
5712
5793
|
this.statusline = deps.statusline ? new Statusline(this.host, symbol ?? "", (sym) => this.inner?.data.symbolIcon(sym)) : null;
|
|
5713
5794
|
this.statusline?.setMeta(seed.timeframe ?? "60", this.state.provider ?? "");
|
|
5714
5795
|
this.statusline?.onChart(this.inner);
|
|
@@ -6022,6 +6103,13 @@ var ChartCell = class {
|
|
|
6022
6103
|
id: "visible",
|
|
6023
6104
|
get: () => this.watermarkOn,
|
|
6024
6105
|
set: (v) => this.setWatermarkVisible(v)
|
|
6106
|
+
},
|
|
6107
|
+
{
|
|
6108
|
+
kind: "toggle",
|
|
6109
|
+
label: "Replay watermark",
|
|
6110
|
+
id: "replay",
|
|
6111
|
+
get: () => this.replayWatermarkOn,
|
|
6112
|
+
set: (v) => this.setReplayWatermarkVisible(v)
|
|
6025
6113
|
}
|
|
6026
6114
|
]
|
|
6027
6115
|
};
|
|
@@ -6090,6 +6178,12 @@ var ChartCell = class {
|
|
|
6090
6178
|
this.watermark?.setVisible(visible);
|
|
6091
6179
|
this.deps.onStateDirty();
|
|
6092
6180
|
}
|
|
6181
|
+
/** Show/hide the "Replay" line under this cell's watermark while it replays (persisted per cell). */
|
|
6182
|
+
setReplayWatermarkVisible(visible) {
|
|
6183
|
+
this.replayWatermarkOn = visible;
|
|
6184
|
+
this.watermark?.setReplayVisible(visible);
|
|
6185
|
+
this.deps.onStateDirty();
|
|
6186
|
+
}
|
|
6093
6187
|
/** Show/hide this cell's indicator titles — the in-chart legend rows (persisted per cell). */
|
|
6094
6188
|
setIndicatorTitlesVisible(visible) {
|
|
6095
6189
|
this.indicatorTitlesOn = visible;
|
|
@@ -6563,6 +6657,7 @@ var ChartCell = class {
|
|
|
6563
6657
|
if (!this.inner || this.destroyed) return;
|
|
6564
6658
|
if (cs.priceStyle && cs.priceStyle !== this.priceStyle) this.setPriceStyle(cs.priceStyle);
|
|
6565
6659
|
if (cs.watermark !== void 0 && cs.watermark !== this.watermarkOn) this.setWatermarkVisible(cs.watermark);
|
|
6660
|
+
if (cs.replayWatermark !== void 0 && cs.replayWatermark !== this.replayWatermarkOn) this.setReplayWatermarkVisible(cs.replayWatermark);
|
|
6566
6661
|
if (cs.indicatorTitles !== void 0 && cs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(cs.indicatorTitles);
|
|
6567
6662
|
if (cs.indicatorValues !== void 0 && cs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(cs.indicatorValues);
|
|
6568
6663
|
if (cs.rendererConfig != null) this.inner.renderer.applyConfig(cs.rendererConfig);
|
|
@@ -6597,6 +6692,7 @@ var ChartCell = class {
|
|
|
6597
6692
|
...live ? { symbol: live.symbol, provider: live.provider, timeframe: live.timeframe } : {},
|
|
6598
6693
|
priceStyle: this.priceStyle,
|
|
6599
6694
|
watermark: this.watermarkOn,
|
|
6695
|
+
replayWatermark: this.replayWatermarkOn,
|
|
6600
6696
|
indicatorTitles: this.indicatorTitlesOn,
|
|
6601
6697
|
indicatorValues: this.indicatorValuesOn,
|
|
6602
6698
|
rendererConfig: this.inner?.renderer.getConfig() ?? void 0,
|
|
@@ -6639,6 +6735,298 @@ var ChartCell = class {
|
|
|
6639
6735
|
}
|
|
6640
6736
|
};
|
|
6641
6737
|
|
|
6738
|
+
// src/workspace/WorkspaceReplay.ts
|
|
6739
|
+
var MAX_BARS_PER_STEP = 1e5;
|
|
6740
|
+
var WorkspaceReplay = class {
|
|
6741
|
+
constructor(host) {
|
|
6742
|
+
this.host = host;
|
|
6743
|
+
this.bus = new TypedEventBus();
|
|
6744
|
+
/** The running session: the shared replay time (every chart shows the bars closed by it). */
|
|
6745
|
+
this.session = null;
|
|
6746
|
+
this.playing = false;
|
|
6747
|
+
this.intervalMs = 1e3;
|
|
6748
|
+
this.timer = null;
|
|
6749
|
+
/** Cells whose replay a symbol switch ended — restarted at the shared time once their bars land. */
|
|
6750
|
+
this.rejoining = /* @__PURE__ */ new Set();
|
|
6751
|
+
this.cellSubs = /* @__PURE__ */ new Map();
|
|
6752
|
+
/** Inside a group step: chart events are the controller's own doing. */
|
|
6753
|
+
this.stepping = false;
|
|
6754
|
+
/** Inside `finish`: the charts' `replay:end` events are the controller's own doing. */
|
|
6755
|
+
this.stopping = false;
|
|
6756
|
+
/** Driving a chart's own timer: its play/pause events are not the user's. */
|
|
6757
|
+
this.muted = 0;
|
|
6758
|
+
/** A chart finished during a group step — the session ends right after it. */
|
|
6759
|
+
this.finishedDuringStep = false;
|
|
6760
|
+
for (const cell of host.cells()) this.watch(cell);
|
|
6761
|
+
this.offHost = host.onCells((e) => {
|
|
6762
|
+
if (e.kind === "created") this.onCellCreated(e.id);
|
|
6763
|
+
else if (e.kind === "destroyed") this.onCellDestroyed(e.id);
|
|
6764
|
+
});
|
|
6765
|
+
}
|
|
6766
|
+
/**
|
|
6767
|
+
* Enter replay on every cell (or seek, when already replaying) — paused. Resolves once
|
|
6768
|
+
* every chart shows its rewound history (a chart whose history does not reach that far
|
|
6769
|
+
* loads older bars first). A chart with nothing to replay after the shared time stays
|
|
6770
|
+
* as it is.
|
|
6771
|
+
*/
|
|
6772
|
+
async start(opts) {
|
|
6773
|
+
const cells = this.host.cells();
|
|
6774
|
+
const ref = cells.find((c) => c.id === (opts.cell ?? this.host.activeId())) ?? cells[0];
|
|
6775
|
+
if (!ref) return;
|
|
6776
|
+
if (this.playing) this.pause();
|
|
6777
|
+
this.rejoining.clear();
|
|
6778
|
+
await ref.chart.replay.start({ from: opts.from });
|
|
6779
|
+
const cursor = ref.chart.replay.state.cursorTime;
|
|
6780
|
+
if (cursor == null) return;
|
|
6781
|
+
const session = { clock: barClose(cursor, ref.chart.market.timeframe) };
|
|
6782
|
+
this.session = session;
|
|
6783
|
+
await Promise.all(this.host.cells().filter((c) => c.id !== ref.id).map((c) => this.startCell(c, session.clock)));
|
|
6784
|
+
if (this.session !== session) return;
|
|
6785
|
+
this.syncPlayback();
|
|
6786
|
+
const s = this.activeChart()?.replay.state;
|
|
6787
|
+
this.bus.emit("replay:start", { cursorTime: s?.cursorTime ?? cursor, remaining: s?.remaining ?? 0 });
|
|
6788
|
+
}
|
|
6789
|
+
/** Reveal the next bar(s): the clock moves to the next bar close on any chart. Returns false when nothing is left. */
|
|
6790
|
+
step() {
|
|
6791
|
+
const solo = this.solo();
|
|
6792
|
+
return solo ? solo.replay.step() : this.stepGroup();
|
|
6793
|
+
}
|
|
6794
|
+
/** Reveal the next update — in a single-chart layout, the chart's `stepUpdate()` (tick replay); otherwise {@link step}. */
|
|
6795
|
+
stepUpdate() {
|
|
6796
|
+
const solo = this.solo();
|
|
6797
|
+
return solo ? solo.replay.stepUpdate() : this.stepGroup();
|
|
6798
|
+
}
|
|
6799
|
+
/** Advance one update every `intervalMs` (default: the last pace, initially 1000). Calling it again while playing changes the pace. */
|
|
6800
|
+
play(intervalMs) {
|
|
6801
|
+
if (!this.session) return this;
|
|
6802
|
+
if (intervalMs !== void 0) {
|
|
6803
|
+
if (!(Number.isFinite(intervalMs) && intervalMs > 0)) {
|
|
6804
|
+
console.warn(`[vela] workspace.replay.play(${intervalMs}) ignored \u2014 the interval must be a positive number of ms`);
|
|
6805
|
+
return this;
|
|
6806
|
+
}
|
|
6807
|
+
this.intervalMs = intervalMs;
|
|
6808
|
+
}
|
|
6809
|
+
this.playing = true;
|
|
6810
|
+
this.clearTimer();
|
|
6811
|
+
this.syncPlayback();
|
|
6812
|
+
this.bus.emit("replay:play", { intervalMs: this.intervalMs });
|
|
6813
|
+
return this;
|
|
6814
|
+
}
|
|
6815
|
+
pause() {
|
|
6816
|
+
if (!this.playing) return this;
|
|
6817
|
+
this.playing = false;
|
|
6818
|
+
this.syncPlayback();
|
|
6819
|
+
this.bus.emit("replay:pause", void 0);
|
|
6820
|
+
return this;
|
|
6821
|
+
}
|
|
6822
|
+
/** Leave replay on every chart: full history back, live updates resumed. */
|
|
6823
|
+
stop() {
|
|
6824
|
+
this.finish("stopped");
|
|
6825
|
+
return this;
|
|
6826
|
+
}
|
|
6827
|
+
/** `active`/`playing`/`intervalMs` for the whole workspace; the cursor fields read the ACTIVE cell's chart. */
|
|
6828
|
+
get state() {
|
|
6829
|
+
const s = this.session ? this.activeChart()?.replay.state : void 0;
|
|
6830
|
+
return {
|
|
6831
|
+
active: this.session !== null,
|
|
6832
|
+
playing: this.playing,
|
|
6833
|
+
cursorTime: s?.cursorTime ?? null,
|
|
6834
|
+
remaining: s?.remaining ?? 0,
|
|
6835
|
+
nextTime: s?.nextTime ?? null,
|
|
6836
|
+
intervalMs: this.intervalMs
|
|
6837
|
+
};
|
|
6838
|
+
}
|
|
6839
|
+
/** The active cell's replayable history (`chart.replay.bounds`). */
|
|
6840
|
+
get bounds() {
|
|
6841
|
+
return this.activeChart()?.replay.bounds ?? null;
|
|
6842
|
+
}
|
|
6843
|
+
on(event, handler) {
|
|
6844
|
+
return this.bus.on(event, handler);
|
|
6845
|
+
}
|
|
6846
|
+
destroy() {
|
|
6847
|
+
this.session = null;
|
|
6848
|
+
this.playing = false;
|
|
6849
|
+
this.clearTimer();
|
|
6850
|
+
this.offHost();
|
|
6851
|
+
for (const off of this.cellSubs.values()) off();
|
|
6852
|
+
this.cellSubs.clear();
|
|
6853
|
+
this.bus.clear();
|
|
6854
|
+
}
|
|
6855
|
+
// ── internals ──
|
|
6856
|
+
/** The one chart of a single-chart layout — it replays on its own, tick replay included. */
|
|
6857
|
+
solo() {
|
|
6858
|
+
const cells = this.host.cells();
|
|
6859
|
+
return cells.length === 1 ? cells[0].chart : null;
|
|
6860
|
+
}
|
|
6861
|
+
activeChart() {
|
|
6862
|
+
const cells = this.host.cells();
|
|
6863
|
+
return (cells.find((c) => c.id === this.host.activeId()) ?? cells[0])?.chart ?? null;
|
|
6864
|
+
}
|
|
6865
|
+
/** Rewind one chart to the shared time: every bar that closed by `clock`, none after. */
|
|
6866
|
+
async startCell(cell, clock) {
|
|
6867
|
+
const replay = cell.chart.replay;
|
|
6868
|
+
const from = lastOpenClosedBy(clock, cell.chart.market.timeframe);
|
|
6869
|
+
const bounds = replay.bounds;
|
|
6870
|
+
if (!bounds || bounds.last <= from) return;
|
|
6871
|
+
await replay.start({ from });
|
|
6872
|
+
}
|
|
6873
|
+
/** Move the clock to the next bar close on any chart; each chart reveals what closed by then. */
|
|
6874
|
+
stepGroup() {
|
|
6875
|
+
const session = this.session;
|
|
6876
|
+
if (!session) return false;
|
|
6877
|
+
const cells = this.host.cells();
|
|
6878
|
+
let next = Infinity;
|
|
6879
|
+
for (const c of cells) {
|
|
6880
|
+
const t = c.chart.replay.state.nextTime;
|
|
6881
|
+
if (t != null) next = Math.min(next, barClose(t, c.chart.market.timeframe));
|
|
6882
|
+
}
|
|
6883
|
+
if (!Number.isFinite(next)) return false;
|
|
6884
|
+
this.stepping = true;
|
|
6885
|
+
try {
|
|
6886
|
+
for (const c of cells) {
|
|
6887
|
+
const tf = c.chart.market.timeframe;
|
|
6888
|
+
for (let n = 0; n < MAX_BARS_PER_STEP; n += 1) {
|
|
6889
|
+
const t = c.chart.replay.state.nextTime;
|
|
6890
|
+
if (t == null || barClose(t, tf) > next) break;
|
|
6891
|
+
if (!c.chart.replay.step()) break;
|
|
6892
|
+
}
|
|
6893
|
+
}
|
|
6894
|
+
} finally {
|
|
6895
|
+
this.stepping = false;
|
|
6896
|
+
}
|
|
6897
|
+
session.clock = next;
|
|
6898
|
+
if (this.finishedDuringStep) {
|
|
6899
|
+
this.finishedDuringStep = false;
|
|
6900
|
+
this.finish("finished");
|
|
6901
|
+
return true;
|
|
6902
|
+
}
|
|
6903
|
+
const s = this.activeChart()?.replay.state;
|
|
6904
|
+
if (s?.cursorTime != null) this.bus.emit("replay:step", { cursorTime: s.cursorTime, remaining: s.remaining });
|
|
6905
|
+
return true;
|
|
6906
|
+
}
|
|
6907
|
+
/** Put the timers in the state `playing` asks for: a single chart runs its own (it paces
|
|
6908
|
+
* ticks), several run on the controller's clock. */
|
|
6909
|
+
syncPlayback() {
|
|
6910
|
+
const solo = this.solo();
|
|
6911
|
+
const run = this.playing && this.session !== null;
|
|
6912
|
+
this.muted += 1;
|
|
6913
|
+
try {
|
|
6914
|
+
for (const c of this.host.cells()) {
|
|
6915
|
+
if (c.chart === solo && run) {
|
|
6916
|
+
if (!c.chart.replay.state.playing || c.chart.replay.state.intervalMs !== this.intervalMs) c.chart.replay.play(this.intervalMs);
|
|
6917
|
+
} else if (c.chart.replay.state.playing) {
|
|
6918
|
+
c.chart.replay.pause();
|
|
6919
|
+
}
|
|
6920
|
+
}
|
|
6921
|
+
} finally {
|
|
6922
|
+
this.muted -= 1;
|
|
6923
|
+
}
|
|
6924
|
+
if (!run || solo) this.clearTimer();
|
|
6925
|
+
else if (this.timer == null) this.schedule();
|
|
6926
|
+
}
|
|
6927
|
+
schedule() {
|
|
6928
|
+
this.timer = setTimeout(() => {
|
|
6929
|
+
this.timer = null;
|
|
6930
|
+
if (!this.playing || !this.session || this.solo()) return;
|
|
6931
|
+
if (this.stepGroup() || this.rejoining.size > 0) {
|
|
6932
|
+
if (this.playing && this.session && this.timer == null) this.schedule();
|
|
6933
|
+
} else {
|
|
6934
|
+
this.pause();
|
|
6935
|
+
}
|
|
6936
|
+
}, this.intervalMs);
|
|
6937
|
+
}
|
|
6938
|
+
clearTimer() {
|
|
6939
|
+
if (this.timer != null) clearTimeout(this.timer);
|
|
6940
|
+
this.timer = null;
|
|
6941
|
+
}
|
|
6942
|
+
/** End the session on every chart (idempotent). */
|
|
6943
|
+
finish(reason) {
|
|
6944
|
+
const had = this.session !== null;
|
|
6945
|
+
this.session = null;
|
|
6946
|
+
this.playing = false;
|
|
6947
|
+
this.clearTimer();
|
|
6948
|
+
this.rejoining.clear();
|
|
6949
|
+
this.stopping = true;
|
|
6950
|
+
try {
|
|
6951
|
+
for (const c of this.host.cells()) c.chart.replay.stop();
|
|
6952
|
+
} finally {
|
|
6953
|
+
this.stopping = false;
|
|
6954
|
+
}
|
|
6955
|
+
if (had) this.bus.emit("replay:end", { reason });
|
|
6956
|
+
}
|
|
6957
|
+
watch(cell) {
|
|
6958
|
+
const { id, chart } = cell;
|
|
6959
|
+
const isActive = () => (this.host.activeId() ?? this.host.cells()[0]?.id) === id;
|
|
6960
|
+
const offs = [
|
|
6961
|
+
chart.on("replay:end", ({ reason }) => {
|
|
6962
|
+
if (!this.session || this.stopping) return;
|
|
6963
|
+
if (reason === "market") this.rejoining.add(id);
|
|
6964
|
+
else if (this.stepping) this.finishedDuringStep || (this.finishedDuringStep = reason === "finished");
|
|
6965
|
+
else this.finish(reason);
|
|
6966
|
+
}),
|
|
6967
|
+
chart.on("market:changed", () => {
|
|
6968
|
+
if (!this.rejoining.delete(id) || !this.session) return;
|
|
6969
|
+
void this.startCell(cell, this.session.clock).then(() => this.syncPlayback());
|
|
6970
|
+
}),
|
|
6971
|
+
chart.on("replay:step", (e) => {
|
|
6972
|
+
if (this.session && !this.stepping && isActive()) this.bus.emit("replay:step", e);
|
|
6973
|
+
}),
|
|
6974
|
+
chart.on("replay:tick", (e) => {
|
|
6975
|
+
if (this.session && isActive()) this.bus.emit("replay:tick", e);
|
|
6976
|
+
}),
|
|
6977
|
+
// A single chart runs its own timer: a pause it takes on its own (a seek) is the session's.
|
|
6978
|
+
chart.on("replay:pause", () => {
|
|
6979
|
+
if (!this.session || this.muted > 0 || !this.playing || chart !== this.solo()) return;
|
|
6980
|
+
this.playing = false;
|
|
6981
|
+
this.bus.emit("replay:pause", void 0);
|
|
6982
|
+
})
|
|
6983
|
+
];
|
|
6984
|
+
this.cellSubs.set(id, () => {
|
|
6985
|
+
for (const off of offs) off();
|
|
6986
|
+
});
|
|
6987
|
+
}
|
|
6988
|
+
onCellCreated(id) {
|
|
6989
|
+
const cell = this.host.cells().find((c) => c.id === id);
|
|
6990
|
+
if (!cell) return;
|
|
6991
|
+
this.watch(cell);
|
|
6992
|
+
const session = this.session;
|
|
6993
|
+
if (!session) return;
|
|
6994
|
+
this.rejoining.add(id);
|
|
6995
|
+
void cell.chart.ready().then(async () => {
|
|
6996
|
+
if (this.session !== session || !this.rejoining.delete(id)) return;
|
|
6997
|
+
await this.startCell(cell, session.clock);
|
|
6998
|
+
this.syncPlayback();
|
|
6999
|
+
});
|
|
7000
|
+
this.syncPlayback();
|
|
7001
|
+
}
|
|
7002
|
+
onCellDestroyed(id) {
|
|
7003
|
+
this.cellSubs.get(id)?.();
|
|
7004
|
+
this.cellSubs.delete(id);
|
|
7005
|
+
this.rejoining.delete(id);
|
|
7006
|
+
this.syncPlayback();
|
|
7007
|
+
}
|
|
7008
|
+
};
|
|
7009
|
+
function barClose(open, timeframe) {
|
|
7010
|
+
const months = monthSpan(timeframe);
|
|
7011
|
+
if (months > 0) {
|
|
7012
|
+
const d = new Date(open);
|
|
7013
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + months, d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes());
|
|
7014
|
+
}
|
|
7015
|
+
return open + timeframeToMs(timeframe ?? "60");
|
|
7016
|
+
}
|
|
7017
|
+
function lastOpenClosedBy(time, timeframe) {
|
|
7018
|
+
const months = monthSpan(timeframe);
|
|
7019
|
+
if (months > 0) {
|
|
7020
|
+
const d = new Date(time);
|
|
7021
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth() - months, d.getUTCDate(), d.getUTCHours(), d.getUTCMinutes());
|
|
7022
|
+
}
|
|
7023
|
+
return time - timeframeToMs(timeframe ?? "60");
|
|
7024
|
+
}
|
|
7025
|
+
function monthSpan(timeframe) {
|
|
7026
|
+
const m = /^(\d*)M$/.exec((timeframe ?? "").trim());
|
|
7027
|
+
return m ? Number(m[1] || 1) : 0;
|
|
7028
|
+
}
|
|
7029
|
+
|
|
6642
7030
|
// src/workspace/layouts.ts
|
|
6643
7031
|
var registry = /* @__PURE__ */ new Map();
|
|
6644
7032
|
function registerLayout(def) {
|
|
@@ -7095,16 +7483,16 @@ var MobileBar = class {
|
|
|
7095
7483
|
host.appendChild(this.el);
|
|
7096
7484
|
this.renderActions();
|
|
7097
7485
|
}
|
|
7098
|
-
/** Re-project the
|
|
7099
|
-
* indicators slot (call after
|
|
7100
|
-
*
|
|
7486
|
+
/** Re-project the contributed actions placed on the bar (left-aligned by default —
|
|
7487
|
+
* see `mobilePlacement`) as icon-only stops in the indicators slot (call after
|
|
7488
|
+
* registrations change). The rest are three-dots drawer rows. */
|
|
7101
7489
|
renderActions() {
|
|
7102
7490
|
const ctx = this.opts.getContext?.();
|
|
7103
7491
|
if (!ctx) return;
|
|
7104
7492
|
const doc = this.el.ownerDocument;
|
|
7105
7493
|
this.actionsHost.replaceChildren();
|
|
7106
7494
|
const builtin = new Set(TOPBAR_BUILTIN_IDS);
|
|
7107
|
-
for (const action of widgetActions("topbar", ctx).filter((a) => a
|
|
7495
|
+
for (const action of widgetActions("topbar", ctx).filter((a) => mobilePlacement(a) === "bar" && !builtin.has(a.id))) {
|
|
7108
7496
|
const b = doc.createElement("button");
|
|
7109
7497
|
b.className = "vela-mb-item";
|
|
7110
7498
|
b.setAttribute("aria-label", action.label);
|
|
@@ -7660,6 +8048,7 @@ var MoreDrawer = class {
|
|
|
7660
8048
|
const value = shape ? `${shape.cols} \xD7 ${shape.rows}` : this.opts.layout.presets().find((p) => p.checked)?.label;
|
|
7661
8049
|
list.appendChild(this.row(doc, "Layout", { icon: "layout", value, chevron: true, onClick: () => this.show("layout") }));
|
|
7662
8050
|
}
|
|
8051
|
+
for (const act of this.opts.primaryActions?.() ?? []) list.appendChild(this.actionRow(doc, act));
|
|
7663
8052
|
for (const panel of this.opts.panels()) {
|
|
7664
8053
|
list.appendChild(
|
|
7665
8054
|
this.row(doc, panel.title, {
|
|
@@ -7675,19 +8064,19 @@ var MoreDrawer = class {
|
|
|
7675
8064
|
const alertCount = this.opts.alerts().length;
|
|
7676
8065
|
list.appendChild(this.row(doc, "Alerts", { icon: "bell", value: alertCount > 0 ? String(alertCount) : void 0, chevron: true, onClick: () => this.show("alerts") }));
|
|
7677
8066
|
}
|
|
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
|
-
}
|
|
8067
|
+
for (const act of this.opts.actions()) list.appendChild(this.actionRow(doc, act));
|
|
7689
8068
|
this.drawer.body.appendChild(list);
|
|
7690
8069
|
}
|
|
8070
|
+
/** A contributed action's row: runs it and closes the drawer. */
|
|
8071
|
+
actionRow(doc, act) {
|
|
8072
|
+
return this.row(doc, act.label, {
|
|
8073
|
+
icon: act.icon,
|
|
8074
|
+
onClick: () => {
|
|
8075
|
+
act.run();
|
|
8076
|
+
this.drawer.hide();
|
|
8077
|
+
}
|
|
8078
|
+
});
|
|
8079
|
+
}
|
|
7691
8080
|
renderStyle(doc) {
|
|
7692
8081
|
const list = doc.createElement("div");
|
|
7693
8082
|
list.className = "vela-md-list";
|
|
@@ -8151,6 +8540,7 @@ function buildContext(host) {
|
|
|
8151
8540
|
setPriceStyle: (style) => host.active()?.setPriceStyle(style),
|
|
8152
8541
|
openSymbolSearch: (query) => host.openSymbolSearch(query),
|
|
8153
8542
|
togglePanel: (id, open) => host.togglePanel(id, open),
|
|
8543
|
+
dockStrip: (el) => host.dockStrip(el),
|
|
8154
8544
|
host: host.root,
|
|
8155
8545
|
toast: (message, kind) => host.toast(message, kind),
|
|
8156
8546
|
addIndicator: (entry) => host.active()?.addExternalIndicator(entry),
|
|
@@ -8162,7 +8552,8 @@ function buildContext(host) {
|
|
|
8162
8552
|
get activeCellId() {
|
|
8163
8553
|
return host.active()?.id ?? "";
|
|
8164
8554
|
},
|
|
8165
|
-
setActiveCell: (id) => host.setActiveCell(id)
|
|
8555
|
+
setActiveCell: (id) => host.setActiveCell(id),
|
|
8556
|
+
replay: host.replay
|
|
8166
8557
|
};
|
|
8167
8558
|
}
|
|
8168
8559
|
|
|
@@ -8225,6 +8616,8 @@ var CSS21 = `
|
|
|
8225
8616
|
.vela-workspace { position: relative; width: 100%; height: 100%; display: flex; flex-direction: column; background: var(--vela-bg); }
|
|
8226
8617
|
.vela-ws-main { position: relative; display: flex; flex-direction: row; flex: 1 1 auto; min-height: 0; }
|
|
8227
8618
|
.vela-ws-toolbar { position: relative; flex: none; }
|
|
8619
|
+
.vela-ws-strips { position: relative; flex: none; display: flex; flex-direction: column; min-width: 0; }
|
|
8620
|
+
.vela-ws-strips:empty { display: none; }
|
|
8228
8621
|
.vela-ws-grid { position: relative; flex: 1 1 auto; min-width: 0; display: grid; gap: ${GAP_PX}px; background: var(--vela-border-soft); }
|
|
8229
8622
|
.vela-cell { background: var(--vela-bg); position: relative; }
|
|
8230
8623
|
/* Active-cell highlight: an overlay ring ABOVE the chart's own canvas stack (a plain
|
|
@@ -8396,6 +8789,20 @@ var VelaWorkspace = class {
|
|
|
8396
8789
|
const hostEl = typeof container === "string" ? document.querySelector(container) : container;
|
|
8397
8790
|
if (!hostEl) throw new Error(`VelaWorkspace: container not found: ${String(container)}`);
|
|
8398
8791
|
this.opts = opts;
|
|
8792
|
+
this.replay = new WorkspaceReplay({
|
|
8793
|
+
cells: () => this.cells(),
|
|
8794
|
+
activeId: () => this.activeId,
|
|
8795
|
+
onCells: (handler) => {
|
|
8796
|
+
const offs = [
|
|
8797
|
+
this.events.on("cell:created", ({ id }) => handler({ kind: "created", id })),
|
|
8798
|
+
this.events.on("cell:destroyed", ({ id }) => handler({ kind: "destroyed", id })),
|
|
8799
|
+
this.events.on("cell:active", ({ id }) => handler({ kind: "active", id }))
|
|
8800
|
+
];
|
|
8801
|
+
return () => {
|
|
8802
|
+
for (const off of offs) off();
|
|
8803
|
+
};
|
|
8804
|
+
}
|
|
8805
|
+
});
|
|
8399
8806
|
this.persistKey = opts.persist === void 0 || opts.persist === false ? null : opts.persist === true ? "vela-workspace" : opts.persist;
|
|
8400
8807
|
this.storage = opts.storage ?? localStorageAdapter();
|
|
8401
8808
|
let boot = null;
|
|
@@ -8533,6 +8940,9 @@ var VelaWorkspace = class {
|
|
|
8533
8940
|
this.dock.refresh();
|
|
8534
8941
|
if (boot?.panels) this.dock.applyState(boot.panels);
|
|
8535
8942
|
this.root.appendChild(main);
|
|
8943
|
+
this.stripsEl = doc.createElement("div");
|
|
8944
|
+
this.stripsEl.className = "vela-ws-strips";
|
|
8945
|
+
this.root.appendChild(this.stripsEl);
|
|
8536
8946
|
this.toastHost = new Toast(this.gridEl);
|
|
8537
8947
|
const attribution = rendererDefaults().attribution;
|
|
8538
8948
|
if (attribution !== false) {
|
|
@@ -8767,9 +9177,16 @@ var VelaWorkspace = class {
|
|
|
8767
9177
|
setActiveCell: (id) => this.setActiveCell(id),
|
|
8768
9178
|
openSymbolSearch: (query) => this.symbolPicker.open(query ?? ""),
|
|
8769
9179
|
togglePanel: (id, open) => this.dock.toggle(id, open),
|
|
9180
|
+
dockStrip: (el) => {
|
|
9181
|
+
this.stripsEl.appendChild(el);
|
|
9182
|
+
return () => {
|
|
9183
|
+
if (el.parentElement === this.stripsEl) el.remove();
|
|
9184
|
+
};
|
|
9185
|
+
},
|
|
8770
9186
|
root: this.root,
|
|
8771
9187
|
toast: (message, kind) => this.toastHost.show(message, kind),
|
|
8772
|
-
stateDirty: () => this.markStateDirty()
|
|
9188
|
+
stateDirty: () => this.markStateDirty(),
|
|
9189
|
+
replay: this.replay
|
|
8773
9190
|
});
|
|
8774
9191
|
}
|
|
8775
9192
|
/** Re-project contributed topbar actions + side panels, and mount late-registered attachments. */
|
|
@@ -9077,6 +9494,7 @@ var VelaWorkspace = class {
|
|
|
9077
9494
|
if (this.destroyed) return;
|
|
9078
9495
|
this.flushPendingState();
|
|
9079
9496
|
this.destroyed = true;
|
|
9497
|
+
this.replay.destroy();
|
|
9080
9498
|
if (this.persistKey !== null && typeof window !== "undefined") window.removeEventListener("beforeunload", this.onUnload);
|
|
9081
9499
|
this.resizeObserver?.disconnect();
|
|
9082
9500
|
this.splitters.destroy();
|
|
@@ -9843,14 +10261,13 @@ var VelaWorkspace = class {
|
|
|
9843
10261
|
panels: () => has("panels") ? [...this.dock.list()] : [],
|
|
9844
10262
|
onTogglePanel: (id) => this.dock.toggle(id),
|
|
9845
10263
|
...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
|
-
},
|
|
10264
|
+
// Actions placed on the bottom bar (left-aligned by default) have their stop
|
|
10265
|
+
// there — only the menu-placed ones land in the drawer, or they would appear
|
|
10266
|
+
// twice: left (primary) ones with the primary rows, right ones at the end.
|
|
10267
|
+
// Built-in-id actions are slot OVERRIDES: they reach the drawer through the
|
|
10268
|
+
// slot's own routed button (screenshot) or stop (indicators), never as a row.
|
|
10269
|
+
primaryActions: () => this.drawerActions("left"),
|
|
10270
|
+
actions: () => this.drawerActions("right"),
|
|
9854
10271
|
// The desktop layout dropdown's whole surface — the grid canvas, the
|
|
9855
10272
|
// non-canvas presets and the sync switches — relocated into the kebab
|
|
9856
10273
|
// drawer (the topbar is hidden on mobile). Same reads as the topbar block;
|
|
@@ -9875,6 +10292,11 @@ var VelaWorkspace = class {
|
|
|
9875
10292
|
}));
|
|
9876
10293
|
this.moreDrawer.open();
|
|
9877
10294
|
}
|
|
10295
|
+
/** The contributed topbar actions the mobile menu lists, from one cluster. */
|
|
10296
|
+
drawerActions(cluster) {
|
|
10297
|
+
const builtin = new Set(TOPBAR_BUILTIN_IDS);
|
|
10298
|
+
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()) }));
|
|
10299
|
+
}
|
|
9878
10300
|
openTimezoneDrawer() {
|
|
9879
10301
|
this.timezoneDrawer ?? (this.timezoneDrawer = new TimezoneDrawer({
|
|
9880
10302
|
host: this.root,
|
|
@@ -10038,4 +10460,4 @@ var VelaWorkspace = class {
|
|
|
10038
10460
|
}
|
|
10039
10461
|
};
|
|
10040
10462
|
|
|
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 };
|
|
10463
|
+
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 };
|