@luxalgo/vela 0.6.3 → 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
@@ -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);
@@ -6342,10 +6546,12 @@ var WebGL2Backend = class {
6342
6546
  }
6343
6547
  }
6344
6548
  }
6345
- /** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height). */
6549
+ /** Renderer-owned session highlight bands, clipped per-pane (scissor reconstructs full height).
6550
+ * Session-zone washes (pre/post-market) paint first, host highlights on top. */
6346
6551
  emitHighlights(b, scene, pane, coords) {
6347
- if (scene.highlights.length === 0) return;
6348
- for (const band of scene.highlights) {
6552
+ const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
6553
+ if (bands.length === 0) return;
6554
+ for (const band of bands) {
6349
6555
  const x1 = coords.timeToX(band.from);
6350
6556
  const x2 = coords.timeToX(band.to);
6351
6557
  if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
@@ -8319,10 +8525,12 @@ var Canvas2dBackend = class {
8319
8525
  ctx.fillStyle = bg.color;
8320
8526
  ctx.fillRect(x1, pane.bounds.top, x2 - x1, pane.bounds.height);
8321
8527
  }
8322
- /** Renderer-owned session highlight bands: full-height (all panes), behind grid + data. */
8528
+ /** Renderer-owned session highlight bands: full-height (all panes), behind grid + data.
8529
+ * Session-zone washes (pre/post-market) paint first, host highlights on top. */
8323
8530
  drawHighlights(ctx, scene, coords) {
8324
- if (scene.highlights.length === 0) return;
8325
- for (const band of scene.highlights) {
8531
+ const bands = [...scene.sessionHighlightBands(), ...scene.highlights];
8532
+ if (bands.length === 0) return;
8533
+ for (const band of bands) {
8326
8534
  const x1 = coords.timeToX(band.from);
8327
8535
  const x2 = coords.timeToX(band.to);
8328
8536
  if (x2 < 0 || x1 > coords.width || x2 <= x1) continue;
@@ -9096,14 +9304,6 @@ var DrawingSceneRenderer = class {
9096
9304
  }
9097
9305
  };
9098
9306
 
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
9307
  // src/renderers/native/chrome/ChromeRenderer.ts
9108
9308
  var ChromeRenderer = class {
9109
9309
  constructor() {
@@ -9909,6 +10109,7 @@ var SettingsDialog = class {
9909
10109
  for (const hr of hs.rows) {
9910
10110
  if (hr.kind === "heading") body.append(this.sectionTitle(hr.label));
9911
10111
  else if (hr.kind === "toggle") body.append(this.boolRow(hr.label, hr.get(), (v) => hr.set(v)));
10112
+ else if (hr.kind === "color") body.append(this.colorRow(hr.label, hr.get(), (v) => hr.set(v)));
9912
10113
  else body.append(this.selectRowLabeled(hr.label, hr.get(), hr.options.map((o) => [o, o]), (v) => hr.set(v)));
9913
10114
  }
9914
10115
  }
@@ -12620,14 +12821,14 @@ function btn(label, primary, onClick) {
12620
12821
  // src/renderers/native/drawings/DrawingSettingsPopup.ts
12621
12822
  var BTN = 30;
12622
12823
  var ICON = 21;
12623
- var STYLE_ID3 = "vela-drawing-popup-styles";
12824
+ var STYLE_ID4 = "vela-drawing-popup-styles";
12624
12825
  var STYLE_REV = "3";
12625
- function ensureStyles3() {
12826
+ function ensureStyles4() {
12626
12827
  if (typeof document === "undefined") return;
12627
- const existing = document.getElementById(STYLE_ID3);
12828
+ const existing = document.getElementById(STYLE_ID4);
12628
12829
  if (existing?.dataset.rev === STYLE_REV) return;
12629
12830
  const s = existing ?? document.createElement("style");
12630
- s.id = STYLE_ID3;
12831
+ s.id = STYLE_ID4;
12631
12832
  s.dataset.rev = STYLE_REV;
12632
12833
  s.textContent = `
12633
12834
  .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 +12885,7 @@ var DrawingSettingsPopup = class {
12684
12885
  * `onClose` fires on an outside-click dismissal (not a programmatic close). */
12685
12886
  open(drawing, anchor, actions, onClose) {
12686
12887
  this.close();
12687
- ensureStyles3();
12888
+ ensureStyles4();
12688
12889
  this.onClose = onClose ?? null;
12689
12890
  const t = this.theme;
12690
12891
  const schema = drawing.schema();
@@ -14522,7 +14723,7 @@ var LUXALGO_WORDMARK_SVG = '<svg viewBox="0 0 964 252" aria-hidden="true"><path
14522
14723
 
14523
14724
  // src/renderers/native/chrome/AttributionMark.ts
14524
14725
  var ATTRIBUTION_URL = "https://luxalgo.com/vela";
14525
- var STYLE_ID4 = "vela-attribution-styles";
14726
+ var STYLE_ID5 = "vela-attribution-styles";
14526
14727
  var CSS = `
14527
14728
  .vela-attribution {
14528
14729
  text-decoration: none;
@@ -14585,11 +14786,11 @@ var CSS = `
14585
14786
  [data-vela-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark,
14586
14787
  [data-layout='mobile'] .vela-attribution:hover .vela-attr-wordmark { transform: translateX(0); }
14587
14788
  `;
14588
- function ensureStyles4(doc) {
14589
- let s = doc.getElementById(STYLE_ID4);
14789
+ function ensureStyles5(doc) {
14790
+ let s = doc.getElementById(STYLE_ID5);
14590
14791
  if (!s) {
14591
14792
  s = doc.createElement("style");
14592
- s.id = STYLE_ID4;
14793
+ s.id = STYLE_ID5;
14593
14794
  doc.head.appendChild(s);
14594
14795
  }
14595
14796
  s.textContent = CSS;
@@ -14618,7 +14819,7 @@ function createCustomMark(doc, html, background) {
14618
14819
  return el;
14619
14820
  }
14620
14821
  function createAttributionMark(doc, background) {
14621
- ensureStyles4(doc);
14822
+ ensureStyles5(doc);
14622
14823
  const a = doc.createElement("a");
14623
14824
  a.className = "vela-attribution";
14624
14825
  a.href = ATTRIBUTION_URL;
@@ -15130,6 +15331,8 @@ var NativeRenderer = class {
15130
15331
  // total right-gutter width (master column + merged-scale columns)
15131
15332
  this.paneControls = null;
15132
15333
  // per-pane hover button cluster (top-right)
15334
+ this.axisScaleButtons = null;
15335
+ // A/L hover buttons at the bottom of each pane's price scale
15133
15336
  this.paneActionCbs = /* @__PURE__ */ new Set();
15134
15337
  // ── data window (crosshair OHLC + per-series readout, pulled by host panels) ──
15135
15338
  this.hoverLogical = null;
@@ -15173,7 +15376,7 @@ var NativeRenderer = class {
15173
15376
  this.priceStyleCbs = /* @__PURE__ */ new Set();
15174
15377
  this.tableOverlays = /* @__PURE__ */ new Map();
15175
15378
  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"];
15379
+ 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
15380
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
15178
15381
  * so moving onto the button doesn't count as leaving). */
15179
15382
  this.onScrollProximityMove = (e) => {
@@ -15289,6 +15492,9 @@ var NativeRenderer = class {
15289
15492
  case "highlights":
15290
15493
  this.scene.highlights = sanitizeHighlights(value);
15291
15494
  break;
15495
+ case "sessionZones":
15496
+ this.scene.sessionZones = sanitizeSessionZones(value);
15497
+ break;
15292
15498
  case "gridlines":
15293
15499
  this.scene.showGrid = Boolean(value);
15294
15500
  break;
@@ -15405,6 +15611,8 @@ var NativeRenderer = class {
15405
15611
  return this.scene.indicatorZOrder();
15406
15612
  case "highlights":
15407
15613
  return this.scene.highlights;
15614
+ case "sessionZones":
15615
+ return this.scene.sessionZones;
15408
15616
  case "gridlines":
15409
15617
  return this.scene.showGrid;
15410
15618
  case "axisLabels":
@@ -15614,7 +15822,8 @@ var NativeRenderer = class {
15614
15822
  baselineLevel: s.baseline.baselineLevel
15615
15823
  };
15616
15824
  })(),
15617
- series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale }
15825
+ series: { style: this.scene.priceStyle, baseline: this.scene.baselineValue, spacing: this.coords.spacingScale },
15826
+ sessions: { premarketColor: s.sessions.premarketColor, postmarketColor: s.sessions.postmarketColor }
15618
15827
  };
15619
15828
  }
15620
15829
  /**
@@ -15696,6 +15905,7 @@ var NativeRenderer = class {
15696
15905
  width: next.baseline.width,
15697
15906
  baselineLevel: next.baseline.baselineLevel
15698
15907
  };
15908
+ s.sessions = { premarketColor: next.sessions.premarketColor, postmarketColor: next.sessions.postmarketColor };
15699
15909
  this.setPriceStyle(next.series.style);
15700
15910
  this.scene.candleOverride = candleOverrideFor(this.scene.priceStyle, s.chartTypes);
15701
15911
  this.scene.baselineValue = next.series.baseline;
@@ -15709,6 +15919,7 @@ var NativeRenderer = class {
15709
15919
  }
15710
15920
  this.inputsUI?.setTheme(this.theme);
15711
15921
  this.paneControls?.setTheme(this.theme);
15922
+ this.axisScaleButtons?.setTheme(this.theme);
15712
15923
  this.userDrawings?.setTheme(this.chromeTheme());
15713
15924
  this.refreshScrollButtonTheme();
15714
15925
  this.refreshAttributionColor();
@@ -16047,7 +16258,7 @@ var NativeRenderer = class {
16047
16258
  this.surfaceBackground = theme.background;
16048
16259
  this.surfaceTextColor = theme.textColor;
16049
16260
  this.wrapper = document.createElement("div");
16050
- Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair" });
16261
+ Object.assign(this.wrapper.style, { position: "relative", width: "100%", height: "100%", overflow: "hidden", cursor: "crosshair", userSelect: "none", webkitUserSelect: "none" });
16051
16262
  applyChromeTokens(this.wrapper, this.chromeTheme());
16052
16263
  this.volumeCanvas = document.createElement("canvas");
16053
16264
  Object.assign(this.volumeCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
@@ -16216,6 +16427,15 @@ var NativeRenderer = class {
16216
16427
  this.emitPaneAction({ type: "maximize", paneId, maximized });
16217
16428
  }
16218
16429
  });
16430
+ this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
16431
+ panes: () => this.axisScaleViews(),
16432
+ rightAxis: () => this.rightAxisW,
16433
+ onToggleAuto: (paneId) => this.togglePaneAuto(paneId),
16434
+ onToggleLog: (paneId) => {
16435
+ const pane = this.scene.panes.get(paneId);
16436
+ if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
16437
+ }
16438
+ });
16219
16439
  this.resizeObserver = new ResizeObserver(() => this.resize());
16220
16440
  this.resizeObserver.observe(this.wrapper);
16221
16441
  this.watchDpr();
@@ -16292,6 +16512,7 @@ var NativeRenderer = class {
16292
16512
  this.applyBackground();
16293
16513
  this.inputsUI.setTheme(theme);
16294
16514
  this.paneControls?.setTheme(this.theme);
16515
+ this.axisScaleButtons?.setTheme(this.theme);
16295
16516
  this.settingsDialog?.refreshConfig(this.getConfig());
16296
16517
  this.syncThemeControl();
16297
16518
  this.settingsDialog?.setTheme(this.theme);
@@ -16340,6 +16561,7 @@ var NativeRenderer = class {
16340
16561
  this.liveRegion = null;
16341
16562
  this.inputsUI?.destroy();
16342
16563
  this.paneControls?.destroy();
16564
+ this.axisScaleButtons?.destroy();
16343
16565
  for (const overlay of this.tableOverlays.values()) overlay.destroy();
16344
16566
  this.tableOverlays.clear();
16345
16567
  this.resizeObserver?.disconnect();
@@ -17734,6 +17956,25 @@ var NativeRenderer = class {
17734
17956
  invert: paneInvert(this.scene, p)
17735
17957
  }));
17736
17958
  }
17959
+ /** Thin projection of the panes for the axis A/L hover buttons (top-to-bottom order). */
17960
+ axisScaleViews() {
17961
+ return this.scene.orderedPanes().map((p) => ({
17962
+ id: p.id,
17963
+ top: p.bounds.top,
17964
+ height: p.bounds.height,
17965
+ collapsed: p.collapsed,
17966
+ auto: p.manualScale == null,
17967
+ log: paneLogScale(this.scene, p)
17968
+ }));
17969
+ }
17970
+ /** Toggle one pane's autoscale (the A axis button): off freezes the current window into
17971
+ * manual mode, on drops it — the per-pane twin of the chart-level `autoScale` feature. */
17972
+ togglePaneAuto(paneId) {
17973
+ const pane = this.scene.panes.get(paneId);
17974
+ if (!pane) return;
17975
+ pane.manualScale = pane.manualScale == null ? { ...pane.scale } : null;
17976
+ this.scheduler?.invalidate(4 /* Full */);
17977
+ }
17737
17978
  /** Set one pane's axis mode. The price pane routes to the scene-level setting (persisted +
17738
17979
  * keyboard shortcuts); a study pane keeps its own, so panes never affect each other. */
17739
17980
  setPaneScaleMode(paneId, mode) {
@@ -17745,19 +17986,18 @@ var NativeRenderer = class {
17745
17986
  }
17746
17987
  this.scheduler?.invalidate(4 /* Full */);
17747
17988
  }
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. */
17989
+ /** Set one pane's logarithmic flag; the price pane routes to the scene-level flag.
17990
+ * Log and auto are INDEPENDENT: a frozen (manual) window is kept — its price bounds
17991
+ * stay put and only its `log` tag flips, so the same range re-renders in the new
17992
+ * space instead of snapping back to autoscale. */
17750
17993
  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;
17994
+ const pane = paneId === PRICE_PANE_ID ? this.scene.orderedPanes().find((p) => p.kind === "price") : this.scene.panes.get(paneId);
17995
+ if (paneId === PRICE_PANE_ID) this.scene.logScale = log;
17996
+ else {
17997
+ if (!pane) return;
17998
+ pane.logScale = log;
17760
17999
  }
18000
+ if (pane?.manualScale) pane.manualScale.log = log;
17761
18001
  this.scheduler?.invalidate(4 /* Full */);
17762
18002
  }
17763
18003
  /** Flip one pane's axis (high at the bottom). The price pane routes to the scene-level flag
@@ -17856,6 +18096,7 @@ var NativeRenderer = class {
17856
18096
  }
17857
18097
  this.inputsUI?.reposition();
17858
18098
  this.paneControls?.reposition();
18099
+ this.axisScaleButtons?.reposition();
17859
18100
  this.repositionScrollButton();
17860
18101
  this.positionAttribution();
17861
18102
  this.publishPricePaneBounds();
@@ -17874,6 +18115,7 @@ var NativeRenderer = class {
17874
18115
  }
17875
18116
  this.inputsUI?.reposition();
17876
18117
  this.paneControls?.reposition();
18118
+ this.axisScaleButtons?.reposition();
17877
18119
  this.repositionScrollButton();
17878
18120
  this.positionAttribution();
17879
18121
  this.publishPricePaneBounds();
@@ -18081,6 +18323,22 @@ function sanitizeHighlights(value) {
18081
18323
  }
18082
18324
  return out.sort((a, b) => a.from - b.from);
18083
18325
  }
18326
+ function sanitizeSessionZones(value) {
18327
+ if (value == null || typeof value !== "object") return null;
18328
+ const v = value;
18329
+ const windows = (raw) => {
18330
+ if (!Array.isArray(raw)) return [];
18331
+ const out = [];
18332
+ for (const w of raw) {
18333
+ if (!Array.isArray(w)) continue;
18334
+ const from = Number(w[0]);
18335
+ const to = Number(w[1]);
18336
+ if (Number.isFinite(from) && Number.isFinite(to) && to > from) out.push([from, to]);
18337
+ }
18338
+ return out.sort((a, b) => a[0] - b[0]);
18339
+ };
18340
+ return { pre: windows(v.pre), post: windows(v.post) };
18341
+ }
18084
18342
  function clamp012(v) {
18085
18343
  return Math.max(0, Math.min(1, v));
18086
18344
  }