@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.
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-7Y7CJ7EN.js → chunk-BEJ57HP4.js} +8 -1
  5. package/dist/{chunk-7D6YIF34.js → chunk-FQBT4NFM.js} +431 -74
  6. package/dist/{chunk-U5KCQHAC.js → chunk-N6MBJHMV.js} +129 -41
  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 +500 -85
  13. package/dist/index.d.cts +63 -9
  14. package/dist/index.d.ts +63 -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-Bcone6Vh.d.ts} +9 -5
  19. package/dist/{plugin-CSb_sTiN.d.cts → plugin-DO_DX0gp.d.cts} +9 -5
  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 +500 -85
  35. package/dist/vela.global.min.js +84 -47
  36. package/dist/widget.cjs +801 -227
  37. package/dist/widget.d.cts +34 -8
  38. package/dist/widget.d.ts +34 -8
  39. package/dist/widget.js +145 -73
  40. package/dist/workspace.cjs +756 -178
  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
@@ -9872,7 +9872,7 @@ var Vela = (function (exports) {
9872
9872
  } else {
9873
9873
  const left = Math.min(a.left - h.left, Math.max(0, h.width - tip.offsetWidth - 4));
9874
9874
  tip.style.left = `${Math.max(0, left)}px`;
9875
- tip.style.top = `${a.bottom - h.top + 6}px`;
9875
+ tip.style.top = opts.placement === "above" ? `${Math.max(0, a.top - h.top - tip.offsetHeight - 6)}px` : `${a.bottom - h.top + 6}px`;
9876
9876
  }
9877
9877
  };
9878
9878
  const arm = (e) => {
@@ -16247,6 +16247,23 @@ var Vela = (function (exports) {
16247
16247
  background: var(--vela-hover-strong);
16248
16248
  color: var(--vela-fg-bright);
16249
16249
  }
16250
+ /* Checkmark mode (context/action menus): every row reserves the leading mark column so
16251
+ labels align; a checked row fills it with a \u2713 and brightens its ink \u2014 the row surface
16252
+ stays free for the hover wash. */
16253
+ .vela-menu-item .vela-menu-mark {
16254
+ width: 14px;
16255
+ flex: none;
16256
+ display: inline-flex;
16257
+ align-items: center;
16258
+ justify-content: center;
16259
+ }
16260
+ .vela-menu-item .vela-menu-mark .vela-icon {
16261
+ width: 14px;
16262
+ height: 14px;
16263
+ font-size: 14px;
16264
+ color: var(--vela-fg-bright);
16265
+ }
16266
+ .vela-menu-item[data-checkmark] { color: var(--vela-fg-bright); }
16250
16267
  /* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
16251
16268
  same control language as the settings dialog's toggles. */
16252
16269
  .vela-menu-switch {
@@ -16317,6 +16334,7 @@ var Vela = (function (exports) {
16317
16334
  this.host = opts.host;
16318
16335
  this.onSelect = opts.onSelect;
16319
16336
  this.onFavorite = opts.onFavorite;
16337
+ this.checkmarks = opts.checkmarks === true;
16320
16338
  this.positioner = doc.createElement("div");
16321
16339
  this.positioner.className = "vela-ui-layer";
16322
16340
  this.list = doc.createElement("ul");
@@ -16395,6 +16413,7 @@ var Vela = (function (exports) {
16395
16413
  build() {
16396
16414
  const doc = this.doc;
16397
16415
  this.list.replaceChildren();
16416
+ const markable = this.checkmarks && this.items.some((i) => !(i.submenu && i.submenu.length > 0) && !i.toggle && i.checked !== void 0);
16398
16417
  for (const item of this.items) {
16399
16418
  if (item.separatorBefore) {
16400
16419
  const sep = doc.createElement("li");
@@ -16408,7 +16427,16 @@ var Vela = (function (exports) {
16408
16427
  li.dataset.veiId = item.id;
16409
16428
  if (item.toggle) {
16410
16429
  li.dataset.toggle = "1";
16411
- } else if (!branch && item.checked) {
16430
+ }
16431
+ if (markable) {
16432
+ const mark = doc.createElement("span");
16433
+ mark.className = "vela-menu-mark";
16434
+ if (!branch && !item.toggle && item.checked) {
16435
+ mark.appendChild(iconEl("check", doc));
16436
+ li.dataset.checkmark = "1";
16437
+ }
16438
+ li.appendChild(mark);
16439
+ } else if (!branch && !item.toggle && item.checked) {
16412
16440
  li.dataset.checked = "1";
16413
16441
  }
16414
16442
  if (item.icon) li.appendChild(iconEl(item.icon, doc));
@@ -16457,6 +16485,7 @@ var Vela = (function (exports) {
16457
16485
  placement: "right-start",
16458
16486
  onSelect: this.onSelect,
16459
16487
  onFavorite: this.onFavorite,
16488
+ checkmarks: this.checkmarks,
16460
16489
  id: `${this.mid}--${item.id}`
16461
16490
  });
16462
16491
  sub.setItems(item.submenu ?? []);
@@ -16481,7 +16510,8 @@ var Vela = (function (exports) {
16481
16510
  triggerId: opts.triggerId,
16482
16511
  id: opts.id,
16483
16512
  minWidth: opts.minWidth,
16484
- onFavorite: opts.onFavorite
16513
+ onFavorite: opts.onFavorite,
16514
+ checkmarks: opts.checkmarks
16485
16515
  });
16486
16516
  this.root.setItems(opts.items);
16487
16517
  }
@@ -16509,6 +16539,13 @@ var Vela = (function (exports) {
16509
16539
  }
16510
16540
  };
16511
16541
 
16542
+ // src/core/model/inputs.ts
16543
+ function inputVisible(when, values) {
16544
+ if (!when) return true;
16545
+ const conds = Array.isArray(when) ? when : [when];
16546
+ return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
16547
+ }
16548
+
16512
16549
  // src/ui/tokens.ts
16513
16550
  var STATIC_ID = "vela-ui-tokens";
16514
16551
  var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
@@ -16517,8 +16554,13 @@ var Vela = (function (exports) {
16517
16554
  ${STATIC_DECLS}
16518
16555
  font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
16519
16556
  box-sizing: border-box;
16557
+ /* Chrome text (titles, buttons, menus, readouts) is UI, not copy \u2014 never selectable. */
16558
+ user-select: none;
16559
+ -webkit-user-select: none;
16520
16560
  }
16521
16561
  .vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
16562
+ /* Text ENTRY is the one exception: selection is part of editing. */
16563
+ .vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
16522
16564
  .vela-icon { display: inline-flex; align-items: center; flex: none; }
16523
16565
  .vela-icon svg { display: block; }
16524
16566
  `;
@@ -18704,6 +18746,9 @@ ${overlayScrollbarCss(".vela-dialog *")}
18704
18746
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
18705
18747
  -moz-appearance: textfield;
18706
18748
  appearance: textfield;
18749
+ /* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
18750
+ user-select: text;
18751
+ -webkit-user-select: text;
18707
18752
  }
18708
18753
  .vela-num input::-webkit-inner-spin-button, .vela-num input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
18709
18754
  .vela-num input:hover { border-color: var(--vela-fg-muted); }
@@ -18916,6 +18961,9 @@ ${overlayScrollbarCss(".vela-dialog *")}
18916
18961
  overflow: hidden;
18917
18962
  text-overflow: ellipsis;
18918
18963
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
18964
+ /* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
18965
+ user-select: text;
18966
+ -webkit-user-select: text;
18919
18967
  }
18920
18968
  .vela-text-field:hover { border-color: var(--vela-fg-muted); }
18921
18969
  .vela-text-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
@@ -19009,6 +19057,9 @@ ${overlayScrollbarCss(".vela-dialog *")}
19009
19057
  resize: vertical;
19010
19058
  outline: none;
19011
19059
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
19060
+ /* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
19061
+ user-select: text;
19062
+ -webkit-user-select: text;
19012
19063
  }
19013
19064
  .vela-textarea-field:hover { border-color: var(--vela-fg-muted); }
19014
19065
  .vela-textarea-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
@@ -19537,6 +19588,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
19537
19588
  font-size: 14px;
19538
19589
  }
19539
19590
  .vela-field-label[data-size='sm'] { font-size: inherit; opacity: 0.85; }
19591
+ /* Titles are fully inert: spans, not label[for] \u2014 native labels propagate :hover and
19592
+ clicks onto their control, and both belong to the input alone. */
19540
19593
  .vela-field-cell { display: flex; align-items: center; gap: 8px; justify-self: start; }
19541
19594
  .vela-field-bool { display: flex; align-items: center; gap: 8px; min-height: 22px; }
19542
19595
  .vela-field-stacked { display: flex; flex-direction: column; gap: 8px; }
@@ -19595,11 +19648,17 @@ ${overlayScrollbarCss(".vela-dialog *")}
19595
19648
  return el;
19596
19649
  }
19597
19650
  function labelEl(text, opts) {
19598
- const el = opts.id ? document.createElement("label") : document.createElement("span");
19599
- if (opts.id && el instanceof HTMLLabelElement) el.htmlFor = opts.id;
19651
+ const el = document.createElement("span");
19600
19652
  el.className = "vela-field-label";
19601
19653
  if (opts.size) el.dataset.size = opts.size;
19602
19654
  el.textContent = text;
19655
+ const id = opts.id;
19656
+ if (id) {
19657
+ el.id = `${id}--title`;
19658
+ queueMicrotask(() => {
19659
+ el.ownerDocument.getElementById(id)?.setAttribute("aria-labelledby", el.id);
19660
+ });
19661
+ }
19603
19662
  return el;
19604
19663
  }
19605
19664
  function asList(control) {
@@ -19628,9 +19687,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
19628
19687
  it.className = "vela-field-inline-item";
19629
19688
  if (item.toggleFirst) it.style.flexDirection = "row-reverse";
19630
19689
  if (item.label) {
19631
- const lbl = labelEl(item.label, { id: item.id, size: size3 });
19632
- if (item.toggleFirst) lbl.style.cursor = "pointer";
19633
- it.appendChild(lbl);
19690
+ it.appendChild(labelEl(item.label, { id: item.id, size: size3 }));
19634
19691
  }
19635
19692
  if (item.fit) it.appendChild(item.control);
19636
19693
  else {
@@ -19655,7 +19712,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
19655
19712
  tone: "bright",
19656
19713
  onChange: (v) => opts.toggle?.onChange(v)
19657
19714
  });
19658
- wrap3.append(sw.el, labelEl(opts.label, { size: size3 }));
19715
+ wrap3.append(sw.el, labelEl(opts.label, { id: opts.toggle?.id ?? opts.id, size: size3 }));
19659
19716
  if (opts.info) wrap3.appendChild(opts.info);
19660
19717
  if (opts.toggle?.get) {
19661
19718
  const get = opts.toggle.get;
@@ -19694,7 +19751,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
19694
19751
  if (controls.length > 0) dimControls(controls, v);
19695
19752
  }
19696
19753
  });
19697
- left.append(sw.el, labelEl(opts.label, { size: size3 }));
19754
+ left.append(sw.el, labelEl(opts.label, { id: opts.toggle.id ?? opts.id, size: size3 }));
19698
19755
  wrap2.appendChild(left);
19699
19756
  if (controls.length > 0) dimControls(controls, opts.toggle.checked);
19700
19757
  if (opts.toggle.get) {
@@ -19848,6 +19905,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
19848
19905
  this.row = null;
19849
19906
  this.snapshot = null;
19850
19907
  this.dialogTips = [];
19908
+ /** Re-applies every `when` gate against current values; set while the dialog is open. */
19909
+ this.refreshVisibility = null;
19851
19910
  this.choicePop = null;
19852
19911
  this.calendarPop = null;
19853
19912
  this.calendarAnchor = null;
@@ -19910,14 +19969,17 @@ ${overlayScrollbarCss(".vela-dialog *")}
19910
19969
  tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
19911
19970
  const tabEls = [];
19912
19971
  const bodies = [];
19972
+ const gates = [];
19973
+ let active = 0;
19913
19974
  const activate = (idx) => {
19975
+ active = idx;
19914
19976
  for (let i = 0; i < tabEls.length; i += 1) {
19915
- const active = i === idx;
19977
+ const on = i === idx;
19916
19978
  const el = tabEls[i];
19917
- el.style.borderBottomColor = active ? fg : "transparent";
19918
- el.style.color = active ? "inherit" : "var(--vela-fg-muted)";
19919
- el.classList.toggle("vela-ind-tab-active", active);
19920
- bodies[i].style.display = active ? "grid" : "none";
19979
+ el.style.borderBottomColor = on ? fg : "transparent";
19980
+ el.style.color = on ? "inherit" : "var(--vela-fg-muted)";
19981
+ el.classList.toggle("vela-ind-tab-active", on);
19982
+ bodies[i].style.display = on ? "grid" : "none";
19921
19983
  }
19922
19984
  };
19923
19985
  for (const [idx, def] of tabDefs.entries()) {
@@ -19928,11 +19990,24 @@ ${overlayScrollbarCss(".vela-dialog *")}
19928
19990
  tab.addEventListener("click", () => activate(idx));
19929
19991
  tabs.appendChild(tab);
19930
19992
  tabEls.push(tab);
19931
- bodies.push(this.buildTabBody(def.inputs, row));
19993
+ bodies.push(this.buildTabBody(def.inputs, row, gates));
19994
+ if (def.inputs.every((d) => d.when)) {
19995
+ gates.push({ el: tab, visible: (bag) => def.inputs.some((d) => inputVisible(d.when, bag)) });
19996
+ }
19932
19997
  }
19933
19998
  ui.body.appendChild(tabs);
19934
19999
  for (const b of bodies) ui.body.appendChild(b);
19935
20000
  activate(0);
20001
+ const refresh = () => {
20002
+ const bag = valuesBag(row);
20003
+ for (const g of gates) g.el.style.display = g.visible(bag) ? "" : "none";
20004
+ if (tabEls[active]?.style.display === "none") {
20005
+ const next2 = tabEls.findIndex((el) => el.style.display !== "none");
20006
+ if (next2 >= 0) activate(next2);
20007
+ }
20008
+ };
20009
+ this.refreshVisibility = refresh;
20010
+ refresh();
19936
20011
  const onBackdropDown = (e) => {
19937
20012
  if (e.target !== ui.backdrop && e.target !== ui.positioner) return;
19938
20013
  if (isPopoverOpen() || eventDismissedPopover(e)) {
@@ -19952,16 +20027,27 @@ ${overlayScrollbarCss(".vela-dialog *")}
19952
20027
  }
19953
20028
  /** One tab's scrollable body — one shared grid so every group's controls share a
19954
20029
  * left edge (a 100px number field lines up with a wider session/time pair). */
19955
- buildTabBody(inputs, row) {
20030
+ buildTabBody(inputs, row, gates) {
19956
20031
  const body = document.createElement("div");
19957
20032
  body.style.cssText = inputDialogBodyStyle(this.host.mobile());
19958
20033
  for (const group of groupInputs(inputs)) {
19959
20034
  const section = document.createElement("div");
19960
20035
  section.style.cssText = "display:contents;";
20036
+ let header = null;
19961
20037
  if (group.name) {
19962
- section.appendChild(fieldSection(group.name, { variant: "inputs", first: body.childElementCount === 0 }));
20038
+ header = fieldSection(group.name, { variant: "inputs", first: body.childElementCount === 0 });
20039
+ section.appendChild(header);
20040
+ }
20041
+ for (const inputRow of group.rows) {
20042
+ const el = this.buildInputRow(section, inputRow, row);
20043
+ if (inputRow.some((d) => d.when)) {
20044
+ gates.push({ el, visible: (bag) => rowVisible(inputRow, bag) });
20045
+ }
20046
+ }
20047
+ if (header && group.rows.every((r) => r.every((d) => d.when))) {
20048
+ const rows = group.rows;
20049
+ gates.push({ el: header, visible: (bag) => rows.some((r) => rowVisible(r, bag)) });
19963
20050
  }
19964
- for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
19965
20051
  body.appendChild(section);
19966
20052
  }
19967
20053
  return body;
@@ -19969,6 +20055,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
19969
20055
  close() {
19970
20056
  document.removeEventListener("keydown", this.onDialogKey);
19971
20057
  closeOpenPopovers();
20058
+ this.refreshVisibility = null;
19972
20059
  this.choicePop = null;
19973
20060
  this.calendarPop = null;
19974
20061
  this.calendarAnchor = null;
@@ -19998,11 +20085,21 @@ ${overlayScrollbarCss(".vela-dialog *")}
19998
20085
  }
19999
20086
  this.close();
20000
20087
  }
20088
+ /** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
20089
+ commit(row, key, value) {
20090
+ row.values[key] = value;
20091
+ this.host.onChange?.({ indicatorId: row.id, key, value });
20092
+ this.refreshVisibility?.();
20093
+ }
20001
20094
  /** One settings row (or several `inline=` inputs) placed into a section's grid. */
20002
20095
  buildInputRow(grid, decls, row) {
20003
20096
  const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
20097
+ const append = (el) => {
20098
+ grid.appendChild(el);
20099
+ return el;
20100
+ };
20004
20101
  if (decls.length > 1) {
20005
- grid.appendChild(fieldRow({
20102
+ return append(fieldRow({
20006
20103
  label: "",
20007
20104
  inline: decls.map((d) => ({
20008
20105
  label: nameOf(d) || void 0,
@@ -20013,14 +20110,13 @@ ${overlayScrollbarCss(".vela-dialog *")}
20013
20110
  })),
20014
20111
  info: [...decls].reverse().find((d) => d.tooltip)?.tooltip ? this.infoButton([...decls].reverse().find((d) => d.tooltip).tooltip) : void 0
20015
20112
  }));
20016
- return;
20017
20113
  }
20018
20114
  const lead = decls[0];
20019
20115
  const id = idOf(lead);
20020
20116
  const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
20021
20117
  if (lead.type === "bool") {
20022
20118
  const current = Boolean(row.values[lead.key] ?? lead.defval);
20023
- grid.appendChild(fieldRow({
20119
+ return append(fieldRow({
20024
20120
  label: nameOf(lead),
20025
20121
  id,
20026
20122
  bool: true,
@@ -20028,26 +20124,21 @@ ${overlayScrollbarCss(".vela-dialog *")}
20028
20124
  toggle: {
20029
20125
  id,
20030
20126
  checked: current,
20031
- onChange: (v) => {
20032
- row.values[lead.key] = v;
20033
- this.host.onChange?.({ indicatorId: row.id, key: lead.key, value: v });
20034
- }
20127
+ onChange: (v) => this.commit(row, lead.key, v)
20035
20128
  }
20036
20129
  }));
20037
- return;
20038
20130
  }
20039
20131
  if (lead.type === "text_area") {
20040
- grid.appendChild(fieldRow({
20132
+ return append(fieldRow({
20041
20133
  label: nameOf(lead),
20042
20134
  id,
20043
20135
  stacked: true,
20044
20136
  info,
20045
20137
  control: this.buildControl(row, lead, id)
20046
20138
  }));
20047
- return;
20048
20139
  }
20049
20140
  const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
20050
- grid.appendChild(fieldRow({
20141
+ return append(fieldRow({
20051
20142
  label: nameOf(lead),
20052
20143
  id,
20053
20144
  fit,
@@ -20059,10 +20150,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
20059
20150
  /** Build the typed control for one input, committing edits live via `onChange`. */
20060
20151
  buildControl(row, inp, id) {
20061
20152
  const current = row.values[inp.key] ?? inp.defval;
20062
- const emit = (value) => {
20063
- row.values[inp.key] = value;
20064
- this.host.onChange?.({ indicatorId: row.id, key: inp.key, value });
20065
- };
20153
+ const emit = (value) => this.commit(row, inp.key, value);
20066
20154
  if (inp.type === "bool") {
20067
20155
  return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
20068
20156
  }
@@ -20483,6 +20571,14 @@ ${overlayScrollbarCss(".vela-dialog *")}
20483
20571
  }
20484
20572
  return names.map((name) => ({ name, inputs: byTab.get(name) }));
20485
20573
  }
20574
+ function rowVisible(decls, bag) {
20575
+ return decls.some((d) => inputVisible(d.when, bag));
20576
+ }
20577
+ function valuesBag(row) {
20578
+ const bag = {};
20579
+ for (const inp of row.inputs) bag[inp.key] = row.values[inp.key] ?? inp.defval;
20580
+ return bag;
20581
+ }
20486
20582
  function groupInputs(inputs) {
20487
20583
  const order = [];
20488
20584
  const byGroup = /* @__PURE__ */ new Map();
@@ -20945,6 +21041,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
20945
21041
  host: this.container,
20946
21042
  items,
20947
21043
  placement: "bottom-start",
21044
+ // Right-click action menu — checked state reads as a leading ✓ (see the kit Menu).
21045
+ checkmarks: true,
20948
21046
  onSelect: (itemId) => {
20949
21047
  if (itemId !== "values" || !this.rowMenuId) return;
20950
21048
  const target = this.rows.get(this.rowMenuId);
@@ -21280,6 +21378,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
21280
21378
  syncRowActions(row) {
21281
21379
  const open2 = row.highlighted;
21282
21380
  row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
21381
+ if (open2) row.el.appendChild(row.statusEl);
21382
+ else row.el.insertBefore(row.statusEl, row.valuesEl);
21283
21383
  for (const child of Array.from(row.controlsEl.children)) {
21284
21384
  if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
21285
21385
  if (child === row.extrasEl) {
@@ -21523,6 +21623,148 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
21523
21623
  }
21524
21624
  };
21525
21625
 
21626
+ // src/renderers/native/chrome/axisLayout.ts
21627
+ var AXIS_MASTER_W = 64;
21628
+ var AXIS_MERGED_W = 56;
21629
+ var PANE_SEPARATOR_PX = 3;
21630
+ function axisColumnX(dataW, column) {
21631
+ return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
21632
+ }
21633
+
21634
+ // src/renderers/native/chrome/AxisScaleButtons.ts
21635
+ var STYLE_ID2 = "vela-axis-scale-buttons";
21636
+ var BTN_PX = 18;
21637
+ var BTN_GAP = 4;
21638
+ var AXIS_BORDER = 1;
21639
+ var CLUSTER_W = AXIS_MASTER_W - AXIS_BORDER;
21640
+ var PAD_TOP = 8;
21641
+ var PAD_BOTTOM = 6;
21642
+ var CLUSTER_H = BTN_PX + PAD_TOP + PAD_BOTTOM;
21643
+ var MIN_PANE_H = 48;
21644
+ function ensureStyles2() {
21645
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID2)) return;
21646
+ const st = document.createElement("style");
21647
+ st.id = STYLE_ID2;
21648
+ st.textContent = `
21649
+ .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);}
21650
+ .vela-axis-btn:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
21651
+ .vela-axis-on,.vela-axis-on:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
21652
+ `;
21653
+ document.head.appendChild(st);
21654
+ }
21655
+ var AxisScaleButtons = class {
21656
+ constructor(plot, theme, deps) {
21657
+ this.plot = plot;
21658
+ this.theme = theme;
21659
+ this.deps = deps;
21660
+ this.tipDisposers = [];
21661
+ this.hoverPaneId = null;
21662
+ /** Reveal over the master scale column of the pane under the cursor (mouse only —
21663
+ * touch has no hover, and a drag-rescale on the axis must not sprout buttons). */
21664
+ this.onPlotMove = (e) => {
21665
+ if (e.pointerType !== "mouse") return;
21666
+ const rect = this.plot.getBoundingClientRect();
21667
+ const x = e.clientX - rect.left;
21668
+ const gutterLeft = rect.width - this.deps.rightAxis();
21669
+ let hit = null;
21670
+ if (x >= gutterLeft && x <= gutterLeft + AXIS_MASTER_W) {
21671
+ const y = e.clientY - rect.top;
21672
+ for (const p of this.deps.panes()) {
21673
+ if (p.collapsed || p.height < MIN_PANE_H) continue;
21674
+ if (y >= p.top && y <= p.top + p.height) {
21675
+ hit = p.id;
21676
+ break;
21677
+ }
21678
+ }
21679
+ }
21680
+ this.setHoverPane(hit);
21681
+ if (hit) this.sync();
21682
+ };
21683
+ this.onPlotLeave = () => this.setHoverPane(null);
21684
+ ensureStyles2();
21685
+ this.cluster = document.createElement("div");
21686
+ Object.assign(this.cluster.style, {
21687
+ position: "absolute",
21688
+ display: "none",
21689
+ justifyContent: "center",
21690
+ alignItems: "center",
21691
+ gap: `${BTN_GAP}px`,
21692
+ padding: `${PAD_TOP}px 0 ${PAD_BOTTOM}px`,
21693
+ width: `${CLUSTER_W}px`,
21694
+ boxSizing: "border-box",
21695
+ zIndex: "6",
21696
+ pointerEvents: "auto"
21697
+ });
21698
+ this.applyTheme();
21699
+ this.autoBtn = this.button("A", "Auto (fits data to screen)", () => this.deps.onToggleAuto(this.hoverPaneId ?? ""));
21700
+ this.logBtn = this.button("L", "Logarithmic scale", () => this.deps.onToggleLog(this.hoverPaneId ?? ""));
21701
+ this.cluster.append(this.autoBtn, this.logBtn);
21702
+ this.plot.appendChild(this.cluster);
21703
+ this.plot.addEventListener("pointermove", this.onPlotMove);
21704
+ this.plot.addEventListener("pointerleave", this.onPlotLeave);
21705
+ }
21706
+ button(label, title, onClick) {
21707
+ const b = document.createElement("button");
21708
+ b.type = "button";
21709
+ b.setAttribute("aria-label", title);
21710
+ b.textContent = label;
21711
+ b.className = "vela-axis-btn";
21712
+ Object.assign(b.style, { width: `${BTN_PX}px`, height: `${BTN_PX}px` });
21713
+ this.tipDisposers.push(attachChromeTooltip(b, {
21714
+ host: this.plot,
21715
+ theme: () => this.theme,
21716
+ text: () => title,
21717
+ placement: "above"
21718
+ }));
21719
+ b.addEventListener("click", (e) => {
21720
+ e.stopPropagation();
21721
+ onClick();
21722
+ this.sync();
21723
+ });
21724
+ return b;
21725
+ }
21726
+ /** Re-tint the chip when the plot background/border change (theme swap, config edit). */
21727
+ setTheme(theme) {
21728
+ this.theme = theme;
21729
+ this.applyTheme();
21730
+ }
21731
+ /** The strip is SOLID chart background: the buttons cover axis labels, never blend with them. */
21732
+ applyTheme() {
21733
+ this.cluster.style.background = this.theme.background;
21734
+ }
21735
+ setHoverPane(paneId) {
21736
+ if (this.hoverPaneId === paneId) return;
21737
+ this.hoverPaneId = paneId;
21738
+ this.reposition();
21739
+ }
21740
+ /** Re-place (or hide) the cluster after a hover or pane-layout change. */
21741
+ reposition() {
21742
+ const pane = this.hoverPaneId ? this.deps.panes().find((p) => p.id === this.hoverPaneId) : void 0;
21743
+ if (!pane || pane.collapsed || pane.height < MIN_PANE_H) {
21744
+ this.cluster.style.display = "none";
21745
+ return;
21746
+ }
21747
+ this.cluster.style.right = `${this.deps.rightAxis() - AXIS_MASTER_W}px`;
21748
+ this.cluster.style.top = `${pane.top + pane.height - CLUSTER_H}px`;
21749
+ this.cluster.style.display = "flex";
21750
+ this.sync();
21751
+ }
21752
+ /** Reflect the hovered pane's current auto/log state on the buttons. */
21753
+ sync() {
21754
+ const pane = this.deps.panes().find((p) => p.id === this.hoverPaneId);
21755
+ if (!pane) return;
21756
+ this.autoBtn.classList.toggle("vela-axis-on", pane.auto);
21757
+ this.logBtn.classList.toggle("vela-axis-on", pane.log);
21758
+ }
21759
+ destroy() {
21760
+ this.plot.removeEventListener("pointermove", this.onPlotMove);
21761
+ this.plot.removeEventListener("pointerleave", this.onPlotLeave);
21762
+ for (const dispose of this.tipDisposers) dispose();
21763
+ this.tipDisposers.length = 0;
21764
+ this.cluster.remove();
21765
+ }
21766
+ };
21767
+
21526
21768
  // src/renderers/shared/TableOverlay.ts
21527
21769
  var SIZE_PX3 = {
21528
21770
  auto: 13,
@@ -21880,6 +22122,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
21880
22122
  var BASELINE_FILL_ALPHA = 0.25;
21881
22123
  var BASELINE_FILL_ALPHA_FAR = 0.05;
21882
22124
  var BASELINE_LEVEL_DEFAULT = 50;
22125
+ var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
22126
+ var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
21883
22127
  function defaultChartStyle() {
21884
22128
  return {
21885
22129
  chartTypes: {},
@@ -21910,7 +22154,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
21910
22154
  bottomFillColor2: null,
21911
22155
  width: 2,
21912
22156
  baselineLevel: BASELINE_LEVEL_DEFAULT
21913
- }
22157
+ },
22158
+ sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
21914
22159
  };
21915
22160
  }
21916
22161
  var LINE_STYLES = ["solid", "dashed", "dotted"];
@@ -22044,6 +22289,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22044
22289
  const area = asObject(p.area);
22045
22290
  const baseline = asObject(p.baseline);
22046
22291
  const series = asObject(p.series);
22292
+ const sessions = asObject(p.sessions);
22047
22293
  const stacking = asObject(p.stacking);
22048
22294
  const stackSeries = asObject(stacking.series);
22049
22295
  return {
@@ -22136,6 +22382,10 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22136
22382
  baseline: series.baseline === null ? null : isNum(series.baseline) ? series.baseline : base.series.baseline,
22137
22383
  spacing: isNum(series.spacing) ? clampSpacing(series.spacing) : base.series.spacing
22138
22384
  },
22385
+ sessions: {
22386
+ premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
22387
+ postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
22388
+ },
22139
22389
  stacking: {
22140
22390
  candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
22141
22391
  // Additive like `chartTypes`: a patch names only the ids it carries; the rest keep
@@ -22382,6 +22632,8 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22382
22632
  this.tradeMarkers = defaultTradeMarkersState();
22383
22633
  /** Renderer-owned shaded time bands (session highlighting), behind grid + data. */
22384
22634
  this.highlights = [];
22635
+ /** Pre/post-market bands pushed by the host (`sessionZones` feature); null ⇒ no sessions. */
22636
+ this.sessionZones = null;
22385
22637
  /** Draw-order key of the price candles, relative to indicator series z (see `seriesZ`).
22386
22638
  * Indicators with z below this draw BEHIND the candles; at/above draw in front.
22387
22639
  * Default 0 with indicators mounting at z < 0 ⇒ the price reads on top of every overlay,
@@ -22416,6 +22668,15 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22416
22668
  if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
22417
22669
  return this.orderedCache;
22418
22670
  }
22671
+ /** The session zones resolved into colored bands (pre/post-market washes from the
22672
+ * config's session colors) — consumed by the same painting path as {@link highlights}. */
22673
+ sessionHighlightBands() {
22674
+ if (!this.sessionZones) return [];
22675
+ const out = [];
22676
+ for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
22677
+ for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
22678
+ return out;
22679
+ }
22419
22680
  indicatorsForPane(paneId) {
22420
22681
  const out = [];
22421
22682
  for (const model of this.indicators.values()) if (model.paneId === paneId) out.push(model);
@@ -22499,6 +22760,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22499
22760
  assignIndicatorZ(id) {
22500
22761
  if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.bottomZ() - 1);
22501
22762
  }
22763
+ /** Mount-time default for a LAYER-BACKED native (it paints on a canvas stacked above the
22764
+ * data canvas by default): top of the stack, so the recorded order tells the truth from
22765
+ * the first frame. Keeps an existing key, so a restored stack survives the remount. */
22766
+ assignIndicatorZTop(id) {
22767
+ if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.topZ() + 1);
22768
+ }
22502
22769
  forgetIndicatorZ(id) {
22503
22770
  this.seriesZ.delete(id);
22504
22771
  }
@@ -23346,10 +23613,12 @@ void main() {
23346
23613
  }
23347
23614
  }
23348
23615
  }
23349
- /** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height). */
23616
+ /** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
23617
+ * Session-zone washes (pre/post-market) paint first, host highlights on top. */
23350
23618
  emitHighlights(b, scene, pane, coords) {
23351
- if (scene.highlights.length === 0) return;
23352
- for (const band of scene.highlights) {
23619
+ const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
23620
+ if (bands.length === 0) return;
23621
+ for (const band of bands) {
23353
23622
  const x1 = coords.timeToX(band.from);
23354
23623
  const x2 = coords.timeToX(band.to);
23355
23624
  if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
@@ -25323,10 +25592,12 @@ void main() {
25323
25592
  ctx.fillStyle = bg.color;
25324
25593
  ctx.fillRect(x1, pane.bounds.top, x2 - x1, pane.bounds.height);
25325
25594
  }
25326
- /** Renderer-owned session highlight bands: full-height (all panes), behind grid + data. */
25595
+ /** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
25596
+ * Session-zone washes (pre/post-market) paint first, host highlights on top. */
25327
25597
  drawHighlights(ctx, scene, coords) {
25328
- if (scene.highlights.length === 0) return;
25329
- for (const band of scene.highlights) {
25598
+ const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
25599
+ if (bands.length === 0) return;
25600
+ for (const band of bands) {
25330
25601
  const x1 = coords.timeToX(band.from);
25331
25602
  const x2 = coords.timeToX(band.to);
25332
25603
  if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
@@ -26100,14 +26371,6 @@ void main() {
26100
26371
  }
26101
26372
  };
26102
26373
 
26103
- // src/renderers/native/chrome/axisLayout.ts
26104
- var AXIS_MASTER_W = 64;
26105
- var AXIS_MERGED_W = 56;
26106
- var PANE_SEPARATOR_PX = 3;
26107
- function axisColumnX(dataW, column) {
26108
- return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
26109
- }
26110
-
26111
26374
  // src/renderers/native/chrome/ChromeRenderer.ts
26112
26375
  var ChromeRenderer = class {
26113
26376
  constructor() {
@@ -26990,6 +27253,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
26990
27253
  for (const hr of hs.rows) {
26991
27254
  if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
26992
27255
  else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
27256
+ else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
26993
27257
  else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options.map((o) => [o, o]), (v) => hr.set(v)));
26994
27258
  }
26995
27259
  }
@@ -29714,14 +29978,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29714
29978
  // src/renderers/native/drawings/DrawingSettingsPopup.ts
29715
29979
  var BTN = 30;
29716
29980
  var ICON = 21;
29717
- var STYLE_ID2 = "vela-drawing-popup-styles";
29981
+ var STYLE_ID3 = "vela-drawing-popup-styles";
29718
29982
  var STYLE_REV = "3";
29719
- function ensureStyles2() {
29983
+ function ensureStyles3() {
29720
29984
  if (typeof document === "undefined") return;
29721
- const existing = document.getElementById(STYLE_ID2);
29985
+ const existing = document.getElementById(STYLE_ID3);
29722
29986
  if (existing?.dataset.rev === STYLE_REV) return;
29723
29987
  const s = existing ?? document.createElement("style");
29724
- s.id = STYLE_ID2;
29988
+ s.id = STYLE_ID3;
29725
29989
  s.dataset.rev = STYLE_REV;
29726
29990
  s.textContent = `
29727
29991
  .vela-dpop-btn{background:transparent;color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
@@ -29778,7 +30042,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29778
30042
  * `onClose` fires on an outside-click dismissal (not a programmatic close). */
29779
30043
  open(drawing, anchor, actions, onClose) {
29780
30044
  this.close();
29781
- ensureStyles2();
30045
+ ensureStyles3();
29782
30046
  this.onClose = onClose ?? null;
29783
30047
  const t = this.theme;
29784
30048
  const schema = drawing.schema();
@@ -30506,7 +30770,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30506
30770
  this.onCollapse = options.onCollapse ?? (() => {
30507
30771
  });
30508
30772
  this.onDrawingsSync = options.onDrawingsSync ?? null;
30509
- ensureStyles3();
30773
+ ensureStyles4();
30510
30774
  this.root = document.createElement("div");
30511
30775
  this.root.className = "vela-dtb";
30512
30776
  this.styleRoot();
@@ -30989,13 +31253,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30989
31253
  }
30990
31254
  };
30991
31255
  var MAGNET_ID = "__magnet__";
30992
- var STYLE_ID3 = "vela-drawing-toolbar-styles";
30993
- function ensureStyles3() {
31256
+ var STYLE_ID4 = "vela-drawing-toolbar-styles";
31257
+ function ensureStyles4() {
30994
31258
  if (typeof document === "undefined") return;
30995
- let s = document.getElementById(STYLE_ID3);
31259
+ let s = document.getElementById(STYLE_ID4);
30996
31260
  if (!s) {
30997
31261
  s = document.createElement("style");
30998
- s.id = STYLE_ID3;
31262
+ s.id = STYLE_ID4;
30999
31263
  document.head.appendChild(s);
31000
31264
  }
31001
31265
  s.textContent = `
@@ -32260,13 +32524,26 @@ ${overlayScrollbarCss(".vela-sd-pane")}
32260
32524
  return [...registry3.values()];
32261
32525
  }
32262
32526
 
32527
+ // src/renderers/native/core/layerStacking.ts
32528
+ function stackLayers(entries, candleZ) {
32529
+ const keyed = entries.map((e) => ({
32530
+ id: e.id,
32531
+ key: e.ownerZ ?? (e.placement === "below-data" ? -Infinity : candleZ)
32532
+ }));
32533
+ keyed.sort((a, b) => a.key - b.key);
32534
+ const below = [];
32535
+ const above = [];
32536
+ for (const k of keyed) (k.key < candleZ ? below : above).push(k.id);
32537
+ return { below, above };
32538
+ }
32539
+
32263
32540
  // src/renderers/native/chrome/luxalgo-logos.ts
32264
32541
  var LUXALGO_SYMBOL_SVG = '<svg viewBox="0 0 45 40" aria-hidden="true"><g fill="currentColor"><path d="m40.25 38 4.58-7.998L28.802 2l-16.03 28 9.16-.001 6.87-12z"/><path d="M34.525 32.002 9.33 31.997 27.655 0h-9.158L.18 31.993 4.759 40h34.347z"/></g></svg>';
32265
32542
  var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path fill="currentColor" fill-rule="evenodd" d="M 614 94.500 L 614 189 633.500 189 L 653 189 653 94.500 L 653 0 633.500 0 L 614 0 614 94.500 M 0 97.500 L 0 189 63.023 189 L 126.045 189 125.773 170.750 L 125.500 152.500 84.250 152.239 L 43 151.978 43 78.989 L 43 6 21.500 6 L 0 6 0 97.500 M 450.387 96.451 C 421.871 146.253, 398.306 186.994, 398.020 186.987 C 397.423 186.972, 362 124.724, 362 123.691 C 362 123.316, 370.100 108.818, 380 91.472 C 389.900 74.127, 398 59.500, 398 58.968 C 398 58.380, 391.007 58, 380.210 58 L 362.420 58 348.060 83.138 C 334.400 107.052, 333.637 108.160, 332.399 105.888 C 331.682 104.575, 325.287 93.375, 318.187 81 L 305.277 58.500 287.139 58.227 C 277.162 58.078, 269 58.186, 269 58.469 C 269 58.751, 277.154 73.274, 287.120 90.741 C 297.086 108.209, 305.261 122.983, 305.287 123.574 C 305.312 124.164, 297.318 138.789, 287.522 156.074 C 277.725 173.358, 269.524 187.838, 269.296 188.250 C 269.068 188.662, 276.977 189, 286.870 189 L 304.859 189 319.163 163.959 C 331.321 142.674, 333.647 139.165, 334.670 140.563 C 335.331 141.468, 341.864 152.737, 349.186 165.606 L 362.500 189.003 402.442 188.752 L 442.384 188.500 472.080 136.695 C 488.413 108.203, 501.939 84.729, 502.138 84.531 C 502.337 84.333, 516 107.757, 532.500 136.583 L 562.500 188.994 584.750 188.997 C 596.987 188.999, 607 188.854, 607 188.675 C 607 188.319, 503.014 6.686, 502.532 6.201 C 502.369 6.037, 478.904 46.649, 450.387 96.451 M 717.500 55.017 C 697.175 57.389, 680.641 68.752, 672.036 86.264 C 665.866 98.818, 663.840 108.024, 663.840 123.500 C 663.840 133.409, 664.328 138.355, 665.892 144.300 C 672.076 167.810, 689.191 184.757, 711.529 189.489 C 730.549 193.518, 747.264 189.540, 761.282 177.648 C 764.944 174.542, 768.178 172, 768.470 172 C 769.345 172, 769.030 185.018, 767.979 192.285 C 765.618 208.613, 755.148 218.672, 738.473 220.632 C 732.586 221.324, 723.136 219.599, 717.709 216.843 C 712.693 214.295, 706.636 207.946, 703.972 202.443 C 702.789 199.999, 701.749 198, 701.661 198.001 C 700.201 198.007, 665.596 205.071, 665.230 205.437 C 664.326 206.341, 669.156 218.313, 672.607 223.721 C 682.146 238.672, 700.008 248.257, 723.500 251.033 C 758.132 255.124, 788.930 241.875, 800.287 218 C 806.923 204.047, 807 203.001, 807 126.542 L 807 58 788.500 58 L 770 58 770 66.500 C 770 71.175, 769.641 75, 769.203 75 C 768.765 75, 766.627 73.142, 764.453 70.872 C 752.805 58.708, 735.614 52.903, 717.500 55.017 M 878.500 55.031 C 850.107 59.412, 830.755 75.064, 822.285 100.500 C 819.905 107.644, 819.614 110.051, 819.565 123 C 819.501 139.581, 820.786 145.825, 826.763 158 C 832.104 168.880, 842.842 179.424, 854.077 184.822 C 867.651 191.343, 873.132 192.462, 891.500 192.462 C 909.868 192.462, 915.349 191.343, 928.923 184.822 C 944.653 177.265, 956.667 162.306, 961.710 144 C 963.074 139.048, 963.481 134.047, 963.418 123 C 963.346 110.140, 963.038 107.595, 960.694 100.500 C 953.325 78.193, 936.695 62.854, 913.500 56.969 C 904.262 54.625, 887.240 53.682, 878.500 55.031 M 134.015 102.750 C 134.027 139.250, 134.310 148.689, 135.549 153.947 C 141.139 177.663, 159.815 193, 183.105 193 C 196.453 193, 210.163 186.171, 217.936 175.650 C 219.826 173.093, 221.738 171, 222.186 171 C 222.634 171, 223 175.050, 223 180 L 223 189 242.507 189 L 262.014 189 261.757 123.750 L 261.500 58.500 242.500 58.500 L 223.500 58.500 223 99.500 C 222.513 139.406, 222.441 140.610, 220.289 144.634 C 209.729 164.377, 183.014 164.880, 174.299 145.500 C 172.677 141.895, 172.469 137.382, 172.190 99.750 L 171.880 58 152.940 58 L 134 58 134.015 102.750 M 729.932 87.998 C 719.311 89.821, 710.155 97.164, 705.141 107.880 C 702.851 112.772, 702.510 114.798, 702.505 123.521 C 702.500 132.834, 702.732 134.014, 705.787 140.218 C 709.761 148.290, 715.989 154.097, 723.853 157.063 C 731.409 159.912, 744.359 159.466, 750.795 156.136 C 767.289 147.601, 774.103 127.713, 767.019 108.780 C 761.433 93.851, 746.051 85.232, 729.932 87.998 M 883.972 88.348 C 865.749 92.310, 854.873 109.966, 858.119 130.316 C 860.933 147.963, 872.346 158.830, 889.084 159.799 C 900.714 160.472, 906.867 158.234, 914.650 150.497 C 919.856 145.323, 921.123 143.340, 922.900 137.587 C 925.412 129.456, 925.655 118.570, 923.489 111.275 C 918.479 94.405, 901.478 84.541, 883.972 88.348"/></svg>';
32266
32543
 
32267
32544
  // src/renderers/native/chrome/AttributionMark.ts
32268
32545
  var ATTRIBUTION_URL = "https://luxalgo.com/vela";
32269
- var STYLE_ID4 = "vela-attribution-styles";
32546
+ var STYLE_ID5 = "vela-attribution-styles";
32270
32547
  var CSS2 = `
32271
32548
  .vela-attribution {
32272
32549
  text-decoration: none;
@@ -32329,11 +32606,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
32329
32606
  [data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
32330
32607
  [data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
32331
32608
  `;
32332
- function ensureStyles4(doc) {
32333
- let s = doc.getElementById(STYLE_ID4);
32609
+ function ensureStyles5(doc) {
32610
+ let s = doc.getElementById(STYLE_ID5);
32334
32611
  if (!s) {
32335
32612
  s = doc.createElement("style");
32336
- s.id = STYLE_ID4;
32613
+ s.id = STYLE_ID5;
32337
32614
  doc.head.appendChild(s);
32338
32615
  }
32339
32616
  s.textContent = CSS2;
@@ -32362,7 +32639,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
32362
32639
  return el;
32363
32640
  }
32364
32641
  function createAttributionMark(doc, background) {
32365
- ensureStyles4(doc);
32642
+ ensureStyles5(doc);
32366
32643
  const a = doc.createElement("a");
32367
32644
  a.className = "vela-attribution";
32368
32645
  a.href = ATTRIBUTION_URL;
@@ -32760,6 +33037,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
32760
33037
  this.volumeRenderer = new VolumeRenderer();
32761
33038
  /** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
32762
33039
  this.extLayers = [];
33040
+ /** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
33041
+ this.layerOrderSig = "";
32763
33042
  // The attribution mark (see chrome/AttributionMark + the NOTICE file): default-on;
32764
33043
  // disabling requires an equivalent visible attribution elsewhere in the host UI.
32765
33044
  this.attributionEl = null;
@@ -32874,6 +33153,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
32874
33153
  // total right-gutter width (master column + merged-scale columns)
32875
33154
  this.paneControls = null;
32876
33155
  // per-pane hover button cluster (top-right)
33156
+ this.axisScaleButtons = null;
33157
+ // A/L hover buttons at the bottom of each pane's price scale
32877
33158
  this.paneActionCbs = /* @__PURE__ */ new Set();
32878
33159
  // ── data window (crosshair OHLC + per-series readout, pulled by host panels) ──
32879
33160
  this.hoverLogical = null;
@@ -32917,7 +33198,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
32917
33198
  this.priceStyleCbs = /* @__PURE__ */ new Set();
32918
33199
  this.tableOverlays = /* @__PURE__ */ new Map();
32919
33200
  this.name = "native";
32920
- 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"];
33201
+ 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"];
32921
33202
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
32922
33203
  * so moving onto the button doesn't count as leaving). */
32923
33204
  this.onScrollProximityMove = (e) => {
@@ -33033,6 +33314,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33033
33314
  case "highlights":
33034
33315
  this.scene.highlights = sanitizeHighlights(value);
33035
33316
  break;
33317
+ case "sessionZones":
33318
+ this.scene.sessionZones = sanitizeSessionZones(value);
33319
+ break;
33036
33320
  case "gridlines":
33037
33321
  this.scene.showGrid = Boolean(value);
33038
33322
  break;
@@ -33149,6 +33433,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33149
33433
  return this.scene.indicatorZOrder();
33150
33434
  case "highlights":
33151
33435
  return this.scene.highlights;
33436
+ case "sessionZones":
33437
+ return this.scene.sessionZones;
33152
33438
  case "gridlines":
33153
33439
  return this.scene.showGrid;
33154
33440
  case "axisLabels":
@@ -33358,7 +33644,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33358
33644
  baselineLevel: s.baseline.baselineLevel
33359
33645
  };
33360
33646
  })(),
33361
- series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale }
33647
+ series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
33648
+ sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
33362
33649
  };
33363
33650
  }
33364
33651
  /**
@@ -33440,6 +33727,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33440
33727
  width: next2.baseline.width,
33441
33728
  baselineLevel: next2.baseline.baselineLevel
33442
33729
  };
33730
+ s.sessions = { premarketColor: next2.sessions.premarketColor, postmarketColor: next2.sessions.postmarketColor };
33443
33731
  this.setPriceStyle(next2.series.style);
33444
33732
  this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
33445
33733
  this.scene.baselineValue = next2.series.baseline;
@@ -33453,6 +33741,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33453
33741
  }
33454
33742
  this.inputsUI?.setTheme(this.theme);
33455
33743
  this.paneControls?.setTheme(this.theme);
33744
+ this.axisScaleButtons?.setTheme(this.theme);
33456
33745
  this.userDrawings?.setTheme(this.chromeTheme());
33457
33746
  this.refreshScrollButtonTheme();
33458
33747
  this.refreshAttributionColor();
@@ -33680,9 +33969,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33680
33969
  if (el.getAttribute("data-vela-screenshot") === "under") rasterizeOverlay(ctx, el, frame);
33681
33970
  }
33682
33971
  }
33683
- const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
33684
- const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
33685
- for (const canvas of [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas]) {
33972
+ for (const canvas of [...this.canvasPile(), this.chromeCanvas, this.drawingsCanvas]) {
33686
33973
  if (canvas && canvas.width > 0 && canvas.height > 0) ctx.drawImage(canvas, 0, 0);
33687
33974
  }
33688
33975
  if (frame) {
@@ -33791,7 +34078,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33791
34078
  this.surfaceBackground = theme.background;
33792
34079
  this.surfaceTextColor = theme.textColor;
33793
34080
  this.wrapper = document.createElement("div");
33794
- Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair" });
34081
+ Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair", userSelect: "none", webkitUserSelect: "none" });
33795
34082
  applyChromeTokens(this.wrapper, this.chromeTheme());
33796
34083
  this.volumeCanvas = document.createElement("canvas");
33797
34084
  Object.assign(this.volumeCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
@@ -33816,6 +34103,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33816
34103
  const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
33817
34104
  const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
33818
34105
  this.plot.append(...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
34106
+ this.layerOrderSig = "";
33819
34107
  this.wrapper.appendChild(this.plot);
33820
34108
  this.factoryConfig = this.getConfig();
33821
34109
  this.attributionEl = this.buildAttributionEl();
@@ -33960,6 +34248,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33960
34248
  this.emitPaneAction({ type: "maximize", paneId, maximized });
33961
34249
  }
33962
34250
  });
34251
+ this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
34252
+ panes: () => this.axisScaleViews(),
34253
+ rightAxis: () => this.rightAxisW,
34254
+ onToggleAuto: (paneId) => this.togglePaneAuto(paneId),
34255
+ onToggleLog: (paneId) => {
34256
+ const pane = this.scene.panes.get(paneId);
34257
+ if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
34258
+ }
34259
+ });
33963
34260
  this.resizeObserver = new ResizeObserver(() => this.resize());
33964
34261
  this.resizeObserver.observe(this.wrapper);
33965
34262
  this.watchDpr();
@@ -34036,6 +34333,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34036
34333
  this.applyBackground();
34037
34334
  this.inputsUI.setTheme(theme);
34038
34335
  this.paneControls?.setTheme(this.theme);
34336
+ this.axisScaleButtons?.setTheme(this.theme);
34039
34337
  this.settingsDialog?.refreshConfig(this.getConfig());
34040
34338
  this.syncThemeControl();
34041
34339
  this.settingsDialog?.setTheme(this.theme);
@@ -34084,6 +34382,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34084
34382
  this.liveRegion = null;
34085
34383
  this.inputsUI?.destroy();
34086
34384
  this.paneControls?.destroy();
34385
+ this.axisScaleButtons?.destroy();
34087
34386
  for (const overlay of this.tableOverlays.values()) overlay.destroy();
34088
34387
  this.tableOverlays.clear();
34089
34388
  this.resizeObserver?.disconnect();
@@ -34333,7 +34632,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34333
34632
  mountIndicator(model) {
34334
34633
  this.scene.indicators.set(model.id, model);
34335
34634
  this.refreshAnchorOffset(model);
34336
- this.scene.assignIndicatorZ(model.id);
34635
+ if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
34636
+ else this.scene.assignIndicatorZ(model.id);
34337
34637
  this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
34338
34638
  native: !!model.native,
34339
34639
  ...model.shorttitle ? { settingsTitle: model.title } : {}
@@ -35152,10 +35452,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35152
35452
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
35153
35453
  for (const l of this.extLayers) {
35154
35454
  if (!l.def.repaintOnCursor) continue;
35155
- l.instance.render(this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs));
35455
+ const lp = this.layerPane(l.def.id) ?? pane;
35456
+ if (lp.collapsed) continue;
35457
+ l.instance.render(this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs));
35156
35458
  if (this.animZoom && l.instance.animating?.()) this.animator.start();
35157
35459
  }
35158
35460
  }
35461
+ /** Blank one SDK layer canvas (a collapsed host pane suppresses the layer's painting). */
35462
+ clearLayerCanvas(canvas) {
35463
+ if (canvas.width === 0 || canvas.height === 0) return;
35464
+ canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
35465
+ }
35159
35466
  /** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths). */
35160
35467
  extLayerArgs(id, scale, bounds, nowMs) {
35161
35468
  return {
@@ -35174,6 +35481,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35174
35481
  }
35175
35482
  /** Paint the below-data (L-1) + geometry (L0) + chrome (L1) layers from the current scene/coords. */
35176
35483
  paintData() {
35484
+ this.syncLayerCanvasOrder();
35177
35485
  this.stampScaleInvert();
35178
35486
  this.backend.modelAlpha = this.modelAlpha;
35179
35487
  this.backend.candleBodyAlpha = this.candleBodyAlpha;
@@ -35204,9 +35512,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35204
35512
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
35205
35513
  let folded = null;
35206
35514
  for (const l of this.extLayers) {
35207
- const args = this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs);
35515
+ const lp = this.layerPane(l.def.id) ?? pane;
35516
+ if (lp.collapsed) {
35517
+ this.clearLayerCanvas(l.canvas);
35518
+ continue;
35519
+ }
35520
+ const args = this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs);
35208
35521
  l.instance.render(args);
35209
- folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
35522
+ if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
35210
35523
  if (this.animZoom && l.instance.animating?.()) this.animator.start();
35211
35524
  }
35212
35525
  if (folded) {
@@ -35368,6 +35681,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35368
35681
  pane.scaleTarget = { min: 0, max: maxVol / VOLUME_PANE_FILL_FRAC };
35369
35682
  pane.axisFormat = "volume";
35370
35683
  }
35684
+ } else if (this.layerNativesOwnPane(pane, masterModels)) {
35685
+ pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
35686
+ pane.percentBaseline = this.bars[i0]?.close ?? 0;
35371
35687
  }
35372
35688
  if (pane === pricePane && this.scene.tradeMarkers.visible && pane.bounds.height > 0) {
35373
35689
  const th = this.tradesScaleHints(vr);
@@ -35454,6 +35770,59 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35454
35770
  }
35455
35771
  return null;
35456
35772
  }
35773
+ /** The mounted native indicator that OWNS an SDK layer — the one whose type equals the
35774
+ * layer id (the id doubles as the data channel, so the pairing is the SDK's own
35775
+ * contract). Null for chart-type channels and while the owner is hidden (a hidden
35776
+ * indicator leaves the scene; its cleared data channel paints nothing anyway). */
35777
+ layerOwner(layerId) {
35778
+ for (const m of this.scene.indicators.values()) {
35779
+ if (m.native?.type === layerId) return m;
35780
+ }
35781
+ return null;
35782
+ }
35783
+ /** The pane an SDK layer paints on: its owner's pane, else the price pane. */
35784
+ layerPane(layerId) {
35785
+ const owner = this.layerOwner(layerId);
35786
+ const paneId = owner ? owner.paneId ?? PRICE_PANE_ID : PRICE_PANE_ID;
35787
+ return this.scene.panes.get(paneId) ?? null;
35788
+ }
35789
+ /** The SDK layer canvases split around the data canvas, each side back-to-front:
35790
+ * owned layers by their owner's z key against the candles' (an indicator restacked
35791
+ * below the candles takes its layer canvas along), unowned by declared placement. */
35792
+ orderedLayerCanvases() {
35793
+ const byId = new Map(this.extLayers.map((l) => [l.def.id, l.canvas]));
35794
+ const { below, above } = stackLayers(
35795
+ this.extLayers.map((l) => {
35796
+ const owner = this.layerOwner(l.def.id);
35797
+ return {
35798
+ id: l.def.id,
35799
+ placement: l.def.placement === "below-data" ? "below-data" : "above-data",
35800
+ ownerZ: owner ? this.scene.zOf(owner.id) : null
35801
+ };
35802
+ }),
35803
+ this.scene.candleZ
35804
+ );
35805
+ return { below: below.map((id) => byId.get(id)), above: above.map((id) => byId.get(id)) };
35806
+ }
35807
+ /** The full canvas pile in paint order (layers + data/volume/vpvr) — what the DOM
35808
+ * stacking and the screenshot compositor must both follow. */
35809
+ canvasPile() {
35810
+ const { below, above } = this.orderedLayerCanvases();
35811
+ return [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above];
35812
+ }
35813
+ /** Re-slot the SDK layer canvases in the plot when the computed order changed (a z
35814
+ * write, a restored config, an indicator mount/remove/restack). Runs at the top of
35815
+ * every data frame; a no-op when the signature is unchanged. Re-inserting an
35816
+ * absolutely-positioned, pointer-transparent canvas repaints nothing by itself. */
35817
+ syncLayerCanvasOrder() {
35818
+ if (this.extLayers.length === 0 || !this.plot) return;
35819
+ const { below, above } = this.orderedLayerCanvases();
35820
+ 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(",");
35821
+ if (sig === this.layerOrderSig) return;
35822
+ this.layerOrderSig = sig;
35823
+ for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
35824
+ for (const c of above) this.plot.insertBefore(c, this.chromeCanvas);
35825
+ }
35457
35826
  /** True when an active volume layer is this study pane's ONLY content — so its scale should
35458
35827
  * come from volume, not the empty {0,1} placeholder. (In the price pane, or alongside a real
35459
35828
  * series, volume stays a bottom overlay and the master scale wins.) */
@@ -35463,6 +35832,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35463
35832
  if (this.nativeLayerPane("volume") !== pane) return false;
35464
35833
  return masterModels.every((m) => m.native?.type === "volume");
35465
35834
  }
35835
+ /** True when this study pane's master content is only SDK-layer natives (series-less
35836
+ * models whose type names a mounted layer) — its scale then follows the visible bars
35837
+ * (see the call site). Any real master series takes over the scale as usual. */
35838
+ layerNativesOwnPane(pane, masterModels) {
35839
+ if (pane.kind === "price" || masterModels.length === 0) return false;
35840
+ return masterModels.every(
35841
+ (m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type)
35842
+ );
35843
+ }
35466
35844
  /** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
35467
35845
  * band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
35468
35846
  * Top-to-bottom order. Every pane is independent — the price pane from the scene setting,
@@ -35478,6 +35856,25 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35478
35856
  invert: paneInvert(this.scene, p)
35479
35857
  }));
35480
35858
  }
35859
+ /** Thin projection of the panes for the axis A/L hover buttons (top-to-bottom order). */
35860
+ axisScaleViews() {
35861
+ return this.scene.orderedPanes().map((p) => ({
35862
+ id: p.id,
35863
+ top: p.bounds.top,
35864
+ height: p.bounds.height,
35865
+ collapsed: p.collapsed,
35866
+ auto: p.manualScale == null,
35867
+ log: paneLogScale(this.scene, p)
35868
+ }));
35869
+ }
35870
+ /** Toggle one pane's autoscale (the A axis button): off freezes the current window into
35871
+ * manual mode, on drops it — the per-pane twin of the chart-level `autoScale` feature. */
35872
+ togglePaneAuto(paneId) {
35873
+ const pane = this.scene.panes.get(paneId);
35874
+ if (!pane) return;
35875
+ pane.manualScale = pane.manualScale == null ? { ...pane.scale } : null;
35876
+ this.scheduler?.invalidate(4 /* Full */);
35877
+ }
35481
35878
  /** Set one pane's axis mode. The price pane routes to the scene-level setting (persisted +
35482
35879
  * keyboard shortcuts); a study pane keeps its own, so panes never affect each other. */
35483
35880
  setPaneScaleMode(paneId, mode) {
@@ -35489,19 +35886,18 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35489
35886
  }
35490
35887
  this.scheduler?.invalidate(4 /* Full */);
35491
35888
  }
35492
- /** Set one pane's logarithmic flag. Clears that pane's frozen (manual) window so the new
35493
- * space re-fits, and routes the price pane to the scene-level flag. */
35889
+ /** Set one pane's logarithmic flag; the price pane routes to the scene-level flag.
35890
+ * Log and auto are INDEPENDENT: a frozen (manual) window is kept — its price bounds
35891
+ * stay put and only its `log` tag flips, so the same range re-renders in the new
35892
+ * space instead of snapping back to autoscale. */
35494
35893
  setPaneLog(paneId, log) {
35495
- if (paneId === PRICE_PANE_ID) {
35496
- this.scene.logScale = log;
35497
- const price = this.scene.orderedPanes().find((p) => p.kind === "price");
35498
- if (price) price.manualScale = null;
35499
- } else {
35500
- const p = this.scene.panes.get(paneId);
35501
- if (!p) return;
35502
- p.logScale = log;
35503
- p.manualScale = null;
35894
+ const pane = paneId === PRICE_PANE_ID ? this.scene.orderedPanes().find((p) => p.kind === "price") : this.scene.panes.get(paneId);
35895
+ if (paneId === PRICE_PANE_ID) this.scene.logScale = log;
35896
+ else {
35897
+ if (!pane) return;
35898
+ pane.logScale = log;
35504
35899
  }
35900
+ if (pane?.manualScale) pane.manualScale.log = log;
35505
35901
  this.scheduler?.invalidate(4 /* Full */);
35506
35902
  }
35507
35903
  /** Flip one pane's axis (high at the bottom). The price pane routes to the scene-level flag
@@ -35600,6 +35996,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35600
35996
  }
35601
35997
  this.inputsUI?.reposition();
35602
35998
  this.paneControls?.reposition();
35999
+ this.axisScaleButtons?.reposition();
35603
36000
  this.repositionScrollButton();
35604
36001
  this.positionAttribution();
35605
36002
  this.publishPricePaneBounds();
@@ -35618,6 +36015,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35618
36015
  }
35619
36016
  this.inputsUI?.reposition();
35620
36017
  this.paneControls?.reposition();
36018
+ this.axisScaleButtons?.reposition();
35621
36019
  this.repositionScrollButton();
35622
36020
  this.positionAttribution();
35623
36021
  this.publishPricePaneBounds();
@@ -35825,6 +36223,22 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35825
36223
  }
35826
36224
  return out.sort((a, b) => a.from - b.from);
35827
36225
  }
36226
+ function sanitizeSessionZones(value) {
36227
+ if (value == null || typeof value !== "object") return null;
36228
+ const v = value;
36229
+ const windows = (raw) => {
36230
+ if (!Array.isArray(raw)) return [];
36231
+ const out = [];
36232
+ for (const w of raw) {
36233
+ if (!Array.isArray(w)) continue;
36234
+ const from = Number(w[0]);
36235
+ const to = Number(w[1]);
36236
+ if (Number.isFinite(from) && Number.isFinite(to) && to > from) out.push([from, to]);
36237
+ }
36238
+ return out.sort((a, b) => a[0] - b[0]);
36239
+ };
36240
+ return { pre: windows(v.pre), post: windows(v.post) };
36241
+ }
35828
36242
  function clamp012(v) {
35829
36243
  return Math.max(0, Math.min(1, v));
35830
36244
  }
@@ -37703,6 +38117,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37703
38117
  exports.getDrawingType = getDrawingType;
37704
38118
  exports.getNativeIndicator = getNativeIndicator;
37705
38119
  exports.iconMarkup = iconMarkup;
38120
+ exports.inputVisible = inputVisible;
37706
38121
  exports.legendActions = legendActions;
37707
38122
  exports.nativeIndicatorDescriptors = nativeIndicatorDescriptors;
37708
38123
  exports.nativeIndicatorTypes = nativeIndicatorTypes;