@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.
package/dist/index.cjs CHANGED
@@ -13145,7 +13145,8 @@ function addRecent(hex6) {
13145
13145
  recents.unshift(hex6);
13146
13146
  if (recents.length > 10) recents.length = 10;
13147
13147
  }
13148
- function buildColorPicker(color, theme, onChange) {
13148
+ function buildColorPicker(color, theme, onChange, options = {}) {
13149
+ const commit = options.commit ?? "live";
13149
13150
  const parsed = splitColor(color);
13150
13151
  let curHex = parsed.hex6;
13151
13152
  let curAlpha = parsed.alpha;
@@ -13228,7 +13229,12 @@ function buildColorPicker(color, theme, onChange) {
13228
13229
  const r = track.getBoundingClientRect();
13229
13230
  curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
13230
13231
  paintOpacity();
13231
- emit();
13232
+ if (commit === "live") emit();
13233
+ };
13234
+ const endDrag = () => {
13235
+ if (!dragging) return;
13236
+ dragging = false;
13237
+ if (commit === "release") emit();
13232
13238
  };
13233
13239
  track.addEventListener("pointerdown", (e) => {
13234
13240
  e.stopPropagation();
@@ -13239,7 +13245,8 @@ function buildColorPicker(color, theme, onChange) {
13239
13245
  track.addEventListener("pointermove", (e) => {
13240
13246
  if (dragging) onDrag(e.clientX);
13241
13247
  });
13242
- track.addEventListener("pointerup", () => dragging = false);
13248
+ track.addEventListener("pointerup", endDrag);
13249
+ track.addEventListener("pointercancel", endDrag);
13243
13250
  function emit() {
13244
13251
  onChange(combineColor(curHex, curAlpha));
13245
13252
  }
@@ -13262,7 +13269,7 @@ function buildColorPicker(color, theme, onChange) {
13262
13269
  return root;
13263
13270
  }
13264
13271
  function colorField(theme, getVal, onVal, opts) {
13265
- return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover }).el;
13272
+ return new ColorField({ theme, getVal, onVal, shape: opts?.shape, id: opts?.id, popover: opts?.popover, commit: opts?.commit }).el;
13266
13273
  }
13267
13274
  var ColorField = class {
13268
13275
  constructor(opts) {
@@ -13271,6 +13278,7 @@ var ColorField = class {
13271
13278
  this.getVal = opts.getVal;
13272
13279
  this.onVal = opts.onVal;
13273
13280
  this.popoverOpts = opts.popover;
13281
+ this.commit = opts.commit;
13274
13282
  const trigger = document.createElement("button");
13275
13283
  trigger.type = "button";
13276
13284
  if (opts.id) trigger.id = opts.id;
@@ -13310,10 +13318,15 @@ var ColorField = class {
13310
13318
  boundary: this.popoverOpts?.boundary,
13311
13319
  zIndex: this.popoverOpts?.zIndex,
13312
13320
  className: "vela-color-field-pop",
13313
- content: buildColorPicker(this.getVal(), this.theme, (val) => {
13314
- this.onVal(val);
13315
- this.paint();
13316
- })
13321
+ content: buildColorPicker(
13322
+ this.getVal(),
13323
+ this.theme,
13324
+ (val) => {
13325
+ this.onVal(val);
13326
+ this.paint();
13327
+ },
13328
+ { commit: this.commit }
13329
+ )
13317
13330
  });
13318
13331
  pop.show();
13319
13332
  }
@@ -13794,7 +13807,8 @@ function buildFieldControl(desc) {
13794
13807
  const el2 = colorField(desc.theme, desc.get, desc.onChange, {
13795
13808
  shape: "circle",
13796
13809
  id: desc.id,
13797
- popover: desc.popover
13810
+ popover: desc.popover,
13811
+ commit: desc.commit
13798
13812
  });
13799
13813
  if (desc.title) el2.title = desc.title;
13800
13814
  return { el: el2 };
@@ -14172,7 +14186,10 @@ var IndicatorInputsDialog = class {
14172
14186
  id,
14173
14187
  theme: this.host.theme(),
14174
14188
  get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
14175
- onChange: (v) => emit(v)
14189
+ onChange: (v) => emit(v),
14190
+ // An input change re-executes the script over its whole history: commit the
14191
+ // opacity drag once on release, not once per pointer move.
14192
+ commit: "release"
14176
14193
  }).el;
14177
14194
  }
14178
14195
  if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
@@ -14419,10 +14436,6 @@ var IndicatorInputsDialog = class {
14419
14436
  }
14420
14437
  /** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
14421
14438
  openCalendar(anchor, current, onPick) {
14422
- const parsed = parseIsoDate(current);
14423
- let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
14424
- let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
14425
- const selected = parsed ? isoDate(parsed) : current;
14426
14439
  const pop = new Popover({
14427
14440
  trigger: anchor,
14428
14441
  theme: this.host.theme(),
@@ -14434,80 +14447,10 @@ var IndicatorInputsDialog = class {
14434
14447
  this.calendarPop = null;
14435
14448
  this.calendarAnchor = null;
14436
14449
  },
14437
- content: (el) => {
14438
- const title = document.createElement("div");
14439
- title.className = "vela-ind-cal-title";
14440
- const prev2 = document.createElement("button");
14441
- prev2.type = "button";
14442
- prev2.className = "vela-ind-cal-nav";
14443
- prev2.setAttribute("aria-label", "Previous month");
14444
- prev2.innerHTML = iconAt("chevron-left", 14);
14445
- const next = document.createElement("button");
14446
- next.type = "button";
14447
- next.className = "vela-ind-cal-nav";
14448
- next.setAttribute("aria-label", "Next month");
14449
- next.innerHTML = iconAt("chevron-right", 14);
14450
- const head = document.createElement("div");
14451
- head.className = "vela-ind-cal-head";
14452
- head.append(prev2, title, next);
14453
- const week = document.createElement("div");
14454
- week.className = "vela-ind-cal-week";
14455
- for (const d of WEEKDAY_LABELS) {
14456
- const cell = document.createElement("span");
14457
- cell.textContent = d;
14458
- week.appendChild(cell);
14459
- }
14460
- const grid = document.createElement("div");
14461
- grid.className = "vela-ind-cal-grid";
14462
- const paint = () => {
14463
- title.textContent = `${MONTH_LABELS[month]} ${year}`;
14464
- grid.replaceChildren();
14465
- const first = new Date(year, month, 1);
14466
- const startPad = first.getDay();
14467
- const days = new Date(year, month + 1, 0).getDate();
14468
- const today = isoDate(/* @__PURE__ */ new Date());
14469
- for (let i = 0; i < startPad; i++) {
14470
- const blank = document.createElement("span");
14471
- blank.className = "vela-ind-cal-blank";
14472
- grid.appendChild(blank);
14473
- }
14474
- for (let day = 1; day <= days; day++) {
14475
- const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
14476
- const b = document.createElement("button");
14477
- b.type = "button";
14478
- b.className = "vela-ind-cal-day";
14479
- b.textContent = String(day);
14480
- if (iso === selected) b.dataset.checked = "1";
14481
- if (iso === today) b.dataset.today = "1";
14482
- b.addEventListener("click", (e) => {
14483
- e.stopPropagation();
14484
- pop.hide();
14485
- onPick(iso);
14486
- });
14487
- grid.appendChild(b);
14488
- }
14489
- };
14490
- prev2.addEventListener("click", (e) => {
14491
- e.stopPropagation();
14492
- month -= 1;
14493
- if (month < 0) {
14494
- month = 11;
14495
- year -= 1;
14496
- }
14497
- paint();
14498
- });
14499
- next.addEventListener("click", (e) => {
14500
- e.stopPropagation();
14501
- month += 1;
14502
- if (month > 11) {
14503
- month = 0;
14504
- year += 1;
14505
- }
14506
- paint();
14507
- });
14508
- paint();
14509
- el.append(head, week, grid);
14510
- }
14450
+ content: (el) => fillCalendar(el, current, (iso) => {
14451
+ pop.hide();
14452
+ onPick(iso);
14453
+ })
14511
14454
  });
14512
14455
  this.calendarPop = pop;
14513
14456
  this.calendarAnchor = anchor;
@@ -14616,7 +14559,7 @@ function groupInputs(inputs) {
14616
14559
  var CALENDAR_SVG = iconAt("calendar", 14);
14617
14560
  var CLOCK_SVG = iconAt("clock", 14);
14618
14561
  var DIALOG_STYLE_ID2 = "vela-ind-dialog-styles";
14619
- var DIALOG_STYLE_REV = "29";
14562
+ var DIALOG_STYLE_REV = "30";
14620
14563
  var LEGEND_ICON_PX = 16;
14621
14564
  function ensureDialogStyles() {
14622
14565
  if (typeof document === "undefined") return;
@@ -14632,8 +14575,12 @@ function ensureDialogStyles() {
14632
14575
  .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;}
14633
14576
  .vela-ind-combo-chevron:hover{opacity:0.9;}
14634
14577
  .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;}
14578
+ .vela-ind-cal [hidden]{display:none !important;}
14635
14579
  .vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
14636
- .vela-ind-cal-title{flex:1;text-align:center;font-weight:600;font-size:14px;color:var(--vela-fg-bright);}
14580
+ .vela-ind-cal-title{flex:1;display:flex;align-items:center;justify-content:center;gap:2px;min-width:0;}
14581
+ .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;}
14582
+ .vela-ind-cal-switch:not(:disabled):hover{background:var(--vela-hover);}
14583
+ .vela-ind-cal-switch:disabled{cursor:default;}
14637
14584
  .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;}
14638
14585
  .vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
14639
14586
  .vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
@@ -14644,6 +14591,12 @@ function ensureDialogStyles() {
14644
14591
  .vela-ind-cal-day:hover{background:var(--vela-hover);}
14645
14592
  .vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
14646
14593
  .vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
14594
+ .vela-ind-cal-cells{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;width:calc(7 * 28px + 6 * 2px);}
14595
+ .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;}
14596
+ .vela-ind-cal-cell:hover{background:var(--vela-hover);}
14597
+ .vela-ind-cal-cell[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
14598
+ .vela-ind-cal-cell[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
14599
+ .vela-ind-cal-cell[data-outside]{opacity:0.45;}
14647
14600
  ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
14648
14601
  .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;}
14649
14602
  .vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
@@ -14678,6 +14631,7 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
14678
14631
  return { value: v, label: v };
14679
14632
  });
14680
14633
  var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
14634
+ var MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
14681
14635
  var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
14682
14636
  function normalizeDateInput(raw) {
14683
14637
  const t = raw.trim();
@@ -14699,6 +14653,151 @@ function parseIsoDate(raw) {
14699
14653
  function isoDate(d) {
14700
14654
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
14701
14655
  }
14656
+ function fillCalendar(el, current, onPick) {
14657
+ const today = /* @__PURE__ */ new Date();
14658
+ const selected = parseIsoDate(current);
14659
+ let year = selected?.getFullYear() ?? today.getFullYear();
14660
+ let month = selected?.getMonth() ?? today.getMonth();
14661
+ let mode = "date";
14662
+ const prev2 = document.createElement("button");
14663
+ prev2.type = "button";
14664
+ prev2.className = "vela-ind-cal-nav";
14665
+ prev2.innerHTML = iconAt("chevron-left", 14);
14666
+ const next = document.createElement("button");
14667
+ next.type = "button";
14668
+ next.className = "vela-ind-cal-nav";
14669
+ next.innerHTML = iconAt("chevron-right", 14);
14670
+ const monthBtn = document.createElement("button");
14671
+ monthBtn.type = "button";
14672
+ monthBtn.className = "vela-ind-cal-switch";
14673
+ monthBtn.setAttribute("aria-label", "Choose month");
14674
+ const yearBtn = document.createElement("button");
14675
+ yearBtn.type = "button";
14676
+ yearBtn.className = "vela-ind-cal-switch";
14677
+ yearBtn.setAttribute("aria-label", "Choose year");
14678
+ const title = document.createElement("div");
14679
+ title.className = "vela-ind-cal-title";
14680
+ title.append(monthBtn, yearBtn);
14681
+ const head = document.createElement("div");
14682
+ head.className = "vela-ind-cal-head";
14683
+ head.append(prev2, title, next);
14684
+ const week = document.createElement("div");
14685
+ week.className = "vela-ind-cal-week";
14686
+ for (const d of WEEKDAY_LABELS) {
14687
+ const cell = document.createElement("span");
14688
+ cell.textContent = d;
14689
+ week.appendChild(cell);
14690
+ }
14691
+ const grid = document.createElement("div");
14692
+ const paint = () => {
14693
+ const decade = Math.floor(year / 10) * 10;
14694
+ monthBtn.hidden = mode !== "date";
14695
+ monthBtn.textContent = MONTH_LABELS[month] ?? "";
14696
+ yearBtn.textContent = mode === "year" ? `${decade}-${decade + 9}` : String(year);
14697
+ yearBtn.disabled = mode === "year";
14698
+ week.hidden = mode !== "date";
14699
+ prev2.setAttribute("aria-label", mode === "date" ? "Previous month" : mode === "month" ? "Previous year" : "Previous decade");
14700
+ next.setAttribute("aria-label", mode === "date" ? "Next month" : mode === "month" ? "Next year" : "Next decade");
14701
+ grid.className = mode === "date" ? "vela-ind-cal-grid" : "vela-ind-cal-cells";
14702
+ grid.replaceChildren();
14703
+ if (mode === "date") {
14704
+ const startPad = new Date(year, month, 1).getDay();
14705
+ const days = new Date(year, month + 1, 0).getDate();
14706
+ const todayIso = isoDate(today);
14707
+ const selectedIso = selected ? isoDate(selected) : "";
14708
+ for (let i = 0; i < startPad; i++) {
14709
+ const blank = document.createElement("span");
14710
+ blank.className = "vela-ind-cal-blank";
14711
+ grid.appendChild(blank);
14712
+ }
14713
+ for (let day = 1; day <= days; day++) {
14714
+ const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
14715
+ const b = document.createElement("button");
14716
+ b.type = "button";
14717
+ b.className = "vela-ind-cal-day";
14718
+ b.textContent = String(day);
14719
+ if (iso === selectedIso) b.dataset.checked = "1";
14720
+ if (iso === todayIso) b.dataset.today = "1";
14721
+ b.addEventListener("click", (e) => {
14722
+ e.stopPropagation();
14723
+ onPick(iso);
14724
+ });
14725
+ grid.appendChild(b);
14726
+ }
14727
+ return;
14728
+ }
14729
+ if (mode === "month") {
14730
+ for (let m = 0; m < 12; m++) {
14731
+ const b = document.createElement("button");
14732
+ b.type = "button";
14733
+ b.className = "vela-ind-cal-cell";
14734
+ b.textContent = MONTH_SHORT[m] ?? "";
14735
+ if (selected?.getFullYear() === year && selected.getMonth() === m) b.dataset.checked = "1";
14736
+ if (today.getFullYear() === year && today.getMonth() === m) b.dataset.today = "1";
14737
+ b.addEventListener("click", (e) => {
14738
+ e.stopPropagation();
14739
+ month = m;
14740
+ mode = "date";
14741
+ paint();
14742
+ });
14743
+ grid.appendChild(b);
14744
+ }
14745
+ return;
14746
+ }
14747
+ for (let y = decade - 1; y <= decade + 10; y++) {
14748
+ const b = document.createElement("button");
14749
+ b.type = "button";
14750
+ b.className = "vela-ind-cal-cell";
14751
+ b.textContent = String(y);
14752
+ if (y < decade || y > decade + 9) b.dataset.outside = "1";
14753
+ if (selected?.getFullYear() === y) b.dataset.checked = "1";
14754
+ if (today.getFullYear() === y) b.dataset.today = "1";
14755
+ b.addEventListener("click", (e) => {
14756
+ e.stopPropagation();
14757
+ year = y;
14758
+ mode = "month";
14759
+ paint();
14760
+ });
14761
+ grid.appendChild(b);
14762
+ }
14763
+ };
14764
+ const step = (dir) => {
14765
+ if (mode === "date") {
14766
+ month += dir;
14767
+ if (month < 0) {
14768
+ month = 11;
14769
+ year -= 1;
14770
+ }
14771
+ if (month > 11) {
14772
+ month = 0;
14773
+ year += 1;
14774
+ }
14775
+ } else {
14776
+ year += mode === "month" ? dir : dir * 10;
14777
+ }
14778
+ paint();
14779
+ };
14780
+ prev2.addEventListener("click", (e) => {
14781
+ e.stopPropagation();
14782
+ step(-1);
14783
+ });
14784
+ next.addEventListener("click", (e) => {
14785
+ e.stopPropagation();
14786
+ step(1);
14787
+ });
14788
+ monthBtn.addEventListener("click", (e) => {
14789
+ e.stopPropagation();
14790
+ mode = "month";
14791
+ paint();
14792
+ });
14793
+ yearBtn.addEventListener("click", (e) => {
14794
+ e.stopPropagation();
14795
+ mode = "year";
14796
+ paint();
14797
+ });
14798
+ paint();
14799
+ el.append(head, week, grid);
14800
+ }
14702
14801
  function normalizeTimeInput(raw) {
14703
14802
  const t = raw.trim();
14704
14803
  const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
@@ -15980,10 +16079,12 @@ var BASELINE_LEVEL_DEFAULT = 50;
15980
16079
  var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
15981
16080
  var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
15982
16081
  var EXTENDED_SHADE = POSTMARKET_SHADE;
16082
+ var DEFAULT_MARGINS = { top: 10, bottom: 10, right: 10 };
15983
16083
  function defaultChartStyle() {
15984
16084
  return {
15985
16085
  chartTypes: {},
15986
16086
  fontSize: 11,
16087
+ margins: { ...DEFAULT_MARGINS },
15987
16088
  gridVert: { visible: true, color: null },
15988
16089
  gridHorz: { visible: true, color: null },
15989
16090
  borderColor: null,
@@ -16107,6 +16208,12 @@ function clampLevel(v) {
16107
16208
  function clampSpacing(v) {
16108
16209
  return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
16109
16210
  }
16211
+ function clampMarginPct(v) {
16212
+ return v < 0 ? 0 : v > 40 ? 40 : v;
16213
+ }
16214
+ function clampMarginBars(v) {
16215
+ return Math.round(v < 0 ? 0 : v > 200 ? 200 : v);
16216
+ }
16110
16217
  function factoryResetConfig(factory, priceStyle = factory.series.style) {
16111
16218
  const bag = {};
16112
16219
  for (const t of chartTypes()) {
@@ -16155,6 +16262,7 @@ function mergeConfig(base, patch) {
16155
16262
  const ps = asObject(p.priceScale);
16156
16263
  const anim = asObject(p.animations);
16157
16264
  const panes = asObject(p.panes);
16265
+ const margins = asObject(p.margins);
16158
16266
  const trades = asObject(p.trades);
16159
16267
  const ts = asObject(p.timeScale);
16160
16268
  const marks = asObject(p.marks);
@@ -16214,6 +16322,11 @@ function mergeConfig(base, patch) {
16214
16322
  panes: {
16215
16323
  separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
16216
16324
  },
16325
+ margins: {
16326
+ top: isNum(margins.top) ? clampMarginPct(margins.top) : base.margins.top,
16327
+ bottom: isNum(margins.bottom) ? clampMarginPct(margins.bottom) : base.margins.bottom,
16328
+ right: isNum(margins.right) ? clampMarginBars(margins.right) : base.margins.right
16329
+ },
16217
16330
  trades: {
16218
16331
  visible: isBool(trades.visible) ? trades.visible : base.trades.visible,
16219
16332
  labels: isBool(trades.labels) ? trades.labels : base.trades.labels,
@@ -21584,6 +21697,10 @@ var BUILTIN_SETTINGS_IDS = [
21584
21697
  "canvas.grid",
21585
21698
  "canvas.grid.vertical",
21586
21699
  "canvas.grid.horizontal",
21700
+ "canvas.margins",
21701
+ "canvas.margins.top",
21702
+ "canvas.margins.bottom",
21703
+ "canvas.margins.right",
21587
21704
  "canvas.theme"
21588
21705
  ];
21589
21706
  function settingsIdCatalog(hostSections, markGroups = []) {
@@ -22114,6 +22231,10 @@ var SettingsDialog = class {
22114
22231
  body.append(sid(this.toggleRow("Horizontal", config.grid.horzLines.visible, (v) => this.emit({ grid: { horzLines: { visible: v } } }), [
22115
22232
  this.swatch(config.grid.horzLines.color, (v) => this.emit({ grid: { horzLines: { color: v } } }))
22116
22233
  ]), "canvas.grid.horizontal"));
22234
+ body.append(sid(this.sectionTitle("Margins"), "canvas.margins"));
22235
+ body.append(sid(this.numberRow("Top", config.margins.top, 0, 40, 1, (v) => this.emit({ margins: { top: v } }), "%"), "canvas.margins.top"));
22236
+ body.append(sid(this.numberRow("Bottom", config.margins.bottom, 0, 40, 1, (v) => this.emit({ margins: { bottom: v } }), "%"), "canvas.margins.bottom"));
22237
+ body.append(sid(this.numberRow("Right", config.margins.right, 0, 200, 1, (v) => this.emit({ margins: { right: v } }), "bars"), "canvas.margins.right"));
22117
22238
  if (this.themeControl) {
22118
22239
  const tc = this.themeControl;
22119
22240
  body.append(sid(this.sectionTitle("Theme"), "canvas.theme"));
@@ -22685,23 +22806,16 @@ var SettingsDialog = class {
22685
22806
  this.hintTips.push(dispose);
22686
22807
  return el;
22687
22808
  }
22688
- numberRow(label, value, min, max, step, onChange) {
22689
- return fieldRow({
22690
- label,
22691
- labelSize: "sm",
22692
- className: "vela-sd-row",
22693
- control: buildFieldControl({
22694
- kind: "number",
22695
- value,
22696
- min,
22697
- max,
22698
- step,
22699
- fill: false,
22700
- commit: "live",
22701
- steppers: true,
22702
- onChange
22703
- }).el
22704
- });
22809
+ /** `unit` (optional) trails the input as muted text — "%", "bars". */
22810
+ numberRow(label, value, min, max, step, onChange, unit) {
22811
+ const controls = [buildFieldControl({ kind: "number", value, min, max, step, fill: false, commit: "live", steppers: true, onChange }).el];
22812
+ if (unit) {
22813
+ const u = document.createElement("span");
22814
+ u.textContent = unit;
22815
+ u.style.cssText = "color:var(--vela-fg-muted);";
22816
+ controls.push(u);
22817
+ }
22818
+ return this.rowWith(label, controls);
22705
22819
  }
22706
22820
  /** A dropdown whose option values differ from their display labels. */
22707
22821
  selectRowLabeled(label, value, options, onChange) {
@@ -28220,9 +28334,7 @@ function createProjector(coords, paneOf, paneIdAtY, barsInRange, seriesInRange)
28220
28334
  }
28221
28335
 
28222
28336
  // src/renderers/native/core/autoscale.ts
28223
- var MARGIN_TOP = 2 / 7;
28224
- var MARGIN_BOTTOM = 1 / 7;
28225
- function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0) {
28337
+ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0, margins = DEFAULT_MARGINS) {
28226
28338
  let min = Infinity;
28227
28339
  let max = -Infinity;
28228
28340
  const consider = (v) => {
@@ -28257,14 +28369,17 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
28257
28369
  const pad = Math.abs(min) * 0.1 || 1;
28258
28370
  return { min: min - pad, max: max + pad, log: log && min - pad > 0 };
28259
28371
  }
28372
+ const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
28373
+ const above = margins.top / 100 / content;
28374
+ const below = margins.bottom / 100 / content;
28260
28375
  if (log && min > 0) {
28261
28376
  const lmin = Math.log(min);
28262
28377
  const lmax = Math.log(max);
28263
28378
  const lspan = lmax - lmin;
28264
- return { min: Math.exp(lmin - lspan * MARGIN_BOTTOM), max: Math.exp(lmax + lspan * MARGIN_TOP), log: true };
28379
+ return { min: Math.exp(lmin - lspan * below), max: Math.exp(lmax + lspan * above), log: true };
28265
28380
  }
28266
28381
  const span = max - min;
28267
- return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
28382
+ return { min: min - span * below, max: max + span * above };
28268
28383
  }
28269
28384
  function considerSeries(s, i0, i1, off, consider) {
28270
28385
  if (!seriesInScale(s)) return;
@@ -30091,6 +30206,7 @@ var NativeRenderer = class {
30091
30206
  intro: this.intro.style !== false
30092
30207
  },
30093
30208
  panes: { separatorColor: s.separatorColor ?? t.borderColor },
30209
+ margins: { ...s.margins },
30094
30210
  trades: {
30095
30211
  visible: this.scene.tradeMarkers.visible,
30096
30212
  labels: this.scene.tradeMarkers.labels,
@@ -30203,6 +30319,10 @@ var NativeRenderer = class {
30203
30319
  this.animAutoscale.toggle(next.animations.autoscale);
30204
30320
  this.intro = { style: next.animations.intro ? this.introOnStyle : false, duration: this.intro.duration || INTRO_DURATION_DEFAULT_MS };
30205
30321
  s.separatorColor = keepInherit(s.separatorColor, next.panes.separatorColor, prevTheme.borderColor);
30322
+ if (next.margins.right !== s.margins.right && this.coords.barCount > 0) {
30323
+ this.applyViewport({ barSpacing: this.coords.getViewport().barSpacing, rightOffset: next.margins.right });
30324
+ }
30325
+ s.margins = { ...next.margins };
30206
30326
  this.scene.tradeMarkers = {
30207
30327
  visible: next.trades.visible,
30208
30328
  labels: next.trades.labels,
@@ -30446,7 +30566,7 @@ var NativeRenderer = class {
30446
30566
  /** Glide the view back to the most recent bars, keeping the current zoom (barSpacing). */
30447
30567
  scrollToRealtime() {
30448
30568
  if (this.coords.barCount === 0) return;
30449
- this.glideRightOffset(ZOOM_OUT_MARGIN_BARS);
30569
+ this.glideRightOffset(this.scene.style.margins.right);
30450
30570
  }
30451
30571
  /** Ease rightOffset to `target` at constant zoom (see animTick's scroll glide);
30452
30572
  * instant when the scroll glide is off. Shared by scroll-to-latest and panBy. */
@@ -32388,6 +32508,7 @@ var NativeRenderer = class {
32388
32508
  const pricePane = panes.find((p) => p.kind === "price") ?? null;
32389
32509
  this.chrome.prepare(this.scene, this.coords, this.theme);
32390
32510
  const animating = this.animator.active;
32511
+ const margins = this.scene.style.margins;
32391
32512
  for (const pane of panes) {
32392
32513
  if (pane.manualScale) {
32393
32514
  pane.scaleTarget = pane.manualScale;
@@ -32403,7 +32524,7 @@ var NativeRenderer = class {
32403
32524
  if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
32404
32525
  }
32405
32526
  const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels) || !this.paneHasMeasurableContent(masterModels, dr));
32406
- pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
32527
+ pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
32407
32528
  pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
32408
32529
  pane.axisFormat = void 0;
32409
32530
  pane.axisBands = void 0;
@@ -32414,7 +32535,7 @@ var NativeRenderer = class {
32414
32535
  pane.axisFormat = "volume";
32415
32536
  }
32416
32537
  } else if (this.layerNativesOwnPane(pane, masterModels)) {
32417
- pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
32538
+ pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
32418
32539
  pane.percentBaseline = this.bars[i0]?.close ?? 0;
32419
32540
  if (masterModels.every((m) => m.paneAxis != null)) {
32420
32541
  pane.axisFormat = "none";
@@ -32443,7 +32564,7 @@ var NativeRenderer = class {
32443
32564
  continue;
32444
32565
  }
32445
32566
  const mdr = this.chrome.paneDrawingsRange([model], this.scene, false, vr);
32446
- sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id));
32567
+ sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id), margins);
32447
32568
  if (!animating || !sl.initialized) {
32448
32569
  sl.scale = { ...sl.scaleTarget };
32449
32570
  sl.initialized = true;
@@ -32484,13 +32605,13 @@ var NativeRenderer = class {
32484
32605
  for (const pane of this.scene.panes.values()) pane.manualScale = null;
32485
32606
  for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
32486
32607
  const visibleBars = Math.min(n, 200);
32487
- const rightOffset = 6;
32608
+ const rightOffset = this.scene.style.margins.right;
32488
32609
  const v = this.clampViewport(w / ((visibleBars + rightOffset) * this.coords.spacingScale), rightOffset);
32489
32610
  this.coords.setViewport(v);
32490
32611
  this.targetBarSpacing = v.barSpacing;
32491
32612
  }
32492
32613
  /** Re-frame after a series replacement (a symbol/timeframe switch): keep the user's
32493
- * zoom (bar spacing), re-anchor the newest bars at the default right offset.
32614
+ * zoom (bar spacing), re-anchor the newest bars at the configured right margin.
32494
32615
  * `clampViewport`'s fit-all-bars floor deliberately does NOT apply — a progressive
32495
32616
  * head may still be backfilling toward the previous depth, and raising the spacing
32496
32617
  * to its temporary bar count would lose the zoom this exists to keep. */
@@ -32499,7 +32620,7 @@ var NativeRenderer = class {
32499
32620
  this.panVelocity = 0;
32500
32621
  for (const pane of this.scene.panes.values()) pane.manualScale = null;
32501
32622
  for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
32502
- const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset: defaultViewport().rightOffset };
32623
+ const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset: this.scene.style.margins.right };
32503
32624
  this.coords.setViewport(v);
32504
32625
  this.targetBarSpacing = v.barSpacing;
32505
32626
  }
package/dist/index.d.cts CHANGED
@@ -1,9 +1,9 @@
1
- import { D as DrawingsDocument, R as Resolved } from './contributions-D6p1RB38.cjs';
2
- export { B as BarsChangeReason, C as CellStateContext, b as ContextSelect, c as DataControl, d as DrawingsControl, e as EngineAlert, f as EngineCapabilities, g as EngineContextSnapshot, h as EngineFactory, i as EngineWarning, j as ExecutionHandlers, k as ExecutionMarket, l as ExecutionRequest, m as ExecutionSession, E as ExternalIndicatorEntry, F as FetchSeries, n as IndicatorEventMap, I as IndicatorHandle, o as IndicatorSummary, L as LegendActionDescriptor, p as LegendCalloutContent, q as LegendCalloutDescriptor, r as LegendCalloutItem, s as LegendCalloutSpec, t as LegendIndicatorInfo, M as MarksControl, N as NativeIndicator, u as NativeIndicatorContext, v as NativeIndicatorDescriptor, w as NativeIndicatorInfo, x as NativeIndicatorOutput, O as OVERRIDABLE_TOPBAR_IDS, P as ParsedSymbol, y as PreparedScript, z as RendererControl, A as RunIndicatorResult, G as SceneInspection, a as ScriptRun, H as ScriptRunCause, J as ScriptRunResult, S as ScriptingEngine, K as SidePanelButton, Q as SidePanelDescriptor, T as SidePanelHandle, U as SidePanelHeader, X as StatePersistenceHandler, Y as StrategyFill, Z as StrategyState, _ as StrategyTrade, $ as SymbolRankingHook, a0 as TypedEventBus, V as Vela, a1 as VelaDeps, a2 as VelaEventMap, a3 as VisibleBarRange, a4 as WidgetActionDescriptor, a5 as WidgetActionTarget, a6 as WidgetAttachment, W as WidgetContext, a7 as getNativeIndicator, a8 as legendActions, a9 as legendCallouts, aa as nativeIndicatorDescriptors, ab as nativeIndicatorTypes, ac as registerDefaultEngine, ad as registerLegendAction, ae as registerLegendCallout, af as registerNativeIndicator, ag as registerSidePanel, ah as registerStatePersistence, ai as registerSymbolRanking, aj as registerWidgetAction, ak as registerWidgetAttachment, al as resolveEngines, am as sidePanels, an as statePersistenceHandlers, ao as symbolRanking, ap as topbarActionOverride, aq as unregisterDefaultEngine, ar as unregisterLegendAction, as as unregisterLegendCallout, at as unregisterNativeIndicator, au as unregisterSidePanel, av as unregisterStatePersistence, aw as unregisterWidgetAction, ax as unregisterWidgetAttachment, ay as widgetActions, az as widgetAttachments } from './contributions-D6p1RB38.cjs';
1
+ import { D as DrawingsDocument, R as Resolved } from './contributions-DF_Ea07m.cjs';
2
+ export { B as BarsChangeReason, C as CellStateContext, b as ContextSelect, c as DataControl, d as DrawingsControl, e as EngineAlert, f as EngineCapabilities, g as EngineContextSnapshot, h as EngineFactory, i as EngineWarning, j as ExecutionHandlers, k as ExecutionMarket, l as ExecutionRequest, m as ExecutionSession, E as ExternalIndicatorEntry, F as FetchSeries, n as IndicatorEventMap, I as IndicatorHandle, o as IndicatorSummary, L as LegendActionDescriptor, p as LegendCalloutContent, q as LegendCalloutDescriptor, r as LegendCalloutItem, s as LegendCalloutSpec, t as LegendIndicatorInfo, M as MarksControl, N as NativeIndicator, u as NativeIndicatorContext, v as NativeIndicatorDescriptor, w as NativeIndicatorInfo, x as NativeIndicatorOutput, O as OVERRIDABLE_TOPBAR_IDS, P as ParsedSymbol, y as PreparedScript, z as RendererControl, A as RunIndicatorResult, G as SceneInspection, a as ScriptRun, H as ScriptRunCause, J as ScriptRunResult, S as ScriptingEngine, K as SidePanelButton, Q as SidePanelDescriptor, T as SidePanelHandle, U as SidePanelHeader, X as StatePersistenceHandler, Y as StrategyFill, Z as StrategyState, _ as StrategyTrade, $ as SymbolRankingHook, a0 as TypedEventBus, V as Vela, a1 as VelaDeps, a2 as VelaEventMap, a3 as VisibleBarRange, a4 as WidgetActionDescriptor, a5 as WidgetActionTarget, a6 as WidgetAttachment, W as WidgetContext, a7 as getNativeIndicator, a8 as legendActions, a9 as legendCallouts, aa as nativeIndicatorDescriptors, ab as nativeIndicatorTypes, ac as registerDefaultEngine, ad as registerLegendAction, ae as registerLegendCallout, af as registerNativeIndicator, ag as registerSidePanel, ah as registerStatePersistence, ai as registerSymbolRanking, aj as registerWidgetAction, ak as registerWidgetAttachment, al as resolveEngines, am as sidePanels, an as statePersistenceHandlers, ao as symbolRanking, ap as topbarActionOverride, aq as unregisterDefaultEngine, ar as unregisterLegendAction, as as unregisterLegendCallout, at as unregisterNativeIndicator, au as unregisterSidePanel, av as unregisterStatePersistence, aw as unregisterWidgetAction, ax as unregisterWidgetAttachment, ay as widgetActions, az as widgetAttachments } from './contributions-DF_Ea07m.cjs';
3
3
  import { D as Drawing, S as SerializedDrawing, U as Unsubscribe, L as LineStyle, P as PriceStyle, e as IChartRenderer, R as RendererCapabilities, f as RendererDisplayOptions, g as IndicatorRenderHandle, h as IndicatorStatus, c as VelaTheme, W as WallClock, O as OHLCV, i as Pane, j as PaneAction, k as MoveTarget, l as IndicatorModel, m as ScenePatch, I as InputValue, n as SymbolPickerFn, o as LegendActionView, p as LegendCalloutView, q as InputChangeEvent, T as ThemeName, C as CrosshairEvent, r as ClickEvent, A as AxisLongPressEvent, s as TimelineMark, t as MarkGroup, u as MarkClickEvent, a as VisibleRange, v as DataWindowReadout, w as IDrawingsRendererPort, x as Millis, y as SnapMode, z as DrawingTypeKey, B as ToolbarDefinition, M as MarketConfig } from './options-D1AJKsn_.cjs';
4
4
  export { E as AddIndicatorOptions, F as AnimationConfig, G as Background, H as BoxFontFamily, J as BoxHAlign, K as BoxTextSize, Q as BoxVAlign, X as CandleBarColor, Y as CandleSeries, Z as CandleStyle, _ as DataWindowGroup, $ as DataWindowOHLC, a0 as DataWindowRow, a1 as DirtyRange, a2 as DrawingBox, a3 as DrawingExtend, a4 as DrawingIntent, a5 as DrawingLabel, a6 as DrawingLine, a7 as DrawingLinefill, a8 as DrawingMode, a9 as DrawingPoint, aa as DrawingPolyline, ab as DrawingSeriesBar, ac as DrawingSeriesGateway, ad as DrawingSeriesState, ae as DrawingStyle, af as DrawingTable, ag as DrawingText, ah as DrawingXLoc, ai as DrawingsOption, aj as Fill, ak as FillGradientStop, al as IndicatorMeta, am as InputCondition, an as InputSchema, ao as InputType, ap as InputWhen, aq as IntroAnimation, ar as IntroConfig, as as IntroStyle, at as LabelStyle, au as LabelYLoc, av as LineLikeKind, aw as LineLikeSeries, ax as LineLikeStyle, ay as MarkContent, az as MarkContentSource, aA as MarkGlyph, aB as MarkPanelItem, aC as MarkShape, aD as MarkerPoint, aE as MarkerSeries, aF as MarketSnapshot, aG as MarketSwitch, aH as PaneAxis, aI as PaneAxisBand, aJ as PaneHint, aK as PaneKind, aL as PolylinePoint, aM as PriceLine, aN as Projector, aO as ProviderName, aP as RendererConstructor, aQ as Scene, aR as SchemaPatch, aS as SecondClock, aT as SeriesDisplay, aU as SeriesKind, aV as SeriesPoint, aW as SeriesSpec, aX as SeriesSurface, aY as SeriesValueDelta, aZ as SettingsField, a_ as SettingsSchema, a$ as SettingsVisibilityPolicy, b0 as TableCell, b1 as TableMerge, b2 as TablePosition, b3 as ToolbarGroupConfig, b4 as TradeExecution, b5 as ValuePatch, b as VelaOptions, V as VisibleRangePreset, b6 as buildToolbar, b7 as defaultToolbar, b8 as inputDeltas, b9 as inputVisible, ba as seriesInScale, bb as seriesShownOn } from './options-D1AJKsn_.cjs';
5
5
  import { M as MarketDataFeed, D as DataProvider, P as ProviderInfo, a as SymbolDescriptor, S as SymbolInfo, b as ProviderCapabilities, B as BarRange } from './DataProvider-CGa4KHRa.cjs';
6
- export { A as ACCENT, a as ACCENT_BRIGHT, B as BEARISH, b as BULLISH, c as BarTransform, d as BasePaintingModulation, C as CATEGORICAL, e as CHIP_PLATE, f as CROSSHAIR, g as ChartTypeDefinition, h as ChartTypeSettingsInstance, i as ChartTypeSettingsSection, j as ChartTypeSettingsSubsection, D as DrawingTypeMeta, H as HIGHLIGHT, I as INFO, k as INVALID, l as IdentifiableKind, M as MARKER, N as NEUTRAL, m as NormalizedSettingsRow, R as RendererLayerArgs, n as RendererLayerDefinition, o as RendererLayerInstance, S as SERIES_LINE, p as SESSION_OFF, q as SESSION_POST, r as SESSION_PRE, s as SLATE, t as SLATE_DEEP, u as SeriesDataEngine, v as SeriesDataEngineHost, w as SettingsInlineControl, x as SettingsRowCondition, y as SettingsRowDescriptor, z as SettingsRowInlineNumber, E as SettingsRowSwatch, F as SettingsRowValueKey, G as SettingsRowWhen, J as SettingsRowWidth, K as SettingsSelectOption, L as SettingsValueRow, T as TRADE_EXIT, O as TRADE_LONG, P as TRADE_SHORT, V as VALID, W as WARNING, Q as categoricalColor, U as chartType, X as chartTypes, Y as createDrawing, Z as deserializeDrawing, _ as drawingTypes, $ as getDrawingType, a0 as normalizeSettingsRow, a1 as registerChartType, a2 as registerDrawingType, a3 as registerRendererDefaults, a4 as registerRendererLayer, a5 as rendererDefaults, a6 as rendererLayers, a7 as settingsRowValueKeys, a8 as stableSeriesId, a9 as tickerModifierIds, aa as unregisterChartType, ab as unregisterRendererDefaults, ac as unregisterRendererLayer } from './plugin-CxWfg6vK.cjs';
6
+ export { A as ACCENT, a as ACCENT_BRIGHT, B as BEARISH, b as BULLISH, c as BarTransform, d as BasePaintingModulation, C as CATEGORICAL, e as CHIP_PLATE, f as CROSSHAIR, g as ChartTypeDefinition, h as ChartTypeSettingsInstance, i as ChartTypeSettingsSection, j as ChartTypeSettingsSubsection, D as DrawingTypeMeta, H as HIGHLIGHT, I as INFO, k as INVALID, l as IdentifiableKind, M as MARKER, N as NEUTRAL, m as NormalizedSettingsRow, R as RendererLayerArgs, n as RendererLayerDefinition, o as RendererLayerInstance, S as SERIES_LINE, p as SESSION_OFF, q as SESSION_POST, r as SESSION_PRE, s as SLATE, t as SLATE_DEEP, u as SeriesDataEngine, v as SeriesDataEngineHost, w as SettingsInlineControl, x as SettingsRowCondition, y as SettingsRowDescriptor, z as SettingsRowInlineNumber, E as SettingsRowSwatch, F as SettingsRowValueKey, G as SettingsRowWhen, J as SettingsRowWidth, K as SettingsSelectOption, L as SettingsValueRow, T as TRADE_EXIT, O as TRADE_LONG, P as TRADE_SHORT, V as VALID, W as WARNING, Q as categoricalColor, U as chartType, X as chartTypes, Y as createDrawing, Z as deserializeDrawing, _ as drawingTypes, $ as getDrawingType, a0 as normalizeSettingsRow, a1 as registerChartType, a2 as registerDrawingType, a3 as registerRendererDefaults, a4 as registerRendererLayer, a5 as rendererDefaults, a6 as rendererLayers, a7 as settingsRowValueKeys, a8 as stableSeriesId, a9 as tickerModifierIds, aa as unregisterChartType, ab as unregisterRendererDefaults, ac as unregisterRendererLayer } from './plugin-DPyVJFoA.cjs';
7
7
  export { D as DEFAULT_PANEL_MAX_WIDTH, a as DEFAULT_PANEL_MIN_WIDTH, b as DEFAULT_PANEL_WIDTH, S as SidePanelOptions, c as clampPanelWidth } from './side-panel-HF0IAzwf.cjs';
8
8
  export { i as iconMarkup, r as registerIcon } from './icons-BZYbJXSV.cjs';
9
9
  export { a as KeyBindingDescriptor, R as ResolvedBinding } from './keymap-CGOz5F5f.cjs';
@@ -138,11 +138,23 @@ interface SessionShadeStyle {
138
138
  postmarketColor: string;
139
139
  extendedColor: string;
140
140
  }
141
+ /**
142
+ * Empty space kept around the data. `top` / `bottom` are the share of each pane's
143
+ * PIXEL height the autoscaled window reserves above the highest / below the lowest
144
+ * visible value (percent); `right` is the whitespace after the newest bar, in bars,
145
+ * that fit, re-frame and scroll-to-latest land on.
146
+ */
147
+ interface ChartMargins {
148
+ top: number;
149
+ bottom: number;
150
+ right: number;
151
+ }
141
152
  interface ChartStyle {
142
153
  /** Per-chart-type settings (plugin SDK sections), keyed by type id then row key. */
143
154
  chartTypes: Record<string, Record<string, unknown>>;
144
155
  /** Axis/label font size in CSS px (the family stays on the theme). */
145
156
  fontSize: number;
157
+ margins: ChartMargins;
146
158
  gridVert: GridLineStyle;
147
159
  gridHorz: GridLineStyle;
148
160
  /** Axis frame lines (the right price-axis border); `null` ⇒ inherit `theme.borderColor`. */
@@ -213,6 +225,9 @@ interface ChartConfig {
213
225
  panes: {
214
226
  separatorColor: string;
215
227
  };
228
+ /** Whitespace around the data (the Canvas tab's Margins group): top/bottom in percent
229
+ * of pane height, right in bars. See {@link ChartMargins}. */
230
+ margins: ChartMargins;
216
231
  /** Strategy trade markers (the `tradeMarkers` feature): the order-fill units on the price pane. */
217
232
  trades: {
218
233
  visible: boolean;
@@ -982,7 +997,7 @@ declare class NativeRenderer implements IChartRenderer {
982
997
  private tradesScaleHints;
983
998
  private fitContent;
984
999
  /** Re-frame after a series replacement (a symbol/timeframe switch): keep the user's
985
- * zoom (bar spacing), re-anchor the newest bars at the default right offset.
1000
+ * zoom (bar spacing), re-anchor the newest bars at the configured right margin.
986
1001
  * `clampViewport`'s fit-all-bars floor deliberately does NOT apply — a progressive
987
1002
  * head may still be backfilling toward the previous depth, and raising the spacing
988
1003
  * to its temporary bar count would lose the zoom this exists to keep. */