@luxalgo/vela 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/{DataProvider-DHX4x6-r.d.ts → DataProvider-BmHkcwvJ.d.ts} +1 -1
  2. package/dist/{DataProvider-CNmk84SH.d.cts → DataProvider-DrN7ZIou.d.cts} +1 -1
  3. package/dist/{chunk-TP56QRMK.js → chunk-2R7BANEM.js} +1 -1
  4. package/dist/{chunk-U5KCQHAC.js → chunk-3QX6QOCX.js} +80 -12
  5. package/dist/{chunk-7D6YIF34.js → chunk-6FA4TDUF.js} +325 -67
  6. package/dist/{chunk-7Y7CJ7EN.js → chunk-BEJ57HP4.js} +8 -1
  7. package/dist/{chunk-QHZ7IXFL.js → chunk-OICPLNU7.js} +59 -9
  8. package/dist/{contributions-DDpv7hEL.d.cts → contributions-2bEmCF9S.d.cts} +2 -2
  9. package/dist/{contributions-ciV6Neyd.d.ts → contributions-DatoAzMx.d.ts} +2 -2
  10. package/dist/{history-CFqZm5re.d.ts → history-BmyjaVir.d.cts} +6 -6
  11. package/dist/{history-ChoT34nz.d.cts → history-CvOtsjT5.d.ts} +6 -6
  12. package/dist/index.cjs +394 -78
  13. package/dist/index.d.cts +36 -9
  14. package/dist/index.d.ts +36 -9
  15. package/dist/index.js +4 -4
  16. package/dist/{options-BqGeFHtp.d.ts → options-YdiaaVjt.d.cts} +20 -1
  17. package/dist/{options-BqGeFHtp.d.cts → options-YdiaaVjt.d.ts} +20 -1
  18. package/dist/{plugin-C97gUCVf.d.ts → plugin-BeCMF1VQ.d.ts} +3 -3
  19. package/dist/{plugin-CSb_sTiN.d.cts → plugin-CkiStRaI.d.cts} +3 -3
  20. package/dist/plugin.cjs +8 -0
  21. package/dist/plugin.d.cts +4 -4
  22. package/dist/plugin.d.ts +4 -4
  23. package/dist/plugin.js +2 -2
  24. package/dist/providers/binance.d.cts +2 -2
  25. package/dist/providers/binance.d.ts +2 -2
  26. package/dist/providers/coinbase.d.cts +2 -2
  27. package/dist/providers/coinbase.d.ts +2 -2
  28. package/dist/providers/hyperliquid.d.cts +2 -2
  29. package/dist/providers/hyperliquid.d.ts +2 -2
  30. package/dist/ui.cjs +59 -9
  31. package/dist/ui.d.cts +10 -2
  32. package/dist/ui.d.ts +10 -2
  33. package/dist/ui.js +2 -2
  34. package/dist/vela.global.js +394 -78
  35. package/dist/vela.global.min.js +84 -47
  36. package/dist/widget.cjs +646 -191
  37. package/dist/widget.d.cts +29 -8
  38. package/dist/widget.d.ts +29 -8
  39. package/dist/widget.js +145 -73
  40. package/dist/workspace.cjs +601 -142
  41. package/dist/workspace.d.cts +29 -5
  42. package/dist/workspace.d.ts +29 -5
  43. package/dist/workspace.js +143 -67
  44. package/package.json +1 -1
  45. /package/dist/{chunk-Q3XQHLIH.js → chunk-AOGZBKUE.js} +0 -0
package/dist/widget.cjs CHANGED
@@ -9878,7 +9878,7 @@ function attachChromeTooltip(anchor, opts) {
9878
9878
  } else {
9879
9879
  const left = Math.min(a.left - h.left, Math.max(0, h.width - tip.offsetWidth - 4));
9880
9880
  tip.style.left = `${Math.max(0, left)}px`;
9881
- tip.style.top = `${a.bottom - h.top + 6}px`;
9881
+ tip.style.top = opts.placement === "above" ? `${Math.max(0, a.top - h.top - tip.offsetHeight - 6)}px` : `${a.bottom - h.top + 6}px`;
9882
9882
  }
9883
9883
  };
9884
9884
  const arm = (e) => {
@@ -10004,6 +10004,23 @@ var MENU_CSS = `
10004
10004
  background: var(--vela-hover-strong);
10005
10005
  color: var(--vela-fg-bright);
10006
10006
  }
10007
+ /* Checkmark mode (context/action menus): every row reserves the leading mark column so
10008
+ labels align; a checked row fills it with a \u2713 and brightens its ink \u2014 the row surface
10009
+ stays free for the hover wash. */
10010
+ .vela-menu-item .vela-menu-mark {
10011
+ width: 14px;
10012
+ flex: none;
10013
+ display: inline-flex;
10014
+ align-items: center;
10015
+ justify-content: center;
10016
+ }
10017
+ .vela-menu-item .vela-menu-mark .vela-icon {
10018
+ width: 14px;
10019
+ height: 14px;
10020
+ font-size: 14px;
10021
+ color: var(--vela-fg-bright);
10022
+ }
10023
+ .vela-menu-item[data-checkmark] { color: var(--vela-fg-bright); }
10007
10024
  /* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
10008
10025
  same control language as the settings dialog's toggles. */
10009
10026
  .vela-menu-switch {
@@ -10072,6 +10089,7 @@ var Surface = class _Surface {
10072
10089
  this.host = opts.host;
10073
10090
  this.onSelect = opts.onSelect;
10074
10091
  this.onFavorite = opts.onFavorite;
10092
+ this.checkmarks = opts.checkmarks === true;
10075
10093
  this.positioner = doc.createElement("div");
10076
10094
  this.positioner.className = "vela-ui-layer";
10077
10095
  this.list = doc.createElement("ul");
@@ -10150,6 +10168,7 @@ var Surface = class _Surface {
10150
10168
  build() {
10151
10169
  const doc = this.doc;
10152
10170
  this.list.replaceChildren();
10171
+ const markable = this.checkmarks && this.items.some((i) => !(i.submenu && i.submenu.length > 0) && !i.toggle && i.checked !== void 0);
10153
10172
  for (const item of this.items) {
10154
10173
  if (item.separatorBefore) {
10155
10174
  const sep = doc.createElement("li");
@@ -10163,7 +10182,16 @@ var Surface = class _Surface {
10163
10182
  li.dataset.veiId = item.id;
10164
10183
  if (item.toggle) {
10165
10184
  li.dataset.toggle = "1";
10166
- } else if (!branch && item.checked) {
10185
+ }
10186
+ if (markable) {
10187
+ const mark = doc.createElement("span");
10188
+ mark.className = "vela-menu-mark";
10189
+ if (!branch && !item.toggle && item.checked) {
10190
+ mark.appendChild(iconEl("check", doc));
10191
+ li.dataset.checkmark = "1";
10192
+ }
10193
+ li.appendChild(mark);
10194
+ } else if (!branch && !item.toggle && item.checked) {
10167
10195
  li.dataset.checked = "1";
10168
10196
  }
10169
10197
  if (item.icon) li.appendChild(iconEl(item.icon, doc));
@@ -10212,6 +10240,7 @@ var Surface = class _Surface {
10212
10240
  placement: "right-start",
10213
10241
  onSelect: this.onSelect,
10214
10242
  onFavorite: this.onFavorite,
10243
+ checkmarks: this.checkmarks,
10215
10244
  id: `${this.mid}--${item.id}`
10216
10245
  });
10217
10246
  sub.setItems(item.submenu ?? []);
@@ -10236,7 +10265,8 @@ var Menu = class {
10236
10265
  triggerId: opts.triggerId,
10237
10266
  id: opts.id,
10238
10267
  minWidth: opts.minWidth,
10239
- onFavorite: opts.onFavorite
10268
+ onFavorite: opts.onFavorite,
10269
+ checkmarks: opts.checkmarks
10240
10270
  });
10241
10271
  this.root.setItems(opts.items);
10242
10272
  }
@@ -10264,6 +10294,13 @@ var Menu = class {
10264
10294
  }
10265
10295
  };
10266
10296
 
10297
+ // src/core/model/inputs.ts
10298
+ function inputVisible(when, values) {
10299
+ if (!when) return true;
10300
+ const conds = Array.isArray(when) ? when : [when];
10301
+ return conds.every((c) => c.anyOf ? c.anyOf.some((x) => x === values[c.key]) : values[c.key] === c.equals);
10302
+ }
10303
+
10267
10304
  // src/ui/tokens.ts
10268
10305
  var STATIC_ID = "vela-ui-tokens";
10269
10306
  var STATIC_DECLS = Object.entries(STATIC_TOKENS).map(([k, v]) => ` ${k}: ${v};`).join("\n");
@@ -10272,8 +10309,13 @@ var STATIC_CSS = `
10272
10309
  ${STATIC_DECLS}
10273
10310
  font-family: var(--vela-font, -apple-system, system-ui, sans-serif);
10274
10311
  box-sizing: border-box;
10312
+ /* Chrome text (titles, buttons, menus, readouts) is UI, not copy \u2014 never selectable. */
10313
+ user-select: none;
10314
+ -webkit-user-select: none;
10275
10315
  }
10276
10316
  .vela-ui *, .vela-ui-layer * { box-sizing: border-box; }
10317
+ /* Text ENTRY is the one exception: selection is part of editing. */
10318
+ .vela-ui :is(input, textarea), .vela-ui-layer :is(input, textarea) { user-select: text; -webkit-user-select: text; }
10277
10319
  .vela-icon { display: inline-flex; align-items: center; flex: none; }
10278
10320
  .vela-icon svg { display: block; }
10279
10321
  `;
@@ -11275,6 +11317,9 @@ var NUMBER_CSS = `
11275
11317
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
11276
11318
  -moz-appearance: textfield;
11277
11319
  appearance: textfield;
11320
+ /* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
11321
+ user-select: text;
11322
+ -webkit-user-select: text;
11278
11323
  }
11279
11324
  .vela-num input::-webkit-inner-spin-button, .vela-num input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
11280
11325
  .vela-num input:hover { border-color: var(--vela-fg-muted); }
@@ -11487,6 +11532,9 @@ var TEXT_CSS = `
11487
11532
  overflow: hidden;
11488
11533
  text-overflow: ellipsis;
11489
11534
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
11535
+ /* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
11536
+ user-select: text;
11537
+ -webkit-user-select: text;
11490
11538
  }
11491
11539
  .vela-text-field:hover { border-color: var(--vela-fg-muted); }
11492
11540
  .vela-text-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
@@ -11580,6 +11628,9 @@ var TEXTAREA_CSS = `
11580
11628
  resize: vertical;
11581
11629
  outline: none;
11582
11630
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
11631
+ /* Selectable even under a user-select:none host (chart wrapper, .vela-ui). */
11632
+ user-select: text;
11633
+ -webkit-user-select: text;
11583
11634
  }
11584
11635
  .vela-textarea-field:hover { border-color: var(--vela-fg-muted); }
11585
11636
  .vela-textarea-field:focus { border-color: var(--vela-focus); box-shadow: 0 0 0 3px var(--vela-focus-soft); }
@@ -12108,6 +12159,8 @@ var FIELD_CSS = `
12108
12159
  font-size: 14px;
12109
12160
  }
12110
12161
  .vela-field-label[data-size='sm'] { font-size: inherit; opacity: 0.85; }
12162
+ /* Titles are fully inert: spans, not label[for] \u2014 native labels propagate :hover and
12163
+ clicks onto their control, and both belong to the input alone. */
12111
12164
  .vela-field-cell { display: flex; align-items: center; gap: 8px; justify-self: start; }
12112
12165
  .vela-field-bool { display: flex; align-items: center; gap: 8px; min-height: 22px; }
12113
12166
  .vela-field-stacked { display: flex; flex-direction: column; gap: 8px; }
@@ -12166,11 +12219,17 @@ function fieldSeparator() {
12166
12219
  return el;
12167
12220
  }
12168
12221
  function labelEl(text, opts) {
12169
- const el = opts.id ? document.createElement("label") : document.createElement("span");
12170
- if (opts.id && el instanceof HTMLLabelElement) el.htmlFor = opts.id;
12222
+ const el = document.createElement("span");
12171
12223
  el.className = "vela-field-label";
12172
12224
  if (opts.size) el.dataset.size = opts.size;
12173
12225
  el.textContent = text;
12226
+ const id = opts.id;
12227
+ if (id) {
12228
+ el.id = `${id}--title`;
12229
+ queueMicrotask(() => {
12230
+ el.ownerDocument.getElementById(id)?.setAttribute("aria-labelledby", el.id);
12231
+ });
12232
+ }
12174
12233
  return el;
12175
12234
  }
12176
12235
  function asList(control) {
@@ -12199,9 +12258,7 @@ function fieldRow(opts) {
12199
12258
  it.className = "vela-field-inline-item";
12200
12259
  if (item.toggleFirst) it.style.flexDirection = "row-reverse";
12201
12260
  if (item.label) {
12202
- const lbl = labelEl(item.label, { id: item.id, size });
12203
- if (item.toggleFirst) lbl.style.cursor = "pointer";
12204
- it.appendChild(lbl);
12261
+ it.appendChild(labelEl(item.label, { id: item.id, size }));
12205
12262
  }
12206
12263
  if (item.fit) it.appendChild(item.control);
12207
12264
  else {
@@ -12226,7 +12283,7 @@ function fieldRow(opts) {
12226
12283
  tone: "bright",
12227
12284
  onChange: (v) => opts.toggle?.onChange(v)
12228
12285
  });
12229
- wrap2.append(sw.el, labelEl(opts.label, { size }));
12286
+ wrap2.append(sw.el, labelEl(opts.label, { id: opts.toggle?.id ?? opts.id, size }));
12230
12287
  if (opts.info) wrap2.appendChild(opts.info);
12231
12288
  if (opts.toggle?.get) {
12232
12289
  const get = opts.toggle.get;
@@ -12265,7 +12322,7 @@ function fieldRow(opts) {
12265
12322
  if (controls.length > 0) dimControls(controls, v);
12266
12323
  }
12267
12324
  });
12268
- left.append(sw.el, labelEl(opts.label, { size }));
12325
+ left.append(sw.el, labelEl(opts.label, { id: opts.toggle.id ?? opts.id, size }));
12269
12326
  wrap.appendChild(left);
12270
12327
  if (controls.length > 0) dimControls(controls, opts.toggle.checked);
12271
12328
  if (opts.toggle.get) {
@@ -12419,6 +12476,8 @@ var IndicatorInputsDialog = class {
12419
12476
  this.row = null;
12420
12477
  this.snapshot = null;
12421
12478
  this.dialogTips = [];
12479
+ /** Re-applies every `when` gate against current values; set while the dialog is open. */
12480
+ this.refreshVisibility = null;
12422
12481
  this.choicePop = null;
12423
12482
  this.calendarPop = null;
12424
12483
  this.calendarAnchor = null;
@@ -12481,14 +12540,17 @@ var IndicatorInputsDialog = class {
12481
12540
  tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
12482
12541
  const tabEls = [];
12483
12542
  const bodies = [];
12543
+ const gates = [];
12544
+ let active = 0;
12484
12545
  const activate = (idx) => {
12546
+ active = idx;
12485
12547
  for (let i = 0; i < tabEls.length; i += 1) {
12486
- const active = i === idx;
12548
+ const on = i === idx;
12487
12549
  const el = tabEls[i];
12488
- el.style.borderBottomColor = active ? fg : "transparent";
12489
- el.style.color = active ? "inherit" : "var(--vela-fg-muted)";
12490
- el.classList.toggle("vela-ind-tab-active", active);
12491
- bodies[i].style.display = active ? "grid" : "none";
12550
+ el.style.borderBottomColor = on ? fg : "transparent";
12551
+ el.style.color = on ? "inherit" : "var(--vela-fg-muted)";
12552
+ el.classList.toggle("vela-ind-tab-active", on);
12553
+ bodies[i].style.display = on ? "grid" : "none";
12492
12554
  }
12493
12555
  };
12494
12556
  for (const [idx, def] of tabDefs.entries()) {
@@ -12499,11 +12561,24 @@ var IndicatorInputsDialog = class {
12499
12561
  tab.addEventListener("click", () => activate(idx));
12500
12562
  tabs.appendChild(tab);
12501
12563
  tabEls.push(tab);
12502
- bodies.push(this.buildTabBody(def.inputs, row));
12564
+ bodies.push(this.buildTabBody(def.inputs, row, gates));
12565
+ if (def.inputs.every((d) => d.when)) {
12566
+ gates.push({ el: tab, visible: (bag) => def.inputs.some((d) => inputVisible(d.when, bag)) });
12567
+ }
12503
12568
  }
12504
12569
  ui.body.appendChild(tabs);
12505
12570
  for (const b of bodies) ui.body.appendChild(b);
12506
12571
  activate(0);
12572
+ const refresh = () => {
12573
+ const bag = valuesBag(row);
12574
+ for (const g of gates) g.el.style.display = g.visible(bag) ? "" : "none";
12575
+ if (tabEls[active]?.style.display === "none") {
12576
+ const next = tabEls.findIndex((el) => el.style.display !== "none");
12577
+ if (next >= 0) activate(next);
12578
+ }
12579
+ };
12580
+ this.refreshVisibility = refresh;
12581
+ refresh();
12507
12582
  const onBackdropDown = (e) => {
12508
12583
  if (e.target !== ui.backdrop && e.target !== ui.positioner) return;
12509
12584
  if (isPopoverOpen() || eventDismissedPopover(e)) {
@@ -12523,16 +12598,27 @@ var IndicatorInputsDialog = class {
12523
12598
  }
12524
12599
  /** One tab's scrollable body — one shared grid so every group's controls share a
12525
12600
  * left edge (a 100px number field lines up with a wider session/time pair). */
12526
- buildTabBody(inputs, row) {
12601
+ buildTabBody(inputs, row, gates) {
12527
12602
  const body = document.createElement("div");
12528
12603
  body.style.cssText = inputDialogBodyStyle(this.host.mobile());
12529
12604
  for (const group of groupInputs(inputs)) {
12530
12605
  const section = document.createElement("div");
12531
12606
  section.style.cssText = "display:contents;";
12607
+ let header = null;
12532
12608
  if (group.name) {
12533
- section.appendChild(fieldSection(group.name, { variant: "inputs", first: body.childElementCount === 0 }));
12609
+ header = fieldSection(group.name, { variant: "inputs", first: body.childElementCount === 0 });
12610
+ section.appendChild(header);
12611
+ }
12612
+ for (const inputRow of group.rows) {
12613
+ const el = this.buildInputRow(section, inputRow, row);
12614
+ if (inputRow.some((d) => d.when)) {
12615
+ gates.push({ el, visible: (bag) => rowVisible(inputRow, bag) });
12616
+ }
12617
+ }
12618
+ if (header && group.rows.every((r) => r.every((d) => d.when))) {
12619
+ const rows = group.rows;
12620
+ gates.push({ el: header, visible: (bag) => rows.some((r) => rowVisible(r, bag)) });
12534
12621
  }
12535
- for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
12536
12622
  body.appendChild(section);
12537
12623
  }
12538
12624
  return body;
@@ -12540,6 +12626,7 @@ var IndicatorInputsDialog = class {
12540
12626
  close() {
12541
12627
  document.removeEventListener("keydown", this.onDialogKey);
12542
12628
  closeOpenPopovers();
12629
+ this.refreshVisibility = null;
12543
12630
  this.choicePop = null;
12544
12631
  this.calendarPop = null;
12545
12632
  this.calendarAnchor = null;
@@ -12569,11 +12656,21 @@ var IndicatorInputsDialog = class {
12569
12656
  }
12570
12657
  this.close();
12571
12658
  }
12659
+ /** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
12660
+ commit(row, key, value) {
12661
+ row.values[key] = value;
12662
+ this.host.onChange?.({ indicatorId: row.id, key, value });
12663
+ this.refreshVisibility?.();
12664
+ }
12572
12665
  /** One settings row (or several `inline=` inputs) placed into a section's grid. */
12573
12666
  buildInputRow(grid, decls, row) {
12574
12667
  const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
12668
+ const append = (el) => {
12669
+ grid.appendChild(el);
12670
+ return el;
12671
+ };
12575
12672
  if (decls.length > 1) {
12576
- grid.appendChild(fieldRow({
12673
+ return append(fieldRow({
12577
12674
  label: "",
12578
12675
  inline: decls.map((d) => ({
12579
12676
  label: nameOf(d) || void 0,
@@ -12584,14 +12681,13 @@ var IndicatorInputsDialog = class {
12584
12681
  })),
12585
12682
  info: [...decls].reverse().find((d) => d.tooltip)?.tooltip ? this.infoButton([...decls].reverse().find((d) => d.tooltip).tooltip) : void 0
12586
12683
  }));
12587
- return;
12588
12684
  }
12589
12685
  const lead = decls[0];
12590
12686
  const id = idOf(lead);
12591
12687
  const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
12592
12688
  if (lead.type === "bool") {
12593
12689
  const current = Boolean(row.values[lead.key] ?? lead.defval);
12594
- grid.appendChild(fieldRow({
12690
+ return append(fieldRow({
12595
12691
  label: nameOf(lead),
12596
12692
  id,
12597
12693
  bool: true,
@@ -12599,26 +12695,21 @@ var IndicatorInputsDialog = class {
12599
12695
  toggle: {
12600
12696
  id,
12601
12697
  checked: current,
12602
- onChange: (v) => {
12603
- row.values[lead.key] = v;
12604
- this.host.onChange?.({ indicatorId: row.id, key: lead.key, value: v });
12605
- }
12698
+ onChange: (v) => this.commit(row, lead.key, v)
12606
12699
  }
12607
12700
  }));
12608
- return;
12609
12701
  }
12610
12702
  if (lead.type === "text_area") {
12611
- grid.appendChild(fieldRow({
12703
+ return append(fieldRow({
12612
12704
  label: nameOf(lead),
12613
12705
  id,
12614
12706
  stacked: true,
12615
12707
  info,
12616
12708
  control: this.buildControl(row, lead, id)
12617
12709
  }));
12618
- return;
12619
12710
  }
12620
12711
  const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
12621
- grid.appendChild(fieldRow({
12712
+ return append(fieldRow({
12622
12713
  label: nameOf(lead),
12623
12714
  id,
12624
12715
  fit,
@@ -12630,10 +12721,7 @@ var IndicatorInputsDialog = class {
12630
12721
  /** Build the typed control for one input, committing edits live via `onChange`. */
12631
12722
  buildControl(row, inp, id) {
12632
12723
  const current = row.values[inp.key] ?? inp.defval;
12633
- const emit = (value) => {
12634
- row.values[inp.key] = value;
12635
- this.host.onChange?.({ indicatorId: row.id, key: inp.key, value });
12636
- };
12724
+ const emit = (value) => this.commit(row, inp.key, value);
12637
12725
  if (inp.type === "bool") {
12638
12726
  return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
12639
12727
  }
@@ -13054,6 +13142,14 @@ function tabInputs(inputs) {
13054
13142
  }
13055
13143
  return names.map((name) => ({ name, inputs: byTab.get(name) }));
13056
13144
  }
13145
+ function rowVisible(decls, bag) {
13146
+ return decls.some((d) => inputVisible(d.when, bag));
13147
+ }
13148
+ function valuesBag(row) {
13149
+ const bag = {};
13150
+ for (const inp of row.inputs) bag[inp.key] = row.values[inp.key] ?? inp.defval;
13151
+ return bag;
13152
+ }
13057
13153
  function groupInputs(inputs) {
13058
13154
  const order = [];
13059
13155
  const byGroup = /* @__PURE__ */ new Map();
@@ -13516,6 +13612,8 @@ var InputsUI = class {
13516
13612
  host: this.container,
13517
13613
  items,
13518
13614
  placement: "bottom-start",
13615
+ // Right-click action menu — checked state reads as a leading ✓ (see the kit Menu).
13616
+ checkmarks: true,
13519
13617
  onSelect: (itemId) => {
13520
13618
  if (itemId !== "values" || !this.rowMenuId) return;
13521
13619
  const target = this.rows.get(this.rowMenuId);
@@ -13851,6 +13949,8 @@ var InputsUI = class {
13851
13949
  syncRowActions(row) {
13852
13950
  const open2 = row.highlighted;
13853
13951
  row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
13952
+ if (open2) row.el.appendChild(row.statusEl);
13953
+ else row.el.insertBefore(row.statusEl, row.valuesEl);
13854
13954
  for (const child of Array.from(row.controlsEl.children)) {
13855
13955
  if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
13856
13956
  if (child === row.extrasEl) {
@@ -14094,6 +14194,148 @@ var PaneControls = class {
14094
14194
  }
14095
14195
  };
14096
14196
 
14197
+ // src/renderers/native/chrome/axisLayout.ts
14198
+ var AXIS_MASTER_W = 64;
14199
+ var AXIS_MERGED_W = 56;
14200
+ var PANE_SEPARATOR_PX = 3;
14201
+ function axisColumnX(dataW, column) {
14202
+ return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
14203
+ }
14204
+
14205
+ // src/renderers/native/chrome/AxisScaleButtons.ts
14206
+ var STYLE_ID2 = "vela-axis-scale-buttons";
14207
+ var BTN_PX = 18;
14208
+ var BTN_GAP = 4;
14209
+ var AXIS_BORDER = 1;
14210
+ var CLUSTER_W = AXIS_MASTER_W - AXIS_BORDER;
14211
+ var PAD_TOP = 8;
14212
+ var PAD_BOTTOM = 6;
14213
+ var CLUSTER_H = BTN_PX + PAD_TOP + PAD_BOTTOM;
14214
+ var MIN_PANE_H = 48;
14215
+ function ensureStyles2() {
14216
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID2)) return;
14217
+ const st = document.createElement("style");
14218
+ st.id = STYLE_ID2;
14219
+ st.textContent = `
14220
+ .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);}
14221
+ .vela-axis-btn:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
14222
+ .vela-axis-on,.vela-axis-on:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
14223
+ `;
14224
+ document.head.appendChild(st);
14225
+ }
14226
+ var AxisScaleButtons = class {
14227
+ constructor(plot, theme, deps) {
14228
+ this.plot = plot;
14229
+ this.theme = theme;
14230
+ this.deps = deps;
14231
+ this.tipDisposers = [];
14232
+ this.hoverPaneId = null;
14233
+ /** Reveal over the master scale column of the pane under the cursor (mouse only —
14234
+ * touch has no hover, and a drag-rescale on the axis must not sprout buttons). */
14235
+ this.onPlotMove = (e) => {
14236
+ if (e.pointerType !== "mouse") return;
14237
+ const rect = this.plot.getBoundingClientRect();
14238
+ const x = e.clientX - rect.left;
14239
+ const gutterLeft = rect.width - this.deps.rightAxis();
14240
+ let hit = null;
14241
+ if (x >= gutterLeft && x <= gutterLeft + AXIS_MASTER_W) {
14242
+ const y = e.clientY - rect.top;
14243
+ for (const p of this.deps.panes()) {
14244
+ if (p.collapsed || p.height < MIN_PANE_H) continue;
14245
+ if (y >= p.top && y <= p.top + p.height) {
14246
+ hit = p.id;
14247
+ break;
14248
+ }
14249
+ }
14250
+ }
14251
+ this.setHoverPane(hit);
14252
+ if (hit) this.sync();
14253
+ };
14254
+ this.onPlotLeave = () => this.setHoverPane(null);
14255
+ ensureStyles2();
14256
+ this.cluster = document.createElement("div");
14257
+ Object.assign(this.cluster.style, {
14258
+ position: "absolute",
14259
+ display: "none",
14260
+ justifyContent: "center",
14261
+ alignItems: "center",
14262
+ gap: `${BTN_GAP}px`,
14263
+ padding: `${PAD_TOP}px 0 ${PAD_BOTTOM}px`,
14264
+ width: `${CLUSTER_W}px`,
14265
+ boxSizing: "border-box",
14266
+ zIndex: "6",
14267
+ pointerEvents: "auto"
14268
+ });
14269
+ this.applyTheme();
14270
+ this.autoBtn = this.button("A", "Auto (fits data to screen)", () => this.deps.onToggleAuto(this.hoverPaneId ?? ""));
14271
+ this.logBtn = this.button("L", "Logarithmic scale", () => this.deps.onToggleLog(this.hoverPaneId ?? ""));
14272
+ this.cluster.append(this.autoBtn, this.logBtn);
14273
+ this.plot.appendChild(this.cluster);
14274
+ this.plot.addEventListener("pointermove", this.onPlotMove);
14275
+ this.plot.addEventListener("pointerleave", this.onPlotLeave);
14276
+ }
14277
+ button(label, title, onClick) {
14278
+ const b = document.createElement("button");
14279
+ b.type = "button";
14280
+ b.setAttribute("aria-label", title);
14281
+ b.textContent = label;
14282
+ b.className = "vela-axis-btn";
14283
+ Object.assign(b.style, { width: `${BTN_PX}px`, height: `${BTN_PX}px` });
14284
+ this.tipDisposers.push(attachChromeTooltip(b, {
14285
+ host: this.plot,
14286
+ theme: () => this.theme,
14287
+ text: () => title,
14288
+ placement: "above"
14289
+ }));
14290
+ b.addEventListener("click", (e) => {
14291
+ e.stopPropagation();
14292
+ onClick();
14293
+ this.sync();
14294
+ });
14295
+ return b;
14296
+ }
14297
+ /** Re-tint the chip when the plot background/border change (theme swap, config edit). */
14298
+ setTheme(theme) {
14299
+ this.theme = theme;
14300
+ this.applyTheme();
14301
+ }
14302
+ /** The strip is SOLID chart background: the buttons cover axis labels, never blend with them. */
14303
+ applyTheme() {
14304
+ this.cluster.style.background = this.theme.background;
14305
+ }
14306
+ setHoverPane(paneId) {
14307
+ if (this.hoverPaneId === paneId) return;
14308
+ this.hoverPaneId = paneId;
14309
+ this.reposition();
14310
+ }
14311
+ /** Re-place (or hide) the cluster after a hover or pane-layout change. */
14312
+ reposition() {
14313
+ const pane = this.hoverPaneId ? this.deps.panes().find((p) => p.id === this.hoverPaneId) : void 0;
14314
+ if (!pane || pane.collapsed || pane.height < MIN_PANE_H) {
14315
+ this.cluster.style.display = "none";
14316
+ return;
14317
+ }
14318
+ this.cluster.style.right = `${this.deps.rightAxis() - AXIS_MASTER_W}px`;
14319
+ this.cluster.style.top = `${pane.top + pane.height - CLUSTER_H}px`;
14320
+ this.cluster.style.display = "flex";
14321
+ this.sync();
14322
+ }
14323
+ /** Reflect the hovered pane's current auto/log state on the buttons. */
14324
+ sync() {
14325
+ const pane = this.deps.panes().find((p) => p.id === this.hoverPaneId);
14326
+ if (!pane) return;
14327
+ this.autoBtn.classList.toggle("vela-axis-on", pane.auto);
14328
+ this.logBtn.classList.toggle("vela-axis-on", pane.log);
14329
+ }
14330
+ destroy() {
14331
+ this.plot.removeEventListener("pointermove", this.onPlotMove);
14332
+ this.plot.removeEventListener("pointerleave", this.onPlotLeave);
14333
+ for (const dispose of this.tipDisposers) dispose();
14334
+ this.tipDisposers.length = 0;
14335
+ this.cluster.remove();
14336
+ }
14337
+ };
14338
+
14097
14339
  // src/renderers/shared/TableOverlay.ts
14098
14340
  var SIZE_PX3 = {
14099
14341
  auto: 13,
@@ -14451,6 +14693,8 @@ var BASELINE_BOTTOM_LINE = BEARISH;
14451
14693
  var BASELINE_FILL_ALPHA = 0.25;
14452
14694
  var BASELINE_FILL_ALPHA_FAR = 0.05;
14453
14695
  var BASELINE_LEVEL_DEFAULT = 50;
14696
+ var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
14697
+ var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
14454
14698
  function defaultChartStyle() {
14455
14699
  return {
14456
14700
  chartTypes: {},
@@ -14481,7 +14725,8 @@ function defaultChartStyle() {
14481
14725
  bottomFillColor2: null,
14482
14726
  width: 2,
14483
14727
  baselineLevel: BASELINE_LEVEL_DEFAULT
14484
- }
14728
+ },
14729
+ sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
14485
14730
  };
14486
14731
  }
14487
14732
  var LINE_STYLES = ["solid", "dashed", "dotted"];
@@ -14615,6 +14860,7 @@ function mergeConfig(base, patch) {
14615
14860
  const area = asObject(p.area);
14616
14861
  const baseline = asObject(p.baseline);
14617
14862
  const series = asObject(p.series);
14863
+ const sessions = asObject(p.sessions);
14618
14864
  const stacking = asObject(p.stacking);
14619
14865
  const stackSeries = asObject(stacking.series);
14620
14866
  return {
@@ -14707,6 +14953,10 @@ function mergeConfig(base, patch) {
14707
14953
  baseline: series.baseline === null ? null : isNum(series.baseline) ? series.baseline : base.series.baseline,
14708
14954
  spacing: isNum(series.spacing) ? clampSpacing(series.spacing) : base.series.spacing
14709
14955
  },
14956
+ sessions: {
14957
+ premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
14958
+ postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
14959
+ },
14710
14960
  stacking: {
14711
14961
  candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
14712
14962
  // Additive like `chartTypes`: a patch names only the ids it carries; the rest keep
@@ -14953,6 +15203,8 @@ var SceneGraph = class {
14953
15203
  this.tradeMarkers = defaultTradeMarkersState();
14954
15204
  /** Renderer-owned shaded time bands (session highlighting), behind grid + data. */
14955
15205
  this.highlights = [];
15206
+ /** Pre/post-market bands pushed by the host (`sessionZones` feature); null ⇒ no sessions. */
15207
+ this.sessionZones = null;
14956
15208
  /** Draw-order key of the price candles, relative to indicator series z (see `seriesZ`).
14957
15209
  * Indicators with z below this draw BEHIND the candles; at/above draw in front.
14958
15210
  * Default 0 with indicators mounting at z < 0 ⇒ the price reads on top of every overlay,
@@ -14987,6 +15239,15 @@ var SceneGraph = class {
14987
15239
  if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
14988
15240
  return this.orderedCache;
14989
15241
  }
15242
+ /** The session zones resolved into colored bands (pre/post-market washes from the
15243
+ * config's session colors) — consumed by the same painting path as {@link highlights}. */
15244
+ sessionHighlightBands() {
15245
+ if (!this.sessionZones) return [];
15246
+ const out = [];
15247
+ for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
15248
+ for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
15249
+ return out;
15250
+ }
14990
15251
  indicatorsForPane(paneId) {
14991
15252
  const out = [];
14992
15253
  for (const model of this.indicators.values()) if (model.paneId === paneId) out.push(model);
@@ -15917,10 +16178,12 @@ var WebGL2Backend = class {
15917
16178
  }
15918
16179
  }
15919
16180
  }
15920
- /** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height). */
16181
+ /** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
16182
+ * Session-zone washes (pre/post-market) paint first, host highlights on top. */
15921
16183
  emitHighlights(b, scene, pane, coords) {
15922
- if (scene.highlights.length === 0) return;
15923
- for (const band of scene.highlights) {
16184
+ const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
16185
+ if (bands.length === 0) return;
16186
+ for (const band of bands) {
15924
16187
  const x1 = coords.timeToX(band.from);
15925
16188
  const x2 = coords.timeToX(band.to);
15926
16189
  if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
@@ -17894,10 +18157,12 @@ var Canvas2dBackend = class {
17894
18157
  ctx.fillStyle = bg.color;
17895
18158
  ctx.fillRect(x1, pane.bounds.top, x2 - x1, pane.bounds.height);
17896
18159
  }
17897
- /** Renderer-owned session highlight bands: full-height (all panes), behind grid + data. */
18160
+ /** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
18161
+ * Session-zone washes (pre/post-market) paint first, host highlights on top. */
17898
18162
  drawHighlights(ctx, scene, coords) {
17899
- if (scene.highlights.length === 0) return;
17900
- for (const band of scene.highlights) {
18163
+ const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
18164
+ if (bands.length === 0) return;
18165
+ for (const band of bands) {
17901
18166
  const x1 = coords.timeToX(band.from);
17902
18167
  const x2 = coords.timeToX(band.to);
17903
18168
  if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
@@ -18671,14 +18936,6 @@ var DrawingSceneRenderer = class {
18671
18936
  }
18672
18937
  };
18673
18938
 
18674
- // src/renderers/native/chrome/axisLayout.ts
18675
- var AXIS_MASTER_W = 64;
18676
- var AXIS_MERGED_W = 56;
18677
- var PANE_SEPARATOR_PX = 3;
18678
- function axisColumnX(dataW, column) {
18679
- return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
18680
- }
18681
-
18682
18939
  // src/renderers/native/chrome/ChromeRenderer.ts
18683
18940
  var ChromeRenderer = class {
18684
18941
  constructor() {
@@ -19565,6 +19822,7 @@ var SettingsDialog = class {
19565
19822
  for (const hr of hs.rows) {
19566
19823
  if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
19567
19824
  else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
19825
+ else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
19568
19826
  else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options.map((o) => [o, o]), (v) => hr.set(v)));
19569
19827
  }
19570
19828
  }
@@ -22289,14 +22547,14 @@ function btn(label, primary, onClick) {
22289
22547
  // src/renderers/native/drawings/DrawingSettingsPopup.ts
22290
22548
  var BTN = 30;
22291
22549
  var ICON = 21;
22292
- var STYLE_ID2 = "vela-drawing-popup-styles";
22550
+ var STYLE_ID3 = "vela-drawing-popup-styles";
22293
22551
  var STYLE_REV = "3";
22294
- function ensureStyles2() {
22552
+ function ensureStyles3() {
22295
22553
  if (typeof document === "undefined") return;
22296
- const existing = document.getElementById(STYLE_ID2);
22554
+ const existing = document.getElementById(STYLE_ID3);
22297
22555
  if (existing?.dataset.rev === STYLE_REV) return;
22298
22556
  const s = existing ?? document.createElement("style");
22299
- s.id = STYLE_ID2;
22557
+ s.id = STYLE_ID3;
22300
22558
  s.dataset.rev = STYLE_REV;
22301
22559
  s.textContent = `
22302
22560
  .vela-dpop-btn{background:transparent;color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
@@ -22353,7 +22611,7 @@ var DrawingSettingsPopup = class {
22353
22611
  * `onClose` fires on an outside-click dismissal (not a programmatic close). */
22354
22612
  open(drawing, anchor, actions, onClose) {
22355
22613
  this.close();
22356
- ensureStyles2();
22614
+ ensureStyles3();
22357
22615
  this.onClose = onClose ?? null;
22358
22616
  const t = this.theme;
22359
22617
  const schema = drawing.schema();
@@ -23081,7 +23339,7 @@ var DrawingToolbar = class {
23081
23339
  this.onCollapse = options.onCollapse ?? (() => {
23082
23340
  });
23083
23341
  this.onDrawingsSync = options.onDrawingsSync ?? null;
23084
- ensureStyles3();
23342
+ ensureStyles4();
23085
23343
  this.root = document.createElement("div");
23086
23344
  this.root.className = "vela-dtb";
23087
23345
  this.styleRoot();
@@ -23564,13 +23822,13 @@ var DrawingToolbar = class {
23564
23822
  }
23565
23823
  };
23566
23824
  var MAGNET_ID = "__magnet__";
23567
- var STYLE_ID3 = "vela-drawing-toolbar-styles";
23568
- function ensureStyles3() {
23825
+ var STYLE_ID4 = "vela-drawing-toolbar-styles";
23826
+ function ensureStyles4() {
23569
23827
  if (typeof document === "undefined") return;
23570
- let s = document.getElementById(STYLE_ID3);
23828
+ let s = document.getElementById(STYLE_ID4);
23571
23829
  if (!s) {
23572
23830
  s = document.createElement("style");
23573
- s.id = STYLE_ID3;
23831
+ s.id = STYLE_ID4;
23574
23832
  document.head.appendChild(s);
23575
23833
  }
23576
23834
  s.textContent = `
@@ -24835,7 +25093,7 @@ var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path
24835
25093
 
24836
25094
  // src/renderers/native/chrome/AttributionMark.ts
24837
25095
  var ATTRIBUTION_URL = "https://luxalgo.com/vela";
24838
- var STYLE_ID4 = "vela-attribution-styles";
25096
+ var STYLE_ID5 = "vela-attribution-styles";
24839
25097
  var CSS2 = `
24840
25098
  .vela-attribution {
24841
25099
  text-decoration: none;
@@ -24898,11 +25156,11 @@ var CSS2 = `
24898
25156
  [data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
24899
25157
  [data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
24900
25158
  `;
24901
- function ensureStyles4(doc) {
24902
- let s = doc.getElementById(STYLE_ID4);
25159
+ function ensureStyles5(doc) {
25160
+ let s = doc.getElementById(STYLE_ID5);
24903
25161
  if (!s) {
24904
25162
  s = doc.createElement("style");
24905
- s.id = STYLE_ID4;
25163
+ s.id = STYLE_ID5;
24906
25164
  doc.head.appendChild(s);
24907
25165
  }
24908
25166
  s.textContent = CSS2;
@@ -24931,7 +25189,7 @@ function createCustomMark(doc, html, background) {
24931
25189
  return el;
24932
25190
  }
24933
25191
  function createAttributionMark(doc, background) {
24934
- ensureStyles4(doc);
25192
+ ensureStyles5(doc);
24935
25193
  const a = doc.createElement("a");
24936
25194
  a.className = "vela-attribution";
24937
25195
  a.href = ATTRIBUTION_URL;
@@ -25443,6 +25701,8 @@ var NativeRenderer = class {
25443
25701
  // total right-gutter width (master column + merged-scale columns)
25444
25702
  this.paneControls = null;
25445
25703
  // per-pane hover button cluster (top-right)
25704
+ this.axisScaleButtons = null;
25705
+ // A/L hover buttons at the bottom of each pane's price scale
25446
25706
  this.paneActionCbs = /* @__PURE__ */ new Set();
25447
25707
  // ── data window (crosshair OHLC + per-series readout, pulled by host panels) ──
25448
25708
  this.hoverLogical = null;
@@ -25486,7 +25746,7 @@ var NativeRenderer = class {
25486
25746
  this.priceStyleCbs = /* @__PURE__ */ new Set();
25487
25747
  this.tableOverlays = /* @__PURE__ */ new Map();
25488
25748
  this.name = "native";
25489
- 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"];
25749
+ 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"];
25490
25750
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
25491
25751
  * so moving onto the button doesn't count as leaving). */
25492
25752
  this.onScrollProximityMove = (e) => {
@@ -25602,6 +25862,9 @@ var NativeRenderer = class {
25602
25862
  case "highlights":
25603
25863
  this.scene.highlights = sanitizeHighlights(value);
25604
25864
  break;
25865
+ case "sessionZones":
25866
+ this.scene.sessionZones = sanitizeSessionZones(value);
25867
+ break;
25605
25868
  case "gridlines":
25606
25869
  this.scene.showGrid = Boolean(value);
25607
25870
  break;
@@ -25718,6 +25981,8 @@ var NativeRenderer = class {
25718
25981
  return this.scene.indicatorZOrder();
25719
25982
  case "highlights":
25720
25983
  return this.scene.highlights;
25984
+ case "sessionZones":
25985
+ return this.scene.sessionZones;
25721
25986
  case "gridlines":
25722
25987
  return this.scene.showGrid;
25723
25988
  case "axisLabels":
@@ -25927,7 +26192,8 @@ var NativeRenderer = class {
25927
26192
  baselineLevel: s.baseline.baselineLevel
25928
26193
  };
25929
26194
  })(),
25930
- series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale }
26195
+ series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
26196
+ sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
25931
26197
  };
25932
26198
  }
25933
26199
  /**
@@ -26009,6 +26275,7 @@ var NativeRenderer = class {
26009
26275
  width: next.baseline.width,
26010
26276
  baselineLevel: next.baseline.baselineLevel
26011
26277
  };
26278
+ s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
26012
26279
  this.setPriceStyle(next.series.style);
26013
26280
  this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
26014
26281
  this.scene.baselineValue = next.series.baseline;
@@ -26022,6 +26289,7 @@ var NativeRenderer = class {
26022
26289
  }
26023
26290
  this.inputsUI?.setTheme(this.theme);
26024
26291
  this.paneControls?.setTheme(this.theme);
26292
+ this.axisScaleButtons?.setTheme(this.theme);
26025
26293
  this.userDrawings?.setTheme(this.chromeTheme());
26026
26294
  this.refreshScrollButtonTheme();
26027
26295
  this.refreshAttributionColor();
@@ -26360,7 +26628,7 @@ var NativeRenderer = class {
26360
26628
  this.surfaceBackground = theme.background;
26361
26629
  this.surfaceTextColor = theme.textColor;
26362
26630
  this.wrapper = document.createElement("div");
26363
- Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair" });
26631
+ Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair", userSelect: "none", webkitUserSelect: "none" });
26364
26632
  applyChromeTokens(this.wrapper, this.chromeTheme());
26365
26633
  this.volumeCanvas = document.createElement("canvas");
26366
26634
  Object.assign(this.volumeCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
@@ -26529,6 +26797,15 @@ var NativeRenderer = class {
26529
26797
  this.emitPaneAction({ type: "maximize", paneId, maximized });
26530
26798
  }
26531
26799
  });
26800
+ this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
26801
+ panes: () => this.axisScaleViews(),
26802
+ rightAxis: () => this.rightAxisW,
26803
+ onToggleAuto: (paneId) => this.togglePaneAuto(paneId),
26804
+ onToggleLog: (paneId) => {
26805
+ const pane = this.scene.panes.get(paneId);
26806
+ if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
26807
+ }
26808
+ });
26532
26809
  this.resizeObserver = new ResizeObserver(() => this.resize());
26533
26810
  this.resizeObserver.observe(this.wrapper);
26534
26811
  this.watchDpr();
@@ -26605,6 +26882,7 @@ var NativeRenderer = class {
26605
26882
  this.applyBackground();
26606
26883
  this.inputsUI.setTheme(theme);
26607
26884
  this.paneControls?.setTheme(this.theme);
26885
+ this.axisScaleButtons?.setTheme(this.theme);
26608
26886
  this.settingsDialog?.refreshConfig(this.getConfig());
26609
26887
  this.syncThemeControl();
26610
26888
  this.settingsDialog?.setTheme(this.theme);
@@ -26653,6 +26931,7 @@ var NativeRenderer = class {
26653
26931
  this.liveRegion = null;
26654
26932
  this.inputsUI?.destroy();
26655
26933
  this.paneControls?.destroy();
26934
+ this.axisScaleButtons?.destroy();
26656
26935
  for (const overlay of this.tableOverlays.values()) overlay.destroy();
26657
26936
  this.tableOverlays.clear();
26658
26937
  this.resizeObserver?.disconnect();
@@ -28047,6 +28326,25 @@ var NativeRenderer = class {
28047
28326
  invert: paneInvert(this.scene, p)
28048
28327
  }));
28049
28328
  }
28329
+ /** Thin projection of the panes for the axis A/L hover buttons (top-to-bottom order). */
28330
+ axisScaleViews() {
28331
+ return this.scene.orderedPanes().map((p) => ({
28332
+ id: p.id,
28333
+ top: p.bounds.top,
28334
+ height: p.bounds.height,
28335
+ collapsed: p.collapsed,
28336
+ auto: p.manualScale == null,
28337
+ log: paneLogScale(this.scene, p)
28338
+ }));
28339
+ }
28340
+ /** Toggle one pane's autoscale (the A axis button): off freezes the current window into
28341
+ * manual mode, on drops it — the per-pane twin of the chart-level `autoScale` feature. */
28342
+ togglePaneAuto(paneId) {
28343
+ const pane = this.scene.panes.get(paneId);
28344
+ if (!pane) return;
28345
+ pane.manualScale = pane.manualScale == null ? { ...pane.scale } : null;
28346
+ this.scheduler?.invalidate(4 /* Full */);
28347
+ }
28050
28348
  /** Set one pane's axis mode. The price pane routes to the scene-level setting (persisted +
28051
28349
  * keyboard shortcuts); a study pane keeps its own, so panes never affect each other. */
28052
28350
  setPaneScaleMode(paneId, mode) {
@@ -28058,19 +28356,18 @@ var NativeRenderer = class {
28058
28356
  }
28059
28357
  this.scheduler?.invalidate(4 /* Full */);
28060
28358
  }
28061
- /** Set one pane's logarithmic flag. Clears that pane's frozen (manual) window so the new
28062
- * space re-fits, and routes the price pane to the scene-level flag. */
28359
+ /** Set one pane's logarithmic flag; the price pane routes to the scene-level flag.
28360
+ * Log and auto are INDEPENDENT: a frozen (manual) window is kept — its price bounds
28361
+ * stay put and only its `log` tag flips, so the same range re-renders in the new
28362
+ * space instead of snapping back to autoscale. */
28063
28363
  setPaneLog(paneId, log) {
28064
- if (paneId === PRICE_PANE_ID) {
28065
- this.scene.logScale = log;
28066
- const price = this.scene.orderedPanes().find((p) => p.kind === "price");
28067
- if (price) price.manualScale = null;
28068
- } else {
28069
- const p = this.scene.panes.get(paneId);
28070
- if (!p) return;
28071
- p.logScale = log;
28072
- p.manualScale = null;
28364
+ const pane = paneId === PRICE_PANE_ID ? this.scene.orderedPanes().find((p) => p.kind === "price") : this.scene.panes.get(paneId);
28365
+ if (paneId === PRICE_PANE_ID) this.scene.logScale = log;
28366
+ else {
28367
+ if (!pane) return;
28368
+ pane.logScale = log;
28073
28369
  }
28370
+ if (pane?.manualScale) pane.manualScale.log = log;
28074
28371
  this.scheduler?.invalidate(4 /* Full */);
28075
28372
  }
28076
28373
  /** Flip one pane's axis (high at the bottom). The price pane routes to the scene-level flag
@@ -28169,6 +28466,7 @@ var NativeRenderer = class {
28169
28466
  }
28170
28467
  this.inputsUI?.reposition();
28171
28468
  this.paneControls?.reposition();
28469
+ this.axisScaleButtons?.reposition();
28172
28470
  this.repositionScrollButton();
28173
28471
  this.positionAttribution();
28174
28472
  this.publishPricePaneBounds();
@@ -28187,6 +28485,7 @@ var NativeRenderer = class {
28187
28485
  }
28188
28486
  this.inputsUI?.reposition();
28189
28487
  this.paneControls?.reposition();
28488
+ this.axisScaleButtons?.reposition();
28190
28489
  this.repositionScrollButton();
28191
28490
  this.positionAttribution();
28192
28491
  this.publishPricePaneBounds();
@@ -28394,6 +28693,22 @@ function sanitizeHighlights(value) {
28394
28693
  }
28395
28694
  return out.sort((a, b) => a.from - b.from);
28396
28695
  }
28696
+ function sanitizeSessionZones(value) {
28697
+ if (value == null || typeof value !== "object") return null;
28698
+ const v = value;
28699
+ const windows = (raw) => {
28700
+ if (!Array.isArray(raw)) return [];
28701
+ const out = [];
28702
+ for (const w of raw) {
28703
+ if (!Array.isArray(w)) continue;
28704
+ const from = Number(w[0]);
28705
+ const to = Number(w[1]);
28706
+ if (Number.isFinite(from) && Number.isFinite(to) && to > from) out.push([from, to]);
28707
+ }
28708
+ return out.sort((a, b) => a[0] - b[0]);
28709
+ };
28710
+ return { pre: windows(v.pre), post: windows(v.post) };
28711
+ }
28397
28712
  function clamp012(v) {
28398
28713
  return Math.max(0, Math.min(1, v));
28399
28714
  }
@@ -29426,7 +29741,7 @@ var Drawer = class {
29426
29741
  };
29427
29742
 
29428
29743
  // src/widget/layout-picker.ts
29429
- var STYLE_ID5 = "vela-widget-layout-picker-v14";
29744
+ var STYLE_ID6 = "vela-widget-layout-picker-v14";
29430
29745
  var CSS3 = `
29431
29746
  .vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
29432
29747
  .vela-lp {
@@ -29531,7 +29846,7 @@ var CSS3 = `
29531
29846
  `;
29532
29847
  var GRID = 4;
29533
29848
  function layoutGridCanvas(doc) {
29534
- injectStyles(STYLE_ID5, CSS3, doc);
29849
+ injectStyles(STYLE_ID6, CSS3, doc);
29535
29850
  const el = doc.createElement("div");
29536
29851
  el.className = "vela-lp-grid";
29537
29852
  const squares = [];
@@ -29571,7 +29886,7 @@ var LayoutPicker = class {
29571
29886
  };
29572
29887
  this.opts = opts;
29573
29888
  this.doc = opts.host.ownerDocument;
29574
- injectStyles(STYLE_ID5, CSS3, this.doc);
29889
+ injectStyles(STYLE_ID6, CSS3, this.doc);
29575
29890
  const doc = this.doc;
29576
29891
  this.layer = doc.createElement("div");
29577
29892
  this.layer.className = "vela-ui-layer vela-lp-layer";
@@ -29916,7 +30231,7 @@ function timeframeLabel(value) {
29916
30231
  }
29917
30232
 
29918
30233
  // src/widget/topbar.ts
29919
- var STYLE_ID6 = "vela-topbar";
30234
+ var STYLE_ID7 = "vela-topbar";
29920
30235
  var CSS4 = `
29921
30236
  .vela-widget-topbar {
29922
30237
  display: flex;
@@ -29973,12 +30288,14 @@ var CSS4 = `
29973
30288
  color: var(--vela-fg-muted);
29974
30289
  }
29975
30290
  .vela-widget-tf-caret:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
30291
+ /* The merged trigger is a plain button (hover feedback only) \u2014 the highlight
30292
+ background marks the CURRENT chip among favorites, and a lone trigger with a
30293
+ permanent highlight would read as stuck-pressed. */
29976
30294
  .vela-widget-tf-caret[data-solo='1'] {
29977
30295
  width: auto;
29978
30296
  padding: 0 6px 0 9px;
29979
30297
  gap: 4px;
29980
30298
  color: var(--vela-fg-bright);
29981
- background: var(--vela-hover-strong);
29982
30299
  font-size: 13px;
29983
30300
  font-weight: 550;
29984
30301
  white-space: nowrap;
@@ -30081,7 +30398,7 @@ var Topbar = class {
30081
30398
  this.timeframe = opts.timeframe;
30082
30399
  this.priceStyle = opts.priceStyle;
30083
30400
  const doc = host.ownerDocument;
30084
- injectStyles(STYLE_ID6, CSS4, doc);
30401
+ injectStyles(STYLE_ID7, CSS4, doc);
30085
30402
  this.el = doc.createElement("div");
30086
30403
  this.el.className = "vela-widget-topbar";
30087
30404
  this.symbolEl = doc.createElement("button");
@@ -30430,7 +30747,7 @@ function tickerIconEl(doc, base, name, className) {
30430
30747
  }
30431
30748
 
30432
30749
  // src/widget/statusline.ts
30433
- var STYLE_ID7 = "vela-widget-statusline";
30750
+ var STYLE_ID8 = "vela-widget-statusline";
30434
30751
  var CSS5 = `
30435
30752
  .vela-statusline {
30436
30753
  position: absolute;
@@ -30622,7 +30939,7 @@ var Statusline = class {
30622
30939
  this.fitMode = false;
30623
30940
  this.fitRO = null;
30624
30941
  const doc = host.ownerDocument;
30625
- injectStyles(STYLE_ID7, CSS5, doc);
30942
+ injectStyles(STYLE_ID8, CSS5, doc);
30626
30943
  host.classList.add("vela-has-statusline");
30627
30944
  this.el = doc.createElement("div");
30628
30945
  this.el.className = "vela-statusline";
@@ -30895,11 +31212,70 @@ var MarketStatusTracker = class {
30895
31212
  }
30896
31213
  };
30897
31214
 
31215
+ // src/widget/session-shading.ts
31216
+ function deriveSessionZones(regular, extended) {
31217
+ const pre = [];
31218
+ const post = [];
31219
+ for (const [extStart, extEnd] of extended) {
31220
+ const inside = regular.filter(([s, e]) => e > extStart && s < extEnd).sort((a, b) => a[0] - b[0]);
31221
+ let cursor = extStart;
31222
+ for (const [regStart, regEnd] of inside) {
31223
+ if (regStart > cursor) pre.push([cursor, Math.min(regStart, extEnd)]);
31224
+ cursor = Math.max(cursor, regEnd);
31225
+ }
31226
+ if (cursor < extEnd) post.push([cursor, extEnd]);
31227
+ }
31228
+ return { pre, post };
31229
+ }
31230
+ var FETCH_AHEAD_MS2 = 10 * 864e5;
31231
+ var MIN_LOOKBACK_MS = 3 * 864e5;
31232
+ var MAX_LOOKBACK_MS = 120 * 864e5;
31233
+ var SessionShadingTracker = class {
31234
+ constructor(onZones) {
31235
+ this.onZones = onZones;
31236
+ /** Invalidates detached async work — bumped by every track()/stop(). */
31237
+ this.epoch = 0;
31238
+ }
31239
+ /** (Re)bind to a chart's data surface + market and evaluate once. */
31240
+ track(data, symbol, opts) {
31241
+ const my = ++this.epoch;
31242
+ void this.evaluate(my, data, symbol, opts);
31243
+ }
31244
+ stop() {
31245
+ this.epoch += 1;
31246
+ }
31247
+ async evaluate(my, data, symbol, opts) {
31248
+ const resolved = data.resolve(symbol);
31249
+ const provider = resolved ? data.providerInstance(resolved.provider) : void 0;
31250
+ const si = await data.symbolInfo(symbol).catch(() => void 0);
31251
+ if (my !== this.epoch) return;
31252
+ const hasSessions = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
31253
+ if (!provider?.getCalendar || !hasSessions || !resolved) {
31254
+ this.onZones(null);
31255
+ return;
31256
+ }
31257
+ if (opts.session !== "extended") {
31258
+ this.onZones({ pre: [], post: [] });
31259
+ return;
31260
+ }
31261
+ const now = Date.now();
31262
+ const lookback = Math.max(MIN_LOOKBACK_MS, Math.min(MAX_LOOKBACK_MS, opts.lookbackMs));
31263
+ const range = { from: now - lookback, to: now + FETCH_AHEAD_MS2 };
31264
+ const [regular, extended] = await Promise.all([
31265
+ provider.getCalendar(resolved.ticker, { ...range, session: "regular" }).catch(() => null),
31266
+ provider.getCalendar(resolved.ticker, { ...range, session: "extended" }).catch(() => null)
31267
+ ]);
31268
+ if (my !== this.epoch) return;
31269
+ if (!regular || !extended) return;
31270
+ this.onZones(deriveSessionZones(regular, extended));
31271
+ }
31272
+ };
31273
+
30898
31274
  // src/widget/watermark.ts
30899
31275
  var MAX_FONT_PX = 36;
30900
31276
  var MIN_FONT_PX = 12;
30901
31277
  var FILL = 0.9;
30902
- var STYLE_ID8 = "vela-widget-watermark";
31278
+ var STYLE_ID9 = "vela-widget-watermark";
30903
31279
  var CSS6 = `
30904
31280
  .vela-watermark {
30905
31281
  position: absolute;
@@ -30938,7 +31314,7 @@ var Watermark = class {
30938
31314
  * canvas, so the mark stays out of its way. Starts true: the FIRST `load:start`
30939
31315
  * fires during chart construction, before any subscriber can see it. */
30940
31316
  this.loading = true;
30941
- injectStyles(STYLE_ID8, CSS6, host.ownerDocument);
31317
+ injectStyles(STYLE_ID9, CSS6, host.ownerDocument);
30942
31318
  this.el = host.ownerDocument.createElement("div");
30943
31319
  this.el.className = "vela-watermark";
30944
31320
  this.el.dataset.velaScreenshot = "under";
@@ -31002,7 +31378,7 @@ var RANGE_PRESETS = [
31002
31378
  { id: "ALL", tf: "W", preset: "ALL", bars: 5e3 }
31003
31379
  // everything the provider serves
31004
31380
  ];
31005
- var STYLE_ID9 = "vela-widget-bottombar";
31381
+ var STYLE_ID10 = "vela-widget-bottombar";
31006
31382
  var CSS7 = `
31007
31383
  .vela-widget-bottombar {
31008
31384
  display: flex;
@@ -31082,7 +31458,7 @@ var Bottombar = class {
31082
31458
  this.timer = null;
31083
31459
  this.timezone = opts.timezone;
31084
31460
  const doc = host.ownerDocument;
31085
- injectStyles(STYLE_ID9, CSS7, doc);
31461
+ injectStyles(STYLE_ID10, CSS7, doc);
31086
31462
  this.el = doc.createElement("div");
31087
31463
  this.el.className = "vela-widget-bottombar";
31088
31464
  for (const preset of RANGE_PRESETS) {
@@ -31109,7 +31485,8 @@ var Bottombar = class {
31109
31485
  const session = doc.createElement("span");
31110
31486
  session.className = "vela-bb-session";
31111
31487
  this.sessionEl = session;
31112
- session.title = "Session \u2014 stocks & ETFs only";
31488
+ session.style.display = "none";
31489
+ session.title = "Session \u2014 regular (RTH) vs extended (ETH) hours";
31113
31490
  for (const [key, label] of [["regular", "RTH"], ["extended", "ETH"]]) {
31114
31491
  const b = doc.createElement("button");
31115
31492
  b.className = "vela-bb-session-btn" + (key === "regular" ? " is-active" : "");
@@ -31160,15 +31537,15 @@ var Bottombar = class {
31160
31537
  }
31161
31538
  /**
31162
31539
  * Reflect the ACTIVE chart's session posture. `enabled: false` (a continuous
31163
- * market, or metadata not landed yet) shows the disabled RTH-active stub the
31164
- * pre-session-model look, byte-for-byte. Enabled, the chips become clickable and
31165
- * the active one tracks the chart's current session.
31540
+ * market, or metadata not landed yet) HIDES the toggle entirelyRTH/ETH is
31541
+ * meaningless there. Enabled, the chips appear and the active one tracks the
31542
+ * chart's current session.
31166
31543
  */
31167
31544
  setSession(state) {
31168
- if (this.sessionEl) this.sessionEl.title = state.enabled ? "Session \u2014 regular (RTH) vs extended (ETH) hours" : "Session \u2014 stocks & ETFs only";
31545
+ if (this.sessionEl) this.sessionEl.style.display = state.enabled ? "" : "none";
31169
31546
  for (const [key, b] of this.sessionButtons) {
31170
31547
  b.disabled = !state.enabled;
31171
- b.classList.toggle("is-active", state.enabled ? key === state.session : key === "regular");
31548
+ b.classList.toggle("is-active", key === (state.enabled ? state.session : "regular"));
31172
31549
  }
31173
31550
  }
31174
31551
  destroy() {
@@ -31241,7 +31618,7 @@ function filterSymbols(list, query, limit = 100) {
31241
31618
  }
31242
31619
  return [...prefix, ...substr, ...desc, ...venue].slice(0, limit);
31243
31620
  }
31244
- var STYLE_ID10 = "vela-widget-symbolpicker";
31621
+ var STYLE_ID11 = "vela-widget-symbolpicker";
31245
31622
  var CSS8 = `
31246
31623
  .vela-sp-searchrow {
31247
31624
  display: flex;
@@ -31339,7 +31716,7 @@ var SymbolPicker = class {
31339
31716
  this.seed = "";
31340
31717
  this.activeTab = "All";
31341
31718
  const doc = (opts.host ?? document.body).ownerDocument;
31342
- injectStyles(STYLE_ID10, CSS8, doc);
31719
+ injectStyles(STYLE_ID11, CSS8, doc);
31343
31720
  this.input = doc.createElement("input");
31344
31721
  this.input.className = "vela-sp-input";
31345
31722
  this.input.placeholder = "Search symbol\u2026";
@@ -31662,7 +32039,7 @@ function treeIsEmpty(panes) {
31662
32039
  }
31663
32040
 
31664
32041
  // src/widget/side-panel.ts
31665
- var STYLE_ID11 = "vela-widget-sidepanel";
32042
+ var STYLE_ID12 = "vela-widget-sidepanel";
31666
32043
  var DEFAULT_PANEL_WIDTH = 280;
31667
32044
  var DEFAULT_PANEL_MIN_WIDTH = 200;
31668
32045
  var DEFAULT_PANEL_MAX_WIDTH = 640;
@@ -31766,7 +32143,7 @@ var SidePanel = class {
31766
32143
  * a programmatic {@link setWidth}, so restoring a persisted width raises no change. */
31767
32144
  this.onWidthChange = null;
31768
32145
  const doc = host.ownerDocument;
31769
- injectStyles(STYLE_ID11, CSS9, doc);
32146
+ injectStyles(STYLE_ID12, CSS9, doc);
31770
32147
  this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
31771
32148
  this.maxWidth = opts.resizable ? opts.maxWidth ?? DEFAULT_PANEL_MAX_WIDTH : Number.POSITIVE_INFINITY;
31772
32149
  this.declaredWidth = clampPanelWidth(opts.width ?? DEFAULT_PANEL_WIDTH, this.minWidth, this.maxWidth);
@@ -31875,7 +32252,7 @@ var SidePanel = class {
31875
32252
  };
31876
32253
 
31877
32254
  // src/widget/object-tree.ts
31878
- var STYLE_ID12 = "vela-widget-objtree";
32255
+ var STYLE_ID13 = "vela-widget-objtree";
31879
32256
  var CSS10 = `
31880
32257
  /* One pane's block. The transparent border reserves the drop-target outline. */
31881
32258
  .vela-ot-pane {
@@ -32114,7 +32491,7 @@ var ObjectTree = class extends SidePanel {
32114
32491
  this.menu = null;
32115
32492
  this.menuActions = /* @__PURE__ */ new Map();
32116
32493
  this.drag = null;
32117
- injectStyles(STYLE_ID12, CSS10, host.ownerDocument);
32494
+ injectStyles(STYLE_ID13, CSS10, host.ownerDocument);
32118
32495
  this.body.addEventListener("contextmenu", (e) => this.onContextMenu(e));
32119
32496
  this.body.addEventListener("pointerdown", (e) => this.onPointerDown(e));
32120
32497
  }
@@ -33096,7 +33473,7 @@ var ObjectTree = class extends SidePanel {
33096
33473
  };
33097
33474
 
33098
33475
  // src/widget/data-window.ts
33099
- var STYLE_ID13 = "vela-widget-datawindow";
33476
+ var STYLE_ID14 = "vela-widget-datawindow";
33100
33477
  var CSS11 = `
33101
33478
  .vela-dw-group {
33102
33479
  padding: 10px 8px 4px;
@@ -33109,6 +33486,9 @@ var CSS11 = `
33109
33486
  letter-spacing: 0.08em;
33110
33487
  }
33111
33488
  .vela-dw-group:first-child { border-top: none; margin-top: 0; padding-top: 8px; }
33489
+ /* The readout is DATA, not chrome: selectable (an exception to the UI-wide
33490
+ user-select:none) so values can be copied out. The panel header stays chrome. */
33491
+ .vela-dw-group, .vela-dw-row { user-select: text; -webkit-user-select: text; cursor: text; }
33112
33492
  .vela-dw-row {
33113
33493
  display: flex;
33114
33494
  align-items: center;
@@ -33156,7 +33536,7 @@ var DataWindow = class extends SidePanel {
33156
33536
  super(host, "Data window", "vela-dw");
33157
33537
  this.chart = null;
33158
33538
  this.unsubs = [];
33159
- injectStyles(STYLE_ID13, CSS11, host.ownerDocument);
33539
+ injectStyles(STYLE_ID14, CSS11, host.ownerDocument);
33160
33540
  }
33161
33541
  toggle(open2 = this.el.hidden) {
33162
33542
  super.toggle(open2);
@@ -33358,7 +33738,7 @@ var PanelDock = class {
33358
33738
  };
33359
33739
 
33360
33740
  // src/widget/shortcuts-help.ts
33361
- var STYLE_ID14 = "vela-widget-shortcuts";
33741
+ var STYLE_ID15 = "vela-widget-shortcuts";
33362
33742
  var CSS12 = `
33363
33743
  .vela-sh-cat {
33364
33744
  color: var(--vela-fg-muted);
@@ -33384,7 +33764,7 @@ var ShortcutsHelp = class {
33384
33764
  constructor(keymap, host, onOpenChange) {
33385
33765
  this.keymap = keymap;
33386
33766
  const doc = (host ?? document.body).ownerDocument;
33387
- injectStyles(STYLE_ID14, CSS12, doc);
33767
+ injectStyles(STYLE_ID15, CSS12, doc);
33388
33768
  this.list = doc.createElement("div");
33389
33769
  this.dialog = new Dialog({
33390
33770
  title: "Keyboard shortcuts",
@@ -33571,6 +33951,9 @@ var ChartContextMenu = class {
33571
33951
  host,
33572
33952
  items: [],
33573
33953
  placement: "bottom-start",
33954
+ // Pointer-anchored action menu: checked state reads as a leading ✓, not a
33955
+ // washed row (which would read as hover in a menu with no trigger button).
33956
+ checkmarks: true,
33574
33957
  onSelect: (id) => this.run(id)
33575
33958
  });
33576
33959
  host.addEventListener("contextmenu", this.onContextMenu);
@@ -33672,7 +34055,7 @@ var ChartContextMenu = class {
33672
34055
  };
33673
34056
 
33674
34057
  // src/widget/indicator-picker.ts
33675
- var STYLE_ID15 = "vela-widget-indpicker";
34058
+ var STYLE_ID16 = "vela-widget-indpicker";
33676
34059
  var CSS13 = `
33677
34060
  .vela-ip-searchrow {
33678
34061
  display: flex;
@@ -33750,7 +34133,7 @@ var IndicatorPicker = class {
33750
34133
  this.isOpen = false;
33751
34134
  this.opts = opts;
33752
34135
  const doc = (opts.host ?? document.body).ownerDocument;
33753
- injectStyles(STYLE_ID15, CSS13, doc);
34136
+ injectStyles(STYLE_ID16, CSS13, doc);
33754
34137
  this.search = doc.createElement("input");
33755
34138
  this.search.className = "vela-ip-search";
33756
34139
  this.search.placeholder = "Search\u2026";
@@ -33878,7 +34261,7 @@ var IndicatorPicker = class {
33878
34261
  };
33879
34262
 
33880
34263
  // src/widget/timeframe-quick.ts
33881
- var STYLE_ID16 = "vela-widget-tfquick";
34264
+ var STYLE_ID17 = "vela-widget-tfquick";
33882
34265
  var CSS14 = `
33883
34266
  .vela-tq-input {
33884
34267
  display: block;
@@ -33902,7 +34285,7 @@ var CSS14 = `
33902
34285
  var TimeframeQuick = class {
33903
34286
  constructor(opts) {
33904
34287
  const doc = (opts.host ?? document.body).ownerDocument;
33905
- injectStyles(STYLE_ID16, CSS14, doc);
34288
+ injectStyles(STYLE_ID17, CSS14, doc);
33906
34289
  this.input = doc.createElement("input");
33907
34290
  this.input.className = "vela-tq-input";
33908
34291
  this.input.setAttribute("spellcheck", "false");
@@ -34084,7 +34467,7 @@ var LayoutModeController = class {
34084
34467
  };
34085
34468
 
34086
34469
  // src/widget/mobile-bar.ts
34087
- var STYLE_ID17 = "vela-widget-mobilebar";
34470
+ var STYLE_ID18 = "vela-widget-mobilebar";
34088
34471
  var CSS15 = `
34089
34472
  .vela-mobilebar {
34090
34473
  display: none;
@@ -34135,7 +34518,7 @@ var MobileBar = class {
34135
34518
  constructor(host, opts) {
34136
34519
  this.opts = opts;
34137
34520
  const doc = host.ownerDocument;
34138
- injectStyles(STYLE_ID17, CSS15, doc);
34521
+ injectStyles(STYLE_ID18, CSS15, doc);
34139
34522
  this.el = doc.createElement("div");
34140
34523
  this.el.className = "vela-mobilebar";
34141
34524
  const item = (cls, label, onClick, icon2) => {
@@ -34194,7 +34577,7 @@ var MobileBar = class {
34194
34577
  };
34195
34578
 
34196
34579
  // src/widget/timeframe-drawer.ts
34197
- var STYLE_ID18 = "vela-widget-tf-drawer";
34580
+ var STYLE_ID19 = "vela-widget-tf-drawer";
34198
34581
  var CSS16 = `
34199
34582
  .vela-tfd-heading {
34200
34583
  padding: 4px 2px 8px;
@@ -34251,7 +34634,7 @@ var TimeframeDrawer = class {
34251
34634
  this.rangeChips = /* @__PURE__ */ new Map();
34252
34635
  this.tfChips = /* @__PURE__ */ new Map();
34253
34636
  const doc = opts.host.ownerDocument;
34254
- injectStyles(STYLE_ID18, CSS16, doc);
34637
+ injectStyles(STYLE_ID19, CSS16, doc);
34255
34638
  this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
34256
34639
  const rangeHeading = doc.createElement("div");
34257
34640
  rangeHeading.className = "vela-tfd-heading";
@@ -34309,7 +34692,7 @@ var TimeframeDrawer = class {
34309
34692
  };
34310
34693
 
34311
34694
  // src/widget/drawings-drawer.ts
34312
- var STYLE_ID19 = "vela-widget-drawings-drawer";
34695
+ var STYLE_ID20 = "vela-widget-drawings-drawer";
34313
34696
  var CSS17 = `
34314
34697
  /* Search + tabs stay pinned while the tool list scrolls underneath. */
34315
34698
  .vela-dd-sticky {
@@ -34412,7 +34795,7 @@ var DrawingsDrawer = class {
34412
34795
  this.definition = { groups: [] };
34413
34796
  this.activeGroup = "";
34414
34797
  const doc = opts.host.ownerDocument;
34415
- injectStyles(STYLE_ID19, CSS17, doc);
34798
+ injectStyles(STYLE_ID20, CSS17, doc);
34416
34799
  this.drawer = new Drawer({
34417
34800
  host: opts.host,
34418
34801
  title: "Drawings",
@@ -34554,7 +34937,7 @@ var DrawingsDrawer = class {
34554
34937
  };
34555
34938
 
34556
34939
  // src/widget/more-drawer.ts
34557
- var STYLE_ID20 = "vela-widget-more-drawer";
34940
+ var STYLE_ID21 = "vela-widget-more-drawer";
34558
34941
  var CSS18 = `
34559
34942
  .vela-md-actions {
34560
34943
  display: flex;
@@ -34630,7 +35013,7 @@ var MoreDrawer = class {
34630
35013
  constructor(opts) {
34631
35014
  this.opts = opts;
34632
35015
  this.view = "main";
34633
- injectStyles(STYLE_ID20, CSS18, opts.host.ownerDocument);
35016
+ injectStyles(STYLE_ID21, CSS18, opts.host.ownerDocument);
34634
35017
  this.drawer = new Drawer({ host: opts.host, onOpenChange: opts.onOpenChange });
34635
35018
  }
34636
35019
  open() {
@@ -34822,7 +35205,7 @@ var MoreDrawer = class {
34822
35205
  };
34823
35206
 
34824
35207
  // src/widget/timezone-drawer.ts
34825
- var STYLE_ID21 = "vela-widget-timezone-drawer";
35208
+ var STYLE_ID22 = "vela-widget-timezone-drawer";
34826
35209
  var CSS19 = `
34827
35210
  .vela-tzd-list { padding: 2px 0 4px; }
34828
35211
  .vela-tzd-row {
@@ -34842,7 +35225,7 @@ var CSS19 = `
34842
35225
  var TimezoneDrawer = class {
34843
35226
  constructor(opts) {
34844
35227
  this.opts = opts;
34845
- injectStyles(STYLE_ID21, CSS19, opts.host.ownerDocument);
35228
+ injectStyles(STYLE_ID22, CSS19, opts.host.ownerDocument);
34846
35229
  this.drawer = new Drawer({ host: opts.host, title: "Time zone", onOpenChange: opts.onOpenChange });
34847
35230
  }
34848
35231
  open() {
@@ -34880,7 +35263,7 @@ var TimezoneDrawer = class {
34880
35263
  };
34881
35264
 
34882
35265
  // src/widget/price-scale-drawer.ts
34883
- var STYLE_ID22 = "vela-widget-price-scale-drawer";
35266
+ var STYLE_ID23 = "vela-widget-price-scale-drawer";
34884
35267
  var CSS20 = `
34885
35268
  .vela-psd-list { padding: 2px 0 4px; }
34886
35269
  .vela-psd-row {
@@ -34910,7 +35293,7 @@ var PriceScaleDrawer = class {
34910
35293
  constructor(opts) {
34911
35294
  this.opts = opts;
34912
35295
  this.pane = null;
34913
- injectStyles(STYLE_ID22, CSS20, opts.host.ownerDocument);
35296
+ injectStyles(STYLE_ID23, CSS20, opts.host.ownerDocument);
34914
35297
  this.drawer = new Drawer({ host: opts.host, title: "Price scale", onOpenChange: opts.onOpenChange });
34915
35298
  }
34916
35299
  open() {
@@ -35046,7 +35429,7 @@ var PriceScaleDrawer = class {
35046
35429
  };
35047
35430
 
35048
35431
  // src/widget/drawing-pill.ts
35049
- var STYLE_ID23 = "vela-widget-drawing-pill";
35432
+ var STYLE_ID24 = "vela-widget-drawing-pill";
35050
35433
  var CSS21 = `
35051
35434
  .vela-drawpill { display: none; }
35052
35435
  [data-layout='mobile'] .vela-drawpill {
@@ -35107,7 +35490,7 @@ var DrawingPill = class {
35107
35490
  this.chart = null;
35108
35491
  this.chartSubs = [];
35109
35492
  const doc = host.ownerDocument;
35110
- injectStyles(STYLE_ID23, CSS21, doc);
35493
+ injectStyles(STYLE_ID24, CSS21, doc);
35111
35494
  this.el = doc.createElement("div");
35112
35495
  this.el.className = "vela-drawpill";
35113
35496
  this.el.hidden = true;
@@ -35495,7 +35878,7 @@ var WidgetHistory = class {
35495
35878
  };
35496
35879
 
35497
35880
  // src/widget/toast.ts
35498
- var STYLE_ID24 = "vela-widget-toast";
35881
+ var STYLE_ID25 = "vela-widget-toast";
35499
35882
  var CSS22 = `
35500
35883
  .vela-toast {
35501
35884
  position: absolute;
@@ -35526,7 +35909,7 @@ var CSS22 = `
35526
35909
  var Toast = class {
35527
35910
  constructor(host) {
35528
35911
  this.timer = null;
35529
- injectStyles(STYLE_ID24, CSS22, host.ownerDocument);
35912
+ injectStyles(STYLE_ID25, CSS22, host.ownerDocument);
35530
35913
  this.el = host.ownerDocument.createElement("div");
35531
35914
  this.el.className = "vela-toast";
35532
35915
  host.appendChild(this.el);
@@ -35609,6 +35992,11 @@ var WIDGET_CSS = `
35609
35992
  `;
35610
35993
  var VelaWidget = class {
35611
35994
  constructor(container, opts) {
35995
+ /** Keeps the pre/post-market shading on the symbol's real calendar (see {@link SessionShadingTracker}). */
35996
+ this.sessionShading = new SessionShadingTracker((zones) => this.inner?.renderer.set("sessionZones", zones));
35997
+ /** Whether the ACTIVE symbol declares real trading sessions (RTH/ETH meaningful) —
35998
+ * gates the bottombar toggle and the settings dialog's Trading session group. */
35999
+ this.sessionsAvailable = false;
35612
36000
  this.shortcutsHelp = null;
35613
36001
  this.inner = null;
35614
36002
  /** The manifest library (loaded once). */
@@ -36072,17 +36460,140 @@ var VelaWidget = class {
36072
36460
  this.markStateDirty();
36073
36461
  void this.inner?.setMarket({ session });
36074
36462
  }
36075
- /** Re-derive the RTH/ETH toggle's posture from the ACTIVE symbol's metadata: enabled
36076
- * iff the resolved symbol declares a real session vocabulary (not `24x7`). */
36463
+ /** Re-derive the session chrome from the ACTIVE symbol's metadata: the RTH/ETH
36464
+ * toggle shows (and the settings dialog's Trading session group appears) iff the
36465
+ * resolved symbol declares a real session vocabulary (not `24x7`); the pre/post
36466
+ * shading re-derives for the new market either way. */
36077
36467
  refreshSessionToggle() {
36078
36468
  const chart = this.inner;
36079
- if (!chart || !this.bottombar) return;
36469
+ if (!chart) return;
36080
36470
  void chart.data.symbolInfo(this.symbol).then((si) => {
36081
36471
  if (this.destroyed || this.inner !== chart) return;
36082
36472
  const enabled = typeof si?.session === "string" && si.session !== "" && si.session !== "24x7";
36083
36473
  this.bottombar?.setSession({ session: this.session ?? "regular", enabled });
36474
+ if (enabled !== this.sessionsAvailable) {
36475
+ this.sessionsAvailable = enabled;
36476
+ this.pushSettingsSections();
36477
+ }
36478
+ this.refreshSessionShading();
36084
36479
  });
36085
36480
  }
36481
+ /** (Re)derive the pre/post-market shading bands for the current market. The loaded
36482
+ * depth (bars × timeframe) bounds the calendar fetch; the tracker clamps it. */
36483
+ refreshSessionShading() {
36484
+ const chart = this.inner;
36485
+ if (!chart) return;
36486
+ const lookbackMs = Math.max(this.bars, this.rangeBars) * timeframeMs(this.timeframe);
36487
+ this.sessionShading.track(chart.data, this.symbol, { session: this.session ?? "regular", lookbackMs });
36488
+ }
36489
+ /** The session-shade colors live in the renderer CONFIG (persisted with it, edited
36490
+ * live by the dialog swatch) — the widget only proxies them into its settings rows. */
36491
+ sessionShadeColor(key) {
36492
+ const cfg = this.inner?.renderer.getConfig();
36493
+ const v = cfg?.sessions?.[key];
36494
+ return typeof v === "string" ? v : "";
36495
+ }
36496
+ setSessionShadeColor(key, color) {
36497
+ this.inner?.renderer.applyConfig({ sessions: { [key]: color } });
36498
+ this.markStateDirty();
36499
+ }
36500
+ /**
36501
+ * (Re)contribute the widget's settings-dialog sections: status line parts, the fetch
36502
+ * depth, the watermark toggle, and — only while the active symbol HAS sessions — the
36503
+ * Trading session group (RTH/ETH switch + the pre/post-market shading colors) inside
36504
+ * the Symbol tab. Re-run whenever a gate changes (the dialog reads them on open).
36505
+ */
36506
+ pushSettingsSections() {
36507
+ const chart = this.inner;
36508
+ if (!chart) return;
36509
+ const rth = "Regular hours (RTH)";
36510
+ const eth = "Extended hours (ETH)";
36511
+ const sessionSection = {
36512
+ title: "Trading session",
36513
+ placement: "symbol",
36514
+ rows: [
36515
+ {
36516
+ kind: "select",
36517
+ label: "Session",
36518
+ options: [rth, eth],
36519
+ get: () => (this.session ?? "regular") === "extended" ? eth : rth,
36520
+ set: (v) => this.setSession(v === eth ? "extended" : "regular")
36521
+ },
36522
+ {
36523
+ kind: "color",
36524
+ label: "Pre-market",
36525
+ get: () => this.sessionShadeColor("premarketColor"),
36526
+ set: (v) => this.setSessionShadeColor("premarketColor", v)
36527
+ },
36528
+ {
36529
+ kind: "color",
36530
+ label: "Post-market",
36531
+ get: () => this.sessionShadeColor("postmarketColor"),
36532
+ set: (v) => this.setSessionShadeColor("postmarketColor", v)
36533
+ }
36534
+ ]
36535
+ };
36536
+ const advanced = {
36537
+ title: "Advanced",
36538
+ placement: "end",
36539
+ rows: [
36540
+ {
36541
+ kind: "select",
36542
+ label: "Bars to fetch",
36543
+ options: ["500", "1000", "2000", "5000", "10000", "20000"],
36544
+ get: () => String(this.bars),
36545
+ set: (v) => {
36546
+ this.bars = Number(v);
36547
+ this.markStateDirty();
36548
+ void this.inner?.setMarket({ bars: Math.max(this.bars, this.rangeBars) });
36549
+ }
36550
+ }
36551
+ ]
36552
+ };
36553
+ const watermarkSection = {
36554
+ title: "Watermark",
36555
+ placement: "symbol",
36556
+ rows: [
36557
+ {
36558
+ kind: "toggle",
36559
+ label: "Symbol watermark",
36560
+ get: () => this.watermarkOn,
36561
+ set: (v) => this.setWatermarkVisible(v)
36562
+ }
36563
+ ]
36564
+ };
36565
+ const sections = [];
36566
+ if (this.statusline) {
36567
+ const sl = this.statusline;
36568
+ sections.push({
36569
+ title: "Status line",
36570
+ rows: [
36571
+ { kind: "heading", label: "Status line" },
36572
+ { kind: "toggle", label: "Symbol name", get: () => sl.partVisible("name"), set: (v) => sl.setPartVisible("name", v) },
36573
+ { kind: "toggle", label: "Market status", get: () => sl.partVisible("market"), set: (v) => sl.setPartVisible("market", v) },
36574
+ { kind: "toggle", label: "OHLC values", get: () => sl.partVisible("ohlc"), set: (v) => sl.setPartVisible("ohlc", v) },
36575
+ { kind: "toggle", label: "Bar change values", get: () => sl.partVisible("change"), set: (v) => sl.setPartVisible("change", v) },
36576
+ { kind: "heading", label: "Indicators" },
36577
+ {
36578
+ kind: "toggle",
36579
+ label: "Titles",
36580
+ get: () => this.indicatorTitlesOn,
36581
+ set: (v) => this.setIndicatorTitlesVisible(v)
36582
+ },
36583
+ {
36584
+ kind: "toggle",
36585
+ label: "Values",
36586
+ get: () => this.indicatorValuesOn,
36587
+ set: (v) => this.setIndicatorValuesVisible(v)
36588
+ }
36589
+ ]
36590
+ });
36591
+ }
36592
+ sections.push(advanced);
36593
+ if (this.sessionsAvailable) sections.push(sessionSection);
36594
+ sections.push(watermarkSection);
36595
+ chart.renderer.setSettingsSections(sections);
36596
+ }
36086
36597
  /** Star/unstar a timeframe — the topbar chips and dropdown stars follow, and the
36087
36598
  * set persists with the rest of the shell state. */
36088
36599
  setTimeframeFavorite(tf, on) {
@@ -36388,6 +36899,7 @@ var VelaWidget = class {
36388
36899
  this.indicatorPicker?.destroy();
36389
36900
  this.tfQuick.destroy();
36390
36901
  this.marketStatus?.stop();
36902
+ this.sessionShading.stop();
36391
36903
  this.statusline?.destroy();
36392
36904
  this.watermark?.destroy();
36393
36905
  this.bottombar?.destroy();
@@ -36526,7 +37038,10 @@ var VelaWidget = class {
36526
37038
  this.refreshSessionToggle();
36527
37039
  this.marketStatus?.track(chart.data, symbol);
36528
37040
  }
36529
- if (timeframe !== this.timeframe) this.syncTimeframeChrome(timeframe);
37041
+ if (timeframe !== this.timeframe) {
37042
+ this.syncTimeframeChrome(timeframe);
37043
+ this.refreshSessionShading();
37044
+ }
36530
37045
  });
36531
37046
  this.history.onChart(chart);
36532
37047
  chart.on("alert", (alert) => {
@@ -36575,67 +37090,7 @@ var VelaWidget = class {
36575
37090
  this.statusline?.onChart(chart);
36576
37091
  this.drawingPill.onChart(chart);
36577
37092
  this.syncStatuslineColors();
36578
- const advanced = {
36579
- title: "Advanced",
36580
- placement: "end",
36581
- rows: [
36582
- {
36583
- kind: "select",
36584
- label: "Bars to fetch",
36585
- options: ["500", "1000", "2000", "5000", "10000", "20000"],
36586
- get: () => String(this.bars),
36587
- set: (v) => {
36588
- this.bars = Number(v);
36589
- this.markStateDirty();
36590
- void this.inner?.setMarket({ bars: Math.max(this.bars, this.rangeBars) });
36591
- }
36592
- }
36593
- ]
36594
- };
36595
- const watermarkSection = {
36596
- title: "Watermark",
36597
- placement: "symbol",
36598
- rows: [
36599
- {
36600
- kind: "toggle",
36601
- label: "Symbol watermark",
36602
- get: () => this.watermarkOn,
36603
- set: (v) => this.setWatermarkVisible(v)
36604
- }
36605
- ]
36606
- };
36607
- if (this.statusline) {
36608
- const sl = this.statusline;
36609
- chart.renderer.setSettingsSections([
36610
- {
36611
- title: "Status line",
36612
- rows: [
36613
- { kind: "heading", label: "Status line" },
36614
- { kind: "toggle", label: "Symbol name", get: () => sl.partVisible("name"), set: (v) => sl.setPartVisible("name", v) },
36615
- { kind: "toggle", label: "Market status", get: () => sl.partVisible("market"), set: (v) => sl.setPartVisible("market", v) },
36616
- { kind: "toggle", label: "OHLC values", get: () => sl.partVisible("ohlc"), set: (v) => sl.setPartVisible("ohlc", v) },
36617
- { kind: "toggle", label: "Bar change values", get: () => sl.partVisible("change"), set: (v) => sl.setPartVisible("change", v) },
36618
- { kind: "heading", label: "Indicators" },
36619
- {
36620
- kind: "toggle",
36621
- label: "Titles",
36622
- get: () => this.indicatorTitlesOn,
36623
- set: (v) => this.setIndicatorTitlesVisible(v)
36624
- },
36625
- {
36626
- kind: "toggle",
36627
- label: "Values",
36628
- get: () => this.indicatorValuesOn,
36629
- set: (v) => this.setIndicatorValuesVisible(v)
36630
- }
36631
- ]
36632
- },
36633
- advanced,
36634
- watermarkSection
36635
- ]);
36636
- } else {
36637
- chart.renderer.setSettingsSections([advanced, watermarkSection]);
36638
- }
37093
+ this.pushSettingsSections();
36639
37094
  void chart.ready().then(() => {
36640
37095
  if (this.buildSeq !== seq || this.destroyed) return;
36641
37096
  if (this.pendingRange) {