@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
@@ -1,6 +1,6 @@
1
- import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-7Y7CJ7EN.js';
2
- import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-QHZ7IXFL.js';
3
- import { icon, iconAt, withAlpha, NEUTRAL, BEARISH, BULLISH, isDarkColor, ACCENT, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-PN5KWFZ4.js';
1
+ import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-BEJ57HP4.js';
2
+ import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-OICPLNU7.js';
3
+ import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-PN5KWFZ4.js';
4
4
 
5
5
  // src/core/events/EventBus.ts
6
6
  var TypedEventBus = class {
@@ -2184,7 +2184,7 @@ function attachChromeTooltip(anchor, opts) {
2184
2184
  } else {
2185
2185
  const left = Math.min(a.left - h.left, Math.max(0, h.width - tip.offsetWidth - 4));
2186
2186
  tip.style.left = `${Math.max(0, left)}px`;
2187
- tip.style.top = `${a.bottom - h.top + 6}px`;
2187
+ tip.style.top = opts.placement === "above" ? `${Math.max(0, a.top - h.top - tip.offsetHeight - 6)}px` : `${a.bottom - h.top + 6}px`;
2188
2188
  }
2189
2189
  };
2190
2190
  const arm = (e) => {
@@ -2844,6 +2844,8 @@ var IndicatorInputsDialog = class {
2844
2844
  this.row = null;
2845
2845
  this.snapshot = null;
2846
2846
  this.dialogTips = [];
2847
+ /** Re-applies every `when` gate against current values; set while the dialog is open. */
2848
+ this.refreshVisibility = null;
2847
2849
  this.choicePop = null;
2848
2850
  this.calendarPop = null;
2849
2851
  this.calendarAnchor = null;
@@ -2906,14 +2908,17 @@ var IndicatorInputsDialog = class {
2906
2908
  tabs.style.cssText = `display:flex;gap:12px;padding:0 20px;border-bottom:1px solid ${border};flex:0 0 auto;`;
2907
2909
  const tabEls = [];
2908
2910
  const bodies = [];
2911
+ const gates = [];
2912
+ let active = 0;
2909
2913
  const activate = (idx) => {
2914
+ active = idx;
2910
2915
  for (let i = 0; i < tabEls.length; i += 1) {
2911
- const active = i === idx;
2916
+ const on = i === idx;
2912
2917
  const el = tabEls[i];
2913
- el.style.borderBottomColor = active ? fg : "transparent";
2914
- el.style.color = active ? "inherit" : "var(--vela-fg-muted)";
2915
- el.classList.toggle("vela-ind-tab-active", active);
2916
- bodies[i].style.display = active ? "grid" : "none";
2918
+ el.style.borderBottomColor = on ? fg : "transparent";
2919
+ el.style.color = on ? "inherit" : "var(--vela-fg-muted)";
2920
+ el.classList.toggle("vela-ind-tab-active", on);
2921
+ bodies[i].style.display = on ? "grid" : "none";
2917
2922
  }
2918
2923
  };
2919
2924
  for (const [idx, def] of tabDefs.entries()) {
@@ -2924,11 +2929,24 @@ var IndicatorInputsDialog = class {
2924
2929
  tab.addEventListener("click", () => activate(idx));
2925
2930
  tabs.appendChild(tab);
2926
2931
  tabEls.push(tab);
2927
- bodies.push(this.buildTabBody(def.inputs, row));
2932
+ bodies.push(this.buildTabBody(def.inputs, row, gates));
2933
+ if (def.inputs.every((d) => d.when)) {
2934
+ gates.push({ el: tab, visible: (bag) => def.inputs.some((d) => inputVisible(d.when, bag)) });
2935
+ }
2928
2936
  }
2929
2937
  ui.body.appendChild(tabs);
2930
2938
  for (const b of bodies) ui.body.appendChild(b);
2931
2939
  activate(0);
2940
+ const refresh = () => {
2941
+ const bag = valuesBag(row);
2942
+ for (const g of gates) g.el.style.display = g.visible(bag) ? "" : "none";
2943
+ if (tabEls[active]?.style.display === "none") {
2944
+ const next = tabEls.findIndex((el) => el.style.display !== "none");
2945
+ if (next >= 0) activate(next);
2946
+ }
2947
+ };
2948
+ this.refreshVisibility = refresh;
2949
+ refresh();
2932
2950
  const onBackdropDown = (e) => {
2933
2951
  if (e.target !== ui.backdrop && e.target !== ui.positioner) return;
2934
2952
  if (isPopoverOpen() || eventDismissedPopover(e)) {
@@ -2948,16 +2966,27 @@ var IndicatorInputsDialog = class {
2948
2966
  }
2949
2967
  /** One tab's scrollable body — one shared grid so every group's controls share a
2950
2968
  * left edge (a 100px number field lines up with a wider session/time pair). */
2951
- buildTabBody(inputs, row) {
2969
+ buildTabBody(inputs, row, gates) {
2952
2970
  const body = document.createElement("div");
2953
2971
  body.style.cssText = inputDialogBodyStyle(this.host.mobile());
2954
2972
  for (const group of groupInputs(inputs)) {
2955
2973
  const section = document.createElement("div");
2956
2974
  section.style.cssText = "display:contents;";
2975
+ let header = null;
2957
2976
  if (group.name) {
2958
- section.appendChild(fieldSection(group.name, { variant: "inputs", first: body.childElementCount === 0 }));
2977
+ header = fieldSection(group.name, { variant: "inputs", first: body.childElementCount === 0 });
2978
+ section.appendChild(header);
2979
+ }
2980
+ for (const inputRow of group.rows) {
2981
+ const el = this.buildInputRow(section, inputRow, row);
2982
+ if (inputRow.some((d) => d.when)) {
2983
+ gates.push({ el, visible: (bag) => rowVisible(inputRow, bag) });
2984
+ }
2985
+ }
2986
+ if (header && group.rows.every((r) => r.every((d) => d.when))) {
2987
+ const rows = group.rows;
2988
+ gates.push({ el: header, visible: (bag) => rows.some((r) => rowVisible(r, bag)) });
2959
2989
  }
2960
- for (const inputRow of group.rows) this.buildInputRow(section, inputRow, row);
2961
2990
  body.appendChild(section);
2962
2991
  }
2963
2992
  return body;
@@ -2965,6 +2994,7 @@ var IndicatorInputsDialog = class {
2965
2994
  close() {
2966
2995
  document.removeEventListener("keydown", this.onDialogKey);
2967
2996
  closeOpenPopovers();
2997
+ this.refreshVisibility = null;
2968
2998
  this.choicePop = null;
2969
2999
  this.calendarPop = null;
2970
3000
  this.calendarAnchor = null;
@@ -2994,11 +3024,21 @@ var IndicatorInputsDialog = class {
2994
3024
  }
2995
3025
  this.close();
2996
3026
  }
3027
+ /** Write one edit through: store it, notify the host, and re-apply the `when` gates. */
3028
+ commit(row, key, value) {
3029
+ row.values[key] = value;
3030
+ this.host.onChange?.({ indicatorId: row.id, key, value });
3031
+ this.refreshVisibility?.();
3032
+ }
2997
3033
  /** One settings row (or several `inline=` inputs) placed into a section's grid. */
2998
3034
  buildInputRow(grid, decls, row) {
2999
3035
  const idOf = (inp) => `vela-inp-${row.id}-${inp.key}`;
3036
+ const append = (el) => {
3037
+ grid.appendChild(el);
3038
+ return el;
3039
+ };
3000
3040
  if (decls.length > 1) {
3001
- grid.appendChild(fieldRow({
3041
+ return append(fieldRow({
3002
3042
  label: "",
3003
3043
  inline: decls.map((d) => ({
3004
3044
  label: nameOf(d) || void 0,
@@ -3009,14 +3049,13 @@ var IndicatorInputsDialog = class {
3009
3049
  })),
3010
3050
  info: [...decls].reverse().find((d) => d.tooltip)?.tooltip ? this.infoButton([...decls].reverse().find((d) => d.tooltip).tooltip) : void 0
3011
3051
  }));
3012
- return;
3013
3052
  }
3014
3053
  const lead = decls[0];
3015
3054
  const id = idOf(lead);
3016
3055
  const info = lead.tooltip ? this.infoButton(lead.tooltip) : void 0;
3017
3056
  if (lead.type === "bool") {
3018
3057
  const current = Boolean(row.values[lead.key] ?? lead.defval);
3019
- grid.appendChild(fieldRow({
3058
+ return append(fieldRow({
3020
3059
  label: nameOf(lead),
3021
3060
  id,
3022
3061
  bool: true,
@@ -3024,26 +3063,21 @@ var IndicatorInputsDialog = class {
3024
3063
  toggle: {
3025
3064
  id,
3026
3065
  checked: current,
3027
- onChange: (v) => {
3028
- row.values[lead.key] = v;
3029
- this.host.onChange?.({ indicatorId: row.id, key: lead.key, value: v });
3030
- }
3066
+ onChange: (v) => this.commit(row, lead.key, v)
3031
3067
  }
3032
3068
  }));
3033
- return;
3034
3069
  }
3035
3070
  if (lead.type === "text_area") {
3036
- grid.appendChild(fieldRow({
3071
+ return append(fieldRow({
3037
3072
  label: nameOf(lead),
3038
3073
  id,
3039
3074
  stacked: true,
3040
3075
  info,
3041
3076
  control: this.buildControl(row, lead, id)
3042
3077
  }));
3043
- return;
3044
3078
  }
3045
3079
  const fit = lead.type === "color" || lead.type === "session" || lead.type === "time";
3046
- grid.appendChild(fieldRow({
3080
+ return append(fieldRow({
3047
3081
  label: nameOf(lead),
3048
3082
  id,
3049
3083
  fit,
@@ -3055,10 +3089,7 @@ var IndicatorInputsDialog = class {
3055
3089
  /** Build the typed control for one input, committing edits live via `onChange`. */
3056
3090
  buildControl(row, inp, id) {
3057
3091
  const current = row.values[inp.key] ?? inp.defval;
3058
- const emit = (value) => {
3059
- row.values[inp.key] = value;
3060
- this.host.onChange?.({ indicatorId: row.id, key: inp.key, value });
3061
- };
3092
+ const emit = (value) => this.commit(row, inp.key, value);
3062
3093
  if (inp.type === "bool") {
3063
3094
  return buildFieldControl({ kind: "switch", id, checked: Boolean(current), onChange: (v) => emit(v) }).el;
3064
3095
  }
@@ -3479,6 +3510,14 @@ function tabInputs(inputs) {
3479
3510
  }
3480
3511
  return names.map((name) => ({ name, inputs: byTab.get(name) }));
3481
3512
  }
3513
+ function rowVisible(decls, bag) {
3514
+ return decls.some((d) => inputVisible(d.when, bag));
3515
+ }
3516
+ function valuesBag(row) {
3517
+ const bag = {};
3518
+ for (const inp of row.inputs) bag[inp.key] = row.values[inp.key] ?? inp.defval;
3519
+ return bag;
3520
+ }
3482
3521
  function groupInputs(inputs) {
3483
3522
  const order = [];
3484
3523
  const byGroup = /* @__PURE__ */ new Map();
@@ -3941,6 +3980,8 @@ var InputsUI = class {
3941
3980
  host: this.container,
3942
3981
  items,
3943
3982
  placement: "bottom-start",
3983
+ // Right-click action menu — checked state reads as a leading ✓ (see the kit Menu).
3984
+ checkmarks: true,
3944
3985
  onSelect: (itemId) => {
3945
3986
  if (itemId !== "values" || !this.rowMenuId) return;
3946
3987
  const target = this.rows.get(this.rowMenuId);
@@ -4276,6 +4317,8 @@ var InputsUI = class {
4276
4317
  syncRowActions(row) {
4277
4318
  const open = row.highlighted;
4278
4319
  row.controlsEl.style.display = open || row.hidden ? "inline-flex" : "none";
4320
+ if (open) row.el.appendChild(row.statusEl);
4321
+ else row.el.insertBefore(row.statusEl, row.valuesEl);
4279
4322
  for (const child of Array.from(row.controlsEl.children)) {
4280
4323
  if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
4281
4324
  if (child === row.extrasEl) {
@@ -4519,6 +4562,148 @@ var PaneControls = class {
4519
4562
  }
4520
4563
  };
4521
4564
 
4565
+ // src/renderers/native/chrome/axisLayout.ts
4566
+ var AXIS_MASTER_W = 64;
4567
+ var AXIS_MERGED_W = 56;
4568
+ var PANE_SEPARATOR_PX = 3;
4569
+ function axisColumnX(dataW, column) {
4570
+ return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
4571
+ }
4572
+
4573
+ // src/renderers/native/chrome/AxisScaleButtons.ts
4574
+ var STYLE_ID3 = "vela-axis-scale-buttons";
4575
+ var BTN_PX = 18;
4576
+ var BTN_GAP = 4;
4577
+ var AXIS_BORDER = 1;
4578
+ var CLUSTER_W = AXIS_MASTER_W - AXIS_BORDER;
4579
+ var PAD_TOP = 8;
4580
+ var PAD_BOTTOM = 6;
4581
+ var CLUSTER_H = BTN_PX + PAD_TOP + PAD_BOTTOM;
4582
+ var MIN_PANE_H = 48;
4583
+ function ensureStyles3() {
4584
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID3)) return;
4585
+ const st = document.createElement("style");
4586
+ st.id = STYLE_ID3;
4587
+ st.textContent = `
4588
+ .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);}
4589
+ .vela-axis-btn:hover{background:var(--vela-active);color:var(--vela-fg-bright);}
4590
+ .vela-axis-on,.vela-axis-on:hover{background:var(--vela-selected-bg);color:var(--vela-selected-fg);}
4591
+ `;
4592
+ document.head.appendChild(st);
4593
+ }
4594
+ var AxisScaleButtons = class {
4595
+ constructor(plot, theme, deps) {
4596
+ this.plot = plot;
4597
+ this.theme = theme;
4598
+ this.deps = deps;
4599
+ this.tipDisposers = [];
4600
+ this.hoverPaneId = null;
4601
+ /** Reveal over the master scale column of the pane under the cursor (mouse only —
4602
+ * touch has no hover, and a drag-rescale on the axis must not sprout buttons). */
4603
+ this.onPlotMove = (e) => {
4604
+ if (e.pointerType !== "mouse") return;
4605
+ const rect = this.plot.getBoundingClientRect();
4606
+ const x = e.clientX - rect.left;
4607
+ const gutterLeft = rect.width - this.deps.rightAxis();
4608
+ let hit = null;
4609
+ if (x >= gutterLeft && x <= gutterLeft + AXIS_MASTER_W) {
4610
+ const y = e.clientY - rect.top;
4611
+ for (const p of this.deps.panes()) {
4612
+ if (p.collapsed || p.height < MIN_PANE_H) continue;
4613
+ if (y >= p.top && y <= p.top + p.height) {
4614
+ hit = p.id;
4615
+ break;
4616
+ }
4617
+ }
4618
+ }
4619
+ this.setHoverPane(hit);
4620
+ if (hit) this.sync();
4621
+ };
4622
+ this.onPlotLeave = () => this.setHoverPane(null);
4623
+ ensureStyles3();
4624
+ this.cluster = document.createElement("div");
4625
+ Object.assign(this.cluster.style, {
4626
+ position: "absolute",
4627
+ display: "none",
4628
+ justifyContent: "center",
4629
+ alignItems: "center",
4630
+ gap: `${BTN_GAP}px`,
4631
+ padding: `${PAD_TOP}px 0 ${PAD_BOTTOM}px`,
4632
+ width: `${CLUSTER_W}px`,
4633
+ boxSizing: "border-box",
4634
+ zIndex: "6",
4635
+ pointerEvents: "auto"
4636
+ });
4637
+ this.applyTheme();
4638
+ this.autoBtn = this.button("A", "Auto (fits data to screen)", () => this.deps.onToggleAuto(this.hoverPaneId ?? ""));
4639
+ this.logBtn = this.button("L", "Logarithmic scale", () => this.deps.onToggleLog(this.hoverPaneId ?? ""));
4640
+ this.cluster.append(this.autoBtn, this.logBtn);
4641
+ this.plot.appendChild(this.cluster);
4642
+ this.plot.addEventListener("pointermove", this.onPlotMove);
4643
+ this.plot.addEventListener("pointerleave", this.onPlotLeave);
4644
+ }
4645
+ button(label, title, onClick) {
4646
+ const b = document.createElement("button");
4647
+ b.type = "button";
4648
+ b.setAttribute("aria-label", title);
4649
+ b.textContent = label;
4650
+ b.className = "vela-axis-btn";
4651
+ Object.assign(b.style, { width: `${BTN_PX}px`, height: `${BTN_PX}px` });
4652
+ this.tipDisposers.push(attachChromeTooltip(b, {
4653
+ host: this.plot,
4654
+ theme: () => this.theme,
4655
+ text: () => title,
4656
+ placement: "above"
4657
+ }));
4658
+ b.addEventListener("click", (e) => {
4659
+ e.stopPropagation();
4660
+ onClick();
4661
+ this.sync();
4662
+ });
4663
+ return b;
4664
+ }
4665
+ /** Re-tint the chip when the plot background/border change (theme swap, config edit). */
4666
+ setTheme(theme) {
4667
+ this.theme = theme;
4668
+ this.applyTheme();
4669
+ }
4670
+ /** The strip is SOLID chart background: the buttons cover axis labels, never blend with them. */
4671
+ applyTheme() {
4672
+ this.cluster.style.background = this.theme.background;
4673
+ }
4674
+ setHoverPane(paneId) {
4675
+ if (this.hoverPaneId === paneId) return;
4676
+ this.hoverPaneId = paneId;
4677
+ this.reposition();
4678
+ }
4679
+ /** Re-place (or hide) the cluster after a hover or pane-layout change. */
4680
+ reposition() {
4681
+ const pane = this.hoverPaneId ? this.deps.panes().find((p) => p.id === this.hoverPaneId) : void 0;
4682
+ if (!pane || pane.collapsed || pane.height < MIN_PANE_H) {
4683
+ this.cluster.style.display = "none";
4684
+ return;
4685
+ }
4686
+ this.cluster.style.right = `${this.deps.rightAxis() - AXIS_MASTER_W}px`;
4687
+ this.cluster.style.top = `${pane.top + pane.height - CLUSTER_H}px`;
4688
+ this.cluster.style.display = "flex";
4689
+ this.sync();
4690
+ }
4691
+ /** Reflect the hovered pane's current auto/log state on the buttons. */
4692
+ sync() {
4693
+ const pane = this.deps.panes().find((p) => p.id === this.hoverPaneId);
4694
+ if (!pane) return;
4695
+ this.autoBtn.classList.toggle("vela-axis-on", pane.auto);
4696
+ this.logBtn.classList.toggle("vela-axis-on", pane.log);
4697
+ }
4698
+ destroy() {
4699
+ this.plot.removeEventListener("pointermove", this.onPlotMove);
4700
+ this.plot.removeEventListener("pointerleave", this.onPlotLeave);
4701
+ for (const dispose of this.tipDisposers) dispose();
4702
+ this.tipDisposers.length = 0;
4703
+ this.cluster.remove();
4704
+ }
4705
+ };
4706
+
4522
4707
  // src/renderers/shared/TableOverlay.ts
4523
4708
  var SIZE_PX = {
4524
4709
  auto: 13,
@@ -4876,6 +5061,8 @@ var BASELINE_BOTTOM_LINE = BEARISH;
4876
5061
  var BASELINE_FILL_ALPHA = 0.25;
4877
5062
  var BASELINE_FILL_ALPHA_FAR = 0.05;
4878
5063
  var BASELINE_LEVEL_DEFAULT = 50;
5064
+ var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
5065
+ var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
4879
5066
  function defaultChartStyle() {
4880
5067
  return {
4881
5068
  chartTypes: {},
@@ -4906,7 +5093,8 @@ function defaultChartStyle() {
4906
5093
  bottomFillColor2: null,
4907
5094
  width: 2,
4908
5095
  baselineLevel: BASELINE_LEVEL_DEFAULT
4909
- }
5096
+ },
5097
+ sessions: { premarketColor: PREMARKET_SHADE, postmarketColor: POSTMARKET_SHADE }
4910
5098
  };
4911
5099
  }
4912
5100
  var LINE_STYLES = ["solid", "dashed", "dotted"];
@@ -5040,6 +5228,7 @@ function mergeConfig(base, patch) {
5040
5228
  const area = asObject(p.area);
5041
5229
  const baseline = asObject(p.baseline);
5042
5230
  const series = asObject(p.series);
5231
+ const sessions = asObject(p.sessions);
5043
5232
  const stacking = asObject(p.stacking);
5044
5233
  const stackSeries = asObject(stacking.series);
5045
5234
  return {
@@ -5132,6 +5321,10 @@ function mergeConfig(base, patch) {
5132
5321
  baseline: series.baseline === null ? null : isNum(series.baseline) ? series.baseline : base.series.baseline,
5133
5322
  spacing: isNum(series.spacing) ? clampSpacing(series.spacing) : base.series.spacing
5134
5323
  },
5324
+ sessions: {
5325
+ premarketColor: isColor(sessions.premarketColor) ? sessions.premarketColor : base.sessions.premarketColor,
5326
+ postmarketColor: isColor(sessions.postmarketColor) ? sessions.postmarketColor : base.sessions.postmarketColor
5327
+ },
5135
5328
  stacking: {
5136
5329
  candles: isNum(stacking.candles) ? stacking.candles : base.stacking.candles,
5137
5330
  // Additive like `chartTypes`: a patch names only the ids it carries; the rest keep
@@ -5378,6 +5571,8 @@ var SceneGraph = class {
5378
5571
  this.tradeMarkers = defaultTradeMarkersState();
5379
5572
  /** Renderer-owned shaded time bands (session highlighting), behind grid + data. */
5380
5573
  this.highlights = [];
5574
+ /** Pre/post-market bands pushed by the host (`sessionZones` feature); null ⇒ no sessions. */
5575
+ this.sessionZones = null;
5381
5576
  /** Draw-order key of the price candles, relative to indicator series z (see `seriesZ`).
5382
5577
  * Indicators with z below this draw BEHIND the candles; at/above draw in front.
5383
5578
  * Default 0 with indicators mounting at z < 0 ⇒ the price reads on top of every overlay,
@@ -5412,6 +5607,15 @@ var SceneGraph = class {
5412
5607
  if (!this.orderedCache) this.orderedCache = [...this.panes.values()].sort((a, b) => a.order - b.order);
5413
5608
  return this.orderedCache;
5414
5609
  }
5610
+ /** The session zones resolved into colored bands (pre/post-market washes from the
5611
+ * config's session colors) — consumed by the same painting path as {@link highlights}. */
5612
+ sessionHighlightBands() {
5613
+ if (!this.sessionZones) return [];
5614
+ const out = [];
5615
+ for (const [from, to] of this.sessionZones.pre) out.push({ from, to, color: this.style.sessions.premarketColor });
5616
+ for (const [from, to] of this.sessionZones.post) out.push({ from, to, color: this.style.sessions.postmarketColor });
5617
+ return out;
5618
+ }
5415
5619
  indicatorsForPane(paneId) {
5416
5620
  const out = [];
5417
5621
  for (const model of this.indicators.values()) if (model.paneId === paneId) out.push(model);
@@ -5495,6 +5699,12 @@ var SceneGraph = class {
5495
5699
  assignIndicatorZ(id) {
5496
5700
  if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.bottomZ() - 1);
5497
5701
  }
5702
+ /** Mount-time default for a LAYER-BACKED native (it paints on a canvas stacked above the
5703
+ * data canvas by default): top of the stack, so the recorded order tells the truth from
5704
+ * the first frame. Keeps an existing key, so a restored stack survives the remount. */
5705
+ assignIndicatorZTop(id) {
5706
+ if (!this.seriesZ.has(id)) this.seriesZ.set(id, this.topZ() + 1);
5707
+ }
5498
5708
  forgetIndicatorZ(id) {
5499
5709
  this.seriesZ.delete(id);
5500
5710
  }
@@ -6342,10 +6552,12 @@ var WebGL2Backend = class {
6342
6552
  }
6343
6553
  }
6344
6554
  }
6345
- /** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height). */
6555
+ /** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
6556
+ * Session-zone washes (pre/post-market) paint first, host highlights on top. */
6346
6557
  emitHighlights(b, scene, pane, coords) {
6347
- if (scene.highlights.length === 0) return;
6348
- for (const band of scene.highlights) {
6558
+ const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
6559
+ if (bands.length === 0) return;
6560
+ for (const band of bands) {
6349
6561
  const x1 = coords.timeToX(band.from);
6350
6562
  const x2 = coords.timeToX(band.to);
6351
6563
  if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
@@ -8319,10 +8531,12 @@ var Canvas2dBackend = class {
8319
8531
  ctx.fillStyle = bg.color;
8320
8532
  ctx.fillRect(x1, pane.bounds.top, x2 - x1, pane.bounds.height);
8321
8533
  }
8322
- /** Renderer-owned session highlight bands: full-height (all panes), behind grid + data. */
8534
+ /** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
8535
+ * Session-zone washes (pre/post-market) paint first, host highlights on top. */
8323
8536
  drawHighlights(ctx, scene, coords) {
8324
- if (scene.highlights.length === 0) return;
8325
- for (const band of scene.highlights) {
8537
+ const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
8538
+ if (bands.length === 0) return;
8539
+ for (const band of bands) {
8326
8540
  const x1 = coords.timeToX(band.from);
8327
8541
  const x2 = coords.timeToX(band.to);
8328
8542
  if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
@@ -9096,14 +9310,6 @@ var DrawingSceneRenderer = class {
9096
9310
  }
9097
9311
  };
9098
9312
 
9099
- // src/renderers/native/chrome/axisLayout.ts
9100
- var AXIS_MASTER_W = 64;
9101
- var AXIS_MERGED_W = 56;
9102
- var PANE_SEPARATOR_PX = 3;
9103
- function axisColumnX(dataW, column) {
9104
- return column === 0 ? dataW : dataW + AXIS_MASTER_W + (column - 1) * AXIS_MERGED_W;
9105
- }
9106
-
9107
9313
  // src/renderers/native/chrome/ChromeRenderer.ts
9108
9314
  var ChromeRenderer = class {
9109
9315
  constructor() {
@@ -9909,6 +10115,7 @@ var SettingsDialog = class {
9909
10115
  for (const hr of hs.rows) {
9910
10116
  if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
9911
10117
  else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
10118
+ else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
9912
10119
  else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options.map((o) => [o, o]), (v) => hr.set(v)));
9913
10120
  }
9914
10121
  }
@@ -12620,14 +12827,14 @@ function btn(label, primary, onClick) {
12620
12827
  // src/renderers/native/drawings/DrawingSettingsPopup.ts
12621
12828
  var BTN = 30;
12622
12829
  var ICON = 21;
12623
- var STYLE_ID3 = "vela-drawing-popup-styles";
12830
+ var STYLE_ID4 = "vela-drawing-popup-styles";
12624
12831
  var STYLE_REV = "3";
12625
- function ensureStyles3() {
12832
+ function ensureStyles4() {
12626
12833
  if (typeof document === "undefined") return;
12627
- const existing = document.getElementById(STYLE_ID3);
12834
+ const existing = document.getElementById(STYLE_ID4);
12628
12835
  if (existing?.dataset.rev === STYLE_REV) return;
12629
12836
  const s = existing ?? document.createElement("style");
12630
- s.id = STYLE_ID3;
12837
+ s.id = STYLE_ID4;
12631
12838
  s.dataset.rev = STYLE_REV;
12632
12839
  s.textContent = `
12633
12840
  .vela-dpop-btn{background:transparent;color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
@@ -12684,7 +12891,7 @@ var DrawingSettingsPopup = class {
12684
12891
  * `onClose` fires on an outside-click dismissal (not a programmatic close). */
12685
12892
  open(drawing, anchor, actions, onClose) {
12686
12893
  this.close();
12687
- ensureStyles3();
12894
+ ensureStyles4();
12688
12895
  this.onClose = onClose ?? null;
12689
12896
  const t = this.theme;
12690
12897
  const schema = drawing.schema();
@@ -14516,13 +14723,26 @@ var VolumeRenderer = class {
14516
14723
  }
14517
14724
  };
14518
14725
 
14726
+ // src/renderers/native/core/layerStacking.ts
14727
+ function stackLayers(entries, candleZ) {
14728
+ const keyed = entries.map((e) => ({
14729
+ id: e.id,
14730
+ key: e.ownerZ ?? (e.placement === "below-data" ? -Infinity : candleZ)
14731
+ }));
14732
+ keyed.sort((a, b) => a.key - b.key);
14733
+ const below = [];
14734
+ const above = [];
14735
+ for (const k of keyed) (k.key < candleZ ? below : above).push(k.id);
14736
+ return { below, above };
14737
+ }
14738
+
14519
14739
  // src/renderers/native/chrome/luxalgo-logos.ts
14520
14740
  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>';
14521
14741
  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>';
14522
14742
 
14523
14743
  // src/renderers/native/chrome/AttributionMark.ts
14524
14744
  var ATTRIBUTION_URL = "https://luxalgo.com/vela";
14525
- var STYLE_ID4 = "vela-attribution-styles";
14745
+ var STYLE_ID5 = "vela-attribution-styles";
14526
14746
  var CSS = `
14527
14747
  .vela-attribution {
14528
14748
  text-decoration: none;
@@ -14585,11 +14805,11 @@ var CSS = `
14585
14805
  [data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
14586
14806
  [data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
14587
14807
  `;
14588
- function ensureStyles4(doc) {
14589
- let s = doc.getElementById(STYLE_ID4);
14808
+ function ensureStyles5(doc) {
14809
+ let s = doc.getElementById(STYLE_ID5);
14590
14810
  if (!s) {
14591
14811
  s = doc.createElement("style");
14592
- s.id = STYLE_ID4;
14812
+ s.id = STYLE_ID5;
14593
14813
  doc.head.appendChild(s);
14594
14814
  }
14595
14815
  s.textContent = CSS;
@@ -14618,7 +14838,7 @@ function createCustomMark(doc, html, background) {
14618
14838
  return el;
14619
14839
  }
14620
14840
  function createAttributionMark(doc, background) {
14621
- ensureStyles4(doc);
14841
+ ensureStyles5(doc);
14622
14842
  const a = doc.createElement("a");
14623
14843
  a.className = "vela-attribution";
14624
14844
  a.href = ATTRIBUTION_URL;
@@ -15016,6 +15236,8 @@ var NativeRenderer = class {
15016
15236
  this.volumeRenderer = new VolumeRenderer();
15017
15237
  /** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
15018
15238
  this.extLayers = [];
15239
+ /** Last applied layer-canvas order (ids below + above the data canvas) — re-slotted only on change. */
15240
+ this.layerOrderSig = "";
15019
15241
  // The attribution mark (see chrome/AttributionMark + the NOTICE file): default-on;
15020
15242
  // disabling requires an equivalent visible attribution elsewhere in the host UI.
15021
15243
  this.attributionEl = null;
@@ -15130,6 +15352,8 @@ var NativeRenderer = class {
15130
15352
  // total right-gutter width (master column + merged-scale columns)
15131
15353
  this.paneControls = null;
15132
15354
  // per-pane hover button cluster (top-right)
15355
+ this.axisScaleButtons = null;
15356
+ // A/L hover buttons at the bottom of each pane's price scale
15133
15357
  this.paneActionCbs = /* @__PURE__ */ new Set();
15134
15358
  // ── data window (crosshair OHLC + per-series readout, pulled by host panels) ──
15135
15359
  this.hoverLogical = null;
@@ -15173,7 +15397,7 @@ var NativeRenderer = class {
15173
15397
  this.priceStyleCbs = /* @__PURE__ */ new Set();
15174
15398
  this.tableOverlays = /* @__PURE__ */ new Map();
15175
15399
  this.name = "native";
15176
- 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"];
15400
+ 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"];
15177
15401
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
15178
15402
  * so moving onto the button doesn't count as leaving). */
15179
15403
  this.onScrollProximityMove = (e) => {
@@ -15289,6 +15513,9 @@ var NativeRenderer = class {
15289
15513
  case "highlights":
15290
15514
  this.scene.highlights = sanitizeHighlights(value);
15291
15515
  break;
15516
+ case "sessionZones":
15517
+ this.scene.sessionZones = sanitizeSessionZones(value);
15518
+ break;
15292
15519
  case "gridlines":
15293
15520
  this.scene.showGrid = Boolean(value);
15294
15521
  break;
@@ -15405,6 +15632,8 @@ var NativeRenderer = class {
15405
15632
  return this.scene.indicatorZOrder();
15406
15633
  case "highlights":
15407
15634
  return this.scene.highlights;
15635
+ case "sessionZones":
15636
+ return this.scene.sessionZones;
15408
15637
  case "gridlines":
15409
15638
  return this.scene.showGrid;
15410
15639
  case "axisLabels":
@@ -15614,7 +15843,8 @@ var NativeRenderer = class {
15614
15843
  baselineLevel: s.baseline.baselineLevel
15615
15844
  };
15616
15845
  })(),
15617
- series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale }
15846
+ series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
15847
+ sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
15618
15848
  };
15619
15849
  }
15620
15850
  /**
@@ -15696,6 +15926,7 @@ var NativeRenderer = class {
15696
15926
  width: next.baseline.width,
15697
15927
  baselineLevel: next.baseline.baselineLevel
15698
15928
  };
15929
+ s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
15699
15930
  this.setPriceStyle(next.series.style);
15700
15931
  this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
15701
15932
  this.scene.baselineValue = next.series.baseline;
@@ -15709,6 +15940,7 @@ var NativeRenderer = class {
15709
15940
  }
15710
15941
  this.inputsUI?.setTheme(this.theme);
15711
15942
  this.paneControls?.setTheme(this.theme);
15943
+ this.axisScaleButtons?.setTheme(this.theme);
15712
15944
  this.userDrawings?.setTheme(this.chromeTheme());
15713
15945
  this.refreshScrollButtonTheme();
15714
15946
  this.refreshAttributionColor();
@@ -15936,9 +16168,7 @@ var NativeRenderer = class {
15936
16168
  if (el.getAttribute("data-vela-screenshot") === "under") rasterizeOverlay(ctx, el, frame);
15937
16169
  }
15938
16170
  }
15939
- const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
15940
- const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
15941
- for (const canvas of [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas]) {
16171
+ for (const canvas of [...this.canvasPile(), this.chromeCanvas, this.drawingsCanvas]) {
15942
16172
  if (canvas && canvas.width > 0 && canvas.height > 0) ctx.drawImage(canvas, 0, 0);
15943
16173
  }
15944
16174
  if (frame) {
@@ -16047,7 +16277,7 @@ var NativeRenderer = class {
16047
16277
  this.surfaceBackground = theme.background;
16048
16278
  this.surfaceTextColor = theme.textColor;
16049
16279
  this.wrapper = document.createElement("div");
16050
- Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair" });
16280
+ Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair", userSelect: "none", webkitUserSelect: "none" });
16051
16281
  applyChromeTokens(this.wrapper, this.chromeTheme());
16052
16282
  this.volumeCanvas = document.createElement("canvas");
16053
16283
  Object.assign(this.volumeCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
@@ -16072,6 +16302,7 @@ var NativeRenderer = class {
16072
16302
  const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
16073
16303
  const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
16074
16304
  this.plot.append(...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
16305
+ this.layerOrderSig = "";
16075
16306
  this.wrapper.appendChild(this.plot);
16076
16307
  this.factoryConfig = this.getConfig();
16077
16308
  this.attributionEl = this.buildAttributionEl();
@@ -16216,6 +16447,15 @@ var NativeRenderer = class {
16216
16447
  this.emitPaneAction({ type: "maximize", paneId, maximized });
16217
16448
  }
16218
16449
  });
16450
+ this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
16451
+ panes: () => this.axisScaleViews(),
16452
+ rightAxis: () => this.rightAxisW,
16453
+ onToggleAuto: (paneId) => this.togglePaneAuto(paneId),
16454
+ onToggleLog: (paneId) => {
16455
+ const pane = this.scene.panes.get(paneId);
16456
+ if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
16457
+ }
16458
+ });
16219
16459
  this.resizeObserver = new ResizeObserver(() => this.resize());
16220
16460
  this.resizeObserver.observe(this.wrapper);
16221
16461
  this.watchDpr();
@@ -16292,6 +16532,7 @@ var NativeRenderer = class {
16292
16532
  this.applyBackground();
16293
16533
  this.inputsUI.setTheme(theme);
16294
16534
  this.paneControls?.setTheme(this.theme);
16535
+ this.axisScaleButtons?.setTheme(this.theme);
16295
16536
  this.settingsDialog?.refreshConfig(this.getConfig());
16296
16537
  this.syncThemeControl();
16297
16538
  this.settingsDialog?.setTheme(this.theme);
@@ -16340,6 +16581,7 @@ var NativeRenderer = class {
16340
16581
  this.liveRegion = null;
16341
16582
  this.inputsUI?.destroy();
16342
16583
  this.paneControls?.destroy();
16584
+ this.axisScaleButtons?.destroy();
16343
16585
  for (const overlay of this.tableOverlays.values()) overlay.destroy();
16344
16586
  this.tableOverlays.clear();
16345
16587
  this.resizeObserver?.disconnect();
@@ -16589,7 +16831,8 @@ var NativeRenderer = class {
16589
16831
  mountIndicator(model) {
16590
16832
  this.scene.indicators.set(model.id, model);
16591
16833
  this.refreshAnchorOffset(model);
16592
- this.scene.assignIndicatorZ(model.id);
16834
+ if (model.native && this.extLayers.some((l) => l.def.id === model.native.type)) this.scene.assignIndicatorZTop(model.id);
16835
+ else this.scene.assignIndicatorZ(model.id);
16593
16836
  this.inputsUI.upsert(model.id, model.shorttitle ?? model.title, model.inputs, model.inputValues, model.paneId, {
16594
16837
  native: !!model.native,
16595
16838
  ...model.shorttitle ? { settingsTitle: model.title } : {}
@@ -17408,10 +17651,17 @@ var NativeRenderer = class {
17408
17651
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
17409
17652
  for (const l of this.extLayers) {
17410
17653
  if (!l.def.repaintOnCursor) continue;
17411
- l.instance.render(this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs));
17654
+ const lp = this.layerPane(l.def.id) ?? pane;
17655
+ if (lp.collapsed) continue;
17656
+ l.instance.render(this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs));
17412
17657
  if (this.animZoom && l.instance.animating?.()) this.animator.start();
17413
17658
  }
17414
17659
  }
17660
+ /** Blank one SDK layer canvas (a collapsed host pane suppresses the layer's painting). */
17661
+ clearLayerCanvas(canvas) {
17662
+ if (canvas.width === 0 || canvas.height === 0) return;
17663
+ canvas.getContext("2d")?.clearRect(0, 0, canvas.width, canvas.height);
17664
+ }
17415
17665
  /** One frame's args for an SDK renderer layer (shared by the data + cursor paint paths). */
17416
17666
  extLayerArgs(id, scale, bounds, nowMs) {
17417
17667
  return {
@@ -17430,6 +17680,7 @@ var NativeRenderer = class {
17430
17680
  }
17431
17681
  /** Paint the below-data (L-1) + geometry (L0) + chrome (L1) layers from the current scene/coords. */
17432
17682
  paintData() {
17683
+ this.syncLayerCanvasOrder();
17433
17684
  this.stampScaleInvert();
17434
17685
  this.backend.modelAlpha = this.modelAlpha;
17435
17686
  this.backend.candleBodyAlpha = this.candleBodyAlpha;
@@ -17460,9 +17711,14 @@ var NativeRenderer = class {
17460
17711
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
17461
17712
  let folded = null;
17462
17713
  for (const l of this.extLayers) {
17463
- const args = this.extLayerArgs(l.def.id, pane.scale, pane.bounds, nowMs);
17714
+ const lp = this.layerPane(l.def.id) ?? pane;
17715
+ if (lp.collapsed) {
17716
+ this.clearLayerCanvas(l.canvas);
17717
+ continue;
17718
+ }
17719
+ const args = this.extLayerArgs(l.def.id, lp.scale, lp.bounds, nowMs);
17464
17720
  l.instance.render(args);
17465
- folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
17721
+ if (lp === pane) folded = foldBaseModulation(folded, l.instance.modulateBase?.(args) ?? null);
17466
17722
  if (this.animZoom && l.instance.animating?.()) this.animator.start();
17467
17723
  }
17468
17724
  if (folded) {
@@ -17624,6 +17880,9 @@ var NativeRenderer = class {
17624
17880
  pane.scaleTarget = { min: 0, max: maxVol / VOLUME_PANE_FILL_FRAC };
17625
17881
  pane.axisFormat = "volume";
17626
17882
  }
17883
+ } else if (this.layerNativesOwnPane(pane, masterModels)) {
17884
+ pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
17885
+ pane.percentBaseline = this.bars[i0]?.close ?? 0;
17627
17886
  }
17628
17887
  if (pane === pricePane && this.scene.tradeMarkers.visible && pane.bounds.height > 0) {
17629
17888
  const th = this.tradesScaleHints(vr);
@@ -17710,6 +17969,59 @@ var NativeRenderer = class {
17710
17969
  }
17711
17970
  return null;
17712
17971
  }
17972
+ /** The mounted native indicator that OWNS an SDK layer — the one whose type equals the
17973
+ * layer id (the id doubles as the data channel, so the pairing is the SDK's own
17974
+ * contract). Null for chart-type channels and while the owner is hidden (a hidden
17975
+ * indicator leaves the scene; its cleared data channel paints nothing anyway). */
17976
+ layerOwner(layerId) {
17977
+ for (const m of this.scene.indicators.values()) {
17978
+ if (m.native?.type === layerId) return m;
17979
+ }
17980
+ return null;
17981
+ }
17982
+ /** The pane an SDK layer paints on: its owner's pane, else the price pane. */
17983
+ layerPane(layerId) {
17984
+ const owner = this.layerOwner(layerId);
17985
+ const paneId = owner ? owner.paneId ?? PRICE_PANE_ID : PRICE_PANE_ID;
17986
+ return this.scene.panes.get(paneId) ?? null;
17987
+ }
17988
+ /** The SDK layer canvases split around the data canvas, each side back-to-front:
17989
+ * owned layers by their owner's z key against the candles' (an indicator restacked
17990
+ * below the candles takes its layer canvas along), unowned by declared placement. */
17991
+ orderedLayerCanvases() {
17992
+ const byId = new Map(this.extLayers.map((l) => [l.def.id, l.canvas]));
17993
+ const { below, above } = stackLayers(
17994
+ this.extLayers.map((l) => {
17995
+ const owner = this.layerOwner(l.def.id);
17996
+ return {
17997
+ id: l.def.id,
17998
+ placement: l.def.placement === "below-data" ? "below-data" : "above-data",
17999
+ ownerZ: owner ? this.scene.zOf(owner.id) : null
18000
+ };
18001
+ }),
18002
+ this.scene.candleZ
18003
+ );
18004
+ return { below: below.map((id) => byId.get(id)), above: above.map((id) => byId.get(id)) };
18005
+ }
18006
+ /** The full canvas pile in paint order (layers + data/volume/vpvr) — what the DOM
18007
+ * stacking and the screenshot compositor must both follow. */
18008
+ canvasPile() {
18009
+ const { below, above } = this.orderedLayerCanvases();
18010
+ return [...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above];
18011
+ }
18012
+ /** Re-slot the SDK layer canvases in the plot when the computed order changed (a z
18013
+ * write, a restored config, an indicator mount/remove/restack). Runs at the top of
18014
+ * every data frame; a no-op when the signature is unchanged. Re-inserting an
18015
+ * absolutely-positioned, pointer-transparent canvas repaints nothing by itself. */
18016
+ syncLayerCanvasOrder() {
18017
+ if (this.extLayers.length === 0 || !this.plot) return;
18018
+ const { below, above } = this.orderedLayerCanvases();
18019
+ 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(",");
18020
+ if (sig === this.layerOrderSig) return;
18021
+ this.layerOrderSig = sig;
18022
+ for (const c of below) this.plot.insertBefore(c, this.dataCanvas);
18023
+ for (const c of above) this.plot.insertBefore(c, this.chromeCanvas);
18024
+ }
17713
18025
  /** True when an active volume layer is this study pane's ONLY content — so its scale should
17714
18026
  * come from volume, not the empty {0,1} placeholder. (In the price pane, or alongside a real
17715
18027
  * series, volume stays a bottom overlay and the master scale wins.) */
@@ -17719,6 +18031,15 @@ var NativeRenderer = class {
17719
18031
  if (this.nativeLayerPane("volume") !== pane) return false;
17720
18032
  return masterModels.every((m) => m.native?.type === "volume");
17721
18033
  }
18034
+ /** True when this study pane's master content is only SDK-layer natives (series-less
18035
+ * models whose type names a mounted layer) — its scale then follows the visible bars
18036
+ * (see the call site). Any real master series takes over the scale as usual. */
18037
+ layerNativesOwnPane(pane, masterModels) {
18038
+ if (pane.kind === "price" || masterModels.length === 0) return false;
18039
+ return masterModels.every(
18040
+ (m) => m.series.length === 0 && !!m.native && this.extLayers.some((l) => l.def.id === m.native.type)
18041
+ );
18042
+ }
17722
18043
  /** Per-pane scale state for a host UI (e.g. a price-axis context menu): the pane's pixel
17723
18044
  * band (`top`/`height`, so a click y maps to a pane) plus its current axis `mode`/`log`.
17724
18045
  * Top-to-bottom order. Every pane is independent — the price pane from the scene setting,
@@ -17734,6 +18055,25 @@ var NativeRenderer = class {
17734
18055
  invert: paneInvert(this.scene, p)
17735
18056
  }));
17736
18057
  }
18058
+ /** Thin projection of the panes for the axis A/L hover buttons (top-to-bottom order). */
18059
+ axisScaleViews() {
18060
+ return this.scene.orderedPanes().map((p) => ({
18061
+ id: p.id,
18062
+ top: p.bounds.top,
18063
+ height: p.bounds.height,
18064
+ collapsed: p.collapsed,
18065
+ auto: p.manualScale == null,
18066
+ log: paneLogScale(this.scene, p)
18067
+ }));
18068
+ }
18069
+ /** Toggle one pane's autoscale (the A axis button): off freezes the current window into
18070
+ * manual mode, on drops it — the per-pane twin of the chart-level `autoScale` feature. */
18071
+ togglePaneAuto(paneId) {
18072
+ const pane = this.scene.panes.get(paneId);
18073
+ if (!pane) return;
18074
+ pane.manualScale = pane.manualScale == null ? { ...pane.scale } : null;
18075
+ this.scheduler?.invalidate(4 /* Full */);
18076
+ }
17737
18077
  /** Set one pane's axis mode. The price pane routes to the scene-level setting (persisted +
17738
18078
  * keyboard shortcuts); a study pane keeps its own, so panes never affect each other. */
17739
18079
  setPaneScaleMode(paneId, mode) {
@@ -17745,19 +18085,18 @@ var NativeRenderer = class {
17745
18085
  }
17746
18086
  this.scheduler?.invalidate(4 /* Full */);
17747
18087
  }
17748
- /** Set one pane's logarithmic flag. Clears that pane's frozen (manual) window so the new
17749
- * space re-fits, and routes the price pane to the scene-level flag. */
18088
+ /** Set one pane's logarithmic flag; the price pane routes to the scene-level flag.
18089
+ * Log and auto are INDEPENDENT: a frozen (manual) window is kept — its price bounds
18090
+ * stay put and only its `log` tag flips, so the same range re-renders in the new
18091
+ * space instead of snapping back to autoscale. */
17750
18092
  setPaneLog(paneId, log) {
17751
- if (paneId === PRICE_PANE_ID) {
17752
- this.scene.logScale = log;
17753
- const price = this.scene.orderedPanes().find((p) => p.kind === "price");
17754
- if (price) price.manualScale = null;
17755
- } else {
17756
- const p = this.scene.panes.get(paneId);
17757
- if (!p) return;
17758
- p.logScale = log;
17759
- p.manualScale = null;
18093
+ const pane = paneId === PRICE_PANE_ID ? this.scene.orderedPanes().find((p) => p.kind === "price") : this.scene.panes.get(paneId);
18094
+ if (paneId === PRICE_PANE_ID) this.scene.logScale = log;
18095
+ else {
18096
+ if (!pane) return;
18097
+ pane.logScale = log;
17760
18098
  }
18099
+ if (pane?.manualScale) pane.manualScale.log = log;
17761
18100
  this.scheduler?.invalidate(4 /* Full */);
17762
18101
  }
17763
18102
  /** Flip one pane's axis (high at the bottom). The price pane routes to the scene-level flag
@@ -17856,6 +18195,7 @@ var NativeRenderer = class {
17856
18195
  }
17857
18196
  this.inputsUI?.reposition();
17858
18197
  this.paneControls?.reposition();
18198
+ this.axisScaleButtons?.reposition();
17859
18199
  this.repositionScrollButton();
17860
18200
  this.positionAttribution();
17861
18201
  this.publishPricePaneBounds();
@@ -17874,6 +18214,7 @@ var NativeRenderer = class {
17874
18214
  }
17875
18215
  this.inputsUI?.reposition();
17876
18216
  this.paneControls?.reposition();
18217
+ this.axisScaleButtons?.reposition();
17877
18218
  this.repositionScrollButton();
17878
18219
  this.positionAttribution();
17879
18220
  this.publishPricePaneBounds();
@@ -18081,6 +18422,22 @@ function sanitizeHighlights(value) {
18081
18422
  }
18082
18423
  return out.sort((a, b) => a.from - b.from);
18083
18424
  }
18425
+ function sanitizeSessionZones(value) {
18426
+ if (value == null || typeof value !== "object") return null;
18427
+ const v = value;
18428
+ const windows = (raw) => {
18429
+ if (!Array.isArray(raw)) return [];
18430
+ const out = [];
18431
+ for (const w of raw) {
18432
+ if (!Array.isArray(w)) continue;
18433
+ const from = Number(w[0]);
18434
+ const to = Number(w[1]);
18435
+ if (Number.isFinite(from) && Number.isFinite(to) && to > from) out.push([from, to]);
18436
+ }
18437
+ return out.sort((a, b) => a[0] - b[0]);
18438
+ };
18439
+ return { pre: windows(v.pre), post: windows(v.post) };
18440
+ }
18084
18441
  function clamp012(v) {
18085
18442
  return Math.max(0, Math.min(1, v));
18086
18443
  }