@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/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;
|
|
@@ -13343,6 +13407,7 @@ var CSS8 = `
|
|
|
13343
13407
|
.vela-sp-badge[data-p='hyperliquid'] { color: #50d2c1; } /* palette-exempt: venue brand mark */
|
|
13344
13408
|
.vela-sp-empty { padding: var(--vela-space-3); color: var(--vela-fg-muted); text-align: center; }
|
|
13345
13409
|
`;
|
|
13410
|
+
var PAGE = 100;
|
|
13346
13411
|
var SymbolPicker = class {
|
|
13347
13412
|
constructor(opts) {
|
|
13348
13413
|
this.source = () => [];
|
|
@@ -13350,6 +13415,7 @@ var SymbolPicker = class {
|
|
|
13350
13415
|
this.highlighted = 0;
|
|
13351
13416
|
this.seed = "";
|
|
13352
13417
|
this.activeTab = "All";
|
|
13418
|
+
this.visible = PAGE;
|
|
13353
13419
|
const doc = (opts.host ?? document.body).ownerDocument;
|
|
13354
13420
|
injectStyles(STYLE_ID7, CSS8, doc);
|
|
13355
13421
|
this.input = doc.createElement("input");
|
|
@@ -13376,6 +13442,12 @@ var SymbolPicker = class {
|
|
|
13376
13442
|
}
|
|
13377
13443
|
this.list = doc.createElement("div");
|
|
13378
13444
|
this.list.className = "vela-sp-list";
|
|
13445
|
+
this.list.addEventListener("scroll", () => {
|
|
13446
|
+
if (this.rows.length < this.visible) return;
|
|
13447
|
+
if (this.list.scrollTop + this.list.clientHeight < this.list.scrollHeight - 200) return;
|
|
13448
|
+
this.visible += PAGE;
|
|
13449
|
+
this.grow();
|
|
13450
|
+
});
|
|
13379
13451
|
this.dialog = new Dialog({
|
|
13380
13452
|
title: "Symbol Search",
|
|
13381
13453
|
host: opts.host,
|
|
@@ -13441,11 +13513,15 @@ var SymbolPicker = class {
|
|
|
13441
13513
|
} else delete el.dataset.highlighted;
|
|
13442
13514
|
});
|
|
13443
13515
|
}
|
|
13444
|
-
|
|
13445
|
-
const doc = this.list.ownerDocument;
|
|
13516
|
+
computeRows() {
|
|
13446
13517
|
const TAB_TYPES = { Crypto: ["crypto"], Stocks: ["stock"], ETFs: ["etf"], Forex: ["forex"], Commodities: ["commodity"] };
|
|
13447
13518
|
const pool = this.activeTab === "All" ? this.source() : this.source().filter((s) => TAB_TYPES[this.activeTab]?.includes((s.type ?? "").toLowerCase()) || this.activeTab === "Crypto" && (s.type ?? "").toLowerCase() === "futures");
|
|
13448
|
-
|
|
13519
|
+
return filterSymbols(pool, this.input.value, this.visible);
|
|
13520
|
+
}
|
|
13521
|
+
refresh() {
|
|
13522
|
+
const doc = this.list.ownerDocument;
|
|
13523
|
+
this.visible = PAGE;
|
|
13524
|
+
this.rows = this.computeRows();
|
|
13449
13525
|
this.highlighted = 0;
|
|
13450
13526
|
this.list.replaceChildren();
|
|
13451
13527
|
if (!this.rows.length) {
|
|
@@ -13455,34 +13531,42 @@ var SymbolPicker = class {
|
|
|
13455
13531
|
this.list.appendChild(empty);
|
|
13456
13532
|
return;
|
|
13457
13533
|
}
|
|
13458
|
-
for (const s of this.rows)
|
|
13459
|
-
const row = doc.createElement("div");
|
|
13460
|
-
row.className = "vela-sp-row";
|
|
13461
|
-
row.dataset.ticker = s.ticker;
|
|
13462
|
-
const venue = s.prefix ?? s.provider;
|
|
13463
|
-
if (venue) row.dataset.venue = venue;
|
|
13464
|
-
const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
|
|
13465
|
-
const main = doc.createElement("span");
|
|
13466
|
-
main.className = "vela-sp-main";
|
|
13467
|
-
const t = doc.createElement("span");
|
|
13468
|
-
t.className = "vela-sp-ticker";
|
|
13469
|
-
t.textContent = s.ticker;
|
|
13470
|
-
const d = doc.createElement("span");
|
|
13471
|
-
d.className = "vela-sp-desc";
|
|
13472
|
-
d.textContent = s.description ?? (s.type ?? "");
|
|
13473
|
-
main.append(t, d);
|
|
13474
|
-
row.append(av, main);
|
|
13475
|
-
if (venue) {
|
|
13476
|
-
const badge = doc.createElement("span");
|
|
13477
|
-
badge.className = "vela-sp-badge";
|
|
13478
|
-
badge.dataset.p = s.provider ?? venue;
|
|
13479
|
-
badge.textContent = venue;
|
|
13480
|
-
row.appendChild(badge);
|
|
13481
|
-
}
|
|
13482
|
-
this.list.appendChild(row);
|
|
13483
|
-
}
|
|
13534
|
+
for (const s of this.rows) this.list.appendChild(this.rowEl(s));
|
|
13484
13535
|
this.renderHighlight();
|
|
13485
13536
|
}
|
|
13537
|
+
/** Append the page the grown `visible` just uncovered — rows already on screen stay put. */
|
|
13538
|
+
grow() {
|
|
13539
|
+
const already = this.rows.length;
|
|
13540
|
+
this.rows = this.computeRows();
|
|
13541
|
+
for (const s of this.rows.slice(already)) this.list.appendChild(this.rowEl(s));
|
|
13542
|
+
}
|
|
13543
|
+
rowEl(s) {
|
|
13544
|
+
const doc = this.list.ownerDocument;
|
|
13545
|
+
const row = doc.createElement("div");
|
|
13546
|
+
row.className = "vela-sp-row";
|
|
13547
|
+
row.dataset.ticker = s.ticker;
|
|
13548
|
+
const venue = s.prefix ?? s.provider;
|
|
13549
|
+
if (venue) row.dataset.venue = venue;
|
|
13550
|
+
const av = tickerIconEl(doc, baseOf(s), s.ticker, "vela-sp-avatar");
|
|
13551
|
+
const main = doc.createElement("span");
|
|
13552
|
+
main.className = "vela-sp-main";
|
|
13553
|
+
const t = doc.createElement("span");
|
|
13554
|
+
t.className = "vela-sp-ticker";
|
|
13555
|
+
t.textContent = s.ticker;
|
|
13556
|
+
const d = doc.createElement("span");
|
|
13557
|
+
d.className = "vela-sp-desc";
|
|
13558
|
+
d.textContent = s.description ?? (s.type ?? "");
|
|
13559
|
+
main.append(t, d);
|
|
13560
|
+
row.append(av, main);
|
|
13561
|
+
if (venue) {
|
|
13562
|
+
const badge = doc.createElement("span");
|
|
13563
|
+
badge.className = "vela-sp-badge";
|
|
13564
|
+
badge.dataset.p = s.provider ?? venue;
|
|
13565
|
+
badge.textContent = venue;
|
|
13566
|
+
row.appendChild(badge);
|
|
13567
|
+
}
|
|
13568
|
+
return row;
|
|
13569
|
+
}
|
|
13486
13570
|
};
|
|
13487
13571
|
|
|
13488
13572
|
// src/widget/indicator-picker.ts
|
|
@@ -16169,7 +16253,7 @@ function attachChromeTooltip(anchor, opts) {
|
|
|
16169
16253
|
} else {
|
|
16170
16254
|
const left = Math.min(a.left - h.left, Math.max(0, h.width - tip.offsetWidth - 4));
|
|
16171
16255
|
tip.style.left = `${Math.max(0, left)}px`;
|
|
16172
|
-
tip.style.top = `${a.bottom - h.top + 6}px`;
|
|
16256
|
+
tip.style.top = opts.placement === "above" ? `${Math.max(0, a.top - h.top - tip.offsetHeight - 6)}px` : `${a.bottom - h.top + 6}px`;
|
|
16173
16257
|
}
|
|
16174
16258
|
};
|
|
16175
16259
|
const arm = (e) => {
|
|
@@ -19342,6 +19426,13 @@ var DrawingsControl = class {
|
|
|
19342
19426
|
}
|
|
19343
19427
|
};
|
|
19344
19428
|
|
|
19429
|
+
// src/core/model/inputs.ts
|
|
19430
|
+
function inputVisible(when, values) {
|
|
19431
|
+
if (!when) return true;
|
|
19432
|
+
const conds = Array.isArray(when) ? when : [when];
|
|
19433
|
+
return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
|
|
19434
|
+
}
|
|
19435
|
+
|
|
19345
19436
|
// src/renderers/shared/IndicatorInputsDialog.ts
|
|
19346
19437
|
var SOURCES = ["close", "open", "high", "low", "hl2", "hlc3", "ohlc4", "volume"];
|
|
19347
19438
|
var IndicatorInputsDialog = class {
|
|
@@ -19354,6 +19445,8 @@ var IndicatorInputsDialog = class {
|
|
|
19354
19445
|
this.row = null;
|
|
19355
19446
|
this.snapshot = null;
|
|
19356
19447
|
this.dialogTips = [];
|
|
19448
|
+
/** Re-applies every `when` gate against current values; set while the dialog is open. */
|
|
19449
|
+
this.refreshVisibility = null;
|
|
19357
19450
|
this.choicePop = null;
|
|
19358
19451
|
this.calendarPop = null;
|
|
19359
19452
|
this.calendarAnchor = null;
|
|
@@ -19416,14 +19509,17 @@ var IndicatorInputsDialog = class {
|
|
|
19416
19509
|
tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
|
|
19417
19510
|
const tabEls = [];
|
|
19418
19511
|
const bodies = [];
|
|
19512
|
+
const gates = [];
|
|
19513
|
+
let active = 0;
|
|
19419
19514
|
const activate = (idx) => {
|
|
19515
|
+
active = idx;
|
|
19420
19516
|
for (let i = 0; i < tabEls.length; i += 1) {
|
|
19421
|
-
const
|
|
19517
|
+
const on = i === idx;
|
|
19422
19518
|
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 =
|
|
19519
|
+
el.style.borderBottomColor = on ? fg : "transparent";
|
|
19520
|
+
el.style.color = on ? "inherit" : "var(--vela-fg-muted)";
|
|
19521
|
+
el.classList.toggle("vela-ind-tab-active", on);
|
|
19522
|
+
bodies[i].style.display = on ? "grid" : "none";
|
|
19427
19523
|
}
|
|
19428
19524
|
};
|
|
19429
19525
|
for (const [idx, def] of tabDefs.entries()) {
|
|
@@ -19434,11 +19530,24 @@ var IndicatorInputsDialog = class {
|
|
|
19434
19530
|
tab.addEventListener("click", () => activate(idx));
|
|
19435
19531
|
tabs.appendChild(tab);
|
|
19436
19532
|
tabEls.push(tab);
|
|
19437
|
-
bodies.push(this.buildTabBody(def.inputs, row));
|
|
19533
|
+
bodies.push(this.buildTabBody(def.inputs, row, gates));
|
|
19534
|
+
if (def.inputs.every((d) => d.when)) {
|
|
19535
|
+
gates.push({ el: tab, visible: (bag) => def.inputs.some((d) => inputVisible(d.when, bag)) });
|
|
19536
|
+
}
|
|
19438
19537
|
}
|
|
19439
19538
|
ui.body.appendChild(tabs);
|
|
19440
19539
|
for (const b of bodies) ui.body.appendChild(b);
|
|
19441
19540
|
activate(0);
|
|
19541
|
+
const refresh = () => {
|
|
19542
|
+
const bag = valuesBag(row);
|
|
19543
|
+
for (const g of gates) g.el.style.display = g.visible(bag) ? "" : "none";
|
|
19544
|
+
if (tabEls[active]?.style.display === "none") {
|
|
19545
|
+
const next = tabEls.findIndex((el) => el.style.display !== "none");
|
|
19546
|
+
if (next >= 0) activate(next);
|
|
19547
|
+
}
|
|
19548
|
+
};
|
|
19549
|
+
this.refreshVisibility = refresh;
|
|
19550
|
+
refresh();
|
|
19442
19551
|
const onBackdropDown = (e) => {
|
|
19443
19552
|
if (e.target !== ui.backdrop && e.target !== ui.positioner) return;
|
|
19444
19553
|
if (isPopoverOpen() || eventDismissedPopover(e)) {
|
|
@@ -19458,16 +19567,27 @@ var IndicatorInputsDialog = class {
|
|
|
19458
19567
|
}
|
|
19459
19568
|
/** One tab's scrollable body — one shared grid so every group's controls share a
|
|
19460
19569
|
* left edge (a 100px number field lines up with a wider session/time pair). */
|
|
19461
|
-
buildTabBody(inputs, row) {
|
|
19570
|
+
buildTabBody(inputs, row, gates) {
|
|
19462
19571
|
const body = document.createElement("div");
|
|
19463
19572
|
body.style.cssText = inputDialogBodyStyle(this.host.mobile());
|
|
19464
19573
|
for (const group of groupInputs(inputs)) {
|
|
19465
19574
|
const section = document.createElement("div");
|
|
19466
19575
|
section.style.cssText = "display:contents;";
|
|
19576
|
+
let header = null;
|
|
19467
19577
|
if (group.name) {
|
|
19468
|
-
|
|
19578
|
+
header = fieldSection(group.name, { variant: "inputs", first: body.childElementCount === 0 });
|
|
19579
|
+
section.appendChild(header);
|
|
19580
|
+
}
|
|
19581
|
+
for (const inputRow of group.rows) {
|
|
19582
|
+
const el = this.buildInputRow(section, inputRow, row);
|
|
19583
|
+
if (inputRow.some((d) => d.when)) {
|
|
19584
|
+
gates.push({ el, visible: (bag) => rowVisible(inputRow, bag) });
|
|
19585
|
+
}
|
|
19586
|
+
}
|
|
19587
|
+
if (header && group.rows.every((r) => r.every((d) => d.when))) {
|
|
19588
|
+
const rows = group.rows;
|
|
19589
|
+
gates.push({ el: header, visible: (bag) => rows.some((r) => rowVisible(r, bag)) });
|
|
19469
19590
|
}
|
|
19470
|
-
for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
|
|
19471
19591
|
body.appendChild(section);
|
|
19472
19592
|
}
|
|
19473
19593
|
return body;
|
|
@@ -19475,6 +19595,7 @@ var IndicatorInputsDialog = class {
|
|
|
19475
19595
|
close() {
|
|
19476
19596
|
document.removeEventListener("keydown", this.onDialogKey);
|
|
19477
19597
|
closeOpenPopovers();
|
|
19598
|
+
this.refreshVisibility = null;
|
|
19478
19599
|
this.choicePop = null;
|
|
19479
19600
|
this.calendarPop = null;
|
|
19480
19601
|
this.calendarAnchor = null;
|
|
@@ -19504,11 +19625,21 @@ var IndicatorInputsDialog = class {
|
|
|
19504
19625
|
}
|
|
19505
19626
|
this.close();
|
|
19506
19627
|
}
|
|
19628
|
+
/** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
|
|
19629
|
+
commit(row, key, value) {
|
|
19630
|
+
row.values[key] = value;
|
|
19631
|
+
this.host.onChange?.({ indicatorId: row.id, key, value });
|
|
19632
|
+
this.refreshVisibility?.();
|
|
19633
|
+
}
|
|
19507
19634
|
/** One settings row (or several `inline=` inputs) placed into a section's grid. */
|
|
19508
19635
|
buildInputRow(grid, decls, row) {
|
|
19509
19636
|
const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
|
|
19637
|
+
const append = (el) => {
|
|
19638
|
+
grid.appendChild(el);
|
|
19639
|
+
return el;
|
|
19640
|
+
};
|
|
19510
19641
|
if (decls.length > 1) {
|
|
19511
|
-
|
|
19642
|
+
return append(fieldRow({
|
|
19512
19643
|
label: "",
|
|
19513
19644
|
inline: decls.map((d) => ({
|
|
19514
19645
|
label: nameOf(d) || void 0,
|
|
@@ -19519,14 +19650,13 @@ var IndicatorInputsDialog = class {
|
|
|
19519
19650
|
})),
|
|
19520
19651
|
info: [...decls].reverse().find((d) => d.tooltip)?.tooltip ? this.infoButton([...decls].reverse().find((d) => d.tooltip).tooltip) : void 0
|
|
19521
19652
|
}));
|
|
19522
|
-
return;
|
|
19523
19653
|
}
|
|
19524
19654
|
const lead = decls[0];
|
|
19525
19655
|
const id = idOf(lead);
|
|
19526
19656
|
const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
|
|
19527
19657
|
if (lead.type === "bool") {
|
|
19528
19658
|
const current = Boolean(row.values[lead.key] ?? lead.defval);
|
|
19529
|
-
|
|
19659
|
+
return append(fieldRow({
|
|
19530
19660
|
label: nameOf(lead),
|
|
19531
19661
|
id,
|
|
19532
19662
|
bool: true,
|
|
@@ -19534,26 +19664,21 @@ var IndicatorInputsDialog = class {
|
|
|
19534
19664
|
toggle: {
|
|
19535
19665
|
id,
|
|
19536
19666
|
checked: current,
|
|
19537
|
-
onChange: (v) =>
|
|
19538
|
-
row.values[lead.key] = v;
|
|
19539
|
-
this.host.onChange?.({ indicatorId: row.id, key: lead.key, value: v });
|
|
19540
|
-
}
|
|
19667
|
+
onChange: (v) => this.commit(row, lead.key, v)
|
|
19541
19668
|
}
|
|
19542
19669
|
}));
|
|
19543
|
-
return;
|
|
19544
19670
|
}
|
|
19545
19671
|
if (lead.type === "text_area") {
|
|
19546
|
-
|
|
19672
|
+
return append(fieldRow({
|
|
19547
19673
|
label: nameOf(lead),
|
|
19548
19674
|
id,
|
|
19549
19675
|
stacked: true,
|
|
19550
19676
|
info,
|
|
19551
19677
|
control: this.buildControl(row, lead, id)
|
|
19552
19678
|
}));
|
|
19553
|
-
return;
|
|
19554
19679
|
}
|
|
19555
19680
|
const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
|
|
19556
|
-
|
|
19681
|
+
return append(fieldRow({
|
|
19557
19682
|
label: nameOf(lead),
|
|
19558
19683
|
id,
|
|
19559
19684
|
fit,
|
|
@@ -19565,10 +19690,7 @@ var IndicatorInputsDialog = class {
|
|
|
19565
19690
|
/** Build the typed control for one input, committing edits live via `onChange`. */
|
|
19566
19691
|
buildControl(row, inp, id) {
|
|
19567
19692
|
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
|
-
};
|
|
19693
|
+
const emit = (value) => this.commit(row, inp.key, value);
|
|
19572
19694
|
if (inp.type === "bool") {
|
|
19573
19695
|
return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
|
|
19574
19696
|
}
|
|
@@ -19989,6 +20111,14 @@ function tabInputs(inputs) {
|
|
|
19989
20111
|
}
|
|
19990
20112
|
return names.map((name) => ({ name, inputs: byTab.get(name) }));
|
|
19991
20113
|
}
|
|
20114
|
+
function rowVisible(decls, bag) {
|
|
20115
|
+
return decls.some((d) => inputVisible(d.when, bag));
|
|
20116
|
+
}
|
|
20117
|
+
function valuesBag(row) {
|
|
20118
|
+
const bag = {};
|
|
20119
|
+
for (const inp of row.inputs) bag[inp.key] = row.values[inp.key] ?? inp.defval;
|
|
20120
|
+
return bag;
|
|
20121
|
+
}
|
|
19992
20122
|
function groupInputs(inputs) {
|
|
19993
20123
|
const order = [];
|
|
19994
20124
|
const byGroup = /* @__PURE__ */ new Map();
|
|
@@ -20451,6 +20581,8 @@ var InputsUI = class {
|
|
|
20451
20581
|
host: this.container,
|
|
20452
20582
|
items,
|
|
20453
20583
|
placement: "bottom-start",
|
|
20584
|
+
// Right-click action menu — checked state reads as a leading ✓ (see the kit Menu).
|
|
20585
|
+
checkmarks: true,
|
|
20454
20586
|
onSelect: (itemId) => {
|
|
20455
20587
|
if (itemId !== "values" || !this.rowMenuId) return;
|
|
20456
20588
|
const target = this.rows.get(this.rowMenuId);
|
|
@@ -20786,6 +20918,8 @@ var InputsUI = class {
|
|
|
20786
20918
|
syncRowActions(row) {
|
|
20787
20919
|
const open2 = row.highlighted;
|
|
20788
20920
|
row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
|
|
20921
|
+
if (open2) row.el.appendChild(row.statusEl);
|
|
20922
|
+
else row.el.insertBefore(row.statusEl, row.valuesEl);
|
|
20789
20923
|
for (const child of Array.from(row.controlsEl.children)) {
|
|
20790
20924
|
if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
|
|
20791
20925
|
if (child === row.extrasEl) {
|
|
@@ -21029,6 +21163,148 @@ var PaneControls = class {
|
|
|
21029
21163
|
}
|
|
21030
21164
|
};
|
|
21031
21165
|
|
|
21166
|
+
// src/renderers/native/chrome/axisLayout.ts
|
|
21167
|
+
var AXIS_MASTER_W = 64;
|
|
21168
|
+
var AXIS_MERGED_W = 56;
|
|
21169
|
+
var PANE_SEPARATOR_PX = 3;
|
|
21170
|
+
function axisColumnX(dataW, column) {
|
|
21171
|
+
return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
|
|
21172
|
+
}
|
|
21173
|
+
|
|
21174
|
+
// src/renderers/native/chrome/AxisScaleButtons.ts
|
|
21175
|
+
var STYLE_ID22 = "vela-axis-scale-buttons";
|
|
21176
|
+
var BTN_PX = 18;
|
|
21177
|
+
var BTN_GAP = 4;
|
|
21178
|
+
var AXIS_BORDER = 1;
|
|
21179
|
+
var CLUSTER_W = AXIS_MASTER_W - AXIS_BORDER;
|
|
21180
|
+
var PAD_TOP = 8;
|
|
21181
|
+
var PAD_BOTTOM = 6;
|
|
21182
|
+
var CLUSTER_H = BTN_PX + PAD_TOP + PAD_BOTTOM;
|
|
21183
|
+
var MIN_PANE_H = 48;
|
|
21184
|
+
function ensureStyles4() {
|
|
21185
|
+
if (typeof document === "undefined" || document.getElementById(STYLE_ID22)) return;
|
|
21186
|
+
const st = document.createElement("style");
|
|
21187
|
+
st.id = STYLE_ID22;
|
|
21188
|
+
st.textContent = `
|
|
21189
|
+
.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);}
|
|
21190
|
+
.vela-axis-btn:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
|
|
21191
|
+
.vela-axis-on,.vela-axis-on:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
|
|
21192
|
+
`;
|
|
21193
|
+
document.head.appendChild(st);
|
|
21194
|
+
}
|
|
21195
|
+
var AxisScaleButtons = class {
|
|
21196
|
+
constructor(plot, theme, deps) {
|
|
21197
|
+
this.plot = plot;
|
|
21198
|
+
this.theme = theme;
|
|
21199
|
+
this.deps = deps;
|
|
21200
|
+
this.tipDisposers = [];
|
|
21201
|
+
this.hoverPaneId = null;
|
|
21202
|
+
/** Reveal over the master scale column of the pane under the cursor (mouse only —
|
|
21203
|
+
* touch has no hover, and a drag-rescale on the axis must not sprout buttons). */
|
|
21204
|
+
this.onPlotMove = (e) => {
|
|
21205
|
+
if (e.pointerType !== "mouse") return;
|
|
21206
|
+
const rect = this.plot.getBoundingClientRect();
|
|
21207
|
+
const x = e.clientX - rect.left;
|
|
21208
|
+
const gutterLeft = rect.width - this.deps.rightAxis();
|
|
21209
|
+
let hit = null;
|
|
21210
|
+
if (x >= gutterLeft && x <= gutterLeft + AXIS_MASTER_W) {
|
|
21211
|
+
const y = e.clientY - rect.top;
|
|
21212
|
+
for (const p of this.deps.panes()) {
|
|
21213
|
+
if (p.collapsed || p.height < MIN_PANE_H) continue;
|
|
21214
|
+
if (y >= p.top && y <= p.top + p.height) {
|
|
21215
|
+
hit = p.id;
|
|
21216
|
+
break;
|
|
21217
|
+
}
|
|
21218
|
+
}
|
|
21219
|
+
}
|
|
21220
|
+
this.setHoverPane(hit);
|
|
21221
|
+
if (hit) this.sync();
|
|
21222
|
+
};
|
|
21223
|
+
this.onPlotLeave = () => this.setHoverPane(null);
|
|
21224
|
+
ensureStyles4();
|
|
21225
|
+
this.cluster = document.createElement("div");
|
|
21226
|
+
Object.assign(this.cluster.style, {
|
|
21227
|
+
position: "absolute",
|
|
21228
|
+
display: "none",
|
|
21229
|
+
justifyContent: "center",
|
|
21230
|
+
alignItems: "center",
|
|
21231
|
+
gap: `${BTN_GAP}px`,
|
|
21232
|
+
padding: `${PAD_TOP}px 0 ${PAD_BOTTOM}px`,
|
|
21233
|
+
width: `${CLUSTER_W}px`,
|
|
21234
|
+
boxSizing: "border-box",
|
|
21235
|
+
zIndex: "6",
|
|
21236
|
+
pointerEvents: "auto"
|
|
21237
|
+
});
|
|
21238
|
+
this.applyTheme();
|
|
21239
|
+
this.autoBtn = this.button("A", "Auto (fits data to screen)", () => this.deps.onToggleAuto(this.hoverPaneId ?? ""));
|
|
21240
|
+
this.logBtn = this.button("L", "Logarithmic scale", () => this.deps.onToggleLog(this.hoverPaneId ?? ""));
|
|
21241
|
+
this.cluster.append(this.autoBtn, this.logBtn);
|
|
21242
|
+
this.plot.appendChild(this.cluster);
|
|
21243
|
+
this.plot.addEventListener("pointermove", this.onPlotMove);
|
|
21244
|
+
this.plot.addEventListener("pointerleave", this.onPlotLeave);
|
|
21245
|
+
}
|
|
21246
|
+
button(label, title, onClick) {
|
|
21247
|
+
const b = document.createElement("button");
|
|
21248
|
+
b.type = "button";
|
|
21249
|
+
b.setAttribute("aria-label", title);
|
|
21250
|
+
b.textContent = label;
|
|
21251
|
+
b.className = "vela-axis-btn";
|
|
21252
|
+
Object.assign(b.style, { width: `${BTN_PX}px`, height: `${BTN_PX}px` });
|
|
21253
|
+
this.tipDisposers.push(attachChromeTooltip(b, {
|
|
21254
|
+
host: this.plot,
|
|
21255
|
+
theme: () => this.theme,
|
|
21256
|
+
text: () => title,
|
|
21257
|
+
placement: "above"
|
|
21258
|
+
}));
|
|
21259
|
+
b.addEventListener("click", (e) => {
|
|
21260
|
+
e.stopPropagation();
|
|
21261
|
+
onClick();
|
|
21262
|
+
this.sync();
|
|
21263
|
+
});
|
|
21264
|
+
return b;
|
|
21265
|
+
}
|
|
21266
|
+
/** Re-tint the chip when the plot background/border change (theme swap, config edit). */
|
|
21267
|
+
setTheme(theme) {
|
|
21268
|
+
this.theme = theme;
|
|
21269
|
+
this.applyTheme();
|
|
21270
|
+
}
|
|
21271
|
+
/** The strip is SOLID chart background: the buttons cover axis labels, never blend with them. */
|
|
21272
|
+
applyTheme() {
|
|
21273
|
+
this.cluster.style.background = this.theme.background;
|
|
21274
|
+
}
|
|
21275
|
+
setHoverPane(paneId) {
|
|
21276
|
+
if (this.hoverPaneId === paneId) return;
|
|
21277
|
+
this.hoverPaneId = paneId;
|
|
21278
|
+
this.reposition();
|
|
21279
|
+
}
|
|
21280
|
+
/** Re-place (or hide) the cluster after a hover or pane-layout change. */
|
|
21281
|
+
reposition() {
|
|
21282
|
+
const pane = this.hoverPaneId ? this.deps.panes().find((p) => p.id === this.hoverPaneId) : void 0;
|
|
21283
|
+
if (!pane || pane.collapsed || pane.height < MIN_PANE_H) {
|
|
21284
|
+
this.cluster.style.display = "none";
|
|
21285
|
+
return;
|
|
21286
|
+
}
|
|
21287
|
+
this.cluster.style.right = `${this.deps.rightAxis() - AXIS_MASTER_W}px`;
|
|
21288
|
+
this.cluster.style.top = `${pane.top + pane.height - CLUSTER_H}px`;
|
|
21289
|
+
this.cluster.style.display = "flex";
|
|
21290
|
+
this.sync();
|
|
21291
|
+
}
|
|
21292
|
+
/** Reflect the hovered pane's current auto/log state on the buttons. */
|
|
21293
|
+
sync() {
|
|
21294
|
+
const pane = this.deps.panes().find((p) => p.id === this.hoverPaneId);
|
|
21295
|
+
if (!pane) return;
|
|
21296
|
+
this.autoBtn.classList.toggle("vela-axis-on", pane.auto);
|
|
21297
|
+
this.logBtn.classList.toggle("vela-axis-on", pane.log);
|
|
21298
|
+
}
|
|
21299
|
+
destroy() {
|
|
21300
|
+
this.plot.removeEventListener("pointermove", this.onPlotMove);
|
|
21301
|
+
this.plot.removeEventListener("pointerleave", this.onPlotLeave);
|
|
21302
|
+
for (const dispose of this.tipDisposers) dispose();
|
|
21303
|
+
this.tipDisposers.length = 0;
|
|
21304
|
+
this.cluster.remove();
|
|
21305
|
+
}
|
|
21306
|
+
};
|
|
21307
|
+
|
|
21032
21308
|
// src/renderers/shared/TableOverlay.ts
|
|
21033
21309
|
var SIZE_PX3 = {
|
|
21034
21310
|
auto: 13,
|
|
@@ -21613,6 +21889,8 @@ var SceneGraph = class {
|
|
|
21613
21889
|
this.tradeMarkers = defaultTradeMarkersState();
|
|
21614
21890
|
/** Renderer-owned shaded time bands (session highlighting), behind grid + data. */
|
|
21615
21891
|
this.highlights = [];
|
|
21892
|
+
/** Pre/post-market bands pushed by the host (`sessionZones` feature); null ⇒ no sessions. */
|
|
21893
|
+
this.sessionZones = null;
|
|
21616
21894
|
/** Draw-order key of the price candles, relative to indicator series z (see `seriesZ`).
|
|
21617
21895
|
* Indicators with z below this draw BEHIND the candles; at/above draw in front.
|
|
21618
21896
|
* Default 0 with indicators mounting at z < 0 ⇒ the price reads on top of every overlay,
|
|
@@ -21647,6 +21925,15 @@ var SceneGraph = class {
|
|
|
21647
21925
|
if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
|
|
21648
21926
|
return this.orderedCache;
|
|
21649
21927
|
}
|
|
21928
|
+
/** The session zones resolved into colored bands (pre/post-market washes from the
|
|
21929
|
+
* config's session colors) — consumed by the same painting path as {@link highlights}. */
|
|
21930
|
+
sessionHighlightBands() {
|
|
21931
|
+
if (!this.sessionZones) return [];
|
|
21932
|
+
const out = [];
|
|
21933
|
+
for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
|
|
21934
|
+
for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
|
|
21935
|
+
return out;
|
|
21936
|
+
}
|
|
21650
21937
|
indicatorsForPane(paneId) {
|
|
21651
21938
|
const out = [];
|
|
21652
21939
|
for (const model of this.indicators.values()) if (model.paneId === paneId) out.push(model);
|
|
@@ -21730,6 +22017,12 @@ var SceneGraph = class {
|
|
|
21730
22017
|
assignIndicatorZ(id) {
|
|
21731
22018
|
if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.bottomZ() - 1);
|
|
21732
22019
|
}
|
|
22020
|
+
/** Mount-time default for a LAYER-BACKED native (it paints on a canvas stacked above the
|
|
22021
|
+
* data canvas by default): top of the stack, so the recorded order tells the truth from
|
|
22022
|
+
* the first frame. Keeps an existing key, so a restored stack survives the remount. */
|
|
22023
|
+
assignIndicatorZTop(id) {
|
|
22024
|
+
if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.topZ() + 1);
|
|
22025
|
+
}
|
|
21733
22026
|
forgetIndicatorZ(id) {
|
|
21734
22027
|
this.seriesZ.delete(id);
|
|
21735
22028
|
}
|
|
@@ -22577,10 +22870,12 @@ var WebGL2Backend = class {
|
|
|
22577
22870
|
}
|
|
22578
22871
|
}
|
|
22579
22872
|
}
|
|
22580
|
-
/** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
|
|
22873
|
+
/** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
|
|
22874
|
+
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
22581
22875
|
emitHighlights(b, scene, pane, coords) {
|
|
22582
|
-
|
|
22583
|
-
|
|
22876
|
+
const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
|
|
22877
|
+
if (bands.length === 0) return;
|
|
22878
|
+
for (const band of bands) {
|
|
22584
22879
|
const x1 = coords.timeToX(band.from);
|
|
22585
22880
|
const x2 = coords.timeToX(band.to);
|
|
22586
22881
|
if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
|
|
@@ -24554,10 +24849,12 @@ var Canvas2dBackend = class {
|
|
|
24554
24849
|
ctx.fillStyle = bg.color;
|
|
24555
24850
|
ctx.fillRect(x1, pane.bounds.top, x2 - x1, pane.bounds.height);
|
|
24556
24851
|
}
|
|
24557
|
-
/** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
|
|
24852
|
+
/** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
|
|
24853
|
+
* Session-zone washes (pre/post-market) paint first, host highlights on top. */
|
|
24558
24854
|
drawHighlights(ctx, scene, coords) {
|
|
24559
|
-
|
|
24560
|
-
|
|
24855
|
+
const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
|
|
24856
|
+
if (bands.length === 0) return;
|
|
24857
|
+
for (const band of bands) {
|
|
24561
24858
|
const x1 = coords.timeToX(band.from);
|
|
24562
24859
|
const x2 = coords.timeToX(band.to);
|
|
24563
24860
|
if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
|
|
@@ -25331,14 +25628,6 @@ var DrawingSceneRenderer = class {
|
|
|
25331
25628
|
}
|
|
25332
25629
|
};
|
|
25333
25630
|
|
|
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
25631
|
// src/renderers/native/chrome/ChromeRenderer.ts
|
|
25343
25632
|
var ChromeRenderer = class {
|
|
25344
25633
|
constructor() {
|
|
@@ -26144,6 +26433,7 @@ var SettingsDialog = class {
|
|
|
26144
26433
|
for (const hr of hs.rows) {
|
|
26145
26434
|
if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
|
|
26146
26435
|
else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
26436
|
+
else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
|
|
26147
26437
|
else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options.map((o) => [o, o]), (v) => hr.set(v)));
|
|
26148
26438
|
}
|
|
26149
26439
|
}
|
|
@@ -28855,14 +29145,14 @@ function btn(label, primary, onClick) {
|
|
|
28855
29145
|
// src/renderers/native/drawings/DrawingSettingsPopup.ts
|
|
28856
29146
|
var BTN = 30;
|
|
28857
29147
|
var ICON = 21;
|
|
28858
|
-
var
|
|
29148
|
+
var STYLE_ID23 = "vela-drawing-popup-styles";
|
|
28859
29149
|
var STYLE_REV = "3";
|
|
28860
|
-
function
|
|
29150
|
+
function ensureStyles5() {
|
|
28861
29151
|
if (typeof document === "undefined") return;
|
|
28862
|
-
const existing = document.getElementById(
|
|
29152
|
+
const existing = document.getElementById(STYLE_ID23);
|
|
28863
29153
|
if (existing?.dataset.rev === STYLE_REV) return;
|
|
28864
29154
|
const s = existing ?? document.createElement("style");
|
|
28865
|
-
s.id =
|
|
29155
|
+
s.id = STYLE_ID23;
|
|
28866
29156
|
s.dataset.rev = STYLE_REV;
|
|
28867
29157
|
s.textContent = `
|
|
28868
29158
|
.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 +29209,7 @@ var DrawingSettingsPopup = class {
|
|
|
28919
29209
|
* `onClose` fires on an outside-click dismissal (not a programmatic close). */
|
|
28920
29210
|
open(drawing, anchor, actions, onClose) {
|
|
28921
29211
|
this.close();
|
|
28922
|
-
|
|
29212
|
+
ensureStyles5();
|
|
28923
29213
|
this.onClose = onClose ?? null;
|
|
28924
29214
|
const t = this.theme;
|
|
28925
29215
|
const schema = drawing.schema();
|
|
@@ -30771,6 +31061,19 @@ function rendererLayers() {
|
|
|
30771
31061
|
return [...registry4.values()];
|
|
30772
31062
|
}
|
|
30773
31063
|
|
|
31064
|
+
// src/renderers/native/core/layerStacking.ts
|
|
31065
|
+
function stackLayers(entries, candleZ) {
|
|
31066
|
+
const keyed = entries.map((e) => ({
|
|
31067
|
+
id: e.id,
|
|
31068
|
+
key: e.ownerZ ?? (e.placement === "below-data" ? -Infinity : candleZ)
|
|
31069
|
+
}));
|
|
31070
|
+
keyed.sort((a, b) => a.key - b.key);
|
|
31071
|
+
const below = [];
|
|
31072
|
+
const above = [];
|
|
31073
|
+
for (const k of keyed) (k.key < candleZ ? below : above).push(k.id);
|
|
31074
|
+
return { below, above };
|
|
31075
|
+
}
|
|
31076
|
+
|
|
30774
31077
|
// src/renderers/shared/dom-raster.ts
|
|
30775
31078
|
function hasInk(color) {
|
|
30776
31079
|
if (!color || color === "transparent") return false;
|
|
@@ -31143,6 +31446,8 @@ var NativeRenderer = class {
|
|
|
31143
31446
|
this.volumeRenderer = new VolumeRenderer();
|
|
31144
31447
|
/** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
|
|
31145
31448
|
this.extLayers = [];
|
|
31449
|
+
/** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
|
|
31450
|
+
this.layerOrderSig = "";
|
|
31146
31451
|
// The attribution mark (see chrome/AttributionMark + the NOTICE file): default-on;
|
|
31147
31452
|
// disabling requires an equivalent visible attribution elsewhere in the host UI.
|
|
31148
31453
|
this.attributionEl = null;
|
|
@@ -31257,6 +31562,8 @@ var NativeRenderer = class {
|
|
|
31257
31562
|
// total right-gutter width (master column + merged-scale columns)
|
|
31258
31563
|
this.paneControls = null;
|
|
31259
31564
|
// per-pane hover button cluster (top-right)
|
|
31565
|
+
this.axisScaleButtons = null;
|
|
31566
|
+
// A/L hover buttons at the bottom of each pane's price scale
|
|
31260
31567
|
this.paneActionCbs = /* @__PURE__ */ new Set();
|
|
31261
31568
|
// ── data window (crosshair OHLC + per-series readout, pulled by host panels) ──
|
|
31262
31569
|
this.hoverLogical = null;
|
|
@@ -31300,7 +31607,7 @@ var NativeRenderer = class {
|
|
|
31300
31607
|
this.priceStyleCbs = /* @__PURE__ */ new Set();
|
|
31301
31608
|
this.tableOverlays = /* @__PURE__ */ new Map();
|
|
31302
31609
|
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"];
|
|
31610
|
+
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
31611
|
/** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
|
|
31305
31612
|
* so moving onto the button doesn't count as leaving). */
|
|
31306
31613
|
this.onScrollProximityMove = (e) => {
|
|
@@ -31416,6 +31723,9 @@ var NativeRenderer = class {
|
|
|
31416
31723
|
case "highlights":
|
|
31417
31724
|
this.scene.highlights = sanitizeHighlights(value);
|
|
31418
31725
|
break;
|
|
31726
|
+
case "sessionZones":
|
|
31727
|
+
this.scene.sessionZones = sanitizeSessionZones(value);
|
|
31728
|
+
break;
|
|
31419
31729
|
case "gridlines":
|
|
31420
31730
|
this.scene.showGrid = Boolean(value);
|
|
31421
31731
|
break;
|
|
@@ -31532,6 +31842,8 @@ var NativeRenderer = class {
|
|
|
31532
31842
|
return this.scene.indicatorZOrder();
|
|
31533
31843
|
case "highlights":
|
|
31534
31844
|
return this.scene.highlights;
|
|
31845
|
+
case "sessionZones":
|
|
31846
|
+
return this.scene.sessionZones;
|
|
31535
31847
|
case "gridlines":
|
|
31536
31848
|
return this.scene.showGrid;
|
|
31537
31849
|
case "axisLabels":
|
|
@@ -31741,7 +32053,8 @@ var NativeRenderer = class {
|
|
|
31741
32053
|
baselineLevel: s.baseline.baselineLevel
|
|
31742
32054
|
};
|
|
31743
32055
|
})(),
|
|
31744
|
-
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale }
|
|
32056
|
+
series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
|
|
32057
|
+
sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
|
|
31745
32058
|
};
|
|
31746
32059
|
}
|
|
31747
32060
|
/**
|
|
@@ -31823,6 +32136,7 @@ var NativeRenderer = class {
|
|
|
31823
32136
|
width: next.baseline.width,
|
|
31824
32137
|
baselineLevel: next.baseline.baselineLevel
|
|
31825
32138
|
};
|
|
32139
|
+
s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
|
|
31826
32140
|
this.setPriceStyle(next.series.style);
|
|
31827
32141
|
this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
|
|
31828
32142
|
this.scene.baselineValue = next.series.baseline;
|
|
@@ -31836,6 +32150,7 @@ var NativeRenderer = class {
|
|
|
31836
32150
|
}
|
|
31837
32151
|
this.inputsUI?.setTheme(this.theme);
|
|
31838
32152
|
this.paneControls?.setTheme(this.theme);
|
|
32153
|
+
this.axisScaleButtons?.setTheme(this.theme);
|
|
31839
32154
|
this.userDrawings?.setTheme(this.chromeTheme());
|
|
31840
32155
|
this.refreshScrollButtonTheme();
|
|
31841
32156
|
this.refreshAttributionColor();
|
|
@@ -32063,9 +32378,7 @@ var NativeRenderer = class {
|
|
|
32063
32378
|
if (el.getAttribute("data-vela-screenshot") === "under") rasterizeOverlay(ctx, el, frame);
|
|
32064
32379
|
}
|
|
32065
32380
|
}
|
|
32066
|
-
const
|
|
32067
|
-
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
32068
|
-
for (const canvas of [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas]) {
|
|
32381
|
+
for (const canvas of [...this.canvasPile(), this.chromeCanvas, this.drawingsCanvas]) {
|
|
32069
32382
|
if (canvas && canvas.width > 0 && canvas.height > 0) ctx.drawImage(canvas, 0, 0);
|
|
32070
32383
|
}
|
|
32071
32384
|
if (frame) {
|
|
@@ -32174,7 +32487,7 @@ var NativeRenderer = class {
|
|
|
32174
32487
|
this.surfaceBackground = theme.background;
|
|
32175
32488
|
this.surfaceTextColor = theme.textColor;
|
|
32176
32489
|
this.wrapper = document.createElement("div");
|
|
32177
|
-
Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair" });
|
|
32490
|
+
Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair", userSelect: "none", webkitUserSelect: "none" });
|
|
32178
32491
|
applyChromeTokens(this.wrapper, this.chromeTheme());
|
|
32179
32492
|
this.volumeCanvas = document.createElement("canvas");
|
|
32180
32493
|
Object.assign(this.volumeCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
|
|
@@ -32199,6 +32512,7 @@ var NativeRenderer = class {
|
|
|
32199
32512
|
const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
|
|
32200
32513
|
const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
|
|
32201
32514
|
this.plot.append(...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
|
|
32515
|
+
this.layerOrderSig = "";
|
|
32202
32516
|
this.wrapper.appendChild(this.plot);
|
|
32203
32517
|
this.factoryConfig = this.getConfig();
|
|
32204
32518
|
this.attributionEl = this.buildAttributionEl();
|
|
@@ -32343,6 +32657,15 @@ var NativeRenderer = class {
|
|
|
32343
32657
|
this.emitPaneAction({ type: "maximize", paneId, maximized });
|
|
32344
32658
|
}
|
|
32345
32659
|
});
|
|
32660
|
+
this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
|
|
32661
|
+
panes: () => this.axisScaleViews(),
|
|
32662
|
+
rightAxis: () => this.rightAxisW,
|
|
32663
|
+
onToggleAuto: (paneId) => this.togglePaneAuto(paneId),
|
|
32664
|
+
onToggleLog: (paneId) => {
|
|
32665
|
+
const pane = this.scene.panes.get(paneId);
|
|
32666
|
+
if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
|
|
32667
|
+
}
|
|
32668
|
+
});
|
|
32346
32669
|
this.resizeObserver = new ResizeObserver(() => this.resize());
|
|
32347
32670
|
this.resizeObserver.observe(this.wrapper);
|
|
32348
32671
|
this.watchDpr();
|
|
@@ -32419,6 +32742,7 @@ var NativeRenderer = class {
|
|
|
32419
32742
|
this.applyBackground();
|
|
32420
32743
|
this.inputsUI.setTheme(theme);
|
|
32421
32744
|
this.paneControls?.setTheme(this.theme);
|
|
32745
|
+
this.axisScaleButtons?.setTheme(this.theme);
|
|
32422
32746
|
this.settingsDialog?.refreshConfig(this.getConfig());
|
|
32423
32747
|
this.syncThemeControl();
|
|
32424
32748
|
this.settingsDialog?.setTheme(this.theme);
|
|
@@ -32467,6 +32791,7 @@ var NativeRenderer = class {
|
|
|
32467
32791
|
this.liveRegion = null;
|
|
32468
32792
|
this.inputsUI?.destroy();
|
|
32469
32793
|
this.paneControls?.destroy();
|
|
32794
|
+
this.axisScaleButtons?.destroy();
|
|
32470
32795
|
for (const overlay of this.tableOverlays.values()) overlay.destroy();
|
|
32471
32796
|
this.tableOverlays.clear();
|
|
32472
32797
|
this.resizeObserver?.disconnect();
|
|
@@ -32716,7 +33041,8 @@ var NativeRenderer = class {
|
|
|
32716
33041
|
mountIndicator(model) {
|
|
32717
33042
|
this.scene.indicators.set(model.id, model);
|
|
32718
33043
|
this.refreshAnchorOffset(model);
|
|
32719
|
-
this.scene.
|
|
33044
|
+
if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
|
|
33045
|
+
else this.scene.assignIndicatorZ(model.id);
|
|
32720
33046
|
this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
|
|
32721
33047
|
native: !!model.native,
|
|
32722
33048
|
...model.shorttitle ? { settingsTitle: model.title } : {}
|
|
@@ -33535,10 +33861,17 @@ var NativeRenderer = class {
|
|
|
33535
33861
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
33536
33862
|
for (const l of this.extLayers) {
|
|
33537
33863
|
if (!l.def.repaintOnCursor) continue;
|
|
33538
|
-
|
|
33864
|
+
const lp = this.layerPane(l.def.id) ?? pane;
|
|
33865
|
+
if (lp.collapsed) continue;
|
|
33866
|
+
l.instance.render(this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs));
|
|
33539
33867
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
33540
33868
|
}
|
|
33541
33869
|
}
|
|
33870
|
+
/** Blank one SDK layer canvas (a collapsed host pane suppresses the layer's painting). */
|
|
33871
|
+
clearLayerCanvas(canvas) {
|
|
33872
|
+
if (canvas.width === 0 || canvas.height === 0) return;
|
|
33873
|
+
canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
|
|
33874
|
+
}
|
|
33542
33875
|
/** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths). */
|
|
33543
33876
|
extLayerArgs(id, scale, bounds, nowMs) {
|
|
33544
33877
|
return {
|
|
@@ -33557,6 +33890,7 @@ var NativeRenderer = class {
|
|
|
33557
33890
|
}
|
|
33558
33891
|
/** Paint the below-data (L-1) + geometry (L0) + chrome (L1) layers from the current scene/coords. */
|
|
33559
33892
|
paintData() {
|
|
33893
|
+
this.syncLayerCanvasOrder();
|
|
33560
33894
|
this.stampScaleInvert();
|
|
33561
33895
|
this.backend.modelAlpha = this.modelAlpha;
|
|
33562
33896
|
this.backend.candleBodyAlpha = this.candleBodyAlpha;
|
|
@@ -33587,9 +33921,14 @@ var NativeRenderer = class {
|
|
|
33587
33921
|
const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
|
|
33588
33922
|
let folded = null;
|
|
33589
33923
|
for (const l of this.extLayers) {
|
|
33590
|
-
const
|
|
33924
|
+
const lp = this.layerPane(l.def.id) ?? pane;
|
|
33925
|
+
if (lp.collapsed) {
|
|
33926
|
+
this.clearLayerCanvas(l.canvas);
|
|
33927
|
+
continue;
|
|
33928
|
+
}
|
|
33929
|
+
const args = this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs);
|
|
33591
33930
|
l.instance.render(args);
|
|
33592
|
-
folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
33931
|
+
if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
|
|
33593
33932
|
if (this.animZoom && l.instance.animating?.()) this.animator.start();
|
|
33594
33933
|
}
|
|
33595
33934
|
if (folded) {
|
|
@@ -33751,6 +34090,9 @@ var NativeRenderer = class {
|
|
|
33751
34090
|
pane.scaleTarget = { min: 0, max: maxVol / VOLUME_PANE_FILL_FRAC };
|
|
33752
34091
|
pane.axisFormat = "volume";
|
|
33753
34092
|
}
|
|
34093
|
+
} else if (this.layerNativesOwnPane(pane, masterModels)) {
|
|
34094
|
+
pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
|
|
34095
|
+
pane.percentBaseline = this.bars[i0]?.close ?? 0;
|
|
33754
34096
|
}
|
|
33755
34097
|
if (pane === pricePane && this.scene.tradeMarkers.visible && pane.bounds.height > 0) {
|
|
33756
34098
|
const th = this.tradesScaleHints(vr);
|
|
@@ -33837,6 +34179,59 @@ var NativeRenderer = class {
|
|
|
33837
34179
|
}
|
|
33838
34180
|
return null;
|
|
33839
34181
|
}
|
|
34182
|
+
/** The mounted native indicator that OWNS an SDK layer — the one whose type equals the
|
|
34183
|
+
* layer id (the id doubles as the data channel, so the pairing is the SDK's own
|
|
34184
|
+
* contract). Null for chart-type channels and while the owner is hidden (a hidden
|
|
34185
|
+
* indicator leaves the scene; its cleared data channel paints nothing anyway). */
|
|
34186
|
+
layerOwner(layerId) {
|
|
34187
|
+
for (const m of this.scene.indicators.values()) {
|
|
34188
|
+
if (m.native?.type === layerId) return m;
|
|
34189
|
+
}
|
|
34190
|
+
return null;
|
|
34191
|
+
}
|
|
34192
|
+
/** The pane an SDK layer paints on: its owner's pane, else the price pane. */
|
|
34193
|
+
layerPane(layerId) {
|
|
34194
|
+
const owner = this.layerOwner(layerId);
|
|
34195
|
+
const paneId = owner ? owner.paneId ?? PRICE_PANE_ID2 : PRICE_PANE_ID2;
|
|
34196
|
+
return this.scene.panes.get(paneId) ?? null;
|
|
34197
|
+
}
|
|
34198
|
+
/** The SDK layer canvases split around the data canvas, each side back-to-front:
|
|
34199
|
+
* owned layers by their owner's z key against the candles' (an indicator restacked
|
|
34200
|
+
* below the candles takes its layer canvas along), unowned by declared placement. */
|
|
34201
|
+
orderedLayerCanvases() {
|
|
34202
|
+
const byId = new Map(this.extLayers.map((l) => [l.def.id, l.canvas]));
|
|
34203
|
+
const { below, above } = stackLayers(
|
|
34204
|
+
this.extLayers.map((l) => {
|
|
34205
|
+
const owner = this.layerOwner(l.def.id);
|
|
34206
|
+
return {
|
|
34207
|
+
id: l.def.id,
|
|
34208
|
+
placement: l.def.placement === "below-data" ? "below-data" : "above-data",
|
|
34209
|
+
ownerZ: owner ? this.scene.zOf(owner.id) : null
|
|
34210
|
+
};
|
|
34211
|
+
}),
|
|
34212
|
+
this.scene.candleZ
|
|
34213
|
+
);
|
|
34214
|
+
return { below: below.map((id) => byId.get(id)), above: above.map((id) => byId.get(id)) };
|
|
34215
|
+
}
|
|
34216
|
+
/** The full canvas pile in paint order (layers + data/volume/vpvr) — what the DOM
|
|
34217
|
+
* stacking and the screenshot compositor must both follow. */
|
|
34218
|
+
canvasPile() {
|
|
34219
|
+
const { below, above } = this.orderedLayerCanvases();
|
|
34220
|
+
return [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above];
|
|
34221
|
+
}
|
|
34222
|
+
/** Re-slot the SDK layer canvases in the plot when the computed order changed (a z
|
|
34223
|
+
* write, a restored config, an indicator mount/remove/restack). Runs at the top of
|
|
34224
|
+
* every data frame; a no-op when the signature is unchanged. Re-inserting an
|
|
34225
|
+
* absolutely-positioned, pointer-transparent canvas repaints nothing by itself. */
|
|
34226
|
+
syncLayerCanvasOrder() {
|
|
34227
|
+
if (this.extLayers.length === 0 || !this.plot) return;
|
|
34228
|
+
const { below, above } = this.orderedLayerCanvases();
|
|
34229
|
+
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(",");
|
|
34230
|
+
if (sig === this.layerOrderSig) return;
|
|
34231
|
+
this.layerOrderSig = sig;
|
|
34232
|
+
for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
|
|
34233
|
+
for (const c of above) this.plot.insertBefore(c, this.chromeCanvas);
|
|
34234
|
+
}
|
|
33840
34235
|
/** True when an active volume layer is this study pane's ONLY content — so its scale should
|
|
33841
34236
|
* come from volume, not the empty {0,1} placeholder. (In the price pane, or alongside a real
|
|
33842
34237
|
* series, volume stays a bottom overlay and the master scale wins.) */
|
|
@@ -33846,6 +34241,15 @@ var NativeRenderer = class {
|
|
|
33846
34241
|
if (this.nativeLayerPane("volume") !== pane) return false;
|
|
33847
34242
|
return masterModels.every((m) => m.native?.type === "volume");
|
|
33848
34243
|
}
|
|
34244
|
+
/** True when this study pane's master content is only SDK-layer natives (series-less
|
|
34245
|
+
* models whose type names a mounted layer) — its scale then follows the visible bars
|
|
34246
|
+
* (see the call site). Any real master series takes over the scale as usual. */
|
|
34247
|
+
layerNativesOwnPane(pane, masterModels) {
|
|
34248
|
+
if (pane.kind === "price" || masterModels.length === 0) return false;
|
|
34249
|
+
return masterModels.every(
|
|
34250
|
+
(m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type)
|
|
34251
|
+
);
|
|
34252
|
+
}
|
|
33849
34253
|
/** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
|
|
33850
34254
|
* band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
|
|
33851
34255
|
* Top-to-bottom order. Every pane is independent — the price pane from the scene setting,
|
|
@@ -33861,6 +34265,25 @@ var NativeRenderer = class {
|
|
|
33861
34265
|
invert: paneInvert(this.scene, p)
|
|
33862
34266
|
}));
|
|
33863
34267
|
}
|
|
34268
|
+
/** Thin projection of the panes for the axis A/L hover buttons (top-to-bottom order). */
|
|
34269
|
+
axisScaleViews() {
|
|
34270
|
+
return this.scene.orderedPanes().map((p) => ({
|
|
34271
|
+
id: p.id,
|
|
34272
|
+
top: p.bounds.top,
|
|
34273
|
+
height: p.bounds.height,
|
|
34274
|
+
collapsed: p.collapsed,
|
|
34275
|
+
auto: p.manualScale == null,
|
|
34276
|
+
log: paneLogScale(this.scene, p)
|
|
34277
|
+
}));
|
|
34278
|
+
}
|
|
34279
|
+
/** Toggle one pane's autoscale (the A axis button): off freezes the current window into
|
|
34280
|
+
* manual mode, on drops it — the per-pane twin of the chart-level `autoScale` feature. */
|
|
34281
|
+
togglePaneAuto(paneId) {
|
|
34282
|
+
const pane = this.scene.panes.get(paneId);
|
|
34283
|
+
if (!pane) return;
|
|
34284
|
+
pane.manualScale = pane.manualScale == null ? { ...pane.scale } : null;
|
|
34285
|
+
this.scheduler?.invalidate(4 /* Full */);
|
|
34286
|
+
}
|
|
33864
34287
|
/** Set one pane's axis mode. The price pane routes to the scene-level setting (persisted +
|
|
33865
34288
|
* keyboard shortcuts); a study pane keeps its own, so panes never affect each other. */
|
|
33866
34289
|
setPaneScaleMode(paneId, mode) {
|
|
@@ -33872,19 +34295,18 @@ var NativeRenderer = class {
|
|
|
33872
34295
|
}
|
|
33873
34296
|
this.scheduler?.invalidate(4 /* Full */);
|
|
33874
34297
|
}
|
|
33875
|
-
/** Set one pane's logarithmic flag
|
|
33876
|
-
*
|
|
34298
|
+
/** Set one pane's logarithmic flag; the price pane routes to the scene-level flag.
|
|
34299
|
+
* Log and auto are INDEPENDENT: a frozen (manual) window is kept — its price bounds
|
|
34300
|
+
* stay put and only its `log` tag flips, so the same range re-renders in the new
|
|
34301
|
+
* space instead of snapping back to autoscale. */
|
|
33877
34302
|
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;
|
|
34303
|
+
const pane = paneId === PRICE_PANE_ID2 ? this.scene.orderedPanes().find((p) => p.kind === "price") : this.scene.panes.get(paneId);
|
|
34304
|
+
if (paneId === PRICE_PANE_ID2) this.scene.logScale = log;
|
|
34305
|
+
else {
|
|
34306
|
+
if (!pane) return;
|
|
34307
|
+
pane.logScale = log;
|
|
33887
34308
|
}
|
|
34309
|
+
if (pane?.manualScale) pane.manualScale.log = log;
|
|
33888
34310
|
this.scheduler?.invalidate(4 /* Full */);
|
|
33889
34311
|
}
|
|
33890
34312
|
/** Flip one pane's axis (high at the bottom). The price pane routes to the scene-level flag
|
|
@@ -33983,6 +34405,7 @@ var NativeRenderer = class {
|
|
|
33983
34405
|
}
|
|
33984
34406
|
this.inputsUI?.reposition();
|
|
33985
34407
|
this.paneControls?.reposition();
|
|
34408
|
+
this.axisScaleButtons?.reposition();
|
|
33986
34409
|
this.repositionScrollButton();
|
|
33987
34410
|
this.positionAttribution();
|
|
33988
34411
|
this.publishPricePaneBounds();
|
|
@@ -34001,6 +34424,7 @@ var NativeRenderer = class {
|
|
|
34001
34424
|
}
|
|
34002
34425
|
this.inputsUI?.reposition();
|
|
34003
34426
|
this.paneControls?.reposition();
|
|
34427
|
+
this.axisScaleButtons?.reposition();
|
|
34004
34428
|
this.repositionScrollButton();
|
|
34005
34429
|
this.positionAttribution();
|
|
34006
34430
|
this.publishPricePaneBounds();
|
|
@@ -34208,6 +34632,22 @@ function sanitizeHighlights(value) {
|
|
|
34208
34632
|
}
|
|
34209
34633
|
return out.sort((a, b) => a.from - b.from);
|
|
34210
34634
|
}
|
|
34635
|
+
function sanitizeSessionZones(value) {
|
|
34636
|
+
if (value == null || typeof value !== "object") return null;
|
|
34637
|
+
const v = value;
|
|
34638
|
+
const windows = (raw) => {
|
|
34639
|
+
if (!Array.isArray(raw)) return [];
|
|
34640
|
+
const out = [];
|
|
34641
|
+
for (const w of raw) {
|
|
34642
|
+
if (!Array.isArray(w)) continue;
|
|
34643
|
+
const from = Number(w[0]);
|
|
34644
|
+
const to = Number(w[1]);
|
|
34645
|
+
if (Number.isFinite(from) && Number.isFinite(to) && to > from) out.push([from, to]);
|
|
34646
|
+
}
|
|
34647
|
+
return out.sort((a, b) => a[0] - b[0]);
|
|
34648
|
+
};
|
|
34649
|
+
return { pre: windows(v.pre), post: windows(v.post) };
|
|
34650
|
+
}
|
|
34211
34651
|
function clamp012(v) {
|
|
34212
34652
|
return Math.max(0, Math.min(1, v));
|
|
34213
34653
|
}
|
|
@@ -34761,7 +35201,7 @@ function fmtChange(open2, close) {
|
|
|
34761
35201
|
}
|
|
34762
35202
|
|
|
34763
35203
|
// src/widget/statusline.ts
|
|
34764
|
-
var
|
|
35204
|
+
var STYLE_ID24 = "vela-widget-statusline";
|
|
34765
35205
|
var CSS21 = `
|
|
34766
35206
|
.vela-statusline {
|
|
34767
35207
|
position: absolute;
|
|
@@ -34953,7 +35393,7 @@ var Statusline = class {
|
|
|
34953
35393
|
this.fitMode = false;
|
|
34954
35394
|
this.fitRO = null;
|
|
34955
35395
|
const doc = host.ownerDocument;
|
|
34956
|
-
injectStyles(
|
|
35396
|
+
injectStyles(STYLE_ID24, CSS21, doc);
|
|
34957
35397
|
host.classList.add("vela-has-statusline");
|
|
34958
35398
|
this.el = doc.createElement("div");
|
|
34959
35399
|
this.el.className = "vela-statusline";
|
|
@@ -35226,11 +35666,70 @@ var MarketStatusTracker = class {
|
|
|
35226
35666
|
}
|
|
35227
35667
|
};
|
|
35228
35668
|
|
|
35669
|
+
// src/widget/session-shading.ts
|
|
35670
|
+
function deriveSessionZones(regular, extended) {
|
|
35671
|
+
const pre = [];
|
|
35672
|
+
const post = [];
|
|
35673
|
+
for (const [extStart, extEnd] of extended) {
|
|
35674
|
+
const inside = regular.filter(([s, e]) => e > extStart && s < extEnd).sort((a, b) => a[0] - b[0]);
|
|
35675
|
+
let cursor = extStart;
|
|
35676
|
+
for (const [regStart, regEnd] of inside) {
|
|
35677
|
+
if (regStart > cursor) pre.push([cursor, Math.min(regStart, extEnd)]);
|
|
35678
|
+
cursor = Math.max(cursor, regEnd);
|
|
35679
|
+
}
|
|
35680
|
+
if (cursor < extEnd) post.push([cursor, extEnd]);
|
|
35681
|
+
}
|
|
35682
|
+
return { pre, post };
|
|
35683
|
+
}
|
|
35684
|
+
var FETCH_AHEAD_MS2 = 10 * 864e5;
|
|
35685
|
+
var MIN_LOOKBACK_MS = 3 * 864e5;
|
|
35686
|
+
var MAX_LOOKBACK_MS = 120 * 864e5;
|
|
35687
|
+
var SessionShadingTracker = class {
|
|
35688
|
+
constructor(onZones) {
|
|
35689
|
+
this.onZones = onZones;
|
|
35690
|
+
/** Invalidates detached async work — bumped by every track()/stop(). */
|
|
35691
|
+
this.epoch = 0;
|
|
35692
|
+
}
|
|
35693
|
+
/** (Re)bind to a chart's data surface + market and evaluate once. */
|
|
35694
|
+
track(data, symbol, opts) {
|
|
35695
|
+
const my = ++this.epoch;
|
|
35696
|
+
void this.evaluate(my, data, symbol, opts);
|
|
35697
|
+
}
|
|
35698
|
+
stop() {
|
|
35699
|
+
this.epoch += 1;
|
|
35700
|
+
}
|
|
35701
|
+
async evaluate(my, data, symbol, opts) {
|
|
35702
|
+
const resolved = data.resolve(symbol);
|
|
35703
|
+
const provider = resolved ? data.providerInstance(resolved.provider) : void 0;
|
|
35704
|
+
const si = await data.symbolInfo(symbol).catch(() => void 0);
|
|
35705
|
+
if (my !== this.epoch) return;
|
|
35706
|
+
const hasSessions = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
|
|
35707
|
+
if (!provider?.getCalendar || !hasSessions || !resolved) {
|
|
35708
|
+
this.onZones(null);
|
|
35709
|
+
return;
|
|
35710
|
+
}
|
|
35711
|
+
if (opts.session !== "extended") {
|
|
35712
|
+
this.onZones({ pre: [], post: [] });
|
|
35713
|
+
return;
|
|
35714
|
+
}
|
|
35715
|
+
const now = Date.now();
|
|
35716
|
+
const lookback = Math.max(MIN_LOOKBACK_MS, Math.min(MAX_LOOKBACK_MS, opts.lookbackMs));
|
|
35717
|
+
const range = { from: now - lookback, to: now + FETCH_AHEAD_MS2 };
|
|
35718
|
+
const [regular, extended] = await Promise.all([
|
|
35719
|
+
provider.getCalendar(resolved.ticker, { ...range, session: "regular" }).catch(() => null),
|
|
35720
|
+
provider.getCalendar(resolved.ticker, { ...range, session: "extended" }).catch(() => null)
|
|
35721
|
+
]);
|
|
35722
|
+
if (my !== this.epoch) return;
|
|
35723
|
+
if (!regular || !extended) return;
|
|
35724
|
+
this.onZones(deriveSessionZones(regular, extended));
|
|
35725
|
+
}
|
|
35726
|
+
};
|
|
35727
|
+
|
|
35229
35728
|
// src/widget/watermark.ts
|
|
35230
35729
|
var MAX_FONT_PX = 36;
|
|
35231
35730
|
var MIN_FONT_PX = 12;
|
|
35232
35731
|
var FILL = 0.9;
|
|
35233
|
-
var
|
|
35732
|
+
var STYLE_ID25 = "vela-widget-watermark";
|
|
35234
35733
|
var CSS22 = `
|
|
35235
35734
|
.vela-watermark {
|
|
35236
35735
|
position: absolute;
|
|
@@ -35269,7 +35768,7 @@ var Watermark = class {
|
|
|
35269
35768
|
* canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
|
|
35270
35769
|
* fires during chart construction, before any subscriber can see it. */
|
|
35271
35770
|
this.loading = true;
|
|
35272
|
-
injectStyles(
|
|
35771
|
+
injectStyles(STYLE_ID25, CSS22, host.ownerDocument);
|
|
35273
35772
|
this.el = host.ownerDocument.createElement("div");
|
|
35274
35773
|
this.el.className = "vela-watermark";
|
|
35275
35774
|
this.el.dataset.velaScreenshot = "under";
|
|
@@ -35335,6 +35834,9 @@ var ChartContextMenu = class {
|
|
|
35335
35834
|
host,
|
|
35336
35835
|
items: [],
|
|
35337
35836
|
placement: "bottom-start",
|
|
35837
|
+
// Pointer-anchored action menu: checked state reads as a leading ✓, not a
|
|
35838
|
+
// washed row (which would read as hover in a menu with no trigger button).
|
|
35839
|
+
checkmarks: true,
|
|
35338
35840
|
onSelect: (id) => this.run(id)
|
|
35339
35841
|
});
|
|
35340
35842
|
host.addEventListener("contextmenu", this.onContextMenu);
|
|
@@ -35553,6 +36055,8 @@ var ChartCell = class {
|
|
|
35553
36055
|
this.activeRangeId = null;
|
|
35554
36056
|
/** Latched verdict of {@link sessionAvailable} (async metadata, sticky per symbol). */
|
|
35555
36057
|
this.sessionAvailableFlag = false;
|
|
36058
|
+
/** Keeps the pre/post-market shading on the symbol's real calendar (see {@link SessionShadingTracker}). */
|
|
36059
|
+
this.sessionShading = new SessionShadingTracker((zones) => this.inner?.renderer.set("sessionZones", zones));
|
|
35556
36060
|
this.manifest = [];
|
|
35557
36061
|
/** A restored ledger's manifest entry NAMES, waiting for the manifest to resolve
|
|
35558
36062
|
* (a pool/persisted cell can be built before the shared manifest has loaded). */
|
|
@@ -35709,67 +36213,7 @@ var ChartCell = class {
|
|
|
35709
36213
|
});
|
|
35710
36214
|
this.syncPresentNatives();
|
|
35711
36215
|
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
|
-
}
|
|
36216
|
+
this.pushSettingsSections();
|
|
35773
36217
|
this.offMarket = this.inner.on("market:changed", ({ symbol: symbol2, timeframe }) => {
|
|
35774
36218
|
this.state.symbol = symbol2;
|
|
35775
36219
|
this.state.provider = parseSymbol(symbol2).provider ?? void 0;
|
|
@@ -35814,9 +36258,130 @@ var ChartCell = class {
|
|
|
35814
36258
|
if (available !== this.sessionAvailableFlag) {
|
|
35815
36259
|
this.sessionAvailableFlag = available;
|
|
35816
36260
|
this.deps.onMarketChanged(this.id);
|
|
36261
|
+
this.pushSettingsSections();
|
|
35817
36262
|
}
|
|
36263
|
+
this.refreshSessionShading();
|
|
35818
36264
|
});
|
|
35819
36265
|
}
|
|
36266
|
+
/** (Re)derive the pre/post-market shading bands for this cell's market. The loaded
|
|
36267
|
+
* depth (bars × timeframe) bounds the calendar fetch; the tracker clamps it. */
|
|
36268
|
+
refreshSessionShading() {
|
|
36269
|
+
const chart = this.inner;
|
|
36270
|
+
const symbol = this.state.symbol;
|
|
36271
|
+
if (!chart || !symbol) return;
|
|
36272
|
+
const lookbackMs = Math.max(this.state.bars ?? 1e3, this.rangeBars) * timeframeMs(this.state.timeframe ?? "60");
|
|
36273
|
+
this.sessionShading.track(chart.data, symbol, { session: this.session, lookbackMs });
|
|
36274
|
+
}
|
|
36275
|
+
/** The session-shade colors live in the renderer CONFIG (persisted with it, edited
|
|
36276
|
+
* live by the dialog swatch) — the cell only proxies them into its settings rows. */
|
|
36277
|
+
sessionShadeColor(key) {
|
|
36278
|
+
const cfg = this.inner?.renderer.getConfig();
|
|
36279
|
+
const v = cfg?.sessions?.[key];
|
|
36280
|
+
return typeof v === "string" ? v : "";
|
|
36281
|
+
}
|
|
36282
|
+
setSessionShadeColor(key, color) {
|
|
36283
|
+
this.inner?.renderer.applyConfig({ sessions: { [key]: color } });
|
|
36284
|
+
this.deps.onStateDirty();
|
|
36285
|
+
}
|
|
36286
|
+
/**
|
|
36287
|
+
* (Re)contribute this cell's settings-dialog sections: status line parts, the
|
|
36288
|
+
* per-cell fetch depth, the watermark toggle, and — only while the cell's symbol
|
|
36289
|
+
* HAS sessions — the Trading session group (RTH/ETH switch + the pre/post-market
|
|
36290
|
+
* shading colors) inside the Symbol tab. Bars/watermark/titles are persistable
|
|
36291
|
+
* cell state; a depth-only reload is silent, so mark dirty here. Re-run whenever
|
|
36292
|
+
* a gate changes (the dialog reads the sections on open).
|
|
36293
|
+
*/
|
|
36294
|
+
pushSettingsSections() {
|
|
36295
|
+
const chart = this.inner;
|
|
36296
|
+
if (!chart) return;
|
|
36297
|
+
const rth = "Regular hours (RTH)";
|
|
36298
|
+
const eth = "Extended hours (ETH)";
|
|
36299
|
+
const sessionSection = {
|
|
36300
|
+
title: "Trading session",
|
|
36301
|
+
placement: "symbol",
|
|
36302
|
+
rows: [
|
|
36303
|
+
{
|
|
36304
|
+
kind: "select",
|
|
36305
|
+
label: "Session",
|
|
36306
|
+
options: [rth, eth],
|
|
36307
|
+
get: () => this.session === "extended" ? eth : rth,
|
|
36308
|
+
set: (v) => this.setSession(v === eth ? "extended" : "regular")
|
|
36309
|
+
},
|
|
36310
|
+
{
|
|
36311
|
+
kind: "color",
|
|
36312
|
+
label: "Pre-market",
|
|
36313
|
+
get: () => this.sessionShadeColor("premarketColor"),
|
|
36314
|
+
set: (v) => this.setSessionShadeColor("premarketColor", v)
|
|
36315
|
+
},
|
|
36316
|
+
{
|
|
36317
|
+
kind: "color",
|
|
36318
|
+
label: "Post-market",
|
|
36319
|
+
get: () => this.sessionShadeColor("postmarketColor"),
|
|
36320
|
+
set: (v) => this.setSessionShadeColor("postmarketColor", v)
|
|
36321
|
+
}
|
|
36322
|
+
]
|
|
36323
|
+
};
|
|
36324
|
+
const advanced = {
|
|
36325
|
+
title: "Advanced",
|
|
36326
|
+
placement: "end",
|
|
36327
|
+
rows: [
|
|
36328
|
+
{
|
|
36329
|
+
kind: "select",
|
|
36330
|
+
label: "Bars to fetch",
|
|
36331
|
+
options: ["500", "1000", "2000", "5000", "10000", "20000"],
|
|
36332
|
+
get: () => String(this.state.bars ?? 1e3),
|
|
36333
|
+
set: (v) => {
|
|
36334
|
+
this.state.bars = Number(v);
|
|
36335
|
+
this.deps.onStateDirty();
|
|
36336
|
+
void this.inner?.setMarket({ bars: Math.max(this.state.bars, this.rangeBars) });
|
|
36337
|
+
}
|
|
36338
|
+
}
|
|
36339
|
+
]
|
|
36340
|
+
};
|
|
36341
|
+
const watermarkSection = {
|
|
36342
|
+
title: "Watermark",
|
|
36343
|
+
placement: "symbol",
|
|
36344
|
+
rows: [
|
|
36345
|
+
{
|
|
36346
|
+
kind: "toggle",
|
|
36347
|
+
label: "Symbol watermark",
|
|
36348
|
+
get: () => this.watermarkOn,
|
|
36349
|
+
set: (v) => this.setWatermarkVisible(v)
|
|
36350
|
+
}
|
|
36351
|
+
]
|
|
36352
|
+
};
|
|
36353
|
+
const sections = [];
|
|
36354
|
+
if (this.statusline) {
|
|
36355
|
+
const sl = this.statusline;
|
|
36356
|
+
sections.push({
|
|
36357
|
+
title: "Status line",
|
|
36358
|
+
rows: [
|
|
36359
|
+
{ kind: "heading", label: "Status line" },
|
|
36360
|
+
{ kind: "toggle", label: "Symbol name", get: () => sl.partVisible("name"), set: (v) => sl.setPartVisible("name", v) },
|
|
36361
|
+
{ kind: "toggle", label: "Market status", get: () => sl.partVisible("market"), set: (v) => sl.setPartVisible("market", v) },
|
|
36362
|
+
{ kind: "toggle", label: "OHLC values", get: () => sl.partVisible("ohlc"), set: (v) => sl.setPartVisible("ohlc", v) },
|
|
36363
|
+
{ kind: "toggle", label: "Bar change values", get: () => sl.partVisible("change"), set: (v) => sl.setPartVisible("change", v) },
|
|
36364
|
+
{ kind: "heading", label: "Indicators" },
|
|
36365
|
+
{
|
|
36366
|
+
kind: "toggle",
|
|
36367
|
+
label: "Titles",
|
|
36368
|
+
get: () => this.indicatorTitlesOn,
|
|
36369
|
+
set: (v) => this.setIndicatorTitlesVisible(v)
|
|
36370
|
+
},
|
|
36371
|
+
{
|
|
36372
|
+
kind: "toggle",
|
|
36373
|
+
label: "Values",
|
|
36374
|
+
get: () => this.indicatorValuesOn,
|
|
36375
|
+
set: (v) => this.setIndicatorValuesVisible(v)
|
|
36376
|
+
}
|
|
36377
|
+
]
|
|
36378
|
+
});
|
|
36379
|
+
}
|
|
36380
|
+
sections.push(advanced);
|
|
36381
|
+
if (this.sessionAvailableFlag) sections.push(sessionSection);
|
|
36382
|
+
sections.push(watermarkSection);
|
|
36383
|
+
chart.renderer.setSettingsSections(sections);
|
|
36384
|
+
}
|
|
35820
36385
|
/** Show/hide this cell's symbol watermark (persisted per cell). */
|
|
35821
36386
|
setWatermarkVisible(visible) {
|
|
35822
36387
|
this.watermarkOn = visible;
|
|
@@ -35871,6 +36436,7 @@ var ChartCell = class {
|
|
|
35871
36436
|
this.state.priceStyle = style;
|
|
35872
36437
|
this.inner?.renderer.set("priceStyle", style);
|
|
35873
36438
|
this.syncStatuslineColors();
|
|
36439
|
+
this.deps.onPriceStyleChanged(this.id);
|
|
35874
36440
|
}
|
|
35875
36441
|
/** OHLC/change ink in the status line follows the ACTIVE price style's configured
|
|
35876
36442
|
* colors and direction rule (candle bodies by close-vs-open, baseline by position
|
|
@@ -36094,6 +36660,7 @@ var ChartCell = class {
|
|
|
36094
36660
|
this.contextMenu.destroy();
|
|
36095
36661
|
this.history.destroy();
|
|
36096
36662
|
this.marketStatus?.stop();
|
|
36663
|
+
this.sessionShading.stop();
|
|
36097
36664
|
this.statusline?.destroy();
|
|
36098
36665
|
this.watermark?.destroy();
|
|
36099
36666
|
this.inner?.destroy();
|
|
@@ -36378,7 +36945,7 @@ var GAP_PX = 2;
|
|
|
36378
36945
|
var POOL_CAP = 16;
|
|
36379
36946
|
var TIME_AXIS_H3 = 22;
|
|
36380
36947
|
var ALERT_CAP = 50;
|
|
36381
|
-
var
|
|
36948
|
+
var STYLE_ID26 = "vela-workspace";
|
|
36382
36949
|
var CSS23 = `
|
|
36383
36950
|
.vela-workspace { position: relative; width: 100%; height: 100%; display: flex; flex-direction: column; background: var(--vela-bg); }
|
|
36384
36951
|
.vela-ws-main { position: relative; display: flex; flex-direction: row; flex: 1 1 auto; min-height: 0; }
|
|
@@ -36525,7 +37092,7 @@ var VelaWorkspace = class {
|
|
|
36525
37092
|
this.order = boot?.charts ? boot.charts.map((c) => c.id) : declaredOrder(opts.cells);
|
|
36526
37093
|
const bootActive = boot?.activeCellId ?? null;
|
|
36527
37094
|
const doc = hostEl.ownerDocument;
|
|
36528
|
-
injectStyles(
|
|
37095
|
+
injectStyles(STYLE_ID26, CSS23, doc);
|
|
36529
37096
|
this.root = doc.createElement("div");
|
|
36530
37097
|
this.root.className = "vela-workspace";
|
|
36531
37098
|
ensureUIHost(this.root, resolveTheme(opts.theme));
|
|
@@ -37125,6 +37692,7 @@ var VelaWorkspace = class {
|
|
|
37125
37692
|
context: () => this.context(),
|
|
37126
37693
|
activate: (id2) => this.setActiveCell(id2),
|
|
37127
37694
|
onMarketChanged: (id2) => this.onCellMarketChanged(id2),
|
|
37695
|
+
onPriceStyleChanged: (id2) => this.onCellPriceStyleChanged(id2),
|
|
37128
37696
|
onIndicatorsChanged: (id2) => this.onCellIndicatorsChanged(id2),
|
|
37129
37697
|
onStateDirty: () => this.markStateDirty(),
|
|
37130
37698
|
manifestSettled: () => this.manifestSettled
|
|
@@ -37414,6 +37982,16 @@ var VelaWorkspace = class {
|
|
|
37414
37982
|
this.objectTree.setSymbol(cell.symbol);
|
|
37415
37983
|
this.bottombar?.setSession({ session: cell.session, enabled: cell.sessionAvailable });
|
|
37416
37984
|
}
|
|
37985
|
+
/** Trigger ② — a cell's price style changed: the topbar button/menu only if active.
|
|
37986
|
+
* Reads the cell back (not the requested style) so the button reflects what the
|
|
37987
|
+
* renderer actually applied. */
|
|
37988
|
+
onCellPriceStyleChanged(id) {
|
|
37989
|
+
this.markStateDirty();
|
|
37990
|
+
if (id !== this.activeId) return;
|
|
37991
|
+
const cell = this.cellsById.get(id);
|
|
37992
|
+
if (!cell) return;
|
|
37993
|
+
this.topbar.setPriceStyle(cell.priceStyle);
|
|
37994
|
+
}
|
|
37417
37995
|
/** Trigger ② — a cell's indicator ledger changed: count + picker only if active. */
|
|
37418
37996
|
onCellIndicatorsChanged(id) {
|
|
37419
37997
|
this.markStateDirty();
|