@luxalgo/vela 0.6.14 → 0.6.15
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 +1 -1
- package/dist/{DataProvider-DhzstpQb.d.ts → DataProvider-XLdJB86x.d.ts} +1 -1
- package/dist/{DataProvider-DlMtrwqM.d.cts → DataProvider-xH2VBcFT.d.cts} +1 -1
- package/dist/{chunk-F4M24ANM.js → chunk-32A7KHFM.js} +73 -3
- package/dist/{chunk-KG4YT3TI.js → chunk-6DPAYEGO.js} +8 -1
- package/dist/{chunk-64TF7JAJ.js → chunk-7C7NWEXH.js} +345 -148
- package/dist/{chunk-N2Q5EEFH.js → chunk-J5QIR2Z3.js} +163 -130
- package/dist/{chunk-MHY7MVXH.js → chunk-MWDNPGU3.js} +7 -1
- package/dist/{chunk-G77Y7LK2.js → chunk-MWS4CUJL.js} +2 -2
- package/dist/{contributions-37nni40G.d.ts → contributions-h5BzEiV2.d.ts} +13 -2
- package/dist/{contributions-lPojhTxI.d.cts → contributions-x7iamlnd.d.cts} +13 -2
- package/dist/index.cjs +174 -127
- package/dist/index.d.cts +20 -12
- package/dist/index.d.ts +20 -12
- package/dist/index.js +4 -4
- package/dist/{options-CYS5Wmlx.d.ts → options-bdSAtp7y.d.cts} +5 -0
- package/dist/{options-CYS5Wmlx.d.cts → options-bdSAtp7y.d.ts} +5 -0
- package/dist/{plugin-Cwikpz1m.d.cts → plugin-2sJcf5mk.d.cts} +11 -8
- package/dist/{plugin-DHyaoMjW.d.ts → plugin-BLaNPh55.d.ts} +11 -8
- package/dist/plugin.cjs +8 -0
- package/dist/plugin.d.cts +5 -5
- package/dist/plugin.d.ts +5 -5
- 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/{side-panel-CT9ZwIGz.d.cts → side-panel-HF0IAzwf.d.cts} +17 -0
- package/dist/{side-panel-CT9ZwIGz.d.ts → side-panel-HF0IAzwf.d.ts} +17 -0
- package/dist/{statusline-model-D-q_OOx9.d.ts → statusline-model--tH0VWCy.d.cts} +6 -4
- package/dist/{statusline-model-CiJm-riV.d.cts → statusline-model-CLpaO3Dl.d.ts} +6 -4
- package/dist/ui.cjs +12 -0
- package/dist/ui.d.cts +1 -1
- package/dist/ui.d.ts +1 -1
- package/dist/ui.js +3 -3
- package/dist/vela.global.js +174 -127
- package/dist/vela.global.min.js +52 -46
- package/dist/widget.cjs +585 -272
- package/dist/widget.d.cts +15 -11
- package/dist/widget.d.ts +15 -11
- package/dist/widget.js +7 -7
- package/dist/workspace.cjs +585 -272
- package/dist/workspace.d.cts +29 -10
- package/dist/workspace.d.ts +29 -10
- package/dist/workspace.js +6 -6
- package/package.json +1 -1
package/dist/widget.cjs
CHANGED
|
@@ -46,6 +46,7 @@ var SERIES_LINE = "#3b82f6";
|
|
|
46
46
|
var CROSSHAIR = "#9aa0ad";
|
|
47
47
|
var SLATE_DEEP = "#1e293b";
|
|
48
48
|
var SLATE = "#475569";
|
|
49
|
+
var CHIP_PLATE = "#595959";
|
|
49
50
|
var TRADE_LONG = ACCENT;
|
|
50
51
|
var TRADE_SHORT = BEARISH;
|
|
51
52
|
var TRADE_EXIT = "#d500f9";
|
|
@@ -1086,6 +1087,12 @@ ${STATIC_DECLS}
|
|
|
1086
1087
|
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
1087
1088
|
/* Text ENTRY is the one exception: selection is part of editing. */
|
|
1088
1089
|
.vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
|
|
1090
|
+
/* iOS Safari zooms the page when a focused field is under 16px, and often
|
|
1091
|
+
keeps that zoom after the field blurs or a dialog closes. The shell's
|
|
1092
|
+
mobile size class is the honest gate \u2014 not a media query \u2014 so an embedded
|
|
1093
|
+
chart on a wide desktop still gets the rule when it is in the phone layout.
|
|
1094
|
+
Scoped to the kit root so a host page's own [data-layout] is never touched. */
|
|
1095
|
+
.vela-ui[data-layout='mobile'] :is(input, textarea, select) { font-size: 16px; }
|
|
1089
1096
|
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
1090
1097
|
.vela-icon svg { display: block; }
|
|
1091
1098
|
`;
|
|
@@ -1181,6 +1188,10 @@ registerIcon(
|
|
|
1181
1188
|
"market-post",
|
|
1182
1189
|
svg24('<path d="M12 10V2"/><path d="m4.93 10.93 1.41 1.41"/><path d="M2 18h2"/><path d="M20 18h2"/><path d="m19.07 10.93-1.41 1.41"/><path d="M22 22H2"/><path d="m16 14-4 4-4-4"/><path d="M16 6a4 4 0 0 0-8 0"/>')
|
|
1183
1190
|
);
|
|
1191
|
+
registerIcon(
|
|
1192
|
+
"market-extended",
|
|
1193
|
+
svg24('<path d="M22 22H2"/><path d="M12 3a5 5 0 0 0 7.5 7.5A7.5 7.5 0 1 1 12 3Z"/>')
|
|
1194
|
+
);
|
|
1184
1195
|
registerIcon("market-closed", svg24('<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>'));
|
|
1185
1196
|
registerIcon(
|
|
1186
1197
|
"market-holiday",
|
|
@@ -1194,6 +1205,8 @@ registerIcon("chevrons-right", S('<path d="m4 3.5 4.5 4.5L4 12.5"/><path d="m8.5
|
|
|
1194
1205
|
registerIcon("chevrons-left", S('<path d="M12 3.5 7.5 8l4.5 4.5"/><path d="M7.5 3.5 3 8l4.5 4.5"/>'));
|
|
1195
1206
|
registerIcon("check", S('<path d="m2.8 8.4 3.4 3.4 7-7.6"/>'));
|
|
1196
1207
|
registerIcon("close", S('<path d="m3.8 3.8 8.4 8.4M12.2 3.8l-8.4 8.4"/>'));
|
|
1208
|
+
registerIcon("pin", S('<path d="M5.6 2.5h4.8M6.4 2.5v3.6L4.4 8.4v.9h7.2v-.9L9.6 6.1V2.5M8 9.3v4.2"/>'));
|
|
1209
|
+
registerIcon("pin-filled", S('<path d="M5.6 2.5h4.8M6.4 2.5v3.6L4.4 8.4v.9h7.2v-.9L9.6 6.1V2.5M8 9.3v4.2"/><path d="M6.4 2.5h3.2v3.6l2 2.3v.9H4.4v-.9l2-2.3z" fill="currentColor" stroke="none"/>'));
|
|
1197
1210
|
registerIcon("eye", S('<path d="M1.5 8s2.5-5.4 6.5-5.4S14.5 8 14.5 8 12 13.4 8 13.4 1.5 8 1.5 8z"/><circle cx="8" cy="8" r="1.8"/>'));
|
|
1198
1211
|
registerIcon("eye-off", S('<path d="M1.5 8s2.5-5.4 6.5-5.4S14.5 8 14.5 8 12 13.4 8 13.4 1.5 8 1.5 8z" opacity="0.45"/><path d="m3 13 10-10"/>'));
|
|
1199
1212
|
registerIcon("lock", S('<rect x="3.2" y="7" width="9.6" height="6.6" rx="1.2"/><path d="M5.6 7V5.2a2.4 2.4 0 0 1 4.8 0V7"/>'));
|
|
@@ -5022,6 +5035,7 @@ var BASELINE_FILL_ALPHA_FAR = 0.05;
|
|
|
5022
5035
|
var BASELINE_LEVEL_DEFAULT = 50;
|
|
5023
5036
|
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
5024
5037
|
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
5038
|
+
var EXTENDED_SHADE = POSTMARKET_SHADE;
|
|
5025
5039
|
function defaultChartStyle() {
|
|
5026
5040
|
return {
|
|
5027
5041
|
chartTypes: {},
|
|
@@ -5030,7 +5044,7 @@ function defaultChartStyle() {
|
|
|
5030
5044
|
gridHorz: { visible: true, color: null },
|
|
5031
5045
|
borderColor: null,
|
|
5032
5046
|
separatorColor: null,
|
|
5033
|
-
crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground:
|
|
5047
|
+
crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: CHIP_PLATE },
|
|
5034
5048
|
candle: {
|
|
5035
5049
|
bodyVisible: true,
|
|
5036
5050
|
borderVisible: false,
|
|
@@ -5053,7 +5067,7 @@ function defaultChartStyle() {
|
|
|
5053
5067
|
width: 2,
|
|
5054
5068
|
baselineLevel: BASELINE_LEVEL_DEFAULT
|
|
5055
5069
|
},
|
|
5056
|
-
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
|
|
5070
|
+
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE, extendedColor: EXTENDED_SHADE }
|
|
5057
5071
|
};
|
|
5058
5072
|
}
|
|
5059
5073
|
var LINE_STYLES = ["solid", "dashed", "dotted"];
|
|
@@ -5282,7 +5296,8 @@ function mergeConfig(base, patch) {
|
|
|
5282
5296
|
},
|
|
5283
5297
|
sessions: {
|
|
5284
5298
|
premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
|
|
5285
|
-
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
|
|
5299
|
+
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor,
|
|
5300
|
+
extendedColor: isColor(sessions.extendedColor) ? sessions.extendedColor : base.sessions.extendedColor
|
|
5286
5301
|
},
|
|
5287
5302
|
stacking: {
|
|
5288
5303
|
candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
|
|
@@ -12278,6 +12293,22 @@ var CSS5 = `
|
|
|
12278
12293
|
}
|
|
12279
12294
|
.vela-panel-close .vela-icon { width: 16px; height: 16px; }
|
|
12280
12295
|
.vela-panel-close:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12296
|
+
/* The pin: same footprint as the close button it sits beside; filled + bright once pinned. */
|
|
12297
|
+
.vela-panel-pin {
|
|
12298
|
+
all: unset;
|
|
12299
|
+
cursor: pointer;
|
|
12300
|
+
width: 26px;
|
|
12301
|
+
height: 26px;
|
|
12302
|
+
flex: none;
|
|
12303
|
+
display: inline-flex;
|
|
12304
|
+
align-items: center;
|
|
12305
|
+
justify-content: center;
|
|
12306
|
+
border-radius: 4px;
|
|
12307
|
+
color: var(--vela-fg-muted);
|
|
12308
|
+
}
|
|
12309
|
+
.vela-panel-pin .vela-icon { width: 15px; height: 15px; }
|
|
12310
|
+
.vela-panel-pin:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12311
|
+
.vela-panel-pin[data-on='1'] { color: var(--vela-fg-bright); }
|
|
12281
12312
|
.vela-panel-body { flex: 1; overflow: auto; padding: 8px; }
|
|
12282
12313
|
.vela-panel-body::-webkit-scrollbar { width: 8px; }
|
|
12283
12314
|
.vela-panel-body::-webkit-scrollbar-thumb { background: var(--vela-scroll); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
|
|
@@ -12302,8 +12333,23 @@ var CSS5 = `
|
|
|
12302
12333
|
width: 1px;
|
|
12303
12334
|
background: transparent;
|
|
12304
12335
|
}
|
|
12336
|
+
/* The same hover ink as the workspace grid splitters and the canvas pane separators, so
|
|
12337
|
+
every draggable seam in the shell reads as one family. */
|
|
12305
12338
|
.vela-panel-resizer:hover::after,
|
|
12306
|
-
.vela-panel-resizer[data-dragging]::after { background: var(--vela-
|
|
12339
|
+
.vela-panel-resizer[data-dragging]::after { background: var(--vela-separator-hover-line); }
|
|
12340
|
+
/* An OVERLAY panel floats over the chart's right edge instead of taking a column: the
|
|
12341
|
+
chart keeps its width and layout (the dock host is position:relative \u2014 the anchoring the
|
|
12342
|
+
mobile rule below relies on too). Same z-order as that mobile overlay, above the chart's
|
|
12343
|
+
own DOM overlays; the shadow separates it from the plot it covers. */
|
|
12344
|
+
.vela-panel[data-overlay] {
|
|
12345
|
+
position: absolute;
|
|
12346
|
+
top: 0;
|
|
12347
|
+
right: 0;
|
|
12348
|
+
bottom: 0;
|
|
12349
|
+
max-width: 100%;
|
|
12350
|
+
z-index: 25;
|
|
12351
|
+
box-shadow: var(--vela-shadow);
|
|
12352
|
+
}
|
|
12307
12353
|
/* Mobile: a 280px column would crush a phone-width chart \u2014 the panel overlays the
|
|
12308
12354
|
chart area instead (its flex parent is position:relative), full-bleed, closed by
|
|
12309
12355
|
the same header \u2715. Width dragging is a pointer affordance; off on mobile. */
|
|
@@ -12334,6 +12380,10 @@ var SidePanel = class {
|
|
|
12334
12380
|
/** Notified when the USER settles a new width (drag release, or double-click reset) — never on
|
|
12335
12381
|
* a programmatic {@link setWidth}, so restoring a persisted width raises no change. */
|
|
12336
12382
|
this.onWidthChange = null;
|
|
12383
|
+
/** Notified when the USER pins or unpins a floating panel (`overlay` is the NEW placement) —
|
|
12384
|
+
* never on a programmatic {@link setOverlay}, for the same reason as widths. */
|
|
12385
|
+
this.onPlacementChange = null;
|
|
12386
|
+
this.pin = null;
|
|
12337
12387
|
const doc = host.ownerDocument;
|
|
12338
12388
|
injectStyles(STYLE_ID4, CSS5, doc);
|
|
12339
12389
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
@@ -12343,6 +12393,9 @@ var SidePanel = class {
|
|
|
12343
12393
|
this.el = doc.createElement("div");
|
|
12344
12394
|
this.el.className = `vela-panel ${modifier}`;
|
|
12345
12395
|
this.el.hidden = true;
|
|
12396
|
+
this.floatable = opts.overlay === true;
|
|
12397
|
+
this.overlayOn = this.floatable;
|
|
12398
|
+
if (this.overlayOn) this.el.dataset.overlay = "1";
|
|
12346
12399
|
this.el.style.setProperty("--vela-panel-w", `${this.widthPx}px`);
|
|
12347
12400
|
const header = doc.createElement("div");
|
|
12348
12401
|
header.className = "vela-panel-header";
|
|
@@ -12356,7 +12409,16 @@ var SidePanel = class {
|
|
|
12356
12409
|
close.appendChild(iconEl("close", doc));
|
|
12357
12410
|
close.title = "Close";
|
|
12358
12411
|
close.addEventListener("click", () => this.toggle(false));
|
|
12359
|
-
|
|
12412
|
+
if (this.floatable) {
|
|
12413
|
+
this.pin = doc.createElement("button");
|
|
12414
|
+
this.pin.className = "vela-panel-pin";
|
|
12415
|
+
this.pin.addEventListener("click", () => {
|
|
12416
|
+
this.setOverlay(!this.overlayOn);
|
|
12417
|
+
this.onPlacementChange?.(this.overlayOn);
|
|
12418
|
+
});
|
|
12419
|
+
this.refreshPin();
|
|
12420
|
+
}
|
|
12421
|
+
header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], close);
|
|
12360
12422
|
this.body = doc.createElement("div");
|
|
12361
12423
|
this.body.className = "vela-panel-body";
|
|
12362
12424
|
this.el.append(header, this.body);
|
|
@@ -12398,6 +12460,29 @@ var SidePanel = class {
|
|
|
12398
12460
|
this.widthPx = next;
|
|
12399
12461
|
this.el.style.setProperty("--vela-panel-w", `${next}px`);
|
|
12400
12462
|
}
|
|
12463
|
+
/** Whether the panel floats over the chart right now (false for every docked panel). */
|
|
12464
|
+
get overlay() {
|
|
12465
|
+
return this.overlayOn;
|
|
12466
|
+
}
|
|
12467
|
+
/** Float or dock the panel. Only a panel declared `overlay` can float — on any other this is
|
|
12468
|
+
* a no-op. Silent — {@link onPlacementChange} reports the user's pin clicks only. */
|
|
12469
|
+
setOverlay(overlay) {
|
|
12470
|
+
if (!this.floatable || overlay === this.overlayOn) return;
|
|
12471
|
+
this.overlayOn = overlay;
|
|
12472
|
+
if (overlay) this.el.dataset.overlay = "1";
|
|
12473
|
+
else delete this.el.dataset.overlay;
|
|
12474
|
+
this.refreshPin();
|
|
12475
|
+
}
|
|
12476
|
+
refreshPin() {
|
|
12477
|
+
if (!this.pin) return;
|
|
12478
|
+
const pinned = !this.overlayOn;
|
|
12479
|
+
const doc = this.pin.ownerDocument;
|
|
12480
|
+
this.pin.replaceChildren(iconEl(pinned ? "pin-filled" : "pin", doc));
|
|
12481
|
+
this.pin.dataset.on = pinned ? "1" : "0";
|
|
12482
|
+
this.pin.title = pinned ? "Unpin \u2014 float over the chart" : "Pin beside the chart";
|
|
12483
|
+
this.pin.setAttribute("aria-label", this.pin.title);
|
|
12484
|
+
this.pin.setAttribute("aria-pressed", pinned ? "true" : "false");
|
|
12485
|
+
}
|
|
12401
12486
|
destroy() {
|
|
12402
12487
|
this.el.remove();
|
|
12403
12488
|
}
|
|
@@ -13798,6 +13883,8 @@ var PanelDock = class {
|
|
|
13798
13883
|
this.entries = [];
|
|
13799
13884
|
/** Widths the USER settled, by panel id — the only ones worth persisting. */
|
|
13800
13885
|
this.widths = /* @__PURE__ */ new Map();
|
|
13886
|
+
/** Floating panels the USER pinned as columns, by id — remembered for late registrations too. */
|
|
13887
|
+
this.pinned = /* @__PURE__ */ new Set();
|
|
13801
13888
|
/** A restored `open` naming a panel that has not registered yet: honored when it docks. */
|
|
13802
13889
|
this.pendingOpen = null;
|
|
13803
13890
|
this.chart = null;
|
|
@@ -13820,7 +13907,8 @@ var PanelDock = class {
|
|
|
13820
13907
|
width: desc.width,
|
|
13821
13908
|
resizable: desc.resizable,
|
|
13822
13909
|
minWidth: desc.minWidth,
|
|
13823
|
-
maxWidth: desc.maxWidth
|
|
13910
|
+
maxWidth: desc.maxWidth,
|
|
13911
|
+
overlay: desc.overlay
|
|
13824
13912
|
});
|
|
13825
13913
|
const entry = {
|
|
13826
13914
|
id: desc.id,
|
|
@@ -13868,7 +13956,8 @@ var PanelDock = class {
|
|
|
13868
13956
|
const open2 = this.openId;
|
|
13869
13957
|
if (open2) out.open = open2;
|
|
13870
13958
|
if (this.widths.size > 0) out.widths = Object.fromEntries(this.widths);
|
|
13871
|
-
|
|
13959
|
+
if (this.pinned.size > 0) out.pinned = [...this.pinned];
|
|
13960
|
+
return out.open || out.widths || out.pinned ? out : null;
|
|
13872
13961
|
}
|
|
13873
13962
|
/**
|
|
13874
13963
|
* Restore a persisted dock state. Widths apply to any panel present, and are remembered for
|
|
@@ -13876,7 +13965,8 @@ var PanelDock = class {
|
|
|
13876
13965
|
* a document that predates the dock has no `panels` field at all, so the shell never calls
|
|
13877
13966
|
* this and the default (everything closed) stands. An `open` naming a panel that has not
|
|
13878
13967
|
* registered yet is held until it docks (a plugin loaded after the restore), unless the user
|
|
13879
|
-
* opens something in the meantime.
|
|
13968
|
+
* opens something in the meantime. `pinned` is the whole list of floating panels the user
|
|
13969
|
+
* docked — its absence means none is, so every floatable panel floats again.
|
|
13880
13970
|
*/
|
|
13881
13971
|
applyState(state) {
|
|
13882
13972
|
if (!state) return;
|
|
@@ -13886,6 +13976,8 @@ var PanelDock = class {
|
|
|
13886
13976
|
this.entries.find((e) => e.id === id)?.panel.setWidth(px);
|
|
13887
13977
|
}
|
|
13888
13978
|
}
|
|
13979
|
+
this.pinned = new Set(state.pinned ?? []);
|
|
13980
|
+
for (const entry of this.entries) entry.panel.setOverlay(!this.pinned.has(entry.id));
|
|
13889
13981
|
for (const entry of this.entries) entry.panel.toggle(entry.id === state.open);
|
|
13890
13982
|
this.pendingOpen = state.open && !this.entries.some((e) => e.id === state.open) ? state.open : null;
|
|
13891
13983
|
}
|
|
@@ -13899,6 +13991,7 @@ var PanelDock = class {
|
|
|
13899
13991
|
this.entries.sort((a, b) => a.order - b.order);
|
|
13900
13992
|
const stored = this.widths.get(entry.id);
|
|
13901
13993
|
if (stored !== void 0) entry.panel.setWidth(stored);
|
|
13994
|
+
if (this.pinned.has(entry.id)) entry.panel.setOverlay(false);
|
|
13902
13995
|
entry.panel.onOpenChange = (open2) => {
|
|
13903
13996
|
if (open2) {
|
|
13904
13997
|
for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
|
|
@@ -13912,6 +14005,11 @@ var PanelDock = class {
|
|
|
13912
14005
|
this.widths.set(entry.id, px);
|
|
13913
14006
|
this.deps.changed?.();
|
|
13914
14007
|
};
|
|
14008
|
+
entry.panel.onPlacementChange = (overlay) => {
|
|
14009
|
+
if (overlay) this.pinned.delete(entry.id);
|
|
14010
|
+
else this.pinned.add(entry.id);
|
|
14011
|
+
this.deps.changed?.();
|
|
14012
|
+
};
|
|
13915
14013
|
if (this.pendingOpen === entry.id) entry.panel.toggle(true);
|
|
13916
14014
|
if (!entry.contributed) this.publish();
|
|
13917
14015
|
}
|
|
@@ -14000,6 +14098,17 @@ function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
|
|
|
14000
14098
|
}
|
|
14001
14099
|
var STYLE_ID7 = "vela-widget-symbolpicker";
|
|
14002
14100
|
var CSS8 = `
|
|
14101
|
+
/* Search + market tabs stay pinned while the result list scrolls underneath
|
|
14102
|
+
(mobile fullscreen: the dialog body is the scroller). Negative margin eats
|
|
14103
|
+
the body's padding so scrolled rows cannot peek around the island. */
|
|
14104
|
+
.vela-sp-sticky {
|
|
14105
|
+
position: sticky;
|
|
14106
|
+
top: 0;
|
|
14107
|
+
z-index: 1;
|
|
14108
|
+
background: var(--vela-surface);
|
|
14109
|
+
margin: calc(-1 * var(--vela-space-4)) calc(-1 * var(--vela-space-4)) 0;
|
|
14110
|
+
padding: var(--vela-space-4) var(--vela-space-4) 0;
|
|
14111
|
+
}
|
|
14003
14112
|
.vela-sp-searchrow {
|
|
14004
14113
|
display: flex;
|
|
14005
14114
|
align-items: center;
|
|
@@ -14019,8 +14128,10 @@ var CSS8 = `
|
|
|
14019
14128
|
border: none;
|
|
14020
14129
|
font-size: 14px;
|
|
14021
14130
|
outline: none;
|
|
14131
|
+
text-transform: uppercase;
|
|
14022
14132
|
}
|
|
14023
|
-
.vela-sp-
|
|
14133
|
+
.vela-sp-input::placeholder { text-transform: none; }
|
|
14134
|
+
.vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
|
|
14024
14135
|
/* Mobile (fullscreen dialog): the asset-class strip scrolls sideways instead of
|
|
14025
14136
|
overflowing the body, and the result list stops capping itself \u2014 the body owns
|
|
14026
14137
|
the scrolling in the fullscreen presentation. */
|
|
@@ -14069,7 +14180,7 @@ var CSS8 = `
|
|
|
14069
14180
|
font-weight: 700;
|
|
14070
14181
|
}
|
|
14071
14182
|
.vela-sp-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
|
|
14072
|
-
.vela-sp-ticker { font-weight: 700; color: var(--vela-fg-bright); font-size: 14px; }
|
|
14183
|
+
.vela-sp-ticker { font-weight: 700; color: var(--vela-fg-bright); font-size: 14px; text-transform: uppercase; }
|
|
14073
14184
|
.vela-sp-desc { color: var(--vela-fg-muted); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
14074
14185
|
.vela-sp-badge {
|
|
14075
14186
|
flex: none;
|
|
@@ -14132,6 +14243,8 @@ var SymbolPicker = class {
|
|
|
14132
14243
|
searchRow.append(iconEl("search", doc), this.input);
|
|
14133
14244
|
this.tabs = doc.createElement("div");
|
|
14134
14245
|
this.tabs.className = "vela-sp-tabs";
|
|
14246
|
+
const sticky = doc.createElement("div");
|
|
14247
|
+
sticky.className = "vela-sp-sticky";
|
|
14135
14248
|
for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
|
|
14136
14249
|
const b = doc.createElement("button");
|
|
14137
14250
|
b.className = "vela-sp-tab";
|
|
@@ -14145,6 +14258,7 @@ var SymbolPicker = class {
|
|
|
14145
14258
|
});
|
|
14146
14259
|
this.tabs.appendChild(b);
|
|
14147
14260
|
}
|
|
14261
|
+
sticky.append(searchRow, this.tabs);
|
|
14148
14262
|
this.list = doc.createElement("div");
|
|
14149
14263
|
this.list.className = "vela-sp-list";
|
|
14150
14264
|
this.list.addEventListener("scroll", () => {
|
|
@@ -14157,21 +14271,32 @@ var SymbolPicker = class {
|
|
|
14157
14271
|
title: "Symbol Search",
|
|
14158
14272
|
host: opts.host,
|
|
14159
14273
|
closeOnInteractOutside: true,
|
|
14160
|
-
content: (body) => body.append(
|
|
14274
|
+
content: (body) => body.append(sticky, this.list),
|
|
14161
14275
|
onOpenChange: (open2) => {
|
|
14162
14276
|
if (open2) {
|
|
14163
|
-
this.input.value = this.seed;
|
|
14277
|
+
this.input.value = this.seed.toUpperCase();
|
|
14164
14278
|
this.seed = "";
|
|
14165
14279
|
this.refresh();
|
|
14166
14280
|
setTimeout(() => {
|
|
14167
14281
|
this.input.focus();
|
|
14168
14282
|
this.input.setSelectionRange(this.input.value.length, this.input.value.length);
|
|
14169
14283
|
}, 0);
|
|
14284
|
+
} else {
|
|
14285
|
+
this.input.blur();
|
|
14170
14286
|
}
|
|
14171
14287
|
opts.onOpenChange?.(open2);
|
|
14172
14288
|
}
|
|
14173
14289
|
});
|
|
14174
|
-
this.input.addEventListener("input", () =>
|
|
14290
|
+
this.input.addEventListener("input", () => {
|
|
14291
|
+
const next = this.input.value.toUpperCase();
|
|
14292
|
+
if (this.input.value !== next) {
|
|
14293
|
+
const start = this.input.selectionStart;
|
|
14294
|
+
const end = this.input.selectionEnd;
|
|
14295
|
+
this.input.value = next;
|
|
14296
|
+
if (start != null && end != null) this.input.setSelectionRange(start, end);
|
|
14297
|
+
}
|
|
14298
|
+
this.refresh();
|
|
14299
|
+
});
|
|
14175
14300
|
this.input.addEventListener("keydown", (e) => {
|
|
14176
14301
|
if (e.key === "ArrowDown") this.moveHighlight(1);
|
|
14177
14302
|
else if (e.key === "ArrowUp") this.moveHighlight(-1);
|
|
@@ -17806,6 +17931,7 @@ function createCustomMark(doc, html, background) {
|
|
|
17806
17931
|
});
|
|
17807
17932
|
el.style.color = attributionMarkColor(background);
|
|
17808
17933
|
el.innerHTML = html;
|
|
17934
|
+
el.dataset.velaScreenshot = "1";
|
|
17809
17935
|
return el;
|
|
17810
17936
|
}
|
|
17811
17937
|
function createAttributionMark(doc, background) {
|
|
@@ -17824,6 +17950,7 @@ function createAttributionMark(doc, background) {
|
|
|
17824
17950
|
cursor: "pointer"
|
|
17825
17951
|
});
|
|
17826
17952
|
applyAttributionMarkTheme(a, background);
|
|
17953
|
+
a.dataset.velaScreenshot = "1";
|
|
17827
17954
|
const symbol = doc.createElement("span");
|
|
17828
17955
|
symbol.className = "vela-attr-symbol";
|
|
17829
17956
|
symbol.setAttribute("aria-hidden", "true");
|
|
@@ -17992,7 +18119,11 @@ function sanitizePanels(raw) {
|
|
|
17992
18119
|
}
|
|
17993
18120
|
if (Object.keys(widths).length > 0) out.widths = widths;
|
|
17994
18121
|
}
|
|
17995
|
-
|
|
18122
|
+
if (Array.isArray(p.pinned)) {
|
|
18123
|
+
const pinned = p.pinned.filter((id) => typeof id === "string" && id !== "");
|
|
18124
|
+
if (pinned.length > 0) out.pinned = [...new Set(pinned)];
|
|
18125
|
+
}
|
|
18126
|
+
return out.open || out.widths || out.pinned ? out : null;
|
|
17996
18127
|
}
|
|
17997
18128
|
function sanitizeTrackSizes(raw) {
|
|
17998
18129
|
if (raw == null || typeof raw !== "object") return null;
|
|
@@ -20155,6 +20286,15 @@ var RendererControl = class {
|
|
|
20155
20286
|
console.warn(`[vela] renderer "${this.renderer.name}" does not support screenshot().`);
|
|
20156
20287
|
return null;
|
|
20157
20288
|
}
|
|
20289
|
+
/**
|
|
20290
|
+
* Raster of the current chart onto a canvas (same pixels as {@link screenshot}),
|
|
20291
|
+
* or null if the renderer has no canvas export. Silent — a host compositing
|
|
20292
|
+
* several charts skips a renderer that cannot contribute.
|
|
20293
|
+
*/
|
|
20294
|
+
screenshotCanvas() {
|
|
20295
|
+
if (typeof this.renderer.screenshotCanvas === "function") return this.renderer.screenshotCanvas();
|
|
20296
|
+
return null;
|
|
20297
|
+
}
|
|
20158
20298
|
/**
|
|
20159
20299
|
* The active renderer's full cosmetic config as a serializable, versioned JSON
|
|
20160
20300
|
* document — persist it (templates, saved settings) and feed it back to
|
|
@@ -22723,6 +22863,66 @@ function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
|
22723
22863
|
center: (wickLeftDev + wickDev / 2) / dpr
|
|
22724
22864
|
};
|
|
22725
22865
|
}
|
|
22866
|
+
function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
22867
|
+
const out = [];
|
|
22868
|
+
let col = NaN;
|
|
22869
|
+
let intervals = [];
|
|
22870
|
+
const flush = () => {
|
|
22871
|
+
if (intervals.length === 0) return;
|
|
22872
|
+
const merged = [intervals[0]];
|
|
22873
|
+
for (let k = 1; k < intervals.length; k += 1) {
|
|
22874
|
+
const prev2 = merged[merged.length - 1];
|
|
22875
|
+
const next = intervals[k];
|
|
22876
|
+
if (Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
|
|
22877
|
+
prev2.hi = next.hi;
|
|
22878
|
+
if (next.headIdx < prev2.headIdx) {
|
|
22879
|
+
prev2.headIdx = next.headIdx;
|
|
22880
|
+
prev2.headTime = next.headTime;
|
|
22881
|
+
prev2.open = next.open;
|
|
22882
|
+
}
|
|
22883
|
+
if (next.lastIdx > prev2.lastIdx) {
|
|
22884
|
+
prev2.lastIdx = next.lastIdx;
|
|
22885
|
+
prev2.close = next.close;
|
|
22886
|
+
}
|
|
22887
|
+
} else {
|
|
22888
|
+
merged.push(next);
|
|
22889
|
+
}
|
|
22890
|
+
}
|
|
22891
|
+
for (const iv of merged) out.push({ x: col, hi: iv.hi, lo: iv.lo, headTime: iv.headTime, open: iv.open, close: iv.close });
|
|
22892
|
+
intervals = [];
|
|
22893
|
+
};
|
|
22894
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
22895
|
+
const b = bars[i];
|
|
22896
|
+
if (!b || b.high <= b.low) continue;
|
|
22897
|
+
const x = Math.round(xOf(i));
|
|
22898
|
+
if (x !== col) {
|
|
22899
|
+
flush();
|
|
22900
|
+
col = x;
|
|
22901
|
+
}
|
|
22902
|
+
let lo = b.low;
|
|
22903
|
+
let hi = b.high;
|
|
22904
|
+
let headIdx = i;
|
|
22905
|
+
let headTime = b.time;
|
|
22906
|
+
let open2 = b.open;
|
|
22907
|
+
for (let k = intervals.length - 1; k >= 0; k -= 1) {
|
|
22908
|
+
const iv = intervals[k];
|
|
22909
|
+
if (iv.lo > hi || iv.hi < lo) continue;
|
|
22910
|
+
if (iv.lo < lo) lo = iv.lo;
|
|
22911
|
+
if (iv.hi > hi) hi = iv.hi;
|
|
22912
|
+
if (iv.headIdx < headIdx) {
|
|
22913
|
+
headIdx = iv.headIdx;
|
|
22914
|
+
headTime = iv.headTime;
|
|
22915
|
+
open2 = iv.open;
|
|
22916
|
+
}
|
|
22917
|
+
intervals.splice(k, 1);
|
|
22918
|
+
}
|
|
22919
|
+
let insertAt = 0;
|
|
22920
|
+
while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
|
|
22921
|
+
intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open: open2, lastIdx: i, close: b.close });
|
|
22922
|
+
}
|
|
22923
|
+
flush();
|
|
22924
|
+
return out;
|
|
22925
|
+
}
|
|
22726
22926
|
|
|
22727
22927
|
// src/renderers/native/backend/gl/Batch.ts
|
|
22728
22928
|
var FLOATS_PER_VERT = 8;
|
|
@@ -23695,44 +23895,19 @@ var WebGL2Backend = class {
|
|
|
23695
23895
|
}
|
|
23696
23896
|
}
|
|
23697
23897
|
/**
|
|
23698
|
-
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated):
|
|
23699
|
-
*
|
|
23700
|
-
*
|
|
23898
|
+
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
|
|
23899
|
+
* rounded pixel column collapse into high-low sticks — one per contiguous
|
|
23900
|
+
* coverage run (see {@link aggregateCandleColumns}), so a price gap inside the
|
|
23901
|
+
* column stays a void. Draw cost stays bounded by screen width, not bar count;
|
|
23902
|
+
* stick color follows its own first-open→last-close.
|
|
23701
23903
|
*/
|
|
23702
23904
|
emitCandlesAggregated(b, bars, i0, i1, coords, pane, up, down, barColors) {
|
|
23703
|
-
|
|
23704
|
-
|
|
23705
|
-
|
|
23706
|
-
|
|
23707
|
-
|
|
23708
|
-
let headTime = 0;
|
|
23709
|
-
let active = false;
|
|
23710
|
-
const flush = () => {
|
|
23711
|
-
if (!active) return;
|
|
23712
|
-
const c = parseColor(barColors.get(headTime) ?? (closeV >= openV ? up : down));
|
|
23713
|
-
const hY = coords.priceToY(hi, pane.scale, pane.bounds);
|
|
23714
|
-
const lY = coords.priceToY(lo, pane.scale, pane.bounds);
|
|
23715
|
-
b.rect(px - 0.5, hY, 1, lY - hY, c);
|
|
23716
|
-
};
|
|
23717
|
-
for (let i = i0; i <= i1; i += 1) {
|
|
23718
|
-
const bar = bars[i];
|
|
23719
|
-
if (!bar || bar.high <= bar.low) continue;
|
|
23720
|
-
const x = Math.round(coords.logicalToX(i));
|
|
23721
|
-
if (!active || x !== px) {
|
|
23722
|
-
flush();
|
|
23723
|
-
px = x;
|
|
23724
|
-
lo = bar.low;
|
|
23725
|
-
hi = bar.high;
|
|
23726
|
-
openV = bar.open;
|
|
23727
|
-
headTime = bar.time;
|
|
23728
|
-
active = true;
|
|
23729
|
-
} else {
|
|
23730
|
-
if (bar.low < lo) lo = bar.low;
|
|
23731
|
-
if (bar.high > hi) hi = bar.high;
|
|
23732
|
-
}
|
|
23733
|
-
closeV = bar.close;
|
|
23905
|
+
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
23906
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
23907
|
+
const c = parseColor(barColors.get(s.headTime) ?? (s.close >= s.open ? up : down));
|
|
23908
|
+
const hY = yOf(s.hi);
|
|
23909
|
+
b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
|
|
23734
23910
|
}
|
|
23735
|
-
flush();
|
|
23736
23911
|
}
|
|
23737
23912
|
emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
23738
23913
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
@@ -25030,13 +25205,15 @@ var SceneGraph = class {
|
|
|
25030
25205
|
if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
|
|
25031
25206
|
return this.orderedCache;
|
|
25032
25207
|
}
|
|
25033
|
-
/** The session zones resolved into colored bands (pre/post-market
|
|
25034
|
-
* config's session colors) — consumed by the same painting path as
|
|
25208
|
+
/** The session zones resolved into colored bands (pre/post-market or extended-hours
|
|
25209
|
+
* washes from the config's session colors) — consumed by the same painting path as
|
|
25210
|
+
* {@link highlights}. */
|
|
25035
25211
|
sessionHighlightBands() {
|
|
25036
25212
|
if (!this.sessionZones) return [];
|
|
25037
25213
|
const out = [];
|
|
25038
25214
|
for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
|
|
25039
25215
|
for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
|
|
25216
|
+
for (const [from, to] of this.sessionZones.extended) out.push({ from, to, color: this.style.sessions.extendedColor });
|
|
25040
25217
|
return out;
|
|
25041
25218
|
}
|
|
25042
25219
|
indicatorsForPane(paneId) {
|
|
@@ -25575,50 +25752,23 @@ var Canvas2dBackend = class {
|
|
|
25575
25752
|
}
|
|
25576
25753
|
}
|
|
25577
25754
|
/**
|
|
25578
|
-
* Sub-pixel LOD:
|
|
25579
|
-
*
|
|
25580
|
-
*
|
|
25581
|
-
*
|
|
25755
|
+
* Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
|
|
25756
|
+
* sticks (one per contiguous coverage run — see {@link aggregateCandleColumns}),
|
|
25757
|
+
* so draw cost is bounded by screen width (not bar count) when zoomed far out
|
|
25758
|
+
* and a price gap inside the column stays a void. Each stick's color follows
|
|
25759
|
+
* its first-open→last-close direction (a barcolor() on the head bar still wins).
|
|
25582
25760
|
*/
|
|
25583
25761
|
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, up, down, barColors) {
|
|
25584
|
-
|
|
25585
|
-
|
|
25586
|
-
|
|
25587
|
-
|
|
25588
|
-
|
|
25589
|
-
let headTime = 0;
|
|
25590
|
-
let active = false;
|
|
25591
|
-
const flush = () => {
|
|
25592
|
-
if (!active) return;
|
|
25593
|
-
const color = barColors.get(headTime) ?? (closeV >= openV ? up : down);
|
|
25594
|
-
const hY = coords.priceToY(hi, pane.scale, pane.bounds);
|
|
25595
|
-
const lY = coords.priceToY(lo, pane.scale, pane.bounds);
|
|
25596
|
-
ctx.strokeStyle = color;
|
|
25597
|
-
ctx.lineWidth = 1;
|
|
25762
|
+
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
25763
|
+
ctx.lineWidth = 1;
|
|
25764
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
25765
|
+
const x = s.x + 0.5;
|
|
25766
|
+
ctx.strokeStyle = barColors.get(s.headTime) ?? (s.close >= s.open ? up : down);
|
|
25598
25767
|
ctx.beginPath();
|
|
25599
|
-
ctx.moveTo(
|
|
25600
|
-
ctx.lineTo(
|
|
25768
|
+
ctx.moveTo(x, yOf(s.hi));
|
|
25769
|
+
ctx.lineTo(x, yOf(s.lo));
|
|
25601
25770
|
ctx.stroke();
|
|
25602
|
-
};
|
|
25603
|
-
for (let i = i0; i <= i1; i += 1) {
|
|
25604
|
-
const b = bars[i];
|
|
25605
|
-
if (!b || b.high <= b.low) continue;
|
|
25606
|
-
const x = Math.round(coords.logicalToX(i)) + 0.5;
|
|
25607
|
-
if (!active || x !== px) {
|
|
25608
|
-
flush();
|
|
25609
|
-
px = x;
|
|
25610
|
-
lo = b.low;
|
|
25611
|
-
hi = b.high;
|
|
25612
|
-
openV = b.open;
|
|
25613
|
-
headTime = b.time;
|
|
25614
|
-
active = true;
|
|
25615
|
-
} else {
|
|
25616
|
-
if (b.low < lo) lo = b.low;
|
|
25617
|
-
if (b.high > hi) hi = b.high;
|
|
25618
|
-
}
|
|
25619
|
-
closeV = b.close;
|
|
25620
25771
|
}
|
|
25621
|
-
flush();
|
|
25622
25772
|
}
|
|
25623
25773
|
drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
25624
25774
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
@@ -26642,6 +26792,32 @@ var DrawingSceneRenderer = class {
|
|
|
26642
26792
|
}
|
|
26643
26793
|
};
|
|
26644
26794
|
|
|
26795
|
+
// src/renderers/native/chrome/tz.ts
|
|
26796
|
+
function tzOffsetMs(ms, timeZone) {
|
|
26797
|
+
if (!timeZone || timeZone === "UTC") return 0;
|
|
26798
|
+
try {
|
|
26799
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
26800
|
+
timeZone,
|
|
26801
|
+
hourCycle: "h23",
|
|
26802
|
+
year: "numeric",
|
|
26803
|
+
month: "2-digit",
|
|
26804
|
+
day: "2-digit",
|
|
26805
|
+
hour: "2-digit",
|
|
26806
|
+
minute: "2-digit",
|
|
26807
|
+
second: "2-digit"
|
|
26808
|
+
});
|
|
26809
|
+
const parts = dtf.formatToParts(new Date(ms));
|
|
26810
|
+
const get = (t) => Number(parts.find((p) => p.type === t)?.value);
|
|
26811
|
+
const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
|
|
26812
|
+
return asUTC - ms;
|
|
26813
|
+
} catch {
|
|
26814
|
+
return 0;
|
|
26815
|
+
}
|
|
26816
|
+
}
|
|
26817
|
+
function zonedDate(ms, timeZone) {
|
|
26818
|
+
return new Date(ms + tzOffsetMs(ms, timeZone));
|
|
26819
|
+
}
|
|
26820
|
+
|
|
26645
26821
|
// src/renderers/native/chrome/ticks.ts
|
|
26646
26822
|
function priceTicks(min, max, target = 6) {
|
|
26647
26823
|
if (!(max > min) || !Number.isFinite(min) || !Number.isFinite(max)) return [];
|
|
@@ -26789,7 +26965,14 @@ var STEP_LADDER = [
|
|
|
26789
26965
|
YEAR
|
|
26790
26966
|
];
|
|
26791
26967
|
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
26968
|
+
var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
26792
26969
|
var pad2 = (n) => n < 10 ? `0${n}` : String(n);
|
|
26970
|
+
function formatTimeStamp(ms, timeZone, barIntervalMs) {
|
|
26971
|
+
const d = zonedDate(ms, timeZone);
|
|
26972
|
+
const date = `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} '${pad2(d.getUTCFullYear() % 100)}`;
|
|
26973
|
+
if (barIntervalMs >= DAY) return date;
|
|
26974
|
+
return `${date} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}`;
|
|
26975
|
+
}
|
|
26793
26976
|
function pickStep(targetMs) {
|
|
26794
26977
|
for (const step of STEP_LADDER) if (step >= targetMs) return step;
|
|
26795
26978
|
return STEP_LADDER[STEP_LADDER.length - 1];
|
|
@@ -26831,32 +27014,6 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
|
|
|
26831
27014
|
return out;
|
|
26832
27015
|
}
|
|
26833
27016
|
|
|
26834
|
-
// src/renderers/native/chrome/tz.ts
|
|
26835
|
-
function tzOffsetMs(ms, timeZone) {
|
|
26836
|
-
if (!timeZone || timeZone === "UTC") return 0;
|
|
26837
|
-
try {
|
|
26838
|
-
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
26839
|
-
timeZone,
|
|
26840
|
-
hourCycle: "h23",
|
|
26841
|
-
year: "numeric",
|
|
26842
|
-
month: "2-digit",
|
|
26843
|
-
day: "2-digit",
|
|
26844
|
-
hour: "2-digit",
|
|
26845
|
-
minute: "2-digit",
|
|
26846
|
-
second: "2-digit"
|
|
26847
|
-
});
|
|
26848
|
-
const parts = dtf.formatToParts(new Date(ms));
|
|
26849
|
-
const get = (t) => Number(parts.find((p) => p.type === t)?.value);
|
|
26850
|
-
const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
|
|
26851
|
-
return asUTC - ms;
|
|
26852
|
-
} catch {
|
|
26853
|
-
return 0;
|
|
26854
|
-
}
|
|
26855
|
-
}
|
|
26856
|
-
function zonedDate(ms, timeZone) {
|
|
26857
|
-
return new Date(ms + tzOffsetMs(ms, timeZone));
|
|
26858
|
-
}
|
|
26859
|
-
|
|
26860
27017
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
26861
27018
|
var ChromeRenderer = class {
|
|
26862
27019
|
constructor() {
|
|
@@ -27303,7 +27460,7 @@ var CrosshairRenderer = class {
|
|
|
27303
27460
|
const price = coords.yToPrice(ch.y, pane.scale, pane.bounds);
|
|
27304
27461
|
this.chip(ctx, dataW + 1, ch.y, formatAxisValue(pane.scale, pane.bounds.height, price, percentScaleFor(scene, pane), scene.priceMintick, pane.axisFormat), chipBg, "left", false, theme.background);
|
|
27305
27462
|
}
|
|
27306
|
-
this.chip(ctx, x, dataH + 1,
|
|
27463
|
+
this.chip(ctx, x, dataH + 1, formatTimeStamp(coords.logicalToTime(logical), scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
27307
27464
|
}
|
|
27308
27465
|
destroy() {
|
|
27309
27466
|
this.canvas = null;
|
|
@@ -27351,7 +27508,7 @@ var CrosshairRenderer = class {
|
|
|
27351
27508
|
this.chip(ctx, dataW + 1, ext.y, formatAxisValue(pane.scale, pane.bounds.height, ext.price, percentScaleFor(scene, pane), scene.priceMintick, pane.axisFormat), chipBg, "left", false, theme.background);
|
|
27352
27509
|
}
|
|
27353
27510
|
}
|
|
27354
|
-
this.chip(ctx, x, dataH + 1,
|
|
27511
|
+
this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
27355
27512
|
ctx.globalAlpha = 1;
|
|
27356
27513
|
}
|
|
27357
27514
|
/** A soft band + a brighter crisp center line over the hovered separator, so it reads as
|
|
@@ -27384,11 +27541,6 @@ function setDash3(ctx, style) {
|
|
|
27384
27541
|
else if (style === "dotted") ctx.setLineDash([2, 3]);
|
|
27385
27542
|
else ctx.setLineDash([]);
|
|
27386
27543
|
}
|
|
27387
|
-
function formatStamp(ms, timeZone) {
|
|
27388
|
-
const d = zonedDate(ms, timeZone);
|
|
27389
|
-
const pad = (n) => n < 10 ? `0${n}` : String(n);
|
|
27390
|
-
return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
27391
|
-
}
|
|
27392
27544
|
|
|
27393
27545
|
// src/renderers/native/chrome/settings-visibility.ts
|
|
27394
27546
|
function settingsIdSlug(label) {
|
|
@@ -33155,6 +33307,9 @@ function clipHighlightRect(x1, x2, left, right) {
|
|
|
33155
33307
|
const end = Math.min(right, x2);
|
|
33156
33308
|
return end > x ? { x, width: end - x } : null;
|
|
33157
33309
|
}
|
|
33310
|
+
function bandEdgeSlot(logical) {
|
|
33311
|
+
return Math.ceil(logical) - 0.5;
|
|
33312
|
+
}
|
|
33158
33313
|
var BackdropRenderer = class {
|
|
33159
33314
|
constructor() {
|
|
33160
33315
|
this.canvas = null;
|
|
@@ -33185,21 +33340,22 @@ var BackdropRenderer = class {
|
|
|
33185
33340
|
this.drawGrid(ctx, scene, coords, theme, coords.width, gridAlpha);
|
|
33186
33341
|
}
|
|
33187
33342
|
/** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
|
|
33188
|
-
* Session-zone washes
|
|
33343
|
+
* Session-zone washes paint first (edges snapped to bar-slot boundaries so a band
|
|
33344
|
+
* never cuts a candle in two), host highlights on top (continuous time mapping —
|
|
33345
|
+
* hosts may paint sub-bar spans). */
|
|
33189
33346
|
drawHighlights(ctx, scene, coords) {
|
|
33190
33347
|
const sessions = scene.sessionHighlightBands();
|
|
33191
33348
|
if (sessions.length > 0) {
|
|
33192
33349
|
const left = Math.max(0, coords.logicalToX(-0.5));
|
|
33193
33350
|
const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
|
|
33194
|
-
this.drawHighlightSet(ctx, sessions, coords, left, right);
|
|
33351
|
+
this.drawHighlightSet(ctx, sessions, coords, left, right, true);
|
|
33195
33352
|
}
|
|
33196
|
-
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
|
|
33353
|
+
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width, false);
|
|
33197
33354
|
}
|
|
33198
|
-
drawHighlightSet(ctx, bands, coords, left, right) {
|
|
33355
|
+
drawHighlightSet(ctx, bands, coords, left, right, snapToSlots) {
|
|
33356
|
+
const edgeX = (ms) => snapToSlots ? coords.logicalToX(bandEdgeSlot(coords.timeToLogical(ms))) : coords.timeToX(ms);
|
|
33199
33357
|
for (const band of bands) {
|
|
33200
|
-
const
|
|
33201
|
-
const x2 = coords.timeToX(band.to);
|
|
33202
|
-
const rect = clipHighlightRect(x1, x2, left, right);
|
|
33358
|
+
const rect = clipHighlightRect(edgeX(band.from), edgeX(band.to), left, right);
|
|
33203
33359
|
if (!rect) continue;
|
|
33204
33360
|
ctx.fillStyle = band.color;
|
|
33205
33361
|
ctx.fillRect(rect.x, 0, rect.width, coords.height);
|
|
@@ -34346,7 +34502,7 @@ var NativeRenderer = class {
|
|
|
34346
34502
|
};
|
|
34347
34503
|
})(),
|
|
34348
34504
|
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
|
|
34349
|
-
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
|
|
34505
|
+
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor, extendedColor: s.sessions.extendedColor }
|
|
34350
34506
|
};
|
|
34351
34507
|
}
|
|
34352
34508
|
/**
|
|
@@ -34428,7 +34584,7 @@ var NativeRenderer = class {
|
|
|
34428
34584
|
width: next.baseline.width,
|
|
34429
34585
|
baselineLevel: next.baseline.baselineLevel
|
|
34430
34586
|
};
|
|
34431
|
-
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
|
|
34587
|
+
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor, extendedColor: next.sessions.extendedColor };
|
|
34432
34588
|
this.setPriceStyle(next.series.style);
|
|
34433
34589
|
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
34434
34590
|
this.scene.baselineValue = next.series.baseline;
|
|
@@ -34650,9 +34806,10 @@ var NativeRenderer = class {
|
|
|
34650
34806
|
* opts in with a `data-vela-screenshot` attribute on the mount container's
|
|
34651
34807
|
* subtree (the widget marks its status line, and its symbol watermark with
|
|
34652
34808
|
* `"under"` — drawn beneath the canvases, where it sits on screen). Only the
|
|
34653
|
-
* crosshair (L2) is intentionally excluded.
|
|
34809
|
+
* crosshair (L2) is intentionally excluded. {@link screenshot} is this canvas
|
|
34810
|
+
* as a PNG data URL.
|
|
34654
34811
|
*/
|
|
34655
|
-
|
|
34812
|
+
screenshotCanvas() {
|
|
34656
34813
|
if (!this.dataCanvas) return null;
|
|
34657
34814
|
this.computeScales();
|
|
34658
34815
|
this.paintData();
|
|
@@ -34680,7 +34837,10 @@ var NativeRenderer = class {
|
|
|
34680
34837
|
if (el.getAttribute("data-vela-screenshot") !== "under") rasterizeOverlay(ctx, el, frame);
|
|
34681
34838
|
}
|
|
34682
34839
|
}
|
|
34683
|
-
return out
|
|
34840
|
+
return out;
|
|
34841
|
+
}
|
|
34842
|
+
screenshot() {
|
|
34843
|
+
return this.screenshotCanvas()?.toDataURL("image/png") ?? null;
|
|
34684
34844
|
}
|
|
34685
34845
|
/**
|
|
34686
34846
|
* The candles-first-appear reveal — animates the **candles themselves** (no extra
|
|
@@ -36075,7 +36235,7 @@ var NativeRenderer = class {
|
|
|
36075
36235
|
const pricePane = this.dataWindowPricePane();
|
|
36076
36236
|
const ohlc = this.dataWindowOHLC(bar, pricePane);
|
|
36077
36237
|
const parts = [];
|
|
36078
|
-
if (bar) parts.push(
|
|
36238
|
+
if (bar) parts.push(formatStamp(bar.time, this.scene.timezone));
|
|
36079
36239
|
if (ohlc) parts.push(`Open ${ohlc.o}, High ${ohlc.h}, Low ${ohlc.l}, Close ${ohlc.c}`);
|
|
36080
36240
|
for (const group of this.dataWindowGroups(idx, pricePane)) {
|
|
36081
36241
|
for (const row of group.rows) parts.push(`${row.label} ${row.value}`);
|
|
@@ -36962,7 +37122,7 @@ function formatStampParts(ms, timeZone) {
|
|
|
36962
37122
|
time: `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`
|
|
36963
37123
|
};
|
|
36964
37124
|
}
|
|
36965
|
-
function
|
|
37125
|
+
function formatStamp(ms, timeZone) {
|
|
36966
37126
|
const { date, time } = formatStampParts(ms, timeZone);
|
|
36967
37127
|
return `${date} ${time}`;
|
|
36968
37128
|
}
|
|
@@ -36993,7 +37153,7 @@ function sanitizeSessionZones(value) {
|
|
|
36993
37153
|
}
|
|
36994
37154
|
return out.sort((a, b) => a[0] - b[0]);
|
|
36995
37155
|
};
|
|
36996
|
-
return { pre: windows(v.pre), post: windows(v.post) };
|
|
37156
|
+
return { pre: windows(v.pre), post: windows(v.post), extended: windows(v.extended) };
|
|
36997
37157
|
}
|
|
36998
37158
|
function clamp012(v) {
|
|
36999
37159
|
return Math.max(0, Math.min(1, v));
|
|
@@ -37682,6 +37842,7 @@ var MARKET_LABELS = {
|
|
|
37682
37842
|
open: "Market Open",
|
|
37683
37843
|
pre: "Pre-Market",
|
|
37684
37844
|
post: "Post-Market",
|
|
37845
|
+
extended: "Extended Hours",
|
|
37685
37846
|
closed: "Market Closed",
|
|
37686
37847
|
holiday: "Market Holiday"
|
|
37687
37848
|
};
|
|
@@ -37689,6 +37850,7 @@ var MARKET_INKS = {
|
|
|
37689
37850
|
open: "var(--vela-up)",
|
|
37690
37851
|
pre: SESSION_PRE,
|
|
37691
37852
|
post: SESSION_POST,
|
|
37853
|
+
extended: SESSION_POST,
|
|
37692
37854
|
closed: SESSION_OFF,
|
|
37693
37855
|
holiday: SESSION_OFF
|
|
37694
37856
|
};
|
|
@@ -38002,118 +38164,17 @@ var Statusline = class {
|
|
|
38002
38164
|
}
|
|
38003
38165
|
};
|
|
38004
38166
|
|
|
38005
|
-
// src/widget/market-status.ts
|
|
38006
|
-
function civilDate(ms, tz) {
|
|
38007
|
-
try {
|
|
38008
|
-
return new Intl.DateTimeFormat("en-CA", { timeZone: tz }).format(ms);
|
|
38009
|
-
} catch {
|
|
38010
|
-
return "";
|
|
38011
|
-
}
|
|
38012
|
-
}
|
|
38013
|
-
function isWeekday(ms, tz) {
|
|
38014
|
-
try {
|
|
38015
|
-
const wd = new Intl.DateTimeFormat("en-US", { timeZone: tz, weekday: "short" }).format(ms);
|
|
38016
|
-
return wd !== "Sat" && wd !== "Sun";
|
|
38017
|
-
} catch {
|
|
38018
|
-
return false;
|
|
38019
|
-
}
|
|
38020
|
-
}
|
|
38021
|
-
function deriveMarketStatus(now, w, tz) {
|
|
38022
|
-
const within = (ws) => ws.find(([s, e]) => now >= s && now < e);
|
|
38023
|
-
if (within(w.regular)) return "open";
|
|
38024
|
-
const ext = within(w.extended);
|
|
38025
|
-
if (ext) return w.regular.some(([s]) => s >= now && s < ext[1]) ? "pre" : "post";
|
|
38026
|
-
if (isWeekday(now, tz)) {
|
|
38027
|
-
const today = civilDate(now, tz);
|
|
38028
|
-
if (today !== "" && !w.extended.some(([s]) => civilDate(s, tz) === today)) return "holiday";
|
|
38029
|
-
}
|
|
38030
|
-
return "closed";
|
|
38031
|
-
}
|
|
38032
|
-
function nextStatusBoundary(now, w) {
|
|
38033
|
-
let next = null;
|
|
38034
|
-
for (const ws of [w.regular, w.extended]) {
|
|
38035
|
-
for (const [s, e] of ws) {
|
|
38036
|
-
for (const t of [s, e]) {
|
|
38037
|
-
if (t > now && (next == null || t < next)) next = t;
|
|
38038
|
-
}
|
|
38039
|
-
}
|
|
38040
|
-
}
|
|
38041
|
-
return next;
|
|
38042
|
-
}
|
|
38043
|
-
var FETCH_BACK_MS = 4 * 864e5;
|
|
38044
|
-
var FETCH_AHEAD_MS = 10 * 864e5;
|
|
38045
|
-
var MIN_TIMER_MS = 15e3;
|
|
38046
|
-
var MAX_TIMER_MS = 36e5;
|
|
38047
|
-
var RETRY_MS2 = 6e4;
|
|
38048
|
-
var MarketStatusTracker = class {
|
|
38049
|
-
constructor(onStatus) {
|
|
38050
|
-
this.onStatus = onStatus;
|
|
38051
|
-
/** Invalidates detached async work — bumped by every track()/stop(). */
|
|
38052
|
-
this.epoch = 0;
|
|
38053
|
-
this.timer = null;
|
|
38054
|
-
}
|
|
38055
|
-
/** (Re)bind to a chart's data surface + symbol and start evaluating. */
|
|
38056
|
-
track(data, symbol) {
|
|
38057
|
-
const my = ++this.epoch;
|
|
38058
|
-
this.clearTimer();
|
|
38059
|
-
void this.evaluate(my, data, symbol);
|
|
38060
|
-
}
|
|
38061
|
-
stop() {
|
|
38062
|
-
this.epoch += 1;
|
|
38063
|
-
this.clearTimer();
|
|
38064
|
-
}
|
|
38065
|
-
async evaluate(my, data, symbol) {
|
|
38066
|
-
const resolved = data.resolve(symbol);
|
|
38067
|
-
const provider = resolved ? data.providerInstance(resolved.provider) : void 0;
|
|
38068
|
-
const si = await data.symbolInfo(symbol).catch(() => void 0);
|
|
38069
|
-
if (my !== this.epoch) return;
|
|
38070
|
-
const hasSessions = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
38071
|
-
if (!provider?.getCalendar || !hasSessions || !resolved) {
|
|
38072
|
-
this.onStatus("open");
|
|
38073
|
-
return;
|
|
38074
|
-
}
|
|
38075
|
-
const tz = typeof si?.timezone === "string" && si.timezone !== "" ? si.timezone : "Etc/UTC";
|
|
38076
|
-
const now = Date.now();
|
|
38077
|
-
const range = { from: now - FETCH_BACK_MS, to: now + FETCH_AHEAD_MS };
|
|
38078
|
-
const [regular, extended] = await Promise.all([
|
|
38079
|
-
provider.getCalendar(resolved.ticker, { ...range, session: "regular" }).catch(() => null),
|
|
38080
|
-
provider.getCalendar(resolved.ticker, { ...range, session: "extended" }).catch(() => null)
|
|
38081
|
-
]);
|
|
38082
|
-
if (my !== this.epoch) return;
|
|
38083
|
-
if (!regular || !extended) {
|
|
38084
|
-
this.arm(my, data, symbol, RETRY_MS2);
|
|
38085
|
-
return;
|
|
38086
|
-
}
|
|
38087
|
-
const w = { regular, extended };
|
|
38088
|
-
this.onStatus(deriveMarketStatus(now, w, tz));
|
|
38089
|
-
const boundary = nextStatusBoundary(now, w);
|
|
38090
|
-
const delay = Math.min(boundary != null ? boundary - now : MAX_TIMER_MS, MAX_TIMER_MS);
|
|
38091
|
-
this.arm(my, data, symbol, Math.max(delay, MIN_TIMER_MS));
|
|
38092
|
-
}
|
|
38093
|
-
arm(my, data, symbol, delay) {
|
|
38094
|
-
this.clearTimer();
|
|
38095
|
-
this.timer = setTimeout(() => {
|
|
38096
|
-
this.timer = null;
|
|
38097
|
-
if (my !== this.epoch) return;
|
|
38098
|
-
void this.evaluate(my, data, symbol);
|
|
38099
|
-
}, delay);
|
|
38100
|
-
}
|
|
38101
|
-
clearTimer() {
|
|
38102
|
-
if (this.timer != null) clearTimeout(this.timer);
|
|
38103
|
-
this.timer = null;
|
|
38104
|
-
}
|
|
38105
|
-
};
|
|
38106
|
-
|
|
38107
38167
|
// src/widget/session-shading.ts
|
|
38108
38168
|
var DAY_MS2 = 864e5;
|
|
38109
38169
|
var MINUTE_MS = 6e4;
|
|
38110
|
-
function parseWindow(text) {
|
|
38170
|
+
function parseWindow(text, allowOvernight = false) {
|
|
38111
38171
|
if (typeof text !== "string") return null;
|
|
38112
38172
|
const m = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/.exec(text);
|
|
38113
38173
|
if (!m) return null;
|
|
38114
38174
|
const start = Number(m[1]) * 60 + Number(m[2]);
|
|
38115
38175
|
const end = Number(m[3]) * 60 + Number(m[4]);
|
|
38116
|
-
if (
|
|
38176
|
+
if (end > 1440 || start >= 1440) return null;
|
|
38177
|
+
if (start >= end && !allowOvernight) return null;
|
|
38117
38178
|
return { start, end };
|
|
38118
38179
|
}
|
|
38119
38180
|
function parseSessionSpec(si) {
|
|
@@ -38123,9 +38184,13 @@ function parseSessionSpec(si) {
|
|
|
38123
38184
|
if (!regular) return null;
|
|
38124
38185
|
const tz = si?.["timezone"];
|
|
38125
38186
|
const timezone = typeof tz === "string" && tz !== "" ? tz : "Etc/UTC";
|
|
38126
|
-
const ext = parseWindow(si?.["session_extended"]);
|
|
38187
|
+
const ext = parseWindow(si?.["session_extended"], true);
|
|
38188
|
+
if (ext && ext.start >= ext.end) {
|
|
38189
|
+
if (ext.start >= regular.end && ext.end >= regular.end) return { regular, extended: ext, overnight: true, timezone };
|
|
38190
|
+
return { regular, extended: { start: 0, end: 1440 }, overnight: false, timezone };
|
|
38191
|
+
}
|
|
38127
38192
|
const extended = ext && ext.start <= regular.start && ext.end >= regular.end ? ext : { start: 0, end: 1440 };
|
|
38128
|
-
return { regular, extended, timezone };
|
|
38193
|
+
return { regular, extended, overnight: false, timezone };
|
|
38129
38194
|
}
|
|
38130
38195
|
var dtfCache = /* @__PURE__ */ new Map();
|
|
38131
38196
|
function civilFormatter(tz) {
|
|
@@ -38161,27 +38226,48 @@ function civilParts(dtf, ms) {
|
|
|
38161
38226
|
minute: Number(get("minute"))
|
|
38162
38227
|
};
|
|
38163
38228
|
}
|
|
38229
|
+
function mergeAbutting(bands) {
|
|
38230
|
+
const sorted = [...bands].sort((a, b) => a[0] - b[0]);
|
|
38231
|
+
const out = [];
|
|
38232
|
+
for (const [s, e] of sorted) {
|
|
38233
|
+
if (e <= s) continue;
|
|
38234
|
+
const last = out[out.length - 1];
|
|
38235
|
+
if (last && s <= last[1]) last[1] = Math.max(last[1], e);
|
|
38236
|
+
else out.push([s, e]);
|
|
38237
|
+
}
|
|
38238
|
+
return out;
|
|
38239
|
+
}
|
|
38164
38240
|
function expandSessionZones(spec, from, to) {
|
|
38165
38241
|
const pre = [];
|
|
38166
38242
|
const post = [];
|
|
38243
|
+
const extended = [];
|
|
38167
38244
|
const dtf = civilFormatter(spec.timezone);
|
|
38168
|
-
if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post };
|
|
38245
|
+
if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post, extended };
|
|
38169
38246
|
const seen = /* @__PURE__ */ new Set();
|
|
38170
38247
|
for (let cursor = from - DAY_MS2; cursor < to + DAY_MS2; cursor += DAY_MS2) {
|
|
38171
38248
|
const civil = civilParts(dtf, cursor);
|
|
38172
38249
|
const key = civil.year * 1e4 + civil.month * 100 + civil.day;
|
|
38173
38250
|
if (!Number.isFinite(key) || seen.has(key)) continue;
|
|
38174
38251
|
seen.add(key);
|
|
38175
|
-
|
|
38252
|
+
const sunday = civil.weekday === "Sun";
|
|
38253
|
+
if (civil.weekday === "Sat" || sunday && !spec.overnight) continue;
|
|
38176
38254
|
const naive = Date.UTC(civil.year, civil.month - 1, civil.day);
|
|
38177
38255
|
const noonGuess = naive + 720 * MINUTE_MS;
|
|
38178
38256
|
const atNoon = civilParts(dtf, noonGuess);
|
|
38179
38257
|
const offset = Date.UTC(atNoon.year, atNoon.month - 1, atNoon.day, atNoon.hour, atNoon.minute) - noonGuess;
|
|
38180
38258
|
const at = (minutes) => naive + minutes * MINUTE_MS - offset;
|
|
38259
|
+
if (spec.overnight) {
|
|
38260
|
+
if (!sunday) {
|
|
38261
|
+
extended.push([at(0), at(spec.regular.start)]);
|
|
38262
|
+
if (spec.regular.end < spec.extended.end) extended.push([at(spec.regular.end), at(spec.extended.end)]);
|
|
38263
|
+
}
|
|
38264
|
+
if (civil.weekday !== "Fri") extended.push([at(spec.extended.start), at(1440)]);
|
|
38265
|
+
continue;
|
|
38266
|
+
}
|
|
38181
38267
|
if (spec.extended.start < spec.regular.start) pre.push([at(spec.extended.start), at(spec.regular.start)]);
|
|
38182
38268
|
if (spec.regular.end < spec.extended.end) post.push([at(spec.regular.end), at(spec.extended.end)]);
|
|
38183
38269
|
}
|
|
38184
|
-
return { pre, post };
|
|
38270
|
+
return { pre, post, extended: mergeAbutting(extended) };
|
|
38185
38271
|
}
|
|
38186
38272
|
var COVER_PAD_MIN_MS = 2 * DAY_MS2;
|
|
38187
38273
|
var SessionShadingTracker = class {
|
|
@@ -38236,7 +38322,7 @@ var SessionShadingTracker = class {
|
|
|
38236
38322
|
return;
|
|
38237
38323
|
}
|
|
38238
38324
|
if (this.session !== "extended" || !this.intraday) {
|
|
38239
|
-
if (force) this.onZones({ pre: [], post: [] });
|
|
38325
|
+
if (force) this.onZones({ pre: [], post: [], extended: [] });
|
|
38240
38326
|
return;
|
|
38241
38327
|
}
|
|
38242
38328
|
const from = Math.min(range.from, range.to);
|
|
@@ -38250,6 +38336,112 @@ var SessionShadingTracker = class {
|
|
|
38250
38336
|
}
|
|
38251
38337
|
};
|
|
38252
38338
|
|
|
38339
|
+
// src/widget/market-status.ts
|
|
38340
|
+
function civilDate(ms, tz) {
|
|
38341
|
+
try {
|
|
38342
|
+
return new Intl.DateTimeFormat("en-CA", { timeZone: tz }).format(ms);
|
|
38343
|
+
} catch {
|
|
38344
|
+
return "";
|
|
38345
|
+
}
|
|
38346
|
+
}
|
|
38347
|
+
function isWeekday(ms, tz) {
|
|
38348
|
+
try {
|
|
38349
|
+
const wd = new Intl.DateTimeFormat("en-US", { timeZone: tz, weekday: "short" }).format(ms);
|
|
38350
|
+
return wd !== "Sat" && wd !== "Sun";
|
|
38351
|
+
} catch {
|
|
38352
|
+
return false;
|
|
38353
|
+
}
|
|
38354
|
+
}
|
|
38355
|
+
function deriveMarketStatus(now, w, tz, overnight = false) {
|
|
38356
|
+
const within = (ws) => ws.find(([s, e]) => now >= s && now < e);
|
|
38357
|
+
if (within(w.regular)) return "open";
|
|
38358
|
+
const ext = within(w.extended);
|
|
38359
|
+
if (ext) {
|
|
38360
|
+
if (overnight) return "extended";
|
|
38361
|
+
return w.regular.some(([s]) => s >= now && s < ext[1]) ? "pre" : "post";
|
|
38362
|
+
}
|
|
38363
|
+
if (isWeekday(now, tz)) {
|
|
38364
|
+
const today = civilDate(now, tz);
|
|
38365
|
+
if (today !== "" && !w.extended.some(([s]) => civilDate(s, tz) === today)) return "holiday";
|
|
38366
|
+
}
|
|
38367
|
+
return "closed";
|
|
38368
|
+
}
|
|
38369
|
+
function nextStatusBoundary(now, w) {
|
|
38370
|
+
let next = null;
|
|
38371
|
+
for (const ws of [w.regular, w.extended]) {
|
|
38372
|
+
for (const [s, e] of ws) {
|
|
38373
|
+
for (const t of [s, e]) {
|
|
38374
|
+
if (t > now && (next == null || t < next)) next = t;
|
|
38375
|
+
}
|
|
38376
|
+
}
|
|
38377
|
+
}
|
|
38378
|
+
return next;
|
|
38379
|
+
}
|
|
38380
|
+
var FETCH_BACK_MS = 4 * 864e5;
|
|
38381
|
+
var FETCH_AHEAD_MS = 10 * 864e5;
|
|
38382
|
+
var MIN_TIMER_MS = 15e3;
|
|
38383
|
+
var MAX_TIMER_MS = 36e5;
|
|
38384
|
+
var RETRY_MS2 = 6e4;
|
|
38385
|
+
var MarketStatusTracker = class {
|
|
38386
|
+
constructor(onStatus) {
|
|
38387
|
+
this.onStatus = onStatus;
|
|
38388
|
+
/** Invalidates detached async work — bumped by every track()/stop(). */
|
|
38389
|
+
this.epoch = 0;
|
|
38390
|
+
this.timer = null;
|
|
38391
|
+
}
|
|
38392
|
+
/** (Re)bind to a chart's data surface + symbol and start evaluating. */
|
|
38393
|
+
track(data, symbol) {
|
|
38394
|
+
const my = ++this.epoch;
|
|
38395
|
+
this.clearTimer();
|
|
38396
|
+
void this.evaluate(my, data, symbol);
|
|
38397
|
+
}
|
|
38398
|
+
stop() {
|
|
38399
|
+
this.epoch += 1;
|
|
38400
|
+
this.clearTimer();
|
|
38401
|
+
}
|
|
38402
|
+
async evaluate(my, data, symbol) {
|
|
38403
|
+
const resolved = data.resolve(symbol);
|
|
38404
|
+
const provider = resolved ? data.providerInstance(resolved.provider) : void 0;
|
|
38405
|
+
const si = await data.symbolInfo(symbol).catch(() => void 0);
|
|
38406
|
+
if (my !== this.epoch) return;
|
|
38407
|
+
const hasSessions = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
38408
|
+
if (!provider?.getCalendar || !hasSessions || !resolved) {
|
|
38409
|
+
this.onStatus("open");
|
|
38410
|
+
return;
|
|
38411
|
+
}
|
|
38412
|
+
const tz = typeof si?.timezone === "string" && si.timezone !== "" ? si.timezone : "Etc/UTC";
|
|
38413
|
+
const now = Date.now();
|
|
38414
|
+
const range = { from: now - FETCH_BACK_MS, to: now + FETCH_AHEAD_MS };
|
|
38415
|
+
const [regular, extended] = await Promise.all([
|
|
38416
|
+
provider.getCalendar(resolved.ticker, { ...range, session: "regular" }).catch(() => null),
|
|
38417
|
+
provider.getCalendar(resolved.ticker, { ...range, session: "extended" }).catch(() => null)
|
|
38418
|
+
]);
|
|
38419
|
+
if (my !== this.epoch) return;
|
|
38420
|
+
if (!regular || !extended) {
|
|
38421
|
+
this.arm(my, data, symbol, RETRY_MS2);
|
|
38422
|
+
return;
|
|
38423
|
+
}
|
|
38424
|
+
const w = { regular, extended };
|
|
38425
|
+
const overnight = parseSessionSpec(si)?.overnight === true;
|
|
38426
|
+
this.onStatus(deriveMarketStatus(now, w, tz, overnight));
|
|
38427
|
+
const boundary = nextStatusBoundary(now, w);
|
|
38428
|
+
const delay = Math.min(boundary != null ? boundary - now : MAX_TIMER_MS, MAX_TIMER_MS);
|
|
38429
|
+
this.arm(my, data, symbol, Math.max(delay, MIN_TIMER_MS));
|
|
38430
|
+
}
|
|
38431
|
+
arm(my, data, symbol, delay) {
|
|
38432
|
+
this.clearTimer();
|
|
38433
|
+
this.timer = setTimeout(() => {
|
|
38434
|
+
this.timer = null;
|
|
38435
|
+
if (my !== this.epoch) return;
|
|
38436
|
+
void this.evaluate(my, data, symbol);
|
|
38437
|
+
}, delay);
|
|
38438
|
+
}
|
|
38439
|
+
clearTimer() {
|
|
38440
|
+
if (this.timer != null) clearTimeout(this.timer);
|
|
38441
|
+
this.timer = null;
|
|
38442
|
+
}
|
|
38443
|
+
};
|
|
38444
|
+
|
|
38253
38445
|
// src/widget/watermark.ts
|
|
38254
38446
|
var MAX_FONT_PX = 36;
|
|
38255
38447
|
var MIN_FONT_PX = 12;
|
|
@@ -38763,7 +38955,10 @@ var ChartCell = class {
|
|
|
38763
38955
|
this.activeRangeId = null;
|
|
38764
38956
|
/** Latched verdict of {@link sessionAvailable} (async metadata, sticky per symbol). */
|
|
38765
38957
|
this.sessionAvailableFlag = false;
|
|
38766
|
-
/**
|
|
38958
|
+
/** Latched: the symbol's extended tape wraps midnight (an overnight roll market) —
|
|
38959
|
+
* one extended-hours shading phase instead of the pre/post split. */
|
|
38960
|
+
this.sessionOvernightFlag = false;
|
|
38961
|
+
/** Keeps the session shading on the symbol's real calendar (see {@link SessionShadingTracker}). */
|
|
38767
38962
|
this.sessionShading = new SessionShadingTracker((zones) => this.inner?.renderer.set("sessionZones", zones));
|
|
38768
38963
|
this.manifest = [];
|
|
38769
38964
|
/** A restored ledger's manifest entry NAMES, waiting for the manifest to resolve
|
|
@@ -39016,8 +39211,10 @@ var ChartCell = class {
|
|
|
39016
39211
|
void chart.data.symbolInfo(symbol).then((si) => {
|
|
39017
39212
|
if (this.inner !== chart) return;
|
|
39018
39213
|
const available = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
39019
|
-
|
|
39214
|
+
const overnight = parseSessionSpec(si)?.overnight === true;
|
|
39215
|
+
if (available !== this.sessionAvailableFlag || overnight !== this.sessionOvernightFlag) {
|
|
39020
39216
|
this.sessionAvailableFlag = available;
|
|
39217
|
+
this.sessionOvernightFlag = overnight;
|
|
39021
39218
|
this.deps.onMarketChanged(this.id);
|
|
39022
39219
|
this.pushSettingsSections();
|
|
39023
39220
|
}
|
|
@@ -39051,16 +39248,41 @@ var ChartCell = class {
|
|
|
39051
39248
|
/**
|
|
39052
39249
|
* (Re)contribute this cell's settings-dialog sections: status line parts, the
|
|
39053
39250
|
* per-cell fetch depth, the watermark toggle, and — only while the cell's symbol
|
|
39054
|
-
* HAS sessions — the Trading session group (RTH/ETH switch + the
|
|
39055
|
-
*
|
|
39056
|
-
*
|
|
39057
|
-
* a
|
|
39251
|
+
* HAS sessions — the Trading session group (RTH/ETH switch + the session shading
|
|
39252
|
+
* colors: pre/post-market on day-split tapes, one extended-hours swatch on
|
|
39253
|
+
* overnight roll markets) inside the Symbol tab. Bars/watermark/titles are
|
|
39254
|
+
* persistable cell state; a depth-only reload is silent, so mark dirty here.
|
|
39255
|
+
* Re-run whenever a gate changes (the dialog reads the sections on open).
|
|
39058
39256
|
*/
|
|
39059
39257
|
pushSettingsSections() {
|
|
39060
39258
|
const chart = this.inner;
|
|
39061
39259
|
if (!chart) return;
|
|
39062
39260
|
const rth = "Regular hours (RTH)";
|
|
39063
39261
|
const eth = "Extended hours (ETH)";
|
|
39262
|
+
const shadeRows = this.sessionOvernightFlag ? [
|
|
39263
|
+
{
|
|
39264
|
+
kind: "color",
|
|
39265
|
+
label: "Extended hours",
|
|
39266
|
+
id: "extended-color",
|
|
39267
|
+
get: () => this.sessionShadeColor("extendedColor"),
|
|
39268
|
+
set: (v) => this.setSessionShadeColor("extendedColor", v)
|
|
39269
|
+
}
|
|
39270
|
+
] : [
|
|
39271
|
+
{
|
|
39272
|
+
kind: "color",
|
|
39273
|
+
label: "Pre-market",
|
|
39274
|
+
id: "premarket-color",
|
|
39275
|
+
get: () => this.sessionShadeColor("premarketColor"),
|
|
39276
|
+
set: (v) => this.setSessionShadeColor("premarketColor", v)
|
|
39277
|
+
},
|
|
39278
|
+
{
|
|
39279
|
+
kind: "color",
|
|
39280
|
+
label: "Post-market",
|
|
39281
|
+
id: "postmarket-color",
|
|
39282
|
+
get: () => this.sessionShadeColor("postmarketColor"),
|
|
39283
|
+
set: (v) => this.setSessionShadeColor("postmarketColor", v)
|
|
39284
|
+
}
|
|
39285
|
+
];
|
|
39064
39286
|
const sessionSection = {
|
|
39065
39287
|
title: "Trading session",
|
|
39066
39288
|
id: "trading-session",
|
|
@@ -39074,20 +39296,7 @@ var ChartCell = class {
|
|
|
39074
39296
|
get: () => this.session === "extended" ? eth : rth,
|
|
39075
39297
|
set: (v) => this.setSession(v === eth ? "extended" : "regular")
|
|
39076
39298
|
},
|
|
39077
|
-
|
|
39078
|
-
kind: "color",
|
|
39079
|
-
label: "Pre-market",
|
|
39080
|
-
id: "premarket-color",
|
|
39081
|
-
get: () => this.sessionShadeColor("premarketColor"),
|
|
39082
|
-
set: (v) => this.setSessionShadeColor("premarketColor", v)
|
|
39083
|
-
},
|
|
39084
|
-
{
|
|
39085
|
-
kind: "color",
|
|
39086
|
-
label: "Post-market",
|
|
39087
|
-
id: "postmarket-color",
|
|
39088
|
-
get: () => this.sessionShadeColor("postmarketColor"),
|
|
39089
|
-
set: (v) => this.setSessionShadeColor("postmarketColor", v)
|
|
39090
|
-
}
|
|
39299
|
+
...shadeRows
|
|
39091
39300
|
]
|
|
39092
39301
|
};
|
|
39093
39302
|
const advanced = {
|
|
@@ -39331,6 +39540,10 @@ var ChartCell = class {
|
|
|
39331
39540
|
this.deps.activate(this.id);
|
|
39332
39541
|
this.inner?.renderer.focus();
|
|
39333
39542
|
}
|
|
39543
|
+
/** Raster of this cell's chart (same pixels as the PNG download), or null. */
|
|
39544
|
+
screenshotCanvas() {
|
|
39545
|
+
return this.inner?.renderer.screenshotCanvas() ?? null;
|
|
39546
|
+
}
|
|
39334
39547
|
/** Download this cell's chart as a PNG (named after its market). */
|
|
39335
39548
|
downloadScreenshot() {
|
|
39336
39549
|
const url = this.inner?.renderer.screenshot();
|
|
@@ -39943,6 +40156,54 @@ var SplitterLayer = class {
|
|
|
39943
40156
|
}
|
|
39944
40157
|
};
|
|
39945
40158
|
|
|
40159
|
+
// src/workspace/screenshot.ts
|
|
40160
|
+
function destRect(tile, dpr) {
|
|
40161
|
+
const dx = Math.round(tile.x * dpr);
|
|
40162
|
+
const dy = Math.round(tile.y * dpr);
|
|
40163
|
+
return {
|
|
40164
|
+
dx,
|
|
40165
|
+
dy,
|
|
40166
|
+
dw: Math.round((tile.x + tile.width) * dpr) - dx,
|
|
40167
|
+
dh: Math.round((tile.y + tile.height) * dpr) - dy
|
|
40168
|
+
};
|
|
40169
|
+
}
|
|
40170
|
+
function tilesFromCellRects(grid, cells) {
|
|
40171
|
+
if (grid.width <= 0 || grid.height <= 0) return [];
|
|
40172
|
+
const tiles = [];
|
|
40173
|
+
for (const c of cells) {
|
|
40174
|
+
if (c.hidden || c.width <= 0 || c.height <= 0) continue;
|
|
40175
|
+
tiles.push({ x: c.left - grid.left, y: c.top - grid.top, width: c.width, height: c.height });
|
|
40176
|
+
}
|
|
40177
|
+
return tiles;
|
|
40178
|
+
}
|
|
40179
|
+
function compositeLayoutScreenshot(doc, frame, tiles) {
|
|
40180
|
+
if (frame.width <= 0 || frame.height <= 0 || tiles.length === 0) return null;
|
|
40181
|
+
const dpr = frame.dpr > 0 ? frame.dpr : 1;
|
|
40182
|
+
const out = doc.createElement("canvas");
|
|
40183
|
+
out.width = Math.max(1, Math.round(frame.width * dpr));
|
|
40184
|
+
out.height = Math.max(1, Math.round(frame.height * dpr));
|
|
40185
|
+
const ctx = out.getContext("2d");
|
|
40186
|
+
if (!ctx) return null;
|
|
40187
|
+
ctx.fillStyle = frame.gapColor;
|
|
40188
|
+
ctx.fillRect(0, 0, out.width, out.height);
|
|
40189
|
+
let painted = 0;
|
|
40190
|
+
for (const tile of tiles) {
|
|
40191
|
+
if (tile.width <= 0 || tile.height <= 0) continue;
|
|
40192
|
+
const { dx, dy, dw, dh } = destRect(tile, dpr);
|
|
40193
|
+
if (dw <= 0 || dh <= 0) continue;
|
|
40194
|
+
ctx.drawImage(tile.source, dx, dy, dw, dh);
|
|
40195
|
+
painted += 1;
|
|
40196
|
+
}
|
|
40197
|
+
if (painted === 0) return null;
|
|
40198
|
+
return out.toDataURL("image/png");
|
|
40199
|
+
}
|
|
40200
|
+
function triggerPngDownload(doc, url, filename) {
|
|
40201
|
+
const a = doc.createElement("a");
|
|
40202
|
+
a.href = url;
|
|
40203
|
+
a.download = filename;
|
|
40204
|
+
a.click();
|
|
40205
|
+
}
|
|
40206
|
+
|
|
39946
40207
|
// src/workspace/VelaWorkspace.ts
|
|
39947
40208
|
var DEFAULT_TIMEFRAMES = ["1", "5", "15", "30", "60", "240", "D", "W", "M"];
|
|
39948
40209
|
var GAP_PX = 2;
|
|
@@ -40173,7 +40434,7 @@ var VelaWorkspace = class {
|
|
|
40173
40434
|
...picker ? { onIndicatorsClick: () => picker.open() } : {},
|
|
40174
40435
|
onUndoClick: () => this.active.history.undo(),
|
|
40175
40436
|
onRedoClick: () => this.active.history.redo(),
|
|
40176
|
-
onScreenshotClick: () => this.
|
|
40437
|
+
onScreenshotClick: () => this.downloadScreenshot(),
|
|
40177
40438
|
onAlertsClick: (anchor) => this.openAlertsMenu(anchor),
|
|
40178
40439
|
timeframe: "60",
|
|
40179
40440
|
timeframes: opts.timeframes ?? DEFAULT_TIMEFRAMES,
|
|
@@ -40232,6 +40493,7 @@ var VelaWorkspace = class {
|
|
|
40232
40493
|
this.dock.addBuiltIn({ id: "dataWindow", title: "Data window", icon: "datawindow", order: 10, panel: this.dataWindow, onChart: (c) => this.dataWindow.onChart(c) });
|
|
40233
40494
|
this.dock.addBuiltIn({ id: "objects", title: "Object tree", icon: "objects", order: 20, panel: this.objectTree, onChart: (c) => this.objectTree.onChart(c) });
|
|
40234
40495
|
this.dock.refresh();
|
|
40496
|
+
if (boot?.panels) this.dock.applyState(boot.panels);
|
|
40235
40497
|
this.root.appendChild(main);
|
|
40236
40498
|
this.toastHost = new Toast(this.gridEl);
|
|
40237
40499
|
const attribution = rendererDefaults().attribution;
|
|
@@ -40243,6 +40505,7 @@ var VelaWorkspace = class {
|
|
|
40243
40505
|
bottom: `calc(var(--vela-bottom-gutter, ${TIME_AXIS_H4}px) + 10px)`,
|
|
40244
40506
|
zIndex: "11"
|
|
40245
40507
|
});
|
|
40508
|
+
mark.dataset.velaScreenshot = "1";
|
|
40246
40509
|
this.gridEl.appendChild(mark);
|
|
40247
40510
|
this.attributionMark = mark;
|
|
40248
40511
|
}
|
|
@@ -40379,6 +40642,56 @@ var VelaWorkspace = class {
|
|
|
40379
40642
|
get chart() {
|
|
40380
40643
|
return this.active.chart;
|
|
40381
40644
|
}
|
|
40645
|
+
/**
|
|
40646
|
+
* PNG data URL of the visible layout: every live cell in its grid slot, or the
|
|
40647
|
+
* maximized cell alone. Same pixels the screenshot button downloads. A single
|
|
40648
|
+
* visible cell returns that cell's own chart export (the one-chart case).
|
|
40649
|
+
*/
|
|
40650
|
+
screenshot() {
|
|
40651
|
+
const cells = this.shotCells();
|
|
40652
|
+
if (cells.length === 0) return null;
|
|
40653
|
+
if (cells.length === 1) return cells[0].chart.renderer.screenshot();
|
|
40654
|
+
return this.compositeLayoutShot(cells);
|
|
40655
|
+
}
|
|
40656
|
+
/** Download {@link screenshot} as a PNG. A multi-cell layout is named
|
|
40657
|
+
* `vela-layout.png`; one visible cell keeps `${symbol}-${timeframe}.png`. */
|
|
40658
|
+
downloadScreenshot() {
|
|
40659
|
+
const cells = this.shotCells();
|
|
40660
|
+
if (cells.length <= 1) {
|
|
40661
|
+
(cells[0] ?? this.active).downloadScreenshot();
|
|
40662
|
+
return;
|
|
40663
|
+
}
|
|
40664
|
+
const url = this.compositeLayoutShot(cells);
|
|
40665
|
+
if (!url) return;
|
|
40666
|
+
triggerPngDownload(this.root.ownerDocument, url, "vela-layout.png");
|
|
40667
|
+
}
|
|
40668
|
+
/** Live cells the screenshot should include — maximized siblings are `hidden`. */
|
|
40669
|
+
shotCells() {
|
|
40670
|
+
return this.cells().filter((c) => c.host.style.visibility !== "hidden");
|
|
40671
|
+
}
|
|
40672
|
+
/** Place every cell's raster onto one canvas the size of the grid. */
|
|
40673
|
+
compositeLayoutShot(cells) {
|
|
40674
|
+
const gridEl = this.gridEl;
|
|
40675
|
+
const grid = gridEl.getBoundingClientRect();
|
|
40676
|
+
const win = gridEl.ownerDocument.defaultView;
|
|
40677
|
+
const dpr = win?.devicePixelRatio ?? 1;
|
|
40678
|
+
const gapColor = win ? win.getComputedStyle(gridEl).backgroundColor : "";
|
|
40679
|
+
const tiles = [];
|
|
40680
|
+
for (const cell of cells) {
|
|
40681
|
+
const source = cell.screenshotCanvas();
|
|
40682
|
+
if (!source) continue;
|
|
40683
|
+
const r = cell.host.getBoundingClientRect();
|
|
40684
|
+
const place = tilesFromCellRects(grid, [{ left: r.left, top: r.top, width: r.width, height: r.height }])[0];
|
|
40685
|
+
if (!place) continue;
|
|
40686
|
+
tiles.push({ source, ...place });
|
|
40687
|
+
}
|
|
40688
|
+
return compositeLayoutScreenshot(gridEl.ownerDocument, {
|
|
40689
|
+
width: grid.width,
|
|
40690
|
+
height: grid.height,
|
|
40691
|
+
dpr,
|
|
40692
|
+
gapColor: gapColor || "#000000"
|
|
40693
|
+
}, tiles);
|
|
40694
|
+
}
|
|
40382
40695
|
setActiveCell(id) {
|
|
40383
40696
|
if (id === this.activeId || this.destroyed) return;
|
|
40384
40697
|
const prev2 = this.activeId;
|
|
@@ -41425,7 +41738,7 @@ var VelaWorkspace = class {
|
|
|
41425
41738
|
this.moreDrawer ?? (this.moreDrawer = new MoreDrawer({
|
|
41426
41739
|
host: this.root,
|
|
41427
41740
|
...has("undo-redo") ? { onUndo: () => this.active.history.undo(), onRedo: () => this.active.history.redo() } : {},
|
|
41428
|
-
...has("screenshot") ? { onScreenshot: this.screenshotOverride ? () => this.runOverride(this.screenshotOverride) : () => this.
|
|
41741
|
+
...has("screenshot") ? { onScreenshot: this.screenshotOverride ? () => this.runOverride(this.screenshotOverride) : () => this.downloadScreenshot() } : {},
|
|
41429
41742
|
canUndo: () => this.active.history.canUndo,
|
|
41430
41743
|
canRedo: () => this.active.history.canRedo,
|
|
41431
41744
|
priceStyles: () => priceStyleIds().map((id) => ({ id, label: priceStyleLabel(id), icon: priceStyleIcon(id) })),
|
|
@@ -41526,9 +41839,9 @@ var VelaWorkspace = class {
|
|
|
41526
41839
|
this.keymap.register({
|
|
41527
41840
|
id: "chart.screenshot",
|
|
41528
41841
|
keys: "mod+alt+s",
|
|
41529
|
-
label: ov ? ov.label : "Download a
|
|
41842
|
+
label: ov ? ov.label : "Download a screenshot of the layout",
|
|
41530
41843
|
category: "Chart",
|
|
41531
|
-
run: ov ? () => this.runOverride(ov) : () => this.
|
|
41844
|
+
run: ov ? () => this.runOverride(ov) : () => this.downloadScreenshot()
|
|
41532
41845
|
});
|
|
41533
41846
|
}
|
|
41534
41847
|
this.keymap.register({ id: "chart.reset-view", keys: "alt+r", label: "Reset view (all history)", category: "Chart", run: () => this.active.chart.setVisibleRangePreset("ALL") });
|