@luxalgo/vela 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{DataProvider-DHX4x6-r.d.ts → DataProvider-BmHkcwvJ.d.ts} +1 -1
- package/dist/{DataProvider-CNmk84SH.d.cts → DataProvider-DrN7ZIou.d.cts} +1 -1
- package/dist/{chunk-TP56QRMK.js → chunk-2R7BANEM.js} +1 -1
- package/dist/{chunk-7Y7CJ7EN.js → chunk-BEJ57HP4.js} +8 -1
- package/dist/{chunk-7D6YIF34.js → chunk-FQBT4NFM.js} +431 -74
- package/dist/{chunk-U5KCQHAC.js → chunk-N6MBJHMV.js} +129 -41
- package/dist/{chunk-QHZ7IXFL.js → chunk-OICPLNU7.js} +59 -9
- package/dist/{contributions-DDpv7hEL.d.cts → contributions-2bEmCF9S.d.cts} +2 -2
- package/dist/{contributions-ciV6Neyd.d.ts → contributions-DatoAzMx.d.ts} +2 -2
- package/dist/{history-CFqZm5re.d.ts → history-BmyjaVir.d.cts} +6 -6
- package/dist/{history-ChoT34nz.d.cts → history-CvOtsjT5.d.ts} +6 -6
- package/dist/index.cjs +500 -85
- package/dist/index.d.cts +63 -9
- package/dist/index.d.ts +63 -9
- package/dist/index.js +4 -4
- package/dist/{options-BqGeFHtp.d.ts → options-YdiaaVjt.d.cts} +20 -1
- package/dist/{options-BqGeFHtp.d.cts → options-YdiaaVjt.d.ts} +20 -1
- package/dist/{plugin-C97gUCVf.d.ts → plugin-Bcone6Vh.d.ts} +9 -5
- package/dist/{plugin-CSb_sTiN.d.cts → plugin-DO_DX0gp.d.cts} +9 -5
- package/dist/plugin.cjs +8 -0
- package/dist/plugin.d.cts +4 -4
- package/dist/plugin.d.ts +4 -4
- package/dist/plugin.js +2 -2
- package/dist/providers/binance.d.cts +2 -2
- package/dist/providers/binance.d.ts +2 -2
- package/dist/providers/coinbase.d.cts +2 -2
- package/dist/providers/coinbase.d.ts +2 -2
- package/dist/providers/hyperliquid.d.cts +2 -2
- package/dist/providers/hyperliquid.d.ts +2 -2
- package/dist/ui.cjs +59 -9
- package/dist/ui.d.cts +10 -2
- package/dist/ui.d.ts +10 -2
- package/dist/ui.js +2 -2
- package/dist/vela.global.js +500 -85
- package/dist/vela.global.min.js +84 -47
- package/dist/widget.cjs +801 -227
- package/dist/widget.d.cts +34 -8
- package/dist/widget.d.ts +34 -8
- package/dist/widget.js +145 -73
- package/dist/workspace.cjs +756 -178
- package/dist/workspace.d.cts +29 -5
- package/dist/workspace.d.ts +29 -5
- package/dist/workspace.js +143 -67
- package/package.json +1 -1
- /package/dist/{chunk-Q3XQHLIH.js → chunk-AOGZBKUE.js} +0 -0
package/dist/widget.cjs
CHANGED
|
@@ -9878,7 +9878,7 @@ function attachChromeTooltip(anchor, opts) {
|
|
|
9878
9878
|
} else {
|
|
9879
9879
|
const left = Math.min(a.left - h.left, Math.max(0, h.width - tip.offsetWidth - 4));
|
|
9880
9880
|
tip.style.left = `${Math.max(0, left)}px`;
|
|
9881
|
-
tip.style.top = `${a.bottom - h.top + 6}px`;
|
|
9881
|
+
tip.style.top = opts.placement === "above" ? `${Math.max(0, a.top - h.top - tip.offsetHeight - 6)}px` : `${a.bottom - h.top + 6}px`;
|
|
9882
9882
|
}
|
|
9883
9883
|
};
|
|
9884
9884
|
const arm = (e) => {
|
|
@@ -10004,6 +10004,23 @@ var MENU_CSS = `
|
|
|
10004
10004
|
background: var(--vela-hover-strong);
|
|
10005
10005
|
color: var(--vela-fg-bright);
|
|
10006
10006
|
}
|
|
10007
|
+
/* Checkmark mode (context/action menus): every row reserves the leading mark column so
|
|
10008
|
+
labels align; a checked row fills it with a \u2713 and brightens its ink \u2014 the row surface
|
|
10009
|
+
stays free for the hover wash. */
|
|
10010
|
+
.vela-menu-item .vela-menu-mark {
|
|
10011
|
+
width: 14px;
|
|
10012
|
+
flex: none;
|
|
10013
|
+
display: inline-flex;
|
|
10014
|
+
align-items: center;
|
|
10015
|
+
justify-content: center;
|
|
10016
|
+
}
|
|
10017
|
+
.vela-menu-item .vela-menu-mark .vela-icon {
|
|
10018
|
+
width: 14px;
|
|
10019
|
+
height: 14px;
|
|
10020
|
+
font-size: 14px;
|
|
10021
|
+
color: var(--vela-fg-bright);
|
|
10022
|
+
}
|
|
10023
|
+
.vela-menu-item[data-checkmark] { color: var(--vela-fg-bright); }
|
|
10007
10024
|
/* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
|
|
10008
10025
|
same control language as the settings dialog's toggles. */
|
|
10009
10026
|
.vela-menu-switch {
|
|
@@ -10072,6 +10089,7 @@ var Surface = class _Surface {
|
|
|
10072
10089
|
this.host = opts.host;
|
|
10073
10090
|
this.onSelect = opts.onSelect;
|
|
10074
10091
|
this.onFavorite = opts.onFavorite;
|
|
10092
|
+
this.checkmarks = opts.checkmarks === true;
|
|
10075
10093
|
this.positioner = doc.createElement("div");
|
|
10076
10094
|
this.positioner.className = "vela-ui-layer";
|
|
10077
10095
|
this.list = doc.createElement("ul");
|
|
@@ -10150,6 +10168,7 @@ var Surface = class _Surface {
|
|
|
10150
10168
|
build() {
|
|
10151
10169
|
const doc = this.doc;
|
|
10152
10170
|
this.list.replaceChildren();
|
|
10171
|
+
const markable = this.checkmarks && this.items.some((i) => !(i.submenu && i.submenu.length > 0) && !i.toggle && i.checked !== void 0);
|
|
10153
10172
|
for (const item of this.items) {
|
|
10154
10173
|
if (item.separatorBefore) {
|
|
10155
10174
|
const sep = doc.createElement("li");
|
|
@@ -10163,7 +10182,16 @@ var Surface = class _Surface {
|
|
|
10163
10182
|
li.dataset.veiId = item.id;
|
|
10164
10183
|
if (item.toggle) {
|
|
10165
10184
|
li.dataset.toggle = "1";
|
|
10166
|
-
}
|
|
10185
|
+
}
|
|
10186
|
+
if (markable) {
|
|
10187
|
+
const mark = doc.createElement("span");
|
|
10188
|
+
mark.className = "vela-menu-mark";
|
|
10189
|
+
if (!branch && !item.toggle && item.checked) {
|
|
10190
|
+
mark.appendChild(iconEl("check", doc));
|
|
10191
|
+
li.dataset.checkmark = "1";
|
|
10192
|
+
}
|
|
10193
|
+
li.appendChild(mark);
|
|
10194
|
+
} else if (!branch && !item.toggle && item.checked) {
|
|
10167
10195
|
li.dataset.checked = "1";
|
|
10168
10196
|
}
|
|
10169
10197
|
if (item.icon) li.appendChild(iconEl(item.icon, doc));
|
|
@@ -10212,6 +10240,7 @@ var Surface = class _Surface {
|
|
|
10212
10240
|
placement: "right-start",
|
|
10213
10241
|
onSelect: this.onSelect,
|
|
10214
10242
|
onFavorite: this.onFavorite,
|
|
10243
|
+
checkmarks: this.checkmarks,
|
|
10215
10244
|
id: `${this.mid}--${item.id}`
|
|
10216
10245
|
});
|
|
10217
10246
|
sub.setItems(item.submenu ?? []);
|
|
@@ -10236,7 +10265,8 @@ var Menu = class {
|
|
|
10236
10265
|
triggerId: opts.triggerId,
|
|
10237
10266
|
id: opts.id,
|
|
10238
10267
|
minWidth: opts.minWidth,
|
|
10239
|
-
onFavorite: opts.onFavorite
|
|
10268
|
+
onFavorite: opts.onFavorite,
|
|
10269
|
+
checkmarks: opts.checkmarks
|
|
10240
10270
|
});
|
|
10241
10271
|
this.root.setItems(opts.items);
|
|
10242
10272
|
}
|
|
@@ -10264,6 +10294,13 @@ var Menu = class {
|
|
|
10264
10294
|
}
|
|
10265
10295
|
};
|
|
10266
10296
|
|
|
10297
|
+
// src/core/model/inputs.ts
|
|
10298
|
+
function inputVisible(when, values) {
|
|
10299
|
+
if (!when) return true;
|
|
10300
|
+
const conds = Array.isArray(when) ? when : [when];
|
|
10301
|
+
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
|
|
10302
|
+
}
|
|
10303
|
+
|
|
10267
10304
|
// src/ui/tokens.ts
|
|
10268
10305
|
var STATIC_ID = "vela-ui-tokens";
|
|
10269
10306
|
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
@@ -10272,8 +10309,13 @@ var STATIC_CSS = `
|
|
|
10272
10309
|
${STATIC_DECLS}
|
|
10273
10310
|
font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
|
|
10274
10311
|
box-sizing: border-box;
|
|
10312
|
+
/* Chrome text (titles, buttons, menus, readouts) is UI, not copy \u2014 never selectable. */
|
|
10313
|
+
user-select: none;
|
|
10314
|
+
-webkit-user-select: none;
|
|
10275
10315
|
}
|
|
10276
10316
|
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
10317
|
+
/* Text ENTRY is the one exception: selection is part of editing. */
|
|
10318
|
+
.vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
|
|
10277
10319
|
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
10278
10320
|
.vela-icon svg { display: block; }
|
|
10279
10321
|
`;
|
|
@@ -11275,6 +11317,9 @@ var NUMBER_CSS = `
|
|
|
11275
11317
|
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
11276
11318
|
-moz-appearance: textfield;
|
|
11277
11319
|
appearance: textfield;
|
|
11320
|
+
/* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
|
|
11321
|
+
user-select: text;
|
|
11322
|
+
-webkit-user-select: text;
|
|
11278
11323
|
}
|
|
11279
11324
|
.vela-num input::-webkit-inner-spin-button, .vela-num input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
|
|
11280
11325
|
.vela-num input:hover { border-color: var(--vela-fg-muted); }
|
|
@@ -11487,6 +11532,9 @@ var TEXT_CSS = `
|
|
|
11487
11532
|
overflow: hidden;
|
|
11488
11533
|
text-overflow: ellipsis;
|
|
11489
11534
|
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
11535
|
+
/* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
|
|
11536
|
+
user-select: text;
|
|
11537
|
+
-webkit-user-select: text;
|
|
11490
11538
|
}
|
|
11491
11539
|
.vela-text-field:hover { border-color: var(--vela-fg-muted); }
|
|
11492
11540
|
.vela-text-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
@@ -11580,6 +11628,9 @@ var TEXTAREA_CSS = `
|
|
|
11580
11628
|
resize: vertical;
|
|
11581
11629
|
outline: none;
|
|
11582
11630
|
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
11631
|
+
/* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
|
|
11632
|
+
user-select: text;
|
|
11633
|
+
-webkit-user-select: text;
|
|
11583
11634
|
}
|
|
11584
11635
|
.vela-textarea-field:hover { border-color: var(--vela-fg-muted); }
|
|
11585
11636
|
.vela-textarea-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
@@ -12108,6 +12159,8 @@ var FIELD_CSS = `
|
|
|
12108
12159
|
font-size: 14px;
|
|
12109
12160
|
}
|
|
12110
12161
|
.vela-field-label[data-size='sm'] { font-size: inherit; opacity: 0.85; }
|
|
12162
|
+
/* Titles are fully inert: spans, not label[for] \u2014 native labels propagate :hover and
|
|
12163
|
+
clicks onto their control, and both belong to the input alone. */
|
|
12111
12164
|
.vela-field-cell { display: flex; align-items: center; gap: 8px; justify-self: start; }
|
|
12112
12165
|
.vela-field-bool { display: flex; align-items: center; gap: 8px; min-height: 22px; }
|
|
12113
12166
|
.vela-field-stacked { display: flex; flex-direction: column; gap: 8px; }
|
|
@@ -12166,11 +12219,17 @@ function fieldSeparator() {
|
|
|
12166
12219
|
return el;
|
|
12167
12220
|
}
|
|
12168
12221
|
function labelEl(text, opts) {
|
|
12169
|
-
const el =
|
|
12170
|
-
if (opts.id && el instanceof HTMLLabelElement) el.htmlFor = opts.id;
|
|
12222
|
+
const el = document.createElement("span");
|
|
12171
12223
|
el.className = "vela-field-label";
|
|
12172
12224
|
if (opts.size) el.dataset.size = opts.size;
|
|
12173
12225
|
el.textContent = text;
|
|
12226
|
+
const id = opts.id;
|
|
12227
|
+
if (id) {
|
|
12228
|
+
el.id = `${id}--title`;
|
|
12229
|
+
queueMicrotask(() => {
|
|
12230
|
+
el.ownerDocument.getElementById(id)?.setAttribute("aria-labelledby", el.id);
|
|
12231
|
+
});
|
|
12232
|
+
}
|
|
12174
12233
|
return el;
|
|
12175
12234
|
}
|
|
12176
12235
|
function asList(control) {
|
|
@@ -12199,9 +12258,7 @@ function fieldRow(opts) {
|
|
|
12199
12258
|
it.className = "vela-field-inline-item";
|
|
12200
12259
|
if (item.toggleFirst) it.style.flexDirection = "row-reverse";
|
|
12201
12260
|
if (item.label) {
|
|
12202
|
-
|
|
12203
|
-
if (item.toggleFirst) lbl.style.cursor = "pointer";
|
|
12204
|
-
it.appendChild(lbl);
|
|
12261
|
+
it.appendChild(labelEl(item.label, { id: item.id, size }));
|
|
12205
12262
|
}
|
|
12206
12263
|
if (item.fit) it.appendChild(item.control);
|
|
12207
12264
|
else {
|
|
@@ -12226,7 +12283,7 @@ function fieldRow(opts) {
|
|
|
12226
12283
|
tone: "bright",
|
|
12227
12284
|
onChange: (v) => opts.toggle?.onChange(v)
|
|
12228
12285
|
});
|
|
12229
|
-
wrap2.append(sw.el, labelEl(opts.label, { size }));
|
|
12286
|
+
wrap2.append(sw.el, labelEl(opts.label, { id: opts.toggle?.id ?? opts.id, size }));
|
|
12230
12287
|
if (opts.info) wrap2.appendChild(opts.info);
|
|
12231
12288
|
if (opts.toggle?.get) {
|
|
12232
12289
|
const get = opts.toggle.get;
|
|
@@ -12265,7 +12322,7 @@ function fieldRow(opts) {
|
|
|
12265
12322
|
if (controls.length > 0) dimControls(controls, v);
|
|
12266
12323
|
}
|
|
12267
12324
|
});
|
|
12268
|
-
left.append(sw.el, labelEl(opts.label, { size }));
|
|
12325
|
+
left.append(sw.el, labelEl(opts.label, { id: opts.toggle.id ?? opts.id, size }));
|
|
12269
12326
|
wrap.appendChild(left);
|
|
12270
12327
|
if (controls.length > 0) dimControls(controls, opts.toggle.checked);
|
|
12271
12328
|
if (opts.toggle.get) {
|
|
@@ -12419,6 +12476,8 @@ var IndicatorInputsDialog = class {
|
|
|
12419
12476
|
this.row = null;
|
|
12420
12477
|
this.snapshot = null;
|
|
12421
12478
|
this.dialogTips = [];
|
|
12479
|
+
/** Re-applies every `when` gate against current values; set while the dialog is open. */
|
|
12480
|
+
this.refreshVisibility = null;
|
|
12422
12481
|
this.choicePop = null;
|
|
12423
12482
|
this.calendarPop = null;
|
|
12424
12483
|
this.calendarAnchor = null;
|
|
@@ -12481,14 +12540,17 @@ var IndicatorInputsDialog = class {
|
|
|
12481
12540
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
12482
12541
|
const tabEls = [];
|
|
12483
12542
|
const bodies = [];
|
|
12543
|
+
const gates = [];
|
|
12544
|
+
let active = 0;
|
|
12484
12545
|
const activate = (idx) => {
|
|
12546
|
+
active = idx;
|
|
12485
12547
|
for (let i = 0; i < tabEls.length; i += 1) {
|
|
12486
|
-
const
|
|
12548
|
+
const on = i === idx;
|
|
12487
12549
|
const el = tabEls[i];
|
|
12488
|
-
el.style.borderBottomColor =
|
|
12489
|
-
el.style.color =
|
|
12490
|
-
el.classList.toggle("vela-ind-tab-active",
|
|
12491
|
-
bodies[i].style.display =
|
|
12550
|
+
el.style.borderBottomColor = on ? fg : "transparent";
|
|
12551
|
+
el.style.color = on ? "inherit" : "var(--vela-fg-muted)";
|
|
12552
|
+
el.classList.toggle("vela-ind-tab-active", on);
|
|
12553
|
+
bodies[i].style.display = on ? "grid" : "none";
|
|
12492
12554
|
}
|
|
12493
12555
|
};
|
|
12494
12556
|
for (const [idx, def] of tabDefs.entries()) {
|
|
@@ -12499,11 +12561,24 @@ var IndicatorInputsDialog = class {
|
|
|
12499
12561
|
tab.addEventListener("click", () => activate(idx));
|
|
12500
12562
|
tabs.appendChild(tab);
|
|
12501
12563
|
tabEls.push(tab);
|
|
12502
|
-
bodies.push(this.buildTabBody(def.inputs, row));
|
|
12564
|
+
bodies.push(this.buildTabBody(def.inputs, row, gates));
|
|
12565
|
+
if (def.inputs.every((d) => d.when)) {
|
|
12566
|
+
gates.push({ el: tab, visible: (bag) => def.inputs.some((d) => inputVisible(d.when, bag)) });
|
|
12567
|
+
}
|
|
12503
12568
|
}
|
|
12504
12569
|
ui.body.appendChild(tabs);
|
|
12505
12570
|
for (const b of bodies) ui.body.appendChild(b);
|
|
12506
12571
|
activate(0);
|
|
12572
|
+
const refresh = () => {
|
|
12573
|
+
const bag = valuesBag(row);
|
|
12574
|
+
for (const g of gates) g.el.style.display = g.visible(bag) ? "" : "none";
|
|
12575
|
+
if (tabEls[active]?.style.display === "none") {
|
|
12576
|
+
const next = tabEls.findIndex((el) => el.style.display !== "none");
|
|
12577
|
+
if (next >= 0) activate(next);
|
|
12578
|
+
}
|
|
12579
|
+
};
|
|
12580
|
+
this.refreshVisibility = refresh;
|
|
12581
|
+
refresh();
|
|
12507
12582
|
const onBackdropDown = (e) => {
|
|
12508
12583
|
if (e.target !== ui.backdrop && e.target !== ui.positioner) return;
|
|
12509
12584
|
if (isPopoverOpen() || eventDismissedPopover(e)) {
|
|
@@ -12523,16 +12598,27 @@ var IndicatorInputsDialog = class {
|
|
|
12523
12598
|
}
|
|
12524
12599
|
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
12525
12600
|
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
12526
|
-
buildTabBody(inputs, row) {
|
|
12601
|
+
buildTabBody(inputs, row, gates) {
|
|
12527
12602
|
const body = document.createElement("div");
|
|
12528
12603
|
body.style.cssText = inputDialogBodyStyle(this.host.mobile());
|
|
12529
12604
|
for (const group of groupInputs(inputs)) {
|
|
12530
12605
|
const section = document.createElement("div");
|
|
12531
12606
|
section.style.cssText = "display:contents;";
|
|
12607
|
+
let header = null;
|
|
12532
12608
|
if (group.name) {
|
|
12533
|
-
|
|
12609
|
+
header = fieldSection(group.name, { variant: "inputs", first: body.childElementCount === 0 });
|
|
12610
|
+
section.appendChild(header);
|
|
12611
|
+
}
|
|
12612
|
+
for (const inputRow of group.rows) {
|
|
12613
|
+
const el = this.buildInputRow(section, inputRow, row);
|
|
12614
|
+
if (inputRow.some((d) => d.when)) {
|
|
12615
|
+
gates.push({ el, visible: (bag) => rowVisible(inputRow, bag) });
|
|
12616
|
+
}
|
|
12617
|
+
}
|
|
12618
|
+
if (header && group.rows.every((r) => r.every((d) => d.when))) {
|
|
12619
|
+
const rows = group.rows;
|
|
12620
|
+
gates.push({ el: header, visible: (bag) => rows.some((r) => rowVisible(r, bag)) });
|
|
12534
12621
|
}
|
|
12535
|
-
for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
|
|
12536
12622
|
body.appendChild(section);
|
|
12537
12623
|
}
|
|
12538
12624
|
return body;
|
|
@@ -12540,6 +12626,7 @@ var IndicatorInputsDialog = class {
|
|
|
12540
12626
|
close() {
|
|
12541
12627
|
document.removeEventListener("keydown", this.onDialogKey);
|
|
12542
12628
|
closeOpenPopovers();
|
|
12629
|
+
this.refreshVisibility = null;
|
|
12543
12630
|
this.choicePop = null;
|
|
12544
12631
|
this.calendarPop = null;
|
|
12545
12632
|
this.calendarAnchor = null;
|
|
@@ -12569,11 +12656,21 @@ var IndicatorInputsDialog = class {
|
|
|
12569
12656
|
}
|
|
12570
12657
|
this.close();
|
|
12571
12658
|
}
|
|
12659
|
+
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
12660
|
+
commit(row, key, value) {
|
|
12661
|
+
row.values[key] = value;
|
|
12662
|
+
this.host.onChange?.({ indicatorId: row.id, key, value });
|
|
12663
|
+
this.refreshVisibility?.();
|
|
12664
|
+
}
|
|
12572
12665
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
12573
12666
|
buildInputRow(grid, decls, row) {
|
|
12574
12667
|
const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
|
|
12668
|
+
const append = (el) => {
|
|
12669
|
+
grid.appendChild(el);
|
|
12670
|
+
return el;
|
|
12671
|
+
};
|
|
12575
12672
|
if (decls.length > 1) {
|
|
12576
|
-
|
|
12673
|
+
return append(fieldRow({
|
|
12577
12674
|
label: "",
|
|
12578
12675
|
inline: decls.map((d) => ({
|
|
12579
12676
|
label: nameOf(d) || void 0,
|
|
@@ -12584,14 +12681,13 @@ var IndicatorInputsDialog = class {
|
|
|
12584
12681
|
})),
|
|
12585
12682
|
info: [...decls].reverse().find((d) => d.tooltip)?.tooltip ? this.infoButton([...decls].reverse().find((d) => d.tooltip).tooltip) : void 0
|
|
12586
12683
|
}));
|
|
12587
|
-
return;
|
|
12588
12684
|
}
|
|
12589
12685
|
const lead = decls[0];
|
|
12590
12686
|
const id = idOf(lead);
|
|
12591
12687
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
12592
12688
|
if (lead.type === "bool") {
|
|
12593
12689
|
const current = Boolean(row.values[lead.key] ?? lead.defval);
|
|
12594
|
-
|
|
12690
|
+
return append(fieldRow({
|
|
12595
12691
|
label: nameOf(lead),
|
|
12596
12692
|
id,
|
|
12597
12693
|
bool: true,
|
|
@@ -12599,26 +12695,21 @@ var IndicatorInputsDialog = class {
|
|
|
12599
12695
|
toggle: {
|
|
12600
12696
|
id,
|
|
12601
12697
|
checked: current,
|
|
12602
|
-
onChange: (v) =>
|
|
12603
|
-
row.values[lead.key] = v;
|
|
12604
|
-
this.host.onChange?.({ indicatorId: row.id, key: lead.key, value: v });
|
|
12605
|
-
}
|
|
12698
|
+
onChange: (v) => this.commit(row, lead.key, v)
|
|
12606
12699
|
}
|
|
12607
12700
|
}));
|
|
12608
|
-
return;
|
|
12609
12701
|
}
|
|
12610
12702
|
if (lead.type === "text_area") {
|
|
12611
|
-
|
|
12703
|
+
return append(fieldRow({
|
|
12612
12704
|
label: nameOf(lead),
|
|
12613
12705
|
id,
|
|
12614
12706
|
stacked: true,
|
|
12615
12707
|
info,
|
|
12616
12708
|
control: this.buildControl(row, lead, id)
|
|
12617
12709
|
}));
|
|
12618
|
-
return;
|
|
12619
12710
|
}
|
|
12620
12711
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
12621
|
-
|
|
12712
|
+
return append(fieldRow({
|
|
12622
12713
|
label: nameOf(lead),
|
|
12623
12714
|
id,
|
|
12624
12715
|
fit,
|
|
@@ -12630,10 +12721,7 @@ var IndicatorInputsDialog = class {
|
|
|
12630
12721
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
12631
12722
|
buildControl(row, inp, id) {
|
|
12632
12723
|
const current = row.values[inp.key] ?? inp.defval;
|
|
12633
|
-
const emit = (value) =>
|
|
12634
|
-
row.values[inp.key] = value;
|
|
12635
|
-
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value });
|
|
12636
|
-
};
|
|
12724
|
+
const emit = (value) => this.commit(row, inp.key, value);
|
|
12637
12725
|
if (inp.type === "bool") {
|
|
12638
12726
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
12639
12727
|
}
|
|
@@ -13054,6 +13142,14 @@ function tabInputs(inputs) {
|
|
|
13054
13142
|
}
|
|
13055
13143
|
return names.map((name) => ({ name, inputs: byTab.get(name) }));
|
|
13056
13144
|
}
|
|
13145
|
+
function rowVisible(decls, bag) {
|
|
13146
|
+
return decls.some((d) => inputVisible(d.when, bag));
|
|
13147
|
+
}
|
|
13148
|
+
function valuesBag(row) {
|
|
13149
|
+
const bag = {};
|
|
13150
|
+
for (const inp of row.inputs) bag[inp.key] = row.values[inp.key] ?? inp.defval;
|
|
13151
|
+
return bag;
|
|
13152
|
+
}
|
|
13057
13153
|
function groupInputs(inputs) {
|
|
13058
13154
|
const order = [];
|
|
13059
13155
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -13516,6 +13612,8 @@ var InputsUI = class {
|
|
|
13516
13612
|
host: this.container,
|
|
13517
13613
|
items,
|
|
13518
13614
|
placement: "bottom-start",
|
|
13615
|
+
// Right-click action menu — checked state reads as a leading ✓ (see the kit Menu).
|
|
13616
|
+
checkmarks: true,
|
|
13519
13617
|
onSelect: (itemId) => {
|
|
13520
13618
|
if (itemId !== "values" || !this.rowMenuId) return;
|
|
13521
13619
|
const target = this.rows.get(this.rowMenuId);
|
|
@@ -13851,6 +13949,8 @@ var InputsUI = class {
|
|
|
13851
13949
|
syncRowActions(row) {
|
|
13852
13950
|
const open2 = row.highlighted;
|
|
13853
13951
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
13952
|
+
if (open2) row.el.appendChild(row.statusEl);
|
|
13953
|
+
else row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
13854
13954
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
13855
13955
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
13856
13956
|
if (child === row.extrasEl) {
|
|
@@ -14094,6 +14194,148 @@ var PaneControls = class {
|
|
|
14094
14194
|
}
|
|
14095
14195
|
};
|
|
14096
14196
|
|
|
14197
|
+
// src/renderers/native/chrome/axisLayout.ts
|
|
14198
|
+
var AXIS_MASTER_W = 64;
|
|
14199
|
+
var AXIS_MERGED_W = 56;
|
|
14200
|
+
var PANE_SEPARATOR_PX = 3;
|
|
14201
|
+
function axisColumnX(dataW, column) {
|
|
14202
|
+
return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
|
|
14203
|
+
}
|
|
14204
|
+
|
|
14205
|
+
// src/renderers/native/chrome/AxisScaleButtons.ts
|
|
14206
|
+
var STYLE_ID2 = "vela-axis-scale-buttons";
|
|
14207
|
+
var BTN_PX = 18;
|
|
14208
|
+
var BTN_GAP = 4;
|
|
14209
|
+
var AXIS_BORDER = 1;
|
|
14210
|
+
var CLUSTER_W = AXIS_MASTER_W - AXIS_BORDER;
|
|
14211
|
+
var PAD_TOP = 8;
|
|
14212
|
+
var PAD_BOTTOM = 6;
|
|
14213
|
+
var CLUSTER_H = BTN_PX + PAD_TOP + PAD_BOTTOM;
|
|
14214
|
+
var MIN_PANE_H = 48;
|
|
14215
|
+
function ensureStyles2() {
|
|
14216
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID2)) return;
|
|
14217
|
+
const st = document.createElement("style");
|
|
14218
|
+
st.id = STYLE_ID2;
|
|
14219
|
+
st.textContent = `
|
|
14220
|
+
.vela-axis-btn{display:inline-flex;align-items:center;justify-content:center;padding:0;border:none;border-radius:var(--vela-radius-sm);background:transparent;cursor:pointer;font:600 10px/1 -apple-system,Segoe UI,sans-serif;color:var(--vela-fg-muted);}
|
|
14221
|
+
.vela-axis-btn:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
14222
|
+
.vela-axis-on,.vela-axis-on:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
14223
|
+
`;
|
|
14224
|
+
document.head.appendChild(st);
|
|
14225
|
+
}
|
|
14226
|
+
var AxisScaleButtons = class {
|
|
14227
|
+
constructor(plot, theme, deps) {
|
|
14228
|
+
this.plot = plot;
|
|
14229
|
+
this.theme = theme;
|
|
14230
|
+
this.deps = deps;
|
|
14231
|
+
this.tipDisposers = [];
|
|
14232
|
+
this.hoverPaneId = null;
|
|
14233
|
+
/** Reveal over the master scale column of the pane under the cursor (mouse only —
|
|
14234
|
+
* touch has no hover, and a drag-rescale on the axis must not sprout buttons). */
|
|
14235
|
+
this.onPlotMove = (e) => {
|
|
14236
|
+
if (e.pointerType !== "mouse") return;
|
|
14237
|
+
const rect = this.plot.getBoundingClientRect();
|
|
14238
|
+
const x = e.clientX - rect.left;
|
|
14239
|
+
const gutterLeft = rect.width - this.deps.rightAxis();
|
|
14240
|
+
let hit = null;
|
|
14241
|
+
if (x >= gutterLeft && x <= gutterLeft + AXIS_MASTER_W) {
|
|
14242
|
+
const y = e.clientY - rect.top;
|
|
14243
|
+
for (const p of this.deps.panes()) {
|
|
14244
|
+
if (p.collapsed || p.height < MIN_PANE_H) continue;
|
|
14245
|
+
if (y >= p.top && y <= p.top + p.height) {
|
|
14246
|
+
hit = p.id;
|
|
14247
|
+
break;
|
|
14248
|
+
}
|
|
14249
|
+
}
|
|
14250
|
+
}
|
|
14251
|
+
this.setHoverPane(hit);
|
|
14252
|
+
if (hit) this.sync();
|
|
14253
|
+
};
|
|
14254
|
+
this.onPlotLeave = () => this.setHoverPane(null);
|
|
14255
|
+
ensureStyles2();
|
|
14256
|
+
this.cluster = document.createElement("div");
|
|
14257
|
+
Object.assign(this.cluster.style, {
|
|
14258
|
+
position: "absolute",
|
|
14259
|
+
display: "none",
|
|
14260
|
+
justifyContent: "center",
|
|
14261
|
+
alignItems: "center",
|
|
14262
|
+
gap: `${BTN_GAP}px`,
|
|
14263
|
+
padding: `${PAD_TOP}px 0 ${PAD_BOTTOM}px`,
|
|
14264
|
+
width: `${CLUSTER_W}px`,
|
|
14265
|
+
boxSizing: "border-box",
|
|
14266
|
+
zIndex: "6",
|
|
14267
|
+
pointerEvents: "auto"
|
|
14268
|
+
});
|
|
14269
|
+
this.applyTheme();
|
|
14270
|
+
this.autoBtn = this.button("A", "Auto (fits data to screen)", () => this.deps.onToggleAuto(this.hoverPaneId ?? ""));
|
|
14271
|
+
this.logBtn = this.button("L", "Logarithmic scale", () => this.deps.onToggleLog(this.hoverPaneId ?? ""));
|
|
14272
|
+
this.cluster.append(this.autoBtn, this.logBtn);
|
|
14273
|
+
this.plot.appendChild(this.cluster);
|
|
14274
|
+
this.plot.addEventListener("pointermove", this.onPlotMove);
|
|
14275
|
+
this.plot.addEventListener("pointerleave", this.onPlotLeave);
|
|
14276
|
+
}
|
|
14277
|
+
button(label, title, onClick) {
|
|
14278
|
+
const b = document.createElement("button");
|
|
14279
|
+
b.type = "button";
|
|
14280
|
+
b.setAttribute("aria-label", title);
|
|
14281
|
+
b.textContent = label;
|
|
14282
|
+
b.className = "vela-axis-btn";
|
|
14283
|
+
Object.assign(b.style, { width: `${BTN_PX}px`, height: `${BTN_PX}px` });
|
|
14284
|
+
this.tipDisposers.push(attachChromeTooltip(b, {
|
|
14285
|
+
host: this.plot,
|
|
14286
|
+
theme: () => this.theme,
|
|
14287
|
+
text: () => title,
|
|
14288
|
+
placement: "above"
|
|
14289
|
+
}));
|
|
14290
|
+
b.addEventListener("click", (e) => {
|
|
14291
|
+
e.stopPropagation();
|
|
14292
|
+
onClick();
|
|
14293
|
+
this.sync();
|
|
14294
|
+
});
|
|
14295
|
+
return b;
|
|
14296
|
+
}
|
|
14297
|
+
/** Re-tint the chip when the plot background/border change (theme swap, config edit). */
|
|
14298
|
+
setTheme(theme) {
|
|
14299
|
+
this.theme = theme;
|
|
14300
|
+
this.applyTheme();
|
|
14301
|
+
}
|
|
14302
|
+
/** The strip is SOLID chart background: the buttons cover axis labels, never blend with them. */
|
|
14303
|
+
applyTheme() {
|
|
14304
|
+
this.cluster.style.background = this.theme.background;
|
|
14305
|
+
}
|
|
14306
|
+
setHoverPane(paneId) {
|
|
14307
|
+
if (this.hoverPaneId === paneId) return;
|
|
14308
|
+
this.hoverPaneId = paneId;
|
|
14309
|
+
this.reposition();
|
|
14310
|
+
}
|
|
14311
|
+
/** Re-place (or hide) the cluster after a hover or pane-layout change. */
|
|
14312
|
+
reposition() {
|
|
14313
|
+
const pane = this.hoverPaneId ? this.deps.panes().find((p) => p.id === this.hoverPaneId) : void 0;
|
|
14314
|
+
if (!pane || pane.collapsed || pane.height < MIN_PANE_H) {
|
|
14315
|
+
this.cluster.style.display = "none";
|
|
14316
|
+
return;
|
|
14317
|
+
}
|
|
14318
|
+
this.cluster.style.right = `${this.deps.rightAxis() - AXIS_MASTER_W}px`;
|
|
14319
|
+
this.cluster.style.top = `${pane.top + pane.height - CLUSTER_H}px`;
|
|
14320
|
+
this.cluster.style.display = "flex";
|
|
14321
|
+
this.sync();
|
|
14322
|
+
}
|
|
14323
|
+
/** Reflect the hovered pane's current auto/log state on the buttons. */
|
|
14324
|
+
sync() {
|
|
14325
|
+
const pane = this.deps.panes().find((p) => p.id === this.hoverPaneId);
|
|
14326
|
+
if (!pane) return;
|
|
14327
|
+
this.autoBtn.classList.toggle("vela-axis-on", pane.auto);
|
|
14328
|
+
this.logBtn.classList.toggle("vela-axis-on", pane.log);
|
|
14329
|
+
}
|
|
14330
|
+
destroy() {
|
|
14331
|
+
this.plot.removeEventListener("pointermove", this.onPlotMove);
|
|
14332
|
+
this.plot.removeEventListener("pointerleave", this.onPlotLeave);
|
|
14333
|
+
for (const dispose of this.tipDisposers) dispose();
|
|
14334
|
+
this.tipDisposers.length = 0;
|
|
14335
|
+
this.cluster.remove();
|
|
14336
|
+
}
|
|
14337
|
+
};
|
|
14338
|
+
|
|
14097
14339
|
// src/renderers/shared/TableOverlay.ts
|
|
14098
14340
|
var SIZE_PX3 = {
|
|
14099
14341
|
auto: 13,
|
|
@@ -14451,6 +14693,8 @@ var BASELINE_BOTTOM_LINE = BEARISH;
|
|
|
14451
14693
|
var BASELINE_FILL_ALPHA = 0.25;
|
|
14452
14694
|
var BASELINE_FILL_ALPHA_FAR = 0.05;
|
|
14453
14695
|
var BASELINE_LEVEL_DEFAULT = 50;
|
|
14696
|
+
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
14697
|
+
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
14454
14698
|
function defaultChartStyle() {
|
|
14455
14699
|
return {
|
|
14456
14700
|
chartTypes: {},
|
|
@@ -14481,7 +14725,8 @@ function defaultChartStyle() {
|
|
|
14481
14725
|
bottomFillColor2: null,
|
|
14482
14726
|
width: 2,
|
|
14483
14727
|
baselineLevel: BASELINE_LEVEL_DEFAULT
|
|
14484
|
-
}
|
|
14728
|
+
},
|
|
14729
|
+
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
|
|
14485
14730
|
};
|
|
14486
14731
|
}
|
|
14487
14732
|
var LINE_STYLES = ["solid", "dashed", "dotted"];
|
|
@@ -14615,6 +14860,7 @@ function mergeConfig(base, patch) {
|
|
|
14615
14860
|
const area = asObject(p.area);
|
|
14616
14861
|
const baseline = asObject(p.baseline);
|
|
14617
14862
|
const series = asObject(p.series);
|
|
14863
|
+
const sessions = asObject(p.sessions);
|
|
14618
14864
|
const stacking = asObject(p.stacking);
|
|
14619
14865
|
const stackSeries = asObject(stacking.series);
|
|
14620
14866
|
return {
|
|
@@ -14707,6 +14953,10 @@ function mergeConfig(base, patch) {
|
|
|
14707
14953
|
baseline: series.baseline === null ? null : isNum(series.baseline) ? series.baseline : base.series.baseline,
|
|
14708
14954
|
spacing: isNum(series.spacing) ? clampSpacing(series.spacing) : base.series.spacing
|
|
14709
14955
|
},
|
|
14956
|
+
sessions: {
|
|
14957
|
+
premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
|
|
14958
|
+
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
|
|
14959
|
+
},
|
|
14710
14960
|
stacking: {
|
|
14711
14961
|
candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
|
|
14712
14962
|
// Additive like `chartTypes`: a patch names only the ids it carries; the rest keep
|
|
@@ -14953,6 +15203,8 @@ var SceneGraph = class {
|
|
|
14953
15203
|
this.tradeMarkers = defaultTradeMarkersState();
|
|
14954
15204
|
/** Renderer-owned shaded time bands (session highlighting), behind grid + data. */
|
|
14955
15205
|
this.highlights = [];
|
|
15206
|
+
/** Pre/post-market bands pushed by the host (`sessionZones` feature); null ⇒ no sessions. */
|
|
15207
|
+
this.sessionZones = null;
|
|
14956
15208
|
/** Draw-order key of the price candles, relative to indicator series z (see `seriesZ`).
|
|
14957
15209
|
* Indicators with z below this draw BEHIND the candles; at/above draw in front.
|
|
14958
15210
|
* Default 0 with indicators mounting at z < 0 ⇒ the price reads on top of every overlay,
|
|
@@ -14987,6 +15239,15 @@ var SceneGraph = class {
|
|
|
14987
15239
|
if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
|
|
14988
15240
|
return this.orderedCache;
|
|
14989
15241
|
}
|
|
15242
|
+
/** The session zones resolved into colored bands (pre/post-market washes from the
|
|
15243
|
+
* config's session colors) — consumed by the same painting path as {@link highlights}. */
|
|
15244
|
+
sessionHighlightBands() {
|
|
15245
|
+
if (!this.sessionZones) return [];
|
|
15246
|
+
const out = [];
|
|
15247
|
+
for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
|
|
15248
|
+
for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
|
|
15249
|
+
return out;
|
|
15250
|
+
}
|
|
14990
15251
|
indicatorsForPane(paneId) {
|
|
14991
15252
|
const out = [];
|
|
14992
15253
|
for (const model of this.indicators.values()) if (model.paneId === paneId) out.push(model);
|
|
@@ -15070,6 +15331,12 @@ var SceneGraph = class {
|
|
|
15070
15331
|
assignIndicatorZ(id) {
|
|
15071
15332
|
if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.bottomZ() - 1);
|
|
15072
15333
|
}
|
|
15334
|
+
/** Mount-time default for a LAYER-BACKED native (it paints on a canvas stacked above the
|
|
15335
|
+
* data canvas by default): top of the stack, so the recorded order tells the truth from
|
|
15336
|
+
* the first frame. Keeps an existing key, so a restored stack survives the remount. */
|
|
15337
|
+
assignIndicatorZTop(id) {
|
|
15338
|
+
if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.topZ() + 1);
|
|
15339
|
+
}
|
|
15073
15340
|
forgetIndicatorZ(id) {
|
|
15074
15341
|
this.seriesZ.delete(id);
|
|
15075
15342
|
}
|
|
@@ -15917,10 +16184,12 @@ var WebGL2Backend = class {
|
|
|
15917
16184
|
}
|
|
15918
16185
|
}
|
|
15919
16186
|
}
|
|
15920
|
-
/** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
|
|
16187
|
+
/** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
|
|
16188
|
+
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
15921
16189
|
emitHighlights(b, scene, pane, coords) {
|
|
15922
|
-
|
|
15923
|
-
|
|
16190
|
+
const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
|
|
16191
|
+
if (bands.length === 0) return;
|
|
16192
|
+
for (const band of bands) {
|
|
15924
16193
|
const x1 = coords.timeToX(band.from);
|
|
15925
16194
|
const x2 = coords.timeToX(band.to);
|
|
15926
16195
|
if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
|
|
@@ -17894,10 +18163,12 @@ var Canvas2dBackend = class {
|
|
|
17894
18163
|
ctx.fillStyle = bg.color;
|
|
17895
18164
|
ctx.fillRect(x1, pane.bounds.top, x2 - x1, pane.bounds.height);
|
|
17896
18165
|
}
|
|
17897
|
-
/** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
|
|
18166
|
+
/** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
|
|
18167
|
+
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
17898
18168
|
drawHighlights(ctx, scene, coords) {
|
|
17899
|
-
|
|
17900
|
-
|
|
18169
|
+
const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
|
|
18170
|
+
if (bands.length === 0) return;
|
|
18171
|
+
for (const band of bands) {
|
|
17901
18172
|
const x1 = coords.timeToX(band.from);
|
|
17902
18173
|
const x2 = coords.timeToX(band.to);
|
|
17903
18174
|
if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
|
|
@@ -18671,14 +18942,6 @@ var DrawingSceneRenderer = class {
|
|
|
18671
18942
|
}
|
|
18672
18943
|
};
|
|
18673
18944
|
|
|
18674
|
-
// src/renderers/native/chrome/axisLayout.ts
|
|
18675
|
-
var AXIS_MASTER_W = 64;
|
|
18676
|
-
var AXIS_MERGED_W = 56;
|
|
18677
|
-
var PANE_SEPARATOR_PX = 3;
|
|
18678
|
-
function axisColumnX(dataW, column) {
|
|
18679
|
-
return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
|
|
18680
|
-
}
|
|
18681
|
-
|
|
18682
18945
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
18683
18946
|
var ChromeRenderer = class {
|
|
18684
18947
|
constructor() {
|
|
@@ -19565,6 +19828,7 @@ var SettingsDialog = class {
|
|
|
19565
19828
|
for (const hr of hs.rows) {
|
|
19566
19829
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
19567
19830
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
19831
|
+
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
19568
19832
|
else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options.map((o) => [o, o]), (v) => hr.set(v)));
|
|
19569
19833
|
}
|
|
19570
19834
|
}
|
|
@@ -22289,14 +22553,14 @@ function btn(label, primary, onClick) {
|
|
|
22289
22553
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
22290
22554
|
var BTN = 30;
|
|
22291
22555
|
var ICON = 21;
|
|
22292
|
-
var
|
|
22556
|
+
var STYLE_ID3 = "vela-drawing-popup-styles";
|
|
22293
22557
|
var STYLE_REV = "3";
|
|
22294
|
-
function
|
|
22558
|
+
function ensureStyles3() {
|
|
22295
22559
|
if (typeof document === "undefined") return;
|
|
22296
|
-
const existing = document.getElementById(
|
|
22560
|
+
const existing = document.getElementById(STYLE_ID3);
|
|
22297
22561
|
if (existing?.dataset.rev === STYLE_REV) return;
|
|
22298
22562
|
const s = existing ?? document.createElement("style");
|
|
22299
|
-
s.id =
|
|
22563
|
+
s.id = STYLE_ID3;
|
|
22300
22564
|
s.dataset.rev = STYLE_REV;
|
|
22301
22565
|
s.textContent = `
|
|
22302
22566
|
.vela-dpop-btn{background:transparent;color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
@@ -22353,7 +22617,7 @@ var DrawingSettingsPopup = class {
|
|
|
22353
22617
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
22354
22618
|
open(drawing, anchor, actions, onClose) {
|
|
22355
22619
|
this.close();
|
|
22356
|
-
|
|
22620
|
+
ensureStyles3();
|
|
22357
22621
|
this.onClose = onClose ?? null;
|
|
22358
22622
|
const t = this.theme;
|
|
22359
22623
|
const schema = drawing.schema();
|
|
@@ -23081,7 +23345,7 @@ var DrawingToolbar = class {
|
|
|
23081
23345
|
this.onCollapse = options.onCollapse ?? (() => {
|
|
23082
23346
|
});
|
|
23083
23347
|
this.onDrawingsSync = options.onDrawingsSync ?? null;
|
|
23084
|
-
|
|
23348
|
+
ensureStyles4();
|
|
23085
23349
|
this.root = document.createElement("div");
|
|
23086
23350
|
this.root.className = "vela-dtb";
|
|
23087
23351
|
this.styleRoot();
|
|
@@ -23564,13 +23828,13 @@ var DrawingToolbar = class {
|
|
|
23564
23828
|
}
|
|
23565
23829
|
};
|
|
23566
23830
|
var MAGNET_ID = "__magnet__";
|
|
23567
|
-
var
|
|
23568
|
-
function
|
|
23831
|
+
var STYLE_ID4 = "vela-drawing-toolbar-styles";
|
|
23832
|
+
function ensureStyles4() {
|
|
23569
23833
|
if (typeof document === "undefined") return;
|
|
23570
|
-
let s = document.getElementById(
|
|
23834
|
+
let s = document.getElementById(STYLE_ID4);
|
|
23571
23835
|
if (!s) {
|
|
23572
23836
|
s = document.createElement("style");
|
|
23573
|
-
s.id =
|
|
23837
|
+
s.id = STYLE_ID4;
|
|
23574
23838
|
document.head.appendChild(s);
|
|
23575
23839
|
}
|
|
23576
23840
|
s.textContent = `
|
|
@@ -24829,13 +25093,26 @@ function rendererLayers() {
|
|
|
24829
25093
|
return [...registry3.values()];
|
|
24830
25094
|
}
|
|
24831
25095
|
|
|
25096
|
+
// src/renderers/native/core/layerStacking.ts
|
|
25097
|
+
function stackLayers(entries, candleZ) {
|
|
25098
|
+
const keyed = entries.map((e) => ({
|
|
25099
|
+
id: e.id,
|
|
25100
|
+
key: e.ownerZ ?? (e.placement === "below-data" ? -Infinity : candleZ)
|
|
25101
|
+
}));
|
|
25102
|
+
keyed.sort((a, b) => a.key - b.key);
|
|
25103
|
+
const below = [];
|
|
25104
|
+
const above = [];
|
|
25105
|
+
for (const k of keyed) (k.key < candleZ ? below : above).push(k.id);
|
|
25106
|
+
return { below, above };
|
|
25107
|
+
}
|
|
25108
|
+
|
|
24832
25109
|
// src/renderers/native/chrome/luxalgo-logos.ts
|
|
24833
25110
|
var LUXALGO_SYMBOL_SVG = '<svg viewBox="0 0 45 40" aria-hidden="true"><g fill="currentColor"><path d="m40.25 38 4.58-7.998L28.802 2l-16.03 28 9.16-.001 6.87-12z"/><path d="M34.525 32.002 9.33 31.997 27.655 0h-9.158L.18 31.993 4.759 40h34.347z"/></g></svg>';
|
|
24834
25111
|
var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path fill="currentColor" fill-rule="evenodd" d="M 614 94.500 L 614 189 633.500 189 L 653 189 653 94.500 L 653 0 633.500 0 L 614 0 614 94.500 M 0 97.500 L 0 189 63.023 189 L 126.045 189 125.773 170.750 L 125.500 152.500 84.250 152.239 L 43 151.978 43 78.989 L 43 6 21.500 6 L 0 6 0 97.500 M 450.387 96.451 C 421.871 146.253, 398.306 186.994, 398.020 186.987 C 397.423 186.972, 362 124.724, 362 123.691 C 362 123.316, 370.100 108.818, 380 91.472 C 389.900 74.127, 398 59.500, 398 58.968 C 398 58.380, 391.007 58, 380.210 58 L 362.420 58 348.060 83.138 C 334.400 107.052, 333.637 108.160, 332.399 105.888 C 331.682 104.575, 325.287 93.375, 318.187 81 L 305.277 58.500 287.139 58.227 C 277.162 58.078, 269 58.186, 269 58.469 C 269 58.751, 277.154 73.274, 287.120 90.741 C 297.086 108.209, 305.261 122.983, 305.287 123.574 C 305.312 124.164, 297.318 138.789, 287.522 156.074 C 277.725 173.358, 269.524 187.838, 269.296 188.250 C 269.068 188.662, 276.977 189, 286.870 189 L 304.859 189 319.163 163.959 C 331.321 142.674, 333.647 139.165, 334.670 140.563 C 335.331 141.468, 341.864 152.737, 349.186 165.606 L 362.500 189.003 402.442 188.752 L 442.384 188.500 472.080 136.695 C 488.413 108.203, 501.939 84.729, 502.138 84.531 C 502.337 84.333, 516 107.757, 532.500 136.583 L 562.500 188.994 584.750 188.997 C 596.987 188.999, 607 188.854, 607 188.675 C 607 188.319, 503.014 6.686, 502.532 6.201 C 502.369 6.037, 478.904 46.649, 450.387 96.451 M 717.500 55.017 C 697.175 57.389, 680.641 68.752, 672.036 86.264 C 665.866 98.818, 663.840 108.024, 663.840 123.500 C 663.840 133.409, 664.328 138.355, 665.892 144.300 C 672.076 167.810, 689.191 184.757, 711.529 189.489 C 730.549 193.518, 747.264 189.540, 761.282 177.648 C 764.944 174.542, 768.178 172, 768.470 172 C 769.345 172, 769.030 185.018, 767.979 192.285 C 765.618 208.613, 755.148 218.672, 738.473 220.632 C 732.586 221.324, 723.136 219.599, 717.709 216.843 C 712.693 214.295, 706.636 207.946, 703.972 202.443 C 702.789 199.999, 701.749 198, 701.661 198.001 C 700.201 198.007, 665.596 205.071, 665.230 205.437 C 664.326 206.341, 669.156 218.313, 672.607 223.721 C 682.146 238.672, 700.008 248.257, 723.500 251.033 C 758.132 255.124, 788.930 241.875, 800.287 218 C 806.923 204.047, 807 203.001, 807 126.542 L 807 58 788.500 58 L 770 58 770 66.500 C 770 71.175, 769.641 75, 769.203 75 C 768.765 75, 766.627 73.142, 764.453 70.872 C 752.805 58.708, 735.614 52.903, 717.500 55.017 M 878.500 55.031 C 850.107 59.412, 830.755 75.064, 822.285 100.500 C 819.905 107.644, 819.614 110.051, 819.565 123 C 819.501 139.581, 820.786 145.825, 826.763 158 C 832.104 168.880, 842.842 179.424, 854.077 184.822 C 867.651 191.343, 873.132 192.462, 891.500 192.462 C 909.868 192.462, 915.349 191.343, 928.923 184.822 C 944.653 177.265, 956.667 162.306, 961.710 144 C 963.074 139.048, 963.481 134.047, 963.418 123 C 963.346 110.140, 963.038 107.595, 960.694 100.500 C 953.325 78.193, 936.695 62.854, 913.500 56.969 C 904.262 54.625, 887.240 53.682, 878.500 55.031 M 134.015 102.750 C 134.027 139.250, 134.310 148.689, 135.549 153.947 C 141.139 177.663, 159.815 193, 183.105 193 C 196.453 193, 210.163 186.171, 217.936 175.650 C 219.826 173.093, 221.738 171, 222.186 171 C 222.634 171, 223 175.050, 223 180 L 223 189 242.507 189 L 262.014 189 261.757 123.750 L 261.500 58.500 242.500 58.500 L 223.500 58.500 223 99.500 C 222.513 139.406, 222.441 140.610, 220.289 144.634 C 209.729 164.377, 183.014 164.880, 174.299 145.500 C 172.677 141.895, 172.469 137.382, 172.190 99.750 L 171.880 58 152.940 58 L 134 58 134.015 102.750 M 729.932 87.998 C 719.311 89.821, 710.155 97.164, 705.141 107.880 C 702.851 112.772, 702.510 114.798, 702.505 123.521 C 702.500 132.834, 702.732 134.014, 705.787 140.218 C 709.761 148.290, 715.989 154.097, 723.853 157.063 C 731.409 159.912, 744.359 159.466, 750.795 156.136 C 767.289 147.601, 774.103 127.713, 767.019 108.780 C 761.433 93.851, 746.051 85.232, 729.932 87.998 M 883.972 88.348 C 865.749 92.310, 854.873 109.966, 858.119 130.316 C 860.933 147.963, 872.346 158.830, 889.084 159.799 C 900.714 160.472, 906.867 158.234, 914.650 150.497 C 919.856 145.323, 921.123 143.340, 922.900 137.587 C 925.412 129.456, 925.655 118.570, 923.489 111.275 C 918.479 94.405, 901.478 84.541, 883.972 88.348"/></svg>';
|
|
24835
25112
|
|
|
24836
25113
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
24837
25114
|
var ATTRIBUTION_URL = "https://luxalgo.com/vela";
|
|
24838
|
-
var
|
|
25115
|
+
var STYLE_ID5 = "vela-attribution-styles";
|
|
24839
25116
|
var CSS2 = `
|
|
24840
25117
|
.vela-attribution {
|
|
24841
25118
|
text-decoration: none;
|
|
@@ -24898,11 +25175,11 @@ var CSS2 = `
|
|
|
24898
25175
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
24899
25176
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
24900
25177
|
`;
|
|
24901
|
-
function
|
|
24902
|
-
let s = doc.getElementById(
|
|
25178
|
+
function ensureStyles5(doc) {
|
|
25179
|
+
let s = doc.getElementById(STYLE_ID5);
|
|
24903
25180
|
if (!s) {
|
|
24904
25181
|
s = doc.createElement("style");
|
|
24905
|
-
s.id =
|
|
25182
|
+
s.id = STYLE_ID5;
|
|
24906
25183
|
doc.head.appendChild(s);
|
|
24907
25184
|
}
|
|
24908
25185
|
s.textContent = CSS2;
|
|
@@ -24931,7 +25208,7 @@ function createCustomMark(doc, html, background) {
|
|
|
24931
25208
|
return el;
|
|
24932
25209
|
}
|
|
24933
25210
|
function createAttributionMark(doc, background) {
|
|
24934
|
-
|
|
25211
|
+
ensureStyles5(doc);
|
|
24935
25212
|
const a = doc.createElement("a");
|
|
24936
25213
|
a.className = "vela-attribution";
|
|
24937
25214
|
a.href = ATTRIBUTION_URL;
|
|
@@ -25329,6 +25606,8 @@ var NativeRenderer = class {
|
|
|
25329
25606
|
this.volumeRenderer = new VolumeRenderer();
|
|
25330
25607
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
25331
25608
|
this.extLayers = [];
|
|
25609
|
+
/** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
|
|
25610
|
+
this.layerOrderSig = "";
|
|
25332
25611
|
// The attribution mark (see chrome/AttributionMark + the NOTICE file): default-on;
|
|
25333
25612
|
// disabling requires an equivalent visible attribution elsewhere in the host UI.
|
|
25334
25613
|
this.attributionEl = null;
|
|
@@ -25443,6 +25722,8 @@ var NativeRenderer = class {
|
|
|
25443
25722
|
// total right-gutter width (master column + merged-scale columns)
|
|
25444
25723
|
this.paneControls = null;
|
|
25445
25724
|
// per-pane hover button cluster (top-right)
|
|
25725
|
+
this.axisScaleButtons = null;
|
|
25726
|
+
// A/L hover buttons at the bottom of each pane's price scale
|
|
25446
25727
|
this.paneActionCbs = /* @__PURE__ */ new Set();
|
|
25447
25728
|
// ── data window (crosshair OHLC + per-series readout, pulled by host panels) ──
|
|
25448
25729
|
this.hoverLogical = null;
|
|
@@ -25486,7 +25767,7 @@ var NativeRenderer = class {
|
|
|
25486
25767
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
25487
25768
|
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
25488
25769
|
this.name = "native";
|
|
25489
|
-
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
25770
|
+
this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "indicatorTitles", "indicatorValues"];
|
|
25490
25771
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
25491
25772
|
* so moving onto the button doesn't count as leaving). */
|
|
25492
25773
|
this.onScrollProximityMove = (e) => {
|
|
@@ -25602,6 +25883,9 @@ var NativeRenderer = class {
|
|
|
25602
25883
|
case "highlights":
|
|
25603
25884
|
this.scene.highlights = sanitizeHighlights(value);
|
|
25604
25885
|
break;
|
|
25886
|
+
case "sessionZones":
|
|
25887
|
+
this.scene.sessionZones = sanitizeSessionZones(value);
|
|
25888
|
+
break;
|
|
25605
25889
|
case "gridlines":
|
|
25606
25890
|
this.scene.showGrid = Boolean(value);
|
|
25607
25891
|
break;
|
|
@@ -25718,6 +26002,8 @@ var NativeRenderer = class {
|
|
|
25718
26002
|
return this.scene.indicatorZOrder();
|
|
25719
26003
|
case "highlights":
|
|
25720
26004
|
return this.scene.highlights;
|
|
26005
|
+
case "sessionZones":
|
|
26006
|
+
return this.scene.sessionZones;
|
|
25721
26007
|
case "gridlines":
|
|
25722
26008
|
return this.scene.showGrid;
|
|
25723
26009
|
case "axisLabels":
|
|
@@ -25927,7 +26213,8 @@ var NativeRenderer = class {
|
|
|
25927
26213
|
baselineLevel: s.baseline.baselineLevel
|
|
25928
26214
|
};
|
|
25929
26215
|
})(),
|
|
25930
|
-
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale }
|
|
26216
|
+
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
|
|
26217
|
+
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
|
|
25931
26218
|
};
|
|
25932
26219
|
}
|
|
25933
26220
|
/**
|
|
@@ -26009,6 +26296,7 @@ var NativeRenderer = class {
|
|
|
26009
26296
|
width: next.baseline.width,
|
|
26010
26297
|
baselineLevel: next.baseline.baselineLevel
|
|
26011
26298
|
};
|
|
26299
|
+
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
|
|
26012
26300
|
this.setPriceStyle(next.series.style);
|
|
26013
26301
|
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
26014
26302
|
this.scene.baselineValue = next.series.baseline;
|
|
@@ -26022,6 +26310,7 @@ var NativeRenderer = class {
|
|
|
26022
26310
|
}
|
|
26023
26311
|
this.inputsUI?.setTheme(this.theme);
|
|
26024
26312
|
this.paneControls?.setTheme(this.theme);
|
|
26313
|
+
this.axisScaleButtons?.setTheme(this.theme);
|
|
26025
26314
|
this.userDrawings?.setTheme(this.chromeTheme());
|
|
26026
26315
|
this.refreshScrollButtonTheme();
|
|
26027
26316
|
this.refreshAttributionColor();
|
|
@@ -26249,9 +26538,7 @@ var NativeRenderer = class {
|
|
|
26249
26538
|
if (el.getAttribute("data-vela-screenshot") === "under") rasterizeOverlay(ctx, el, frame);
|
|
26250
26539
|
}
|
|
26251
26540
|
}
|
|
26252
|
-
const
|
|
26253
|
-
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
26254
|
-
for (const canvas of [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas]) {
|
|
26541
|
+
for (const canvas of [...this.canvasPile(), this.chromeCanvas, this.drawingsCanvas]) {
|
|
26255
26542
|
if (canvas && canvas.width > 0 && canvas.height > 0) ctx.drawImage(canvas, 0, 0);
|
|
26256
26543
|
}
|
|
26257
26544
|
if (frame) {
|
|
@@ -26360,7 +26647,7 @@ var NativeRenderer = class {
|
|
|
26360
26647
|
this.surfaceBackground = theme.background;
|
|
26361
26648
|
this.surfaceTextColor = theme.textColor;
|
|
26362
26649
|
this.wrapper = document.createElement("div");
|
|
26363
|
-
Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair" });
|
|
26650
|
+
Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair", userSelect: "none", webkitUserSelect: "none" });
|
|
26364
26651
|
applyChromeTokens(this.wrapper, this.chromeTheme());
|
|
26365
26652
|
this.volumeCanvas = document.createElement("canvas");
|
|
26366
26653
|
Object.assign(this.volumeCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
@@ -26385,6 +26672,7 @@ var NativeRenderer = class {
|
|
|
26385
26672
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
26386
26673
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
26387
26674
|
this.plot.append(...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
26675
|
+
this.layerOrderSig = "";
|
|
26388
26676
|
this.wrapper.appendChild(this.plot);
|
|
26389
26677
|
this.factoryConfig = this.getConfig();
|
|
26390
26678
|
this.attributionEl = this.buildAttributionEl();
|
|
@@ -26529,6 +26817,15 @@ var NativeRenderer = class {
|
|
|
26529
26817
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
26530
26818
|
}
|
|
26531
26819
|
});
|
|
26820
|
+
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
26821
|
+
panes: () => this.axisScaleViews(),
|
|
26822
|
+
rightAxis: () => this.rightAxisW,
|
|
26823
|
+
onToggleAuto: (paneId) => this.togglePaneAuto(paneId),
|
|
26824
|
+
onToggleLog: (paneId) => {
|
|
26825
|
+
const pane = this.scene.panes.get(paneId);
|
|
26826
|
+
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
26827
|
+
}
|
|
26828
|
+
});
|
|
26532
26829
|
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
26533
26830
|
this.resizeObserver.observe(this.wrapper);
|
|
26534
26831
|
this.watchDpr();
|
|
@@ -26605,6 +26902,7 @@ var NativeRenderer = class {
|
|
|
26605
26902
|
this.applyBackground();
|
|
26606
26903
|
this.inputsUI.setTheme(theme);
|
|
26607
26904
|
this.paneControls?.setTheme(this.theme);
|
|
26905
|
+
this.axisScaleButtons?.setTheme(this.theme);
|
|
26608
26906
|
this.settingsDialog?.refreshConfig(this.getConfig());
|
|
26609
26907
|
this.syncThemeControl();
|
|
26610
26908
|
this.settingsDialog?.setTheme(this.theme);
|
|
@@ -26653,6 +26951,7 @@ var NativeRenderer = class {
|
|
|
26653
26951
|
this.liveRegion = null;
|
|
26654
26952
|
this.inputsUI?.destroy();
|
|
26655
26953
|
this.paneControls?.destroy();
|
|
26954
|
+
this.axisScaleButtons?.destroy();
|
|
26656
26955
|
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
26657
26956
|
this.tableOverlays.clear();
|
|
26658
26957
|
this.resizeObserver?.disconnect();
|
|
@@ -26902,7 +27201,8 @@ var NativeRenderer = class {
|
|
|
26902
27201
|
mountIndicator(model) {
|
|
26903
27202
|
this.scene.indicators.set(model.id, model);
|
|
26904
27203
|
this.refreshAnchorOffset(model);
|
|
26905
|
-
this.scene.
|
|
27204
|
+
if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
|
|
27205
|
+
else this.scene.assignIndicatorZ(model.id);
|
|
26906
27206
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
26907
27207
|
native: !!model.native,
|
|
26908
27208
|
...model.shorttitle ? { settingsTitle: model.title } : {}
|
|
@@ -27721,10 +28021,17 @@ var NativeRenderer = class {
|
|
|
27721
28021
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
27722
28022
|
for (const l of this.extLayers) {
|
|
27723
28023
|
if (!l.def.repaintOnCursor) continue;
|
|
27724
|
-
|
|
28024
|
+
const lp = this.layerPane(l.def.id) ?? pane;
|
|
28025
|
+
if (lp.collapsed) continue;
|
|
28026
|
+
l.instance.render(this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs));
|
|
27725
28027
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
27726
28028
|
}
|
|
27727
28029
|
}
|
|
28030
|
+
/** Blank one SDK layer canvas (a collapsed host pane suppresses the layer's painting). */
|
|
28031
|
+
clearLayerCanvas(canvas) {
|
|
28032
|
+
if (canvas.width === 0 || canvas.height === 0) return;
|
|
28033
|
+
canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
|
|
28034
|
+
}
|
|
27728
28035
|
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths). */
|
|
27729
28036
|
extLayerArgs(id, scale, bounds, nowMs) {
|
|
27730
28037
|
return {
|
|
@@ -27743,6 +28050,7 @@ var NativeRenderer = class {
|
|
|
27743
28050
|
}
|
|
27744
28051
|
/** Paint the below-data (L-1) + geometry (L0) + chrome (L1) layers from the current scene/coords. */
|
|
27745
28052
|
paintData() {
|
|
28053
|
+
this.syncLayerCanvasOrder();
|
|
27746
28054
|
this.stampScaleInvert();
|
|
27747
28055
|
this.backend.modelAlpha = this.modelAlpha;
|
|
27748
28056
|
this.backend.candleBodyAlpha = this.candleBodyAlpha;
|
|
@@ -27773,9 +28081,14 @@ var NativeRenderer = class {
|
|
|
27773
28081
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
27774
28082
|
let folded = null;
|
|
27775
28083
|
for (const l of this.extLayers) {
|
|
27776
|
-
const
|
|
28084
|
+
const lp = this.layerPane(l.def.id) ?? pane;
|
|
28085
|
+
if (lp.collapsed) {
|
|
28086
|
+
this.clearLayerCanvas(l.canvas);
|
|
28087
|
+
continue;
|
|
28088
|
+
}
|
|
28089
|
+
const args = this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs);
|
|
27777
28090
|
l.instance.render(args);
|
|
27778
|
-
folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
28091
|
+
if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
27779
28092
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
27780
28093
|
}
|
|
27781
28094
|
if (folded) {
|
|
@@ -27937,6 +28250,9 @@ var NativeRenderer = class {
|
|
|
27937
28250
|
pane.scaleTarget = { min: 0, max: maxVol / VOLUME_PANE_FILL_FRAC };
|
|
27938
28251
|
pane.axisFormat = "volume";
|
|
27939
28252
|
}
|
|
28253
|
+
} else if (this.layerNativesOwnPane(pane, masterModels)) {
|
|
28254
|
+
pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
28255
|
+
pane.percentBaseline = this.bars[i0]?.close ?? 0;
|
|
27940
28256
|
}
|
|
27941
28257
|
if (pane === pricePane && this.scene.tradeMarkers.visible && pane.bounds.height > 0) {
|
|
27942
28258
|
const th = this.tradesScaleHints(vr);
|
|
@@ -28023,6 +28339,59 @@ var NativeRenderer = class {
|
|
|
28023
28339
|
}
|
|
28024
28340
|
return null;
|
|
28025
28341
|
}
|
|
28342
|
+
/** The mounted native indicator that OWNS an SDK layer — the one whose type equals the
|
|
28343
|
+
* layer id (the id doubles as the data channel, so the pairing is the SDK's own
|
|
28344
|
+
* contract). Null for chart-type channels and while the owner is hidden (a hidden
|
|
28345
|
+
* indicator leaves the scene; its cleared data channel paints nothing anyway). */
|
|
28346
|
+
layerOwner(layerId) {
|
|
28347
|
+
for (const m of this.scene.indicators.values()) {
|
|
28348
|
+
if (m.native?.type === layerId) return m;
|
|
28349
|
+
}
|
|
28350
|
+
return null;
|
|
28351
|
+
}
|
|
28352
|
+
/** The pane an SDK layer paints on: its owner's pane, else the price pane. */
|
|
28353
|
+
layerPane(layerId) {
|
|
28354
|
+
const owner = this.layerOwner(layerId);
|
|
28355
|
+
const paneId = owner ? owner.paneId ?? PRICE_PANE_ID : PRICE_PANE_ID;
|
|
28356
|
+
return this.scene.panes.get(paneId) ?? null;
|
|
28357
|
+
}
|
|
28358
|
+
/** The SDK layer canvases split around the data canvas, each side back-to-front:
|
|
28359
|
+
* owned layers by their owner's z key against the candles' (an indicator restacked
|
|
28360
|
+
* below the candles takes its layer canvas along), unowned by declared placement. */
|
|
28361
|
+
orderedLayerCanvases() {
|
|
28362
|
+
const byId = new Map(this.extLayers.map((l) => [l.def.id, l.canvas]));
|
|
28363
|
+
const { below, above } = stackLayers(
|
|
28364
|
+
this.extLayers.map((l) => {
|
|
28365
|
+
const owner = this.layerOwner(l.def.id);
|
|
28366
|
+
return {
|
|
28367
|
+
id: l.def.id,
|
|
28368
|
+
placement: l.def.placement === "below-data" ? "below-data" : "above-data",
|
|
28369
|
+
ownerZ: owner ? this.scene.zOf(owner.id) : null
|
|
28370
|
+
};
|
|
28371
|
+
}),
|
|
28372
|
+
this.scene.candleZ
|
|
28373
|
+
);
|
|
28374
|
+
return { below: below.map((id) => byId.get(id)), above: above.map((id) => byId.get(id)) };
|
|
28375
|
+
}
|
|
28376
|
+
/** The full canvas pile in paint order (layers + data/volume/vpvr) — what the DOM
|
|
28377
|
+
* stacking and the screenshot compositor must both follow. */
|
|
28378
|
+
canvasPile() {
|
|
28379
|
+
const { below, above } = this.orderedLayerCanvases();
|
|
28380
|
+
return [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above];
|
|
28381
|
+
}
|
|
28382
|
+
/** Re-slot the SDK layer canvases in the plot when the computed order changed (a z
|
|
28383
|
+
* write, a restored config, an indicator mount/remove/restack). Runs at the top of
|
|
28384
|
+
* every data frame; a no-op when the signature is unchanged. Re-inserting an
|
|
28385
|
+
* absolutely-positioned, pointer-transparent canvas repaints nothing by itself. */
|
|
28386
|
+
syncLayerCanvasOrder() {
|
|
28387
|
+
if (this.extLayers.length === 0 || !this.plot) return;
|
|
28388
|
+
const { below, above } = this.orderedLayerCanvases();
|
|
28389
|
+
const sig = [...below.map((c) => this.extLayers.find((l) => l.canvas === c).def.id), "|", ...above.map((c) => this.extLayers.find((l) => l.canvas === c).def.id)].join(",");
|
|
28390
|
+
if (sig === this.layerOrderSig) return;
|
|
28391
|
+
this.layerOrderSig = sig;
|
|
28392
|
+
for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
|
|
28393
|
+
for (const c of above) this.plot.insertBefore(c, this.chromeCanvas);
|
|
28394
|
+
}
|
|
28026
28395
|
/** True when an active volume layer is this study pane's ONLY content — so its scale should
|
|
28027
28396
|
* come from volume, not the empty {0,1} placeholder. (In the price pane, or alongside a real
|
|
28028
28397
|
* series, volume stays a bottom overlay and the master scale wins.) */
|
|
@@ -28032,6 +28401,15 @@ var NativeRenderer = class {
|
|
|
28032
28401
|
if (this.nativeLayerPane("volume") !== pane) return false;
|
|
28033
28402
|
return masterModels.every((m) => m.native?.type === "volume");
|
|
28034
28403
|
}
|
|
28404
|
+
/** True when this study pane's master content is only SDK-layer natives (series-less
|
|
28405
|
+
* models whose type names a mounted layer) — its scale then follows the visible bars
|
|
28406
|
+
* (see the call site). Any real master series takes over the scale as usual. */
|
|
28407
|
+
layerNativesOwnPane(pane, masterModels) {
|
|
28408
|
+
if (pane.kind === "price" || masterModels.length === 0) return false;
|
|
28409
|
+
return masterModels.every(
|
|
28410
|
+
(m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type)
|
|
28411
|
+
);
|
|
28412
|
+
}
|
|
28035
28413
|
/** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
|
|
28036
28414
|
* band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
|
|
28037
28415
|
* Top-to-bottom order. Every pane is independent — the price pane from the scene setting,
|
|
@@ -28047,6 +28425,25 @@ var NativeRenderer = class {
|
|
|
28047
28425
|
invert: paneInvert(this.scene, p)
|
|
28048
28426
|
}));
|
|
28049
28427
|
}
|
|
28428
|
+
/** Thin projection of the panes for the axis A/L hover buttons (top-to-bottom order). */
|
|
28429
|
+
axisScaleViews() {
|
|
28430
|
+
return this.scene.orderedPanes().map((p) => ({
|
|
28431
|
+
id: p.id,
|
|
28432
|
+
top: p.bounds.top,
|
|
28433
|
+
height: p.bounds.height,
|
|
28434
|
+
collapsed: p.collapsed,
|
|
28435
|
+
auto: p.manualScale == null,
|
|
28436
|
+
log: paneLogScale(this.scene, p)
|
|
28437
|
+
}));
|
|
28438
|
+
}
|
|
28439
|
+
/** Toggle one pane's autoscale (the A axis button): off freezes the current window into
|
|
28440
|
+
* manual mode, on drops it — the per-pane twin of the chart-level `autoScale` feature. */
|
|
28441
|
+
togglePaneAuto(paneId) {
|
|
28442
|
+
const pane = this.scene.panes.get(paneId);
|
|
28443
|
+
if (!pane) return;
|
|
28444
|
+
pane.manualScale = pane.manualScale == null ? { ...pane.scale } : null;
|
|
28445
|
+
this.scheduler?.invalidate(4 /* Full */);
|
|
28446
|
+
}
|
|
28050
28447
|
/** Set one pane's axis mode. The price pane routes to the scene-level setting (persisted +
|
|
28051
28448
|
* keyboard shortcuts); a study pane keeps its own, so panes never affect each other. */
|
|
28052
28449
|
setPaneScaleMode(paneId, mode) {
|
|
@@ -28058,19 +28455,18 @@ var NativeRenderer = class {
|
|
|
28058
28455
|
}
|
|
28059
28456
|
this.scheduler?.invalidate(4 /* Full */);
|
|
28060
28457
|
}
|
|
28061
|
-
/** Set one pane's logarithmic flag
|
|
28062
|
-
*
|
|
28458
|
+
/** Set one pane's logarithmic flag; the price pane routes to the scene-level flag.
|
|
28459
|
+
* Log and auto are INDEPENDENT: a frozen (manual) window is kept — its price bounds
|
|
28460
|
+
* stay put and only its `log` tag flips, so the same range re-renders in the new
|
|
28461
|
+
* space instead of snapping back to autoscale. */
|
|
28063
28462
|
setPaneLog(paneId, log) {
|
|
28064
|
-
|
|
28065
|
-
|
|
28066
|
-
|
|
28067
|
-
if (
|
|
28068
|
-
|
|
28069
|
-
const p = this.scene.panes.get(paneId);
|
|
28070
|
-
if (!p) return;
|
|
28071
|
-
p.logScale = log;
|
|
28072
|
-
p.manualScale = null;
|
|
28463
|
+
const pane = paneId === PRICE_PANE_ID ? this.scene.orderedPanes().find((p) => p.kind === "price") : this.scene.panes.get(paneId);
|
|
28464
|
+
if (paneId === PRICE_PANE_ID) this.scene.logScale = log;
|
|
28465
|
+
else {
|
|
28466
|
+
if (!pane) return;
|
|
28467
|
+
pane.logScale = log;
|
|
28073
28468
|
}
|
|
28469
|
+
if (pane?.manualScale) pane.manualScale.log = log;
|
|
28074
28470
|
this.scheduler?.invalidate(4 /* Full */);
|
|
28075
28471
|
}
|
|
28076
28472
|
/** Flip one pane's axis (high at the bottom). The price pane routes to the scene-level flag
|
|
@@ -28169,6 +28565,7 @@ var NativeRenderer = class {
|
|
|
28169
28565
|
}
|
|
28170
28566
|
this.inputsUI?.reposition();
|
|
28171
28567
|
this.paneControls?.reposition();
|
|
28568
|
+
this.axisScaleButtons?.reposition();
|
|
28172
28569
|
this.repositionScrollButton();
|
|
28173
28570
|
this.positionAttribution();
|
|
28174
28571
|
this.publishPricePaneBounds();
|
|
@@ -28187,6 +28584,7 @@ var NativeRenderer = class {
|
|
|
28187
28584
|
}
|
|
28188
28585
|
this.inputsUI?.reposition();
|
|
28189
28586
|
this.paneControls?.reposition();
|
|
28587
|
+
this.axisScaleButtons?.reposition();
|
|
28190
28588
|
this.repositionScrollButton();
|
|
28191
28589
|
this.positionAttribution();
|
|
28192
28590
|
this.publishPricePaneBounds();
|
|
@@ -28394,6 +28792,22 @@ function sanitizeHighlights(value) {
|
|
|
28394
28792
|
}
|
|
28395
28793
|
return out.sort((a, b) => a.from - b.from);
|
|
28396
28794
|
}
|
|
28795
|
+
function sanitizeSessionZones(value) {
|
|
28796
|
+
if (value == null || typeof value !== "object") return null;
|
|
28797
|
+
const v = value;
|
|
28798
|
+
const windows = (raw) => {
|
|
28799
|
+
if (!Array.isArray(raw)) return [];
|
|
28800
|
+
const out = [];
|
|
28801
|
+
for (const w of raw) {
|
|
28802
|
+
if (!Array.isArray(w)) continue;
|
|
28803
|
+
const from = Number(w[0]);
|
|
28804
|
+
const to = Number(w[1]);
|
|
28805
|
+
if (Number.isFinite(from) && Number.isFinite(to) && to > from) out.push([from, to]);
|
|
28806
|
+
}
|
|
28807
|
+
return out.sort((a, b) => a[0] - b[0]);
|
|
28808
|
+
};
|
|
28809
|
+
return { pre: windows(v.pre), post: windows(v.post) };
|
|
28810
|
+
}
|
|
28397
28811
|
function clamp012(v) {
|
|
28398
28812
|
return Math.max(0, Math.min(1, v));
|
|
28399
28813
|
}
|
|
@@ -29426,7 +29840,7 @@ var Drawer = class {
|
|
|
29426
29840
|
};
|
|
29427
29841
|
|
|
29428
29842
|
// src/widget/layout-picker.ts
|
|
29429
|
-
var
|
|
29843
|
+
var STYLE_ID6 = "vela-widget-layout-picker-v14";
|
|
29430
29844
|
var CSS3 = `
|
|
29431
29845
|
.vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
|
|
29432
29846
|
.vela-lp {
|
|
@@ -29531,7 +29945,7 @@ var CSS3 = `
|
|
|
29531
29945
|
`;
|
|
29532
29946
|
var GRID = 4;
|
|
29533
29947
|
function layoutGridCanvas(doc) {
|
|
29534
|
-
injectStyles(
|
|
29948
|
+
injectStyles(STYLE_ID6, CSS3, doc);
|
|
29535
29949
|
const el = doc.createElement("div");
|
|
29536
29950
|
el.className = "vela-lp-grid";
|
|
29537
29951
|
const squares = [];
|
|
@@ -29571,7 +29985,7 @@ var LayoutPicker = class {
|
|
|
29571
29985
|
};
|
|
29572
29986
|
this.opts = opts;
|
|
29573
29987
|
this.doc = opts.host.ownerDocument;
|
|
29574
|
-
injectStyles(
|
|
29988
|
+
injectStyles(STYLE_ID6, CSS3, this.doc);
|
|
29575
29989
|
const doc = this.doc;
|
|
29576
29990
|
this.layer = doc.createElement("div");
|
|
29577
29991
|
this.layer.className = "vela-ui-layer vela-lp-layer";
|
|
@@ -29916,7 +30330,7 @@ function timeframeLabel(value) {
|
|
|
29916
30330
|
}
|
|
29917
30331
|
|
|
29918
30332
|
// src/widget/topbar.ts
|
|
29919
|
-
var
|
|
30333
|
+
var STYLE_ID7 = "vela-topbar";
|
|
29920
30334
|
var CSS4 = `
|
|
29921
30335
|
.vela-widget-topbar {
|
|
29922
30336
|
display: flex;
|
|
@@ -29973,12 +30387,14 @@ var CSS4 = `
|
|
|
29973
30387
|
color: var(--vela-fg-muted);
|
|
29974
30388
|
}
|
|
29975
30389
|
.vela-widget-tf-caret:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
30390
|
+
/* The merged trigger is a plain button (hover feedback only) \u2014 the highlight
|
|
30391
|
+
background marks the CURRENT chip among favorites, and a lone trigger with a
|
|
30392
|
+
permanent highlight would read as stuck-pressed. */
|
|
29976
30393
|
.vela-widget-tf-caret[data-solo='1'] {
|
|
29977
30394
|
width: auto;
|
|
29978
30395
|
padding: 0 6px 0 9px;
|
|
29979
30396
|
gap: 4px;
|
|
29980
30397
|
color: var(--vela-fg-bright);
|
|
29981
|
-
background: var(--vela-hover-strong);
|
|
29982
30398
|
font-size: 13px;
|
|
29983
30399
|
font-weight: 550;
|
|
29984
30400
|
white-space: nowrap;
|
|
@@ -30081,7 +30497,7 @@ var Topbar = class {
|
|
|
30081
30497
|
this.timeframe = opts.timeframe;
|
|
30082
30498
|
this.priceStyle = opts.priceStyle;
|
|
30083
30499
|
const doc = host.ownerDocument;
|
|
30084
|
-
injectStyles(
|
|
30500
|
+
injectStyles(STYLE_ID7, CSS4, doc);
|
|
30085
30501
|
this.el = doc.createElement("div");
|
|
30086
30502
|
this.el.className = "vela-widget-topbar";
|
|
30087
30503
|
this.symbolEl = doc.createElement("button");
|
|
@@ -30430,7 +30846,7 @@ function tickerIconEl(doc, base, name, className) {
|
|
|
30430
30846
|
}
|
|
30431
30847
|
|
|
30432
30848
|
// src/widget/statusline.ts
|
|
30433
|
-
var
|
|
30849
|
+
var STYLE_ID8 = "vela-widget-statusline";
|
|
30434
30850
|
var CSS5 = `
|
|
30435
30851
|
.vela-statusline {
|
|
30436
30852
|
position: absolute;
|
|
@@ -30622,7 +31038,7 @@ var Statusline = class {
|
|
|
30622
31038
|
this.fitMode = false;
|
|
30623
31039
|
this.fitRO = null;
|
|
30624
31040
|
const doc = host.ownerDocument;
|
|
30625
|
-
injectStyles(
|
|
31041
|
+
injectStyles(STYLE_ID8, CSS5, doc);
|
|
30626
31042
|
host.classList.add("vela-has-statusline");
|
|
30627
31043
|
this.el = doc.createElement("div");
|
|
30628
31044
|
this.el.className = "vela-statusline";
|
|
@@ -30895,11 +31311,70 @@ var MarketStatusTracker = class {
|
|
|
30895
31311
|
}
|
|
30896
31312
|
};
|
|
30897
31313
|
|
|
31314
|
+
// src/widget/session-shading.ts
|
|
31315
|
+
function deriveSessionZones(regular, extended) {
|
|
31316
|
+
const pre = [];
|
|
31317
|
+
const post = [];
|
|
31318
|
+
for (const [extStart, extEnd] of extended) {
|
|
31319
|
+
const inside = regular.filter(([s, e]) => e > extStart && s < extEnd).sort((a, b) => a[0] - b[0]);
|
|
31320
|
+
let cursor = extStart;
|
|
31321
|
+
for (const [regStart, regEnd] of inside) {
|
|
31322
|
+
if (regStart > cursor) pre.push([cursor, Math.min(regStart, extEnd)]);
|
|
31323
|
+
cursor = Math.max(cursor, regEnd);
|
|
31324
|
+
}
|
|
31325
|
+
if (cursor < extEnd) post.push([cursor, extEnd]);
|
|
31326
|
+
}
|
|
31327
|
+
return { pre, post };
|
|
31328
|
+
}
|
|
31329
|
+
var FETCH_AHEAD_MS2 = 10 * 864e5;
|
|
31330
|
+
var MIN_LOOKBACK_MS = 3 * 864e5;
|
|
31331
|
+
var MAX_LOOKBACK_MS = 120 * 864e5;
|
|
31332
|
+
var SessionShadingTracker = class {
|
|
31333
|
+
constructor(onZones) {
|
|
31334
|
+
this.onZones = onZones;
|
|
31335
|
+
/** Invalidates detached async work — bumped by every track()/stop(). */
|
|
31336
|
+
this.epoch = 0;
|
|
31337
|
+
}
|
|
31338
|
+
/** (Re)bind to a chart's data surface + market and evaluate once. */
|
|
31339
|
+
track(data, symbol, opts) {
|
|
31340
|
+
const my = ++this.epoch;
|
|
31341
|
+
void this.evaluate(my, data, symbol, opts);
|
|
31342
|
+
}
|
|
31343
|
+
stop() {
|
|
31344
|
+
this.epoch += 1;
|
|
31345
|
+
}
|
|
31346
|
+
async evaluate(my, data, symbol, opts) {
|
|
31347
|
+
const resolved = data.resolve(symbol);
|
|
31348
|
+
const provider = resolved ? data.providerInstance(resolved.provider) : void 0;
|
|
31349
|
+
const si = await data.symbolInfo(symbol).catch(() => void 0);
|
|
31350
|
+
if (my !== this.epoch) return;
|
|
31351
|
+
const hasSessions = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
31352
|
+
if (!provider?.getCalendar || !hasSessions || !resolved) {
|
|
31353
|
+
this.onZones(null);
|
|
31354
|
+
return;
|
|
31355
|
+
}
|
|
31356
|
+
if (opts.session !== "extended") {
|
|
31357
|
+
this.onZones({ pre: [], post: [] });
|
|
31358
|
+
return;
|
|
31359
|
+
}
|
|
31360
|
+
const now = Date.now();
|
|
31361
|
+
const lookback = Math.max(MIN_LOOKBACK_MS, Math.min(MAX_LOOKBACK_MS, opts.lookbackMs));
|
|
31362
|
+
const range = { from: now - lookback, to: now + FETCH_AHEAD_MS2 };
|
|
31363
|
+
const [regular, extended] = await Promise.all([
|
|
31364
|
+
provider.getCalendar(resolved.ticker, { ...range, session: "regular" }).catch(() => null),
|
|
31365
|
+
provider.getCalendar(resolved.ticker, { ...range, session: "extended" }).catch(() => null)
|
|
31366
|
+
]);
|
|
31367
|
+
if (my !== this.epoch) return;
|
|
31368
|
+
if (!regular || !extended) return;
|
|
31369
|
+
this.onZones(deriveSessionZones(regular, extended));
|
|
31370
|
+
}
|
|
31371
|
+
};
|
|
31372
|
+
|
|
30898
31373
|
// src/widget/watermark.ts
|
|
30899
31374
|
var MAX_FONT_PX = 36;
|
|
30900
31375
|
var MIN_FONT_PX = 12;
|
|
30901
31376
|
var FILL = 0.9;
|
|
30902
|
-
var
|
|
31377
|
+
var STYLE_ID9 = "vela-widget-watermark";
|
|
30903
31378
|
var CSS6 = `
|
|
30904
31379
|
.vela-watermark {
|
|
30905
31380
|
position: absolute;
|
|
@@ -30938,7 +31413,7 @@ var Watermark = class {
|
|
|
30938
31413
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
30939
31414
|
* fires during chart construction, before any subscriber can see it. */
|
|
30940
31415
|
this.loading = true;
|
|
30941
|
-
injectStyles(
|
|
31416
|
+
injectStyles(STYLE_ID9, CSS6, host.ownerDocument);
|
|
30942
31417
|
this.el = host.ownerDocument.createElement("div");
|
|
30943
31418
|
this.el.className = "vela-watermark";
|
|
30944
31419
|
this.el.dataset.velaScreenshot = "under";
|
|
@@ -31002,7 +31477,7 @@ var RANGE_PRESETS = [
|
|
|
31002
31477
|
{ id: "ALL", tf: "W", preset: "ALL", bars: 5e3 }
|
|
31003
31478
|
// everything the provider serves
|
|
31004
31479
|
];
|
|
31005
|
-
var
|
|
31480
|
+
var STYLE_ID10 = "vela-widget-bottombar";
|
|
31006
31481
|
var CSS7 = `
|
|
31007
31482
|
.vela-widget-bottombar {
|
|
31008
31483
|
display: flex;
|
|
@@ -31082,7 +31557,7 @@ var Bottombar = class {
|
|
|
31082
31557
|
this.timer = null;
|
|
31083
31558
|
this.timezone = opts.timezone;
|
|
31084
31559
|
const doc = host.ownerDocument;
|
|
31085
|
-
injectStyles(
|
|
31560
|
+
injectStyles(STYLE_ID10, CSS7, doc);
|
|
31086
31561
|
this.el = doc.createElement("div");
|
|
31087
31562
|
this.el.className = "vela-widget-bottombar";
|
|
31088
31563
|
for (const preset of RANGE_PRESETS) {
|
|
@@ -31109,7 +31584,8 @@ var Bottombar = class {
|
|
|
31109
31584
|
const session = doc.createElement("span");
|
|
31110
31585
|
session.className = "vela-bb-session";
|
|
31111
31586
|
this.sessionEl = session;
|
|
31112
|
-
session.
|
|
31587
|
+
session.style.display = "none";
|
|
31588
|
+
session.title = "Session \u2014 regular (RTH) vs extended (ETH) hours";
|
|
31113
31589
|
for (const [key, label] of [["regular", "RTH"], ["extended", "ETH"]]) {
|
|
31114
31590
|
const b = doc.createElement("button");
|
|
31115
31591
|
b.className = "vela-bb-session-btn" + (key === "regular" ? " is-active" : "");
|
|
@@ -31160,15 +31636,15 @@ var Bottombar = class {
|
|
|
31160
31636
|
}
|
|
31161
31637
|
/**
|
|
31162
31638
|
* Reflect the ACTIVE chart's session posture. `enabled: false` (a continuous
|
|
31163
|
-
* market, or metadata not landed yet)
|
|
31164
|
-
*
|
|
31165
|
-
*
|
|
31639
|
+
* market, or metadata not landed yet) HIDES the toggle entirely — RTH/ETH is
|
|
31640
|
+
* meaningless there. Enabled, the chips appear and the active one tracks the
|
|
31641
|
+
* chart's current session.
|
|
31166
31642
|
*/
|
|
31167
31643
|
setSession(state) {
|
|
31168
|
-
if (this.sessionEl) this.sessionEl.
|
|
31644
|
+
if (this.sessionEl) this.sessionEl.style.display = state.enabled ? "" : "none";
|
|
31169
31645
|
for (const [key, b] of this.sessionButtons) {
|
|
31170
31646
|
b.disabled = !state.enabled;
|
|
31171
|
-
b.classList.toggle("is-active", state.enabled ?
|
|
31647
|
+
b.classList.toggle("is-active", key === (state.enabled ? state.session : "regular"));
|
|
31172
31648
|
}
|
|
31173
31649
|
}
|
|
31174
31650
|
destroy() {
|
|
@@ -31241,7 +31717,7 @@ function filterSymbols(list, query, limit = 100) {
|
|
|
31241
31717
|
}
|
|
31242
31718
|
return [...prefix, ...substr, ...desc, ...venue].slice(0, limit);
|
|
31243
31719
|
}
|
|
31244
|
-
var
|
|
31720
|
+
var STYLE_ID11 = "vela-widget-symbolpicker";
|
|
31245
31721
|
var CSS8 = `
|
|
31246
31722
|
.vela-sp-searchrow {
|
|
31247
31723
|
display: flex;
|
|
@@ -31331,6 +31807,7 @@ var CSS8 = `
|
|
|
31331
31807
|
.vela-sp-badge[data-p='hyperliquid'] { color: #50d2c1; } /* palette-exempt: venue brand mark */
|
|
31332
31808
|
.vela-sp-empty { padding: var(--vela-space-3); color: var(--vela-fg-muted); text-align: center; }
|
|
31333
31809
|
`;
|
|
31810
|
+
var PAGE = 100;
|
|
31334
31811
|
var SymbolPicker = class {
|
|
31335
31812
|
constructor(opts) {
|
|
31336
31813
|
this.source = () => [];
|
|
@@ -31338,8 +31815,9 @@ var SymbolPicker = class {
|
|
|
31338
31815
|
this.highlighted = 0;
|
|
31339
31816
|
this.seed = "";
|
|
31340
31817
|
this.activeTab = "All";
|
|
31818
|
+
this.visible = PAGE;
|
|
31341
31819
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
31342
|
-
injectStyles(
|
|
31820
|
+
injectStyles(STYLE_ID11, CSS8, doc);
|
|
31343
31821
|
this.input = doc.createElement("input");
|
|
31344
31822
|
this.input.className = "vela-sp-input";
|
|
31345
31823
|
this.input.placeholder = "Search symbol\u2026";
|
|
@@ -31364,6 +31842,12 @@ var SymbolPicker = class {
|
|
|
31364
31842
|
}
|
|
31365
31843
|
this.list = doc.createElement("div");
|
|
31366
31844
|
this.list.className = "vela-sp-list";
|
|
31845
|
+
this.list.addEventListener("scroll", () => {
|
|
31846
|
+
if (this.rows.length < this.visible) return;
|
|
31847
|
+
if (this.list.scrollTop + this.list.clientHeight < this.list.scrollHeight - 200) return;
|
|
31848
|
+
this.visible += PAGE;
|
|
31849
|
+
this.grow();
|
|
31850
|
+
});
|
|
31367
31851
|
this.dialog = new Dialog({
|
|
31368
31852
|
title: "Symbol Search",
|
|
31369
31853
|
host: opts.host,
|
|
@@ -31429,11 +31913,15 @@ var SymbolPicker = class {
|
|
|
31429
31913
|
} else delete el.dataset.highlighted;
|
|
31430
31914
|
});
|
|
31431
31915
|
}
|
|
31432
|
-
|
|
31433
|
-
const doc = this.list.ownerDocument;
|
|
31916
|
+
computeRows() {
|
|
31434
31917
|
const TAB_TYPES = { Crypto: ["crypto"], Stocks: ["stock"], ETFs: ["etf"], Forex: ["forex"], Commodities: ["commodity"] };
|
|
31435
31918
|
const pool = this.activeTab === "All" ? this.source() : this.source().filter((s) => TAB_TYPES[this.activeTab]?.includes((s.type ?? "").toLowerCase()) || this.activeTab === "Crypto" && (s.type ?? "").toLowerCase() === "futures");
|
|
31436
|
-
|
|
31919
|
+
return filterSymbols(pool, this.input.value, this.visible);
|
|
31920
|
+
}
|
|
31921
|
+
refresh() {
|
|
31922
|
+
const doc = this.list.ownerDocument;
|
|
31923
|
+
this.visible = PAGE;
|
|
31924
|
+
this.rows = this.computeRows();
|
|
31437
31925
|
this.highlighted = 0;
|
|
31438
31926
|
this.list.replaceChildren();
|
|
31439
31927
|
if (!this.rows.length) {
|
|
@@ -31443,34 +31931,42 @@ var SymbolPicker = class {
|
|
|
31443
31931
|
this.list.appendChild(empty);
|
|
31444
31932
|
return;
|
|
31445
31933
|
}
|
|
31446
|
-
for (const s of this.rows)
|
|
31447
|
-
const row = doc.createElement("div");
|
|
31448
|
-
row.className = "vela-sp-row";
|
|
31449
|
-
row.dataset.ticker = s.ticker;
|
|
31450
|
-
const venue = s.prefix ?? s.provider;
|
|
31451
|
-
if (venue) row.dataset.venue = venue;
|
|
31452
|
-
const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
|
|
31453
|
-
const main = doc.createElement("span");
|
|
31454
|
-
main.className = "vela-sp-main";
|
|
31455
|
-
const t = doc.createElement("span");
|
|
31456
|
-
t.className = "vela-sp-ticker";
|
|
31457
|
-
t.textContent = s.ticker;
|
|
31458
|
-
const d = doc.createElement("span");
|
|
31459
|
-
d.className = "vela-sp-desc";
|
|
31460
|
-
d.textContent = s.description ?? (s.type ?? "");
|
|
31461
|
-
main.append(t, d);
|
|
31462
|
-
row.append(av, main);
|
|
31463
|
-
if (venue) {
|
|
31464
|
-
const badge = doc.createElement("span");
|
|
31465
|
-
badge.className = "vela-sp-badge";
|
|
31466
|
-
badge.dataset.p = s.provider ?? venue;
|
|
31467
|
-
badge.textContent = venue;
|
|
31468
|
-
row.appendChild(badge);
|
|
31469
|
-
}
|
|
31470
|
-
this.list.appendChild(row);
|
|
31471
|
-
}
|
|
31934
|
+
for (const s of this.rows) this.list.appendChild(this.rowEl(s));
|
|
31472
31935
|
this.renderHighlight();
|
|
31473
31936
|
}
|
|
31937
|
+
/** Append the page the grown `visible` just uncovered — rows already on screen stay put. */
|
|
31938
|
+
grow() {
|
|
31939
|
+
const already = this.rows.length;
|
|
31940
|
+
this.rows = this.computeRows();
|
|
31941
|
+
for (const s of this.rows.slice(already)) this.list.appendChild(this.rowEl(s));
|
|
31942
|
+
}
|
|
31943
|
+
rowEl(s) {
|
|
31944
|
+
const doc = this.list.ownerDocument;
|
|
31945
|
+
const row = doc.createElement("div");
|
|
31946
|
+
row.className = "vela-sp-row";
|
|
31947
|
+
row.dataset.ticker = s.ticker;
|
|
31948
|
+
const venue = s.prefix ?? s.provider;
|
|
31949
|
+
if (venue) row.dataset.venue = venue;
|
|
31950
|
+
const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
|
|
31951
|
+
const main = doc.createElement("span");
|
|
31952
|
+
main.className = "vela-sp-main";
|
|
31953
|
+
const t = doc.createElement("span");
|
|
31954
|
+
t.className = "vela-sp-ticker";
|
|
31955
|
+
t.textContent = s.ticker;
|
|
31956
|
+
const d = doc.createElement("span");
|
|
31957
|
+
d.className = "vela-sp-desc";
|
|
31958
|
+
d.textContent = s.description ?? (s.type ?? "");
|
|
31959
|
+
main.append(t, d);
|
|
31960
|
+
row.append(av, main);
|
|
31961
|
+
if (venue) {
|
|
31962
|
+
const badge = doc.createElement("span");
|
|
31963
|
+
badge.className = "vela-sp-badge";
|
|
31964
|
+
badge.dataset.p = s.provider ?? venue;
|
|
31965
|
+
badge.textContent = venue;
|
|
31966
|
+
row.appendChild(badge);
|
|
31967
|
+
}
|
|
31968
|
+
return row;
|
|
31969
|
+
}
|
|
31474
31970
|
};
|
|
31475
31971
|
|
|
31476
31972
|
// src/widget/object-tree-model.ts
|
|
@@ -31662,7 +32158,7 @@ function treeIsEmpty(panes) {
|
|
|
31662
32158
|
}
|
|
31663
32159
|
|
|
31664
32160
|
// src/widget/side-panel.ts
|
|
31665
|
-
var
|
|
32161
|
+
var STYLE_ID12 = "vela-widget-sidepanel";
|
|
31666
32162
|
var DEFAULT_PANEL_WIDTH = 280;
|
|
31667
32163
|
var DEFAULT_PANEL_MIN_WIDTH = 200;
|
|
31668
32164
|
var DEFAULT_PANEL_MAX_WIDTH = 640;
|
|
@@ -31766,7 +32262,7 @@ var SidePanel = class {
|
|
|
31766
32262
|
* a programmatic {@link setWidth}, so restoring a persisted width raises no change. */
|
|
31767
32263
|
this.onWidthChange = null;
|
|
31768
32264
|
const doc = host.ownerDocument;
|
|
31769
|
-
injectStyles(
|
|
32265
|
+
injectStyles(STYLE_ID12, CSS9, doc);
|
|
31770
32266
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
31771
32267
|
this.maxWidth = opts.resizable ? opts.maxWidth ?? DEFAULT_PANEL_MAX_WIDTH : Number.POSITIVE_INFINITY;
|
|
31772
32268
|
this.declaredWidth = clampPanelWidth(opts.width ?? DEFAULT_PANEL_WIDTH, this.minWidth, this.maxWidth);
|
|
@@ -31875,7 +32371,7 @@ var SidePanel = class {
|
|
|
31875
32371
|
};
|
|
31876
32372
|
|
|
31877
32373
|
// src/widget/object-tree.ts
|
|
31878
|
-
var
|
|
32374
|
+
var STYLE_ID13 = "vela-widget-objtree";
|
|
31879
32375
|
var CSS10 = `
|
|
31880
32376
|
/* One pane's block. The transparent border reserves the drop-target outline. */
|
|
31881
32377
|
.vela-ot-pane {
|
|
@@ -32114,7 +32610,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
32114
32610
|
this.menu = null;
|
|
32115
32611
|
this.menuActions = /* @__PURE__ */ new Map();
|
|
32116
32612
|
this.drag = null;
|
|
32117
|
-
injectStyles(
|
|
32613
|
+
injectStyles(STYLE_ID13, CSS10, host.ownerDocument);
|
|
32118
32614
|
this.body.addEventListener("contextmenu", (e) => this.onContextMenu(e));
|
|
32119
32615
|
this.body.addEventListener("pointerdown", (e) => this.onPointerDown(e));
|
|
32120
32616
|
}
|
|
@@ -33096,7 +33592,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
33096
33592
|
};
|
|
33097
33593
|
|
|
33098
33594
|
// src/widget/data-window.ts
|
|
33099
|
-
var
|
|
33595
|
+
var STYLE_ID14 = "vela-widget-datawindow";
|
|
33100
33596
|
var CSS11 = `
|
|
33101
33597
|
.vela-dw-group {
|
|
33102
33598
|
padding: 10px 8px 4px;
|
|
@@ -33109,6 +33605,9 @@ var CSS11 = `
|
|
|
33109
33605
|
letter-spacing: 0.08em;
|
|
33110
33606
|
}
|
|
33111
33607
|
.vela-dw-group:first-child { border-top: none; margin-top: 0; padding-top: 8px; }
|
|
33608
|
+
/* The readout is DATA, not chrome: selectable (an exception to the UI-wide
|
|
33609
|
+
user-select:none) so values can be copied out. The panel header stays chrome. */
|
|
33610
|
+
.vela-dw-group, .vela-dw-row { user-select: text; -webkit-user-select: text; cursor: text; }
|
|
33112
33611
|
.vela-dw-row {
|
|
33113
33612
|
display: flex;
|
|
33114
33613
|
align-items: center;
|
|
@@ -33156,7 +33655,7 @@ var DataWindow = class extends SidePanel {
|
|
|
33156
33655
|
super(host, "Data window", "vela-dw");
|
|
33157
33656
|
this.chart = null;
|
|
33158
33657
|
this.unsubs = [];
|
|
33159
|
-
injectStyles(
|
|
33658
|
+
injectStyles(STYLE_ID14, CSS11, host.ownerDocument);
|
|
33160
33659
|
}
|
|
33161
33660
|
toggle(open2 = this.el.hidden) {
|
|
33162
33661
|
super.toggle(open2);
|
|
@@ -33358,7 +33857,7 @@ var PanelDock = class {
|
|
|
33358
33857
|
};
|
|
33359
33858
|
|
|
33360
33859
|
// src/widget/shortcuts-help.ts
|
|
33361
|
-
var
|
|
33860
|
+
var STYLE_ID15 = "vela-widget-shortcuts";
|
|
33362
33861
|
var CSS12 = `
|
|
33363
33862
|
.vela-sh-cat {
|
|
33364
33863
|
color: var(--vela-fg-muted);
|
|
@@ -33384,7 +33883,7 @@ var ShortcutsHelp = class {
|
|
|
33384
33883
|
constructor(keymap, host, onOpenChange) {
|
|
33385
33884
|
this.keymap = keymap;
|
|
33386
33885
|
const doc = (host ?? document.body).ownerDocument;
|
|
33387
|
-
injectStyles(
|
|
33886
|
+
injectStyles(STYLE_ID15, CSS12, doc);
|
|
33388
33887
|
this.list = doc.createElement("div");
|
|
33389
33888
|
this.dialog = new Dialog({
|
|
33390
33889
|
title: "Keyboard shortcuts",
|
|
@@ -33571,6 +34070,9 @@ var ChartContextMenu = class {
|
|
|
33571
34070
|
host,
|
|
33572
34071
|
items: [],
|
|
33573
34072
|
placement: "bottom-start",
|
|
34073
|
+
// Pointer-anchored action menu: checked state reads as a leading ✓, not a
|
|
34074
|
+
// washed row (which would read as hover in a menu with no trigger button).
|
|
34075
|
+
checkmarks: true,
|
|
33574
34076
|
onSelect: (id) => this.run(id)
|
|
33575
34077
|
});
|
|
33576
34078
|
host.addEventListener("contextmenu", this.onContextMenu);
|
|
@@ -33672,7 +34174,7 @@ var ChartContextMenu = class {
|
|
|
33672
34174
|
};
|
|
33673
34175
|
|
|
33674
34176
|
// src/widget/indicator-picker.ts
|
|
33675
|
-
var
|
|
34177
|
+
var STYLE_ID16 = "vela-widget-indpicker";
|
|
33676
34178
|
var CSS13 = `
|
|
33677
34179
|
.vela-ip-searchrow {
|
|
33678
34180
|
display: flex;
|
|
@@ -33750,7 +34252,7 @@ var IndicatorPicker = class {
|
|
|
33750
34252
|
this.isOpen = false;
|
|
33751
34253
|
this.opts = opts;
|
|
33752
34254
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
33753
|
-
injectStyles(
|
|
34255
|
+
injectStyles(STYLE_ID16, CSS13, doc);
|
|
33754
34256
|
this.search = doc.createElement("input");
|
|
33755
34257
|
this.search.className = "vela-ip-search";
|
|
33756
34258
|
this.search.placeholder = "Search\u2026";
|
|
@@ -33878,7 +34380,7 @@ var IndicatorPicker = class {
|
|
|
33878
34380
|
};
|
|
33879
34381
|
|
|
33880
34382
|
// src/widget/timeframe-quick.ts
|
|
33881
|
-
var
|
|
34383
|
+
var STYLE_ID17 = "vela-widget-tfquick";
|
|
33882
34384
|
var CSS14 = `
|
|
33883
34385
|
.vela-tq-input {
|
|
33884
34386
|
display: block;
|
|
@@ -33902,7 +34404,7 @@ var CSS14 = `
|
|
|
33902
34404
|
var TimeframeQuick = class {
|
|
33903
34405
|
constructor(opts) {
|
|
33904
34406
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
33905
|
-
injectStyles(
|
|
34407
|
+
injectStyles(STYLE_ID17, CSS14, doc);
|
|
33906
34408
|
this.input = doc.createElement("input");
|
|
33907
34409
|
this.input.className = "vela-tq-input";
|
|
33908
34410
|
this.input.setAttribute("spellcheck", "false");
|
|
@@ -34084,7 +34586,7 @@ var LayoutModeController = class {
|
|
|
34084
34586
|
};
|
|
34085
34587
|
|
|
34086
34588
|
// src/widget/mobile-bar.ts
|
|
34087
|
-
var
|
|
34589
|
+
var STYLE_ID18 = "vela-widget-mobilebar";
|
|
34088
34590
|
var CSS15 = `
|
|
34089
34591
|
.vela-mobilebar {
|
|
34090
34592
|
display: none;
|
|
@@ -34135,7 +34637,7 @@ var MobileBar = class {
|
|
|
34135
34637
|
constructor(host, opts) {
|
|
34136
34638
|
this.opts = opts;
|
|
34137
34639
|
const doc = host.ownerDocument;
|
|
34138
|
-
injectStyles(
|
|
34640
|
+
injectStyles(STYLE_ID18, CSS15, doc);
|
|
34139
34641
|
this.el = doc.createElement("div");
|
|
34140
34642
|
this.el.className = "vela-mobilebar";
|
|
34141
34643
|
const item = (cls, label, onClick, icon2) => {
|
|
@@ -34194,7 +34696,7 @@ var MobileBar = class {
|
|
|
34194
34696
|
};
|
|
34195
34697
|
|
|
34196
34698
|
// src/widget/timeframe-drawer.ts
|
|
34197
|
-
var
|
|
34699
|
+
var STYLE_ID19 = "vela-widget-tf-drawer";
|
|
34198
34700
|
var CSS16 = `
|
|
34199
34701
|
.vela-tfd-heading {
|
|
34200
34702
|
padding: 4px 2px 8px;
|
|
@@ -34251,7 +34753,7 @@ var TimeframeDrawer = class {
|
|
|
34251
34753
|
this.rangeChips = /* @__PURE__ */ new Map();
|
|
34252
34754
|
this.tfChips = /* @__PURE__ */ new Map();
|
|
34253
34755
|
const doc = opts.host.ownerDocument;
|
|
34254
|
-
injectStyles(
|
|
34756
|
+
injectStyles(STYLE_ID19, CSS16, doc);
|
|
34255
34757
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
34256
34758
|
const rangeHeading = doc.createElement("div");
|
|
34257
34759
|
rangeHeading.className = "vela-tfd-heading";
|
|
@@ -34309,7 +34811,7 @@ var TimeframeDrawer = class {
|
|
|
34309
34811
|
};
|
|
34310
34812
|
|
|
34311
34813
|
// src/widget/drawings-drawer.ts
|
|
34312
|
-
var
|
|
34814
|
+
var STYLE_ID20 = "vela-widget-drawings-drawer";
|
|
34313
34815
|
var CSS17 = `
|
|
34314
34816
|
/* Search + tabs stay pinned while the tool list scrolls underneath. */
|
|
34315
34817
|
.vela-dd-sticky {
|
|
@@ -34412,7 +34914,7 @@ var DrawingsDrawer = class {
|
|
|
34412
34914
|
this.definition = { groups: [] };
|
|
34413
34915
|
this.activeGroup = "";
|
|
34414
34916
|
const doc = opts.host.ownerDocument;
|
|
34415
|
-
injectStyles(
|
|
34917
|
+
injectStyles(STYLE_ID20, CSS17, doc);
|
|
34416
34918
|
this.drawer = new Drawer({
|
|
34417
34919
|
host: opts.host,
|
|
34418
34920
|
title: "Drawings",
|
|
@@ -34554,7 +35056,7 @@ var DrawingsDrawer = class {
|
|
|
34554
35056
|
};
|
|
34555
35057
|
|
|
34556
35058
|
// src/widget/more-drawer.ts
|
|
34557
|
-
var
|
|
35059
|
+
var STYLE_ID21 = "vela-widget-more-drawer";
|
|
34558
35060
|
var CSS18 = `
|
|
34559
35061
|
.vela-md-actions {
|
|
34560
35062
|
display: flex;
|
|
@@ -34630,7 +35132,7 @@ var MoreDrawer = class {
|
|
|
34630
35132
|
constructor(opts) {
|
|
34631
35133
|
this.opts = opts;
|
|
34632
35134
|
this.view = "main";
|
|
34633
|
-
injectStyles(
|
|
35135
|
+
injectStyles(STYLE_ID21, CSS18, opts.host.ownerDocument);
|
|
34634
35136
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
34635
35137
|
}
|
|
34636
35138
|
open() {
|
|
@@ -34822,7 +35324,7 @@ var MoreDrawer = class {
|
|
|
34822
35324
|
};
|
|
34823
35325
|
|
|
34824
35326
|
// src/widget/timezone-drawer.ts
|
|
34825
|
-
var
|
|
35327
|
+
var STYLE_ID22 = "vela-widget-timezone-drawer";
|
|
34826
35328
|
var CSS19 = `
|
|
34827
35329
|
.vela-tzd-list { padding: 2px 0 4px; }
|
|
34828
35330
|
.vela-tzd-row {
|
|
@@ -34842,7 +35344,7 @@ var CSS19 = `
|
|
|
34842
35344
|
var TimezoneDrawer = class {
|
|
34843
35345
|
constructor(opts) {
|
|
34844
35346
|
this.opts = opts;
|
|
34845
|
-
injectStyles(
|
|
35347
|
+
injectStyles(STYLE_ID22, CSS19, opts.host.ownerDocument);
|
|
34846
35348
|
this.drawer = new Drawer({ host: opts.host, title: "Time zone", onOpenChange: opts.onOpenChange });
|
|
34847
35349
|
}
|
|
34848
35350
|
open() {
|
|
@@ -34880,7 +35382,7 @@ var TimezoneDrawer = class {
|
|
|
34880
35382
|
};
|
|
34881
35383
|
|
|
34882
35384
|
// src/widget/price-scale-drawer.ts
|
|
34883
|
-
var
|
|
35385
|
+
var STYLE_ID23 = "vela-widget-price-scale-drawer";
|
|
34884
35386
|
var CSS20 = `
|
|
34885
35387
|
.vela-psd-list { padding: 2px 0 4px; }
|
|
34886
35388
|
.vela-psd-row {
|
|
@@ -34910,7 +35412,7 @@ var PriceScaleDrawer = class {
|
|
|
34910
35412
|
constructor(opts) {
|
|
34911
35413
|
this.opts = opts;
|
|
34912
35414
|
this.pane = null;
|
|
34913
|
-
injectStyles(
|
|
35415
|
+
injectStyles(STYLE_ID23, CSS20, opts.host.ownerDocument);
|
|
34914
35416
|
this.drawer = new Drawer({ host: opts.host, title: "Price scale", onOpenChange: opts.onOpenChange });
|
|
34915
35417
|
}
|
|
34916
35418
|
open() {
|
|
@@ -35046,7 +35548,7 @@ var PriceScaleDrawer = class {
|
|
|
35046
35548
|
};
|
|
35047
35549
|
|
|
35048
35550
|
// src/widget/drawing-pill.ts
|
|
35049
|
-
var
|
|
35551
|
+
var STYLE_ID24 = "vela-widget-drawing-pill";
|
|
35050
35552
|
var CSS21 = `
|
|
35051
35553
|
.vela-drawpill { display: none; }
|
|
35052
35554
|
[data-layout='mobile'] .vela-drawpill {
|
|
@@ -35107,7 +35609,7 @@ var DrawingPill = class {
|
|
|
35107
35609
|
this.chart = null;
|
|
35108
35610
|
this.chartSubs = [];
|
|
35109
35611
|
const doc = host.ownerDocument;
|
|
35110
|
-
injectStyles(
|
|
35612
|
+
injectStyles(STYLE_ID24, CSS21, doc);
|
|
35111
35613
|
this.el = doc.createElement("div");
|
|
35112
35614
|
this.el.className = "vela-drawpill";
|
|
35113
35615
|
this.el.hidden = true;
|
|
@@ -35495,7 +35997,7 @@ var WidgetHistory = class {
|
|
|
35495
35997
|
};
|
|
35496
35998
|
|
|
35497
35999
|
// src/widget/toast.ts
|
|
35498
|
-
var
|
|
36000
|
+
var STYLE_ID25 = "vela-widget-toast";
|
|
35499
36001
|
var CSS22 = `
|
|
35500
36002
|
.vela-toast {
|
|
35501
36003
|
position: absolute;
|
|
@@ -35526,7 +36028,7 @@ var CSS22 = `
|
|
|
35526
36028
|
var Toast = class {
|
|
35527
36029
|
constructor(host) {
|
|
35528
36030
|
this.timer = null;
|
|
35529
|
-
injectStyles(
|
|
36031
|
+
injectStyles(STYLE_ID25, CSS22, host.ownerDocument);
|
|
35530
36032
|
this.el = host.ownerDocument.createElement("div");
|
|
35531
36033
|
this.el.className = "vela-toast";
|
|
35532
36034
|
host.appendChild(this.el);
|
|
@@ -35609,6 +36111,11 @@ var WIDGET_CSS = `
|
|
|
35609
36111
|
`;
|
|
35610
36112
|
var VelaWidget = class {
|
|
35611
36113
|
constructor(container, opts) {
|
|
36114
|
+
/** Keeps the pre/post-market shading on the symbol's real calendar (see {@link SessionShadingTracker}). */
|
|
36115
|
+
this.sessionShading = new SessionShadingTracker((zones) => this.inner?.renderer.set("sessionZones", zones));
|
|
36116
|
+
/** Whether the ACTIVE symbol declares real trading sessions (RTH/ETH meaningful) —
|
|
36117
|
+
* gates the bottombar toggle and the settings dialog's Trading session group. */
|
|
36118
|
+
this.sessionsAvailable = false;
|
|
35612
36119
|
this.shortcutsHelp = null;
|
|
35613
36120
|
this.inner = null;
|
|
35614
36121
|
/** The manifest library (loaded once). */
|
|
@@ -36072,17 +36579,140 @@ var VelaWidget = class {
|
|
|
36072
36579
|
this.markStateDirty();
|
|
36073
36580
|
void this.inner?.setMarket({ session });
|
|
36074
36581
|
}
|
|
36075
|
-
/** Re-derive the
|
|
36076
|
-
*
|
|
36582
|
+
/** Re-derive the session chrome from the ACTIVE symbol's metadata: the RTH/ETH
|
|
36583
|
+
* toggle shows (and the settings dialog's Trading session group appears) iff the
|
|
36584
|
+
* resolved symbol declares a real session vocabulary (not `24x7`); the pre/post
|
|
36585
|
+
* shading re-derives for the new market either way. */
|
|
36077
36586
|
refreshSessionToggle() {
|
|
36078
36587
|
const chart = this.inner;
|
|
36079
|
-
if (!chart
|
|
36588
|
+
if (!chart) return;
|
|
36080
36589
|
void chart.data.symbolInfo(this.symbol).then((si) => {
|
|
36081
36590
|
if (this.destroyed || this.inner !== chart) return;
|
|
36082
36591
|
const enabled = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
36083
36592
|
this.bottombar?.setSession({ session: this.session ?? "regular", enabled });
|
|
36593
|
+
if (enabled !== this.sessionsAvailable) {
|
|
36594
|
+
this.sessionsAvailable = enabled;
|
|
36595
|
+
this.pushSettingsSections();
|
|
36596
|
+
}
|
|
36597
|
+
this.refreshSessionShading();
|
|
36084
36598
|
});
|
|
36085
36599
|
}
|
|
36600
|
+
/** (Re)derive the pre/post-market shading bands for the current market. The loaded
|
|
36601
|
+
* depth (bars × timeframe) bounds the calendar fetch; the tracker clamps it. */
|
|
36602
|
+
refreshSessionShading() {
|
|
36603
|
+
const chart = this.inner;
|
|
36604
|
+
if (!chart) return;
|
|
36605
|
+
const lookbackMs = Math.max(this.bars, this.rangeBars) * timeframeMs(this.timeframe);
|
|
36606
|
+
this.sessionShading.track(chart.data, this.symbol, { session: this.session ?? "regular", lookbackMs });
|
|
36607
|
+
}
|
|
36608
|
+
/** The session-shade colors live in the renderer CONFIG (persisted with it, edited
|
|
36609
|
+
* live by the dialog swatch) — the widget only proxies them into its settings rows. */
|
|
36610
|
+
sessionShadeColor(key) {
|
|
36611
|
+
const cfg = this.inner?.renderer.getConfig();
|
|
36612
|
+
const v = cfg?.sessions?.[key];
|
|
36613
|
+
return typeof v === "string" ? v : "";
|
|
36614
|
+
}
|
|
36615
|
+
setSessionShadeColor(key, color) {
|
|
36616
|
+
this.inner?.renderer.applyConfig({ sessions: { [key]: color } });
|
|
36617
|
+
this.markStateDirty();
|
|
36618
|
+
}
|
|
36619
|
+
/**
|
|
36620
|
+
* (Re)contribute the widget's settings-dialog sections: status line parts, the fetch
|
|
36621
|
+
* depth, the watermark toggle, and — only while the active symbol HAS sessions — the
|
|
36622
|
+
* Trading session group (RTH/ETH switch + the pre/post-market shading colors) inside
|
|
36623
|
+
* the Symbol tab. Re-run whenever a gate changes (the dialog reads them on open).
|
|
36624
|
+
*/
|
|
36625
|
+
pushSettingsSections() {
|
|
36626
|
+
const chart = this.inner;
|
|
36627
|
+
if (!chart) return;
|
|
36628
|
+
const rth = "Regular hours (RTH)";
|
|
36629
|
+
const eth = "Extended hours (ETH)";
|
|
36630
|
+
const sessionSection = {
|
|
36631
|
+
title: "Trading session",
|
|
36632
|
+
placement: "symbol",
|
|
36633
|
+
rows: [
|
|
36634
|
+
{
|
|
36635
|
+
kind: "select",
|
|
36636
|
+
label: "Session",
|
|
36637
|
+
options: [rth, eth],
|
|
36638
|
+
get: () => (this.session ?? "regular") === "extended" ? eth : rth,
|
|
36639
|
+
set: (v) => this.setSession(v === eth ? "extended" : "regular")
|
|
36640
|
+
},
|
|
36641
|
+
{
|
|
36642
|
+
kind: "color",
|
|
36643
|
+
label: "Pre-market",
|
|
36644
|
+
get: () => this.sessionShadeColor("premarketColor"),
|
|
36645
|
+
set: (v) => this.setSessionShadeColor("premarketColor", v)
|
|
36646
|
+
},
|
|
36647
|
+
{
|
|
36648
|
+
kind: "color",
|
|
36649
|
+
label: "Post-market",
|
|
36650
|
+
get: () => this.sessionShadeColor("postmarketColor"),
|
|
36651
|
+
set: (v) => this.setSessionShadeColor("postmarketColor", v)
|
|
36652
|
+
}
|
|
36653
|
+
]
|
|
36654
|
+
};
|
|
36655
|
+
const advanced = {
|
|
36656
|
+
title: "Advanced",
|
|
36657
|
+
placement: "end",
|
|
36658
|
+
rows: [
|
|
36659
|
+
{
|
|
36660
|
+
kind: "select",
|
|
36661
|
+
label: "Bars to fetch",
|
|
36662
|
+
options: ["500", "1000", "2000", "5000", "10000", "20000"],
|
|
36663
|
+
get: () => String(this.bars),
|
|
36664
|
+
set: (v) => {
|
|
36665
|
+
this.bars = Number(v);
|
|
36666
|
+
this.markStateDirty();
|
|
36667
|
+
void this.inner?.setMarket({ bars: Math.max(this.bars, this.rangeBars) });
|
|
36668
|
+
}
|
|
36669
|
+
}
|
|
36670
|
+
]
|
|
36671
|
+
};
|
|
36672
|
+
const watermarkSection = {
|
|
36673
|
+
title: "Watermark",
|
|
36674
|
+
placement: "symbol",
|
|
36675
|
+
rows: [
|
|
36676
|
+
{
|
|
36677
|
+
kind: "toggle",
|
|
36678
|
+
label: "Symbol watermark",
|
|
36679
|
+
get: () => this.watermarkOn,
|
|
36680
|
+
set: (v) => this.setWatermarkVisible(v)
|
|
36681
|
+
}
|
|
36682
|
+
]
|
|
36683
|
+
};
|
|
36684
|
+
const sections = [];
|
|
36685
|
+
if (this.statusline) {
|
|
36686
|
+
const sl = this.statusline;
|
|
36687
|
+
sections.push({
|
|
36688
|
+
title: "Status line",
|
|
36689
|
+
rows: [
|
|
36690
|
+
{ kind: "heading", label: "Status line" },
|
|
36691
|
+
{ kind: "toggle", label: "Symbol name", get: () => sl.partVisible("name"), set: (v) => sl.setPartVisible("name", v) },
|
|
36692
|
+
{ kind: "toggle", label: "Market status", get: () => sl.partVisible("market"), set: (v) => sl.setPartVisible("market", v) },
|
|
36693
|
+
{ kind: "toggle", label: "OHLC values", get: () => sl.partVisible("ohlc"), set: (v) => sl.setPartVisible("ohlc", v) },
|
|
36694
|
+
{ kind: "toggle", label: "Bar change values", get: () => sl.partVisible("change"), set: (v) => sl.setPartVisible("change", v) },
|
|
36695
|
+
{ kind: "heading", label: "Indicators" },
|
|
36696
|
+
{
|
|
36697
|
+
kind: "toggle",
|
|
36698
|
+
label: "Titles",
|
|
36699
|
+
get: () => this.indicatorTitlesOn,
|
|
36700
|
+
set: (v) => this.setIndicatorTitlesVisible(v)
|
|
36701
|
+
},
|
|
36702
|
+
{
|
|
36703
|
+
kind: "toggle",
|
|
36704
|
+
label: "Values",
|
|
36705
|
+
get: () => this.indicatorValuesOn,
|
|
36706
|
+
set: (v) => this.setIndicatorValuesVisible(v)
|
|
36707
|
+
}
|
|
36708
|
+
]
|
|
36709
|
+
});
|
|
36710
|
+
}
|
|
36711
|
+
sections.push(advanced);
|
|
36712
|
+
if (this.sessionsAvailable) sections.push(sessionSection);
|
|
36713
|
+
sections.push(watermarkSection);
|
|
36714
|
+
chart.renderer.setSettingsSections(sections);
|
|
36715
|
+
}
|
|
36086
36716
|
/** Star/unstar a timeframe — the topbar chips and dropdown stars follow, and the
|
|
36087
36717
|
* set persists with the rest of the shell state. */
|
|
36088
36718
|
setTimeframeFavorite(tf, on) {
|
|
@@ -36388,6 +37018,7 @@ var VelaWidget = class {
|
|
|
36388
37018
|
this.indicatorPicker?.destroy();
|
|
36389
37019
|
this.tfQuick.destroy();
|
|
36390
37020
|
this.marketStatus?.stop();
|
|
37021
|
+
this.sessionShading.stop();
|
|
36391
37022
|
this.statusline?.destroy();
|
|
36392
37023
|
this.watermark?.destroy();
|
|
36393
37024
|
this.bottombar?.destroy();
|
|
@@ -36526,7 +37157,10 @@ var VelaWidget = class {
|
|
|
36526
37157
|
this.refreshSessionToggle();
|
|
36527
37158
|
this.marketStatus?.track(chart.data, symbol);
|
|
36528
37159
|
}
|
|
36529
|
-
if (timeframe !== this.timeframe)
|
|
37160
|
+
if (timeframe !== this.timeframe) {
|
|
37161
|
+
this.syncTimeframeChrome(timeframe);
|
|
37162
|
+
this.refreshSessionShading();
|
|
37163
|
+
}
|
|
36530
37164
|
});
|
|
36531
37165
|
this.history.onChart(chart);
|
|
36532
37166
|
chart.on("alert", (alert) => {
|
|
@@ -36575,67 +37209,7 @@ var VelaWidget = class {
|
|
|
36575
37209
|
this.statusline?.onChart(chart);
|
|
36576
37210
|
this.drawingPill.onChart(chart);
|
|
36577
37211
|
this.syncStatuslineColors();
|
|
36578
|
-
|
|
36579
|
-
title: "Advanced",
|
|
36580
|
-
placement: "end",
|
|
36581
|
-
rows: [
|
|
36582
|
-
{
|
|
36583
|
-
kind: "select",
|
|
36584
|
-
label: "Bars to fetch",
|
|
36585
|
-
options: ["500", "1000", "2000", "5000", "10000", "20000"],
|
|
36586
|
-
get: () => String(this.bars),
|
|
36587
|
-
set: (v) => {
|
|
36588
|
-
this.bars = Number(v);
|
|
36589
|
-
this.markStateDirty();
|
|
36590
|
-
void this.inner?.setMarket({ bars: Math.max(this.bars, this.rangeBars) });
|
|
36591
|
-
}
|
|
36592
|
-
}
|
|
36593
|
-
]
|
|
36594
|
-
};
|
|
36595
|
-
const watermarkSection = {
|
|
36596
|
-
title: "Watermark",
|
|
36597
|
-
placement: "symbol",
|
|
36598
|
-
rows: [
|
|
36599
|
-
{
|
|
36600
|
-
kind: "toggle",
|
|
36601
|
-
label: "Symbol watermark",
|
|
36602
|
-
get: () => this.watermarkOn,
|
|
36603
|
-
set: (v) => this.setWatermarkVisible(v)
|
|
36604
|
-
}
|
|
36605
|
-
]
|
|
36606
|
-
};
|
|
36607
|
-
if (this.statusline) {
|
|
36608
|
-
const sl = this.statusline;
|
|
36609
|
-
chart.renderer.setSettingsSections([
|
|
36610
|
-
{
|
|
36611
|
-
title: "Status line",
|
|
36612
|
-
rows: [
|
|
36613
|
-
{ kind: "heading", label: "Status line" },
|
|
36614
|
-
{ kind: "toggle", label: "Symbol name", get: () => sl.partVisible("name"), set: (v) => sl.setPartVisible("name", v) },
|
|
36615
|
-
{ kind: "toggle", label: "Market status", get: () => sl.partVisible("market"), set: (v) => sl.setPartVisible("market", v) },
|
|
36616
|
-
{ kind: "toggle", label: "OHLC values", get: () => sl.partVisible("ohlc"), set: (v) => sl.setPartVisible("ohlc", v) },
|
|
36617
|
-
{ kind: "toggle", label: "Bar change values", get: () => sl.partVisible("change"), set: (v) => sl.setPartVisible("change", v) },
|
|
36618
|
-
{ kind: "heading", label: "Indicators" },
|
|
36619
|
-
{
|
|
36620
|
-
kind: "toggle",
|
|
36621
|
-
label: "Titles",
|
|
36622
|
-
get: () => this.indicatorTitlesOn,
|
|
36623
|
-
set: (v) => this.setIndicatorTitlesVisible(v)
|
|
36624
|
-
},
|
|
36625
|
-
{
|
|
36626
|
-
kind: "toggle",
|
|
36627
|
-
label: "Values",
|
|
36628
|
-
get: () => this.indicatorValuesOn,
|
|
36629
|
-
set: (v) => this.setIndicatorValuesVisible(v)
|
|
36630
|
-
}
|
|
36631
|
-
]
|
|
36632
|
-
},
|
|
36633
|
-
advanced,
|
|
36634
|
-
watermarkSection
|
|
36635
|
-
]);
|
|
36636
|
-
} else {
|
|
36637
|
-
chart.renderer.setSettingsSections([advanced, watermarkSection]);
|
|
36638
|
-
}
|
|
37212
|
+
this.pushSettingsSections();
|
|
36639
37213
|
void chart.ready().then(() => {
|
|
36640
37214
|
if (this.buildSeq !== seq || this.destroyed) return;
|
|
36641
37215
|
if (this.pendingRange) {
|