@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/index.cjs
CHANGED
|
@@ -9896,7 +9896,7 @@ function attachChromeTooltip(anchor, opts) {
|
|
|
9896
9896
|
} else {
|
|
9897
9897
|
const left = Math.min(a.left - h.left, Math.max(0, h.width - tip.offsetWidth - 4));
|
|
9898
9898
|
tip.style.left = `${Math.max(0, left)}px`;
|
|
9899
|
-
tip.style.top = `${a.bottom - h.top + 6}px`;
|
|
9899
|
+
tip.style.top = opts.placement === "above" ? `${Math.max(0, a.top - h.top - tip.offsetHeight - 6)}px` : `${a.bottom - h.top + 6}px`;
|
|
9900
9900
|
}
|
|
9901
9901
|
};
|
|
9902
9902
|
const arm = (e) => {
|
|
@@ -10022,6 +10022,23 @@ var MENU_CSS = `
|
|
|
10022
10022
|
background: var(--vela-hover-strong);
|
|
10023
10023
|
color: var(--vela-fg-bright);
|
|
10024
10024
|
}
|
|
10025
|
+
/* Checkmark mode (context/action menus): every row reserves the leading mark column so
|
|
10026
|
+
labels align; a checked row fills it with a \u2713 and brightens its ink \u2014 the row surface
|
|
10027
|
+
stays free for the hover wash. */
|
|
10028
|
+
.vela-menu-item .vela-menu-mark {
|
|
10029
|
+
width: 14px;
|
|
10030
|
+
flex: none;
|
|
10031
|
+
display: inline-flex;
|
|
10032
|
+
align-items: center;
|
|
10033
|
+
justify-content: center;
|
|
10034
|
+
}
|
|
10035
|
+
.vela-menu-item .vela-menu-mark .vela-icon {
|
|
10036
|
+
width: 14px;
|
|
10037
|
+
height: 14px;
|
|
10038
|
+
font-size: 14px;
|
|
10039
|
+
color: var(--vela-fg-bright);
|
|
10040
|
+
}
|
|
10041
|
+
.vela-menu-item[data-checkmark] { color: var(--vela-fg-bright); }
|
|
10025
10042
|
/* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
|
|
10026
10043
|
same control language as the settings dialog's toggles. */
|
|
10027
10044
|
.vela-menu-switch {
|
|
@@ -10090,6 +10107,7 @@ var Surface = class _Surface {
|
|
|
10090
10107
|
this.host = opts.host;
|
|
10091
10108
|
this.onSelect = opts.onSelect;
|
|
10092
10109
|
this.onFavorite = opts.onFavorite;
|
|
10110
|
+
this.checkmarks = opts.checkmarks === true;
|
|
10093
10111
|
this.positioner = doc.createElement("div");
|
|
10094
10112
|
this.positioner.className = "vela-ui-layer";
|
|
10095
10113
|
this.list = doc.createElement("ul");
|
|
@@ -10168,6 +10186,7 @@ var Surface = class _Surface {
|
|
|
10168
10186
|
build() {
|
|
10169
10187
|
const doc = this.doc;
|
|
10170
10188
|
this.list.replaceChildren();
|
|
10189
|
+
const markable = this.checkmarks && this.items.some((i) => !(i.submenu && i.submenu.length > 0) && !i.toggle && i.checked !== void 0);
|
|
10171
10190
|
for (const item of this.items) {
|
|
10172
10191
|
if (item.separatorBefore) {
|
|
10173
10192
|
const sep = doc.createElement("li");
|
|
@@ -10181,7 +10200,16 @@ var Surface = class _Surface {
|
|
|
10181
10200
|
li.dataset.veiId = item.id;
|
|
10182
10201
|
if (item.toggle) {
|
|
10183
10202
|
li.dataset.toggle = "1";
|
|
10184
|
-
}
|
|
10203
|
+
}
|
|
10204
|
+
if (markable) {
|
|
10205
|
+
const mark = doc.createElement("span");
|
|
10206
|
+
mark.className = "vela-menu-mark";
|
|
10207
|
+
if (!branch && !item.toggle && item.checked) {
|
|
10208
|
+
mark.appendChild(iconEl("check", doc));
|
|
10209
|
+
li.dataset.checkmark = "1";
|
|
10210
|
+
}
|
|
10211
|
+
li.appendChild(mark);
|
|
10212
|
+
} else if (!branch && !item.toggle && item.checked) {
|
|
10185
10213
|
li.dataset.checked = "1";
|
|
10186
10214
|
}
|
|
10187
10215
|
if (item.icon) li.appendChild(iconEl(item.icon, doc));
|
|
@@ -10230,6 +10258,7 @@ var Surface = class _Surface {
|
|
|
10230
10258
|
placement: "right-start",
|
|
10231
10259
|
onSelect: this.onSelect,
|
|
10232
10260
|
onFavorite: this.onFavorite,
|
|
10261
|
+
checkmarks: this.checkmarks,
|
|
10233
10262
|
id: `${this.mid}--${item.id}`
|
|
10234
10263
|
});
|
|
10235
10264
|
sub.setItems(item.submenu ?? []);
|
|
@@ -10254,7 +10283,8 @@ var Menu = class {
|
|
|
10254
10283
|
triggerId: opts.triggerId,
|
|
10255
10284
|
id: opts.id,
|
|
10256
10285
|
minWidth: opts.minWidth,
|
|
10257
|
-
onFavorite: opts.onFavorite
|
|
10286
|
+
onFavorite: opts.onFavorite,
|
|
10287
|
+
checkmarks: opts.checkmarks
|
|
10258
10288
|
});
|
|
10259
10289
|
this.root.setItems(opts.items);
|
|
10260
10290
|
}
|
|
@@ -10282,6 +10312,13 @@ var Menu = class {
|
|
|
10282
10312
|
}
|
|
10283
10313
|
};
|
|
10284
10314
|
|
|
10315
|
+
// src/core/model/inputs.ts
|
|
10316
|
+
function inputVisible(when, values) {
|
|
10317
|
+
if (!when) return true;
|
|
10318
|
+
const conds = Array.isArray(when) ? when : [when];
|
|
10319
|
+
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
|
|
10320
|
+
}
|
|
10321
|
+
|
|
10285
10322
|
// src/ui/tokens.ts
|
|
10286
10323
|
var STATIC_ID = "vela-ui-tokens";
|
|
10287
10324
|
var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
|
|
@@ -10290,8 +10327,13 @@ var STATIC_CSS = `
|
|
|
10290
10327
|
${STATIC_DECLS}
|
|
10291
10328
|
font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
|
|
10292
10329
|
box-sizing: border-box;
|
|
10330
|
+
/* Chrome text (titles, buttons, menus, readouts) is UI, not copy \u2014 never selectable. */
|
|
10331
|
+
user-select: none;
|
|
10332
|
+
-webkit-user-select: none;
|
|
10293
10333
|
}
|
|
10294
10334
|
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
10335
|
+
/* Text ENTRY is the one exception: selection is part of editing. */
|
|
10336
|
+
.vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
|
|
10295
10337
|
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
10296
10338
|
.vela-icon svg { display: block; }
|
|
10297
10339
|
`;
|
|
@@ -11288,6 +11330,9 @@ var NUMBER_CSS = `
|
|
|
11288
11330
|
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
11289
11331
|
-moz-appearance: textfield;
|
|
11290
11332
|
appearance: textfield;
|
|
11333
|
+
/* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
|
|
11334
|
+
user-select: text;
|
|
11335
|
+
-webkit-user-select: text;
|
|
11291
11336
|
}
|
|
11292
11337
|
.vela-num input::-webkit-inner-spin-button, .vela-num input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
|
|
11293
11338
|
.vela-num input:hover { border-color: var(--vela-fg-muted); }
|
|
@@ -11500,6 +11545,9 @@ var TEXT_CSS = `
|
|
|
11500
11545
|
overflow: hidden;
|
|
11501
11546
|
text-overflow: ellipsis;
|
|
11502
11547
|
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
11548
|
+
/* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
|
|
11549
|
+
user-select: text;
|
|
11550
|
+
-webkit-user-select: text;
|
|
11503
11551
|
}
|
|
11504
11552
|
.vela-text-field:hover { border-color: var(--vela-fg-muted); }
|
|
11505
11553
|
.vela-text-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
@@ -11593,6 +11641,9 @@ var TEXTAREA_CSS = `
|
|
|
11593
11641
|
resize: vertical;
|
|
11594
11642
|
outline: none;
|
|
11595
11643
|
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
11644
|
+
/* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
|
|
11645
|
+
user-select: text;
|
|
11646
|
+
-webkit-user-select: text;
|
|
11596
11647
|
}
|
|
11597
11648
|
.vela-textarea-field:hover { border-color: var(--vela-fg-muted); }
|
|
11598
11649
|
.vela-textarea-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
@@ -12121,6 +12172,8 @@ var FIELD_CSS = `
|
|
|
12121
12172
|
font-size: 14px;
|
|
12122
12173
|
}
|
|
12123
12174
|
.vela-field-label[data-size='sm'] { font-size: inherit; opacity: 0.85; }
|
|
12175
|
+
/* Titles are fully inert: spans, not label[for] \u2014 native labels propagate :hover and
|
|
12176
|
+
clicks onto their control, and both belong to the input alone. */
|
|
12124
12177
|
.vela-field-cell { display: flex; align-items: center; gap: 8px; justify-self: start; }
|
|
12125
12178
|
.vela-field-bool { display: flex; align-items: center; gap: 8px; min-height: 22px; }
|
|
12126
12179
|
.vela-field-stacked { display: flex; flex-direction: column; gap: 8px; }
|
|
@@ -12179,11 +12232,17 @@ function fieldSeparator() {
|
|
|
12179
12232
|
return el;
|
|
12180
12233
|
}
|
|
12181
12234
|
function labelEl(text, opts) {
|
|
12182
|
-
const el =
|
|
12183
|
-
if (opts.id && el instanceof HTMLLabelElement) el.htmlFor = opts.id;
|
|
12235
|
+
const el = document.createElement("span");
|
|
12184
12236
|
el.className = "vela-field-label";
|
|
12185
12237
|
if (opts.size) el.dataset.size = opts.size;
|
|
12186
12238
|
el.textContent = text;
|
|
12239
|
+
const id = opts.id;
|
|
12240
|
+
if (id) {
|
|
12241
|
+
el.id = `${id}--title`;
|
|
12242
|
+
queueMicrotask(() => {
|
|
12243
|
+
el.ownerDocument.getElementById(id)?.setAttribute("aria-labelledby", el.id);
|
|
12244
|
+
});
|
|
12245
|
+
}
|
|
12187
12246
|
return el;
|
|
12188
12247
|
}
|
|
12189
12248
|
function asList(control) {
|
|
@@ -12212,9 +12271,7 @@ function fieldRow(opts) {
|
|
|
12212
12271
|
it.className = "vela-field-inline-item";
|
|
12213
12272
|
if (item.toggleFirst) it.style.flexDirection = "row-reverse";
|
|
12214
12273
|
if (item.label) {
|
|
12215
|
-
|
|
12216
|
-
if (item.toggleFirst) lbl.style.cursor = "pointer";
|
|
12217
|
-
it.appendChild(lbl);
|
|
12274
|
+
it.appendChild(labelEl(item.label, { id: item.id, size }));
|
|
12218
12275
|
}
|
|
12219
12276
|
if (item.fit) it.appendChild(item.control);
|
|
12220
12277
|
else {
|
|
@@ -12239,7 +12296,7 @@ function fieldRow(opts) {
|
|
|
12239
12296
|
tone: "bright",
|
|
12240
12297
|
onChange: (v) => opts.toggle?.onChange(v)
|
|
12241
12298
|
});
|
|
12242
|
-
wrap2.append(sw.el, labelEl(opts.label, { size }));
|
|
12299
|
+
wrap2.append(sw.el, labelEl(opts.label, { id: opts.toggle?.id ?? opts.id, size }));
|
|
12243
12300
|
if (opts.info) wrap2.appendChild(opts.info);
|
|
12244
12301
|
if (opts.toggle?.get) {
|
|
12245
12302
|
const get = opts.toggle.get;
|
|
@@ -12278,7 +12335,7 @@ function fieldRow(opts) {
|
|
|
12278
12335
|
if (controls.length > 0) dimControls(controls, v);
|
|
12279
12336
|
}
|
|
12280
12337
|
});
|
|
12281
|
-
left.append(sw.el, labelEl(opts.label, { size }));
|
|
12338
|
+
left.append(sw.el, labelEl(opts.label, { id: opts.toggle.id ?? opts.id, size }));
|
|
12282
12339
|
wrap.appendChild(left);
|
|
12283
12340
|
if (controls.length > 0) dimControls(controls, opts.toggle.checked);
|
|
12284
12341
|
if (opts.toggle.get) {
|
|
@@ -12432,6 +12489,8 @@ var IndicatorInputsDialog = class {
|
|
|
12432
12489
|
this.row = null;
|
|
12433
12490
|
this.snapshot = null;
|
|
12434
12491
|
this.dialogTips = [];
|
|
12492
|
+
/** Re-applies every `when` gate against current values; set while the dialog is open. */
|
|
12493
|
+
this.refreshVisibility = null;
|
|
12435
12494
|
this.choicePop = null;
|
|
12436
12495
|
this.calendarPop = null;
|
|
12437
12496
|
this.calendarAnchor = null;
|
|
@@ -12494,14 +12553,17 @@ var IndicatorInputsDialog = class {
|
|
|
12494
12553
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
12495
12554
|
const tabEls = [];
|
|
12496
12555
|
const bodies = [];
|
|
12556
|
+
const gates = [];
|
|
12557
|
+
let active = 0;
|
|
12497
12558
|
const activate = (idx) => {
|
|
12559
|
+
active = idx;
|
|
12498
12560
|
for (let i = 0; i < tabEls.length; i += 1) {
|
|
12499
|
-
const
|
|
12561
|
+
const on = i === idx;
|
|
12500
12562
|
const el = tabEls[i];
|
|
12501
|
-
el.style.borderBottomColor =
|
|
12502
|
-
el.style.color =
|
|
12503
|
-
el.classList.toggle("vela-ind-tab-active",
|
|
12504
|
-
bodies[i].style.display =
|
|
12563
|
+
el.style.borderBottomColor = on ? fg : "transparent";
|
|
12564
|
+
el.style.color = on ? "inherit" : "var(--vela-fg-muted)";
|
|
12565
|
+
el.classList.toggle("vela-ind-tab-active", on);
|
|
12566
|
+
bodies[i].style.display = on ? "grid" : "none";
|
|
12505
12567
|
}
|
|
12506
12568
|
};
|
|
12507
12569
|
for (const [idx, def] of tabDefs.entries()) {
|
|
@@ -12512,11 +12574,24 @@ var IndicatorInputsDialog = class {
|
|
|
12512
12574
|
tab.addEventListener("click", () => activate(idx));
|
|
12513
12575
|
tabs.appendChild(tab);
|
|
12514
12576
|
tabEls.push(tab);
|
|
12515
|
-
bodies.push(this.buildTabBody(def.inputs, row));
|
|
12577
|
+
bodies.push(this.buildTabBody(def.inputs, row, gates));
|
|
12578
|
+
if (def.inputs.every((d) => d.when)) {
|
|
12579
|
+
gates.push({ el: tab, visible: (bag) => def.inputs.some((d) => inputVisible(d.when, bag)) });
|
|
12580
|
+
}
|
|
12516
12581
|
}
|
|
12517
12582
|
ui.body.appendChild(tabs);
|
|
12518
12583
|
for (const b of bodies) ui.body.appendChild(b);
|
|
12519
12584
|
activate(0);
|
|
12585
|
+
const refresh = () => {
|
|
12586
|
+
const bag = valuesBag(row);
|
|
12587
|
+
for (const g of gates) g.el.style.display = g.visible(bag) ? "" : "none";
|
|
12588
|
+
if (tabEls[active]?.style.display === "none") {
|
|
12589
|
+
const next = tabEls.findIndex((el) => el.style.display !== "none");
|
|
12590
|
+
if (next >= 0) activate(next);
|
|
12591
|
+
}
|
|
12592
|
+
};
|
|
12593
|
+
this.refreshVisibility = refresh;
|
|
12594
|
+
refresh();
|
|
12520
12595
|
const onBackdropDown = (e) => {
|
|
12521
12596
|
if (e.target !== ui.backdrop && e.target !== ui.positioner) return;
|
|
12522
12597
|
if (isPopoverOpen() || eventDismissedPopover(e)) {
|
|
@@ -12536,16 +12611,27 @@ var IndicatorInputsDialog = class {
|
|
|
12536
12611
|
}
|
|
12537
12612
|
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
12538
12613
|
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
12539
|
-
buildTabBody(inputs, row) {
|
|
12614
|
+
buildTabBody(inputs, row, gates) {
|
|
12540
12615
|
const body = document.createElement("div");
|
|
12541
12616
|
body.style.cssText = inputDialogBodyStyle(this.host.mobile());
|
|
12542
12617
|
for (const group of groupInputs(inputs)) {
|
|
12543
12618
|
const section = document.createElement("div");
|
|
12544
12619
|
section.style.cssText = "display:contents;";
|
|
12620
|
+
let header = null;
|
|
12545
12621
|
if (group.name) {
|
|
12546
|
-
|
|
12622
|
+
header = fieldSection(group.name, { variant: "inputs", first: body.childElementCount === 0 });
|
|
12623
|
+
section.appendChild(header);
|
|
12624
|
+
}
|
|
12625
|
+
for (const inputRow of group.rows) {
|
|
12626
|
+
const el = this.buildInputRow(section, inputRow, row);
|
|
12627
|
+
if (inputRow.some((d) => d.when)) {
|
|
12628
|
+
gates.push({ el, visible: (bag) => rowVisible(inputRow, bag) });
|
|
12629
|
+
}
|
|
12630
|
+
}
|
|
12631
|
+
if (header && group.rows.every((r) => r.every((d) => d.when))) {
|
|
12632
|
+
const rows = group.rows;
|
|
12633
|
+
gates.push({ el: header, visible: (bag) => rows.some((r) => rowVisible(r, bag)) });
|
|
12547
12634
|
}
|
|
12548
|
-
for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
|
|
12549
12635
|
body.appendChild(section);
|
|
12550
12636
|
}
|
|
12551
12637
|
return body;
|
|
@@ -12553,6 +12639,7 @@ var IndicatorInputsDialog = class {
|
|
|
12553
12639
|
close() {
|
|
12554
12640
|
document.removeEventListener("keydown", this.onDialogKey);
|
|
12555
12641
|
closeOpenPopovers();
|
|
12642
|
+
this.refreshVisibility = null;
|
|
12556
12643
|
this.choicePop = null;
|
|
12557
12644
|
this.calendarPop = null;
|
|
12558
12645
|
this.calendarAnchor = null;
|
|
@@ -12582,11 +12669,21 @@ var IndicatorInputsDialog = class {
|
|
|
12582
12669
|
}
|
|
12583
12670
|
this.close();
|
|
12584
12671
|
}
|
|
12672
|
+
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
12673
|
+
commit(row, key, value) {
|
|
12674
|
+
row.values[key] = value;
|
|
12675
|
+
this.host.onChange?.({ indicatorId: row.id, key, value });
|
|
12676
|
+
this.refreshVisibility?.();
|
|
12677
|
+
}
|
|
12585
12678
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
12586
12679
|
buildInputRow(grid, decls, row) {
|
|
12587
12680
|
const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
|
|
12681
|
+
const append = (el) => {
|
|
12682
|
+
grid.appendChild(el);
|
|
12683
|
+
return el;
|
|
12684
|
+
};
|
|
12588
12685
|
if (decls.length > 1) {
|
|
12589
|
-
|
|
12686
|
+
return append(fieldRow({
|
|
12590
12687
|
label: "",
|
|
12591
12688
|
inline: decls.map((d) => ({
|
|
12592
12689
|
label: nameOf(d) || void 0,
|
|
@@ -12597,14 +12694,13 @@ var IndicatorInputsDialog = class {
|
|
|
12597
12694
|
})),
|
|
12598
12695
|
info: [...decls].reverse().find((d) => d.tooltip)?.tooltip ? this.infoButton([...decls].reverse().find((d) => d.tooltip).tooltip) : void 0
|
|
12599
12696
|
}));
|
|
12600
|
-
return;
|
|
12601
12697
|
}
|
|
12602
12698
|
const lead = decls[0];
|
|
12603
12699
|
const id = idOf(lead);
|
|
12604
12700
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
12605
12701
|
if (lead.type === "bool") {
|
|
12606
12702
|
const current = Boolean(row.values[lead.key] ?? lead.defval);
|
|
12607
|
-
|
|
12703
|
+
return append(fieldRow({
|
|
12608
12704
|
label: nameOf(lead),
|
|
12609
12705
|
id,
|
|
12610
12706
|
bool: true,
|
|
@@ -12612,26 +12708,21 @@ var IndicatorInputsDialog = class {
|
|
|
12612
12708
|
toggle: {
|
|
12613
12709
|
id,
|
|
12614
12710
|
checked: current,
|
|
12615
|
-
onChange: (v) =>
|
|
12616
|
-
row.values[lead.key] = v;
|
|
12617
|
-
this.host.onChange?.({ indicatorId: row.id, key: lead.key, value: v });
|
|
12618
|
-
}
|
|
12711
|
+
onChange: (v) => this.commit(row, lead.key, v)
|
|
12619
12712
|
}
|
|
12620
12713
|
}));
|
|
12621
|
-
return;
|
|
12622
12714
|
}
|
|
12623
12715
|
if (lead.type === "text_area") {
|
|
12624
|
-
|
|
12716
|
+
return append(fieldRow({
|
|
12625
12717
|
label: nameOf(lead),
|
|
12626
12718
|
id,
|
|
12627
12719
|
stacked: true,
|
|
12628
12720
|
info,
|
|
12629
12721
|
control: this.buildControl(row, lead, id)
|
|
12630
12722
|
}));
|
|
12631
|
-
return;
|
|
12632
12723
|
}
|
|
12633
12724
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
12634
|
-
|
|
12725
|
+
return append(fieldRow({
|
|
12635
12726
|
label: nameOf(lead),
|
|
12636
12727
|
id,
|
|
12637
12728
|
fit,
|
|
@@ -12643,10 +12734,7 @@ var IndicatorInputsDialog = class {
|
|
|
12643
12734
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
12644
12735
|
buildControl(row, inp, id) {
|
|
12645
12736
|
const current = row.values[inp.key] ?? inp.defval;
|
|
12646
|
-
const emit = (value) =>
|
|
12647
|
-
row.values[inp.key] = value;
|
|
12648
|
-
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value });
|
|
12649
|
-
};
|
|
12737
|
+
const emit = (value) => this.commit(row, inp.key, value);
|
|
12650
12738
|
if (inp.type === "bool") {
|
|
12651
12739
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
12652
12740
|
}
|
|
@@ -13067,6 +13155,14 @@ function tabInputs(inputs) {
|
|
|
13067
13155
|
}
|
|
13068
13156
|
return names.map((name) => ({ name, inputs: byTab.get(name) }));
|
|
13069
13157
|
}
|
|
13158
|
+
function rowVisible(decls, bag) {
|
|
13159
|
+
return decls.some((d) => inputVisible(d.when, bag));
|
|
13160
|
+
}
|
|
13161
|
+
function valuesBag(row) {
|
|
13162
|
+
const bag = {};
|
|
13163
|
+
for (const inp of row.inputs) bag[inp.key] = row.values[inp.key] ?? inp.defval;
|
|
13164
|
+
return bag;
|
|
13165
|
+
}
|
|
13070
13166
|
function groupInputs(inputs) {
|
|
13071
13167
|
const order = [];
|
|
13072
13168
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -13529,6 +13625,8 @@ var InputsUI = class {
|
|
|
13529
13625
|
host: this.container,
|
|
13530
13626
|
items,
|
|
13531
13627
|
placement: "bottom-start",
|
|
13628
|
+
// Right-click action menu — checked state reads as a leading ✓ (see the kit Menu).
|
|
13629
|
+
checkmarks: true,
|
|
13532
13630
|
onSelect: (itemId) => {
|
|
13533
13631
|
if (itemId !== "values" || !this.rowMenuId) return;
|
|
13534
13632
|
const target = this.rows.get(this.rowMenuId);
|
|
@@ -13864,6 +13962,8 @@ var InputsUI = class {
|
|
|
13864
13962
|
syncRowActions(row) {
|
|
13865
13963
|
const open2 = row.highlighted;
|
|
13866
13964
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
13965
|
+
if (open2) row.el.appendChild(row.statusEl);
|
|
13966
|
+
else row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
13867
13967
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
13868
13968
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
13869
13969
|
if (child === row.extrasEl) {
|
|
@@ -14107,6 +14207,148 @@ var PaneControls = class {
|
|
|
14107
14207
|
}
|
|
14108
14208
|
};
|
|
14109
14209
|
|
|
14210
|
+
// src/renderers/native/chrome/axisLayout.ts
|
|
14211
|
+
var AXIS_MASTER_W = 64;
|
|
14212
|
+
var AXIS_MERGED_W = 56;
|
|
14213
|
+
var PANE_SEPARATOR_PX = 3;
|
|
14214
|
+
function axisColumnX(dataW, column) {
|
|
14215
|
+
return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
|
|
14216
|
+
}
|
|
14217
|
+
|
|
14218
|
+
// src/renderers/native/chrome/AxisScaleButtons.ts
|
|
14219
|
+
var STYLE_ID2 = "vela-axis-scale-buttons";
|
|
14220
|
+
var BTN_PX = 18;
|
|
14221
|
+
var BTN_GAP = 4;
|
|
14222
|
+
var AXIS_BORDER = 1;
|
|
14223
|
+
var CLUSTER_W = AXIS_MASTER_W - AXIS_BORDER;
|
|
14224
|
+
var PAD_TOP = 8;
|
|
14225
|
+
var PAD_BOTTOM = 6;
|
|
14226
|
+
var CLUSTER_H = BTN_PX + PAD_TOP + PAD_BOTTOM;
|
|
14227
|
+
var MIN_PANE_H = 48;
|
|
14228
|
+
function ensureStyles2() {
|
|
14229
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID2)) return;
|
|
14230
|
+
const st = document.createElement("style");
|
|
14231
|
+
st.id = STYLE_ID2;
|
|
14232
|
+
st.textContent = `
|
|
14233
|
+
.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);}
|
|
14234
|
+
.vela-axis-btn:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
14235
|
+
.vela-axis-on,.vela-axis-on:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
14236
|
+
`;
|
|
14237
|
+
document.head.appendChild(st);
|
|
14238
|
+
}
|
|
14239
|
+
var AxisScaleButtons = class {
|
|
14240
|
+
constructor(plot, theme, deps) {
|
|
14241
|
+
this.plot = plot;
|
|
14242
|
+
this.theme = theme;
|
|
14243
|
+
this.deps = deps;
|
|
14244
|
+
this.tipDisposers = [];
|
|
14245
|
+
this.hoverPaneId = null;
|
|
14246
|
+
/** Reveal over the master scale column of the pane under the cursor (mouse only —
|
|
14247
|
+
* touch has no hover, and a drag-rescale on the axis must not sprout buttons). */
|
|
14248
|
+
this.onPlotMove = (e) => {
|
|
14249
|
+
if (e.pointerType !== "mouse") return;
|
|
14250
|
+
const rect = this.plot.getBoundingClientRect();
|
|
14251
|
+
const x = e.clientX - rect.left;
|
|
14252
|
+
const gutterLeft = rect.width - this.deps.rightAxis();
|
|
14253
|
+
let hit = null;
|
|
14254
|
+
if (x >= gutterLeft && x <= gutterLeft + AXIS_MASTER_W) {
|
|
14255
|
+
const y = e.clientY - rect.top;
|
|
14256
|
+
for (const p of this.deps.panes()) {
|
|
14257
|
+
if (p.collapsed || p.height < MIN_PANE_H) continue;
|
|
14258
|
+
if (y >= p.top && y <= p.top + p.height) {
|
|
14259
|
+
hit = p.id;
|
|
14260
|
+
break;
|
|
14261
|
+
}
|
|
14262
|
+
}
|
|
14263
|
+
}
|
|
14264
|
+
this.setHoverPane(hit);
|
|
14265
|
+
if (hit) this.sync();
|
|
14266
|
+
};
|
|
14267
|
+
this.onPlotLeave = () => this.setHoverPane(null);
|
|
14268
|
+
ensureStyles2();
|
|
14269
|
+
this.cluster = document.createElement("div");
|
|
14270
|
+
Object.assign(this.cluster.style, {
|
|
14271
|
+
position: "absolute",
|
|
14272
|
+
display: "none",
|
|
14273
|
+
justifyContent: "center",
|
|
14274
|
+
alignItems: "center",
|
|
14275
|
+
gap: `${BTN_GAP}px`,
|
|
14276
|
+
padding: `${PAD_TOP}px 0 ${PAD_BOTTOM}px`,
|
|
14277
|
+
width: `${CLUSTER_W}px`,
|
|
14278
|
+
boxSizing: "border-box",
|
|
14279
|
+
zIndex: "6",
|
|
14280
|
+
pointerEvents: "auto"
|
|
14281
|
+
});
|
|
14282
|
+
this.applyTheme();
|
|
14283
|
+
this.autoBtn = this.button("A", "Auto (fits data to screen)", () => this.deps.onToggleAuto(this.hoverPaneId ?? ""));
|
|
14284
|
+
this.logBtn = this.button("L", "Logarithmic scale", () => this.deps.onToggleLog(this.hoverPaneId ?? ""));
|
|
14285
|
+
this.cluster.append(this.autoBtn, this.logBtn);
|
|
14286
|
+
this.plot.appendChild(this.cluster);
|
|
14287
|
+
this.plot.addEventListener("pointermove", this.onPlotMove);
|
|
14288
|
+
this.plot.addEventListener("pointerleave", this.onPlotLeave);
|
|
14289
|
+
}
|
|
14290
|
+
button(label, title, onClick) {
|
|
14291
|
+
const b = document.createElement("button");
|
|
14292
|
+
b.type = "button";
|
|
14293
|
+
b.setAttribute("aria-label", title);
|
|
14294
|
+
b.textContent = label;
|
|
14295
|
+
b.className = "vela-axis-btn";
|
|
14296
|
+
Object.assign(b.style, { width: `${BTN_PX}px`, height: `${BTN_PX}px` });
|
|
14297
|
+
this.tipDisposers.push(attachChromeTooltip(b, {
|
|
14298
|
+
host: this.plot,
|
|
14299
|
+
theme: () => this.theme,
|
|
14300
|
+
text: () => title,
|
|
14301
|
+
placement: "above"
|
|
14302
|
+
}));
|
|
14303
|
+
b.addEventListener("click", (e) => {
|
|
14304
|
+
e.stopPropagation();
|
|
14305
|
+
onClick();
|
|
14306
|
+
this.sync();
|
|
14307
|
+
});
|
|
14308
|
+
return b;
|
|
14309
|
+
}
|
|
14310
|
+
/** Re-tint the chip when the plot background/border change (theme swap, config edit). */
|
|
14311
|
+
setTheme(theme) {
|
|
14312
|
+
this.theme = theme;
|
|
14313
|
+
this.applyTheme();
|
|
14314
|
+
}
|
|
14315
|
+
/** The strip is SOLID chart background: the buttons cover axis labels, never blend with them. */
|
|
14316
|
+
applyTheme() {
|
|
14317
|
+
this.cluster.style.background = this.theme.background;
|
|
14318
|
+
}
|
|
14319
|
+
setHoverPane(paneId) {
|
|
14320
|
+
if (this.hoverPaneId === paneId) return;
|
|
14321
|
+
this.hoverPaneId = paneId;
|
|
14322
|
+
this.reposition();
|
|
14323
|
+
}
|
|
14324
|
+
/** Re-place (or hide) the cluster after a hover or pane-layout change. */
|
|
14325
|
+
reposition() {
|
|
14326
|
+
const pane = this.hoverPaneId ? this.deps.panes().find((p) => p.id === this.hoverPaneId) : void 0;
|
|
14327
|
+
if (!pane || pane.collapsed || pane.height < MIN_PANE_H) {
|
|
14328
|
+
this.cluster.style.display = "none";
|
|
14329
|
+
return;
|
|
14330
|
+
}
|
|
14331
|
+
this.cluster.style.right = `${this.deps.rightAxis() - AXIS_MASTER_W}px`;
|
|
14332
|
+
this.cluster.style.top = `${pane.top + pane.height - CLUSTER_H}px`;
|
|
14333
|
+
this.cluster.style.display = "flex";
|
|
14334
|
+
this.sync();
|
|
14335
|
+
}
|
|
14336
|
+
/** Reflect the hovered pane's current auto/log state on the buttons. */
|
|
14337
|
+
sync() {
|
|
14338
|
+
const pane = this.deps.panes().find((p) => p.id === this.hoverPaneId);
|
|
14339
|
+
if (!pane) return;
|
|
14340
|
+
this.autoBtn.classList.toggle("vela-axis-on", pane.auto);
|
|
14341
|
+
this.logBtn.classList.toggle("vela-axis-on", pane.log);
|
|
14342
|
+
}
|
|
14343
|
+
destroy() {
|
|
14344
|
+
this.plot.removeEventListener("pointermove", this.onPlotMove);
|
|
14345
|
+
this.plot.removeEventListener("pointerleave", this.onPlotLeave);
|
|
14346
|
+
for (const dispose of this.tipDisposers) dispose();
|
|
14347
|
+
this.tipDisposers.length = 0;
|
|
14348
|
+
this.cluster.remove();
|
|
14349
|
+
}
|
|
14350
|
+
};
|
|
14351
|
+
|
|
14110
14352
|
// src/renderers/shared/TableOverlay.ts
|
|
14111
14353
|
var SIZE_PX3 = {
|
|
14112
14354
|
auto: 13,
|
|
@@ -14464,6 +14706,8 @@ var BASELINE_BOTTOM_LINE = BEARISH;
|
|
|
14464
14706
|
var BASELINE_FILL_ALPHA = 0.25;
|
|
14465
14707
|
var BASELINE_FILL_ALPHA_FAR = 0.05;
|
|
14466
14708
|
var BASELINE_LEVEL_DEFAULT = 50;
|
|
14709
|
+
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
14710
|
+
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
14467
14711
|
function defaultChartStyle() {
|
|
14468
14712
|
return {
|
|
14469
14713
|
chartTypes: {},
|
|
@@ -14494,7 +14738,8 @@ function defaultChartStyle() {
|
|
|
14494
14738
|
bottomFillColor2: null,
|
|
14495
14739
|
width: 2,
|
|
14496
14740
|
baselineLevel: BASELINE_LEVEL_DEFAULT
|
|
14497
|
-
}
|
|
14741
|
+
},
|
|
14742
|
+
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
|
|
14498
14743
|
};
|
|
14499
14744
|
}
|
|
14500
14745
|
var LINE_STYLES = ["solid", "dashed", "dotted"];
|
|
@@ -14628,6 +14873,7 @@ function mergeConfig(base, patch) {
|
|
|
14628
14873
|
const area = asObject(p.area);
|
|
14629
14874
|
const baseline = asObject(p.baseline);
|
|
14630
14875
|
const series = asObject(p.series);
|
|
14876
|
+
const sessions = asObject(p.sessions);
|
|
14631
14877
|
const stacking = asObject(p.stacking);
|
|
14632
14878
|
const stackSeries = asObject(stacking.series);
|
|
14633
14879
|
return {
|
|
@@ -14720,6 +14966,10 @@ function mergeConfig(base, patch) {
|
|
|
14720
14966
|
baseline: series.baseline === null ? null : isNum(series.baseline) ? series.baseline : base.series.baseline,
|
|
14721
14967
|
spacing: isNum(series.spacing) ? clampSpacing(series.spacing) : base.series.spacing
|
|
14722
14968
|
},
|
|
14969
|
+
sessions: {
|
|
14970
|
+
premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
|
|
14971
|
+
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
|
|
14972
|
+
},
|
|
14723
14973
|
stacking: {
|
|
14724
14974
|
candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
|
|
14725
14975
|
// Additive like `chartTypes`: a patch names only the ids it carries; the rest keep
|
|
@@ -14966,6 +15216,8 @@ var SceneGraph = class {
|
|
|
14966
15216
|
this.tradeMarkers = defaultTradeMarkersState();
|
|
14967
15217
|
/** Renderer-owned shaded time bands (session highlighting), behind grid + data. */
|
|
14968
15218
|
this.highlights = [];
|
|
15219
|
+
/** Pre/post-market bands pushed by the host (`sessionZones` feature); null ⇒ no sessions. */
|
|
15220
|
+
this.sessionZones = null;
|
|
14969
15221
|
/** Draw-order key of the price candles, relative to indicator series z (see `seriesZ`).
|
|
14970
15222
|
* Indicators with z below this draw BEHIND the candles; at/above draw in front.
|
|
14971
15223
|
* Default 0 with indicators mounting at z < 0 ⇒ the price reads on top of every overlay,
|
|
@@ -15000,6 +15252,15 @@ var SceneGraph = class {
|
|
|
15000
15252
|
if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
|
|
15001
15253
|
return this.orderedCache;
|
|
15002
15254
|
}
|
|
15255
|
+
/** The session zones resolved into colored bands (pre/post-market washes from the
|
|
15256
|
+
* config's session colors) — consumed by the same painting path as {@link highlights}. */
|
|
15257
|
+
sessionHighlightBands() {
|
|
15258
|
+
if (!this.sessionZones) return [];
|
|
15259
|
+
const out = [];
|
|
15260
|
+
for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
|
|
15261
|
+
for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
|
|
15262
|
+
return out;
|
|
15263
|
+
}
|
|
15003
15264
|
indicatorsForPane(paneId) {
|
|
15004
15265
|
const out = [];
|
|
15005
15266
|
for (const model of this.indicators.values()) if (model.paneId === paneId) out.push(model);
|
|
@@ -15083,6 +15344,12 @@ var SceneGraph = class {
|
|
|
15083
15344
|
assignIndicatorZ(id) {
|
|
15084
15345
|
if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.bottomZ() - 1);
|
|
15085
15346
|
}
|
|
15347
|
+
/** Mount-time default for a LAYER-BACKED native (it paints on a canvas stacked above the
|
|
15348
|
+
* data canvas by default): top of the stack, so the recorded order tells the truth from
|
|
15349
|
+
* the first frame. Keeps an existing key, so a restored stack survives the remount. */
|
|
15350
|
+
assignIndicatorZTop(id) {
|
|
15351
|
+
if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.topZ() + 1);
|
|
15352
|
+
}
|
|
15086
15353
|
forgetIndicatorZ(id) {
|
|
15087
15354
|
this.seriesZ.delete(id);
|
|
15088
15355
|
}
|
|
@@ -15930,10 +16197,12 @@ var WebGL2Backend = class {
|
|
|
15930
16197
|
}
|
|
15931
16198
|
}
|
|
15932
16199
|
}
|
|
15933
|
-
/** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
|
|
16200
|
+
/** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
|
|
16201
|
+
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
15934
16202
|
emitHighlights(b, scene, pane, coords) {
|
|
15935
|
-
|
|
15936
|
-
|
|
16203
|
+
const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
|
|
16204
|
+
if (bands.length === 0) return;
|
|
16205
|
+
for (const band of bands) {
|
|
15937
16206
|
const x1 = coords.timeToX(band.from);
|
|
15938
16207
|
const x2 = coords.timeToX(band.to);
|
|
15939
16208
|
if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
|
|
@@ -17907,10 +18176,12 @@ var Canvas2dBackend = class {
|
|
|
17907
18176
|
ctx.fillStyle = bg.color;
|
|
17908
18177
|
ctx.fillRect(x1, pane.bounds.top, x2 - x1, pane.bounds.height);
|
|
17909
18178
|
}
|
|
17910
|
-
/** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
|
|
18179
|
+
/** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
|
|
18180
|
+
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
17911
18181
|
drawHighlights(ctx, scene, coords) {
|
|
17912
|
-
|
|
17913
|
-
|
|
18182
|
+
const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
|
|
18183
|
+
if (bands.length === 0) return;
|
|
18184
|
+
for (const band of bands) {
|
|
17914
18185
|
const x1 = coords.timeToX(band.from);
|
|
17915
18186
|
const x2 = coords.timeToX(band.to);
|
|
17916
18187
|
if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
|
|
@@ -18684,14 +18955,6 @@ var DrawingSceneRenderer = class {
|
|
|
18684
18955
|
}
|
|
18685
18956
|
};
|
|
18686
18957
|
|
|
18687
|
-
// src/renderers/native/chrome/axisLayout.ts
|
|
18688
|
-
var AXIS_MASTER_W = 64;
|
|
18689
|
-
var AXIS_MERGED_W = 56;
|
|
18690
|
-
var PANE_SEPARATOR_PX = 3;
|
|
18691
|
-
function axisColumnX(dataW, column) {
|
|
18692
|
-
return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
|
|
18693
|
-
}
|
|
18694
|
-
|
|
18695
18958
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
18696
18959
|
var ChromeRenderer = class {
|
|
18697
18960
|
constructor() {
|
|
@@ -19574,6 +19837,7 @@ var SettingsDialog = class {
|
|
|
19574
19837
|
for (const hr of hs.rows) {
|
|
19575
19838
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
19576
19839
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
19840
|
+
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
19577
19841
|
else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options.map((o) => [o, o]), (v) => hr.set(v)));
|
|
19578
19842
|
}
|
|
19579
19843
|
}
|
|
@@ -22298,14 +22562,14 @@ function btn(label, primary, onClick) {
|
|
|
22298
22562
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
22299
22563
|
var BTN = 30;
|
|
22300
22564
|
var ICON = 21;
|
|
22301
|
-
var
|
|
22565
|
+
var STYLE_ID3 = "vela-drawing-popup-styles";
|
|
22302
22566
|
var STYLE_REV = "3";
|
|
22303
|
-
function
|
|
22567
|
+
function ensureStyles3() {
|
|
22304
22568
|
if (typeof document === "undefined") return;
|
|
22305
|
-
const existing = document.getElementById(
|
|
22569
|
+
const existing = document.getElementById(STYLE_ID3);
|
|
22306
22570
|
if (existing?.dataset.rev === STYLE_REV) return;
|
|
22307
22571
|
const s = existing ?? document.createElement("style");
|
|
22308
|
-
s.id =
|
|
22572
|
+
s.id = STYLE_ID3;
|
|
22309
22573
|
s.dataset.rev = STYLE_REV;
|
|
22310
22574
|
s.textContent = `
|
|
22311
22575
|
.vela-dpop-btn{background:transparent;color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
@@ -22362,7 +22626,7 @@ var DrawingSettingsPopup = class {
|
|
|
22362
22626
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
22363
22627
|
open(drawing, anchor, actions, onClose) {
|
|
22364
22628
|
this.close();
|
|
22365
|
-
|
|
22629
|
+
ensureStyles3();
|
|
22366
22630
|
this.onClose = onClose ?? null;
|
|
22367
22631
|
const t = this.theme;
|
|
22368
22632
|
const schema = drawing.schema();
|
|
@@ -23090,7 +23354,7 @@ var DrawingToolbar = class {
|
|
|
23090
23354
|
this.onCollapse = options.onCollapse ?? (() => {
|
|
23091
23355
|
});
|
|
23092
23356
|
this.onDrawingsSync = options.onDrawingsSync ?? null;
|
|
23093
|
-
|
|
23357
|
+
ensureStyles4();
|
|
23094
23358
|
this.root = document.createElement("div");
|
|
23095
23359
|
this.root.className = "vela-dtb";
|
|
23096
23360
|
this.styleRoot();
|
|
@@ -23573,13 +23837,13 @@ var DrawingToolbar = class {
|
|
|
23573
23837
|
}
|
|
23574
23838
|
};
|
|
23575
23839
|
var MAGNET_ID = "__magnet__";
|
|
23576
|
-
var
|
|
23577
|
-
function
|
|
23840
|
+
var STYLE_ID4 = "vela-drawing-toolbar-styles";
|
|
23841
|
+
function ensureStyles4() {
|
|
23578
23842
|
if (typeof document === "undefined") return;
|
|
23579
|
-
let s = document.getElementById(
|
|
23843
|
+
let s = document.getElementById(STYLE_ID4);
|
|
23580
23844
|
if (!s) {
|
|
23581
23845
|
s = document.createElement("style");
|
|
23582
|
-
s.id =
|
|
23846
|
+
s.id = STYLE_ID4;
|
|
23583
23847
|
document.head.appendChild(s);
|
|
23584
23848
|
}
|
|
23585
23849
|
s.textContent = `
|
|
@@ -24844,13 +25108,26 @@ function rendererLayers() {
|
|
|
24844
25108
|
return [...registry3.values()];
|
|
24845
25109
|
}
|
|
24846
25110
|
|
|
25111
|
+
// src/renderers/native/core/layerStacking.ts
|
|
25112
|
+
function stackLayers(entries, candleZ) {
|
|
25113
|
+
const keyed = entries.map((e) => ({
|
|
25114
|
+
id: e.id,
|
|
25115
|
+
key: e.ownerZ ?? (e.placement === "below-data" ? -Infinity : candleZ)
|
|
25116
|
+
}));
|
|
25117
|
+
keyed.sort((a, b) => a.key - b.key);
|
|
25118
|
+
const below = [];
|
|
25119
|
+
const above = [];
|
|
25120
|
+
for (const k of keyed) (k.key < candleZ ? below : above).push(k.id);
|
|
25121
|
+
return { below, above };
|
|
25122
|
+
}
|
|
25123
|
+
|
|
24847
25124
|
// src/renderers/native/chrome/luxalgo-logos.ts
|
|
24848
25125
|
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>';
|
|
24849
25126
|
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>';
|
|
24850
25127
|
|
|
24851
25128
|
// src/renderers/native/chrome/AttributionMark.ts
|
|
24852
25129
|
var ATTRIBUTION_URL = "https://luxalgo.com/vela";
|
|
24853
|
-
var
|
|
25130
|
+
var STYLE_ID5 = "vela-attribution-styles";
|
|
24854
25131
|
var CSS2 = `
|
|
24855
25132
|
.vela-attribution {
|
|
24856
25133
|
text-decoration: none;
|
|
@@ -24913,11 +25190,11 @@ var CSS2 = `
|
|
|
24913
25190
|
[data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
|
|
24914
25191
|
[data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
|
|
24915
25192
|
`;
|
|
24916
|
-
function
|
|
24917
|
-
let s = doc.getElementById(
|
|
25193
|
+
function ensureStyles5(doc) {
|
|
25194
|
+
let s = doc.getElementById(STYLE_ID5);
|
|
24918
25195
|
if (!s) {
|
|
24919
25196
|
s = doc.createElement("style");
|
|
24920
|
-
s.id =
|
|
25197
|
+
s.id = STYLE_ID5;
|
|
24921
25198
|
doc.head.appendChild(s);
|
|
24922
25199
|
}
|
|
24923
25200
|
s.textContent = CSS2;
|
|
@@ -24946,7 +25223,7 @@ function createCustomMark(doc, html, background) {
|
|
|
24946
25223
|
return el;
|
|
24947
25224
|
}
|
|
24948
25225
|
function createAttributionMark(doc, background) {
|
|
24949
|
-
|
|
25226
|
+
ensureStyles5(doc);
|
|
24950
25227
|
const a = doc.createElement("a");
|
|
24951
25228
|
a.className = "vela-attribution";
|
|
24952
25229
|
a.href = ATTRIBUTION_URL;
|
|
@@ -25344,6 +25621,8 @@ var NativeRenderer = class {
|
|
|
25344
25621
|
this.volumeRenderer = new VolumeRenderer();
|
|
25345
25622
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
25346
25623
|
this.extLayers = [];
|
|
25624
|
+
/** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
|
|
25625
|
+
this.layerOrderSig = "";
|
|
25347
25626
|
// The attribution mark (see chrome/AttributionMark + the NOTICE file): default-on;
|
|
25348
25627
|
// disabling requires an equivalent visible attribution elsewhere in the host UI.
|
|
25349
25628
|
this.attributionEl = null;
|
|
@@ -25458,6 +25737,8 @@ var NativeRenderer = class {
|
|
|
25458
25737
|
// total right-gutter width (master column + merged-scale columns)
|
|
25459
25738
|
this.paneControls = null;
|
|
25460
25739
|
// per-pane hover button cluster (top-right)
|
|
25740
|
+
this.axisScaleButtons = null;
|
|
25741
|
+
// A/L hover buttons at the bottom of each pane's price scale
|
|
25461
25742
|
this.paneActionCbs = /* @__PURE__ */ new Set();
|
|
25462
25743
|
// ── data window (crosshair OHLC + per-series readout, pulled by host panels) ──
|
|
25463
25744
|
this.hoverLogical = null;
|
|
@@ -25501,7 +25782,7 @@ var NativeRenderer = class {
|
|
|
25501
25782
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
25502
25783
|
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
25503
25784
|
this.name = "native";
|
|
25504
|
-
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"];
|
|
25785
|
+
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"];
|
|
25505
25786
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
25506
25787
|
* so moving onto the button doesn't count as leaving). */
|
|
25507
25788
|
this.onScrollProximityMove = (e) => {
|
|
@@ -25617,6 +25898,9 @@ var NativeRenderer = class {
|
|
|
25617
25898
|
case "highlights":
|
|
25618
25899
|
this.scene.highlights = sanitizeHighlights(value);
|
|
25619
25900
|
break;
|
|
25901
|
+
case "sessionZones":
|
|
25902
|
+
this.scene.sessionZones = sanitizeSessionZones(value);
|
|
25903
|
+
break;
|
|
25620
25904
|
case "gridlines":
|
|
25621
25905
|
this.scene.showGrid = Boolean(value);
|
|
25622
25906
|
break;
|
|
@@ -25733,6 +26017,8 @@ var NativeRenderer = class {
|
|
|
25733
26017
|
return this.scene.indicatorZOrder();
|
|
25734
26018
|
case "highlights":
|
|
25735
26019
|
return this.scene.highlights;
|
|
26020
|
+
case "sessionZones":
|
|
26021
|
+
return this.scene.sessionZones;
|
|
25736
26022
|
case "gridlines":
|
|
25737
26023
|
return this.scene.showGrid;
|
|
25738
26024
|
case "axisLabels":
|
|
@@ -25942,7 +26228,8 @@ var NativeRenderer = class {
|
|
|
25942
26228
|
baselineLevel: s.baseline.baselineLevel
|
|
25943
26229
|
};
|
|
25944
26230
|
})(),
|
|
25945
|
-
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale }
|
|
26231
|
+
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
|
|
26232
|
+
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
|
|
25946
26233
|
};
|
|
25947
26234
|
}
|
|
25948
26235
|
/**
|
|
@@ -26024,6 +26311,7 @@ var NativeRenderer = class {
|
|
|
26024
26311
|
width: next.baseline.width,
|
|
26025
26312
|
baselineLevel: next.baseline.baselineLevel
|
|
26026
26313
|
};
|
|
26314
|
+
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
|
|
26027
26315
|
this.setPriceStyle(next.series.style);
|
|
26028
26316
|
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
26029
26317
|
this.scene.baselineValue = next.series.baseline;
|
|
@@ -26037,6 +26325,7 @@ var NativeRenderer = class {
|
|
|
26037
26325
|
}
|
|
26038
26326
|
this.inputsUI?.setTheme(this.theme);
|
|
26039
26327
|
this.paneControls?.setTheme(this.theme);
|
|
26328
|
+
this.axisScaleButtons?.setTheme(this.theme);
|
|
26040
26329
|
this.userDrawings?.setTheme(this.chromeTheme());
|
|
26041
26330
|
this.refreshScrollButtonTheme();
|
|
26042
26331
|
this.refreshAttributionColor();
|
|
@@ -26264,9 +26553,7 @@ var NativeRenderer = class {
|
|
|
26264
26553
|
if (el.getAttribute("data-vela-screenshot") === "under") rasterizeOverlay(ctx, el, frame);
|
|
26265
26554
|
}
|
|
26266
26555
|
}
|
|
26267
|
-
const
|
|
26268
|
-
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
26269
|
-
for (const canvas of [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas]) {
|
|
26556
|
+
for (const canvas of [...this.canvasPile(), this.chromeCanvas, this.drawingsCanvas]) {
|
|
26270
26557
|
if (canvas && canvas.width > 0 && canvas.height > 0) ctx.drawImage(canvas, 0, 0);
|
|
26271
26558
|
}
|
|
26272
26559
|
if (frame) {
|
|
@@ -26375,7 +26662,7 @@ var NativeRenderer = class {
|
|
|
26375
26662
|
this.surfaceBackground = theme.background;
|
|
26376
26663
|
this.surfaceTextColor = theme.textColor;
|
|
26377
26664
|
this.wrapper = document.createElement("div");
|
|
26378
|
-
Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair" });
|
|
26665
|
+
Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair", userSelect: "none", webkitUserSelect: "none" });
|
|
26379
26666
|
applyChromeTokens(this.wrapper, this.chromeTheme());
|
|
26380
26667
|
this.volumeCanvas = document.createElement("canvas");
|
|
26381
26668
|
Object.assign(this.volumeCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
@@ -26400,6 +26687,7 @@ var NativeRenderer = class {
|
|
|
26400
26687
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
26401
26688
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
26402
26689
|
this.plot.append(...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
26690
|
+
this.layerOrderSig = "";
|
|
26403
26691
|
this.wrapper.appendChild(this.plot);
|
|
26404
26692
|
this.factoryConfig = this.getConfig();
|
|
26405
26693
|
this.attributionEl = this.buildAttributionEl();
|
|
@@ -26544,6 +26832,15 @@ var NativeRenderer = class {
|
|
|
26544
26832
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
26545
26833
|
}
|
|
26546
26834
|
});
|
|
26835
|
+
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
26836
|
+
panes: () => this.axisScaleViews(),
|
|
26837
|
+
rightAxis: () => this.rightAxisW,
|
|
26838
|
+
onToggleAuto: (paneId) => this.togglePaneAuto(paneId),
|
|
26839
|
+
onToggleLog: (paneId) => {
|
|
26840
|
+
const pane = this.scene.panes.get(paneId);
|
|
26841
|
+
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
26842
|
+
}
|
|
26843
|
+
});
|
|
26547
26844
|
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
26548
26845
|
this.resizeObserver.observe(this.wrapper);
|
|
26549
26846
|
this.watchDpr();
|
|
@@ -26620,6 +26917,7 @@ var NativeRenderer = class {
|
|
|
26620
26917
|
this.applyBackground();
|
|
26621
26918
|
this.inputsUI.setTheme(theme);
|
|
26622
26919
|
this.paneControls?.setTheme(this.theme);
|
|
26920
|
+
this.axisScaleButtons?.setTheme(this.theme);
|
|
26623
26921
|
this.settingsDialog?.refreshConfig(this.getConfig());
|
|
26624
26922
|
this.syncThemeControl();
|
|
26625
26923
|
this.settingsDialog?.setTheme(this.theme);
|
|
@@ -26668,6 +26966,7 @@ var NativeRenderer = class {
|
|
|
26668
26966
|
this.liveRegion = null;
|
|
26669
26967
|
this.inputsUI?.destroy();
|
|
26670
26968
|
this.paneControls?.destroy();
|
|
26969
|
+
this.axisScaleButtons?.destroy();
|
|
26671
26970
|
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
26672
26971
|
this.tableOverlays.clear();
|
|
26673
26972
|
this.resizeObserver?.disconnect();
|
|
@@ -26917,7 +27216,8 @@ var NativeRenderer = class {
|
|
|
26917
27216
|
mountIndicator(model) {
|
|
26918
27217
|
this.scene.indicators.set(model.id, model);
|
|
26919
27218
|
this.refreshAnchorOffset(model);
|
|
26920
|
-
this.scene.
|
|
27219
|
+
if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
|
|
27220
|
+
else this.scene.assignIndicatorZ(model.id);
|
|
26921
27221
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
26922
27222
|
native: !!model.native,
|
|
26923
27223
|
...model.shorttitle ? { settingsTitle: model.title } : {}
|
|
@@ -27736,10 +28036,17 @@ var NativeRenderer = class {
|
|
|
27736
28036
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
27737
28037
|
for (const l of this.extLayers) {
|
|
27738
28038
|
if (!l.def.repaintOnCursor) continue;
|
|
27739
|
-
|
|
28039
|
+
const lp = this.layerPane(l.def.id) ?? pane;
|
|
28040
|
+
if (lp.collapsed) continue;
|
|
28041
|
+
l.instance.render(this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs));
|
|
27740
28042
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
27741
28043
|
}
|
|
27742
28044
|
}
|
|
28045
|
+
/** Blank one SDK layer canvas (a collapsed host pane suppresses the layer's painting). */
|
|
28046
|
+
clearLayerCanvas(canvas) {
|
|
28047
|
+
if (canvas.width === 0 || canvas.height === 0) return;
|
|
28048
|
+
canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
|
|
28049
|
+
}
|
|
27743
28050
|
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths). */
|
|
27744
28051
|
extLayerArgs(id, scale, bounds, nowMs) {
|
|
27745
28052
|
return {
|
|
@@ -27758,6 +28065,7 @@ var NativeRenderer = class {
|
|
|
27758
28065
|
}
|
|
27759
28066
|
/** Paint the below-data (L-1) + geometry (L0) + chrome (L1) layers from the current scene/coords. */
|
|
27760
28067
|
paintData() {
|
|
28068
|
+
this.syncLayerCanvasOrder();
|
|
27761
28069
|
this.stampScaleInvert();
|
|
27762
28070
|
this.backend.modelAlpha = this.modelAlpha;
|
|
27763
28071
|
this.backend.candleBodyAlpha = this.candleBodyAlpha;
|
|
@@ -27788,9 +28096,14 @@ var NativeRenderer = class {
|
|
|
27788
28096
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
27789
28097
|
let folded = null;
|
|
27790
28098
|
for (const l of this.extLayers) {
|
|
27791
|
-
const
|
|
28099
|
+
const lp = this.layerPane(l.def.id) ?? pane;
|
|
28100
|
+
if (lp.collapsed) {
|
|
28101
|
+
this.clearLayerCanvas(l.canvas);
|
|
28102
|
+
continue;
|
|
28103
|
+
}
|
|
28104
|
+
const args = this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs);
|
|
27792
28105
|
l.instance.render(args);
|
|
27793
|
-
folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
28106
|
+
if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
27794
28107
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
27795
28108
|
}
|
|
27796
28109
|
if (folded) {
|
|
@@ -27952,6 +28265,9 @@ var NativeRenderer = class {
|
|
|
27952
28265
|
pane.scaleTarget = { min: 0, max: maxVol / VOLUME_PANE_FILL_FRAC };
|
|
27953
28266
|
pane.axisFormat = "volume";
|
|
27954
28267
|
}
|
|
28268
|
+
} else if (this.layerNativesOwnPane(pane, masterModels)) {
|
|
28269
|
+
pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
28270
|
+
pane.percentBaseline = this.bars[i0]?.close ?? 0;
|
|
27955
28271
|
}
|
|
27956
28272
|
if (pane === pricePane && this.scene.tradeMarkers.visible && pane.bounds.height > 0) {
|
|
27957
28273
|
const th = this.tradesScaleHints(vr);
|
|
@@ -28038,6 +28354,59 @@ var NativeRenderer = class {
|
|
|
28038
28354
|
}
|
|
28039
28355
|
return null;
|
|
28040
28356
|
}
|
|
28357
|
+
/** The mounted native indicator that OWNS an SDK layer — the one whose type equals the
|
|
28358
|
+
* layer id (the id doubles as the data channel, so the pairing is the SDK's own
|
|
28359
|
+
* contract). Null for chart-type channels and while the owner is hidden (a hidden
|
|
28360
|
+
* indicator leaves the scene; its cleared data channel paints nothing anyway). */
|
|
28361
|
+
layerOwner(layerId) {
|
|
28362
|
+
for (const m of this.scene.indicators.values()) {
|
|
28363
|
+
if (m.native?.type === layerId) return m;
|
|
28364
|
+
}
|
|
28365
|
+
return null;
|
|
28366
|
+
}
|
|
28367
|
+
/** The pane an SDK layer paints on: its owner's pane, else the price pane. */
|
|
28368
|
+
layerPane(layerId) {
|
|
28369
|
+
const owner = this.layerOwner(layerId);
|
|
28370
|
+
const paneId = owner ? owner.paneId ?? PRICE_PANE_ID : PRICE_PANE_ID;
|
|
28371
|
+
return this.scene.panes.get(paneId) ?? null;
|
|
28372
|
+
}
|
|
28373
|
+
/** The SDK layer canvases split around the data canvas, each side back-to-front:
|
|
28374
|
+
* owned layers by their owner's z key against the candles' (an indicator restacked
|
|
28375
|
+
* below the candles takes its layer canvas along), unowned by declared placement. */
|
|
28376
|
+
orderedLayerCanvases() {
|
|
28377
|
+
const byId = new Map(this.extLayers.map((l) => [l.def.id, l.canvas]));
|
|
28378
|
+
const { below, above } = stackLayers(
|
|
28379
|
+
this.extLayers.map((l) => {
|
|
28380
|
+
const owner = this.layerOwner(l.def.id);
|
|
28381
|
+
return {
|
|
28382
|
+
id: l.def.id,
|
|
28383
|
+
placement: l.def.placement === "below-data" ? "below-data" : "above-data",
|
|
28384
|
+
ownerZ: owner ? this.scene.zOf(owner.id) : null
|
|
28385
|
+
};
|
|
28386
|
+
}),
|
|
28387
|
+
this.scene.candleZ
|
|
28388
|
+
);
|
|
28389
|
+
return { below: below.map((id) => byId.get(id)), above: above.map((id) => byId.get(id)) };
|
|
28390
|
+
}
|
|
28391
|
+
/** The full canvas pile in paint order (layers + data/volume/vpvr) — what the DOM
|
|
28392
|
+
* stacking and the screenshot compositor must both follow. */
|
|
28393
|
+
canvasPile() {
|
|
28394
|
+
const { below, above } = this.orderedLayerCanvases();
|
|
28395
|
+
return [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above];
|
|
28396
|
+
}
|
|
28397
|
+
/** Re-slot the SDK layer canvases in the plot when the computed order changed (a z
|
|
28398
|
+
* write, a restored config, an indicator mount/remove/restack). Runs at the top of
|
|
28399
|
+
* every data frame; a no-op when the signature is unchanged. Re-inserting an
|
|
28400
|
+
* absolutely-positioned, pointer-transparent canvas repaints nothing by itself. */
|
|
28401
|
+
syncLayerCanvasOrder() {
|
|
28402
|
+
if (this.extLayers.length === 0 || !this.plot) return;
|
|
28403
|
+
const { below, above } = this.orderedLayerCanvases();
|
|
28404
|
+
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(",");
|
|
28405
|
+
if (sig === this.layerOrderSig) return;
|
|
28406
|
+
this.layerOrderSig = sig;
|
|
28407
|
+
for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
|
|
28408
|
+
for (const c of above) this.plot.insertBefore(c, this.chromeCanvas);
|
|
28409
|
+
}
|
|
28041
28410
|
/** True when an active volume layer is this study pane's ONLY content — so its scale should
|
|
28042
28411
|
* come from volume, not the empty {0,1} placeholder. (In the price pane, or alongside a real
|
|
28043
28412
|
* series, volume stays a bottom overlay and the master scale wins.) */
|
|
@@ -28047,6 +28416,15 @@ var NativeRenderer = class {
|
|
|
28047
28416
|
if (this.nativeLayerPane("volume") !== pane) return false;
|
|
28048
28417
|
return masterModels.every((m) => m.native?.type === "volume");
|
|
28049
28418
|
}
|
|
28419
|
+
/** True when this study pane's master content is only SDK-layer natives (series-less
|
|
28420
|
+
* models whose type names a mounted layer) — its scale then follows the visible bars
|
|
28421
|
+
* (see the call site). Any real master series takes over the scale as usual. */
|
|
28422
|
+
layerNativesOwnPane(pane, masterModels) {
|
|
28423
|
+
if (pane.kind === "price" || masterModels.length === 0) return false;
|
|
28424
|
+
return masterModels.every(
|
|
28425
|
+
(m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type)
|
|
28426
|
+
);
|
|
28427
|
+
}
|
|
28050
28428
|
/** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
|
|
28051
28429
|
* band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
|
|
28052
28430
|
* Top-to-bottom order. Every pane is independent — the price pane from the scene setting,
|
|
@@ -28062,6 +28440,25 @@ var NativeRenderer = class {
|
|
|
28062
28440
|
invert: paneInvert(this.scene, p)
|
|
28063
28441
|
}));
|
|
28064
28442
|
}
|
|
28443
|
+
/** Thin projection of the panes for the axis A/L hover buttons (top-to-bottom order). */
|
|
28444
|
+
axisScaleViews() {
|
|
28445
|
+
return this.scene.orderedPanes().map((p) => ({
|
|
28446
|
+
id: p.id,
|
|
28447
|
+
top: p.bounds.top,
|
|
28448
|
+
height: p.bounds.height,
|
|
28449
|
+
collapsed: p.collapsed,
|
|
28450
|
+
auto: p.manualScale == null,
|
|
28451
|
+
log: paneLogScale(this.scene, p)
|
|
28452
|
+
}));
|
|
28453
|
+
}
|
|
28454
|
+
/** Toggle one pane's autoscale (the A axis button): off freezes the current window into
|
|
28455
|
+
* manual mode, on drops it — the per-pane twin of the chart-level `autoScale` feature. */
|
|
28456
|
+
togglePaneAuto(paneId) {
|
|
28457
|
+
const pane = this.scene.panes.get(paneId);
|
|
28458
|
+
if (!pane) return;
|
|
28459
|
+
pane.manualScale = pane.manualScale == null ? { ...pane.scale } : null;
|
|
28460
|
+
this.scheduler?.invalidate(4 /* Full */);
|
|
28461
|
+
}
|
|
28065
28462
|
/** Set one pane's axis mode. The price pane routes to the scene-level setting (persisted +
|
|
28066
28463
|
* keyboard shortcuts); a study pane keeps its own, so panes never affect each other. */
|
|
28067
28464
|
setPaneScaleMode(paneId, mode) {
|
|
@@ -28073,19 +28470,18 @@ var NativeRenderer = class {
|
|
|
28073
28470
|
}
|
|
28074
28471
|
this.scheduler?.invalidate(4 /* Full */);
|
|
28075
28472
|
}
|
|
28076
|
-
/** Set one pane's logarithmic flag
|
|
28077
|
-
*
|
|
28473
|
+
/** Set one pane's logarithmic flag; the price pane routes to the scene-level flag.
|
|
28474
|
+
* Log and auto are INDEPENDENT: a frozen (manual) window is kept — its price bounds
|
|
28475
|
+
* stay put and only its `log` tag flips, so the same range re-renders in the new
|
|
28476
|
+
* space instead of snapping back to autoscale. */
|
|
28078
28477
|
setPaneLog(paneId, log) {
|
|
28079
|
-
|
|
28080
|
-
|
|
28081
|
-
|
|
28082
|
-
if (
|
|
28083
|
-
|
|
28084
|
-
const p = this.scene.panes.get(paneId);
|
|
28085
|
-
if (!p) return;
|
|
28086
|
-
p.logScale = log;
|
|
28087
|
-
p.manualScale = null;
|
|
28478
|
+
const pane = paneId === PRICE_PANE_ID ? this.scene.orderedPanes().find((p) => p.kind === "price") : this.scene.panes.get(paneId);
|
|
28479
|
+
if (paneId === PRICE_PANE_ID) this.scene.logScale = log;
|
|
28480
|
+
else {
|
|
28481
|
+
if (!pane) return;
|
|
28482
|
+
pane.logScale = log;
|
|
28088
28483
|
}
|
|
28484
|
+
if (pane?.manualScale) pane.manualScale.log = log;
|
|
28089
28485
|
this.scheduler?.invalidate(4 /* Full */);
|
|
28090
28486
|
}
|
|
28091
28487
|
/** Flip one pane's axis (high at the bottom). The price pane routes to the scene-level flag
|
|
@@ -28184,6 +28580,7 @@ var NativeRenderer = class {
|
|
|
28184
28580
|
}
|
|
28185
28581
|
this.inputsUI?.reposition();
|
|
28186
28582
|
this.paneControls?.reposition();
|
|
28583
|
+
this.axisScaleButtons?.reposition();
|
|
28187
28584
|
this.repositionScrollButton();
|
|
28188
28585
|
this.positionAttribution();
|
|
28189
28586
|
this.publishPricePaneBounds();
|
|
@@ -28202,6 +28599,7 @@ var NativeRenderer = class {
|
|
|
28202
28599
|
}
|
|
28203
28600
|
this.inputsUI?.reposition();
|
|
28204
28601
|
this.paneControls?.reposition();
|
|
28602
|
+
this.axisScaleButtons?.reposition();
|
|
28205
28603
|
this.repositionScrollButton();
|
|
28206
28604
|
this.positionAttribution();
|
|
28207
28605
|
this.publishPricePaneBounds();
|
|
@@ -28409,6 +28807,22 @@ function sanitizeHighlights(value) {
|
|
|
28409
28807
|
}
|
|
28410
28808
|
return out.sort((a, b) => a.from - b.from);
|
|
28411
28809
|
}
|
|
28810
|
+
function sanitizeSessionZones(value) {
|
|
28811
|
+
if (value == null || typeof value !== "object") return null;
|
|
28812
|
+
const v = value;
|
|
28813
|
+
const windows = (raw) => {
|
|
28814
|
+
if (!Array.isArray(raw)) return [];
|
|
28815
|
+
const out = [];
|
|
28816
|
+
for (const w of raw) {
|
|
28817
|
+
if (!Array.isArray(w)) continue;
|
|
28818
|
+
const from = Number(w[0]);
|
|
28819
|
+
const to = Number(w[1]);
|
|
28820
|
+
if (Number.isFinite(from) && Number.isFinite(to) && to > from) out.push([from, to]);
|
|
28821
|
+
}
|
|
28822
|
+
return out.sort((a, b) => a[0] - b[0]);
|
|
28823
|
+
};
|
|
28824
|
+
return { pre: windows(v.pre), post: windows(v.post) };
|
|
28825
|
+
}
|
|
28412
28826
|
function clamp012(v) {
|
|
28413
28827
|
return Math.max(0, Math.min(1, v));
|
|
28414
28828
|
}
|
|
@@ -29078,6 +29492,7 @@ exports.drawingTypes = drawingTypes;
|
|
|
29078
29492
|
exports.getDrawingType = getDrawingType;
|
|
29079
29493
|
exports.getNativeIndicator = getNativeIndicator;
|
|
29080
29494
|
exports.iconMarkup = iconMarkup;
|
|
29495
|
+
exports.inputVisible = inputVisible;
|
|
29081
29496
|
exports.legendActions = legendActions;
|
|
29082
29497
|
exports.nativeIndicatorDescriptors = nativeIndicatorDescriptors;
|
|
29083
29498
|
exports.nativeIndicatorTypes = nativeIndicatorTypes;
|