@luxalgo/vela 0.7.5 → 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.
Files changed (41) hide show
  1. package/dist/{DataProvider-Dut6lXGM.d.cts → DataProvider-CGa4KHRa.d.cts} +1 -1
  2. package/dist/{DataProvider-DLeFs1gE.d.ts → DataProvider-CmWUtDVL.d.ts} +1 -1
  3. package/dist/{chunk-ZADTVRUO.js → chunk-PVGWNF7V.js} +1 -1
  4. package/dist/{chunk-MCLI6B3S.js → chunk-RVQWJOEE.js} +277 -120
  5. package/dist/{chunk-BKHSQ4YM.js → chunk-WTWGEB7K.js} +23 -9
  6. package/dist/{chunk-MNG5XPLV.js → chunk-ZA2XWTGH.js} +94 -37
  7. package/dist/{contributions-DYtiRtES.d.cts → contributions-DF_Ea07m.d.cts} +25 -2
  8. package/dist/{contributions-CEkgJKTU.d.ts → contributions-nKOSeLy8.d.ts} +25 -2
  9. package/dist/index.cjs +285 -127
  10. package/dist/index.d.cts +22 -7
  11. package/dist/index.d.ts +22 -7
  12. package/dist/index.js +2 -2
  13. package/dist/{options-DNBoMKkX.d.ts → options-D1AJKsn_.d.cts} +10 -0
  14. package/dist/{options-DNBoMKkX.d.cts → options-D1AJKsn_.d.ts} +10 -0
  15. package/dist/{plugin-CLn--oP6.d.ts → plugin-0oFDnexb.d.ts} +3 -3
  16. package/dist/{plugin-DtkeQVAO.d.cts → plugin-DPyVJFoA.d.cts} +3 -3
  17. package/dist/plugin.d.cts +4 -4
  18. package/dist/plugin.d.ts +4 -4
  19. package/dist/providers/binance.d.cts +2 -2
  20. package/dist/providers/binance.d.ts +2 -2
  21. package/dist/providers/coinbase.d.cts +2 -2
  22. package/dist/providers/coinbase.d.ts +2 -2
  23. package/dist/providers/hyperliquid.d.cts +2 -2
  24. package/dist/providers/hyperliquid.d.ts +2 -2
  25. package/dist/{statusline-model-gA__yaog.d.cts → statusline-model-BoWJ3cWQ.d.cts} +18 -5
  26. package/dist/{statusline-model-DypGXjtr.d.ts → statusline-model-DAFyqVmR.d.ts} +18 -5
  27. package/dist/ui.cjs +23 -9
  28. package/dist/ui.d.cts +20 -3
  29. package/dist/ui.d.ts +20 -3
  30. package/dist/ui.js +2 -2
  31. package/dist/vela.global.js +285 -127
  32. package/dist/vela.global.min.js +59 -49
  33. package/dist/widget.cjs +389 -161
  34. package/dist/widget.d.cts +7 -7
  35. package/dist/widget.d.ts +7 -7
  36. package/dist/widget.js +5 -5
  37. package/dist/workspace.cjs +389 -161
  38. package/dist/workspace.d.cts +40 -8
  39. package/dist/workspace.d.ts +40 -8
  40. package/dist/workspace.js +4 -4
  41. package/package.json +21 -1
@@ -105,10 +105,18 @@ var Vela = (function (exports) {
105
105
  this.records = /* @__PURE__ */ new Map();
106
106
  this.counter = 0;
107
107
  }
108
- /** Allocate a unique, stable per-instance id. */
109
- nextId(prefix = "ind") {
110
- this.counter += 1;
111
- return `${prefix}-${this.counter}`;
108
+ /**
109
+ * Mint a unique per-instance id. Host-supplied ids share the namespace, so a minted
110
+ * id skips anything already recorded — and anything `taken` reports live elsewhere
111
+ * (a handle the orchestrator holds outside the records, e.g. a fail-soft native).
112
+ */
113
+ nextId(prefix = "ind", taken = () => false) {
114
+ let id;
115
+ do {
116
+ this.counter += 1;
117
+ id = `${prefix}-${this.counter}`;
118
+ } while (this.records.has(id) || taken(id));
119
+ return id;
112
120
  }
113
121
  add(record) {
114
122
  this.records.set(record.id, record);
@@ -8653,7 +8661,7 @@ var Vela = (function (exports) {
8653
8661
  this.engines.set(language, engine);
8654
8662
  }
8655
8663
  addIndicator(source, options = {}) {
8656
- const id = this.registry.nextId();
8664
+ const id = this.claimIndicatorId(options.id);
8657
8665
  const title = options.title ?? "Indicator";
8658
8666
  const handle = new IndicatorHandleImpl(id, title, this, source);
8659
8667
  this.handles.set(id, handle);
@@ -8675,7 +8683,7 @@ var Vela = (function (exports) {
8675
8683
  const existing = this.registry.all().find((r) => r.native?.type === type);
8676
8684
  if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
8677
8685
  }
8678
- const id = this.registry.nextId("native");
8686
+ const id = this.registry.nextId("native", (candidate) => this.handles.has(candidate));
8679
8687
  const title = descriptor?.title ?? type;
8680
8688
  const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
8681
8689
  this.handles.set(id, handle);
@@ -8953,6 +8961,24 @@ var Vela = (function (exports) {
8953
8961
  this.events.clear();
8954
8962
  }
8955
8963
  // ── internals ───────────────────────────────────────────────
8964
+ /**
8965
+ * The id a new script indicator runs under: the host's when supplied, else a minted
8966
+ * one. A host id is opaque but must be a non-empty string, and it must not be live
8967
+ * on this chart — a duplicate is a programming error surfaced synchronously, never
8968
+ * renamed behind the caller's back (the whole point of supplying one is that
8969
+ * `handle.id` equals what was passed). "Live" reads both the records and the handle
8970
+ * map: a fail-soft handle never enters the registry but still owns its id.
8971
+ */
8972
+ claimIndicatorId(requested) {
8973
+ if (requested === void 0) return this.registry.nextId("ind", (candidate) => this.handles.has(candidate));
8974
+ if (typeof requested !== "string" || requested.length === 0) {
8975
+ throw new TypeError("[vela] addIndicator: `id` must be a non-empty string");
8976
+ }
8977
+ if (this.handles.has(requested) || this.registry.get(requested)) {
8978
+ throw new Error(`[vela] addIndicator: indicator id "${requested}" is already live on this chart`);
8979
+ }
8980
+ return requested;
8981
+ }
8956
8982
  async startIndicator(id, source, options, handle) {
8957
8983
  try {
8958
8984
  await this.readyPromise;
@@ -20535,7 +20561,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
20535
20561
  recents.unshift(hex6);
20536
20562
  if (recents.length > 10) recents.length = 10;
20537
20563
  }
20538
- function buildColorPicker(color, theme, onChange) {
20564
+ function buildColorPicker(color, theme, onChange, options = {}) {
20565
+ const commit = options.commit ?? "live";
20539
20566
  const parsed = splitColor(color);
20540
20567
  let curHex = parsed.hex6;
20541
20568
  let curAlpha = parsed.alpha;
@@ -20618,7 +20645,12 @@ ${overlayScrollbarCss(".vela-dialog *")}
20618
20645
  const r = track.getBoundingClientRect();
20619
20646
  curAlpha = Math.max(0, Math.min(1, (clientX - r.left) / r.width));
20620
20647
  paintOpacity();
20621
- emit();
20648
+ if (commit === "live") emit();
20649
+ };
20650
+ const endDrag = () => {
20651
+ if (!dragging) return;
20652
+ dragging = false;
20653
+ if (commit === "release") emit();
20622
20654
  };
20623
20655
  track.addEventListener("pointerdown", (e) => {
20624
20656
  e.stopPropagation();
@@ -20629,7 +20661,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
20629
20661
  track.addEventListener("pointermove", (e) => {
20630
20662
  if (dragging) onDrag(e.clientX);
20631
20663
  });
20632
- track.addEventListener("pointerup", () => dragging = false);
20664
+ track.addEventListener("pointerup", endDrag);
20665
+ track.addEventListener("pointercancel", endDrag);
20633
20666
  function emit() {
20634
20667
  onChange(combineColor(curHex, curAlpha));
20635
20668
  }
@@ -20652,7 +20685,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
20652
20685
  return root;
20653
20686
  }
20654
20687
  function colorField(theme, getVal, onVal, opts) {
20655
- 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;
20656
20689
  }
20657
20690
  var ColorField = class {
20658
20691
  constructor(opts) {
@@ -20661,6 +20694,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
20661
20694
  this.getVal = opts.getVal;
20662
20695
  this.onVal = opts.onVal;
20663
20696
  this.popoverOpts = opts.popover;
20697
+ this.commit = opts.commit;
20664
20698
  const trigger = document.createElement("button");
20665
20699
  trigger.type = "button";
20666
20700
  if (opts.id) trigger.id = opts.id;
@@ -20700,10 +20734,15 @@ ${overlayScrollbarCss(".vela-dialog *")}
20700
20734
  boundary: this.popoverOpts?.boundary,
20701
20735
  zIndex: this.popoverOpts?.zIndex,
20702
20736
  className: "vela-color-field-pop",
20703
- content: buildColorPicker(this.getVal(), this.theme, (val) => {
20704
- this.onVal(val);
20705
- this.paint();
20706
- })
20737
+ content: buildColorPicker(
20738
+ this.getVal(),
20739
+ this.theme,
20740
+ (val) => {
20741
+ this.onVal(val);
20742
+ this.paint();
20743
+ },
20744
+ { commit: this.commit }
20745
+ )
20707
20746
  });
20708
20747
  pop.show();
20709
20748
  }
@@ -21184,7 +21223,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
21184
21223
  const el2 = colorField(desc.theme, desc.get, desc.onChange, {
21185
21224
  shape: "circle",
21186
21225
  id: desc.id,
21187
- popover: desc.popover
21226
+ popover: desc.popover,
21227
+ commit: desc.commit
21188
21228
  });
21189
21229
  if (desc.title) el2.title = desc.title;
21190
21230
  return { el: el2 };
@@ -21562,7 +21602,10 @@ ${overlayScrollbarCss(".vela-dialog *")}
21562
21602
  id,
21563
21603
  theme: this.host.theme(),
21564
21604
  get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
21565
- 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"
21566
21609
  }).el;
21567
21610
  }
21568
21611
  if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
@@ -21809,10 +21852,6 @@ ${overlayScrollbarCss(".vela-dialog *")}
21809
21852
  }
21810
21853
  /** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
21811
21854
  openCalendar(anchor, current, onPick) {
21812
- const parsed = parseIsoDate(current);
21813
- let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
21814
- let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
21815
- const selected = parsed ? isoDate(parsed) : current;
21816
21855
  const pop = new Popover({
21817
21856
  trigger: anchor,
21818
21857
  theme: this.host.theme(),
@@ -21824,80 +21863,10 @@ ${overlayScrollbarCss(".vela-dialog *")}
21824
21863
  this.calendarPop = null;
21825
21864
  this.calendarAnchor = null;
21826
21865
  },
21827
- content: (el) => {
21828
- const title = document.createElement("div");
21829
- title.className = "vela-ind-cal-title";
21830
- const prev3 = document.createElement("button");
21831
- prev3.type = "button";
21832
- prev3.className = "vela-ind-cal-nav";
21833
- prev3.setAttribute("aria-label", "Previous month");
21834
- prev3.innerHTML = iconAt("chevron-left", 14);
21835
- const next2 = document.createElement("button");
21836
- next2.type = "button";
21837
- next2.className = "vela-ind-cal-nav";
21838
- next2.setAttribute("aria-label", "Next month");
21839
- next2.innerHTML = iconAt("chevron-right", 14);
21840
- const head = document.createElement("div");
21841
- head.className = "vela-ind-cal-head";
21842
- head.append(prev3, title, next2);
21843
- const week = document.createElement("div");
21844
- week.className = "vela-ind-cal-week";
21845
- for (const d of WEEKDAY_LABELS) {
21846
- const cell = document.createElement("span");
21847
- cell.textContent = d;
21848
- week.appendChild(cell);
21849
- }
21850
- const grid = document.createElement("div");
21851
- grid.className = "vela-ind-cal-grid";
21852
- const paint = () => {
21853
- title.textContent = `${MONTH_LABELS[month]} ${year}`;
21854
- grid.replaceChildren();
21855
- const first2 = new Date(year, month, 1);
21856
- const startPad = first2.getDay();
21857
- const days = new Date(year, month + 1, 0).getDate();
21858
- const today = isoDate(/* @__PURE__ */ new Date());
21859
- for (let i = 0; i < startPad; i++) {
21860
- const blank = document.createElement("span");
21861
- blank.className = "vela-ind-cal-blank";
21862
- grid.appendChild(blank);
21863
- }
21864
- for (let day = 1; day <= days; day++) {
21865
- const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
21866
- const b = document.createElement("button");
21867
- b.type = "button";
21868
- b.className = "vela-ind-cal-day";
21869
- b.textContent = String(day);
21870
- if (iso === selected) b.dataset.checked = "1";
21871
- if (iso === today) b.dataset.today = "1";
21872
- b.addEventListener("click", (e) => {
21873
- e.stopPropagation();
21874
- pop.hide();
21875
- onPick(iso);
21876
- });
21877
- grid.appendChild(b);
21878
- }
21879
- };
21880
- prev3.addEventListener("click", (e) => {
21881
- e.stopPropagation();
21882
- month -= 1;
21883
- if (month < 0) {
21884
- month = 11;
21885
- year -= 1;
21886
- }
21887
- paint();
21888
- });
21889
- next2.addEventListener("click", (e) => {
21890
- e.stopPropagation();
21891
- month += 1;
21892
- if (month > 11) {
21893
- month = 0;
21894
- year += 1;
21895
- }
21896
- paint();
21897
- });
21898
- paint();
21899
- el.append(head, week, grid);
21900
- }
21866
+ content: (el) => fillCalendar(el, current, (iso) => {
21867
+ pop.hide();
21868
+ onPick(iso);
21869
+ })
21901
21870
  });
21902
21871
  this.calendarPop = pop;
21903
21872
  this.calendarAnchor = anchor;
@@ -22006,7 +21975,7 @@ ${overlayScrollbarCss(".vela-dialog *")}
22006
21975
  var CALENDAR_SVG = iconAt("calendar", 14);
22007
21976
  var CLOCK_SVG = iconAt("clock", 14);
22008
21977
  var DIALOG_STYLE_ID2 = "vela-ind-dialog-styles";
22009
- var DIALOG_STYLE_REV = "29";
21978
+ var DIALOG_STYLE_REV = "30";
22010
21979
  var LEGEND_ICON_PX = 16;
22011
21980
  function ensureDialogStyles() {
22012
21981
  if (typeof document === "undefined") return;
@@ -22022,8 +21991,12 @@ ${overlayScrollbarCss(".vela-dialog *")}
22022
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;}
22023
21992
  .vela-ind-combo-chevron:hover{opacity:0.9;}
22024
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;}
22025
21995
  .vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
22026
- .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;}
22027
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;}
22028
22001
  .vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
22029
22002
  .vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
@@ -22034,6 +22007,12 @@ ${overlayScrollbarCss(".vela-dialog *")}
22034
22007
  .vela-ind-cal-day:hover{background:var(--vela-hover);}
22035
22008
  .vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
22036
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;}
22037
22016
  ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22038
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;}
22039
22018
  .vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
@@ -22068,6 +22047,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22068
22047
  return { value: v, label: v };
22069
22048
  });
22070
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"];
22071
22051
  var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
22072
22052
  function normalizeDateInput(raw) {
22073
22053
  const t = raw.trim();
@@ -22089,6 +22069,151 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
22089
22069
  function isoDate(d) {
22090
22070
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
22091
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
+ }
22092
22217
  function normalizeTimeInput(raw) {
22093
22218
  const t = raw.trim();
22094
22219
  const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
@@ -23370,10 +23495,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23370
23495
  var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
23371
23496
  var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
23372
23497
  var EXTENDED_SHADE = POSTMARKET_SHADE;
23498
+ var DEFAULT_MARGINS = { top: 10, bottom: 10, right: 10 };
23373
23499
  function defaultChartStyle() {
23374
23500
  return {
23375
23501
  chartTypes: {},
23376
23502
  fontSize: 11,
23503
+ margins: { ...DEFAULT_MARGINS },
23377
23504
  gridVert: { visible: true, color: null },
23378
23505
  gridHorz: { visible: true, color: null },
23379
23506
  borderColor: null,
@@ -23497,6 +23624,12 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23497
23624
  function clampSpacing(v) {
23498
23625
  return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
23499
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
+ }
23500
23633
  function factoryResetConfig(factory, priceStyle = factory.series.style) {
23501
23634
  const bag = {};
23502
23635
  for (const t of chartTypes()) {
@@ -23545,6 +23678,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23545
23678
  const ps = asObject(p.priceScale);
23546
23679
  const anim = asObject(p.animations);
23547
23680
  const panes = asObject(p.panes);
23681
+ const margins = asObject(p.margins);
23548
23682
  const trades = asObject(p.trades);
23549
23683
  const ts = asObject(p.timeScale);
23550
23684
  const marks = asObject(p.marks);
@@ -23604,6 +23738,11 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23604
23738
  panes: {
23605
23739
  separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
23606
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
+ },
23607
23746
  trades: {
23608
23747
  visible: isBool(trades.visible) ? trades.visible : base.trades.visible,
23609
23748
  labels: isBool(trades.labels) ? trades.labels : base.trades.labels,
@@ -28974,6 +29113,10 @@ void main() {
28974
29113
  "canvas.grid",
28975
29114
  "canvas.grid.vertical",
28976
29115
  "canvas.grid.horizontal",
29116
+ "canvas.margins",
29117
+ "canvas.margins.top",
29118
+ "canvas.margins.bottom",
29119
+ "canvas.margins.right",
28977
29120
  "canvas.theme"
28978
29121
  ];
28979
29122
  function settingsIdCatalog(hostSections, markGroups = []) {
@@ -29504,6 +29647,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
29504
29647
  body.append(sid(this.toggleRow("Horizontal", config.grid.horzLines.visible, (v) => this.emit({ grid: { horzLines: { visible: v } } }), [
29505
29648
  this.swatch(config.grid.horzLines.color, (v) => this.emit({ grid: { horzLines: { color: v } } }))
29506
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"));
29507
29654
  if (this.themeControl) {
29508
29655
  const tc = this.themeControl;
29509
29656
  body.append(sid(this.sectionTitle("Theme"), "canvas.theme"));
@@ -30075,23 +30222,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
30075
30222
  this.hintTips.push(dispose);
30076
30223
  return el;
30077
30224
  }
30078
- numberRow(label, value, min2, max2, step, onChange) {
30079
- return fieldRow({
30080
- label,
30081
- labelSize: "sm",
30082
- className: "vela-sd-row",
30083
- control: buildFieldControl({
30084
- kind: "number",
30085
- value,
30086
- min: min2,
30087
- max: max2,
30088
- step,
30089
- fill: false,
30090
- commit: "live",
30091
- steppers: true,
30092
- onChange
30093
- }).el
30094
- });
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);
30095
30235
  }
30096
30236
  /** A dropdown whose option values differ from their display labels. */
30097
30237
  selectRowLabeled(label, value, options, onChange) {
@@ -35610,9 +35750,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35610
35750
  }
35611
35751
 
35612
35752
  // src/renderers/native/core/autoscale.ts
35613
- var MARGIN_TOP = 2 / 7;
35614
- var MARGIN_BOTTOM = 1 / 7;
35615
- 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) {
35616
35754
  let min2 = Infinity;
35617
35755
  let max2 = -Infinity;
35618
35756
  const consider = (v) => {
@@ -35647,14 +35785,17 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35647
35785
  const pad = Math.abs(min2) * 0.1 || 1;
35648
35786
  return { min: min2 - pad, max: max2 + pad, log: log && min2 - pad > 0 };
35649
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;
35650
35791
  if (log && min2 > 0) {
35651
35792
  const lmin = Math.log(min2);
35652
35793
  const lmax = Math.log(max2);
35653
35794
  const lspan = lmax - lmin;
35654
- 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 };
35655
35796
  }
35656
35797
  const span = max2 - min2;
35657
- return { min: min2 - span * MARGIN_BOTTOM, max: max2 + span * MARGIN_TOP };
35798
+ return { min: min2 - span * below, max: max2 + span * above };
35658
35799
  }
35659
35800
  function considerSeries(s, i0, i1, off, consider) {
35660
35801
  if (!seriesInScale(s)) return;
@@ -37481,6 +37622,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37481
37622
  intro: this.intro.style !== false
37482
37623
  },
37483
37624
  panes: { separatorColor: s.separatorColor ?? t.borderColor },
37625
+ margins: { ...s.margins },
37484
37626
  trades: {
37485
37627
  visible: this.scene.tradeMarkers.visible,
37486
37628
  labels: this.scene.tradeMarkers.labels,
@@ -37593,6 +37735,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37593
37735
  this.animAutoscale.toggle(next2.animations.autoscale);
37594
37736
  this.intro = { style: next2.animations.intro ? this.introOnStyle : false, duration: this.intro.duration || INTRO_DURATION_DEFAULT_MS };
37595
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 };
37596
37742
  this.scene.tradeMarkers = {
37597
37743
  visible: next2.trades.visible,
37598
37744
  labels: next2.trades.labels,
@@ -37836,7 +37982,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37836
37982
  /** Glide the view back to the most recent bars, keeping the current zoom (barSpacing). */
37837
37983
  scrollToRealtime() {
37838
37984
  if (this.coords.barCount === 0) return;
37839
- this.glideRightOffset(ZOOM_OUT_MARGIN_BARS);
37985
+ this.glideRightOffset(this.scene.style.margins.right);
37840
37986
  }
37841
37987
  /** Ease rightOffset to `target` at constant zoom (see animTick's scroll glide);
37842
37988
  * instant when the scroll glide is off. Shared by scroll-to-latest and panBy. */
@@ -39778,6 +39924,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39778
39924
  const pricePane = panes.find((p) => p.kind === "price") ?? null;
39779
39925
  this.chrome.prepare(this.scene, this.coords, this.theme);
39780
39926
  const animating = this.animator.active;
39927
+ const margins = this.scene.style.margins;
39781
39928
  for (const pane of panes) {
39782
39929
  if (pane.manualScale) {
39783
39930
  pane.scaleTarget = pane.manualScale;
@@ -39793,7 +39940,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39793
39940
  if (or2) dr = dr ? { min: Math.min(dr.min, or2.min), max: Math.max(dr.max, or2.max) } : or2;
39794
39941
  }
39795
39942
  const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels) || !this.paneHasMeasurableContent(masterModels, dr));
39796
- 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);
39797
39944
  pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
39798
39945
  pane.axisFormat = void 0;
39799
39946
  pane.axisBands = void 0;
@@ -39804,7 +39951,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39804
39951
  pane.axisFormat = "volume";
39805
39952
  }
39806
39953
  } else if (this.layerNativesOwnPane(pane, masterModels)) {
39807
- 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);
39808
39955
  pane.percentBaseline = this.bars[i0]?.close ?? 0;
39809
39956
  if (masterModels.every((m) => m.paneAxis != null)) {
39810
39957
  pane.axisFormat = "none";
@@ -39833,7 +39980,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39833
39980
  continue;
39834
39981
  }
39835
39982
  const mdr = this.chrome.paneDrawingsRange([model], this.scene, false, vr);
39836
- 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);
39837
39984
  if (!animating || !sl.initialized) {
39838
39985
  sl.scale = { ...sl.scaleTarget };
39839
39986
  sl.initialized = true;
@@ -39874,13 +40021,13 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39874
40021
  for (const pane of this.scene.panes.values()) pane.manualScale = null;
39875
40022
  for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
39876
40023
  const visibleBars = Math.min(n, 200);
39877
- const rightOffset = 6;
40024
+ const rightOffset = this.scene.style.margins.right;
39878
40025
  const v = this.clampViewport(w / ((visibleBars + rightOffset) * this.coords.spacingScale), rightOffset);
39879
40026
  this.coords.setViewport(v);
39880
40027
  this.targetBarSpacing = v.barSpacing;
39881
40028
  }
39882
40029
  /** Re-frame after a series replacement (a symbol/timeframe switch): keep the user's
39883
- * 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.
39884
40031
  * `clampViewport`'s fit-all-bars floor deliberately does NOT apply — a progressive
39885
40032
  * head may still be backfilling toward the previous depth, and raising the spacing
39886
40033
  * to its temporary bar count would lose the zoom this exists to keep. */
@@ -39889,7 +40036,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39889
40036
  this.panVelocity = 0;
39890
40037
  for (const pane of this.scene.panes.values()) pane.manualScale = null;
39891
40038
  for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
39892
- 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 };
39893
40040
  this.coords.setViewport(v);
39894
40041
  this.targetBarSpacing = v.barSpacing;
39895
40042
  }
@@ -43003,6 +43150,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
43003
43150
  }
43004
43151
 
43005
43152
  // src/Vela.ts
43153
+ var asError = (err) => err instanceof Error ? err : new Error(String(err));
43006
43154
  var Vela = class {
43007
43155
  constructor(container, options = {}, deps = {}) {
43008
43156
  registerBuiltinChartTypes();
@@ -43126,7 +43274,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
43126
43274
  * fetch from — the same relationship `script:run` has to `context:changed`.
43127
43275
  */
43128
43276
  runScript(source, options) {
43129
- const handle = this.addIndicator(source, options);
43277
+ let handle;
43278
+ try {
43279
+ handle = this.addIndicator(source, options);
43280
+ } catch (err) {
43281
+ return Promise.resolve({ ok: false, run: null, error: asError(err), onUpdate: () => () => void 0, remove: () => void 0 });
43282
+ }
43130
43283
  const updates = /* @__PURE__ */ new Set();
43131
43284
  const drop = () => {
43132
43285
  try {
@@ -43170,7 +43323,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
43170
43323
  });
43171
43324
  }
43172
43325
  runIndicator(source, options) {
43173
- const handle = this.addIndicator(source, options);
43326
+ let handle;
43327
+ try {
43328
+ handle = this.addIndicator(source, options);
43329
+ } catch (err) {
43330
+ return Promise.resolve({ ok: false, handle: null, error: asError(err), context: null });
43331
+ }
43174
43332
  return new Promise((resolve) => {
43175
43333
  const offReady = handle.on("ready", () => {
43176
43334
  offReady();