@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/workspace.cjs
CHANGED
|
@@ -960,8 +960,13 @@ var STATIC_CSS = `
|
|
|
960
960
|
${STATIC_DECLS}
|
|
961
961
|
font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
|
|
962
962
|
box-sizing: border-box;
|
|
963
|
+
/* Chrome text (titles, buttons, menus, readouts) is UI, not copy \u2014 never selectable. */
|
|
964
|
+
user-select: none;
|
|
965
|
+
-webkit-user-select: none;
|
|
963
966
|
}
|
|
964
967
|
.vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
|
|
968
|
+
/* Text ENTRY is the one exception: selection is part of editing. */
|
|
969
|
+
.vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
|
|
965
970
|
.vela-icon { display: inline-flex; align-items: center; flex: none; }
|
|
966
971
|
.vela-icon svg { display: block; }
|
|
967
972
|
`;
|
|
@@ -1449,6 +1454,23 @@ var MENU_CSS = `
|
|
|
1449
1454
|
background: var(--vela-hover-strong);
|
|
1450
1455
|
color: var(--vela-fg-bright);
|
|
1451
1456
|
}
|
|
1457
|
+
/* Checkmark mode (context/action menus): every row reserves the leading mark column so
|
|
1458
|
+
labels align; a checked row fills it with a \u2713 and brightens its ink \u2014 the row surface
|
|
1459
|
+
stays free for the hover wash. */
|
|
1460
|
+
.vela-menu-item .vela-menu-mark {
|
|
1461
|
+
width: 14px;
|
|
1462
|
+
flex: none;
|
|
1463
|
+
display: inline-flex;
|
|
1464
|
+
align-items: center;
|
|
1465
|
+
justify-content: center;
|
|
1466
|
+
}
|
|
1467
|
+
.vela-menu-item .vela-menu-mark .vela-icon {
|
|
1468
|
+
width: 14px;
|
|
1469
|
+
height: 14px;
|
|
1470
|
+
font-size: 14px;
|
|
1471
|
+
color: var(--vela-fg-bright);
|
|
1472
|
+
}
|
|
1473
|
+
.vela-menu-item[data-checkmark] { color: var(--vela-fg-bright); }
|
|
1452
1474
|
/* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
|
|
1453
1475
|
same control language as the settings dialog's toggles. */
|
|
1454
1476
|
.vela-menu-switch {
|
|
@@ -1517,6 +1539,7 @@ var Surface = class _Surface {
|
|
|
1517
1539
|
this.host = opts.host;
|
|
1518
1540
|
this.onSelect = opts.onSelect;
|
|
1519
1541
|
this.onFavorite = opts.onFavorite;
|
|
1542
|
+
this.checkmarks = opts.checkmarks === true;
|
|
1520
1543
|
this.positioner = doc.createElement("div");
|
|
1521
1544
|
this.positioner.className = "vela-ui-layer";
|
|
1522
1545
|
this.list = doc.createElement("ul");
|
|
@@ -1595,6 +1618,7 @@ var Surface = class _Surface {
|
|
|
1595
1618
|
build() {
|
|
1596
1619
|
const doc = this.doc;
|
|
1597
1620
|
this.list.replaceChildren();
|
|
1621
|
+
const markable = this.checkmarks && this.items.some((i) => !(i.submenu && i.submenu.length > 0) && !i.toggle && i.checked !== void 0);
|
|
1598
1622
|
for (const item of this.items) {
|
|
1599
1623
|
if (item.separatorBefore) {
|
|
1600
1624
|
const sep = doc.createElement("li");
|
|
@@ -1608,7 +1632,16 @@ var Surface = class _Surface {
|
|
|
1608
1632
|
li.dataset.veiId = item.id;
|
|
1609
1633
|
if (item.toggle) {
|
|
1610
1634
|
li.dataset.toggle = "1";
|
|
1611
|
-
}
|
|
1635
|
+
}
|
|
1636
|
+
if (markable) {
|
|
1637
|
+
const mark = doc.createElement("span");
|
|
1638
|
+
mark.className = "vela-menu-mark";
|
|
1639
|
+
if (!branch && !item.toggle && item.checked) {
|
|
1640
|
+
mark.appendChild(iconEl("check", doc));
|
|
1641
|
+
li.dataset.checkmark = "1";
|
|
1642
|
+
}
|
|
1643
|
+
li.appendChild(mark);
|
|
1644
|
+
} else if (!branch && !item.toggle && item.checked) {
|
|
1612
1645
|
li.dataset.checked = "1";
|
|
1613
1646
|
}
|
|
1614
1647
|
if (item.icon) li.appendChild(iconEl(item.icon, doc));
|
|
@@ -1657,6 +1690,7 @@ var Surface = class _Surface {
|
|
|
1657
1690
|
placement: "right-start",
|
|
1658
1691
|
onSelect: this.onSelect,
|
|
1659
1692
|
onFavorite: this.onFavorite,
|
|
1693
|
+
checkmarks: this.checkmarks,
|
|
1660
1694
|
id: `${this.mid}--${item.id}`
|
|
1661
1695
|
});
|
|
1662
1696
|
sub.setItems(item.submenu ?? []);
|
|
@@ -1681,7 +1715,8 @@ var Menu = class {
|
|
|
1681
1715
|
triggerId: opts.triggerId,
|
|
1682
1716
|
id: opts.id,
|
|
1683
1717
|
minWidth: opts.minWidth,
|
|
1684
|
-
onFavorite: opts.onFavorite
|
|
1718
|
+
onFavorite: opts.onFavorite,
|
|
1719
|
+
checkmarks: opts.checkmarks
|
|
1685
1720
|
});
|
|
1686
1721
|
this.root.setItems(opts.items);
|
|
1687
1722
|
}
|
|
@@ -2972,6 +3007,9 @@ var NUMBER_CSS = `
|
|
|
2972
3007
|
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
2973
3008
|
-moz-appearance: textfield;
|
|
2974
3009
|
appearance: textfield;
|
|
3010
|
+
/* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
|
|
3011
|
+
user-select: text;
|
|
3012
|
+
-webkit-user-select: text;
|
|
2975
3013
|
}
|
|
2976
3014
|
.vela-num input::-webkit-inner-spin-button, .vela-num input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
|
|
2977
3015
|
.vela-num input:hover { border-color: var(--vela-fg-muted); }
|
|
@@ -3184,6 +3222,9 @@ var TEXT_CSS = `
|
|
|
3184
3222
|
overflow: hidden;
|
|
3185
3223
|
text-overflow: ellipsis;
|
|
3186
3224
|
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
3225
|
+
/* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
|
|
3226
|
+
user-select: text;
|
|
3227
|
+
-webkit-user-select: text;
|
|
3187
3228
|
}
|
|
3188
3229
|
.vela-text-field:hover { border-color: var(--vela-fg-muted); }
|
|
3189
3230
|
.vela-text-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
@@ -3528,6 +3569,9 @@ var TEXTAREA_CSS = `
|
|
|
3528
3569
|
resize: vertical;
|
|
3529
3570
|
outline: none;
|
|
3530
3571
|
transition: border-color 0.12s ease, box-shadow 0.12s ease;
|
|
3572
|
+
/* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
|
|
3573
|
+
user-select: text;
|
|
3574
|
+
-webkit-user-select: text;
|
|
3531
3575
|
}
|
|
3532
3576
|
.vela-textarea-field:hover { border-color: var(--vela-fg-muted); }
|
|
3533
3577
|
.vela-textarea-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
|
|
@@ -3805,6 +3849,8 @@ var FIELD_CSS = `
|
|
|
3805
3849
|
font-size: 14px;
|
|
3806
3850
|
}
|
|
3807
3851
|
.vela-field-label[data-size='sm'] { font-size: inherit; opacity: 0.85; }
|
|
3852
|
+
/* Titles are fully inert: spans, not label[for] \u2014 native labels propagate :hover and
|
|
3853
|
+
clicks onto their control, and both belong to the input alone. */
|
|
3808
3854
|
.vela-field-cell { display: flex; align-items: center; gap: 8px; justify-self: start; }
|
|
3809
3855
|
.vela-field-bool { display: flex; align-items: center; gap: 8px; min-height: 22px; }
|
|
3810
3856
|
.vela-field-stacked { display: flex; flex-direction: column; gap: 8px; }
|
|
@@ -3863,11 +3909,17 @@ function fieldSeparator() {
|
|
|
3863
3909
|
return el;
|
|
3864
3910
|
}
|
|
3865
3911
|
function labelEl(text, opts) {
|
|
3866
|
-
const el =
|
|
3867
|
-
if (opts.id && el instanceof HTMLLabelElement) el.htmlFor = opts.id;
|
|
3912
|
+
const el = document.createElement("span");
|
|
3868
3913
|
el.className = "vela-field-label";
|
|
3869
3914
|
if (opts.size) el.dataset.size = opts.size;
|
|
3870
3915
|
el.textContent = text;
|
|
3916
|
+
const id = opts.id;
|
|
3917
|
+
if (id) {
|
|
3918
|
+
el.id = `${id}--title`;
|
|
3919
|
+
queueMicrotask(() => {
|
|
3920
|
+
el.ownerDocument.getElementById(id)?.setAttribute("aria-labelledby", el.id);
|
|
3921
|
+
});
|
|
3922
|
+
}
|
|
3871
3923
|
return el;
|
|
3872
3924
|
}
|
|
3873
3925
|
function asList(control) {
|
|
@@ -3896,9 +3948,7 @@ function fieldRow(opts) {
|
|
|
3896
3948
|
it.className = "vela-field-inline-item";
|
|
3897
3949
|
if (item.toggleFirst) it.style.flexDirection = "row-reverse";
|
|
3898
3950
|
if (item.label) {
|
|
3899
|
-
|
|
3900
|
-
if (item.toggleFirst) lbl.style.cursor = "pointer";
|
|
3901
|
-
it.appendChild(lbl);
|
|
3951
|
+
it.appendChild(labelEl(item.label, { id: item.id, size }));
|
|
3902
3952
|
}
|
|
3903
3953
|
if (item.fit) it.appendChild(item.control);
|
|
3904
3954
|
else {
|
|
@@ -3923,7 +3973,7 @@ function fieldRow(opts) {
|
|
|
3923
3973
|
tone: "bright",
|
|
3924
3974
|
onChange: (v) => opts.toggle?.onChange(v)
|
|
3925
3975
|
});
|
|
3926
|
-
wrap2.append(sw.el, labelEl(opts.label, { size }));
|
|
3976
|
+
wrap2.append(sw.el, labelEl(opts.label, { id: opts.toggle?.id ?? opts.id, size }));
|
|
3927
3977
|
if (opts.info) wrap2.appendChild(opts.info);
|
|
3928
3978
|
if (opts.toggle?.get) {
|
|
3929
3979
|
const get = opts.toggle.get;
|
|
@@ -3962,7 +4012,7 @@ function fieldRow(opts) {
|
|
|
3962
4012
|
if (controls.length > 0) dimControls(controls, v);
|
|
3963
4013
|
}
|
|
3964
4014
|
});
|
|
3965
|
-
left.append(sw.el, labelEl(opts.label, { size }));
|
|
4015
|
+
left.append(sw.el, labelEl(opts.label, { id: opts.toggle.id ?? opts.id, size }));
|
|
3966
4016
|
wrap.appendChild(left);
|
|
3967
4017
|
if (controls.length > 0) dimControls(controls, opts.toggle.checked);
|
|
3968
4018
|
if (opts.toggle.get) {
|
|
@@ -4533,6 +4583,8 @@ var BASELINE_BOTTOM_LINE = BEARISH;
|
|
|
4533
4583
|
var BASELINE_FILL_ALPHA = 0.25;
|
|
4534
4584
|
var BASELINE_FILL_ALPHA_FAR = 0.05;
|
|
4535
4585
|
var BASELINE_LEVEL_DEFAULT = 50;
|
|
4586
|
+
var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
|
|
4587
|
+
var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
|
|
4536
4588
|
function defaultChartStyle() {
|
|
4537
4589
|
return {
|
|
4538
4590
|
chartTypes: {},
|
|
@@ -4563,7 +4615,8 @@ function defaultChartStyle() {
|
|
|
4563
4615
|
bottomFillColor2: null,
|
|
4564
4616
|
width: 2,
|
|
4565
4617
|
baselineLevel: BASELINE_LEVEL_DEFAULT
|
|
4566
|
-
}
|
|
4618
|
+
},
|
|
4619
|
+
sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
|
|
4567
4620
|
};
|
|
4568
4621
|
}
|
|
4569
4622
|
var LINE_STYLES = ["solid", "dashed", "dotted"];
|
|
@@ -4697,6 +4750,7 @@ function mergeConfig(base, patch) {
|
|
|
4697
4750
|
const area = asObject(p.area);
|
|
4698
4751
|
const baseline = asObject(p.baseline);
|
|
4699
4752
|
const series = asObject(p.series);
|
|
4753
|
+
const sessions = asObject(p.sessions);
|
|
4700
4754
|
const stacking = asObject(p.stacking);
|
|
4701
4755
|
const stackSeries = asObject(stacking.series);
|
|
4702
4756
|
return {
|
|
@@ -4789,6 +4843,10 @@ function mergeConfig(base, patch) {
|
|
|
4789
4843
|
baseline: series.baseline === null ? null : isNum(series.baseline) ? series.baseline : base.series.baseline,
|
|
4790
4844
|
spacing: isNum(series.spacing) ? clampSpacing(series.spacing) : base.series.spacing
|
|
4791
4845
|
},
|
|
4846
|
+
sessions: {
|
|
4847
|
+
premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
|
|
4848
|
+
postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
|
|
4849
|
+
},
|
|
4792
4850
|
stacking: {
|
|
4793
4851
|
candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
|
|
4794
4852
|
// Additive like `chartTypes`: a patch names only the ids it carries; the rest keep
|
|
@@ -4961,12 +5019,14 @@ var CSS3 = `
|
|
|
4961
5019
|
color: var(--vela-fg-muted);
|
|
4962
5020
|
}
|
|
4963
5021
|
.vela-widget-tf-caret:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
|
|
5022
|
+
/* The merged trigger is a plain button (hover feedback only) \u2014 the highlight
|
|
5023
|
+
background marks the CURRENT chip among favorites, and a lone trigger with a
|
|
5024
|
+
permanent highlight would read as stuck-pressed. */
|
|
4964
5025
|
.vela-widget-tf-caret[data-solo='1'] {
|
|
4965
5026
|
width: auto;
|
|
4966
5027
|
padding: 0 6px 0 9px;
|
|
4967
5028
|
gap: 4px;
|
|
4968
5029
|
color: var(--vela-fg-bright);
|
|
4969
|
-
background: var(--vela-hover-strong);
|
|
4970
5030
|
font-size: 13px;
|
|
4971
5031
|
font-weight: 550;
|
|
4972
5032
|
white-space: nowrap;
|
|
@@ -5565,7 +5625,8 @@ var Bottombar = class {
|
|
|
5565
5625
|
const session = doc.createElement("span");
|
|
5566
5626
|
session.className = "vela-bb-session";
|
|
5567
5627
|
this.sessionEl = session;
|
|
5568
|
-
session.
|
|
5628
|
+
session.style.display = "none";
|
|
5629
|
+
session.title = "Session \u2014 regular (RTH) vs extended (ETH) hours";
|
|
5569
5630
|
for (const [key, label] of [["regular", "RTH"], ["extended", "ETH"]]) {
|
|
5570
5631
|
const b = doc.createElement("button");
|
|
5571
5632
|
b.className = "vela-bb-session-btn" + (key === "regular" ? " is-active" : "");
|
|
@@ -5616,15 +5677,15 @@ var Bottombar = class {
|
|
|
5616
5677
|
}
|
|
5617
5678
|
/**
|
|
5618
5679
|
* Reflect the ACTIVE chart's session posture. `enabled: false` (a continuous
|
|
5619
|
-
* market, or metadata not landed yet)
|
|
5620
|
-
*
|
|
5621
|
-
*
|
|
5680
|
+
* market, or metadata not landed yet) HIDES the toggle entirely — RTH/ETH is
|
|
5681
|
+
* meaningless there. Enabled, the chips appear and the active one tracks the
|
|
5682
|
+
* chart's current session.
|
|
5622
5683
|
*/
|
|
5623
5684
|
setSession(state) {
|
|
5624
|
-
if (this.sessionEl) this.sessionEl.
|
|
5685
|
+
if (this.sessionEl) this.sessionEl.style.display = state.enabled ? "" : "none";
|
|
5625
5686
|
for (const [key, b] of this.sessionButtons) {
|
|
5626
5687
|
b.disabled = !state.enabled;
|
|
5627
|
-
b.classList.toggle("is-active", state.enabled ?
|
|
5688
|
+
b.classList.toggle("is-active", key === (state.enabled ? state.session : "regular"));
|
|
5628
5689
|
}
|
|
5629
5690
|
}
|
|
5630
5691
|
destroy() {
|
|
@@ -12963,6 +13024,9 @@ var CSS7 = `
|
|
|
12963
13024
|
letter-spacing: 0.08em;
|
|
12964
13025
|
}
|
|
12965
13026
|
.vela-dw-group:first-child { border-top: none; margin-top: 0; padding-top: 8px; }
|
|
13027
|
+
/* The readout is DATA, not chrome: selectable (an exception to the UI-wide
|
|
13028
|
+
user-select:none) so values can be copied out. The panel header stays chrome. */
|
|
13029
|
+
.vela-dw-group, .vela-dw-row { user-select: text; -webkit-user-select: text; cursor: text; }
|
|
12966
13030
|
.vela-dw-row {
|
|
12967
13031
|
display: flex;
|
|
12968
13032
|
align-items: center;
|
|
@@ -16169,7 +16233,7 @@ function attachChromeTooltip(anchor, opts) {
|
|
|
16169
16233
|
} else {
|
|
16170
16234
|
const left = Math.min(a.left - h.left, Math.max(0, h.width - tip.offsetWidth - 4));
|
|
16171
16235
|
tip.style.left = `${Math.max(0, left)}px`;
|
|
16172
|
-
tip.style.top = `${a.bottom - h.top + 6}px`;
|
|
16236
|
+
tip.style.top = opts.placement === "above" ? `${Math.max(0, a.top - h.top - tip.offsetHeight - 6)}px` : `${a.bottom - h.top + 6}px`;
|
|
16173
16237
|
}
|
|
16174
16238
|
};
|
|
16175
16239
|
const arm = (e) => {
|
|
@@ -19342,6 +19406,13 @@ var DrawingsControl = class {
|
|
|
19342
19406
|
}
|
|
19343
19407
|
};
|
|
19344
19408
|
|
|
19409
|
+
// src/core/model/inputs.ts
|
|
19410
|
+
function inputVisible(when, values) {
|
|
19411
|
+
if (!when) return true;
|
|
19412
|
+
const conds = Array.isArray(when) ? when : [when];
|
|
19413
|
+
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
|
|
19414
|
+
}
|
|
19415
|
+
|
|
19345
19416
|
// src/renderers/shared/IndicatorInputsDialog.ts
|
|
19346
19417
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
19347
19418
|
var IndicatorInputsDialog = class {
|
|
@@ -19354,6 +19425,8 @@ var IndicatorInputsDialog = class {
|
|
|
19354
19425
|
this.row = null;
|
|
19355
19426
|
this.snapshot = null;
|
|
19356
19427
|
this.dialogTips = [];
|
|
19428
|
+
/** Re-applies every `when` gate against current values; set while the dialog is open. */
|
|
19429
|
+
this.refreshVisibility = null;
|
|
19357
19430
|
this.choicePop = null;
|
|
19358
19431
|
this.calendarPop = null;
|
|
19359
19432
|
this.calendarAnchor = null;
|
|
@@ -19416,14 +19489,17 @@ var IndicatorInputsDialog = class {
|
|
|
19416
19489
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
19417
19490
|
const tabEls = [];
|
|
19418
19491
|
const bodies = [];
|
|
19492
|
+
const gates = [];
|
|
19493
|
+
let active = 0;
|
|
19419
19494
|
const activate = (idx) => {
|
|
19495
|
+
active = idx;
|
|
19420
19496
|
for (let i = 0; i < tabEls.length; i += 1) {
|
|
19421
|
-
const
|
|
19497
|
+
const on = i === idx;
|
|
19422
19498
|
const el = tabEls[i];
|
|
19423
|
-
el.style.borderBottomColor =
|
|
19424
|
-
el.style.color =
|
|
19425
|
-
el.classList.toggle("vela-ind-tab-active",
|
|
19426
|
-
bodies[i].style.display =
|
|
19499
|
+
el.style.borderBottomColor = on ? fg : "transparent";
|
|
19500
|
+
el.style.color = on ? "inherit" : "var(--vela-fg-muted)";
|
|
19501
|
+
el.classList.toggle("vela-ind-tab-active", on);
|
|
19502
|
+
bodies[i].style.display = on ? "grid" : "none";
|
|
19427
19503
|
}
|
|
19428
19504
|
};
|
|
19429
19505
|
for (const [idx, def] of tabDefs.entries()) {
|
|
@@ -19434,11 +19510,24 @@ var IndicatorInputsDialog = class {
|
|
|
19434
19510
|
tab.addEventListener("click", () => activate(idx));
|
|
19435
19511
|
tabs.appendChild(tab);
|
|
19436
19512
|
tabEls.push(tab);
|
|
19437
|
-
bodies.push(this.buildTabBody(def.inputs, row));
|
|
19513
|
+
bodies.push(this.buildTabBody(def.inputs, row, gates));
|
|
19514
|
+
if (def.inputs.every((d) => d.when)) {
|
|
19515
|
+
gates.push({ el: tab, visible: (bag) => def.inputs.some((d) => inputVisible(d.when, bag)) });
|
|
19516
|
+
}
|
|
19438
19517
|
}
|
|
19439
19518
|
ui.body.appendChild(tabs);
|
|
19440
19519
|
for (const b of bodies) ui.body.appendChild(b);
|
|
19441
19520
|
activate(0);
|
|
19521
|
+
const refresh = () => {
|
|
19522
|
+
const bag = valuesBag(row);
|
|
19523
|
+
for (const g of gates) g.el.style.display = g.visible(bag) ? "" : "none";
|
|
19524
|
+
if (tabEls[active]?.style.display === "none") {
|
|
19525
|
+
const next = tabEls.findIndex((el) => el.style.display !== "none");
|
|
19526
|
+
if (next >= 0) activate(next);
|
|
19527
|
+
}
|
|
19528
|
+
};
|
|
19529
|
+
this.refreshVisibility = refresh;
|
|
19530
|
+
refresh();
|
|
19442
19531
|
const onBackdropDown = (e) => {
|
|
19443
19532
|
if (e.target !== ui.backdrop && e.target !== ui.positioner) return;
|
|
19444
19533
|
if (isPopoverOpen() || eventDismissedPopover(e)) {
|
|
@@ -19458,16 +19547,27 @@ var IndicatorInputsDialog = class {
|
|
|
19458
19547
|
}
|
|
19459
19548
|
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
19460
19549
|
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
19461
|
-
buildTabBody(inputs, row) {
|
|
19550
|
+
buildTabBody(inputs, row, gates) {
|
|
19462
19551
|
const body = document.createElement("div");
|
|
19463
19552
|
body.style.cssText = inputDialogBodyStyle(this.host.mobile());
|
|
19464
19553
|
for (const group of groupInputs(inputs)) {
|
|
19465
19554
|
const section = document.createElement("div");
|
|
19466
19555
|
section.style.cssText = "display:contents;";
|
|
19556
|
+
let header = null;
|
|
19467
19557
|
if (group.name) {
|
|
19468
|
-
|
|
19558
|
+
header = fieldSection(group.name, { variant: "inputs", first: body.childElementCount === 0 });
|
|
19559
|
+
section.appendChild(header);
|
|
19560
|
+
}
|
|
19561
|
+
for (const inputRow of group.rows) {
|
|
19562
|
+
const el = this.buildInputRow(section, inputRow, row);
|
|
19563
|
+
if (inputRow.some((d) => d.when)) {
|
|
19564
|
+
gates.push({ el, visible: (bag) => rowVisible(inputRow, bag) });
|
|
19565
|
+
}
|
|
19566
|
+
}
|
|
19567
|
+
if (header && group.rows.every((r) => r.every((d) => d.when))) {
|
|
19568
|
+
const rows = group.rows;
|
|
19569
|
+
gates.push({ el: header, visible: (bag) => rows.some((r) => rowVisible(r, bag)) });
|
|
19469
19570
|
}
|
|
19470
|
-
for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
|
|
19471
19571
|
body.appendChild(section);
|
|
19472
19572
|
}
|
|
19473
19573
|
return body;
|
|
@@ -19475,6 +19575,7 @@ var IndicatorInputsDialog = class {
|
|
|
19475
19575
|
close() {
|
|
19476
19576
|
document.removeEventListener("keydown", this.onDialogKey);
|
|
19477
19577
|
closeOpenPopovers();
|
|
19578
|
+
this.refreshVisibility = null;
|
|
19478
19579
|
this.choicePop = null;
|
|
19479
19580
|
this.calendarPop = null;
|
|
19480
19581
|
this.calendarAnchor = null;
|
|
@@ -19504,11 +19605,21 @@ var IndicatorInputsDialog = class {
|
|
|
19504
19605
|
}
|
|
19505
19606
|
this.close();
|
|
19506
19607
|
}
|
|
19608
|
+
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
19609
|
+
commit(row, key, value) {
|
|
19610
|
+
row.values[key] = value;
|
|
19611
|
+
this.host.onChange?.({ indicatorId: row.id, key, value });
|
|
19612
|
+
this.refreshVisibility?.();
|
|
19613
|
+
}
|
|
19507
19614
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
19508
19615
|
buildInputRow(grid, decls, row) {
|
|
19509
19616
|
const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
|
|
19617
|
+
const append = (el) => {
|
|
19618
|
+
grid.appendChild(el);
|
|
19619
|
+
return el;
|
|
19620
|
+
};
|
|
19510
19621
|
if (decls.length > 1) {
|
|
19511
|
-
|
|
19622
|
+
return append(fieldRow({
|
|
19512
19623
|
label: "",
|
|
19513
19624
|
inline: decls.map((d) => ({
|
|
19514
19625
|
label: nameOf(d) || void 0,
|
|
@@ -19519,14 +19630,13 @@ var IndicatorInputsDialog = class {
|
|
|
19519
19630
|
})),
|
|
19520
19631
|
info: [...decls].reverse().find((d) => d.tooltip)?.tooltip ? this.infoButton([...decls].reverse().find((d) => d.tooltip).tooltip) : void 0
|
|
19521
19632
|
}));
|
|
19522
|
-
return;
|
|
19523
19633
|
}
|
|
19524
19634
|
const lead = decls[0];
|
|
19525
19635
|
const id = idOf(lead);
|
|
19526
19636
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
19527
19637
|
if (lead.type === "bool") {
|
|
19528
19638
|
const current = Boolean(row.values[lead.key] ?? lead.defval);
|
|
19529
|
-
|
|
19639
|
+
return append(fieldRow({
|
|
19530
19640
|
label: nameOf(lead),
|
|
19531
19641
|
id,
|
|
19532
19642
|
bool: true,
|
|
@@ -19534,26 +19644,21 @@ var IndicatorInputsDialog = class {
|
|
|
19534
19644
|
toggle: {
|
|
19535
19645
|
id,
|
|
19536
19646
|
checked: current,
|
|
19537
|
-
onChange: (v) =>
|
|
19538
|
-
row.values[lead.key] = v;
|
|
19539
|
-
this.host.onChange?.({ indicatorId: row.id, key: lead.key, value: v });
|
|
19540
|
-
}
|
|
19647
|
+
onChange: (v) => this.commit(row, lead.key, v)
|
|
19541
19648
|
}
|
|
19542
19649
|
}));
|
|
19543
|
-
return;
|
|
19544
19650
|
}
|
|
19545
19651
|
if (lead.type === "text_area") {
|
|
19546
|
-
|
|
19652
|
+
return append(fieldRow({
|
|
19547
19653
|
label: nameOf(lead),
|
|
19548
19654
|
id,
|
|
19549
19655
|
stacked: true,
|
|
19550
19656
|
info,
|
|
19551
19657
|
control: this.buildControl(row, lead, id)
|
|
19552
19658
|
}));
|
|
19553
|
-
return;
|
|
19554
19659
|
}
|
|
19555
19660
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
19556
|
-
|
|
19661
|
+
return append(fieldRow({
|
|
19557
19662
|
label: nameOf(lead),
|
|
19558
19663
|
id,
|
|
19559
19664
|
fit,
|
|
@@ -19565,10 +19670,7 @@ var IndicatorInputsDialog = class {
|
|
|
19565
19670
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
19566
19671
|
buildControl(row, inp, id) {
|
|
19567
19672
|
const current = row.values[inp.key] ?? inp.defval;
|
|
19568
|
-
const emit = (value) =>
|
|
19569
|
-
row.values[inp.key] = value;
|
|
19570
|
-
this.host.onChange?.({ indicatorId: row.id, key: inp.key, value });
|
|
19571
|
-
};
|
|
19673
|
+
const emit = (value) => this.commit(row, inp.key, value);
|
|
19572
19674
|
if (inp.type === "bool") {
|
|
19573
19675
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
19574
19676
|
}
|
|
@@ -19989,6 +20091,14 @@ function tabInputs(inputs) {
|
|
|
19989
20091
|
}
|
|
19990
20092
|
return names.map((name) => ({ name, inputs: byTab.get(name) }));
|
|
19991
20093
|
}
|
|
20094
|
+
function rowVisible(decls, bag) {
|
|
20095
|
+
return decls.some((d) => inputVisible(d.when, bag));
|
|
20096
|
+
}
|
|
20097
|
+
function valuesBag(row) {
|
|
20098
|
+
const bag = {};
|
|
20099
|
+
for (const inp of row.inputs) bag[inp.key] = row.values[inp.key] ?? inp.defval;
|
|
20100
|
+
return bag;
|
|
20101
|
+
}
|
|
19992
20102
|
function groupInputs(inputs) {
|
|
19993
20103
|
const order = [];
|
|
19994
20104
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -20451,6 +20561,8 @@ var InputsUI = class {
|
|
|
20451
20561
|
host: this.container,
|
|
20452
20562
|
items,
|
|
20453
20563
|
placement: "bottom-start",
|
|
20564
|
+
// Right-click action menu — checked state reads as a leading ✓ (see the kit Menu).
|
|
20565
|
+
checkmarks: true,
|
|
20454
20566
|
onSelect: (itemId) => {
|
|
20455
20567
|
if (itemId !== "values" || !this.rowMenuId) return;
|
|
20456
20568
|
const target = this.rows.get(this.rowMenuId);
|
|
@@ -20786,6 +20898,8 @@ var InputsUI = class {
|
|
|
20786
20898
|
syncRowActions(row) {
|
|
20787
20899
|
const open2 = row.highlighted;
|
|
20788
20900
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
20901
|
+
if (open2) row.el.appendChild(row.statusEl);
|
|
20902
|
+
else row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
20789
20903
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
20790
20904
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
20791
20905
|
if (child === row.extrasEl) {
|
|
@@ -21029,6 +21143,148 @@ var PaneControls = class {
|
|
|
21029
21143
|
}
|
|
21030
21144
|
};
|
|
21031
21145
|
|
|
21146
|
+
// src/renderers/native/chrome/axisLayout.ts
|
|
21147
|
+
var AXIS_MASTER_W = 64;
|
|
21148
|
+
var AXIS_MERGED_W = 56;
|
|
21149
|
+
var PANE_SEPARATOR_PX = 3;
|
|
21150
|
+
function axisColumnX(dataW, column) {
|
|
21151
|
+
return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
|
|
21152
|
+
}
|
|
21153
|
+
|
|
21154
|
+
// src/renderers/native/chrome/AxisScaleButtons.ts
|
|
21155
|
+
var STYLE_ID22 = "vela-axis-scale-buttons";
|
|
21156
|
+
var BTN_PX = 18;
|
|
21157
|
+
var BTN_GAP = 4;
|
|
21158
|
+
var AXIS_BORDER = 1;
|
|
21159
|
+
var CLUSTER_W = AXIS_MASTER_W - AXIS_BORDER;
|
|
21160
|
+
var PAD_TOP = 8;
|
|
21161
|
+
var PAD_BOTTOM = 6;
|
|
21162
|
+
var CLUSTER_H = BTN_PX + PAD_TOP + PAD_BOTTOM;
|
|
21163
|
+
var MIN_PANE_H = 48;
|
|
21164
|
+
function ensureStyles4() {
|
|
21165
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID22)) return;
|
|
21166
|
+
const st = document.createElement("style");
|
|
21167
|
+
st.id = STYLE_ID22;
|
|
21168
|
+
st.textContent = `
|
|
21169
|
+
.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);}
|
|
21170
|
+
.vela-axis-btn:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
21171
|
+
.vela-axis-on,.vela-axis-on:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
21172
|
+
`;
|
|
21173
|
+
document.head.appendChild(st);
|
|
21174
|
+
}
|
|
21175
|
+
var AxisScaleButtons = class {
|
|
21176
|
+
constructor(plot, theme, deps) {
|
|
21177
|
+
this.plot = plot;
|
|
21178
|
+
this.theme = theme;
|
|
21179
|
+
this.deps = deps;
|
|
21180
|
+
this.tipDisposers = [];
|
|
21181
|
+
this.hoverPaneId = null;
|
|
21182
|
+
/** Reveal over the master scale column of the pane under the cursor (mouse only —
|
|
21183
|
+
* touch has no hover, and a drag-rescale on the axis must not sprout buttons). */
|
|
21184
|
+
this.onPlotMove = (e) => {
|
|
21185
|
+
if (e.pointerType !== "mouse") return;
|
|
21186
|
+
const rect = this.plot.getBoundingClientRect();
|
|
21187
|
+
const x = e.clientX - rect.left;
|
|
21188
|
+
const gutterLeft = rect.width - this.deps.rightAxis();
|
|
21189
|
+
let hit = null;
|
|
21190
|
+
if (x >= gutterLeft && x <= gutterLeft + AXIS_MASTER_W) {
|
|
21191
|
+
const y = e.clientY - rect.top;
|
|
21192
|
+
for (const p of this.deps.panes()) {
|
|
21193
|
+
if (p.collapsed || p.height < MIN_PANE_H) continue;
|
|
21194
|
+
if (y >= p.top && y <= p.top + p.height) {
|
|
21195
|
+
hit = p.id;
|
|
21196
|
+
break;
|
|
21197
|
+
}
|
|
21198
|
+
}
|
|
21199
|
+
}
|
|
21200
|
+
this.setHoverPane(hit);
|
|
21201
|
+
if (hit) this.sync();
|
|
21202
|
+
};
|
|
21203
|
+
this.onPlotLeave = () => this.setHoverPane(null);
|
|
21204
|
+
ensureStyles4();
|
|
21205
|
+
this.cluster = document.createElement("div");
|
|
21206
|
+
Object.assign(this.cluster.style, {
|
|
21207
|
+
position: "absolute",
|
|
21208
|
+
display: "none",
|
|
21209
|
+
justifyContent: "center",
|
|
21210
|
+
alignItems: "center",
|
|
21211
|
+
gap: `${BTN_GAP}px`,
|
|
21212
|
+
padding: `${PAD_TOP}px 0 ${PAD_BOTTOM}px`,
|
|
21213
|
+
width: `${CLUSTER_W}px`,
|
|
21214
|
+
boxSizing: "border-box",
|
|
21215
|
+
zIndex: "6",
|
|
21216
|
+
pointerEvents: "auto"
|
|
21217
|
+
});
|
|
21218
|
+
this.applyTheme();
|
|
21219
|
+
this.autoBtn = this.button("A", "Auto (fits data to screen)", () => this.deps.onToggleAuto(this.hoverPaneId ?? ""));
|
|
21220
|
+
this.logBtn = this.button("L", "Logarithmic scale", () => this.deps.onToggleLog(this.hoverPaneId ?? ""));
|
|
21221
|
+
this.cluster.append(this.autoBtn, this.logBtn);
|
|
21222
|
+
this.plot.appendChild(this.cluster);
|
|
21223
|
+
this.plot.addEventListener("pointermove", this.onPlotMove);
|
|
21224
|
+
this.plot.addEventListener("pointerleave", this.onPlotLeave);
|
|
21225
|
+
}
|
|
21226
|
+
button(label, title, onClick) {
|
|
21227
|
+
const b = document.createElement("button");
|
|
21228
|
+
b.type = "button";
|
|
21229
|
+
b.setAttribute("aria-label", title);
|
|
21230
|
+
b.textContent = label;
|
|
21231
|
+
b.className = "vela-axis-btn";
|
|
21232
|
+
Object.assign(b.style, { width: `${BTN_PX}px`, height: `${BTN_PX}px` });
|
|
21233
|
+
this.tipDisposers.push(attachChromeTooltip(b, {
|
|
21234
|
+
host: this.plot,
|
|
21235
|
+
theme: () => this.theme,
|
|
21236
|
+
text: () => title,
|
|
21237
|
+
placement: "above"
|
|
21238
|
+
}));
|
|
21239
|
+
b.addEventListener("click", (e) => {
|
|
21240
|
+
e.stopPropagation();
|
|
21241
|
+
onClick();
|
|
21242
|
+
this.sync();
|
|
21243
|
+
});
|
|
21244
|
+
return b;
|
|
21245
|
+
}
|
|
21246
|
+
/** Re-tint the chip when the plot background/border change (theme swap, config edit). */
|
|
21247
|
+
setTheme(theme) {
|
|
21248
|
+
this.theme = theme;
|
|
21249
|
+
this.applyTheme();
|
|
21250
|
+
}
|
|
21251
|
+
/** The strip is SOLID chart background: the buttons cover axis labels, never blend with them. */
|
|
21252
|
+
applyTheme() {
|
|
21253
|
+
this.cluster.style.background = this.theme.background;
|
|
21254
|
+
}
|
|
21255
|
+
setHoverPane(paneId) {
|
|
21256
|
+
if (this.hoverPaneId === paneId) return;
|
|
21257
|
+
this.hoverPaneId = paneId;
|
|
21258
|
+
this.reposition();
|
|
21259
|
+
}
|
|
21260
|
+
/** Re-place (or hide) the cluster after a hover or pane-layout change. */
|
|
21261
|
+
reposition() {
|
|
21262
|
+
const pane = this.hoverPaneId ? this.deps.panes().find((p) => p.id === this.hoverPaneId) : void 0;
|
|
21263
|
+
if (!pane || pane.collapsed || pane.height < MIN_PANE_H) {
|
|
21264
|
+
this.cluster.style.display = "none";
|
|
21265
|
+
return;
|
|
21266
|
+
}
|
|
21267
|
+
this.cluster.style.right = `${this.deps.rightAxis() - AXIS_MASTER_W}px`;
|
|
21268
|
+
this.cluster.style.top = `${pane.top + pane.height - CLUSTER_H}px`;
|
|
21269
|
+
this.cluster.style.display = "flex";
|
|
21270
|
+
this.sync();
|
|
21271
|
+
}
|
|
21272
|
+
/** Reflect the hovered pane's current auto/log state on the buttons. */
|
|
21273
|
+
sync() {
|
|
21274
|
+
const pane = this.deps.panes().find((p) => p.id === this.hoverPaneId);
|
|
21275
|
+
if (!pane) return;
|
|
21276
|
+
this.autoBtn.classList.toggle("vela-axis-on", pane.auto);
|
|
21277
|
+
this.logBtn.classList.toggle("vela-axis-on", pane.log);
|
|
21278
|
+
}
|
|
21279
|
+
destroy() {
|
|
21280
|
+
this.plot.removeEventListener("pointermove", this.onPlotMove);
|
|
21281
|
+
this.plot.removeEventListener("pointerleave", this.onPlotLeave);
|
|
21282
|
+
for (const dispose of this.tipDisposers) dispose();
|
|
21283
|
+
this.tipDisposers.length = 0;
|
|
21284
|
+
this.cluster.remove();
|
|
21285
|
+
}
|
|
21286
|
+
};
|
|
21287
|
+
|
|
21032
21288
|
// src/renderers/shared/TableOverlay.ts
|
|
21033
21289
|
var SIZE_PX3 = {
|
|
21034
21290
|
auto: 13,
|
|
@@ -21613,6 +21869,8 @@ var SceneGraph = class {
|
|
|
21613
21869
|
this.tradeMarkers = defaultTradeMarkersState();
|
|
21614
21870
|
/** Renderer-owned shaded time bands (session highlighting), behind grid + data. */
|
|
21615
21871
|
this.highlights = [];
|
|
21872
|
+
/** Pre/post-market bands pushed by the host (`sessionZones` feature); null ⇒ no sessions. */
|
|
21873
|
+
this.sessionZones = null;
|
|
21616
21874
|
/** Draw-order key of the price candles, relative to indicator series z (see `seriesZ`).
|
|
21617
21875
|
* Indicators with z below this draw BEHIND the candles; at/above draw in front.
|
|
21618
21876
|
* Default 0 with indicators mounting at z < 0 ⇒ the price reads on top of every overlay,
|
|
@@ -21647,6 +21905,15 @@ var SceneGraph = class {
|
|
|
21647
21905
|
if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
|
|
21648
21906
|
return this.orderedCache;
|
|
21649
21907
|
}
|
|
21908
|
+
/** The session zones resolved into colored bands (pre/post-market washes from the
|
|
21909
|
+
* config's session colors) — consumed by the same painting path as {@link highlights}. */
|
|
21910
|
+
sessionHighlightBands() {
|
|
21911
|
+
if (!this.sessionZones) return [];
|
|
21912
|
+
const out = [];
|
|
21913
|
+
for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
|
|
21914
|
+
for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
|
|
21915
|
+
return out;
|
|
21916
|
+
}
|
|
21650
21917
|
indicatorsForPane(paneId) {
|
|
21651
21918
|
const out = [];
|
|
21652
21919
|
for (const model of this.indicators.values()) if (model.paneId === paneId) out.push(model);
|
|
@@ -22577,10 +22844,12 @@ var WebGL2Backend = class {
|
|
|
22577
22844
|
}
|
|
22578
22845
|
}
|
|
22579
22846
|
}
|
|
22580
|
-
/** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
|
|
22847
|
+
/** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
|
|
22848
|
+
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
22581
22849
|
emitHighlights(b, scene, pane, coords) {
|
|
22582
|
-
|
|
22583
|
-
|
|
22850
|
+
const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
|
|
22851
|
+
if (bands.length === 0) return;
|
|
22852
|
+
for (const band of bands) {
|
|
22584
22853
|
const x1 = coords.timeToX(band.from);
|
|
22585
22854
|
const x2 = coords.timeToX(band.to);
|
|
22586
22855
|
if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
|
|
@@ -24554,10 +24823,12 @@ var Canvas2dBackend = class {
|
|
|
24554
24823
|
ctx.fillStyle = bg.color;
|
|
24555
24824
|
ctx.fillRect(x1, pane.bounds.top, x2 - x1, pane.bounds.height);
|
|
24556
24825
|
}
|
|
24557
|
-
/** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
|
|
24826
|
+
/** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
|
|
24827
|
+
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
24558
24828
|
drawHighlights(ctx, scene, coords) {
|
|
24559
|
-
|
|
24560
|
-
|
|
24829
|
+
const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
|
|
24830
|
+
if (bands.length === 0) return;
|
|
24831
|
+
for (const band of bands) {
|
|
24561
24832
|
const x1 = coords.timeToX(band.from);
|
|
24562
24833
|
const x2 = coords.timeToX(band.to);
|
|
24563
24834
|
if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
|
|
@@ -25331,14 +25602,6 @@ var DrawingSceneRenderer = class {
|
|
|
25331
25602
|
}
|
|
25332
25603
|
};
|
|
25333
25604
|
|
|
25334
|
-
// src/renderers/native/chrome/axisLayout.ts
|
|
25335
|
-
var AXIS_MASTER_W = 64;
|
|
25336
|
-
var AXIS_MERGED_W = 56;
|
|
25337
|
-
var PANE_SEPARATOR_PX = 3;
|
|
25338
|
-
function axisColumnX(dataW, column) {
|
|
25339
|
-
return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
|
|
25340
|
-
}
|
|
25341
|
-
|
|
25342
25605
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
25343
25606
|
var ChromeRenderer = class {
|
|
25344
25607
|
constructor() {
|
|
@@ -26144,6 +26407,7 @@ var SettingsDialog = class {
|
|
|
26144
26407
|
for (const hr of hs.rows) {
|
|
26145
26408
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
26146
26409
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
26410
|
+
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
26147
26411
|
else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options.map((o) => [o, o]), (v) => hr.set(v)));
|
|
26148
26412
|
}
|
|
26149
26413
|
}
|
|
@@ -28855,14 +29119,14 @@ function btn(label, primary, onClick) {
|
|
|
28855
29119
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
28856
29120
|
var BTN = 30;
|
|
28857
29121
|
var ICON = 21;
|
|
28858
|
-
var
|
|
29122
|
+
var STYLE_ID23 = "vela-drawing-popup-styles";
|
|
28859
29123
|
var STYLE_REV = "3";
|
|
28860
|
-
function
|
|
29124
|
+
function ensureStyles5() {
|
|
28861
29125
|
if (typeof document === "undefined") return;
|
|
28862
|
-
const existing = document.getElementById(
|
|
29126
|
+
const existing = document.getElementById(STYLE_ID23);
|
|
28863
29127
|
if (existing?.dataset.rev === STYLE_REV) return;
|
|
28864
29128
|
const s = existing ?? document.createElement("style");
|
|
28865
|
-
s.id =
|
|
29129
|
+
s.id = STYLE_ID23;
|
|
28866
29130
|
s.dataset.rev = STYLE_REV;
|
|
28867
29131
|
s.textContent = `
|
|
28868
29132
|
.vela-dpop-btn{background:transparent;color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
|
|
@@ -28919,7 +29183,7 @@ var DrawingSettingsPopup = class {
|
|
|
28919
29183
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
28920
29184
|
open(drawing, anchor, actions, onClose) {
|
|
28921
29185
|
this.close();
|
|
28922
|
-
|
|
29186
|
+
ensureStyles5();
|
|
28923
29187
|
this.onClose = onClose ?? null;
|
|
28924
29188
|
const t = this.theme;
|
|
28925
29189
|
const schema = drawing.schema();
|
|
@@ -31257,6 +31521,8 @@ var NativeRenderer = class {
|
|
|
31257
31521
|
// total right-gutter width (master column + merged-scale columns)
|
|
31258
31522
|
this.paneControls = null;
|
|
31259
31523
|
// per-pane hover button cluster (top-right)
|
|
31524
|
+
this.axisScaleButtons = null;
|
|
31525
|
+
// A/L hover buttons at the bottom of each pane's price scale
|
|
31260
31526
|
this.paneActionCbs = /* @__PURE__ */ new Set();
|
|
31261
31527
|
// ── data window (crosshair OHLC + per-series readout, pulled by host panels) ──
|
|
31262
31528
|
this.hoverLogical = null;
|
|
@@ -31300,7 +31566,7 @@ var NativeRenderer = class {
|
|
|
31300
31566
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
31301
31567
|
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
31302
31568
|
this.name = "native";
|
|
31303
|
-
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"];
|
|
31569
|
+
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"];
|
|
31304
31570
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
31305
31571
|
* so moving onto the button doesn't count as leaving). */
|
|
31306
31572
|
this.onScrollProximityMove = (e) => {
|
|
@@ -31416,6 +31682,9 @@ var NativeRenderer = class {
|
|
|
31416
31682
|
case "highlights":
|
|
31417
31683
|
this.scene.highlights = sanitizeHighlights(value);
|
|
31418
31684
|
break;
|
|
31685
|
+
case "sessionZones":
|
|
31686
|
+
this.scene.sessionZones = sanitizeSessionZones(value);
|
|
31687
|
+
break;
|
|
31419
31688
|
case "gridlines":
|
|
31420
31689
|
this.scene.showGrid = Boolean(value);
|
|
31421
31690
|
break;
|
|
@@ -31532,6 +31801,8 @@ var NativeRenderer = class {
|
|
|
31532
31801
|
return this.scene.indicatorZOrder();
|
|
31533
31802
|
case "highlights":
|
|
31534
31803
|
return this.scene.highlights;
|
|
31804
|
+
case "sessionZones":
|
|
31805
|
+
return this.scene.sessionZones;
|
|
31535
31806
|
case "gridlines":
|
|
31536
31807
|
return this.scene.showGrid;
|
|
31537
31808
|
case "axisLabels":
|
|
@@ -31741,7 +32012,8 @@ var NativeRenderer = class {
|
|
|
31741
32012
|
baselineLevel: s.baseline.baselineLevel
|
|
31742
32013
|
};
|
|
31743
32014
|
})(),
|
|
31744
|
-
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale }
|
|
32015
|
+
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
|
|
32016
|
+
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
|
|
31745
32017
|
};
|
|
31746
32018
|
}
|
|
31747
32019
|
/**
|
|
@@ -31823,6 +32095,7 @@ var NativeRenderer = class {
|
|
|
31823
32095
|
width: next.baseline.width,
|
|
31824
32096
|
baselineLevel: next.baseline.baselineLevel
|
|
31825
32097
|
};
|
|
32098
|
+
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
|
|
31826
32099
|
this.setPriceStyle(next.series.style);
|
|
31827
32100
|
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
31828
32101
|
this.scene.baselineValue = next.series.baseline;
|
|
@@ -31836,6 +32109,7 @@ var NativeRenderer = class {
|
|
|
31836
32109
|
}
|
|
31837
32110
|
this.inputsUI?.setTheme(this.theme);
|
|
31838
32111
|
this.paneControls?.setTheme(this.theme);
|
|
32112
|
+
this.axisScaleButtons?.setTheme(this.theme);
|
|
31839
32113
|
this.userDrawings?.setTheme(this.chromeTheme());
|
|
31840
32114
|
this.refreshScrollButtonTheme();
|
|
31841
32115
|
this.refreshAttributionColor();
|
|
@@ -32174,7 +32448,7 @@ var NativeRenderer = class {
|
|
|
32174
32448
|
this.surfaceBackground = theme.background;
|
|
32175
32449
|
this.surfaceTextColor = theme.textColor;
|
|
32176
32450
|
this.wrapper = document.createElement("div");
|
|
32177
|
-
Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair" });
|
|
32451
|
+
Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair", userSelect: "none", webkitUserSelect: "none" });
|
|
32178
32452
|
applyChromeTokens(this.wrapper, this.chromeTheme());
|
|
32179
32453
|
this.volumeCanvas = document.createElement("canvas");
|
|
32180
32454
|
Object.assign(this.volumeCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
@@ -32343,6 +32617,15 @@ var NativeRenderer = class {
|
|
|
32343
32617
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
32344
32618
|
}
|
|
32345
32619
|
});
|
|
32620
|
+
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
32621
|
+
panes: () => this.axisScaleViews(),
|
|
32622
|
+
rightAxis: () => this.rightAxisW,
|
|
32623
|
+
onToggleAuto: (paneId) => this.togglePaneAuto(paneId),
|
|
32624
|
+
onToggleLog: (paneId) => {
|
|
32625
|
+
const pane = this.scene.panes.get(paneId);
|
|
32626
|
+
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
32627
|
+
}
|
|
32628
|
+
});
|
|
32346
32629
|
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
32347
32630
|
this.resizeObserver.observe(this.wrapper);
|
|
32348
32631
|
this.watchDpr();
|
|
@@ -32419,6 +32702,7 @@ var NativeRenderer = class {
|
|
|
32419
32702
|
this.applyBackground();
|
|
32420
32703
|
this.inputsUI.setTheme(theme);
|
|
32421
32704
|
this.paneControls?.setTheme(this.theme);
|
|
32705
|
+
this.axisScaleButtons?.setTheme(this.theme);
|
|
32422
32706
|
this.settingsDialog?.refreshConfig(this.getConfig());
|
|
32423
32707
|
this.syncThemeControl();
|
|
32424
32708
|
this.settingsDialog?.setTheme(this.theme);
|
|
@@ -32467,6 +32751,7 @@ var NativeRenderer = class {
|
|
|
32467
32751
|
this.liveRegion = null;
|
|
32468
32752
|
this.inputsUI?.destroy();
|
|
32469
32753
|
this.paneControls?.destroy();
|
|
32754
|
+
this.axisScaleButtons?.destroy();
|
|
32470
32755
|
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
32471
32756
|
this.tableOverlays.clear();
|
|
32472
32757
|
this.resizeObserver?.disconnect();
|
|
@@ -33861,6 +34146,25 @@ var NativeRenderer = class {
|
|
|
33861
34146
|
invert: paneInvert(this.scene, p)
|
|
33862
34147
|
}));
|
|
33863
34148
|
}
|
|
34149
|
+
/** Thin projection of the panes for the axis A/L hover buttons (top-to-bottom order). */
|
|
34150
|
+
axisScaleViews() {
|
|
34151
|
+
return this.scene.orderedPanes().map((p) => ({
|
|
34152
|
+
id: p.id,
|
|
34153
|
+
top: p.bounds.top,
|
|
34154
|
+
height: p.bounds.height,
|
|
34155
|
+
collapsed: p.collapsed,
|
|
34156
|
+
auto: p.manualScale == null,
|
|
34157
|
+
log: paneLogScale(this.scene, p)
|
|
34158
|
+
}));
|
|
34159
|
+
}
|
|
34160
|
+
/** Toggle one pane's autoscale (the A axis button): off freezes the current window into
|
|
34161
|
+
* manual mode, on drops it — the per-pane twin of the chart-level `autoScale` feature. */
|
|
34162
|
+
togglePaneAuto(paneId) {
|
|
34163
|
+
const pane = this.scene.panes.get(paneId);
|
|
34164
|
+
if (!pane) return;
|
|
34165
|
+
pane.manualScale = pane.manualScale == null ? { ...pane.scale } : null;
|
|
34166
|
+
this.scheduler?.invalidate(4 /* Full */);
|
|
34167
|
+
}
|
|
33864
34168
|
/** Set one pane's axis mode. The price pane routes to the scene-level setting (persisted +
|
|
33865
34169
|
* keyboard shortcuts); a study pane keeps its own, so panes never affect each other. */
|
|
33866
34170
|
setPaneScaleMode(paneId, mode) {
|
|
@@ -33872,19 +34176,18 @@ var NativeRenderer = class {
|
|
|
33872
34176
|
}
|
|
33873
34177
|
this.scheduler?.invalidate(4 /* Full */);
|
|
33874
34178
|
}
|
|
33875
|
-
/** Set one pane's logarithmic flag
|
|
33876
|
-
*
|
|
34179
|
+
/** Set one pane's logarithmic flag; the price pane routes to the scene-level flag.
|
|
34180
|
+
* Log and auto are INDEPENDENT: a frozen (manual) window is kept — its price bounds
|
|
34181
|
+
* stay put and only its `log` tag flips, so the same range re-renders in the new
|
|
34182
|
+
* space instead of snapping back to autoscale. */
|
|
33877
34183
|
setPaneLog(paneId, log) {
|
|
33878
|
-
|
|
33879
|
-
|
|
33880
|
-
|
|
33881
|
-
if (
|
|
33882
|
-
|
|
33883
|
-
const p = this.scene.panes.get(paneId);
|
|
33884
|
-
if (!p) return;
|
|
33885
|
-
p.logScale = log;
|
|
33886
|
-
p.manualScale = null;
|
|
34184
|
+
const pane = paneId === PRICE_PANE_ID2 ? this.scene.orderedPanes().find((p) => p.kind === "price") : this.scene.panes.get(paneId);
|
|
34185
|
+
if (paneId === PRICE_PANE_ID2) this.scene.logScale = log;
|
|
34186
|
+
else {
|
|
34187
|
+
if (!pane) return;
|
|
34188
|
+
pane.logScale = log;
|
|
33887
34189
|
}
|
|
34190
|
+
if (pane?.manualScale) pane.manualScale.log = log;
|
|
33888
34191
|
this.scheduler?.invalidate(4 /* Full */);
|
|
33889
34192
|
}
|
|
33890
34193
|
/** Flip one pane's axis (high at the bottom). The price pane routes to the scene-level flag
|
|
@@ -33983,6 +34286,7 @@ var NativeRenderer = class {
|
|
|
33983
34286
|
}
|
|
33984
34287
|
this.inputsUI?.reposition();
|
|
33985
34288
|
this.paneControls?.reposition();
|
|
34289
|
+
this.axisScaleButtons?.reposition();
|
|
33986
34290
|
this.repositionScrollButton();
|
|
33987
34291
|
this.positionAttribution();
|
|
33988
34292
|
this.publishPricePaneBounds();
|
|
@@ -34001,6 +34305,7 @@ var NativeRenderer = class {
|
|
|
34001
34305
|
}
|
|
34002
34306
|
this.inputsUI?.reposition();
|
|
34003
34307
|
this.paneControls?.reposition();
|
|
34308
|
+
this.axisScaleButtons?.reposition();
|
|
34004
34309
|
this.repositionScrollButton();
|
|
34005
34310
|
this.positionAttribution();
|
|
34006
34311
|
this.publishPricePaneBounds();
|
|
@@ -34208,6 +34513,22 @@ function sanitizeHighlights(value) {
|
|
|
34208
34513
|
}
|
|
34209
34514
|
return out.sort((a, b) => a.from - b.from);
|
|
34210
34515
|
}
|
|
34516
|
+
function sanitizeSessionZones(value) {
|
|
34517
|
+
if (value == null || typeof value !== "object") return null;
|
|
34518
|
+
const v = value;
|
|
34519
|
+
const windows = (raw) => {
|
|
34520
|
+
if (!Array.isArray(raw)) return [];
|
|
34521
|
+
const out = [];
|
|
34522
|
+
for (const w of raw) {
|
|
34523
|
+
if (!Array.isArray(w)) continue;
|
|
34524
|
+
const from = Number(w[0]);
|
|
34525
|
+
const to = Number(w[1]);
|
|
34526
|
+
if (Number.isFinite(from) && Number.isFinite(to) && to > from) out.push([from, to]);
|
|
34527
|
+
}
|
|
34528
|
+
return out.sort((a, b) => a[0] - b[0]);
|
|
34529
|
+
};
|
|
34530
|
+
return { pre: windows(v.pre), post: windows(v.post) };
|
|
34531
|
+
}
|
|
34211
34532
|
function clamp012(v) {
|
|
34212
34533
|
return Math.max(0, Math.min(1, v));
|
|
34213
34534
|
}
|
|
@@ -34761,7 +35082,7 @@ function fmtChange(open2, close) {
|
|
|
34761
35082
|
}
|
|
34762
35083
|
|
|
34763
35084
|
// src/widget/statusline.ts
|
|
34764
|
-
var
|
|
35085
|
+
var STYLE_ID24 = "vela-widget-statusline";
|
|
34765
35086
|
var CSS21 = `
|
|
34766
35087
|
.vela-statusline {
|
|
34767
35088
|
position: absolute;
|
|
@@ -34953,7 +35274,7 @@ var Statusline = class {
|
|
|
34953
35274
|
this.fitMode = false;
|
|
34954
35275
|
this.fitRO = null;
|
|
34955
35276
|
const doc = host.ownerDocument;
|
|
34956
|
-
injectStyles(
|
|
35277
|
+
injectStyles(STYLE_ID24, CSS21, doc);
|
|
34957
35278
|
host.classList.add("vela-has-statusline");
|
|
34958
35279
|
this.el = doc.createElement("div");
|
|
34959
35280
|
this.el.className = "vela-statusline";
|
|
@@ -35226,11 +35547,70 @@ var MarketStatusTracker = class {
|
|
|
35226
35547
|
}
|
|
35227
35548
|
};
|
|
35228
35549
|
|
|
35550
|
+
// src/widget/session-shading.ts
|
|
35551
|
+
function deriveSessionZones(regular, extended) {
|
|
35552
|
+
const pre = [];
|
|
35553
|
+
const post = [];
|
|
35554
|
+
for (const [extStart, extEnd] of extended) {
|
|
35555
|
+
const inside = regular.filter(([s, e]) => e > extStart && s < extEnd).sort((a, b) => a[0] - b[0]);
|
|
35556
|
+
let cursor = extStart;
|
|
35557
|
+
for (const [regStart, regEnd] of inside) {
|
|
35558
|
+
if (regStart > cursor) pre.push([cursor, Math.min(regStart, extEnd)]);
|
|
35559
|
+
cursor = Math.max(cursor, regEnd);
|
|
35560
|
+
}
|
|
35561
|
+
if (cursor < extEnd) post.push([cursor, extEnd]);
|
|
35562
|
+
}
|
|
35563
|
+
return { pre, post };
|
|
35564
|
+
}
|
|
35565
|
+
var FETCH_AHEAD_MS2 = 10 * 864e5;
|
|
35566
|
+
var MIN_LOOKBACK_MS = 3 * 864e5;
|
|
35567
|
+
var MAX_LOOKBACK_MS = 120 * 864e5;
|
|
35568
|
+
var SessionShadingTracker = class {
|
|
35569
|
+
constructor(onZones) {
|
|
35570
|
+
this.onZones = onZones;
|
|
35571
|
+
/** Invalidates detached async work — bumped by every track()/stop(). */
|
|
35572
|
+
this.epoch = 0;
|
|
35573
|
+
}
|
|
35574
|
+
/** (Re)bind to a chart's data surface + market and evaluate once. */
|
|
35575
|
+
track(data, symbol, opts) {
|
|
35576
|
+
const my = ++this.epoch;
|
|
35577
|
+
void this.evaluate(my, data, symbol, opts);
|
|
35578
|
+
}
|
|
35579
|
+
stop() {
|
|
35580
|
+
this.epoch += 1;
|
|
35581
|
+
}
|
|
35582
|
+
async evaluate(my, data, symbol, opts) {
|
|
35583
|
+
const resolved = data.resolve(symbol);
|
|
35584
|
+
const provider = resolved ? data.providerInstance(resolved.provider) : void 0;
|
|
35585
|
+
const si = await data.symbolInfo(symbol).catch(() => void 0);
|
|
35586
|
+
if (my !== this.epoch) return;
|
|
35587
|
+
const hasSessions = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
35588
|
+
if (!provider?.getCalendar || !hasSessions || !resolved) {
|
|
35589
|
+
this.onZones(null);
|
|
35590
|
+
return;
|
|
35591
|
+
}
|
|
35592
|
+
if (opts.session !== "extended") {
|
|
35593
|
+
this.onZones({ pre: [], post: [] });
|
|
35594
|
+
return;
|
|
35595
|
+
}
|
|
35596
|
+
const now = Date.now();
|
|
35597
|
+
const lookback = Math.max(MIN_LOOKBACK_MS, Math.min(MAX_LOOKBACK_MS, opts.lookbackMs));
|
|
35598
|
+
const range = { from: now - lookback, to: now + FETCH_AHEAD_MS2 };
|
|
35599
|
+
const [regular, extended] = await Promise.all([
|
|
35600
|
+
provider.getCalendar(resolved.ticker, { ...range, session: "regular" }).catch(() => null),
|
|
35601
|
+
provider.getCalendar(resolved.ticker, { ...range, session: "extended" }).catch(() => null)
|
|
35602
|
+
]);
|
|
35603
|
+
if (my !== this.epoch) return;
|
|
35604
|
+
if (!regular || !extended) return;
|
|
35605
|
+
this.onZones(deriveSessionZones(regular, extended));
|
|
35606
|
+
}
|
|
35607
|
+
};
|
|
35608
|
+
|
|
35229
35609
|
// src/widget/watermark.ts
|
|
35230
35610
|
var MAX_FONT_PX = 36;
|
|
35231
35611
|
var MIN_FONT_PX = 12;
|
|
35232
35612
|
var FILL = 0.9;
|
|
35233
|
-
var
|
|
35613
|
+
var STYLE_ID25 = "vela-widget-watermark";
|
|
35234
35614
|
var CSS22 = `
|
|
35235
35615
|
.vela-watermark {
|
|
35236
35616
|
position: absolute;
|
|
@@ -35269,7 +35649,7 @@ var Watermark = class {
|
|
|
35269
35649
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
35270
35650
|
* fires during chart construction, before any subscriber can see it. */
|
|
35271
35651
|
this.loading = true;
|
|
35272
|
-
injectStyles(
|
|
35652
|
+
injectStyles(STYLE_ID25, CSS22, host.ownerDocument);
|
|
35273
35653
|
this.el = host.ownerDocument.createElement("div");
|
|
35274
35654
|
this.el.className = "vela-watermark";
|
|
35275
35655
|
this.el.dataset.velaScreenshot = "under";
|
|
@@ -35335,6 +35715,9 @@ var ChartContextMenu = class {
|
|
|
35335
35715
|
host,
|
|
35336
35716
|
items: [],
|
|
35337
35717
|
placement: "bottom-start",
|
|
35718
|
+
// Pointer-anchored action menu: checked state reads as a leading ✓, not a
|
|
35719
|
+
// washed row (which would read as hover in a menu with no trigger button).
|
|
35720
|
+
checkmarks: true,
|
|
35338
35721
|
onSelect: (id) => this.run(id)
|
|
35339
35722
|
});
|
|
35340
35723
|
host.addEventListener("contextmenu", this.onContextMenu);
|
|
@@ -35553,6 +35936,8 @@ var ChartCell = class {
|
|
|
35553
35936
|
this.activeRangeId = null;
|
|
35554
35937
|
/** Latched verdict of {@link sessionAvailable} (async metadata, sticky per symbol). */
|
|
35555
35938
|
this.sessionAvailableFlag = false;
|
|
35939
|
+
/** Keeps the pre/post-market shading on the symbol's real calendar (see {@link SessionShadingTracker}). */
|
|
35940
|
+
this.sessionShading = new SessionShadingTracker((zones) => this.inner?.renderer.set("sessionZones", zones));
|
|
35556
35941
|
this.manifest = [];
|
|
35557
35942
|
/** A restored ledger's manifest entry NAMES, waiting for the manifest to resolve
|
|
35558
35943
|
* (a pool/persisted cell can be built before the shared manifest has loaded). */
|
|
@@ -35709,67 +36094,7 @@ var ChartCell = class {
|
|
|
35709
36094
|
});
|
|
35710
36095
|
this.syncPresentNatives();
|
|
35711
36096
|
this.refreshNativeCatalog();
|
|
35712
|
-
|
|
35713
|
-
title: "Advanced",
|
|
35714
|
-
placement: "end",
|
|
35715
|
-
rows: [
|
|
35716
|
-
{
|
|
35717
|
-
kind: "select",
|
|
35718
|
-
label: "Bars to fetch",
|
|
35719
|
-
options: ["500", "1000", "2000", "5000", "10000", "20000"],
|
|
35720
|
-
get: () => String(this.state.bars ?? 1e3),
|
|
35721
|
-
set: (v) => {
|
|
35722
|
-
this.state.bars = Number(v);
|
|
35723
|
-
this.deps.onStateDirty();
|
|
35724
|
-
void this.inner?.setMarket({ bars: Math.max(this.state.bars, this.rangeBars) });
|
|
35725
|
-
}
|
|
35726
|
-
}
|
|
35727
|
-
]
|
|
35728
|
-
};
|
|
35729
|
-
const watermarkSection = {
|
|
35730
|
-
title: "Watermark",
|
|
35731
|
-
placement: "symbol",
|
|
35732
|
-
rows: [
|
|
35733
|
-
{
|
|
35734
|
-
kind: "toggle",
|
|
35735
|
-
label: "Symbol watermark",
|
|
35736
|
-
get: () => this.watermarkOn,
|
|
35737
|
-
set: (v) => this.setWatermarkVisible(v)
|
|
35738
|
-
}
|
|
35739
|
-
]
|
|
35740
|
-
};
|
|
35741
|
-
if (this.statusline) {
|
|
35742
|
-
const sl = this.statusline;
|
|
35743
|
-
this.inner.renderer.setSettingsSections([
|
|
35744
|
-
{
|
|
35745
|
-
title: "Status line",
|
|
35746
|
-
rows: [
|
|
35747
|
-
{ kind: "heading", label: "Status line" },
|
|
35748
|
-
{ kind: "toggle", label: "Symbol name", get: () => sl.partVisible("name"), set: (v) => sl.setPartVisible("name", v) },
|
|
35749
|
-
{ kind: "toggle", label: "Market status", get: () => sl.partVisible("market"), set: (v) => sl.setPartVisible("market", v) },
|
|
35750
|
-
{ kind: "toggle", label: "OHLC values", get: () => sl.partVisible("ohlc"), set: (v) => sl.setPartVisible("ohlc", v) },
|
|
35751
|
-
{ kind: "toggle", label: "Bar change values", get: () => sl.partVisible("change"), set: (v) => sl.setPartVisible("change", v) },
|
|
35752
|
-
{ kind: "heading", label: "Indicators" },
|
|
35753
|
-
{
|
|
35754
|
-
kind: "toggle",
|
|
35755
|
-
label: "Titles",
|
|
35756
|
-
get: () => this.indicatorTitlesOn,
|
|
35757
|
-
set: (v) => this.setIndicatorTitlesVisible(v)
|
|
35758
|
-
},
|
|
35759
|
-
{
|
|
35760
|
-
kind: "toggle",
|
|
35761
|
-
label: "Values",
|
|
35762
|
-
get: () => this.indicatorValuesOn,
|
|
35763
|
-
set: (v) => this.setIndicatorValuesVisible(v)
|
|
35764
|
-
}
|
|
35765
|
-
]
|
|
35766
|
-
},
|
|
35767
|
-
advanced,
|
|
35768
|
-
watermarkSection
|
|
35769
|
-
]);
|
|
35770
|
-
} else {
|
|
35771
|
-
this.inner.renderer.setSettingsSections([advanced, watermarkSection]);
|
|
35772
|
-
}
|
|
36097
|
+
this.pushSettingsSections();
|
|
35773
36098
|
this.offMarket = this.inner.on("market:changed", ({ symbol: symbol2, timeframe }) => {
|
|
35774
36099
|
this.state.symbol = symbol2;
|
|
35775
36100
|
this.state.provider = parseSymbol(symbol2).provider ?? void 0;
|
|
@@ -35814,9 +36139,130 @@ var ChartCell = class {
|
|
|
35814
36139
|
if (available !== this.sessionAvailableFlag) {
|
|
35815
36140
|
this.sessionAvailableFlag = available;
|
|
35816
36141
|
this.deps.onMarketChanged(this.id);
|
|
36142
|
+
this.pushSettingsSections();
|
|
35817
36143
|
}
|
|
36144
|
+
this.refreshSessionShading();
|
|
35818
36145
|
});
|
|
35819
36146
|
}
|
|
36147
|
+
/** (Re)derive the pre/post-market shading bands for this cell's market. The loaded
|
|
36148
|
+
* depth (bars × timeframe) bounds the calendar fetch; the tracker clamps it. */
|
|
36149
|
+
refreshSessionShading() {
|
|
36150
|
+
const chart = this.inner;
|
|
36151
|
+
const symbol = this.state.symbol;
|
|
36152
|
+
if (!chart || !symbol) return;
|
|
36153
|
+
const lookbackMs = Math.max(this.state.bars ?? 1e3, this.rangeBars) * timeframeMs(this.state.timeframe ?? "60");
|
|
36154
|
+
this.sessionShading.track(chart.data, symbol, { session: this.session, lookbackMs });
|
|
36155
|
+
}
|
|
36156
|
+
/** The session-shade colors live in the renderer CONFIG (persisted with it, edited
|
|
36157
|
+
* live by the dialog swatch) — the cell only proxies them into its settings rows. */
|
|
36158
|
+
sessionShadeColor(key) {
|
|
36159
|
+
const cfg = this.inner?.renderer.getConfig();
|
|
36160
|
+
const v = cfg?.sessions?.[key];
|
|
36161
|
+
return typeof v === "string" ? v : "";
|
|
36162
|
+
}
|
|
36163
|
+
setSessionShadeColor(key, color) {
|
|
36164
|
+
this.inner?.renderer.applyConfig({ sessions: { [key]: color } });
|
|
36165
|
+
this.deps.onStateDirty();
|
|
36166
|
+
}
|
|
36167
|
+
/**
|
|
36168
|
+
* (Re)contribute this cell's settings-dialog sections: status line parts, the
|
|
36169
|
+
* per-cell fetch depth, the watermark toggle, and — only while the cell's symbol
|
|
36170
|
+
* HAS sessions — the Trading session group (RTH/ETH switch + the pre/post-market
|
|
36171
|
+
* shading colors) inside the Symbol tab. Bars/watermark/titles are persistable
|
|
36172
|
+
* cell state; a depth-only reload is silent, so mark dirty here. Re-run whenever
|
|
36173
|
+
* a gate changes (the dialog reads the sections on open).
|
|
36174
|
+
*/
|
|
36175
|
+
pushSettingsSections() {
|
|
36176
|
+
const chart = this.inner;
|
|
36177
|
+
if (!chart) return;
|
|
36178
|
+
const rth = "Regular hours (RTH)";
|
|
36179
|
+
const eth = "Extended hours (ETH)";
|
|
36180
|
+
const sessionSection = {
|
|
36181
|
+
title: "Trading session",
|
|
36182
|
+
placement: "symbol",
|
|
36183
|
+
rows: [
|
|
36184
|
+
{
|
|
36185
|
+
kind: "select",
|
|
36186
|
+
label: "Session",
|
|
36187
|
+
options: [rth, eth],
|
|
36188
|
+
get: () => this.session === "extended" ? eth : rth,
|
|
36189
|
+
set: (v) => this.setSession(v === eth ? "extended" : "regular")
|
|
36190
|
+
},
|
|
36191
|
+
{
|
|
36192
|
+
kind: "color",
|
|
36193
|
+
label: "Pre-market",
|
|
36194
|
+
get: () => this.sessionShadeColor("premarketColor"),
|
|
36195
|
+
set: (v) => this.setSessionShadeColor("premarketColor", v)
|
|
36196
|
+
},
|
|
36197
|
+
{
|
|
36198
|
+
kind: "color",
|
|
36199
|
+
label: "Post-market",
|
|
36200
|
+
get: () => this.sessionShadeColor("postmarketColor"),
|
|
36201
|
+
set: (v) => this.setSessionShadeColor("postmarketColor", v)
|
|
36202
|
+
}
|
|
36203
|
+
]
|
|
36204
|
+
};
|
|
36205
|
+
const advanced = {
|
|
36206
|
+
title: "Advanced",
|
|
36207
|
+
placement: "end",
|
|
36208
|
+
rows: [
|
|
36209
|
+
{
|
|
36210
|
+
kind: "select",
|
|
36211
|
+
label: "Bars to fetch",
|
|
36212
|
+
options: ["500", "1000", "2000", "5000", "10000", "20000"],
|
|
36213
|
+
get: () => String(this.state.bars ?? 1e3),
|
|
36214
|
+
set: (v) => {
|
|
36215
|
+
this.state.bars = Number(v);
|
|
36216
|
+
this.deps.onStateDirty();
|
|
36217
|
+
void this.inner?.setMarket({ bars: Math.max(this.state.bars, this.rangeBars) });
|
|
36218
|
+
}
|
|
36219
|
+
}
|
|
36220
|
+
]
|
|
36221
|
+
};
|
|
36222
|
+
const watermarkSection = {
|
|
36223
|
+
title: "Watermark",
|
|
36224
|
+
placement: "symbol",
|
|
36225
|
+
rows: [
|
|
36226
|
+
{
|
|
36227
|
+
kind: "toggle",
|
|
36228
|
+
label: "Symbol watermark",
|
|
36229
|
+
get: () => this.watermarkOn,
|
|
36230
|
+
set: (v) => this.setWatermarkVisible(v)
|
|
36231
|
+
}
|
|
36232
|
+
]
|
|
36233
|
+
};
|
|
36234
|
+
const sections = [];
|
|
36235
|
+
if (this.statusline) {
|
|
36236
|
+
const sl = this.statusline;
|
|
36237
|
+
sections.push({
|
|
36238
|
+
title: "Status line",
|
|
36239
|
+
rows: [
|
|
36240
|
+
{ kind: "heading", label: "Status line" },
|
|
36241
|
+
{ kind: "toggle", label: "Symbol name", get: () => sl.partVisible("name"), set: (v) => sl.setPartVisible("name", v) },
|
|
36242
|
+
{ kind: "toggle", label: "Market status", get: () => sl.partVisible("market"), set: (v) => sl.setPartVisible("market", v) },
|
|
36243
|
+
{ kind: "toggle", label: "OHLC values", get: () => sl.partVisible("ohlc"), set: (v) => sl.setPartVisible("ohlc", v) },
|
|
36244
|
+
{ kind: "toggle", label: "Bar change values", get: () => sl.partVisible("change"), set: (v) => sl.setPartVisible("change", v) },
|
|
36245
|
+
{ kind: "heading", label: "Indicators" },
|
|
36246
|
+
{
|
|
36247
|
+
kind: "toggle",
|
|
36248
|
+
label: "Titles",
|
|
36249
|
+
get: () => this.indicatorTitlesOn,
|
|
36250
|
+
set: (v) => this.setIndicatorTitlesVisible(v)
|
|
36251
|
+
},
|
|
36252
|
+
{
|
|
36253
|
+
kind: "toggle",
|
|
36254
|
+
label: "Values",
|
|
36255
|
+
get: () => this.indicatorValuesOn,
|
|
36256
|
+
set: (v) => this.setIndicatorValuesVisible(v)
|
|
36257
|
+
}
|
|
36258
|
+
]
|
|
36259
|
+
});
|
|
36260
|
+
}
|
|
36261
|
+
sections.push(advanced);
|
|
36262
|
+
if (this.sessionAvailableFlag) sections.push(sessionSection);
|
|
36263
|
+
sections.push(watermarkSection);
|
|
36264
|
+
chart.renderer.setSettingsSections(sections);
|
|
36265
|
+
}
|
|
35820
36266
|
/** Show/hide this cell's symbol watermark (persisted per cell). */
|
|
35821
36267
|
setWatermarkVisible(visible) {
|
|
35822
36268
|
this.watermarkOn = visible;
|
|
@@ -35871,6 +36317,7 @@ var ChartCell = class {
|
|
|
35871
36317
|
this.state.priceStyle = style;
|
|
35872
36318
|
this.inner?.renderer.set("priceStyle", style);
|
|
35873
36319
|
this.syncStatuslineColors();
|
|
36320
|
+
this.deps.onPriceStyleChanged(this.id);
|
|
35874
36321
|
}
|
|
35875
36322
|
/** OHLC/change ink in the status line follows the ACTIVE price style's configured
|
|
35876
36323
|
* colors and direction rule (candle bodies by close-vs-open, baseline by position
|
|
@@ -36094,6 +36541,7 @@ var ChartCell = class {
|
|
|
36094
36541
|
this.contextMenu.destroy();
|
|
36095
36542
|
this.history.destroy();
|
|
36096
36543
|
this.marketStatus?.stop();
|
|
36544
|
+
this.sessionShading.stop();
|
|
36097
36545
|
this.statusline?.destroy();
|
|
36098
36546
|
this.watermark?.destroy();
|
|
36099
36547
|
this.inner?.destroy();
|
|
@@ -36378,7 +36826,7 @@ var GAP_PX = 2;
|
|
|
36378
36826
|
var POOL_CAP = 16;
|
|
36379
36827
|
var TIME_AXIS_H3 = 22;
|
|
36380
36828
|
var ALERT_CAP = 50;
|
|
36381
|
-
var
|
|
36829
|
+
var STYLE_ID26 = "vela-workspace";
|
|
36382
36830
|
var CSS23 = `
|
|
36383
36831
|
.vela-workspace { position: relative; width: 100%; height: 100%; display: flex; flex-direction: column; background: var(--vela-bg); }
|
|
36384
36832
|
.vela-ws-main { position: relative; display: flex; flex-direction: row; flex: 1 1 auto; min-height: 0; }
|
|
@@ -36525,7 +36973,7 @@ var VelaWorkspace = class {
|
|
|
36525
36973
|
this.order = boot?.charts ? boot.charts.map((c) => c.id) : declaredOrder(opts.cells);
|
|
36526
36974
|
const bootActive = boot?.activeCellId ?? null;
|
|
36527
36975
|
const doc = hostEl.ownerDocument;
|
|
36528
|
-
injectStyles(
|
|
36976
|
+
injectStyles(STYLE_ID26, CSS23, doc);
|
|
36529
36977
|
this.root = doc.createElement("div");
|
|
36530
36978
|
this.root.className = "vela-workspace";
|
|
36531
36979
|
ensureUIHost(this.root, resolveTheme(opts.theme));
|
|
@@ -37125,6 +37573,7 @@ var VelaWorkspace = class {
|
|
|
37125
37573
|
context: () => this.context(),
|
|
37126
37574
|
activate: (id2) => this.setActiveCell(id2),
|
|
37127
37575
|
onMarketChanged: (id2) => this.onCellMarketChanged(id2),
|
|
37576
|
+
onPriceStyleChanged: (id2) => this.onCellPriceStyleChanged(id2),
|
|
37128
37577
|
onIndicatorsChanged: (id2) => this.onCellIndicatorsChanged(id2),
|
|
37129
37578
|
onStateDirty: () => this.markStateDirty(),
|
|
37130
37579
|
manifestSettled: () => this.manifestSettled
|
|
@@ -37414,6 +37863,16 @@ var VelaWorkspace = class {
|
|
|
37414
37863
|
this.objectTree.setSymbol(cell.symbol);
|
|
37415
37864
|
this.bottombar?.setSession({ session: cell.session, enabled: cell.sessionAvailable });
|
|
37416
37865
|
}
|
|
37866
|
+
/** Trigger ② — a cell's price style changed: the topbar button/menu only if active.
|
|
37867
|
+
* Reads the cell back (not the requested style) so the button reflects what the
|
|
37868
|
+
* renderer actually applied. */
|
|
37869
|
+
onCellPriceStyleChanged(id) {
|
|
37870
|
+
this.markStateDirty();
|
|
37871
|
+
if (id !== this.activeId) return;
|
|
37872
|
+
const cell = this.cellsById.get(id);
|
|
37873
|
+
if (!cell) return;
|
|
37874
|
+
this.topbar.setPriceStyle(cell.priceStyle);
|
|
37875
|
+
}
|
|
37417
37876
|
/** Trigger ② — a cell's indicator ledger changed: count + picker only if active. */
|
|
37418
37877
|
onCellIndicatorsChanged(id) {
|
|
37419
37878
|
this.markStateDirty();
|