@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/workspace.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"/>'));
|
|
@@ -4968,6 +4981,7 @@ var BASELINE_FILL_ALPHA_FAR = 0.05;
|
|
|
4968
4981
|
var BASELINE_LEVEL_DEFAULT = 50;
|
|
4969
4982
|
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
4970
4983
|
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
4984
|
+
var EXTENDED_SHADE = POSTMARKET_SHADE;
|
|
4971
4985
|
function defaultChartStyle() {
|
|
4972
4986
|
return {
|
|
4973
4987
|
chartTypes: {},
|
|
@@ -4976,7 +4990,7 @@ function defaultChartStyle() {
|
|
|
4976
4990
|
gridHorz: { visible: true, color: null },
|
|
4977
4991
|
borderColor: null,
|
|
4978
4992
|
separatorColor: null,
|
|
4979
|
-
crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground:
|
|
4993
|
+
crosshair: { color: CROSSHAIR, width: 1, style: "dashed", opacity: 0.4, labelBackground: CHIP_PLATE },
|
|
4980
4994
|
candle: {
|
|
4981
4995
|
bodyVisible: true,
|
|
4982
4996
|
borderVisible: false,
|
|
@@ -4999,7 +5013,7 @@ function defaultChartStyle() {
|
|
|
4999
5013
|
width: 2,
|
|
5000
5014
|
baselineLevel: BASELINE_LEVEL_DEFAULT
|
|
5001
5015
|
},
|
|
5002
|
-
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
|
|
5016
|
+
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE, extendedColor: EXTENDED_SHADE }
|
|
5003
5017
|
};
|
|
5004
5018
|
}
|
|
5005
5019
|
var LINE_STYLES = ["solid", "dashed", "dotted"];
|
|
@@ -5228,7 +5242,8 @@ function mergeConfig(base, patch) {
|
|
|
5228
5242
|
},
|
|
5229
5243
|
sessions: {
|
|
5230
5244
|
premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
|
|
5231
|
-
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
|
|
5245
|
+
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor,
|
|
5246
|
+
extendedColor: isColor(sessions.extendedColor) ? sessions.extendedColor : base.sessions.extendedColor
|
|
5232
5247
|
},
|
|
5233
5248
|
stacking: {
|
|
5234
5249
|
candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
|
|
@@ -12224,6 +12239,22 @@ var CSS5 = `
|
|
|
12224
12239
|
}
|
|
12225
12240
|
.vela-panel-close .vela-icon { width: 16px; height: 16px; }
|
|
12226
12241
|
.vela-panel-close:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12242
|
+
/* The pin: same footprint as the close button it sits beside; filled + bright once pinned. */
|
|
12243
|
+
.vela-panel-pin {
|
|
12244
|
+
all: unset;
|
|
12245
|
+
cursor: pointer;
|
|
12246
|
+
width: 26px;
|
|
12247
|
+
height: 26px;
|
|
12248
|
+
flex: none;
|
|
12249
|
+
display: inline-flex;
|
|
12250
|
+
align-items: center;
|
|
12251
|
+
justify-content: center;
|
|
12252
|
+
border-radius: 4px;
|
|
12253
|
+
color: var(--vela-fg-muted);
|
|
12254
|
+
}
|
|
12255
|
+
.vela-panel-pin .vela-icon { width: 15px; height: 15px; }
|
|
12256
|
+
.vela-panel-pin:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
12257
|
+
.vela-panel-pin[data-on='1'] { color: var(--vela-fg-bright); }
|
|
12227
12258
|
.vela-panel-body { flex: 1; overflow: auto; padding: 8px; }
|
|
12228
12259
|
.vela-panel-body::-webkit-scrollbar { width: 8px; }
|
|
12229
12260
|
.vela-panel-body::-webkit-scrollbar-thumb { background: var(--vela-scroll); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
|
|
@@ -12248,8 +12279,23 @@ var CSS5 = `
|
|
|
12248
12279
|
width: 1px;
|
|
12249
12280
|
background: transparent;
|
|
12250
12281
|
}
|
|
12282
|
+
/* The same hover ink as the workspace grid splitters and the canvas pane separators, so
|
|
12283
|
+
every draggable seam in the shell reads as one family. */
|
|
12251
12284
|
.vela-panel-resizer:hover::after,
|
|
12252
|
-
.vela-panel-resizer[data-dragging]::after { background: var(--vela-
|
|
12285
|
+
.vela-panel-resizer[data-dragging]::after { background: var(--vela-separator-hover-line); }
|
|
12286
|
+
/* An OVERLAY panel floats over the chart's right edge instead of taking a column: the
|
|
12287
|
+
chart keeps its width and layout (the dock host is position:relative \u2014 the anchoring the
|
|
12288
|
+
mobile rule below relies on too). Same z-order as that mobile overlay, above the chart's
|
|
12289
|
+
own DOM overlays; the shadow separates it from the plot it covers. */
|
|
12290
|
+
.vela-panel[data-overlay] {
|
|
12291
|
+
position: absolute;
|
|
12292
|
+
top: 0;
|
|
12293
|
+
right: 0;
|
|
12294
|
+
bottom: 0;
|
|
12295
|
+
max-width: 100%;
|
|
12296
|
+
z-index: 25;
|
|
12297
|
+
box-shadow: var(--vela-shadow);
|
|
12298
|
+
}
|
|
12253
12299
|
/* Mobile: a 280px column would crush a phone-width chart \u2014 the panel overlays the
|
|
12254
12300
|
chart area instead (its flex parent is position:relative), full-bleed, closed by
|
|
12255
12301
|
the same header \u2715. Width dragging is a pointer affordance; off on mobile. */
|
|
@@ -12280,6 +12326,10 @@ var SidePanel = class {
|
|
|
12280
12326
|
/** Notified when the USER settles a new width (drag release, or double-click reset) — never on
|
|
12281
12327
|
* a programmatic {@link setWidth}, so restoring a persisted width raises no change. */
|
|
12282
12328
|
this.onWidthChange = null;
|
|
12329
|
+
/** Notified when the USER pins or unpins a floating panel (`overlay` is the NEW placement) —
|
|
12330
|
+
* never on a programmatic {@link setOverlay}, for the same reason as widths. */
|
|
12331
|
+
this.onPlacementChange = null;
|
|
12332
|
+
this.pin = null;
|
|
12283
12333
|
const doc = host.ownerDocument;
|
|
12284
12334
|
injectStyles(STYLE_ID4, CSS5, doc);
|
|
12285
12335
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
@@ -12289,6 +12339,9 @@ var SidePanel = class {
|
|
|
12289
12339
|
this.el = doc.createElement("div");
|
|
12290
12340
|
this.el.className = `vela-panel ${modifier}`;
|
|
12291
12341
|
this.el.hidden = true;
|
|
12342
|
+
this.floatable = opts.overlay === true;
|
|
12343
|
+
this.overlayOn = this.floatable;
|
|
12344
|
+
if (this.overlayOn) this.el.dataset.overlay = "1";
|
|
12292
12345
|
this.el.style.setProperty("--vela-panel-w", `${this.widthPx}px`);
|
|
12293
12346
|
const header = doc.createElement("div");
|
|
12294
12347
|
header.className = "vela-panel-header";
|
|
@@ -12302,7 +12355,16 @@ var SidePanel = class {
|
|
|
12302
12355
|
close.appendChild(iconEl("close", doc));
|
|
12303
12356
|
close.title = "Close";
|
|
12304
12357
|
close.addEventListener("click", () => this.toggle(false));
|
|
12305
|
-
|
|
12358
|
+
if (this.floatable) {
|
|
12359
|
+
this.pin = doc.createElement("button");
|
|
12360
|
+
this.pin.className = "vela-panel-pin";
|
|
12361
|
+
this.pin.addEventListener("click", () => {
|
|
12362
|
+
this.setOverlay(!this.overlayOn);
|
|
12363
|
+
this.onPlacementChange?.(this.overlayOn);
|
|
12364
|
+
});
|
|
12365
|
+
this.refreshPin();
|
|
12366
|
+
}
|
|
12367
|
+
header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], close);
|
|
12306
12368
|
this.body = doc.createElement("div");
|
|
12307
12369
|
this.body.className = "vela-panel-body";
|
|
12308
12370
|
this.el.append(header, this.body);
|
|
@@ -12344,6 +12406,29 @@ var SidePanel = class {
|
|
|
12344
12406
|
this.widthPx = next;
|
|
12345
12407
|
this.el.style.setProperty("--vela-panel-w", `${next}px`);
|
|
12346
12408
|
}
|
|
12409
|
+
/** Whether the panel floats over the chart right now (false for every docked panel). */
|
|
12410
|
+
get overlay() {
|
|
12411
|
+
return this.overlayOn;
|
|
12412
|
+
}
|
|
12413
|
+
/** Float or dock the panel. Only a panel declared `overlay` can float — on any other this is
|
|
12414
|
+
* a no-op. Silent — {@link onPlacementChange} reports the user's pin clicks only. */
|
|
12415
|
+
setOverlay(overlay) {
|
|
12416
|
+
if (!this.floatable || overlay === this.overlayOn) return;
|
|
12417
|
+
this.overlayOn = overlay;
|
|
12418
|
+
if (overlay) this.el.dataset.overlay = "1";
|
|
12419
|
+
else delete this.el.dataset.overlay;
|
|
12420
|
+
this.refreshPin();
|
|
12421
|
+
}
|
|
12422
|
+
refreshPin() {
|
|
12423
|
+
if (!this.pin) return;
|
|
12424
|
+
const pinned = !this.overlayOn;
|
|
12425
|
+
const doc = this.pin.ownerDocument;
|
|
12426
|
+
this.pin.replaceChildren(iconEl(pinned ? "pin-filled" : "pin", doc));
|
|
12427
|
+
this.pin.dataset.on = pinned ? "1" : "0";
|
|
12428
|
+
this.pin.title = pinned ? "Unpin \u2014 float over the chart" : "Pin beside the chart";
|
|
12429
|
+
this.pin.setAttribute("aria-label", this.pin.title);
|
|
12430
|
+
this.pin.setAttribute("aria-pressed", pinned ? "true" : "false");
|
|
12431
|
+
}
|
|
12347
12432
|
destroy() {
|
|
12348
12433
|
this.el.remove();
|
|
12349
12434
|
}
|
|
@@ -13744,6 +13829,8 @@ var PanelDock = class {
|
|
|
13744
13829
|
this.entries = [];
|
|
13745
13830
|
/** Widths the USER settled, by panel id — the only ones worth persisting. */
|
|
13746
13831
|
this.widths = /* @__PURE__ */ new Map();
|
|
13832
|
+
/** Floating panels the USER pinned as columns, by id — remembered for late registrations too. */
|
|
13833
|
+
this.pinned = /* @__PURE__ */ new Set();
|
|
13747
13834
|
/** A restored `open` naming a panel that has not registered yet: honored when it docks. */
|
|
13748
13835
|
this.pendingOpen = null;
|
|
13749
13836
|
this.chart = null;
|
|
@@ -13766,7 +13853,8 @@ var PanelDock = class {
|
|
|
13766
13853
|
width: desc.width,
|
|
13767
13854
|
resizable: desc.resizable,
|
|
13768
13855
|
minWidth: desc.minWidth,
|
|
13769
|
-
maxWidth: desc.maxWidth
|
|
13856
|
+
maxWidth: desc.maxWidth,
|
|
13857
|
+
overlay: desc.overlay
|
|
13770
13858
|
});
|
|
13771
13859
|
const entry = {
|
|
13772
13860
|
id: desc.id,
|
|
@@ -13814,7 +13902,8 @@ var PanelDock = class {
|
|
|
13814
13902
|
const open2 = this.openId;
|
|
13815
13903
|
if (open2) out.open = open2;
|
|
13816
13904
|
if (this.widths.size > 0) out.widths = Object.fromEntries(this.widths);
|
|
13817
|
-
|
|
13905
|
+
if (this.pinned.size > 0) out.pinned = [...this.pinned];
|
|
13906
|
+
return out.open || out.widths || out.pinned ? out : null;
|
|
13818
13907
|
}
|
|
13819
13908
|
/**
|
|
13820
13909
|
* Restore a persisted dock state. Widths apply to any panel present, and are remembered for
|
|
@@ -13822,7 +13911,8 @@ var PanelDock = class {
|
|
|
13822
13911
|
* a document that predates the dock has no `panels` field at all, so the shell never calls
|
|
13823
13912
|
* this and the default (everything closed) stands. An `open` naming a panel that has not
|
|
13824
13913
|
* registered yet is held until it docks (a plugin loaded after the restore), unless the user
|
|
13825
|
-
* opens something in the meantime.
|
|
13914
|
+
* opens something in the meantime. `pinned` is the whole list of floating panels the user
|
|
13915
|
+
* docked — its absence means none is, so every floatable panel floats again.
|
|
13826
13916
|
*/
|
|
13827
13917
|
applyState(state) {
|
|
13828
13918
|
if (!state) return;
|
|
@@ -13832,6 +13922,8 @@ var PanelDock = class {
|
|
|
13832
13922
|
this.entries.find((e) => e.id === id)?.panel.setWidth(px);
|
|
13833
13923
|
}
|
|
13834
13924
|
}
|
|
13925
|
+
this.pinned = new Set(state.pinned ?? []);
|
|
13926
|
+
for (const entry of this.entries) entry.panel.setOverlay(!this.pinned.has(entry.id));
|
|
13835
13927
|
for (const entry of this.entries) entry.panel.toggle(entry.id === state.open);
|
|
13836
13928
|
this.pendingOpen = state.open && !this.entries.some((e) => e.id === state.open) ? state.open : null;
|
|
13837
13929
|
}
|
|
@@ -13845,6 +13937,7 @@ var PanelDock = class {
|
|
|
13845
13937
|
this.entries.sort((a, b) => a.order - b.order);
|
|
13846
13938
|
const stored = this.widths.get(entry.id);
|
|
13847
13939
|
if (stored !== void 0) entry.panel.setWidth(stored);
|
|
13940
|
+
if (this.pinned.has(entry.id)) entry.panel.setOverlay(false);
|
|
13848
13941
|
entry.panel.onOpenChange = (open2) => {
|
|
13849
13942
|
if (open2) {
|
|
13850
13943
|
for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
|
|
@@ -13858,6 +13951,11 @@ var PanelDock = class {
|
|
|
13858
13951
|
this.widths.set(entry.id, px);
|
|
13859
13952
|
this.deps.changed?.();
|
|
13860
13953
|
};
|
|
13954
|
+
entry.panel.onPlacementChange = (overlay) => {
|
|
13955
|
+
if (overlay) this.pinned.delete(entry.id);
|
|
13956
|
+
else this.pinned.add(entry.id);
|
|
13957
|
+
this.deps.changed?.();
|
|
13958
|
+
};
|
|
13861
13959
|
if (this.pendingOpen === entry.id) entry.panel.toggle(true);
|
|
13862
13960
|
if (!entry.contributed) this.publish();
|
|
13863
13961
|
}
|
|
@@ -13933,6 +14031,17 @@ function filterSymbols(list, query, limit = 100, top = TOP_TICKERS) {
|
|
|
13933
14031
|
}
|
|
13934
14032
|
var STYLE_ID7 = "vela-widget-symbolpicker";
|
|
13935
14033
|
var CSS8 = `
|
|
14034
|
+
/* Search + market tabs stay pinned while the result list scrolls underneath
|
|
14035
|
+
(mobile fullscreen: the dialog body is the scroller). Negative margin eats
|
|
14036
|
+
the body's padding so scrolled rows cannot peek around the island. */
|
|
14037
|
+
.vela-sp-sticky {
|
|
14038
|
+
position: sticky;
|
|
14039
|
+
top: 0;
|
|
14040
|
+
z-index: 1;
|
|
14041
|
+
background: var(--vela-surface);
|
|
14042
|
+
margin: calc(-1 * var(--vela-space-4)) calc(-1 * var(--vela-space-4)) 0;
|
|
14043
|
+
padding: var(--vela-space-4) var(--vela-space-4) 0;
|
|
14044
|
+
}
|
|
13936
14045
|
.vela-sp-searchrow {
|
|
13937
14046
|
display: flex;
|
|
13938
14047
|
align-items: center;
|
|
@@ -13952,8 +14061,10 @@ var CSS8 = `
|
|
|
13952
14061
|
border: none;
|
|
13953
14062
|
font-size: 14px;
|
|
13954
14063
|
outline: none;
|
|
14064
|
+
text-transform: uppercase;
|
|
13955
14065
|
}
|
|
13956
|
-
.vela-sp-
|
|
14066
|
+
.vela-sp-input::placeholder { text-transform: none; }
|
|
14067
|
+
.vela-sp-tabs { display: flex; gap: 14px; margin: 12px 2px 0; border-bottom: 1px solid var(--vela-border); padding-bottom: 8px; }
|
|
13957
14068
|
/* Mobile (fullscreen dialog): the asset-class strip scrolls sideways instead of
|
|
13958
14069
|
overflowing the body, and the result list stops capping itself \u2014 the body owns
|
|
13959
14070
|
the scrolling in the fullscreen presentation. */
|
|
@@ -14002,7 +14113,7 @@ var CSS8 = `
|
|
|
14002
14113
|
font-weight: 700;
|
|
14003
14114
|
}
|
|
14004
14115
|
.vela-sp-main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
|
|
14005
|
-
.vela-sp-ticker { font-weight: 700; color: var(--vela-fg-bright); font-size: 14px; }
|
|
14116
|
+
.vela-sp-ticker { font-weight: 700; color: var(--vela-fg-bright); font-size: 14px; text-transform: uppercase; }
|
|
14006
14117
|
.vela-sp-desc { color: var(--vela-fg-muted); font-size: 11px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
14007
14118
|
.vela-sp-badge {
|
|
14008
14119
|
flex: none;
|
|
@@ -14065,6 +14176,8 @@ var SymbolPicker = class {
|
|
|
14065
14176
|
searchRow.append(iconEl("search", doc), this.input);
|
|
14066
14177
|
this.tabs = doc.createElement("div");
|
|
14067
14178
|
this.tabs.className = "vela-sp-tabs";
|
|
14179
|
+
const sticky = doc.createElement("div");
|
|
14180
|
+
sticky.className = "vela-sp-sticky";
|
|
14068
14181
|
for (const t of ["All", "Stocks", "ETFs", "Crypto", "Futures", "Forex", "Commodities"]) {
|
|
14069
14182
|
const b = doc.createElement("button");
|
|
14070
14183
|
b.className = "vela-sp-tab";
|
|
@@ -14078,6 +14191,7 @@ var SymbolPicker = class {
|
|
|
14078
14191
|
});
|
|
14079
14192
|
this.tabs.appendChild(b);
|
|
14080
14193
|
}
|
|
14194
|
+
sticky.append(searchRow, this.tabs);
|
|
14081
14195
|
this.list = doc.createElement("div");
|
|
14082
14196
|
this.list.className = "vela-sp-list";
|
|
14083
14197
|
this.list.addEventListener("scroll", () => {
|
|
@@ -14090,21 +14204,32 @@ var SymbolPicker = class {
|
|
|
14090
14204
|
title: "Symbol Search",
|
|
14091
14205
|
host: opts.host,
|
|
14092
14206
|
closeOnInteractOutside: true,
|
|
14093
|
-
content: (body) => body.append(
|
|
14207
|
+
content: (body) => body.append(sticky, this.list),
|
|
14094
14208
|
onOpenChange: (open2) => {
|
|
14095
14209
|
if (open2) {
|
|
14096
|
-
this.input.value = this.seed;
|
|
14210
|
+
this.input.value = this.seed.toUpperCase();
|
|
14097
14211
|
this.seed = "";
|
|
14098
14212
|
this.refresh();
|
|
14099
14213
|
setTimeout(() => {
|
|
14100
14214
|
this.input.focus();
|
|
14101
14215
|
this.input.setSelectionRange(this.input.value.length, this.input.value.length);
|
|
14102
14216
|
}, 0);
|
|
14217
|
+
} else {
|
|
14218
|
+
this.input.blur();
|
|
14103
14219
|
}
|
|
14104
14220
|
opts.onOpenChange?.(open2);
|
|
14105
14221
|
}
|
|
14106
14222
|
});
|
|
14107
|
-
this.input.addEventListener("input", () =>
|
|
14223
|
+
this.input.addEventListener("input", () => {
|
|
14224
|
+
const next = this.input.value.toUpperCase();
|
|
14225
|
+
if (this.input.value !== next) {
|
|
14226
|
+
const start = this.input.selectionStart;
|
|
14227
|
+
const end = this.input.selectionEnd;
|
|
14228
|
+
this.input.value = next;
|
|
14229
|
+
if (start != null && end != null) this.input.setSelectionRange(start, end);
|
|
14230
|
+
}
|
|
14231
|
+
this.refresh();
|
|
14232
|
+
});
|
|
14108
14233
|
this.input.addEventListener("keydown", (e) => {
|
|
14109
14234
|
if (e.key === "ArrowDown") this.moveHighlight(1);
|
|
14110
14235
|
else if (e.key === "ArrowUp") this.moveHighlight(-1);
|
|
@@ -17724,6 +17849,7 @@ function createCustomMark(doc, html, background) {
|
|
|
17724
17849
|
});
|
|
17725
17850
|
el.style.color = attributionMarkColor(background);
|
|
17726
17851
|
el.innerHTML = html;
|
|
17852
|
+
el.dataset.velaScreenshot = "1";
|
|
17727
17853
|
return el;
|
|
17728
17854
|
}
|
|
17729
17855
|
function createAttributionMark(doc, background) {
|
|
@@ -17742,6 +17868,7 @@ function createAttributionMark(doc, background) {
|
|
|
17742
17868
|
cursor: "pointer"
|
|
17743
17869
|
});
|
|
17744
17870
|
applyAttributionMarkTheme(a, background);
|
|
17871
|
+
a.dataset.velaScreenshot = "1";
|
|
17745
17872
|
const symbol = doc.createElement("span");
|
|
17746
17873
|
symbol.className = "vela-attr-symbol";
|
|
17747
17874
|
symbol.setAttribute("aria-hidden", "true");
|
|
@@ -17910,7 +18037,11 @@ function sanitizePanels(raw) {
|
|
|
17910
18037
|
}
|
|
17911
18038
|
if (Object.keys(widths).length > 0) out.widths = widths;
|
|
17912
18039
|
}
|
|
17913
|
-
|
|
18040
|
+
if (Array.isArray(p.pinned)) {
|
|
18041
|
+
const pinned = p.pinned.filter((id) => typeof id === "string" && id !== "");
|
|
18042
|
+
if (pinned.length > 0) out.pinned = [...new Set(pinned)];
|
|
18043
|
+
}
|
|
18044
|
+
return out.open || out.widths || out.pinned ? out : null;
|
|
17914
18045
|
}
|
|
17915
18046
|
function sanitizeTrackSizes(raw) {
|
|
17916
18047
|
if (raw == null || typeof raw !== "object") return null;
|
|
@@ -20087,6 +20218,15 @@ var RendererControl = class {
|
|
|
20087
20218
|
console.warn(`[vela] renderer "${this.renderer.name}" does not support screenshot().`);
|
|
20088
20219
|
return null;
|
|
20089
20220
|
}
|
|
20221
|
+
/**
|
|
20222
|
+
* Raster of the current chart onto a canvas (same pixels as {@link screenshot}),
|
|
20223
|
+
* or null if the renderer has no canvas export. Silent — a host compositing
|
|
20224
|
+
* several charts skips a renderer that cannot contribute.
|
|
20225
|
+
*/
|
|
20226
|
+
screenshotCanvas() {
|
|
20227
|
+
if (typeof this.renderer.screenshotCanvas === "function") return this.renderer.screenshotCanvas();
|
|
20228
|
+
return null;
|
|
20229
|
+
}
|
|
20090
20230
|
/**
|
|
20091
20231
|
* The active renderer's full cosmetic config as a serializable, versioned JSON
|
|
20092
20232
|
* document — persist it (templates, saved settings) and feed it back to
|
|
@@ -22655,6 +22795,66 @@ function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
|
|
|
22655
22795
|
center: (wickLeftDev + wickDev / 2) / dpr
|
|
22656
22796
|
};
|
|
22657
22797
|
}
|
|
22798
|
+
function aggregateCandleColumns(bars, i0, i1, xOf, yOf) {
|
|
22799
|
+
const out = [];
|
|
22800
|
+
let col = NaN;
|
|
22801
|
+
let intervals = [];
|
|
22802
|
+
const flush = () => {
|
|
22803
|
+
if (intervals.length === 0) return;
|
|
22804
|
+
const merged = [intervals[0]];
|
|
22805
|
+
for (let k = 1; k < intervals.length; k += 1) {
|
|
22806
|
+
const prev2 = merged[merged.length - 1];
|
|
22807
|
+
const next = intervals[k];
|
|
22808
|
+
if (Math.abs(yOf(prev2.hi) - yOf(next.lo)) < 1) {
|
|
22809
|
+
prev2.hi = next.hi;
|
|
22810
|
+
if (next.headIdx < prev2.headIdx) {
|
|
22811
|
+
prev2.headIdx = next.headIdx;
|
|
22812
|
+
prev2.headTime = next.headTime;
|
|
22813
|
+
prev2.open = next.open;
|
|
22814
|
+
}
|
|
22815
|
+
if (next.lastIdx > prev2.lastIdx) {
|
|
22816
|
+
prev2.lastIdx = next.lastIdx;
|
|
22817
|
+
prev2.close = next.close;
|
|
22818
|
+
}
|
|
22819
|
+
} else {
|
|
22820
|
+
merged.push(next);
|
|
22821
|
+
}
|
|
22822
|
+
}
|
|
22823
|
+
for (const iv of merged) out.push({ x: col, hi: iv.hi, lo: iv.lo, headTime: iv.headTime, open: iv.open, close: iv.close });
|
|
22824
|
+
intervals = [];
|
|
22825
|
+
};
|
|
22826
|
+
for (let i = i0; i <= i1; i += 1) {
|
|
22827
|
+
const b = bars[i];
|
|
22828
|
+
if (!b || b.high <= b.low) continue;
|
|
22829
|
+
const x = Math.round(xOf(i));
|
|
22830
|
+
if (x !== col) {
|
|
22831
|
+
flush();
|
|
22832
|
+
col = x;
|
|
22833
|
+
}
|
|
22834
|
+
let lo = b.low;
|
|
22835
|
+
let hi = b.high;
|
|
22836
|
+
let headIdx = i;
|
|
22837
|
+
let headTime = b.time;
|
|
22838
|
+
let open2 = b.open;
|
|
22839
|
+
for (let k = intervals.length - 1; k >= 0; k -= 1) {
|
|
22840
|
+
const iv = intervals[k];
|
|
22841
|
+
if (iv.lo > hi || iv.hi < lo) continue;
|
|
22842
|
+
if (iv.lo < lo) lo = iv.lo;
|
|
22843
|
+
if (iv.hi > hi) hi = iv.hi;
|
|
22844
|
+
if (iv.headIdx < headIdx) {
|
|
22845
|
+
headIdx = iv.headIdx;
|
|
22846
|
+
headTime = iv.headTime;
|
|
22847
|
+
open2 = iv.open;
|
|
22848
|
+
}
|
|
22849
|
+
intervals.splice(k, 1);
|
|
22850
|
+
}
|
|
22851
|
+
let insertAt = 0;
|
|
22852
|
+
while (insertAt < intervals.length && intervals[insertAt].lo < lo) insertAt += 1;
|
|
22853
|
+
intervals.splice(insertAt, 0, { lo, hi, headIdx, headTime, open: open2, lastIdx: i, close: b.close });
|
|
22854
|
+
}
|
|
22855
|
+
flush();
|
|
22856
|
+
return out;
|
|
22857
|
+
}
|
|
22658
22858
|
|
|
22659
22859
|
// src/renderers/native/backend/gl/Batch.ts
|
|
22660
22860
|
var FLOATS_PER_VERT = 8;
|
|
@@ -23627,44 +23827,19 @@ var WebGL2Backend = class {
|
|
|
23627
23827
|
}
|
|
23628
23828
|
}
|
|
23629
23829
|
/**
|
|
23630
|
-
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated):
|
|
23631
|
-
*
|
|
23632
|
-
*
|
|
23830
|
+
* Sub-pixel LOD (mirrors Canvas2dBackend.drawCandlesAggregated): bars sharing a
|
|
23831
|
+
* rounded pixel column collapse into high-low sticks — one per contiguous
|
|
23832
|
+
* coverage run (see {@link aggregateCandleColumns}), so a price gap inside the
|
|
23833
|
+
* column stays a void. Draw cost stays bounded by screen width, not bar count;
|
|
23834
|
+
* stick color follows its own first-open→last-close.
|
|
23633
23835
|
*/
|
|
23634
23836
|
emitCandlesAggregated(b, bars, i0, i1, coords, pane, up, down, barColors) {
|
|
23635
|
-
|
|
23636
|
-
|
|
23637
|
-
|
|
23638
|
-
|
|
23639
|
-
|
|
23640
|
-
let headTime = 0;
|
|
23641
|
-
let active = false;
|
|
23642
|
-
const flush = () => {
|
|
23643
|
-
if (!active) return;
|
|
23644
|
-
const c = parseColor(barColors.get(headTime) ?? (closeV >= openV ? up : down));
|
|
23645
|
-
const hY = coords.priceToY(hi, pane.scale, pane.bounds);
|
|
23646
|
-
const lY = coords.priceToY(lo, pane.scale, pane.bounds);
|
|
23647
|
-
b.rect(px - 0.5, hY, 1, lY - hY, c);
|
|
23648
|
-
};
|
|
23649
|
-
for (let i = i0; i <= i1; i += 1) {
|
|
23650
|
-
const bar = bars[i];
|
|
23651
|
-
if (!bar || bar.high <= bar.low) continue;
|
|
23652
|
-
const x = Math.round(coords.logicalToX(i));
|
|
23653
|
-
if (!active || x !== px) {
|
|
23654
|
-
flush();
|
|
23655
|
-
px = x;
|
|
23656
|
-
lo = bar.low;
|
|
23657
|
-
hi = bar.high;
|
|
23658
|
-
openV = bar.open;
|
|
23659
|
-
headTime = bar.time;
|
|
23660
|
-
active = true;
|
|
23661
|
-
} else {
|
|
23662
|
-
if (bar.low < lo) lo = bar.low;
|
|
23663
|
-
if (bar.high > hi) hi = bar.high;
|
|
23664
|
-
}
|
|
23665
|
-
closeV = bar.close;
|
|
23837
|
+
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
23838
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
23839
|
+
const c = parseColor(barColors.get(s.headTime) ?? (s.close >= s.open ? up : down));
|
|
23840
|
+
const hY = yOf(s.hi);
|
|
23841
|
+
b.rect(s.x - 0.5, hY, 1, yOf(s.lo) - hY, c);
|
|
23666
23842
|
}
|
|
23667
|
-
flush();
|
|
23668
23843
|
}
|
|
23669
23844
|
emitSeries(b, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
23670
23845
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
@@ -24962,13 +25137,15 @@ var SceneGraph = class {
|
|
|
24962
25137
|
if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
|
|
24963
25138
|
return this.orderedCache;
|
|
24964
25139
|
}
|
|
24965
|
-
/** The session zones resolved into colored bands (pre/post-market
|
|
24966
|
-
* config's session colors) — consumed by the same painting path as
|
|
25140
|
+
/** The session zones resolved into colored bands (pre/post-market or extended-hours
|
|
25141
|
+
* washes from the config's session colors) — consumed by the same painting path as
|
|
25142
|
+
* {@link highlights}. */
|
|
24967
25143
|
sessionHighlightBands() {
|
|
24968
25144
|
if (!this.sessionZones) return [];
|
|
24969
25145
|
const out = [];
|
|
24970
25146
|
for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
|
|
24971
25147
|
for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
|
|
25148
|
+
for (const [from, to] of this.sessionZones.extended) out.push({ from, to, color: this.style.sessions.extendedColor });
|
|
24972
25149
|
return out;
|
|
24973
25150
|
}
|
|
24974
25151
|
indicatorsForPane(paneId) {
|
|
@@ -25507,50 +25684,23 @@ var Canvas2dBackend = class {
|
|
|
25507
25684
|
}
|
|
25508
25685
|
}
|
|
25509
25686
|
/**
|
|
25510
|
-
* Sub-pixel LOD:
|
|
25511
|
-
*
|
|
25512
|
-
*
|
|
25513
|
-
*
|
|
25687
|
+
* Sub-pixel LOD: bars sharing a rounded pixel column collapse into high-low
|
|
25688
|
+
* sticks (one per contiguous coverage run — see {@link aggregateCandleColumns}),
|
|
25689
|
+
* so draw cost is bounded by screen width (not bar count) when zoomed far out
|
|
25690
|
+
* and a price gap inside the column stays a void. Each stick's color follows
|
|
25691
|
+
* its first-open→last-close direction (a barcolor() on the head bar still wins).
|
|
25514
25692
|
*/
|
|
25515
25693
|
drawCandlesAggregated(ctx, bars, i0, i1, coords, pane, up, down, barColors) {
|
|
25516
|
-
|
|
25517
|
-
|
|
25518
|
-
|
|
25519
|
-
|
|
25520
|
-
|
|
25521
|
-
let headTime = 0;
|
|
25522
|
-
let active = false;
|
|
25523
|
-
const flush = () => {
|
|
25524
|
-
if (!active) return;
|
|
25525
|
-
const color = barColors.get(headTime) ?? (closeV >= openV ? up : down);
|
|
25526
|
-
const hY = coords.priceToY(hi, pane.scale, pane.bounds);
|
|
25527
|
-
const lY = coords.priceToY(lo, pane.scale, pane.bounds);
|
|
25528
|
-
ctx.strokeStyle = color;
|
|
25529
|
-
ctx.lineWidth = 1;
|
|
25694
|
+
const yOf = (price) => coords.priceToY(price, pane.scale, pane.bounds);
|
|
25695
|
+
ctx.lineWidth = 1;
|
|
25696
|
+
for (const s of aggregateCandleColumns(bars, i0, i1, (i) => coords.logicalToX(i), yOf)) {
|
|
25697
|
+
const x = s.x + 0.5;
|
|
25698
|
+
ctx.strokeStyle = barColors.get(s.headTime) ?? (s.close >= s.open ? up : down);
|
|
25530
25699
|
ctx.beginPath();
|
|
25531
|
-
ctx.moveTo(
|
|
25532
|
-
ctx.lineTo(
|
|
25700
|
+
ctx.moveTo(x, yOf(s.hi));
|
|
25701
|
+
ctx.lineTo(x, yOf(s.lo));
|
|
25533
25702
|
ctx.stroke();
|
|
25534
|
-
};
|
|
25535
|
-
for (let i = i0; i <= i1; i += 1) {
|
|
25536
|
-
const b = bars[i];
|
|
25537
|
-
if (!b || b.high <= b.low) continue;
|
|
25538
|
-
const x = Math.round(coords.logicalToX(i)) + 0.5;
|
|
25539
|
-
if (!active || x !== px) {
|
|
25540
|
-
flush();
|
|
25541
|
-
px = x;
|
|
25542
|
-
lo = b.low;
|
|
25543
|
-
hi = b.high;
|
|
25544
|
-
openV = b.open;
|
|
25545
|
-
headTime = b.time;
|
|
25546
|
-
active = true;
|
|
25547
|
-
} else {
|
|
25548
|
-
if (b.low < lo) lo = b.low;
|
|
25549
|
-
if (b.high > hi) hi = b.high;
|
|
25550
|
-
}
|
|
25551
|
-
closeV = b.close;
|
|
25552
25703
|
}
|
|
25553
|
-
flush();
|
|
25554
25704
|
}
|
|
25555
25705
|
drawSeries(ctx, spec, pane, coords, i0, i1, theme, off = 0) {
|
|
25556
25706
|
if (spec.kind === "candle" || spec.kind === "bar") {
|
|
@@ -26574,6 +26724,32 @@ var DrawingSceneRenderer = class {
|
|
|
26574
26724
|
}
|
|
26575
26725
|
};
|
|
26576
26726
|
|
|
26727
|
+
// src/renderers/native/chrome/tz.ts
|
|
26728
|
+
function tzOffsetMs(ms, timeZone) {
|
|
26729
|
+
if (!timeZone || timeZone === "UTC") return 0;
|
|
26730
|
+
try {
|
|
26731
|
+
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
26732
|
+
timeZone,
|
|
26733
|
+
hourCycle: "h23",
|
|
26734
|
+
year: "numeric",
|
|
26735
|
+
month: "2-digit",
|
|
26736
|
+
day: "2-digit",
|
|
26737
|
+
hour: "2-digit",
|
|
26738
|
+
minute: "2-digit",
|
|
26739
|
+
second: "2-digit"
|
|
26740
|
+
});
|
|
26741
|
+
const parts = dtf.formatToParts(new Date(ms));
|
|
26742
|
+
const get = (t) => Number(parts.find((p) => p.type === t)?.value);
|
|
26743
|
+
const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
|
|
26744
|
+
return asUTC - ms;
|
|
26745
|
+
} catch {
|
|
26746
|
+
return 0;
|
|
26747
|
+
}
|
|
26748
|
+
}
|
|
26749
|
+
function zonedDate(ms, timeZone) {
|
|
26750
|
+
return new Date(ms + tzOffsetMs(ms, timeZone));
|
|
26751
|
+
}
|
|
26752
|
+
|
|
26577
26753
|
// src/renderers/native/chrome/ticks.ts
|
|
26578
26754
|
function priceTicks(min, max, target = 6) {
|
|
26579
26755
|
if (!(max > min) || !Number.isFinite(min) || !Number.isFinite(max)) return [];
|
|
@@ -26721,7 +26897,14 @@ var STEP_LADDER = [
|
|
|
26721
26897
|
YEAR
|
|
26722
26898
|
];
|
|
26723
26899
|
var MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
|
|
26900
|
+
var WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
|
|
26724
26901
|
var pad2 = (n) => n < 10 ? `0${n}` : String(n);
|
|
26902
|
+
function formatTimeStamp(ms, timeZone, barIntervalMs) {
|
|
26903
|
+
const d = zonedDate(ms, timeZone);
|
|
26904
|
+
const date = `${WEEKDAYS[d.getUTCDay()]} ${d.getUTCDate()} ${MONTHS[d.getUTCMonth()]} '${pad2(d.getUTCFullYear() % 100)}`;
|
|
26905
|
+
if (barIntervalMs >= DAY) return date;
|
|
26906
|
+
return `${date} ${pad2(d.getUTCHours())}:${pad2(d.getUTCMinutes())}`;
|
|
26907
|
+
}
|
|
26725
26908
|
function pickStep(targetMs) {
|
|
26726
26909
|
for (const step of STEP_LADDER) if (step >= targetMs) return step;
|
|
26727
26910
|
return STEP_LADDER[STEP_LADDER.length - 1];
|
|
@@ -26763,32 +26946,6 @@ function timeTicks(fromMs, toMs, target = 8, offsetMs = 0) {
|
|
|
26763
26946
|
return out;
|
|
26764
26947
|
}
|
|
26765
26948
|
|
|
26766
|
-
// src/renderers/native/chrome/tz.ts
|
|
26767
|
-
function tzOffsetMs(ms, timeZone) {
|
|
26768
|
-
if (!timeZone || timeZone === "UTC") return 0;
|
|
26769
|
-
try {
|
|
26770
|
-
const dtf = new Intl.DateTimeFormat("en-US", {
|
|
26771
|
-
timeZone,
|
|
26772
|
-
hourCycle: "h23",
|
|
26773
|
-
year: "numeric",
|
|
26774
|
-
month: "2-digit",
|
|
26775
|
-
day: "2-digit",
|
|
26776
|
-
hour: "2-digit",
|
|
26777
|
-
minute: "2-digit",
|
|
26778
|
-
second: "2-digit"
|
|
26779
|
-
});
|
|
26780
|
-
const parts = dtf.formatToParts(new Date(ms));
|
|
26781
|
-
const get = (t) => Number(parts.find((p) => p.type === t)?.value);
|
|
26782
|
-
const asUTC = Date.UTC(get("year"), get("month") - 1, get("day"), get("hour") % 24, get("minute"), get("second"));
|
|
26783
|
-
return asUTC - ms;
|
|
26784
|
-
} catch {
|
|
26785
|
-
return 0;
|
|
26786
|
-
}
|
|
26787
|
-
}
|
|
26788
|
-
function zonedDate(ms, timeZone) {
|
|
26789
|
-
return new Date(ms + tzOffsetMs(ms, timeZone));
|
|
26790
|
-
}
|
|
26791
|
-
|
|
26792
26949
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
26793
26950
|
var ChromeRenderer = class {
|
|
26794
26951
|
constructor() {
|
|
@@ -27235,7 +27392,7 @@ var CrosshairRenderer = class {
|
|
|
27235
27392
|
const price = coords.yToPrice(ch.y, pane.scale, pane.bounds);
|
|
27236
27393
|
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);
|
|
27237
27394
|
}
|
|
27238
|
-
this.chip(ctx, x, dataH + 1,
|
|
27395
|
+
this.chip(ctx, x, dataH + 1, formatTimeStamp(coords.logicalToTime(logical), scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
27239
27396
|
}
|
|
27240
27397
|
destroy() {
|
|
27241
27398
|
this.canvas = null;
|
|
@@ -27283,7 +27440,7 @@ var CrosshairRenderer = class {
|
|
|
27283
27440
|
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);
|
|
27284
27441
|
}
|
|
27285
27442
|
}
|
|
27286
|
-
this.chip(ctx, x, dataH + 1,
|
|
27443
|
+
this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
|
|
27287
27444
|
ctx.globalAlpha = 1;
|
|
27288
27445
|
}
|
|
27289
27446
|
/** A soft band + a brighter crisp center line over the hovered separator, so it reads as
|
|
@@ -27316,11 +27473,6 @@ function setDash3(ctx, style) {
|
|
|
27316
27473
|
else if (style === "dotted") ctx.setLineDash([2, 3]);
|
|
27317
27474
|
else ctx.setLineDash([]);
|
|
27318
27475
|
}
|
|
27319
|
-
function formatStamp(ms, timeZone) {
|
|
27320
|
-
const d = zonedDate(ms, timeZone);
|
|
27321
|
-
const pad = (n) => n < 10 ? `0${n}` : String(n);
|
|
27322
|
-
return `${pad(d.getUTCMonth() + 1)}-${pad(d.getUTCDate())} ${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}`;
|
|
27323
|
-
}
|
|
27324
27476
|
|
|
27325
27477
|
// src/renderers/native/chrome/settings-visibility.ts
|
|
27326
27478
|
function settingsIdSlug(label) {
|
|
@@ -33087,6 +33239,9 @@ function clipHighlightRect(x1, x2, left, right) {
|
|
|
33087
33239
|
const end = Math.min(right, x2);
|
|
33088
33240
|
return end > x ? { x, width: end - x } : null;
|
|
33089
33241
|
}
|
|
33242
|
+
function bandEdgeSlot(logical) {
|
|
33243
|
+
return Math.ceil(logical) - 0.5;
|
|
33244
|
+
}
|
|
33090
33245
|
var BackdropRenderer = class {
|
|
33091
33246
|
constructor() {
|
|
33092
33247
|
this.canvas = null;
|
|
@@ -33117,21 +33272,22 @@ var BackdropRenderer = class {
|
|
|
33117
33272
|
this.drawGrid(ctx, scene, coords, theme, coords.width, gridAlpha);
|
|
33118
33273
|
}
|
|
33119
33274
|
/** Renderer-owned session highlight bands: full-height (all panes), behind the grid.
|
|
33120
|
-
* Session-zone washes
|
|
33275
|
+
* Session-zone washes paint first (edges snapped to bar-slot boundaries so a band
|
|
33276
|
+
* never cuts a candle in two), host highlights on top (continuous time mapping —
|
|
33277
|
+
* hosts may paint sub-bar spans). */
|
|
33121
33278
|
drawHighlights(ctx, scene, coords) {
|
|
33122
33279
|
const sessions = scene.sessionHighlightBands();
|
|
33123
33280
|
if (sessions.length > 0) {
|
|
33124
33281
|
const left = Math.max(0, coords.logicalToX(-0.5));
|
|
33125
33282
|
const right = Math.min(coords.width, coords.logicalToX(coords.barCount - 0.5));
|
|
33126
|
-
this.drawHighlightSet(ctx, sessions, coords, left, right);
|
|
33283
|
+
this.drawHighlightSet(ctx, sessions, coords, left, right, true);
|
|
33127
33284
|
}
|
|
33128
|
-
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width);
|
|
33285
|
+
this.drawHighlightSet(ctx, scene.highlights, coords, 0, coords.width, false);
|
|
33129
33286
|
}
|
|
33130
|
-
drawHighlightSet(ctx, bands, coords, left, right) {
|
|
33287
|
+
drawHighlightSet(ctx, bands, coords, left, right, snapToSlots) {
|
|
33288
|
+
const edgeX = (ms) => snapToSlots ? coords.logicalToX(bandEdgeSlot(coords.timeToLogical(ms))) : coords.timeToX(ms);
|
|
33131
33289
|
for (const band of bands) {
|
|
33132
|
-
const
|
|
33133
|
-
const x2 = coords.timeToX(band.to);
|
|
33134
|
-
const rect = clipHighlightRect(x1, x2, left, right);
|
|
33290
|
+
const rect = clipHighlightRect(edgeX(band.from), edgeX(band.to), left, right);
|
|
33135
33291
|
if (!rect) continue;
|
|
33136
33292
|
ctx.fillStyle = band.color;
|
|
33137
33293
|
ctx.fillRect(rect.x, 0, rect.width, coords.height);
|
|
@@ -34278,7 +34434,7 @@ var NativeRenderer = class {
|
|
|
34278
34434
|
};
|
|
34279
34435
|
})(),
|
|
34280
34436
|
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
|
|
34281
|
-
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
|
|
34437
|
+
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor, extendedColor: s.sessions.extendedColor }
|
|
34282
34438
|
};
|
|
34283
34439
|
}
|
|
34284
34440
|
/**
|
|
@@ -34360,7 +34516,7 @@ var NativeRenderer = class {
|
|
|
34360
34516
|
width: next.baseline.width,
|
|
34361
34517
|
baselineLevel: next.baseline.baselineLevel
|
|
34362
34518
|
};
|
|
34363
|
-
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
|
|
34519
|
+
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor, extendedColor: next.sessions.extendedColor };
|
|
34364
34520
|
this.setPriceStyle(next.series.style);
|
|
34365
34521
|
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
34366
34522
|
this.scene.baselineValue = next.series.baseline;
|
|
@@ -34582,9 +34738,10 @@ var NativeRenderer = class {
|
|
|
34582
34738
|
* opts in with a `data-vela-screenshot` attribute on the mount container's
|
|
34583
34739
|
* subtree (the widget marks its status line, and its symbol watermark with
|
|
34584
34740
|
* `"under"` — drawn beneath the canvases, where it sits on screen). Only the
|
|
34585
|
-
* crosshair (L2) is intentionally excluded.
|
|
34741
|
+
* crosshair (L2) is intentionally excluded. {@link screenshot} is this canvas
|
|
34742
|
+
* as a PNG data URL.
|
|
34586
34743
|
*/
|
|
34587
|
-
|
|
34744
|
+
screenshotCanvas() {
|
|
34588
34745
|
if (!this.dataCanvas) return null;
|
|
34589
34746
|
this.computeScales();
|
|
34590
34747
|
this.paintData();
|
|
@@ -34612,7 +34769,10 @@ var NativeRenderer = class {
|
|
|
34612
34769
|
if (el.getAttribute("data-vela-screenshot") !== "under") rasterizeOverlay(ctx, el, frame);
|
|
34613
34770
|
}
|
|
34614
34771
|
}
|
|
34615
|
-
return out
|
|
34772
|
+
return out;
|
|
34773
|
+
}
|
|
34774
|
+
screenshot() {
|
|
34775
|
+
return this.screenshotCanvas()?.toDataURL("image/png") ?? null;
|
|
34616
34776
|
}
|
|
34617
34777
|
/**
|
|
34618
34778
|
* The candles-first-appear reveal — animates the **candles themselves** (no extra
|
|
@@ -36007,7 +36167,7 @@ var NativeRenderer = class {
|
|
|
36007
36167
|
const pricePane = this.dataWindowPricePane();
|
|
36008
36168
|
const ohlc = this.dataWindowOHLC(bar, pricePane);
|
|
36009
36169
|
const parts = [];
|
|
36010
|
-
if (bar) parts.push(
|
|
36170
|
+
if (bar) parts.push(formatStamp(bar.time, this.scene.timezone));
|
|
36011
36171
|
if (ohlc) parts.push(`Open ${ohlc.o}, High ${ohlc.h}, Low ${ohlc.l}, Close ${ohlc.c}`);
|
|
36012
36172
|
for (const group of this.dataWindowGroups(idx, pricePane)) {
|
|
36013
36173
|
for (const row of group.rows) parts.push(`${row.label} ${row.value}`);
|
|
@@ -36894,7 +37054,7 @@ function formatStampParts(ms, timeZone) {
|
|
|
36894
37054
|
time: `${p(d.getUTCHours())}:${p(d.getUTCMinutes())}`
|
|
36895
37055
|
};
|
|
36896
37056
|
}
|
|
36897
|
-
function
|
|
37057
|
+
function formatStamp(ms, timeZone) {
|
|
36898
37058
|
const { date, time } = formatStampParts(ms, timeZone);
|
|
36899
37059
|
return `${date} ${time}`;
|
|
36900
37060
|
}
|
|
@@ -36925,7 +37085,7 @@ function sanitizeSessionZones(value) {
|
|
|
36925
37085
|
}
|
|
36926
37086
|
return out.sort((a, b) => a[0] - b[0]);
|
|
36927
37087
|
};
|
|
36928
|
-
return { pre: windows(v.pre), post: windows(v.post) };
|
|
37088
|
+
return { pre: windows(v.pre), post: windows(v.post), extended: windows(v.extended) };
|
|
36929
37089
|
}
|
|
36930
37090
|
function clamp012(v) {
|
|
36931
37091
|
return Math.max(0, Math.min(1, v));
|
|
@@ -37614,6 +37774,7 @@ var MARKET_LABELS = {
|
|
|
37614
37774
|
open: "Market Open",
|
|
37615
37775
|
pre: "Pre-Market",
|
|
37616
37776
|
post: "Post-Market",
|
|
37777
|
+
extended: "Extended Hours",
|
|
37617
37778
|
closed: "Market Closed",
|
|
37618
37779
|
holiday: "Market Holiday"
|
|
37619
37780
|
};
|
|
@@ -37621,6 +37782,7 @@ var MARKET_INKS = {
|
|
|
37621
37782
|
open: "var(--vela-up)",
|
|
37622
37783
|
pre: SESSION_PRE,
|
|
37623
37784
|
post: SESSION_POST,
|
|
37785
|
+
extended: SESSION_POST,
|
|
37624
37786
|
closed: SESSION_OFF,
|
|
37625
37787
|
holiday: SESSION_OFF
|
|
37626
37788
|
};
|
|
@@ -37934,118 +38096,17 @@ var Statusline = class {
|
|
|
37934
38096
|
}
|
|
37935
38097
|
};
|
|
37936
38098
|
|
|
37937
|
-
// src/widget/market-status.ts
|
|
37938
|
-
function civilDate(ms, tz) {
|
|
37939
|
-
try {
|
|
37940
|
-
return new Intl.DateTimeFormat("en-CA", { timeZone: tz }).format(ms);
|
|
37941
|
-
} catch {
|
|
37942
|
-
return "";
|
|
37943
|
-
}
|
|
37944
|
-
}
|
|
37945
|
-
function isWeekday(ms, tz) {
|
|
37946
|
-
try {
|
|
37947
|
-
const wd = new Intl.DateTimeFormat("en-US", { timeZone: tz, weekday: "short" }).format(ms);
|
|
37948
|
-
return wd !== "Sat" && wd !== "Sun";
|
|
37949
|
-
} catch {
|
|
37950
|
-
return false;
|
|
37951
|
-
}
|
|
37952
|
-
}
|
|
37953
|
-
function deriveMarketStatus(now, w, tz) {
|
|
37954
|
-
const within = (ws) => ws.find(([s, e]) => now >= s && now < e);
|
|
37955
|
-
if (within(w.regular)) return "open";
|
|
37956
|
-
const ext = within(w.extended);
|
|
37957
|
-
if (ext) return w.regular.some(([s]) => s >= now && s < ext[1]) ? "pre" : "post";
|
|
37958
|
-
if (isWeekday(now, tz)) {
|
|
37959
|
-
const today = civilDate(now, tz);
|
|
37960
|
-
if (today !== "" && !w.extended.some(([s]) => civilDate(s, tz) === today)) return "holiday";
|
|
37961
|
-
}
|
|
37962
|
-
return "closed";
|
|
37963
|
-
}
|
|
37964
|
-
function nextStatusBoundary(now, w) {
|
|
37965
|
-
let next = null;
|
|
37966
|
-
for (const ws of [w.regular, w.extended]) {
|
|
37967
|
-
for (const [s, e] of ws) {
|
|
37968
|
-
for (const t of [s, e]) {
|
|
37969
|
-
if (t > now && (next == null || t < next)) next = t;
|
|
37970
|
-
}
|
|
37971
|
-
}
|
|
37972
|
-
}
|
|
37973
|
-
return next;
|
|
37974
|
-
}
|
|
37975
|
-
var FETCH_BACK_MS = 4 * 864e5;
|
|
37976
|
-
var FETCH_AHEAD_MS = 10 * 864e5;
|
|
37977
|
-
var MIN_TIMER_MS = 15e3;
|
|
37978
|
-
var MAX_TIMER_MS = 36e5;
|
|
37979
|
-
var RETRY_MS2 = 6e4;
|
|
37980
|
-
var MarketStatusTracker = class {
|
|
37981
|
-
constructor(onStatus) {
|
|
37982
|
-
this.onStatus = onStatus;
|
|
37983
|
-
/** Invalidates detached async work — bumped by every track()/stop(). */
|
|
37984
|
-
this.epoch = 0;
|
|
37985
|
-
this.timer = null;
|
|
37986
|
-
}
|
|
37987
|
-
/** (Re)bind to a chart's data surface + symbol and start evaluating. */
|
|
37988
|
-
track(data, symbol) {
|
|
37989
|
-
const my = ++this.epoch;
|
|
37990
|
-
this.clearTimer();
|
|
37991
|
-
void this.evaluate(my, data, symbol);
|
|
37992
|
-
}
|
|
37993
|
-
stop() {
|
|
37994
|
-
this.epoch += 1;
|
|
37995
|
-
this.clearTimer();
|
|
37996
|
-
}
|
|
37997
|
-
async evaluate(my, data, symbol) {
|
|
37998
|
-
const resolved = data.resolve(symbol);
|
|
37999
|
-
const provider = resolved ? data.providerInstance(resolved.provider) : void 0;
|
|
38000
|
-
const si = await data.symbolInfo(symbol).catch(() => void 0);
|
|
38001
|
-
if (my !== this.epoch) return;
|
|
38002
|
-
const hasSessions = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
38003
|
-
if (!provider?.getCalendar || !hasSessions || !resolved) {
|
|
38004
|
-
this.onStatus("open");
|
|
38005
|
-
return;
|
|
38006
|
-
}
|
|
38007
|
-
const tz = typeof si?.timezone === "string" && si.timezone !== "" ? si.timezone : "Etc/UTC";
|
|
38008
|
-
const now = Date.now();
|
|
38009
|
-
const range = { from: now - FETCH_BACK_MS, to: now + FETCH_AHEAD_MS };
|
|
38010
|
-
const [regular, extended] = await Promise.all([
|
|
38011
|
-
provider.getCalendar(resolved.ticker, { ...range, session: "regular" }).catch(() => null),
|
|
38012
|
-
provider.getCalendar(resolved.ticker, { ...range, session: "extended" }).catch(() => null)
|
|
38013
|
-
]);
|
|
38014
|
-
if (my !== this.epoch) return;
|
|
38015
|
-
if (!regular || !extended) {
|
|
38016
|
-
this.arm(my, data, symbol, RETRY_MS2);
|
|
38017
|
-
return;
|
|
38018
|
-
}
|
|
38019
|
-
const w = { regular, extended };
|
|
38020
|
-
this.onStatus(deriveMarketStatus(now, w, tz));
|
|
38021
|
-
const boundary = nextStatusBoundary(now, w);
|
|
38022
|
-
const delay = Math.min(boundary != null ? boundary - now : MAX_TIMER_MS, MAX_TIMER_MS);
|
|
38023
|
-
this.arm(my, data, symbol, Math.max(delay, MIN_TIMER_MS));
|
|
38024
|
-
}
|
|
38025
|
-
arm(my, data, symbol, delay) {
|
|
38026
|
-
this.clearTimer();
|
|
38027
|
-
this.timer = setTimeout(() => {
|
|
38028
|
-
this.timer = null;
|
|
38029
|
-
if (my !== this.epoch) return;
|
|
38030
|
-
void this.evaluate(my, data, symbol);
|
|
38031
|
-
}, delay);
|
|
38032
|
-
}
|
|
38033
|
-
clearTimer() {
|
|
38034
|
-
if (this.timer != null) clearTimeout(this.timer);
|
|
38035
|
-
this.timer = null;
|
|
38036
|
-
}
|
|
38037
|
-
};
|
|
38038
|
-
|
|
38039
38099
|
// src/widget/session-shading.ts
|
|
38040
38100
|
var DAY_MS2 = 864e5;
|
|
38041
38101
|
var MINUTE_MS = 6e4;
|
|
38042
|
-
function parseWindow(text) {
|
|
38102
|
+
function parseWindow(text, allowOvernight = false) {
|
|
38043
38103
|
if (typeof text !== "string") return null;
|
|
38044
38104
|
const m = /^(\d{2})(\d{2})-(\d{2})(\d{2})$/.exec(text);
|
|
38045
38105
|
if (!m) return null;
|
|
38046
38106
|
const start = Number(m[1]) * 60 + Number(m[2]);
|
|
38047
38107
|
const end = Number(m[3]) * 60 + Number(m[4]);
|
|
38048
|
-
if (
|
|
38108
|
+
if (end > 1440 || start >= 1440) return null;
|
|
38109
|
+
if (start >= end && !allowOvernight) return null;
|
|
38049
38110
|
return { start, end };
|
|
38050
38111
|
}
|
|
38051
38112
|
function parseSessionSpec(si) {
|
|
@@ -38055,9 +38116,13 @@ function parseSessionSpec(si) {
|
|
|
38055
38116
|
if (!regular) return null;
|
|
38056
38117
|
const tz = si?.["timezone"];
|
|
38057
38118
|
const timezone = typeof tz === "string" && tz !== "" ? tz : "Etc/UTC";
|
|
38058
|
-
const ext = parseWindow(si?.["session_extended"]);
|
|
38119
|
+
const ext = parseWindow(si?.["session_extended"], true);
|
|
38120
|
+
if (ext && ext.start >= ext.end) {
|
|
38121
|
+
if (ext.start >= regular.end && ext.end >= regular.end) return { regular, extended: ext, overnight: true, timezone };
|
|
38122
|
+
return { regular, extended: { start: 0, end: 1440 }, overnight: false, timezone };
|
|
38123
|
+
}
|
|
38059
38124
|
const extended = ext && ext.start <= regular.start && ext.end >= regular.end ? ext : { start: 0, end: 1440 };
|
|
38060
|
-
return { regular, extended, timezone };
|
|
38125
|
+
return { regular, extended, overnight: false, timezone };
|
|
38061
38126
|
}
|
|
38062
38127
|
var dtfCache = /* @__PURE__ */ new Map();
|
|
38063
38128
|
function civilFormatter(tz) {
|
|
@@ -38093,27 +38158,48 @@ function civilParts(dtf, ms) {
|
|
|
38093
38158
|
minute: Number(get("minute"))
|
|
38094
38159
|
};
|
|
38095
38160
|
}
|
|
38161
|
+
function mergeAbutting(bands) {
|
|
38162
|
+
const sorted = [...bands].sort((a, b) => a[0] - b[0]);
|
|
38163
|
+
const out = [];
|
|
38164
|
+
for (const [s, e] of sorted) {
|
|
38165
|
+
if (e <= s) continue;
|
|
38166
|
+
const last = out[out.length - 1];
|
|
38167
|
+
if (last && s <= last[1]) last[1] = Math.max(last[1], e);
|
|
38168
|
+
else out.push([s, e]);
|
|
38169
|
+
}
|
|
38170
|
+
return out;
|
|
38171
|
+
}
|
|
38096
38172
|
function expandSessionZones(spec, from, to) {
|
|
38097
38173
|
const pre = [];
|
|
38098
38174
|
const post = [];
|
|
38175
|
+
const extended = [];
|
|
38099
38176
|
const dtf = civilFormatter(spec.timezone);
|
|
38100
|
-
if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post };
|
|
38177
|
+
if (!dtf || !Number.isFinite(from) || !Number.isFinite(to)) return { pre, post, extended };
|
|
38101
38178
|
const seen = /* @__PURE__ */ new Set();
|
|
38102
38179
|
for (let cursor = from - DAY_MS2; cursor < to + DAY_MS2; cursor += DAY_MS2) {
|
|
38103
38180
|
const civil = civilParts(dtf, cursor);
|
|
38104
38181
|
const key = civil.year * 1e4 + civil.month * 100 + civil.day;
|
|
38105
38182
|
if (!Number.isFinite(key) || seen.has(key)) continue;
|
|
38106
38183
|
seen.add(key);
|
|
38107
|
-
|
|
38184
|
+
const sunday = civil.weekday === "Sun";
|
|
38185
|
+
if (civil.weekday === "Sat" || sunday && !spec.overnight) continue;
|
|
38108
38186
|
const naive = Date.UTC(civil.year, civil.month - 1, civil.day);
|
|
38109
38187
|
const noonGuess = naive + 720 * MINUTE_MS;
|
|
38110
38188
|
const atNoon = civilParts(dtf, noonGuess);
|
|
38111
38189
|
const offset = Date.UTC(atNoon.year, atNoon.month - 1, atNoon.day, atNoon.hour, atNoon.minute) - noonGuess;
|
|
38112
38190
|
const at = (minutes) => naive + minutes * MINUTE_MS - offset;
|
|
38191
|
+
if (spec.overnight) {
|
|
38192
|
+
if (!sunday) {
|
|
38193
|
+
extended.push([at(0), at(spec.regular.start)]);
|
|
38194
|
+
if (spec.regular.end < spec.extended.end) extended.push([at(spec.regular.end), at(spec.extended.end)]);
|
|
38195
|
+
}
|
|
38196
|
+
if (civil.weekday !== "Fri") extended.push([at(spec.extended.start), at(1440)]);
|
|
38197
|
+
continue;
|
|
38198
|
+
}
|
|
38113
38199
|
if (spec.extended.start < spec.regular.start) pre.push([at(spec.extended.start), at(spec.regular.start)]);
|
|
38114
38200
|
if (spec.regular.end < spec.extended.end) post.push([at(spec.regular.end), at(spec.extended.end)]);
|
|
38115
38201
|
}
|
|
38116
|
-
return { pre, post };
|
|
38202
|
+
return { pre, post, extended: mergeAbutting(extended) };
|
|
38117
38203
|
}
|
|
38118
38204
|
var COVER_PAD_MIN_MS = 2 * DAY_MS2;
|
|
38119
38205
|
var SessionShadingTracker = class {
|
|
@@ -38168,7 +38254,7 @@ var SessionShadingTracker = class {
|
|
|
38168
38254
|
return;
|
|
38169
38255
|
}
|
|
38170
38256
|
if (this.session !== "extended" || !this.intraday) {
|
|
38171
|
-
if (force) this.onZones({ pre: [], post: [] });
|
|
38257
|
+
if (force) this.onZones({ pre: [], post: [], extended: [] });
|
|
38172
38258
|
return;
|
|
38173
38259
|
}
|
|
38174
38260
|
const from = Math.min(range.from, range.to);
|
|
@@ -38182,6 +38268,112 @@ var SessionShadingTracker = class {
|
|
|
38182
38268
|
}
|
|
38183
38269
|
};
|
|
38184
38270
|
|
|
38271
|
+
// src/widget/market-status.ts
|
|
38272
|
+
function civilDate(ms, tz) {
|
|
38273
|
+
try {
|
|
38274
|
+
return new Intl.DateTimeFormat("en-CA", { timeZone: tz }).format(ms);
|
|
38275
|
+
} catch {
|
|
38276
|
+
return "";
|
|
38277
|
+
}
|
|
38278
|
+
}
|
|
38279
|
+
function isWeekday(ms, tz) {
|
|
38280
|
+
try {
|
|
38281
|
+
const wd = new Intl.DateTimeFormat("en-US", { timeZone: tz, weekday: "short" }).format(ms);
|
|
38282
|
+
return wd !== "Sat" && wd !== "Sun";
|
|
38283
|
+
} catch {
|
|
38284
|
+
return false;
|
|
38285
|
+
}
|
|
38286
|
+
}
|
|
38287
|
+
function deriveMarketStatus(now, w, tz, overnight = false) {
|
|
38288
|
+
const within = (ws) => ws.find(([s, e]) => now >= s && now < e);
|
|
38289
|
+
if (within(w.regular)) return "open";
|
|
38290
|
+
const ext = within(w.extended);
|
|
38291
|
+
if (ext) {
|
|
38292
|
+
if (overnight) return "extended";
|
|
38293
|
+
return w.regular.some(([s]) => s >= now && s < ext[1]) ? "pre" : "post";
|
|
38294
|
+
}
|
|
38295
|
+
if (isWeekday(now, tz)) {
|
|
38296
|
+
const today = civilDate(now, tz);
|
|
38297
|
+
if (today !== "" && !w.extended.some(([s]) => civilDate(s, tz) === today)) return "holiday";
|
|
38298
|
+
}
|
|
38299
|
+
return "closed";
|
|
38300
|
+
}
|
|
38301
|
+
function nextStatusBoundary(now, w) {
|
|
38302
|
+
let next = null;
|
|
38303
|
+
for (const ws of [w.regular, w.extended]) {
|
|
38304
|
+
for (const [s, e] of ws) {
|
|
38305
|
+
for (const t of [s, e]) {
|
|
38306
|
+
if (t > now && (next == null || t < next)) next = t;
|
|
38307
|
+
}
|
|
38308
|
+
}
|
|
38309
|
+
}
|
|
38310
|
+
return next;
|
|
38311
|
+
}
|
|
38312
|
+
var FETCH_BACK_MS = 4 * 864e5;
|
|
38313
|
+
var FETCH_AHEAD_MS = 10 * 864e5;
|
|
38314
|
+
var MIN_TIMER_MS = 15e3;
|
|
38315
|
+
var MAX_TIMER_MS = 36e5;
|
|
38316
|
+
var RETRY_MS2 = 6e4;
|
|
38317
|
+
var MarketStatusTracker = class {
|
|
38318
|
+
constructor(onStatus) {
|
|
38319
|
+
this.onStatus = onStatus;
|
|
38320
|
+
/** Invalidates detached async work — bumped by every track()/stop(). */
|
|
38321
|
+
this.epoch = 0;
|
|
38322
|
+
this.timer = null;
|
|
38323
|
+
}
|
|
38324
|
+
/** (Re)bind to a chart's data surface + symbol and start evaluating. */
|
|
38325
|
+
track(data, symbol) {
|
|
38326
|
+
const my = ++this.epoch;
|
|
38327
|
+
this.clearTimer();
|
|
38328
|
+
void this.evaluate(my, data, symbol);
|
|
38329
|
+
}
|
|
38330
|
+
stop() {
|
|
38331
|
+
this.epoch += 1;
|
|
38332
|
+
this.clearTimer();
|
|
38333
|
+
}
|
|
38334
|
+
async evaluate(my, data, symbol) {
|
|
38335
|
+
const resolved = data.resolve(symbol);
|
|
38336
|
+
const provider = resolved ? data.providerInstance(resolved.provider) : void 0;
|
|
38337
|
+
const si = await data.symbolInfo(symbol).catch(() => void 0);
|
|
38338
|
+
if (my !== this.epoch) return;
|
|
38339
|
+
const hasSessions = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
38340
|
+
if (!provider?.getCalendar || !hasSessions || !resolved) {
|
|
38341
|
+
this.onStatus("open");
|
|
38342
|
+
return;
|
|
38343
|
+
}
|
|
38344
|
+
const tz = typeof si?.timezone === "string" && si.timezone !== "" ? si.timezone : "Etc/UTC";
|
|
38345
|
+
const now = Date.now();
|
|
38346
|
+
const range = { from: now - FETCH_BACK_MS, to: now + FETCH_AHEAD_MS };
|
|
38347
|
+
const [regular, extended] = await Promise.all([
|
|
38348
|
+
provider.getCalendar(resolved.ticker, { ...range, session: "regular" }).catch(() => null),
|
|
38349
|
+
provider.getCalendar(resolved.ticker, { ...range, session: "extended" }).catch(() => null)
|
|
38350
|
+
]);
|
|
38351
|
+
if (my !== this.epoch) return;
|
|
38352
|
+
if (!regular || !extended) {
|
|
38353
|
+
this.arm(my, data, symbol, RETRY_MS2);
|
|
38354
|
+
return;
|
|
38355
|
+
}
|
|
38356
|
+
const w = { regular, extended };
|
|
38357
|
+
const overnight = parseSessionSpec(si)?.overnight === true;
|
|
38358
|
+
this.onStatus(deriveMarketStatus(now, w, tz, overnight));
|
|
38359
|
+
const boundary = nextStatusBoundary(now, w);
|
|
38360
|
+
const delay = Math.min(boundary != null ? boundary - now : MAX_TIMER_MS, MAX_TIMER_MS);
|
|
38361
|
+
this.arm(my, data, symbol, Math.max(delay, MIN_TIMER_MS));
|
|
38362
|
+
}
|
|
38363
|
+
arm(my, data, symbol, delay) {
|
|
38364
|
+
this.clearTimer();
|
|
38365
|
+
this.timer = setTimeout(() => {
|
|
38366
|
+
this.timer = null;
|
|
38367
|
+
if (my !== this.epoch) return;
|
|
38368
|
+
void this.evaluate(my, data, symbol);
|
|
38369
|
+
}, delay);
|
|
38370
|
+
}
|
|
38371
|
+
clearTimer() {
|
|
38372
|
+
if (this.timer != null) clearTimeout(this.timer);
|
|
38373
|
+
this.timer = null;
|
|
38374
|
+
}
|
|
38375
|
+
};
|
|
38376
|
+
|
|
38185
38377
|
// src/widget/watermark.ts
|
|
38186
38378
|
var MAX_FONT_PX = 36;
|
|
38187
38379
|
var MIN_FONT_PX = 12;
|
|
@@ -38695,7 +38887,10 @@ var ChartCell = class {
|
|
|
38695
38887
|
this.activeRangeId = null;
|
|
38696
38888
|
/** Latched verdict of {@link sessionAvailable} (async metadata, sticky per symbol). */
|
|
38697
38889
|
this.sessionAvailableFlag = false;
|
|
38698
|
-
/**
|
|
38890
|
+
/** Latched: the symbol's extended tape wraps midnight (an overnight roll market) —
|
|
38891
|
+
* one extended-hours shading phase instead of the pre/post split. */
|
|
38892
|
+
this.sessionOvernightFlag = false;
|
|
38893
|
+
/** Keeps the session shading on the symbol's real calendar (see {@link SessionShadingTracker}). */
|
|
38699
38894
|
this.sessionShading = new SessionShadingTracker((zones) => this.inner?.renderer.set("sessionZones", zones));
|
|
38700
38895
|
this.manifest = [];
|
|
38701
38896
|
/** A restored ledger's manifest entry NAMES, waiting for the manifest to resolve
|
|
@@ -38948,8 +39143,10 @@ var ChartCell = class {
|
|
|
38948
39143
|
void chart.data.symbolInfo(symbol).then((si) => {
|
|
38949
39144
|
if (this.inner !== chart) return;
|
|
38950
39145
|
const available = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
38951
|
-
|
|
39146
|
+
const overnight = parseSessionSpec(si)?.overnight === true;
|
|
39147
|
+
if (available !== this.sessionAvailableFlag || overnight !== this.sessionOvernightFlag) {
|
|
38952
39148
|
this.sessionAvailableFlag = available;
|
|
39149
|
+
this.sessionOvernightFlag = overnight;
|
|
38953
39150
|
this.deps.onMarketChanged(this.id);
|
|
38954
39151
|
this.pushSettingsSections();
|
|
38955
39152
|
}
|
|
@@ -38983,16 +39180,41 @@ var ChartCell = class {
|
|
|
38983
39180
|
/**
|
|
38984
39181
|
* (Re)contribute this cell's settings-dialog sections: status line parts, the
|
|
38985
39182
|
* per-cell fetch depth, the watermark toggle, and — only while the cell's symbol
|
|
38986
|
-
* HAS sessions — the Trading session group (RTH/ETH switch + the
|
|
38987
|
-
*
|
|
38988
|
-
*
|
|
38989
|
-
* a
|
|
39183
|
+
* HAS sessions — the Trading session group (RTH/ETH switch + the session shading
|
|
39184
|
+
* colors: pre/post-market on day-split tapes, one extended-hours swatch on
|
|
39185
|
+
* overnight roll markets) inside the Symbol tab. Bars/watermark/titles are
|
|
39186
|
+
* persistable cell state; a depth-only reload is silent, so mark dirty here.
|
|
39187
|
+
* Re-run whenever a gate changes (the dialog reads the sections on open).
|
|
38990
39188
|
*/
|
|
38991
39189
|
pushSettingsSections() {
|
|
38992
39190
|
const chart = this.inner;
|
|
38993
39191
|
if (!chart) return;
|
|
38994
39192
|
const rth = "Regular hours (RTH)";
|
|
38995
39193
|
const eth = "Extended hours (ETH)";
|
|
39194
|
+
const shadeRows = this.sessionOvernightFlag ? [
|
|
39195
|
+
{
|
|
39196
|
+
kind: "color",
|
|
39197
|
+
label: "Extended hours",
|
|
39198
|
+
id: "extended-color",
|
|
39199
|
+
get: () => this.sessionShadeColor("extendedColor"),
|
|
39200
|
+
set: (v) => this.setSessionShadeColor("extendedColor", v)
|
|
39201
|
+
}
|
|
39202
|
+
] : [
|
|
39203
|
+
{
|
|
39204
|
+
kind: "color",
|
|
39205
|
+
label: "Pre-market",
|
|
39206
|
+
id: "premarket-color",
|
|
39207
|
+
get: () => this.sessionShadeColor("premarketColor"),
|
|
39208
|
+
set: (v) => this.setSessionShadeColor("premarketColor", v)
|
|
39209
|
+
},
|
|
39210
|
+
{
|
|
39211
|
+
kind: "color",
|
|
39212
|
+
label: "Post-market",
|
|
39213
|
+
id: "postmarket-color",
|
|
39214
|
+
get: () => this.sessionShadeColor("postmarketColor"),
|
|
39215
|
+
set: (v) => this.setSessionShadeColor("postmarketColor", v)
|
|
39216
|
+
}
|
|
39217
|
+
];
|
|
38996
39218
|
const sessionSection = {
|
|
38997
39219
|
title: "Trading session",
|
|
38998
39220
|
id: "trading-session",
|
|
@@ -39006,20 +39228,7 @@ var ChartCell = class {
|
|
|
39006
39228
|
get: () => this.session === "extended" ? eth : rth,
|
|
39007
39229
|
set: (v) => this.setSession(v === eth ? "extended" : "regular")
|
|
39008
39230
|
},
|
|
39009
|
-
|
|
39010
|
-
kind: "color",
|
|
39011
|
-
label: "Pre-market",
|
|
39012
|
-
id: "premarket-color",
|
|
39013
|
-
get: () => this.sessionShadeColor("premarketColor"),
|
|
39014
|
-
set: (v) => this.setSessionShadeColor("premarketColor", v)
|
|
39015
|
-
},
|
|
39016
|
-
{
|
|
39017
|
-
kind: "color",
|
|
39018
|
-
label: "Post-market",
|
|
39019
|
-
id: "postmarket-color",
|
|
39020
|
-
get: () => this.sessionShadeColor("postmarketColor"),
|
|
39021
|
-
set: (v) => this.setSessionShadeColor("postmarketColor", v)
|
|
39022
|
-
}
|
|
39231
|
+
...shadeRows
|
|
39023
39232
|
]
|
|
39024
39233
|
};
|
|
39025
39234
|
const advanced = {
|
|
@@ -39263,6 +39472,10 @@ var ChartCell = class {
|
|
|
39263
39472
|
this.deps.activate(this.id);
|
|
39264
39473
|
this.inner?.renderer.focus();
|
|
39265
39474
|
}
|
|
39475
|
+
/** Raster of this cell's chart (same pixels as the PNG download), or null. */
|
|
39476
|
+
screenshotCanvas() {
|
|
39477
|
+
return this.inner?.renderer.screenshotCanvas() ?? null;
|
|
39478
|
+
}
|
|
39266
39479
|
/** Download this cell's chart as a PNG (named after its market). */
|
|
39267
39480
|
downloadScreenshot() {
|
|
39268
39481
|
const url = this.inner?.renderer.screenshot();
|
|
@@ -39881,6 +40094,54 @@ var SplitterLayer = class {
|
|
|
39881
40094
|
}
|
|
39882
40095
|
};
|
|
39883
40096
|
|
|
40097
|
+
// src/workspace/screenshot.ts
|
|
40098
|
+
function destRect(tile, dpr) {
|
|
40099
|
+
const dx = Math.round(tile.x * dpr);
|
|
40100
|
+
const dy = Math.round(tile.y * dpr);
|
|
40101
|
+
return {
|
|
40102
|
+
dx,
|
|
40103
|
+
dy,
|
|
40104
|
+
dw: Math.round((tile.x + tile.width) * dpr) - dx,
|
|
40105
|
+
dh: Math.round((tile.y + tile.height) * dpr) - dy
|
|
40106
|
+
};
|
|
40107
|
+
}
|
|
40108
|
+
function tilesFromCellRects(grid, cells) {
|
|
40109
|
+
if (grid.width <= 0 || grid.height <= 0) return [];
|
|
40110
|
+
const tiles = [];
|
|
40111
|
+
for (const c of cells) {
|
|
40112
|
+
if (c.hidden || c.width <= 0 || c.height <= 0) continue;
|
|
40113
|
+
tiles.push({ x: c.left - grid.left, y: c.top - grid.top, width: c.width, height: c.height });
|
|
40114
|
+
}
|
|
40115
|
+
return tiles;
|
|
40116
|
+
}
|
|
40117
|
+
function compositeLayoutScreenshot(doc, frame, tiles) {
|
|
40118
|
+
if (frame.width <= 0 || frame.height <= 0 || tiles.length === 0) return null;
|
|
40119
|
+
const dpr = frame.dpr > 0 ? frame.dpr : 1;
|
|
40120
|
+
const out = doc.createElement("canvas");
|
|
40121
|
+
out.width = Math.max(1, Math.round(frame.width * dpr));
|
|
40122
|
+
out.height = Math.max(1, Math.round(frame.height * dpr));
|
|
40123
|
+
const ctx = out.getContext("2d");
|
|
40124
|
+
if (!ctx) return null;
|
|
40125
|
+
ctx.fillStyle = frame.gapColor;
|
|
40126
|
+
ctx.fillRect(0, 0, out.width, out.height);
|
|
40127
|
+
let painted = 0;
|
|
40128
|
+
for (const tile of tiles) {
|
|
40129
|
+
if (tile.width <= 0 || tile.height <= 0) continue;
|
|
40130
|
+
const { dx, dy, dw, dh } = destRect(tile, dpr);
|
|
40131
|
+
if (dw <= 0 || dh <= 0) continue;
|
|
40132
|
+
ctx.drawImage(tile.source, dx, dy, dw, dh);
|
|
40133
|
+
painted += 1;
|
|
40134
|
+
}
|
|
40135
|
+
if (painted === 0) return null;
|
|
40136
|
+
return out.toDataURL("image/png");
|
|
40137
|
+
}
|
|
40138
|
+
function triggerPngDownload(doc, url, filename) {
|
|
40139
|
+
const a = doc.createElement("a");
|
|
40140
|
+
a.href = url;
|
|
40141
|
+
a.download = filename;
|
|
40142
|
+
a.click();
|
|
40143
|
+
}
|
|
40144
|
+
|
|
39884
40145
|
// src/workspace/VelaWorkspace.ts
|
|
39885
40146
|
var DEFAULT_TIMEFRAMES = ["1", "5", "15", "30", "60", "240", "D", "W", "M"];
|
|
39886
40147
|
var GAP_PX = 2;
|
|
@@ -40111,7 +40372,7 @@ var VelaWorkspace = class {
|
|
|
40111
40372
|
...picker ? { onIndicatorsClick: () => picker.open() } : {},
|
|
40112
40373
|
onUndoClick: () => this.active.history.undo(),
|
|
40113
40374
|
onRedoClick: () => this.active.history.redo(),
|
|
40114
|
-
onScreenshotClick: () => this.
|
|
40375
|
+
onScreenshotClick: () => this.downloadScreenshot(),
|
|
40115
40376
|
onAlertsClick: (anchor) => this.openAlertsMenu(anchor),
|
|
40116
40377
|
timeframe: "60",
|
|
40117
40378
|
timeframes: opts.timeframes ?? DEFAULT_TIMEFRAMES,
|
|
@@ -40170,6 +40431,7 @@ var VelaWorkspace = class {
|
|
|
40170
40431
|
this.dock.addBuiltIn({ id: "dataWindow", title: "Data window", icon: "datawindow", order: 10, panel: this.dataWindow, onChart: (c) => this.dataWindow.onChart(c) });
|
|
40171
40432
|
this.dock.addBuiltIn({ id: "objects", title: "Object tree", icon: "objects", order: 20, panel: this.objectTree, onChart: (c) => this.objectTree.onChart(c) });
|
|
40172
40433
|
this.dock.refresh();
|
|
40434
|
+
if (boot?.panels) this.dock.applyState(boot.panels);
|
|
40173
40435
|
this.root.appendChild(main);
|
|
40174
40436
|
this.toastHost = new Toast(this.gridEl);
|
|
40175
40437
|
const attribution = rendererDefaults().attribution;
|
|
@@ -40181,6 +40443,7 @@ var VelaWorkspace = class {
|
|
|
40181
40443
|
bottom: `calc(var(--vela-bottom-gutter, ${TIME_AXIS_H4}px) + 10px)`,
|
|
40182
40444
|
zIndex: "11"
|
|
40183
40445
|
});
|
|
40446
|
+
mark.dataset.velaScreenshot = "1";
|
|
40184
40447
|
this.gridEl.appendChild(mark);
|
|
40185
40448
|
this.attributionMark = mark;
|
|
40186
40449
|
}
|
|
@@ -40317,6 +40580,56 @@ var VelaWorkspace = class {
|
|
|
40317
40580
|
get chart() {
|
|
40318
40581
|
return this.active.chart;
|
|
40319
40582
|
}
|
|
40583
|
+
/**
|
|
40584
|
+
* PNG data URL of the visible layout: every live cell in its grid slot, or the
|
|
40585
|
+
* maximized cell alone. Same pixels the screenshot button downloads. A single
|
|
40586
|
+
* visible cell returns that cell's own chart export (the one-chart case).
|
|
40587
|
+
*/
|
|
40588
|
+
screenshot() {
|
|
40589
|
+
const cells = this.shotCells();
|
|
40590
|
+
if (cells.length === 0) return null;
|
|
40591
|
+
if (cells.length === 1) return cells[0].chart.renderer.screenshot();
|
|
40592
|
+
return this.compositeLayoutShot(cells);
|
|
40593
|
+
}
|
|
40594
|
+
/** Download {@link screenshot} as a PNG. A multi-cell layout is named
|
|
40595
|
+
* `vela-layout.png`; one visible cell keeps `${symbol}-${timeframe}.png`. */
|
|
40596
|
+
downloadScreenshot() {
|
|
40597
|
+
const cells = this.shotCells();
|
|
40598
|
+
if (cells.length <= 1) {
|
|
40599
|
+
(cells[0] ?? this.active).downloadScreenshot();
|
|
40600
|
+
return;
|
|
40601
|
+
}
|
|
40602
|
+
const url = this.compositeLayoutShot(cells);
|
|
40603
|
+
if (!url) return;
|
|
40604
|
+
triggerPngDownload(this.root.ownerDocument, url, "vela-layout.png");
|
|
40605
|
+
}
|
|
40606
|
+
/** Live cells the screenshot should include — maximized siblings are `hidden`. */
|
|
40607
|
+
shotCells() {
|
|
40608
|
+
return this.cells().filter((c) => c.host.style.visibility !== "hidden");
|
|
40609
|
+
}
|
|
40610
|
+
/** Place every cell's raster onto one canvas the size of the grid. */
|
|
40611
|
+
compositeLayoutShot(cells) {
|
|
40612
|
+
const gridEl = this.gridEl;
|
|
40613
|
+
const grid = gridEl.getBoundingClientRect();
|
|
40614
|
+
const win = gridEl.ownerDocument.defaultView;
|
|
40615
|
+
const dpr = win?.devicePixelRatio ?? 1;
|
|
40616
|
+
const gapColor = win ? win.getComputedStyle(gridEl).backgroundColor : "";
|
|
40617
|
+
const tiles = [];
|
|
40618
|
+
for (const cell of cells) {
|
|
40619
|
+
const source = cell.screenshotCanvas();
|
|
40620
|
+
if (!source) continue;
|
|
40621
|
+
const r = cell.host.getBoundingClientRect();
|
|
40622
|
+
const place = tilesFromCellRects(grid, [{ left: r.left, top: r.top, width: r.width, height: r.height }])[0];
|
|
40623
|
+
if (!place) continue;
|
|
40624
|
+
tiles.push({ source, ...place });
|
|
40625
|
+
}
|
|
40626
|
+
return compositeLayoutScreenshot(gridEl.ownerDocument, {
|
|
40627
|
+
width: grid.width,
|
|
40628
|
+
height: grid.height,
|
|
40629
|
+
dpr,
|
|
40630
|
+
gapColor: gapColor || "#000000"
|
|
40631
|
+
}, tiles);
|
|
40632
|
+
}
|
|
40320
40633
|
setActiveCell(id) {
|
|
40321
40634
|
if (id === this.activeId || this.destroyed) return;
|
|
40322
40635
|
const prev2 = this.activeId;
|
|
@@ -41363,7 +41676,7 @@ var VelaWorkspace = class {
|
|
|
41363
41676
|
this.moreDrawer ?? (this.moreDrawer = new MoreDrawer({
|
|
41364
41677
|
host: this.root,
|
|
41365
41678
|
...has("undo-redo") ? { onUndo: () => this.active.history.undo(), onRedo: () => this.active.history.redo() } : {},
|
|
41366
|
-
...has("screenshot") ? { onScreenshot: this.screenshotOverride ? () => this.runOverride(this.screenshotOverride) : () => this.
|
|
41679
|
+
...has("screenshot") ? { onScreenshot: this.screenshotOverride ? () => this.runOverride(this.screenshotOverride) : () => this.downloadScreenshot() } : {},
|
|
41367
41680
|
canUndo: () => this.active.history.canUndo,
|
|
41368
41681
|
canRedo: () => this.active.history.canRedo,
|
|
41369
41682
|
priceStyles: () => priceStyleIds().map((id) => ({ id, label: priceStyleLabel(id), icon: priceStyleIcon(id) })),
|
|
@@ -41464,9 +41777,9 @@ var VelaWorkspace = class {
|
|
|
41464
41777
|
this.keymap.register({
|
|
41465
41778
|
id: "chart.screenshot",
|
|
41466
41779
|
keys: "mod+alt+s",
|
|
41467
|
-
label: ov ? ov.label : "Download a
|
|
41780
|
+
label: ov ? ov.label : "Download a screenshot of the layout",
|
|
41468
41781
|
category: "Chart",
|
|
41469
|
-
run: ov ? () => this.runOverride(ov) : () => this.
|
|
41782
|
+
run: ov ? () => this.runOverride(ov) : () => this.downloadScreenshot()
|
|
41470
41783
|
});
|
|
41471
41784
|
}
|
|
41472
41785
|
this.keymap.register({ id: "chart.reset-view", keys: "alt+r", label: "Reset view (all history)", category: "Chart", run: () => this.active.chart.setVisibleRangePreset("ALL") });
|