@luxalgo/vela 0.7.6 → 0.7.7

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.
@@ -20561,7 +20561,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
20561
20561
  recents.unshift(hex6);
20562
20562
  if (recents.length > 10) recents.length = 10;
20563
20563
  }
20564
- function buildColorPicker(color, theme, onChange) {
20564
+ function buildColorPicker(color, theme, onChange, options = {}) {
20565
+ const commit = options.commit ?? "live";
20565
20566
  const parsed = splitColor(color);
20566
20567
  let curHex = parsed.hex6;
20567
20568
  let curAlpha = parsed.alpha;
@@ -20644,7 +20645,12 @@ ${overlayScrollbarCss(".vela-dialog *")}
20644
20645
  const r = track.getBoundingClientRect();
20645
20646
  curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
20646
20647
  paintOpacity();
20647
- emit();
20648
+ if (commit === "live") emit();
20649
+ };
20650
+ const endDrag = () => {
20651
+ if (!dragging) return;
20652
+ dragging = false;
20653
+ if (commit === "release") emit();
20648
20654
  };
20649
20655
  track.addEventListener("pointerdown", (e) => {
20650
20656
  e.stopPropagation();
@@ -20655,7 +20661,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
20655
20661
  track.addEventListener("pointermove", (e) => {
20656
20662
  if (dragging) onDrag(e.clientX);
20657
20663
  });
20658
- track.addEventListener("pointerup", () => dragging = false);
20664
+ track.addEventListener("pointerup", endDrag);
20665
+ track.addEventListener("pointercancel", endDrag);
20659
20666
  function emit() {
20660
20667
  onChange(combineColor(curHex, curAlpha));
20661
20668
  }
@@ -20678,7 +20685,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
20678
20685
  return root;
20679
20686
  }
20680
20687
  function colorField(theme, getVal, onVal, opts) {
20681
- return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover }).el;
20688
+ return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover, commit: opts?.commit }).el;
20682
20689
  }
20683
20690
  var ColorField = class {
20684
20691
  constructor(opts) {
@@ -20687,6 +20694,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
20687
20694
  this.getVal = opts.getVal;
20688
20695
  this.onVal = opts.onVal;
20689
20696
  this.popoverOpts = opts.popover;
20697
+ this.commit = opts.commit;
20690
20698
  const trigger = document.createElement("button");
20691
20699
  trigger.type = "button";
20692
20700
  if (opts.id) trigger.id = opts.id;
@@ -20726,10 +20734,15 @@ ${overlayScrollbarCss(".vela-dialog *")}
20726
20734
  boundary: this.popoverOpts?.boundary,
20727
20735
  zIndex: this.popoverOpts?.zIndex,
20728
20736
  className: "vela-color-field-pop",
20729
- content: buildColorPicker(this.getVal(), this.theme, (val) => {
20730
- this.onVal(val);
20731
- this.paint();
20732
- })
20737
+ content: buildColorPicker(
20738
+ this.getVal(),
20739
+ this.theme,
20740
+ (val) => {
20741
+ this.onVal(val);
20742
+ this.paint();
20743
+ },
20744
+ { commit: this.commit }
20745
+ )
20733
20746
  });
20734
20747
  pop.show();
20735
20748
  }
@@ -21210,7 +21223,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
21210
21223
  const el2 = colorField(desc.theme, desc.get, desc.onChange, {
21211
21224
  shape: "circle",
21212
21225
  id: desc.id,
21213
- popover: desc.popover
21226
+ popover: desc.popover,
21227
+ commit: desc.commit
21214
21228
  });
21215
21229
  if (desc.title) el2.title = desc.title;
21216
21230
  return { el: el2 };
@@ -21588,7 +21602,10 @@ ${overlayScrollbarCss(".vela-dialog *")}
21588
21602
  id,
21589
21603
  theme: this.host.theme(),
21590
21604
  get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
21591
- onChange: (v) => emit(v)
21605
+ onChange: (v) => emit(v),
21606
+ // An input change re-executes the script over its whole history: commit the
21607
+ // opacity drag once on release, not once per pointer move.
21608
+ commit: "release"
21592
21609
  }).el;
21593
21610
  }
21594
21611
  if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
@@ -21835,10 +21852,6 @@ ${overlayScrollbarCss(".vela-dialog *")}
21835
21852
  }
21836
21853
  /** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
21837
21854
  openCalendar(anchor, current, onPick) {
21838
- const parsed = parseIsoDate(current);
21839
- let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
21840
- let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
21841
- const selected = parsed ? isoDate(parsed) : current;
21842
21855
  const pop = new Popover({
21843
21856
  trigger: anchor,
21844
21857
  theme: this.host.theme(),
@@ -21850,80 +21863,10 @@ ${overlayScrollbarCss(".vela-dialog *")}
21850
21863
  this.calendarPop = null;
21851
21864
  this.calendarAnchor = null;
21852
21865
  },
21853
- content: (el) => {
21854
- const title = document.createElement("div");
21855
- title.className = "vela-ind-cal-title";
21856
- const prev3 = document.createElement("button");
21857
- prev3.type = "button";
21858
- prev3.className = "vela-ind-cal-nav";
21859
- prev3.setAttribute("aria-label", "Previous month");
21860
- prev3.innerHTML = iconAt("chevron-left", 14);
21861
- const next2 = document.createElement("button");
21862
- next2.type = "button";
21863
- next2.className = "vela-ind-cal-nav";
21864
- next2.setAttribute("aria-label", "Next month");
21865
- next2.innerHTML = iconAt("chevron-right", 14);
21866
- const head = document.createElement("div");
21867
- head.className = "vela-ind-cal-head";
21868
- head.append(prev3, title, next2);
21869
- const week = document.createElement("div");
21870
- week.className = "vela-ind-cal-week";
21871
- for (const d of WEEKDAY_LABELS) {
21872
- const cell = document.createElement("span");
21873
- cell.textContent = d;
21874
- week.appendChild(cell);
21875
- }
21876
- const grid = document.createElement("div");
21877
- grid.className = "vela-ind-cal-grid";
21878
- const paint = () => {
21879
- title.textContent = `${MONTH_LABELS[month]} ${year}`;
21880
- grid.replaceChildren();
21881
- const first2 = new Date(year, month, 1);
21882
- const startPad = first2.getDay();
21883
- const days = new Date(year, month + 1, 0).getDate();
21884
- const today = isoDate(/* @__PURE__ */ new Date());
21885
- for (let i = 0; i < startPad; i++) {
21886
- const blank = document.createElement("span");
21887
- blank.className = "vela-ind-cal-blank";
21888
- grid.appendChild(blank);
21889
- }
21890
- for (let day = 1; day <= days; day++) {
21891
- const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
21892
- const b = document.createElement("button");
21893
- b.type = "button";
21894
- b.className = "vela-ind-cal-day";
21895
- b.textContent = String(day);
21896
- if (iso === selected) b.dataset.checked = "1";
21897
- if (iso === today) b.dataset.today = "1";
21898
- b.addEventListener("click", (e) => {
21899
- e.stopPropagation();
21900
- pop.hide();
21901
- onPick(iso);
21902
- });
21903
- grid.appendChild(b);
21904
- }
21905
- };
21906
- prev3.addEventListener("click", (e) => {
21907
- e.stopPropagation();
21908
- month -= 1;
21909
- if (month < 0) {
21910
- month = 11;
21911
- year -= 1;
21912
- }
21913
- paint();
21914
- });
21915
- next2.addEventListener("click", (e) => {
21916
- e.stopPropagation();
21917
- month += 1;
21918
- if (month > 11) {
21919
- month = 0;
21920
- year += 1;
21921
- }
21922
- paint();
21923
- });
21924
- paint();
21925
- el.append(head, week, grid);
21926
- }
21866
+ content: (el) => fillCalendar(el, current, (iso) => {
21867
+ pop.hide();
21868
+ onPick(iso);
21869
+ })
21927
21870
  });
21928
21871
  this.calendarPop = pop;
21929
21872
  this.calendarAnchor = anchor;
@@ -22032,7 +21975,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
22032
21975
  var CALENDAR_SVG = iconAt("calendar", 14);
22033
21976
  var CLOCK_SVG = iconAt("clock", 14);
22034
21977
  var DIALOG_STYLE_ID2 = "vela-ind-dialog-styles";
22035
- var DIALOG_STYLE_REV = "29";
21978
+ var DIALOG_STYLE_REV = "30";
22036
21979
  var LEGEND_ICON_PX = 16;
22037
21980
  function ensureDialogStyles() {
22038
21981
  if (typeof document === "undefined") return;
@@ -22048,8 +21991,12 @@ ${overlayScrollbarCss(".vela-dialog *")}
22048
21991
  .vela-ind-combo-chevron{position:absolute;right:0;top:0;bottom:0;width:26px;border:none;background:transparent;color:inherit;opacity:0.55;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;}
22049
21992
  .vela-ind-combo-chevron:hover{opacity:0.9;}
22050
21993
  .vela-ind-cal{background:var(--vela-bg);color:var(--vela-fg);border:none;border-radius:6px;box-shadow:var(--vela-shadow);font:14px var(--vela-font);padding:10px 12px;user-select:none;}
21994
+ .vela-ind-cal [hidden]{display:none !important;}
22051
21995
  .vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
22052
- .vela-ind-cal-title{flex:1;text-align:center;font-weight:600;font-size:14px;color:var(--vela-fg-bright);}
21996
+ .vela-ind-cal-title{flex:1;display:flex;align-items:center;justify-content:center;gap:2px;min-width:0;}
21997
+ .vela-ind-cal-switch{border:none;background:transparent;color:var(--vela-fg-bright);font:inherit;font-weight:600;font-size:14px;padding:2px 6px;border-radius:4px;cursor:pointer;}
21998
+ .vela-ind-cal-switch:not(:disabled):hover{background:var(--vela-hover);}
21999
+ .vela-ind-cal-switch:disabled{cursor:default;}
22053
22000
  .vela-ind-cal-nav{width:24px;height:24px;border:none;background:transparent;color:var(--vela-fg-muted);border-radius:4px;padding:0;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;}
22054
22001
  .vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
22055
22002
  .vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
@@ -22060,6 +22007,12 @@ ${overlayScrollbarCss(".vela-dialog *")}
22060
22007
  .vela-ind-cal-day:hover{background:var(--vela-hover);}
22061
22008
  .vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
22062
22009
  .vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
22010
+ .vela-ind-cal-cells{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;width:calc(7 * 28px + 6 * 2px);}
22011
+ .vela-ind-cal-cell{height:36px;border:none;background:transparent;color:inherit;border-radius:4px;padding:0 4px;cursor:pointer;font:inherit;font-size:14px;}
22012
+ .vela-ind-cal-cell:hover{background:var(--vela-hover);}
22013
+ .vela-ind-cal-cell[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
22014
+ .vela-ind-cal-cell[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
22015
+ .vela-ind-cal-cell[data-outside]{opacity:0.45;}
22063
22016
  ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22064
22017
  .vela-ind-tab{font-weight:600;font-size:13px;line-height:20px;transition:color var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease;}
22065
22018
  .vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
@@ -22094,6 +22047,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22094
22047
  return { value: v, label: v };
22095
22048
  });
22096
22049
  var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
22050
+ var MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
22097
22051
  var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
22098
22052
  function normalizeDateInput(raw) {
22099
22053
  const t = raw.trim();
@@ -22115,6 +22069,151 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22115
22069
  function isoDate(d) {
22116
22070
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
22117
22071
  }
22072
+ function fillCalendar(el, current, onPick) {
22073
+ const today = /* @__PURE__ */ new Date();
22074
+ const selected = parseIsoDate(current);
22075
+ let year = selected?.getFullYear() ?? today.getFullYear();
22076
+ let month = selected?.getMonth() ?? today.getMonth();
22077
+ let mode = "date";
22078
+ const prev3 = document.createElement("button");
22079
+ prev3.type = "button";
22080
+ prev3.className = "vela-ind-cal-nav";
22081
+ prev3.innerHTML = iconAt("chevron-left", 14);
22082
+ const next2 = document.createElement("button");
22083
+ next2.type = "button";
22084
+ next2.className = "vela-ind-cal-nav";
22085
+ next2.innerHTML = iconAt("chevron-right", 14);
22086
+ const monthBtn = document.createElement("button");
22087
+ monthBtn.type = "button";
22088
+ monthBtn.className = "vela-ind-cal-switch";
22089
+ monthBtn.setAttribute("aria-label", "Choose month");
22090
+ const yearBtn = document.createElement("button");
22091
+ yearBtn.type = "button";
22092
+ yearBtn.className = "vela-ind-cal-switch";
22093
+ yearBtn.setAttribute("aria-label", "Choose year");
22094
+ const title = document.createElement("div");
22095
+ title.className = "vela-ind-cal-title";
22096
+ title.append(monthBtn, yearBtn);
22097
+ const head = document.createElement("div");
22098
+ head.className = "vela-ind-cal-head";
22099
+ head.append(prev3, title, next2);
22100
+ const week = document.createElement("div");
22101
+ week.className = "vela-ind-cal-week";
22102
+ for (const d of WEEKDAY_LABELS) {
22103
+ const cell = document.createElement("span");
22104
+ cell.textContent = d;
22105
+ week.appendChild(cell);
22106
+ }
22107
+ const grid = document.createElement("div");
22108
+ const paint = () => {
22109
+ const decade = Math.floor(year / 10) * 10;
22110
+ monthBtn.hidden = mode !== "date";
22111
+ monthBtn.textContent = MONTH_LABELS[month] ?? "";
22112
+ yearBtn.textContent = mode === "year" ? `${decade}-${decade + 9}` : String(year);
22113
+ yearBtn.disabled = mode === "year";
22114
+ week.hidden = mode !== "date";
22115
+ prev3.setAttribute("aria-label", mode === "date" ? "Previous month" : mode === "month" ? "Previous year" : "Previous decade");
22116
+ next2.setAttribute("aria-label", mode === "date" ? "Next month" : mode === "month" ? "Next year" : "Next decade");
22117
+ grid.className = mode === "date" ? "vela-ind-cal-grid" : "vela-ind-cal-cells";
22118
+ grid.replaceChildren();
22119
+ if (mode === "date") {
22120
+ const startPad = new Date(year, month, 1).getDay();
22121
+ const days = new Date(year, month + 1, 0).getDate();
22122
+ const todayIso = isoDate(today);
22123
+ const selectedIso = selected ? isoDate(selected) : "";
22124
+ for (let i = 0; i < startPad; i++) {
22125
+ const blank = document.createElement("span");
22126
+ blank.className = "vela-ind-cal-blank";
22127
+ grid.appendChild(blank);
22128
+ }
22129
+ for (let day = 1; day <= days; day++) {
22130
+ const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
22131
+ const b = document.createElement("button");
22132
+ b.type = "button";
22133
+ b.className = "vela-ind-cal-day";
22134
+ b.textContent = String(day);
22135
+ if (iso === selectedIso) b.dataset.checked = "1";
22136
+ if (iso === todayIso) b.dataset.today = "1";
22137
+ b.addEventListener("click", (e) => {
22138
+ e.stopPropagation();
22139
+ onPick(iso);
22140
+ });
22141
+ grid.appendChild(b);
22142
+ }
22143
+ return;
22144
+ }
22145
+ if (mode === "month") {
22146
+ for (let m = 0; m < 12; m++) {
22147
+ const b = document.createElement("button");
22148
+ b.type = "button";
22149
+ b.className = "vela-ind-cal-cell";
22150
+ b.textContent = MONTH_SHORT[m] ?? "";
22151
+ if (selected?.getFullYear() === year && selected.getMonth() === m) b.dataset.checked = "1";
22152
+ if (today.getFullYear() === year && today.getMonth() === m) b.dataset.today = "1";
22153
+ b.addEventListener("click", (e) => {
22154
+ e.stopPropagation();
22155
+ month = m;
22156
+ mode = "date";
22157
+ paint();
22158
+ });
22159
+ grid.appendChild(b);
22160
+ }
22161
+ return;
22162
+ }
22163
+ for (let y = decade - 1; y <= decade + 10; y++) {
22164
+ const b = document.createElement("button");
22165
+ b.type = "button";
22166
+ b.className = "vela-ind-cal-cell";
22167
+ b.textContent = String(y);
22168
+ if (y < decade || y > decade + 9) b.dataset.outside = "1";
22169
+ if (selected?.getFullYear() === y) b.dataset.checked = "1";
22170
+ if (today.getFullYear() === y) b.dataset.today = "1";
22171
+ b.addEventListener("click", (e) => {
22172
+ e.stopPropagation();
22173
+ year = y;
22174
+ mode = "month";
22175
+ paint();
22176
+ });
22177
+ grid.appendChild(b);
22178
+ }
22179
+ };
22180
+ const step = (dir) => {
22181
+ if (mode === "date") {
22182
+ month += dir;
22183
+ if (month < 0) {
22184
+ month = 11;
22185
+ year -= 1;
22186
+ }
22187
+ if (month > 11) {
22188
+ month = 0;
22189
+ year += 1;
22190
+ }
22191
+ } else {
22192
+ year += mode === "month" ? dir : dir * 10;
22193
+ }
22194
+ paint();
22195
+ };
22196
+ prev3.addEventListener("click", (e) => {
22197
+ e.stopPropagation();
22198
+ step(-1);
22199
+ });
22200
+ next2.addEventListener("click", (e) => {
22201
+ e.stopPropagation();
22202
+ step(1);
22203
+ });
22204
+ monthBtn.addEventListener("click", (e) => {
22205
+ e.stopPropagation();
22206
+ mode = "month";
22207
+ paint();
22208
+ });
22209
+ yearBtn.addEventListener("click", (e) => {
22210
+ e.stopPropagation();
22211
+ mode = "year";
22212
+ paint();
22213
+ });
22214
+ paint();
22215
+ el.append(head, week, grid);
22216
+ }
22118
22217
  function normalizeTimeInput(raw) {
22119
22218
  const t = raw.trim();
22120
22219
  const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
@@ -23396,10 +23495,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23396
23495
  var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
23397
23496
  var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
23398
23497
  var EXTENDED_SHADE = POSTMARKET_SHADE;
23498
+ var DEFAULT_MARGINS = { top: 10, bottom: 10, right: 10 };
23399
23499
  function defaultChartStyle() {
23400
23500
  return {
23401
23501
  chartTypes: {},
23402
23502
  fontSize: 11,
23503
+ margins: { ...DEFAULT_MARGINS },
23403
23504
  gridVert: { visible: true, color: null },
23404
23505
  gridHorz: { visible: true, color: null },
23405
23506
  borderColor: null,
@@ -23523,6 +23624,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23523
23624
  function clampSpacing(v) {
23524
23625
  return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
23525
23626
  }
23627
+ function clampMarginPct(v) {
23628
+ return v < 0 ? 0 : v > 40 ? 40 : v;
23629
+ }
23630
+ function clampMarginBars(v) {
23631
+ return Math.round(v < 0 ? 0 : v > 200 ? 200 : v);
23632
+ }
23526
23633
  function factoryResetConfig(factory, priceStyle = factory.series.style) {
23527
23634
  const bag = {};
23528
23635
  for (const t of chartTypes()) {
@@ -23571,6 +23678,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23571
23678
  const ps = asObject(p.priceScale);
23572
23679
  const anim = asObject(p.animations);
23573
23680
  const panes = asObject(p.panes);
23681
+ const margins = asObject(p.margins);
23574
23682
  const trades = asObject(p.trades);
23575
23683
  const ts = asObject(p.timeScale);
23576
23684
  const marks = asObject(p.marks);
@@ -23630,6 +23738,11 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23630
23738
  panes: {
23631
23739
  separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
23632
23740
  },
23741
+ margins: {
23742
+ top: isNum(margins.top) ? clampMarginPct(margins.top) : base.margins.top,
23743
+ bottom: isNum(margins.bottom) ? clampMarginPct(margins.bottom) : base.margins.bottom,
23744
+ right: isNum(margins.right) ? clampMarginBars(margins.right) : base.margins.right
23745
+ },
23633
23746
  trades: {
23634
23747
  visible: isBool(trades.visible) ? trades.visible : base.trades.visible,
23635
23748
  labels: isBool(trades.labels) ? trades.labels : base.trades.labels,
@@ -29000,6 +29113,10 @@ void main() {
29000
29113
  "canvas.grid",
29001
29114
  "canvas.grid.vertical",
29002
29115
  "canvas.grid.horizontal",
29116
+ "canvas.margins",
29117
+ "canvas.margins.top",
29118
+ "canvas.margins.bottom",
29119
+ "canvas.margins.right",
29003
29120
  "canvas.theme"
29004
29121
  ];
29005
29122
  function settingsIdCatalog(hostSections, markGroups = []) {
@@ -29530,6 +29647,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29530
29647
  body.append(sid(this.toggleRow("Horizontal", config.grid.horzLines.visible, (v) => this.emit({ grid: { horzLines: { visible: v } } }), [
29531
29648
  this.swatch(config.grid.horzLines.color, (v) => this.emit({ grid: { horzLines: { color: v } } }))
29532
29649
  ]), "canvas.grid.horizontal"));
29650
+ body.append(sid(this.sectionTitle("Margins"), "canvas.margins"));
29651
+ body.append(sid(this.numberRow("Top", config.margins.top, 0, 40, 1, (v) => this.emit({ margins: { top: v } }), "%"), "canvas.margins.top"));
29652
+ body.append(sid(this.numberRow("Bottom", config.margins.bottom, 0, 40, 1, (v) => this.emit({ margins: { bottom: v } }), "%"), "canvas.margins.bottom"));
29653
+ body.append(sid(this.numberRow("Right", config.margins.right, 0, 200, 1, (v) => this.emit({ margins: { right: v } }), "bars"), "canvas.margins.right"));
29533
29654
  if (this.themeControl) {
29534
29655
  const tc = this.themeControl;
29535
29656
  body.append(sid(this.sectionTitle("Theme"), "canvas.theme"));
@@ -30101,23 +30222,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30101
30222
  this.hintTips.push(dispose);
30102
30223
  return el;
30103
30224
  }
30104
- numberRow(label, value, min2, max2, step, onChange) {
30105
- return fieldRow({
30106
- label,
30107
- labelSize: "sm",
30108
- className: "vela-sd-row",
30109
- control: buildFieldControl({
30110
- kind: "number",
30111
- value,
30112
- min: min2,
30113
- max: max2,
30114
- step,
30115
- fill: false,
30116
- commit: "live",
30117
- steppers: true,
30118
- onChange
30119
- }).el
30120
- });
30225
+ /** `unit` (optional) trails the input as muted text — "%", "bars". */
30226
+ numberRow(label, value, min2, max2, step, onChange, unit) {
30227
+ const controls = [buildFieldControl({ kind: "number", value, min: min2, max: max2, step, fill: false, commit: "live", steppers: true, onChange }).el];
30228
+ if (unit) {
30229
+ const u = document.createElement("span");
30230
+ u.textContent = unit;
30231
+ u.style.cssText = "color:var(--vela-fg-muted);";
30232
+ controls.push(u);
30233
+ }
30234
+ return this.rowWith(label, controls);
30121
30235
  }
30122
30236
  /** A dropdown whose option values differ from their display labels. */
30123
30237
  selectRowLabeled(label, value, options, onChange) {
@@ -35636,9 +35750,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35636
35750
  }
35637
35751
 
35638
35752
  // src/renderers/native/core/autoscale.ts
35639
- var MARGIN_TOP = 2 / 7;
35640
- var MARGIN_BOTTOM = 1 / 7;
35641
- function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0) {
35753
+ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0, margins = DEFAULT_MARGINS) {
35642
35754
  let min2 = Infinity;
35643
35755
  let max2 = -Infinity;
35644
35756
  const consider = (v) => {
@@ -35673,14 +35785,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35673
35785
  const pad = Math.abs(min2) * 0.1 || 1;
35674
35786
  return { min: min2 - pad, max: max2 + pad, log: log && min2 - pad > 0 };
35675
35787
  }
35788
+ const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
35789
+ const above = margins.top / 100 / content;
35790
+ const below = margins.bottom / 100 / content;
35676
35791
  if (log && min2 > 0) {
35677
35792
  const lmin = Math.log(min2);
35678
35793
  const lmax = Math.log(max2);
35679
35794
  const lspan = lmax - lmin;
35680
- return { min: Math.exp(lmin - lspan * MARGIN_BOTTOM), max: Math.exp(lmax + lspan * MARGIN_TOP), log: true };
35795
+ return { min: Math.exp(lmin - lspan * below), max: Math.exp(lmax + lspan * above), log: true };
35681
35796
  }
35682
35797
  const span = max2 - min2;
35683
- return { min: min2 - span * MARGIN_BOTTOM, max: max2 + span * MARGIN_TOP };
35798
+ return { min: min2 - span * below, max: max2 + span * above };
35684
35799
  }
35685
35800
  function considerSeries(s, i0, i1, off, consider) {
35686
35801
  if (!seriesInScale(s)) return;
@@ -37507,6 +37622,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37507
37622
  intro: this.intro.style !== false
37508
37623
  },
37509
37624
  panes: { separatorColor: s.separatorColor ?? t.borderColor },
37625
+ margins: { ...s.margins },
37510
37626
  trades: {
37511
37627
  visible: this.scene.tradeMarkers.visible,
37512
37628
  labels: this.scene.tradeMarkers.labels,
@@ -37619,6 +37735,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37619
37735
  this.animAutoscale.toggle(next2.animations.autoscale);
37620
37736
  this.intro = { style: next2.animations.intro ? this.introOnStyle : false, duration: this.intro.duration || INTRO_DURATION_DEFAULT_MS };
37621
37737
  s.separatorColor = keepInherit(s.separatorColor, next2.panes.separatorColor, prevTheme.borderColor);
37738
+ if (next2.margins.right !== s.margins.right && this.coords.barCount > 0) {
37739
+ this.applyViewport({ barSpacing: this.coords.getViewport().barSpacing, rightOffset: next2.margins.right });
37740
+ }
37741
+ s.margins = { ...next2.margins };
37622
37742
  this.scene.tradeMarkers = {
37623
37743
  visible: next2.trades.visible,
37624
37744
  labels: next2.trades.labels,
@@ -37862,7 +37982,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37862
37982
  /** Glide the view back to the most recent bars, keeping the current zoom (barSpacing). */
37863
37983
  scrollToRealtime() {
37864
37984
  if (this.coords.barCount === 0) return;
37865
- this.glideRightOffset(ZOOM_OUT_MARGIN_BARS);
37985
+ this.glideRightOffset(this.scene.style.margins.right);
37866
37986
  }
37867
37987
  /** Ease rightOffset to `target` at constant zoom (see animTick's scroll glide);
37868
37988
  * instant when the scroll glide is off. Shared by scroll-to-latest and panBy. */
@@ -39804,6 +39924,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39804
39924
  const pricePane = panes.find((p) => p.kind === "price") ?? null;
39805
39925
  this.chrome.prepare(this.scene, this.coords, this.theme);
39806
39926
  const animating = this.animator.active;
39927
+ const margins = this.scene.style.margins;
39807
39928
  for (const pane of panes) {
39808
39929
  if (pane.manualScale) {
39809
39930
  pane.scaleTarget = pane.manualScale;
@@ -39819,7 +39940,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39819
39940
  if (or2) dr = dr ? { min: Math.min(dr.min, or2.min), max: Math.max(dr.max, or2.max) } : or2;
39820
39941
  }
39821
39942
  const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels) || !this.paneHasMeasurableContent(masterModels, dr));
39822
- pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
39943
+ pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
39823
39944
  pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
39824
39945
  pane.axisFormat = void 0;
39825
39946
  pane.axisBands = void 0;
@@ -39830,7 +39951,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39830
39951
  pane.axisFormat = "volume";
39831
39952
  }
39832
39953
  } else if (this.layerNativesOwnPane(pane, masterModels)) {
39833
- pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
39954
+ pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
39834
39955
  pane.percentBaseline = this.bars[i0]?.close ?? 0;
39835
39956
  if (masterModels.every((m) => m.paneAxis != null)) {
39836
39957
  pane.axisFormat = "none";
@@ -39859,7 +39980,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39859
39980
  continue;
39860
39981
  }
39861
39982
  const mdr = this.chrome.paneDrawingsRange([model], this.scene, false, vr);
39862
- sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id));
39983
+ sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id), margins);
39863
39984
  if (!animating || !sl.initialized) {
39864
39985
  sl.scale = { ...sl.scaleTarget };
39865
39986
  sl.initialized = true;
@@ -39900,13 +40021,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39900
40021
  for (const pane of this.scene.panes.values()) pane.manualScale = null;
39901
40022
  for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
39902
40023
  const visibleBars = Math.min(n, 200);
39903
- const rightOffset = 6;
40024
+ const rightOffset = this.scene.style.margins.right;
39904
40025
  const v = this.clampViewport(w / ((visibleBars + rightOffset) * this.coords.spacingScale), rightOffset);
39905
40026
  this.coords.setViewport(v);
39906
40027
  this.targetBarSpacing = v.barSpacing;
39907
40028
  }
39908
40029
  /** Re-frame after a series replacement (a symbol/timeframe switch): keep the user's
39909
- * zoom (bar spacing), re-anchor the newest bars at the default right offset.
40030
+ * zoom (bar spacing), re-anchor the newest bars at the configured right margin.
39910
40031
  * `clampViewport`'s fit-all-bars floor deliberately does NOT apply — a progressive
39911
40032
  * head may still be backfilling toward the previous depth, and raising the spacing
39912
40033
  * to its temporary bar count would lose the zoom this exists to keep. */
@@ -39915,7 +40036,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39915
40036
  this.panVelocity = 0;
39916
40037
  for (const pane of this.scene.panes.values()) pane.manualScale = null;
39917
40038
  for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
39918
- const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset: defaultViewport().rightOffset };
40039
+ const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset: this.scene.style.margins.right };
39919
40040
  this.coords.setViewport(v);
39920
40041
  this.targetBarSpacing = v.barSpacing;
39921
40042
  }