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