@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.
Files changed (45) hide show
  1. package/dist/{DataProvider-DHX4x6-r.d.ts → DataProvider-BmHkcwvJ.d.ts} +1 -1
  2. package/dist/{DataProvider-CNmk84SH.d.cts → DataProvider-DrN7ZIou.d.cts} +1 -1
  3. package/dist/{chunk-TP56QRMK.js → chunk-2R7BANEM.js} +1 -1
  4. package/dist/{chunk-U5KCQHAC.js → chunk-3QX6QOCX.js} +80 -12
  5. package/dist/{chunk-7D6YIF34.js → chunk-6FA4TDUF.js} +325 -67
  6. package/dist/{chunk-7Y7CJ7EN.js → chunk-BEJ57HP4.js} +8 -1
  7. package/dist/{chunk-QHZ7IXFL.js → chunk-OICPLNU7.js} +59 -9
  8. package/dist/{contributions-DDpv7hEL.d.cts → contributions-2bEmCF9S.d.cts} +2 -2
  9. package/dist/{contributions-ciV6Neyd.d.ts → contributions-DatoAzMx.d.ts} +2 -2
  10. package/dist/{history-CFqZm5re.d.ts → history-BmyjaVir.d.cts} +6 -6
  11. package/dist/{history-ChoT34nz.d.cts → history-CvOtsjT5.d.ts} +6 -6
  12. package/dist/index.cjs +394 -78
  13. package/dist/index.d.cts +36 -9
  14. package/dist/index.d.ts +36 -9
  15. package/dist/index.js +4 -4
  16. package/dist/{options-BqGeFHtp.d.ts → options-YdiaaVjt.d.cts} +20 -1
  17. package/dist/{options-BqGeFHtp.d.cts → options-YdiaaVjt.d.ts} +20 -1
  18. package/dist/{plugin-C97gUCVf.d.ts → plugin-BeCMF1VQ.d.ts} +3 -3
  19. package/dist/{plugin-CSb_sTiN.d.cts → plugin-CkiStRaI.d.cts} +3 -3
  20. package/dist/plugin.cjs +8 -0
  21. package/dist/plugin.d.cts +4 -4
  22. package/dist/plugin.d.ts +4 -4
  23. package/dist/plugin.js +2 -2
  24. package/dist/providers/binance.d.cts +2 -2
  25. package/dist/providers/binance.d.ts +2 -2
  26. package/dist/providers/coinbase.d.cts +2 -2
  27. package/dist/providers/coinbase.d.ts +2 -2
  28. package/dist/providers/hyperliquid.d.cts +2 -2
  29. package/dist/providers/hyperliquid.d.ts +2 -2
  30. package/dist/ui.cjs +59 -9
  31. package/dist/ui.d.cts +10 -2
  32. package/dist/ui.d.ts +10 -2
  33. package/dist/ui.js +2 -2
  34. package/dist/vela.global.js +394 -78
  35. package/dist/vela.global.min.js +84 -47
  36. package/dist/widget.cjs +646 -191
  37. package/dist/widget.d.cts +29 -8
  38. package/dist/widget.d.ts +29 -8
  39. package/dist/widget.js +145 -73
  40. package/dist/workspace.cjs +601 -142
  41. package/dist/workspace.d.cts +29 -5
  42. package/dist/workspace.d.ts +29 -5
  43. package/dist/workspace.js +143 -67
  44. package/package.json +1 -1
  45. /package/dist/{chunk-Q3XQHLIH.js → chunk-AOGZBKUE.js} +0 -0
@@ -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
- } else if (!branch && item.checked) {
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 = opts.id ? document.createElement("label") : document.createElement("span");
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
- const lbl = labelEl(item.label, { id: item.id, size });
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.title = "Session \u2014 stocks & ETFs only";
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) shows the disabled RTH-active stub the
5620
- * pre-session-model look, byte-for-byte. Enabled, the chips become clickable and
5621
- * the active one tracks the chart's current session.
5680
+ * market, or metadata not landed yet) HIDES the toggle entirelyRTH/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.title = state.enabled ? "Session \u2014 regular (RTH) vs extended (ETH) hours" : "Session \u2014 stocks & ETFs only";
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 ? key === state.session : key === "regular");
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 active = i === idx;
19497
+ const on = i === idx;
19422
19498
  const el = tabEls[i];
19423
- el.style.borderBottomColor = active ? fg : "transparent";
19424
- el.style.color = active ? "inherit" : "var(--vela-fg-muted)";
19425
- el.classList.toggle("vela-ind-tab-active", active);
19426
- bodies[i].style.display = active ? "grid" : "none";
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
- section.appendChild(fieldSection(group.name, { variant: "inputs", first: body.childElementCount === 0 }));
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
- grid.appendChild(fieldRow({
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
- grid.appendChild(fieldRow({
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
- grid.appendChild(fieldRow({
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
- grid.appendChild(fieldRow({
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
- if (scene.highlights.length === 0) return;
22583
- for (const band of scene.highlights) {
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
- if (scene.highlights.length === 0) return;
24560
- for (const band of scene.highlights) {
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 STYLE_ID22 = "vela-drawing-popup-styles";
29122
+ var STYLE_ID23 = "vela-drawing-popup-styles";
28859
29123
  var STYLE_REV = "3";
28860
- function ensureStyles4() {
29124
+ function ensureStyles5() {
28861
29125
  if (typeof document === "undefined") return;
28862
- const existing = document.getElementById(STYLE_ID22);
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 = STYLE_ID22;
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
- ensureStyles4();
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. Clears that pane's frozen (manual) window so the new
33876
- * space re-fits, and routes the price pane to the scene-level flag. */
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
- if (paneId === PRICE_PANE_ID2) {
33879
- this.scene.logScale = log;
33880
- const price = this.scene.orderedPanes().find((p) => p.kind === "price");
33881
- if (price) price.manualScale = null;
33882
- } else {
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 STYLE_ID23 = "vela-widget-statusline";
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(STYLE_ID23, CSS21, doc);
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 STYLE_ID24 = "vela-widget-watermark";
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(STYLE_ID24, CSS22, host.ownerDocument);
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
- const advanced = {
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 STYLE_ID25 = "vela-workspace";
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(STYLE_ID25, CSS23, doc);
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();