@luxalgo/vela 0.6.2 → 0.6.4
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-U5KCQHAC.js → chunk-3QX6QOCX.js} +80 -12
- package/dist/{chunk-7D6YIF34.js → chunk-6FA4TDUF.js} +325 -67
- package/dist/{chunk-7Y7CJ7EN.js → chunk-BEJ57HP4.js} +8 -1
- 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 +394 -78
- package/dist/index.d.cts +36 -9
- package/dist/index.d.ts +36 -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-BeCMF1VQ.d.ts} +3 -3
- package/dist/{plugin-CSb_sTiN.d.cts → plugin-CkiStRaI.d.cts} +3 -3
- 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 +394 -78
- package/dist/vela.global.min.js +84 -47
- package/dist/widget.cjs +646 -191
- package/dist/widget.d.cts +29 -8
- package/dist/widget.d.ts +29 -8
- package/dist/widget.js +145 -73
- package/dist/workspace.cjs +601 -142
- 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);
|
|
@@ -15917,10 +16178,12 @@ var WebGL2Backend = class {
|
|
|
15917
16178
|
}
|
|
15918
16179
|
}
|
|
15919
16180
|
}
|
|
15920
|
-
/** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
|
|
16181
|
+
/** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
|
|
16182
|
+
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
15921
16183
|
emitHighlights(b, scene, pane, coords) {
|
|
15922
|
-
|
|
15923
|
-
|
|
16184
|
+
const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
|
|
16185
|
+
if (bands.length === 0) return;
|
|
16186
|
+
for (const band of bands) {
|
|
15924
16187
|
const x1 = coords.timeToX(band.from);
|
|
15925
16188
|
const x2 = coords.timeToX(band.to);
|
|
15926
16189
|
if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
|
|
@@ -17894,10 +18157,12 @@ var Canvas2dBackend = class {
|
|
|
17894
18157
|
ctx.fillStyle = bg.color;
|
|
17895
18158
|
ctx.fillRect(x1, pane.bounds.top, x2 - x1, pane.bounds.height);
|
|
17896
18159
|
}
|
|
17897
|
-
/** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
|
|
18160
|
+
/** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
|
|
18161
|
+
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
17898
18162
|
drawHighlights(ctx, scene, coords) {
|
|
17899
|
-
|
|
17900
|
-
|
|
18163
|
+
const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
|
|
18164
|
+
if (bands.length === 0) return;
|
|
18165
|
+
for (const band of bands) {
|
|
17901
18166
|
const x1 = coords.timeToX(band.from);
|
|
17902
18167
|
const x2 = coords.timeToX(band.to);
|
|
17903
18168
|
if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
|
|
@@ -18671,14 +18936,6 @@ var DrawingSceneRenderer = class {
|
|
|
18671
18936
|
}
|
|
18672
18937
|
};
|
|
18673
18938
|
|
|
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
18939
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
18683
18940
|
var ChromeRenderer = class {
|
|
18684
18941
|
constructor() {
|
|
@@ -19565,6 +19822,7 @@ var SettingsDialog = class {
|
|
|
19565
19822
|
for (const hr of hs.rows) {
|
|
19566
19823
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
19567
19824
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
19825
|
+
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
19568
19826
|
else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options.map((o) => [o, o]), (v) => hr.set(v)));
|
|
19569
19827
|
}
|
|
19570
19828
|
}
|
|
@@ -22289,14 +22547,14 @@ function btn(label, primary, onClick) {
|
|
|
22289
22547
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
22290
22548
|
var BTN = 30;
|
|
22291
22549
|
var ICON = 21;
|
|
22292
|
-
var
|
|
22550
|
+
var STYLE_ID3 = "vela-drawing-popup-styles";
|
|
22293
22551
|
var STYLE_REV = "3";
|
|
22294
|
-
function
|
|
22552
|
+
function ensureStyles3() {
|
|
22295
22553
|
if (typeof document === "undefined") return;
|
|
22296
|
-
const existing = document.getElementById(
|
|
22554
|
+
const existing = document.getElementById(STYLE_ID3);
|
|
22297
22555
|
if (existing?.dataset.rev === STYLE_REV) return;
|
|
22298
22556
|
const s = existing ?? document.createElement("style");
|
|
22299
|
-
s.id =
|
|
22557
|
+
s.id = STYLE_ID3;
|
|
22300
22558
|
s.dataset.rev = STYLE_REV;
|
|
22301
22559
|
s.textContent = `
|
|
22302
22560
|
.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 +22611,7 @@ var DrawingSettingsPopup = class {
|
|
|
22353
22611
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
22354
22612
|
open(drawing, anchor, actions, onClose) {
|
|
22355
22613
|
this.close();
|
|
22356
|
-
|
|
22614
|
+
ensureStyles3();
|
|
22357
22615
|
this.onClose = onClose ?? null;
|
|
22358
22616
|
const t = this.theme;
|
|
22359
22617
|
const schema = drawing.schema();
|
|
@@ -23081,7 +23339,7 @@ var DrawingToolbar = class {
|
|
|
23081
23339
|
this.onCollapse = options.onCollapse ?? (() => {
|
|
23082
23340
|
});
|
|
23083
23341
|
this.onDrawingsSync = options.onDrawingsSync ?? null;
|
|
23084
|
-
|
|
23342
|
+
ensureStyles4();
|
|
23085
23343
|
this.root = document.createElement("div");
|
|
23086
23344
|
this.root.className = "vela-dtb";
|
|
23087
23345
|
this.styleRoot();
|
|
@@ -23564,13 +23822,13 @@ var DrawingToolbar = class {
|
|
|
23564
23822
|
}
|
|
23565
23823
|
};
|
|
23566
23824
|
var MAGNET_ID = "__magnet__";
|
|
23567
|
-
var
|
|
23568
|
-
function
|
|
23825
|
+
var STYLE_ID4 = "vela-drawing-toolbar-styles";
|
|
23826
|
+
function ensureStyles4() {
|
|
23569
23827
|
if (typeof document === "undefined") return;
|
|
23570
|
-
let s = document.getElementById(
|
|
23828
|
+
let s = document.getElementById(STYLE_ID4);
|
|
23571
23829
|
if (!s) {
|
|
23572
23830
|
s = document.createElement("style");
|
|
23573
|
-
s.id =
|
|
23831
|
+
s.id = STYLE_ID4;
|
|
23574
23832
|
document.head.appendChild(s);
|
|
23575
23833
|
}
|
|
23576
23834
|
s.textContent = `
|
|
@@ -24835,7 +25093,7 @@ var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path
|
|
|
24835
25093
|
|
|
24836
25094
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
24837
25095
|
var ATTRIBUTION_URL = "https://luxalgo.com/vela";
|
|
24838
|
-
var
|
|
25096
|
+
var STYLE_ID5 = "vela-attribution-styles";
|
|
24839
25097
|
var CSS2 = `
|
|
24840
25098
|
.vela-attribution {
|
|
24841
25099
|
text-decoration: none;
|
|
@@ -24898,11 +25156,11 @@ var CSS2 = `
|
|
|
24898
25156
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
24899
25157
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
24900
25158
|
`;
|
|
24901
|
-
function
|
|
24902
|
-
let s = doc.getElementById(
|
|
25159
|
+
function ensureStyles5(doc) {
|
|
25160
|
+
let s = doc.getElementById(STYLE_ID5);
|
|
24903
25161
|
if (!s) {
|
|
24904
25162
|
s = doc.createElement("style");
|
|
24905
|
-
s.id =
|
|
25163
|
+
s.id = STYLE_ID5;
|
|
24906
25164
|
doc.head.appendChild(s);
|
|
24907
25165
|
}
|
|
24908
25166
|
s.textContent = CSS2;
|
|
@@ -24931,7 +25189,7 @@ function createCustomMark(doc, html, background) {
|
|
|
24931
25189
|
return el;
|
|
24932
25190
|
}
|
|
24933
25191
|
function createAttributionMark(doc, background) {
|
|
24934
|
-
|
|
25192
|
+
ensureStyles5(doc);
|
|
24935
25193
|
const a = doc.createElement("a");
|
|
24936
25194
|
a.className = "vela-attribution";
|
|
24937
25195
|
a.href = ATTRIBUTION_URL;
|
|
@@ -25443,6 +25701,8 @@ var NativeRenderer = class {
|
|
|
25443
25701
|
// total right-gutter width (master column + merged-scale columns)
|
|
25444
25702
|
this.paneControls = null;
|
|
25445
25703
|
// per-pane hover button cluster (top-right)
|
|
25704
|
+
this.axisScaleButtons = null;
|
|
25705
|
+
// A/L hover buttons at the bottom of each pane's price scale
|
|
25446
25706
|
this.paneActionCbs = /* @__PURE__ */ new Set();
|
|
25447
25707
|
// ── data window (crosshair OHLC + per-series readout, pulled by host panels) ──
|
|
25448
25708
|
this.hoverLogical = null;
|
|
@@ -25486,7 +25746,7 @@ var NativeRenderer = class {
|
|
|
25486
25746
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
25487
25747
|
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
25488
25748
|
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"];
|
|
25749
|
+
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
25750
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
25491
25751
|
* so moving onto the button doesn't count as leaving). */
|
|
25492
25752
|
this.onScrollProximityMove = (e) => {
|
|
@@ -25602,6 +25862,9 @@ var NativeRenderer = class {
|
|
|
25602
25862
|
case "highlights":
|
|
25603
25863
|
this.scene.highlights = sanitizeHighlights(value);
|
|
25604
25864
|
break;
|
|
25865
|
+
case "sessionZones":
|
|
25866
|
+
this.scene.sessionZones = sanitizeSessionZones(value);
|
|
25867
|
+
break;
|
|
25605
25868
|
case "gridlines":
|
|
25606
25869
|
this.scene.showGrid = Boolean(value);
|
|
25607
25870
|
break;
|
|
@@ -25718,6 +25981,8 @@ var NativeRenderer = class {
|
|
|
25718
25981
|
return this.scene.indicatorZOrder();
|
|
25719
25982
|
case "highlights":
|
|
25720
25983
|
return this.scene.highlights;
|
|
25984
|
+
case "sessionZones":
|
|
25985
|
+
return this.scene.sessionZones;
|
|
25721
25986
|
case "gridlines":
|
|
25722
25987
|
return this.scene.showGrid;
|
|
25723
25988
|
case "axisLabels":
|
|
@@ -25927,7 +26192,8 @@ var NativeRenderer = class {
|
|
|
25927
26192
|
baselineLevel: s.baseline.baselineLevel
|
|
25928
26193
|
};
|
|
25929
26194
|
})(),
|
|
25930
|
-
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale }
|
|
26195
|
+
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
|
|
26196
|
+
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
|
|
25931
26197
|
};
|
|
25932
26198
|
}
|
|
25933
26199
|
/**
|
|
@@ -26009,6 +26275,7 @@ var NativeRenderer = class {
|
|
|
26009
26275
|
width: next.baseline.width,
|
|
26010
26276
|
baselineLevel: next.baseline.baselineLevel
|
|
26011
26277
|
};
|
|
26278
|
+
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
|
|
26012
26279
|
this.setPriceStyle(next.series.style);
|
|
26013
26280
|
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
26014
26281
|
this.scene.baselineValue = next.series.baseline;
|
|
@@ -26022,6 +26289,7 @@ var NativeRenderer = class {
|
|
|
26022
26289
|
}
|
|
26023
26290
|
this.inputsUI?.setTheme(this.theme);
|
|
26024
26291
|
this.paneControls?.setTheme(this.theme);
|
|
26292
|
+
this.axisScaleButtons?.setTheme(this.theme);
|
|
26025
26293
|
this.userDrawings?.setTheme(this.chromeTheme());
|
|
26026
26294
|
this.refreshScrollButtonTheme();
|
|
26027
26295
|
this.refreshAttributionColor();
|
|
@@ -26360,7 +26628,7 @@ var NativeRenderer = class {
|
|
|
26360
26628
|
this.surfaceBackground = theme.background;
|
|
26361
26629
|
this.surfaceTextColor = theme.textColor;
|
|
26362
26630
|
this.wrapper = document.createElement("div");
|
|
26363
|
-
Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair" });
|
|
26631
|
+
Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair", userSelect: "none", webkitUserSelect: "none" });
|
|
26364
26632
|
applyChromeTokens(this.wrapper, this.chromeTheme());
|
|
26365
26633
|
this.volumeCanvas = document.createElement("canvas");
|
|
26366
26634
|
Object.assign(this.volumeCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
@@ -26529,6 +26797,15 @@ var NativeRenderer = class {
|
|
|
26529
26797
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
26530
26798
|
}
|
|
26531
26799
|
});
|
|
26800
|
+
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
26801
|
+
panes: () => this.axisScaleViews(),
|
|
26802
|
+
rightAxis: () => this.rightAxisW,
|
|
26803
|
+
onToggleAuto: (paneId) => this.togglePaneAuto(paneId),
|
|
26804
|
+
onToggleLog: (paneId) => {
|
|
26805
|
+
const pane = this.scene.panes.get(paneId);
|
|
26806
|
+
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
26807
|
+
}
|
|
26808
|
+
});
|
|
26532
26809
|
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
26533
26810
|
this.resizeObserver.observe(this.wrapper);
|
|
26534
26811
|
this.watchDpr();
|
|
@@ -26605,6 +26882,7 @@ var NativeRenderer = class {
|
|
|
26605
26882
|
this.applyBackground();
|
|
26606
26883
|
this.inputsUI.setTheme(theme);
|
|
26607
26884
|
this.paneControls?.setTheme(this.theme);
|
|
26885
|
+
this.axisScaleButtons?.setTheme(this.theme);
|
|
26608
26886
|
this.settingsDialog?.refreshConfig(this.getConfig());
|
|
26609
26887
|
this.syncThemeControl();
|
|
26610
26888
|
this.settingsDialog?.setTheme(this.theme);
|
|
@@ -26653,6 +26931,7 @@ var NativeRenderer = class {
|
|
|
26653
26931
|
this.liveRegion = null;
|
|
26654
26932
|
this.inputsUI?.destroy();
|
|
26655
26933
|
this.paneControls?.destroy();
|
|
26934
|
+
this.axisScaleButtons?.destroy();
|
|
26656
26935
|
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
26657
26936
|
this.tableOverlays.clear();
|
|
26658
26937
|
this.resizeObserver?.disconnect();
|
|
@@ -28047,6 +28326,25 @@ var NativeRenderer = class {
|
|
|
28047
28326
|
invert: paneInvert(this.scene, p)
|
|
28048
28327
|
}));
|
|
28049
28328
|
}
|
|
28329
|
+
/** Thin projection of the panes for the axis A/L hover buttons (top-to-bottom order). */
|
|
28330
|
+
axisScaleViews() {
|
|
28331
|
+
return this.scene.orderedPanes().map((p) => ({
|
|
28332
|
+
id: p.id,
|
|
28333
|
+
top: p.bounds.top,
|
|
28334
|
+
height: p.bounds.height,
|
|
28335
|
+
collapsed: p.collapsed,
|
|
28336
|
+
auto: p.manualScale == null,
|
|
28337
|
+
log: paneLogScale(this.scene, p)
|
|
28338
|
+
}));
|
|
28339
|
+
}
|
|
28340
|
+
/** Toggle one pane's autoscale (the A axis button): off freezes the current window into
|
|
28341
|
+
* manual mode, on drops it — the per-pane twin of the chart-level `autoScale` feature. */
|
|
28342
|
+
togglePaneAuto(paneId) {
|
|
28343
|
+
const pane = this.scene.panes.get(paneId);
|
|
28344
|
+
if (!pane) return;
|
|
28345
|
+
pane.manualScale = pane.manualScale == null ? { ...pane.scale } : null;
|
|
28346
|
+
this.scheduler?.invalidate(4 /* Full */);
|
|
28347
|
+
}
|
|
28050
28348
|
/** Set one pane's axis mode. The price pane routes to the scene-level setting (persisted +
|
|
28051
28349
|
* keyboard shortcuts); a study pane keeps its own, so panes never affect each other. */
|
|
28052
28350
|
setPaneScaleMode(paneId, mode) {
|
|
@@ -28058,19 +28356,18 @@ var NativeRenderer = class {
|
|
|
28058
28356
|
}
|
|
28059
28357
|
this.scheduler?.invalidate(4 /* Full */);
|
|
28060
28358
|
}
|
|
28061
|
-
/** Set one pane's logarithmic flag
|
|
28062
|
-
*
|
|
28359
|
+
/** Set one pane's logarithmic flag; the price pane routes to the scene-level flag.
|
|
28360
|
+
* Log and auto are INDEPENDENT: a frozen (manual) window is kept — its price bounds
|
|
28361
|
+
* stay put and only its `log` tag flips, so the same range re-renders in the new
|
|
28362
|
+
* space instead of snapping back to autoscale. */
|
|
28063
28363
|
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;
|
|
28364
|
+
const pane = paneId === PRICE_PANE_ID ? this.scene.orderedPanes().find((p) => p.kind === "price") : this.scene.panes.get(paneId);
|
|
28365
|
+
if (paneId === PRICE_PANE_ID) this.scene.logScale = log;
|
|
28366
|
+
else {
|
|
28367
|
+
if (!pane) return;
|
|
28368
|
+
pane.logScale = log;
|
|
28073
28369
|
}
|
|
28370
|
+
if (pane?.manualScale) pane.manualScale.log = log;
|
|
28074
28371
|
this.scheduler?.invalidate(4 /* Full */);
|
|
28075
28372
|
}
|
|
28076
28373
|
/** Flip one pane's axis (high at the bottom). The price pane routes to the scene-level flag
|
|
@@ -28169,6 +28466,7 @@ var NativeRenderer = class {
|
|
|
28169
28466
|
}
|
|
28170
28467
|
this.inputsUI?.reposition();
|
|
28171
28468
|
this.paneControls?.reposition();
|
|
28469
|
+
this.axisScaleButtons?.reposition();
|
|
28172
28470
|
this.repositionScrollButton();
|
|
28173
28471
|
this.positionAttribution();
|
|
28174
28472
|
this.publishPricePaneBounds();
|
|
@@ -28187,6 +28485,7 @@ var NativeRenderer = class {
|
|
|
28187
28485
|
}
|
|
28188
28486
|
this.inputsUI?.reposition();
|
|
28189
28487
|
this.paneControls?.reposition();
|
|
28488
|
+
this.axisScaleButtons?.reposition();
|
|
28190
28489
|
this.repositionScrollButton();
|
|
28191
28490
|
this.positionAttribution();
|
|
28192
28491
|
this.publishPricePaneBounds();
|
|
@@ -28394,6 +28693,22 @@ function sanitizeHighlights(value) {
|
|
|
28394
28693
|
}
|
|
28395
28694
|
return out.sort((a, b) => a.from - b.from);
|
|
28396
28695
|
}
|
|
28696
|
+
function sanitizeSessionZones(value) {
|
|
28697
|
+
if (value == null || typeof value !== "object") return null;
|
|
28698
|
+
const v = value;
|
|
28699
|
+
const windows = (raw) => {
|
|
28700
|
+
if (!Array.isArray(raw)) return [];
|
|
28701
|
+
const out = [];
|
|
28702
|
+
for (const w of raw) {
|
|
28703
|
+
if (!Array.isArray(w)) continue;
|
|
28704
|
+
const from = Number(w[0]);
|
|
28705
|
+
const to = Number(w[1]);
|
|
28706
|
+
if (Number.isFinite(from) && Number.isFinite(to) && to > from) out.push([from, to]);
|
|
28707
|
+
}
|
|
28708
|
+
return out.sort((a, b) => a[0] - b[0]);
|
|
28709
|
+
};
|
|
28710
|
+
return { pre: windows(v.pre), post: windows(v.post) };
|
|
28711
|
+
}
|
|
28397
28712
|
function clamp012(v) {
|
|
28398
28713
|
return Math.max(0, Math.min(1, v));
|
|
28399
28714
|
}
|
|
@@ -29426,7 +29741,7 @@ var Drawer = class {
|
|
|
29426
29741
|
};
|
|
29427
29742
|
|
|
29428
29743
|
// src/widget/layout-picker.ts
|
|
29429
|
-
var
|
|
29744
|
+
var STYLE_ID6 = "vela-widget-layout-picker-v14";
|
|
29430
29745
|
var CSS3 = `
|
|
29431
29746
|
.vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
|
|
29432
29747
|
.vela-lp {
|
|
@@ -29531,7 +29846,7 @@ var CSS3 = `
|
|
|
29531
29846
|
`;
|
|
29532
29847
|
var GRID = 4;
|
|
29533
29848
|
function layoutGridCanvas(doc) {
|
|
29534
|
-
injectStyles(
|
|
29849
|
+
injectStyles(STYLE_ID6, CSS3, doc);
|
|
29535
29850
|
const el = doc.createElement("div");
|
|
29536
29851
|
el.className = "vela-lp-grid";
|
|
29537
29852
|
const squares = [];
|
|
@@ -29571,7 +29886,7 @@ var LayoutPicker = class {
|
|
|
29571
29886
|
};
|
|
29572
29887
|
this.opts = opts;
|
|
29573
29888
|
this.doc = opts.host.ownerDocument;
|
|
29574
|
-
injectStyles(
|
|
29889
|
+
injectStyles(STYLE_ID6, CSS3, this.doc);
|
|
29575
29890
|
const doc = this.doc;
|
|
29576
29891
|
this.layer = doc.createElement("div");
|
|
29577
29892
|
this.layer.className = "vela-ui-layer vela-lp-layer";
|
|
@@ -29916,7 +30231,7 @@ function timeframeLabel(value) {
|
|
|
29916
30231
|
}
|
|
29917
30232
|
|
|
29918
30233
|
// src/widget/topbar.ts
|
|
29919
|
-
var
|
|
30234
|
+
var STYLE_ID7 = "vela-topbar";
|
|
29920
30235
|
var CSS4 = `
|
|
29921
30236
|
.vela-widget-topbar {
|
|
29922
30237
|
display: flex;
|
|
@@ -29973,12 +30288,14 @@ var CSS4 = `
|
|
|
29973
30288
|
color: var(--vela-fg-muted);
|
|
29974
30289
|
}
|
|
29975
30290
|
.vela-widget-tf-caret:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
30291
|
+
/* The merged trigger is a plain button (hover feedback only) \u2014 the highlight
|
|
30292
|
+
background marks the CURRENT chip among favorites, and a lone trigger with a
|
|
30293
|
+
permanent highlight would read as stuck-pressed. */
|
|
29976
30294
|
.vela-widget-tf-caret[data-solo='1'] {
|
|
29977
30295
|
width: auto;
|
|
29978
30296
|
padding: 0 6px 0 9px;
|
|
29979
30297
|
gap: 4px;
|
|
29980
30298
|
color: var(--vela-fg-bright);
|
|
29981
|
-
background: var(--vela-hover-strong);
|
|
29982
30299
|
font-size: 13px;
|
|
29983
30300
|
font-weight: 550;
|
|
29984
30301
|
white-space: nowrap;
|
|
@@ -30081,7 +30398,7 @@ var Topbar = class {
|
|
|
30081
30398
|
this.timeframe = opts.timeframe;
|
|
30082
30399
|
this.priceStyle = opts.priceStyle;
|
|
30083
30400
|
const doc = host.ownerDocument;
|
|
30084
|
-
injectStyles(
|
|
30401
|
+
injectStyles(STYLE_ID7, CSS4, doc);
|
|
30085
30402
|
this.el = doc.createElement("div");
|
|
30086
30403
|
this.el.className = "vela-widget-topbar";
|
|
30087
30404
|
this.symbolEl = doc.createElement("button");
|
|
@@ -30430,7 +30747,7 @@ function tickerIconEl(doc, base, name, className) {
|
|
|
30430
30747
|
}
|
|
30431
30748
|
|
|
30432
30749
|
// src/widget/statusline.ts
|
|
30433
|
-
var
|
|
30750
|
+
var STYLE_ID8 = "vela-widget-statusline";
|
|
30434
30751
|
var CSS5 = `
|
|
30435
30752
|
.vela-statusline {
|
|
30436
30753
|
position: absolute;
|
|
@@ -30622,7 +30939,7 @@ var Statusline = class {
|
|
|
30622
30939
|
this.fitMode = false;
|
|
30623
30940
|
this.fitRO = null;
|
|
30624
30941
|
const doc = host.ownerDocument;
|
|
30625
|
-
injectStyles(
|
|
30942
|
+
injectStyles(STYLE_ID8, CSS5, doc);
|
|
30626
30943
|
host.classList.add("vela-has-statusline");
|
|
30627
30944
|
this.el = doc.createElement("div");
|
|
30628
30945
|
this.el.className = "vela-statusline";
|
|
@@ -30895,11 +31212,70 @@ var MarketStatusTracker = class {
|
|
|
30895
31212
|
}
|
|
30896
31213
|
};
|
|
30897
31214
|
|
|
31215
|
+
// src/widget/session-shading.ts
|
|
31216
|
+
function deriveSessionZones(regular, extended) {
|
|
31217
|
+
const pre = [];
|
|
31218
|
+
const post = [];
|
|
31219
|
+
for (const [extStart, extEnd] of extended) {
|
|
31220
|
+
const inside = regular.filter(([s, e]) => e > extStart && s < extEnd).sort((a, b) => a[0] - b[0]);
|
|
31221
|
+
let cursor = extStart;
|
|
31222
|
+
for (const [regStart, regEnd] of inside) {
|
|
31223
|
+
if (regStart > cursor) pre.push([cursor, Math.min(regStart, extEnd)]);
|
|
31224
|
+
cursor = Math.max(cursor, regEnd);
|
|
31225
|
+
}
|
|
31226
|
+
if (cursor < extEnd) post.push([cursor, extEnd]);
|
|
31227
|
+
}
|
|
31228
|
+
return { pre, post };
|
|
31229
|
+
}
|
|
31230
|
+
var FETCH_AHEAD_MS2 = 10 * 864e5;
|
|
31231
|
+
var MIN_LOOKBACK_MS = 3 * 864e5;
|
|
31232
|
+
var MAX_LOOKBACK_MS = 120 * 864e5;
|
|
31233
|
+
var SessionShadingTracker = class {
|
|
31234
|
+
constructor(onZones) {
|
|
31235
|
+
this.onZones = onZones;
|
|
31236
|
+
/** Invalidates detached async work — bumped by every track()/stop(). */
|
|
31237
|
+
this.epoch = 0;
|
|
31238
|
+
}
|
|
31239
|
+
/** (Re)bind to a chart's data surface + market and evaluate once. */
|
|
31240
|
+
track(data, symbol, opts) {
|
|
31241
|
+
const my = ++this.epoch;
|
|
31242
|
+
void this.evaluate(my, data, symbol, opts);
|
|
31243
|
+
}
|
|
31244
|
+
stop() {
|
|
31245
|
+
this.epoch += 1;
|
|
31246
|
+
}
|
|
31247
|
+
async evaluate(my, data, symbol, opts) {
|
|
31248
|
+
const resolved = data.resolve(symbol);
|
|
31249
|
+
const provider = resolved ? data.providerInstance(resolved.provider) : void 0;
|
|
31250
|
+
const si = await data.symbolInfo(symbol).catch(() => void 0);
|
|
31251
|
+
if (my !== this.epoch) return;
|
|
31252
|
+
const hasSessions = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
31253
|
+
if (!provider?.getCalendar || !hasSessions || !resolved) {
|
|
31254
|
+
this.onZones(null);
|
|
31255
|
+
return;
|
|
31256
|
+
}
|
|
31257
|
+
if (opts.session !== "extended") {
|
|
31258
|
+
this.onZones({ pre: [], post: [] });
|
|
31259
|
+
return;
|
|
31260
|
+
}
|
|
31261
|
+
const now = Date.now();
|
|
31262
|
+
const lookback = Math.max(MIN_LOOKBACK_MS, Math.min(MAX_LOOKBACK_MS, opts.lookbackMs));
|
|
31263
|
+
const range = { from: now - lookback, to: now + FETCH_AHEAD_MS2 };
|
|
31264
|
+
const [regular, extended] = await Promise.all([
|
|
31265
|
+
provider.getCalendar(resolved.ticker, { ...range, session: "regular" }).catch(() => null),
|
|
31266
|
+
provider.getCalendar(resolved.ticker, { ...range, session: "extended" }).catch(() => null)
|
|
31267
|
+
]);
|
|
31268
|
+
if (my !== this.epoch) return;
|
|
31269
|
+
if (!regular || !extended) return;
|
|
31270
|
+
this.onZones(deriveSessionZones(regular, extended));
|
|
31271
|
+
}
|
|
31272
|
+
};
|
|
31273
|
+
|
|
30898
31274
|
// src/widget/watermark.ts
|
|
30899
31275
|
var MAX_FONT_PX = 36;
|
|
30900
31276
|
var MIN_FONT_PX = 12;
|
|
30901
31277
|
var FILL = 0.9;
|
|
30902
|
-
var
|
|
31278
|
+
var STYLE_ID9 = "vela-widget-watermark";
|
|
30903
31279
|
var CSS6 = `
|
|
30904
31280
|
.vela-watermark {
|
|
30905
31281
|
position: absolute;
|
|
@@ -30938,7 +31314,7 @@ var Watermark = class {
|
|
|
30938
31314
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
30939
31315
|
* fires during chart construction, before any subscriber can see it. */
|
|
30940
31316
|
this.loading = true;
|
|
30941
|
-
injectStyles(
|
|
31317
|
+
injectStyles(STYLE_ID9, CSS6, host.ownerDocument);
|
|
30942
31318
|
this.el = host.ownerDocument.createElement("div");
|
|
30943
31319
|
this.el.className = "vela-watermark";
|
|
30944
31320
|
this.el.dataset.velaScreenshot = "under";
|
|
@@ -31002,7 +31378,7 @@ var RANGE_PRESETS = [
|
|
|
31002
31378
|
{ id: "ALL", tf: "W", preset: "ALL", bars: 5e3 }
|
|
31003
31379
|
// everything the provider serves
|
|
31004
31380
|
];
|
|
31005
|
-
var
|
|
31381
|
+
var STYLE_ID10 = "vela-widget-bottombar";
|
|
31006
31382
|
var CSS7 = `
|
|
31007
31383
|
.vela-widget-bottombar {
|
|
31008
31384
|
display: flex;
|
|
@@ -31082,7 +31458,7 @@ var Bottombar = class {
|
|
|
31082
31458
|
this.timer = null;
|
|
31083
31459
|
this.timezone = opts.timezone;
|
|
31084
31460
|
const doc = host.ownerDocument;
|
|
31085
|
-
injectStyles(
|
|
31461
|
+
injectStyles(STYLE_ID10, CSS7, doc);
|
|
31086
31462
|
this.el = doc.createElement("div");
|
|
31087
31463
|
this.el.className = "vela-widget-bottombar";
|
|
31088
31464
|
for (const preset of RANGE_PRESETS) {
|
|
@@ -31109,7 +31485,8 @@ var Bottombar = class {
|
|
|
31109
31485
|
const session = doc.createElement("span");
|
|
31110
31486
|
session.className = "vela-bb-session";
|
|
31111
31487
|
this.sessionEl = session;
|
|
31112
|
-
session.
|
|
31488
|
+
session.style.display = "none";
|
|
31489
|
+
session.title = "Session \u2014 regular (RTH) vs extended (ETH) hours";
|
|
31113
31490
|
for (const [key, label] of [["regular", "RTH"], ["extended", "ETH"]]) {
|
|
31114
31491
|
const b = doc.createElement("button");
|
|
31115
31492
|
b.className = "vela-bb-session-btn" + (key === "regular" ? " is-active" : "");
|
|
@@ -31160,15 +31537,15 @@ var Bottombar = class {
|
|
|
31160
31537
|
}
|
|
31161
31538
|
/**
|
|
31162
31539
|
* Reflect the ACTIVE chart's session posture. `enabled: false` (a continuous
|
|
31163
|
-
* market, or metadata not landed yet)
|
|
31164
|
-
*
|
|
31165
|
-
*
|
|
31540
|
+
* market, or metadata not landed yet) HIDES the toggle entirely — RTH/ETH is
|
|
31541
|
+
* meaningless there. Enabled, the chips appear and the active one tracks the
|
|
31542
|
+
* chart's current session.
|
|
31166
31543
|
*/
|
|
31167
31544
|
setSession(state) {
|
|
31168
|
-
if (this.sessionEl) this.sessionEl.
|
|
31545
|
+
if (this.sessionEl) this.sessionEl.style.display = state.enabled ? "" : "none";
|
|
31169
31546
|
for (const [key, b] of this.sessionButtons) {
|
|
31170
31547
|
b.disabled = !state.enabled;
|
|
31171
|
-
b.classList.toggle("is-active", state.enabled ?
|
|
31548
|
+
b.classList.toggle("is-active", key === (state.enabled ? state.session : "regular"));
|
|
31172
31549
|
}
|
|
31173
31550
|
}
|
|
31174
31551
|
destroy() {
|
|
@@ -31241,7 +31618,7 @@ function filterSymbols(list, query, limit = 100) {
|
|
|
31241
31618
|
}
|
|
31242
31619
|
return [...prefix, ...substr, ...desc, ...venue].slice(0, limit);
|
|
31243
31620
|
}
|
|
31244
|
-
var
|
|
31621
|
+
var STYLE_ID11 = "vela-widget-symbolpicker";
|
|
31245
31622
|
var CSS8 = `
|
|
31246
31623
|
.vela-sp-searchrow {
|
|
31247
31624
|
display: flex;
|
|
@@ -31339,7 +31716,7 @@ var SymbolPicker = class {
|
|
|
31339
31716
|
this.seed = "";
|
|
31340
31717
|
this.activeTab = "All";
|
|
31341
31718
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
31342
|
-
injectStyles(
|
|
31719
|
+
injectStyles(STYLE_ID11, CSS8, doc);
|
|
31343
31720
|
this.input = doc.createElement("input");
|
|
31344
31721
|
this.input.className = "vela-sp-input";
|
|
31345
31722
|
this.input.placeholder = "Search symbol\u2026";
|
|
@@ -31662,7 +32039,7 @@ function treeIsEmpty(panes) {
|
|
|
31662
32039
|
}
|
|
31663
32040
|
|
|
31664
32041
|
// src/widget/side-panel.ts
|
|
31665
|
-
var
|
|
32042
|
+
var STYLE_ID12 = "vela-widget-sidepanel";
|
|
31666
32043
|
var DEFAULT_PANEL_WIDTH = 280;
|
|
31667
32044
|
var DEFAULT_PANEL_MIN_WIDTH = 200;
|
|
31668
32045
|
var DEFAULT_PANEL_MAX_WIDTH = 640;
|
|
@@ -31766,7 +32143,7 @@ var SidePanel = class {
|
|
|
31766
32143
|
* a programmatic {@link setWidth}, so restoring a persisted width raises no change. */
|
|
31767
32144
|
this.onWidthChange = null;
|
|
31768
32145
|
const doc = host.ownerDocument;
|
|
31769
|
-
injectStyles(
|
|
32146
|
+
injectStyles(STYLE_ID12, CSS9, doc);
|
|
31770
32147
|
this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
|
|
31771
32148
|
this.maxWidth = opts.resizable ? opts.maxWidth ?? DEFAULT_PANEL_MAX_WIDTH : Number.POSITIVE_INFINITY;
|
|
31772
32149
|
this.declaredWidth = clampPanelWidth(opts.width ?? DEFAULT_PANEL_WIDTH, this.minWidth, this.maxWidth);
|
|
@@ -31875,7 +32252,7 @@ var SidePanel = class {
|
|
|
31875
32252
|
};
|
|
31876
32253
|
|
|
31877
32254
|
// src/widget/object-tree.ts
|
|
31878
|
-
var
|
|
32255
|
+
var STYLE_ID13 = "vela-widget-objtree";
|
|
31879
32256
|
var CSS10 = `
|
|
31880
32257
|
/* One pane's block. The transparent border reserves the drop-target outline. */
|
|
31881
32258
|
.vela-ot-pane {
|
|
@@ -32114,7 +32491,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
32114
32491
|
this.menu = null;
|
|
32115
32492
|
this.menuActions = /* @__PURE__ */ new Map();
|
|
32116
32493
|
this.drag = null;
|
|
32117
|
-
injectStyles(
|
|
32494
|
+
injectStyles(STYLE_ID13, CSS10, host.ownerDocument);
|
|
32118
32495
|
this.body.addEventListener("contextmenu", (e) => this.onContextMenu(e));
|
|
32119
32496
|
this.body.addEventListener("pointerdown", (e) => this.onPointerDown(e));
|
|
32120
32497
|
}
|
|
@@ -33096,7 +33473,7 @@ var ObjectTree = class extends SidePanel {
|
|
|
33096
33473
|
};
|
|
33097
33474
|
|
|
33098
33475
|
// src/widget/data-window.ts
|
|
33099
|
-
var
|
|
33476
|
+
var STYLE_ID14 = "vela-widget-datawindow";
|
|
33100
33477
|
var CSS11 = `
|
|
33101
33478
|
.vela-dw-group {
|
|
33102
33479
|
padding: 10px 8px 4px;
|
|
@@ -33109,6 +33486,9 @@ var CSS11 = `
|
|
|
33109
33486
|
letter-spacing: 0.08em;
|
|
33110
33487
|
}
|
|
33111
33488
|
.vela-dw-group:first-child { border-top: none; margin-top: 0; padding-top: 8px; }
|
|
33489
|
+
/* The readout is DATA, not chrome: selectable (an exception to the UI-wide
|
|
33490
|
+
user-select:none) so values can be copied out. The panel header stays chrome. */
|
|
33491
|
+
.vela-dw-group, .vela-dw-row { user-select: text; -webkit-user-select: text; cursor: text; }
|
|
33112
33492
|
.vela-dw-row {
|
|
33113
33493
|
display: flex;
|
|
33114
33494
|
align-items: center;
|
|
@@ -33156,7 +33536,7 @@ var DataWindow = class extends SidePanel {
|
|
|
33156
33536
|
super(host, "Data window", "vela-dw");
|
|
33157
33537
|
this.chart = null;
|
|
33158
33538
|
this.unsubs = [];
|
|
33159
|
-
injectStyles(
|
|
33539
|
+
injectStyles(STYLE_ID14, CSS11, host.ownerDocument);
|
|
33160
33540
|
}
|
|
33161
33541
|
toggle(open2 = this.el.hidden) {
|
|
33162
33542
|
super.toggle(open2);
|
|
@@ -33358,7 +33738,7 @@ var PanelDock = class {
|
|
|
33358
33738
|
};
|
|
33359
33739
|
|
|
33360
33740
|
// src/widget/shortcuts-help.ts
|
|
33361
|
-
var
|
|
33741
|
+
var STYLE_ID15 = "vela-widget-shortcuts";
|
|
33362
33742
|
var CSS12 = `
|
|
33363
33743
|
.vela-sh-cat {
|
|
33364
33744
|
color: var(--vela-fg-muted);
|
|
@@ -33384,7 +33764,7 @@ var ShortcutsHelp = class {
|
|
|
33384
33764
|
constructor(keymap, host, onOpenChange) {
|
|
33385
33765
|
this.keymap = keymap;
|
|
33386
33766
|
const doc = (host ?? document.body).ownerDocument;
|
|
33387
|
-
injectStyles(
|
|
33767
|
+
injectStyles(STYLE_ID15, CSS12, doc);
|
|
33388
33768
|
this.list = doc.createElement("div");
|
|
33389
33769
|
this.dialog = new Dialog({
|
|
33390
33770
|
title: "Keyboard shortcuts",
|
|
@@ -33571,6 +33951,9 @@ var ChartContextMenu = class {
|
|
|
33571
33951
|
host,
|
|
33572
33952
|
items: [],
|
|
33573
33953
|
placement: "bottom-start",
|
|
33954
|
+
// Pointer-anchored action menu: checked state reads as a leading ✓, not a
|
|
33955
|
+
// washed row (which would read as hover in a menu with no trigger button).
|
|
33956
|
+
checkmarks: true,
|
|
33574
33957
|
onSelect: (id) => this.run(id)
|
|
33575
33958
|
});
|
|
33576
33959
|
host.addEventListener("contextmenu", this.onContextMenu);
|
|
@@ -33672,7 +34055,7 @@ var ChartContextMenu = class {
|
|
|
33672
34055
|
};
|
|
33673
34056
|
|
|
33674
34057
|
// src/widget/indicator-picker.ts
|
|
33675
|
-
var
|
|
34058
|
+
var STYLE_ID16 = "vela-widget-indpicker";
|
|
33676
34059
|
var CSS13 = `
|
|
33677
34060
|
.vela-ip-searchrow {
|
|
33678
34061
|
display: flex;
|
|
@@ -33750,7 +34133,7 @@ var IndicatorPicker = class {
|
|
|
33750
34133
|
this.isOpen = false;
|
|
33751
34134
|
this.opts = opts;
|
|
33752
34135
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
33753
|
-
injectStyles(
|
|
34136
|
+
injectStyles(STYLE_ID16, CSS13, doc);
|
|
33754
34137
|
this.search = doc.createElement("input");
|
|
33755
34138
|
this.search.className = "vela-ip-search";
|
|
33756
34139
|
this.search.placeholder = "Search\u2026";
|
|
@@ -33878,7 +34261,7 @@ var IndicatorPicker = class {
|
|
|
33878
34261
|
};
|
|
33879
34262
|
|
|
33880
34263
|
// src/widget/timeframe-quick.ts
|
|
33881
|
-
var
|
|
34264
|
+
var STYLE_ID17 = "vela-widget-tfquick";
|
|
33882
34265
|
var CSS14 = `
|
|
33883
34266
|
.vela-tq-input {
|
|
33884
34267
|
display: block;
|
|
@@ -33902,7 +34285,7 @@ var CSS14 = `
|
|
|
33902
34285
|
var TimeframeQuick = class {
|
|
33903
34286
|
constructor(opts) {
|
|
33904
34287
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
33905
|
-
injectStyles(
|
|
34288
|
+
injectStyles(STYLE_ID17, CSS14, doc);
|
|
33906
34289
|
this.input = doc.createElement("input");
|
|
33907
34290
|
this.input.className = "vela-tq-input";
|
|
33908
34291
|
this.input.setAttribute("spellcheck", "false");
|
|
@@ -34084,7 +34467,7 @@ var LayoutModeController = class {
|
|
|
34084
34467
|
};
|
|
34085
34468
|
|
|
34086
34469
|
// src/widget/mobile-bar.ts
|
|
34087
|
-
var
|
|
34470
|
+
var STYLE_ID18 = "vela-widget-mobilebar";
|
|
34088
34471
|
var CSS15 = `
|
|
34089
34472
|
.vela-mobilebar {
|
|
34090
34473
|
display: none;
|
|
@@ -34135,7 +34518,7 @@ var MobileBar = class {
|
|
|
34135
34518
|
constructor(host, opts) {
|
|
34136
34519
|
this.opts = opts;
|
|
34137
34520
|
const doc = host.ownerDocument;
|
|
34138
|
-
injectStyles(
|
|
34521
|
+
injectStyles(STYLE_ID18, CSS15, doc);
|
|
34139
34522
|
this.el = doc.createElement("div");
|
|
34140
34523
|
this.el.className = "vela-mobilebar";
|
|
34141
34524
|
const item = (cls, label, onClick, icon2) => {
|
|
@@ -34194,7 +34577,7 @@ var MobileBar = class {
|
|
|
34194
34577
|
};
|
|
34195
34578
|
|
|
34196
34579
|
// src/widget/timeframe-drawer.ts
|
|
34197
|
-
var
|
|
34580
|
+
var STYLE_ID19 = "vela-widget-tf-drawer";
|
|
34198
34581
|
var CSS16 = `
|
|
34199
34582
|
.vela-tfd-heading {
|
|
34200
34583
|
padding: 4px 2px 8px;
|
|
@@ -34251,7 +34634,7 @@ var TimeframeDrawer = class {
|
|
|
34251
34634
|
this.rangeChips = /* @__PURE__ */ new Map();
|
|
34252
34635
|
this.tfChips = /* @__PURE__ */ new Map();
|
|
34253
34636
|
const doc = opts.host.ownerDocument;
|
|
34254
|
-
injectStyles(
|
|
34637
|
+
injectStyles(STYLE_ID19, CSS16, doc);
|
|
34255
34638
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
34256
34639
|
const rangeHeading = doc.createElement("div");
|
|
34257
34640
|
rangeHeading.className = "vela-tfd-heading";
|
|
@@ -34309,7 +34692,7 @@ var TimeframeDrawer = class {
|
|
|
34309
34692
|
};
|
|
34310
34693
|
|
|
34311
34694
|
// src/widget/drawings-drawer.ts
|
|
34312
|
-
var
|
|
34695
|
+
var STYLE_ID20 = "vela-widget-drawings-drawer";
|
|
34313
34696
|
var CSS17 = `
|
|
34314
34697
|
/* Search + tabs stay pinned while the tool list scrolls underneath. */
|
|
34315
34698
|
.vela-dd-sticky {
|
|
@@ -34412,7 +34795,7 @@ var DrawingsDrawer = class {
|
|
|
34412
34795
|
this.definition = { groups: [] };
|
|
34413
34796
|
this.activeGroup = "";
|
|
34414
34797
|
const doc = opts.host.ownerDocument;
|
|
34415
|
-
injectStyles(
|
|
34798
|
+
injectStyles(STYLE_ID20, CSS17, doc);
|
|
34416
34799
|
this.drawer = new Drawer({
|
|
34417
34800
|
host: opts.host,
|
|
34418
34801
|
title: "Drawings",
|
|
@@ -34554,7 +34937,7 @@ var DrawingsDrawer = class {
|
|
|
34554
34937
|
};
|
|
34555
34938
|
|
|
34556
34939
|
// src/widget/more-drawer.ts
|
|
34557
|
-
var
|
|
34940
|
+
var STYLE_ID21 = "vela-widget-more-drawer";
|
|
34558
34941
|
var CSS18 = `
|
|
34559
34942
|
.vela-md-actions {
|
|
34560
34943
|
display: flex;
|
|
@@ -34630,7 +35013,7 @@ var MoreDrawer = class {
|
|
|
34630
35013
|
constructor(opts) {
|
|
34631
35014
|
this.opts = opts;
|
|
34632
35015
|
this.view = "main";
|
|
34633
|
-
injectStyles(
|
|
35016
|
+
injectStyles(STYLE_ID21, CSS18, opts.host.ownerDocument);
|
|
34634
35017
|
this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
|
|
34635
35018
|
}
|
|
34636
35019
|
open() {
|
|
@@ -34822,7 +35205,7 @@ var MoreDrawer = class {
|
|
|
34822
35205
|
};
|
|
34823
35206
|
|
|
34824
35207
|
// src/widget/timezone-drawer.ts
|
|
34825
|
-
var
|
|
35208
|
+
var STYLE_ID22 = "vela-widget-timezone-drawer";
|
|
34826
35209
|
var CSS19 = `
|
|
34827
35210
|
.vela-tzd-list { padding: 2px 0 4px; }
|
|
34828
35211
|
.vela-tzd-row {
|
|
@@ -34842,7 +35225,7 @@ var CSS19 = `
|
|
|
34842
35225
|
var TimezoneDrawer = class {
|
|
34843
35226
|
constructor(opts) {
|
|
34844
35227
|
this.opts = opts;
|
|
34845
|
-
injectStyles(
|
|
35228
|
+
injectStyles(STYLE_ID22, CSS19, opts.host.ownerDocument);
|
|
34846
35229
|
this.drawer = new Drawer({ host: opts.host, title: "Time zone", onOpenChange: opts.onOpenChange });
|
|
34847
35230
|
}
|
|
34848
35231
|
open() {
|
|
@@ -34880,7 +35263,7 @@ var TimezoneDrawer = class {
|
|
|
34880
35263
|
};
|
|
34881
35264
|
|
|
34882
35265
|
// src/widget/price-scale-drawer.ts
|
|
34883
|
-
var
|
|
35266
|
+
var STYLE_ID23 = "vela-widget-price-scale-drawer";
|
|
34884
35267
|
var CSS20 = `
|
|
34885
35268
|
.vela-psd-list { padding: 2px 0 4px; }
|
|
34886
35269
|
.vela-psd-row {
|
|
@@ -34910,7 +35293,7 @@ var PriceScaleDrawer = class {
|
|
|
34910
35293
|
constructor(opts) {
|
|
34911
35294
|
this.opts = opts;
|
|
34912
35295
|
this.pane = null;
|
|
34913
|
-
injectStyles(
|
|
35296
|
+
injectStyles(STYLE_ID23, CSS20, opts.host.ownerDocument);
|
|
34914
35297
|
this.drawer = new Drawer({ host: opts.host, title: "Price scale", onOpenChange: opts.onOpenChange });
|
|
34915
35298
|
}
|
|
34916
35299
|
open() {
|
|
@@ -35046,7 +35429,7 @@ var PriceScaleDrawer = class {
|
|
|
35046
35429
|
};
|
|
35047
35430
|
|
|
35048
35431
|
// src/widget/drawing-pill.ts
|
|
35049
|
-
var
|
|
35432
|
+
var STYLE_ID24 = "vela-widget-drawing-pill";
|
|
35050
35433
|
var CSS21 = `
|
|
35051
35434
|
.vela-drawpill { display: none; }
|
|
35052
35435
|
[data-layout='mobile'] .vela-drawpill {
|
|
@@ -35107,7 +35490,7 @@ var DrawingPill = class {
|
|
|
35107
35490
|
this.chart = null;
|
|
35108
35491
|
this.chartSubs = [];
|
|
35109
35492
|
const doc = host.ownerDocument;
|
|
35110
|
-
injectStyles(
|
|
35493
|
+
injectStyles(STYLE_ID24, CSS21, doc);
|
|
35111
35494
|
this.el = doc.createElement("div");
|
|
35112
35495
|
this.el.className = "vela-drawpill";
|
|
35113
35496
|
this.el.hidden = true;
|
|
@@ -35495,7 +35878,7 @@ var WidgetHistory = class {
|
|
|
35495
35878
|
};
|
|
35496
35879
|
|
|
35497
35880
|
// src/widget/toast.ts
|
|
35498
|
-
var
|
|
35881
|
+
var STYLE_ID25 = "vela-widget-toast";
|
|
35499
35882
|
var CSS22 = `
|
|
35500
35883
|
.vela-toast {
|
|
35501
35884
|
position: absolute;
|
|
@@ -35526,7 +35909,7 @@ var CSS22 = `
|
|
|
35526
35909
|
var Toast = class {
|
|
35527
35910
|
constructor(host) {
|
|
35528
35911
|
this.timer = null;
|
|
35529
|
-
injectStyles(
|
|
35912
|
+
injectStyles(STYLE_ID25, CSS22, host.ownerDocument);
|
|
35530
35913
|
this.el = host.ownerDocument.createElement("div");
|
|
35531
35914
|
this.el.className = "vela-toast";
|
|
35532
35915
|
host.appendChild(this.el);
|
|
@@ -35609,6 +35992,11 @@ var WIDGET_CSS = `
|
|
|
35609
35992
|
`;
|
|
35610
35993
|
var VelaWidget = class {
|
|
35611
35994
|
constructor(container, opts) {
|
|
35995
|
+
/** Keeps the pre/post-market shading on the symbol's real calendar (see {@link SessionShadingTracker}). */
|
|
35996
|
+
this.sessionShading = new SessionShadingTracker((zones) => this.inner?.renderer.set("sessionZones", zones));
|
|
35997
|
+
/** Whether the ACTIVE symbol declares real trading sessions (RTH/ETH meaningful) —
|
|
35998
|
+
* gates the bottombar toggle and the settings dialog's Trading session group. */
|
|
35999
|
+
this.sessionsAvailable = false;
|
|
35612
36000
|
this.shortcutsHelp = null;
|
|
35613
36001
|
this.inner = null;
|
|
35614
36002
|
/** The manifest library (loaded once). */
|
|
@@ -36072,17 +36460,140 @@ var VelaWidget = class {
|
|
|
36072
36460
|
this.markStateDirty();
|
|
36073
36461
|
void this.inner?.setMarket({ session });
|
|
36074
36462
|
}
|
|
36075
|
-
/** Re-derive the
|
|
36076
|
-
*
|
|
36463
|
+
/** Re-derive the session chrome from the ACTIVE symbol's metadata: the RTH/ETH
|
|
36464
|
+
* toggle shows (and the settings dialog's Trading session group appears) iff the
|
|
36465
|
+
* resolved symbol declares a real session vocabulary (not `24x7`); the pre/post
|
|
36466
|
+
* shading re-derives for the new market either way. */
|
|
36077
36467
|
refreshSessionToggle() {
|
|
36078
36468
|
const chart = this.inner;
|
|
36079
|
-
if (!chart
|
|
36469
|
+
if (!chart) return;
|
|
36080
36470
|
void chart.data.symbolInfo(this.symbol).then((si) => {
|
|
36081
36471
|
if (this.destroyed || this.inner !== chart) return;
|
|
36082
36472
|
const enabled = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
36083
36473
|
this.bottombar?.setSession({ session: this.session ?? "regular", enabled });
|
|
36474
|
+
if (enabled !== this.sessionsAvailable) {
|
|
36475
|
+
this.sessionsAvailable = enabled;
|
|
36476
|
+
this.pushSettingsSections();
|
|
36477
|
+
}
|
|
36478
|
+
this.refreshSessionShading();
|
|
36084
36479
|
});
|
|
36085
36480
|
}
|
|
36481
|
+
/** (Re)derive the pre/post-market shading bands for the current market. The loaded
|
|
36482
|
+
* depth (bars × timeframe) bounds the calendar fetch; the tracker clamps it. */
|
|
36483
|
+
refreshSessionShading() {
|
|
36484
|
+
const chart = this.inner;
|
|
36485
|
+
if (!chart) return;
|
|
36486
|
+
const lookbackMs = Math.max(this.bars, this.rangeBars) * timeframeMs(this.timeframe);
|
|
36487
|
+
this.sessionShading.track(chart.data, this.symbol, { session: this.session ?? "regular", lookbackMs });
|
|
36488
|
+
}
|
|
36489
|
+
/** The session-shade colors live in the renderer CONFIG (persisted with it, edited
|
|
36490
|
+
* live by the dialog swatch) — the widget only proxies them into its settings rows. */
|
|
36491
|
+
sessionShadeColor(key) {
|
|
36492
|
+
const cfg = this.inner?.renderer.getConfig();
|
|
36493
|
+
const v = cfg?.sessions?.[key];
|
|
36494
|
+
return typeof v === "string" ? v : "";
|
|
36495
|
+
}
|
|
36496
|
+
setSessionShadeColor(key, color) {
|
|
36497
|
+
this.inner?.renderer.applyConfig({ sessions: { [key]: color } });
|
|
36498
|
+
this.markStateDirty();
|
|
36499
|
+
}
|
|
36500
|
+
/**
|
|
36501
|
+
* (Re)contribute the widget's settings-dialog sections: status line parts, the fetch
|
|
36502
|
+
* depth, the watermark toggle, and — only while the active symbol HAS sessions — the
|
|
36503
|
+
* Trading session group (RTH/ETH switch + the pre/post-market shading colors) inside
|
|
36504
|
+
* the Symbol tab. Re-run whenever a gate changes (the dialog reads them on open).
|
|
36505
|
+
*/
|
|
36506
|
+
pushSettingsSections() {
|
|
36507
|
+
const chart = this.inner;
|
|
36508
|
+
if (!chart) return;
|
|
36509
|
+
const rth = "Regular hours (RTH)";
|
|
36510
|
+
const eth = "Extended hours (ETH)";
|
|
36511
|
+
const sessionSection = {
|
|
36512
|
+
title: "Trading session",
|
|
36513
|
+
placement: "symbol",
|
|
36514
|
+
rows: [
|
|
36515
|
+
{
|
|
36516
|
+
kind: "select",
|
|
36517
|
+
label: "Session",
|
|
36518
|
+
options: [rth, eth],
|
|
36519
|
+
get: () => (this.session ?? "regular") === "extended" ? eth : rth,
|
|
36520
|
+
set: (v) => this.setSession(v === eth ? "extended" : "regular")
|
|
36521
|
+
},
|
|
36522
|
+
{
|
|
36523
|
+
kind: "color",
|
|
36524
|
+
label: "Pre-market",
|
|
36525
|
+
get: () => this.sessionShadeColor("premarketColor"),
|
|
36526
|
+
set: (v) => this.setSessionShadeColor("premarketColor", v)
|
|
36527
|
+
},
|
|
36528
|
+
{
|
|
36529
|
+
kind: "color",
|
|
36530
|
+
label: "Post-market",
|
|
36531
|
+
get: () => this.sessionShadeColor("postmarketColor"),
|
|
36532
|
+
set: (v) => this.setSessionShadeColor("postmarketColor", v)
|
|
36533
|
+
}
|
|
36534
|
+
]
|
|
36535
|
+
};
|
|
36536
|
+
const advanced = {
|
|
36537
|
+
title: "Advanced",
|
|
36538
|
+
placement: "end",
|
|
36539
|
+
rows: [
|
|
36540
|
+
{
|
|
36541
|
+
kind: "select",
|
|
36542
|
+
label: "Bars to fetch",
|
|
36543
|
+
options: ["500", "1000", "2000", "5000", "10000", "20000"],
|
|
36544
|
+
get: () => String(this.bars),
|
|
36545
|
+
set: (v) => {
|
|
36546
|
+
this.bars = Number(v);
|
|
36547
|
+
this.markStateDirty();
|
|
36548
|
+
void this.inner?.setMarket({ bars: Math.max(this.bars, this.rangeBars) });
|
|
36549
|
+
}
|
|
36550
|
+
}
|
|
36551
|
+
]
|
|
36552
|
+
};
|
|
36553
|
+
const watermarkSection = {
|
|
36554
|
+
title: "Watermark",
|
|
36555
|
+
placement: "symbol",
|
|
36556
|
+
rows: [
|
|
36557
|
+
{
|
|
36558
|
+
kind: "toggle",
|
|
36559
|
+
label: "Symbol watermark",
|
|
36560
|
+
get: () => this.watermarkOn,
|
|
36561
|
+
set: (v) => this.setWatermarkVisible(v)
|
|
36562
|
+
}
|
|
36563
|
+
]
|
|
36564
|
+
};
|
|
36565
|
+
const sections = [];
|
|
36566
|
+
if (this.statusline) {
|
|
36567
|
+
const sl = this.statusline;
|
|
36568
|
+
sections.push({
|
|
36569
|
+
title: "Status line",
|
|
36570
|
+
rows: [
|
|
36571
|
+
{ kind: "heading", label: "Status line" },
|
|
36572
|
+
{ kind: "toggle", label: "Symbol name", get: () => sl.partVisible("name"), set: (v) => sl.setPartVisible("name", v) },
|
|
36573
|
+
{ kind: "toggle", label: "Market status", get: () => sl.partVisible("market"), set: (v) => sl.setPartVisible("market", v) },
|
|
36574
|
+
{ kind: "toggle", label: "OHLC values", get: () => sl.partVisible("ohlc"), set: (v) => sl.setPartVisible("ohlc", v) },
|
|
36575
|
+
{ kind: "toggle", label: "Bar change values", get: () => sl.partVisible("change"), set: (v) => sl.setPartVisible("change", v) },
|
|
36576
|
+
{ kind: "heading", label: "Indicators" },
|
|
36577
|
+
{
|
|
36578
|
+
kind: "toggle",
|
|
36579
|
+
label: "Titles",
|
|
36580
|
+
get: () => this.indicatorTitlesOn,
|
|
36581
|
+
set: (v) => this.setIndicatorTitlesVisible(v)
|
|
36582
|
+
},
|
|
36583
|
+
{
|
|
36584
|
+
kind: "toggle",
|
|
36585
|
+
label: "Values",
|
|
36586
|
+
get: () => this.indicatorValuesOn,
|
|
36587
|
+
set: (v) => this.setIndicatorValuesVisible(v)
|
|
36588
|
+
}
|
|
36589
|
+
]
|
|
36590
|
+
});
|
|
36591
|
+
}
|
|
36592
|
+
sections.push(advanced);
|
|
36593
|
+
if (this.sessionsAvailable) sections.push(sessionSection);
|
|
36594
|
+
sections.push(watermarkSection);
|
|
36595
|
+
chart.renderer.setSettingsSections(sections);
|
|
36596
|
+
}
|
|
36086
36597
|
/** Star/unstar a timeframe — the topbar chips and dropdown stars follow, and the
|
|
36087
36598
|
* set persists with the rest of the shell state. */
|
|
36088
36599
|
setTimeframeFavorite(tf, on) {
|
|
@@ -36388,6 +36899,7 @@ var VelaWidget = class {
|
|
|
36388
36899
|
this.indicatorPicker?.destroy();
|
|
36389
36900
|
this.tfQuick.destroy();
|
|
36390
36901
|
this.marketStatus?.stop();
|
|
36902
|
+
this.sessionShading.stop();
|
|
36391
36903
|
this.statusline?.destroy();
|
|
36392
36904
|
this.watermark?.destroy();
|
|
36393
36905
|
this.bottombar?.destroy();
|
|
@@ -36526,7 +37038,10 @@ var VelaWidget = class {
|
|
|
36526
37038
|
this.refreshSessionToggle();
|
|
36527
37039
|
this.marketStatus?.track(chart.data, symbol);
|
|
36528
37040
|
}
|
|
36529
|
-
if (timeframe !== this.timeframe)
|
|
37041
|
+
if (timeframe !== this.timeframe) {
|
|
37042
|
+
this.syncTimeframeChrome(timeframe);
|
|
37043
|
+
this.refreshSessionShading();
|
|
37044
|
+
}
|
|
36530
37045
|
});
|
|
36531
37046
|
this.history.onChart(chart);
|
|
36532
37047
|
chart.on("alert", (alert) => {
|
|
@@ -36575,67 +37090,7 @@ var VelaWidget = class {
|
|
|
36575
37090
|
this.statusline?.onChart(chart);
|
|
36576
37091
|
this.drawingPill.onChart(chart);
|
|
36577
37092
|
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
|
-
}
|
|
37093
|
+
this.pushSettingsSections();
|
|
36639
37094
|
void chart.ready().then(() => {
|
|
36640
37095
|
if (this.buildSeq !== seq || this.destroyed) return;
|
|
36641
37096
|
if (this.pendingRange) {
|