@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
@@ -1,5 +1,5 @@
1
1
  import { deserializeDrawing, chartTypes, rendererLayers, isLineLikeSeries, seriesShownOn, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, nativeInstanceChannel, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, DEFAULT_DRAWING_COLOR, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, magnifierTimeframeLabel, lineSegmentIntersection, MAGNIFIER_TIMEFRAME_OPTIONS, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, seriesInScale, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS, stableSeriesId } from './chunk-BFA32GOU.js';
2
- import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, CALLOUT_STYLE_ID, CALLOUT_CSS, Popover, blendOver, splitColor, CalloutBubble, Menu, TextArea, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-BKHSQ4YM.js';
2
+ import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, CALLOUT_STYLE_ID, CALLOUT_CSS, Popover, blendOver, splitColor, CalloutBubble, Menu, TextArea, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-WTWGEB7K.js';
3
3
  import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BULLISH, CATEGORICAL, SERIES_LINE, BEARISH, isDarkColor, INFO, injectStyles, CHIP_PLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, iconMarkup, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, SLATE, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-CAFCLMPF.js';
4
4
 
5
5
  // src/core/events/EventBus.ts
@@ -1845,9 +1845,22 @@ var TIMEZONES = [
1845
1845
  { value: "Pacific/Apia", label: "Apia" },
1846
1846
  { value: "Pacific/Kiritimati", label: "Kiritimati" }
1847
1847
  ];
1848
+ var EXCHANGE_TIMEZONE = "exchange";
1849
+ function isExchangeTimezone(zone) {
1850
+ return zone === EXCHANGE_TIMEZONE;
1851
+ }
1852
+ function resolveTimezone(zone, exchangeZone) {
1853
+ if (!isExchangeTimezone(zone)) return zone;
1854
+ return exchangeZone && exchangeZone !== "" ? exchangeZone : "Etc/UTC";
1855
+ }
1848
1856
  function normalizeTimezone(zone) {
1849
1857
  return zone === "UTC" || zone === "Etc/UTC" || zone === "Etc/GMT" ? "Etc/UTC" : zone;
1850
1858
  }
1859
+ function timezoneMenuRows(current) {
1860
+ const active = normalizeTimezone(current);
1861
+ const [utc, ...zones] = TIMEZONES.map((t) => ({ value: t.value, label: tzMenuLabel(t.value, t.label), checked: t.value === active }));
1862
+ return [utc, { value: EXCHANGE_TIMEZONE, label: "Exchange", checked: isExchangeTimezone(current) }, ...zones];
1863
+ }
1851
1864
  function tzOffset(zone, date = /* @__PURE__ */ new Date()) {
1852
1865
  try {
1853
1866
  const parts = new Intl.DateTimeFormat("en-US", { timeZone: zone, timeZoneName: "shortOffset" }).formatToParts(date);
@@ -3503,7 +3516,10 @@ var IndicatorInputsDialog = class {
3503
3516
  id,
3504
3517
  theme: this.host.theme(),
3505
3518
  get: () => String(bagOf(row, inp)[inp.key] ?? inp.defval),
3506
- onChange: (v) => emit(v)
3519
+ onChange: (v) => emit(v),
3520
+ // An input change re-executes the script over its whole history: commit the
3521
+ // opacity drag once on release, not once per pointer move.
3522
+ commit: "release"
3507
3523
  }).el;
3508
3524
  }
3509
3525
  if (inp.type === "symbol") return this.buildSymbol(id, String(current), emit);
@@ -3750,10 +3766,6 @@ var IndicatorInputsDialog = class {
3750
3766
  }
3751
3767
  /** Open a themed month calendar under `anchor` — same surface + shadow as the choice list. */
3752
3768
  openCalendar(anchor, current, onPick) {
3753
- const parsed = parseIsoDate(current);
3754
- let year = parsed?.getFullYear() ?? (/* @__PURE__ */ new Date()).getFullYear();
3755
- let month = parsed?.getMonth() ?? (/* @__PURE__ */ new Date()).getMonth();
3756
- const selected = parsed ? isoDate(parsed) : current;
3757
3769
  const pop = new Popover({
3758
3770
  trigger: anchor,
3759
3771
  theme: this.host.theme(),
@@ -3765,80 +3777,10 @@ var IndicatorInputsDialog = class {
3765
3777
  this.calendarPop = null;
3766
3778
  this.calendarAnchor = null;
3767
3779
  },
3768
- content: (el) => {
3769
- const title = document.createElement("div");
3770
- title.className = "vela-ind-cal-title";
3771
- const prev2 = document.createElement("button");
3772
- prev2.type = "button";
3773
- prev2.className = "vela-ind-cal-nav";
3774
- prev2.setAttribute("aria-label", "Previous month");
3775
- prev2.innerHTML = iconAt("chevron-left", 14);
3776
- const next = document.createElement("button");
3777
- next.type = "button";
3778
- next.className = "vela-ind-cal-nav";
3779
- next.setAttribute("aria-label", "Next month");
3780
- next.innerHTML = iconAt("chevron-right", 14);
3781
- const head = document.createElement("div");
3782
- head.className = "vela-ind-cal-head";
3783
- head.append(prev2, title, next);
3784
- const week = document.createElement("div");
3785
- week.className = "vela-ind-cal-week";
3786
- for (const d of WEEKDAY_LABELS) {
3787
- const cell = document.createElement("span");
3788
- cell.textContent = d;
3789
- week.appendChild(cell);
3790
- }
3791
- const grid = document.createElement("div");
3792
- grid.className = "vela-ind-cal-grid";
3793
- const paint = () => {
3794
- title.textContent = `${MONTH_LABELS[month]} ${year}`;
3795
- grid.replaceChildren();
3796
- const first = new Date(year, month, 1);
3797
- const startPad = first.getDay();
3798
- const days = new Date(year, month + 1, 0).getDate();
3799
- const today = isoDate(/* @__PURE__ */ new Date());
3800
- for (let i = 0; i < startPad; i++) {
3801
- const blank = document.createElement("span");
3802
- blank.className = "vela-ind-cal-blank";
3803
- grid.appendChild(blank);
3804
- }
3805
- for (let day = 1; day <= days; day++) {
3806
- const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
3807
- const b = document.createElement("button");
3808
- b.type = "button";
3809
- b.className = "vela-ind-cal-day";
3810
- b.textContent = String(day);
3811
- if (iso === selected) b.dataset.checked = "1";
3812
- if (iso === today) b.dataset.today = "1";
3813
- b.addEventListener("click", (e) => {
3814
- e.stopPropagation();
3815
- pop.hide();
3816
- onPick(iso);
3817
- });
3818
- grid.appendChild(b);
3819
- }
3820
- };
3821
- prev2.addEventListener("click", (e) => {
3822
- e.stopPropagation();
3823
- month -= 1;
3824
- if (month < 0) {
3825
- month = 11;
3826
- year -= 1;
3827
- }
3828
- paint();
3829
- });
3830
- next.addEventListener("click", (e) => {
3831
- e.stopPropagation();
3832
- month += 1;
3833
- if (month > 11) {
3834
- month = 0;
3835
- year += 1;
3836
- }
3837
- paint();
3838
- });
3839
- paint();
3840
- el.append(head, week, grid);
3841
- }
3780
+ content: (el) => fillCalendar(el, current, (iso) => {
3781
+ pop.hide();
3782
+ onPick(iso);
3783
+ })
3842
3784
  });
3843
3785
  this.calendarPop = pop;
3844
3786
  this.calendarAnchor = anchor;
@@ -3947,7 +3889,7 @@ function groupInputs(inputs) {
3947
3889
  var CALENDAR_SVG = iconAt("calendar", 14);
3948
3890
  var CLOCK_SVG = iconAt("clock", 14);
3949
3891
  var DIALOG_STYLE_ID = "vela-ind-dialog-styles";
3950
- var DIALOG_STYLE_REV = "29";
3892
+ var DIALOG_STYLE_REV = "30";
3951
3893
  var LEGEND_ICON_PX = 16;
3952
3894
  function ensureDialogStyles() {
3953
3895
  if (typeof document === "undefined") return;
@@ -3963,8 +3905,12 @@ function ensureDialogStyles() {
3963
3905
  .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;}
3964
3906
  .vela-ind-combo-chevron:hover{opacity:0.9;}
3965
3907
  .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;}
3908
+ .vela-ind-cal [hidden]{display:none !important;}
3966
3909
  .vela-ind-cal-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;}
3967
- .vela-ind-cal-title{flex:1;text-align:center;font-weight:600;font-size:14px;color:var(--vela-fg-bright);}
3910
+ .vela-ind-cal-title{flex:1;display:flex;align-items:center;justify-content:center;gap:2px;min-width:0;}
3911
+ .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;}
3912
+ .vela-ind-cal-switch:not(:disabled):hover{background:var(--vela-hover);}
3913
+ .vela-ind-cal-switch:disabled{cursor:default;}
3968
3914
  .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;}
3969
3915
  .vela-ind-cal-nav:hover{background:var(--vela-hover);color:var(--vela-fg-bright);}
3970
3916
  .vela-ind-cal-week,.vela-ind-cal-grid{display:grid;grid-template-columns:repeat(7,28px);gap:2px;}
@@ -3975,6 +3921,12 @@ function ensureDialogStyles() {
3975
3921
  .vela-ind-cal-day:hover{background:var(--vela-hover);}
3976
3922
  .vela-ind-cal-day[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
3977
3923
  .vela-ind-cal-day[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
3924
+ .vela-ind-cal-cells{display:grid;grid-template-columns:repeat(3,1fr);gap:4px;width:calc(7 * 28px + 6 * 2px);}
3925
+ .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;}
3926
+ .vela-ind-cal-cell:hover{background:var(--vela-hover);}
3927
+ .vela-ind-cal-cell[data-checked]{background:var(--vela-hover-strong);color:var(--vela-fg-bright);}
3928
+ .vela-ind-cal-cell[data-today]:not([data-checked]){box-shadow:inset 0 0 0 1px var(--vela-border-strong);}
3929
+ .vela-ind-cal-cell[data-outside]{opacity:0.45;}
3978
3930
  ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
3979
3931
  .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;}
3980
3932
  .vela-ind-tab:not(.vela-ind-tab-active):hover{color:var(--vela-fg-bright);}
@@ -4009,6 +3961,7 @@ var TIME_OPTIONS = Array.from({ length: 48 }, (_, i) => {
4009
3961
  return { value: v, label: v };
4010
3962
  });
4011
3963
  var MONTH_LABELS = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
3964
+ var MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
4012
3965
  var WEEKDAY_LABELS = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
4013
3966
  function normalizeDateInput(raw) {
4014
3967
  const t = raw.trim();
@@ -4030,6 +3983,151 @@ function parseIsoDate(raw) {
4030
3983
  function isoDate(d) {
4031
3984
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
4032
3985
  }
3986
+ function fillCalendar(el, current, onPick) {
3987
+ const today = /* @__PURE__ */ new Date();
3988
+ const selected = parseIsoDate(current);
3989
+ let year = selected?.getFullYear() ?? today.getFullYear();
3990
+ let month = selected?.getMonth() ?? today.getMonth();
3991
+ let mode = "date";
3992
+ const prev2 = document.createElement("button");
3993
+ prev2.type = "button";
3994
+ prev2.className = "vela-ind-cal-nav";
3995
+ prev2.innerHTML = iconAt("chevron-left", 14);
3996
+ const next = document.createElement("button");
3997
+ next.type = "button";
3998
+ next.className = "vela-ind-cal-nav";
3999
+ next.innerHTML = iconAt("chevron-right", 14);
4000
+ const monthBtn = document.createElement("button");
4001
+ monthBtn.type = "button";
4002
+ monthBtn.className = "vela-ind-cal-switch";
4003
+ monthBtn.setAttribute("aria-label", "Choose month");
4004
+ const yearBtn = document.createElement("button");
4005
+ yearBtn.type = "button";
4006
+ yearBtn.className = "vela-ind-cal-switch";
4007
+ yearBtn.setAttribute("aria-label", "Choose year");
4008
+ const title = document.createElement("div");
4009
+ title.className = "vela-ind-cal-title";
4010
+ title.append(monthBtn, yearBtn);
4011
+ const head = document.createElement("div");
4012
+ head.className = "vela-ind-cal-head";
4013
+ head.append(prev2, title, next);
4014
+ const week = document.createElement("div");
4015
+ week.className = "vela-ind-cal-week";
4016
+ for (const d of WEEKDAY_LABELS) {
4017
+ const cell = document.createElement("span");
4018
+ cell.textContent = d;
4019
+ week.appendChild(cell);
4020
+ }
4021
+ const grid = document.createElement("div");
4022
+ const paint = () => {
4023
+ const decade = Math.floor(year / 10) * 10;
4024
+ monthBtn.hidden = mode !== "date";
4025
+ monthBtn.textContent = MONTH_LABELS[month] ?? "";
4026
+ yearBtn.textContent = mode === "year" ? `${decade}-${decade + 9}` : String(year);
4027
+ yearBtn.disabled = mode === "year";
4028
+ week.hidden = mode !== "date";
4029
+ prev2.setAttribute("aria-label", mode === "date" ? "Previous month" : mode === "month" ? "Previous year" : "Previous decade");
4030
+ next.setAttribute("aria-label", mode === "date" ? "Next month" : mode === "month" ? "Next year" : "Next decade");
4031
+ grid.className = mode === "date" ? "vela-ind-cal-grid" : "vela-ind-cal-cells";
4032
+ grid.replaceChildren();
4033
+ if (mode === "date") {
4034
+ const startPad = new Date(year, month, 1).getDay();
4035
+ const days = new Date(year, month + 1, 0).getDate();
4036
+ const todayIso = isoDate(today);
4037
+ const selectedIso = selected ? isoDate(selected) : "";
4038
+ for (let i = 0; i < startPad; i++) {
4039
+ const blank = document.createElement("span");
4040
+ blank.className = "vela-ind-cal-blank";
4041
+ grid.appendChild(blank);
4042
+ }
4043
+ for (let day = 1; day <= days; day++) {
4044
+ const iso = `${year}-${String(month + 1).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
4045
+ const b = document.createElement("button");
4046
+ b.type = "button";
4047
+ b.className = "vela-ind-cal-day";
4048
+ b.textContent = String(day);
4049
+ if (iso === selectedIso) b.dataset.checked = "1";
4050
+ if (iso === todayIso) b.dataset.today = "1";
4051
+ b.addEventListener("click", (e) => {
4052
+ e.stopPropagation();
4053
+ onPick(iso);
4054
+ });
4055
+ grid.appendChild(b);
4056
+ }
4057
+ return;
4058
+ }
4059
+ if (mode === "month") {
4060
+ for (let m = 0; m < 12; m++) {
4061
+ const b = document.createElement("button");
4062
+ b.type = "button";
4063
+ b.className = "vela-ind-cal-cell";
4064
+ b.textContent = MONTH_SHORT[m] ?? "";
4065
+ if (selected?.getFullYear() === year && selected.getMonth() === m) b.dataset.checked = "1";
4066
+ if (today.getFullYear() === year && today.getMonth() === m) b.dataset.today = "1";
4067
+ b.addEventListener("click", (e) => {
4068
+ e.stopPropagation();
4069
+ month = m;
4070
+ mode = "date";
4071
+ paint();
4072
+ });
4073
+ grid.appendChild(b);
4074
+ }
4075
+ return;
4076
+ }
4077
+ for (let y = decade - 1; y <= decade + 10; y++) {
4078
+ const b = document.createElement("button");
4079
+ b.type = "button";
4080
+ b.className = "vela-ind-cal-cell";
4081
+ b.textContent = String(y);
4082
+ if (y < decade || y > decade + 9) b.dataset.outside = "1";
4083
+ if (selected?.getFullYear() === y) b.dataset.checked = "1";
4084
+ if (today.getFullYear() === y) b.dataset.today = "1";
4085
+ b.addEventListener("click", (e) => {
4086
+ e.stopPropagation();
4087
+ year = y;
4088
+ mode = "month";
4089
+ paint();
4090
+ });
4091
+ grid.appendChild(b);
4092
+ }
4093
+ };
4094
+ const step = (dir) => {
4095
+ if (mode === "date") {
4096
+ month += dir;
4097
+ if (month < 0) {
4098
+ month = 11;
4099
+ year -= 1;
4100
+ }
4101
+ if (month > 11) {
4102
+ month = 0;
4103
+ year += 1;
4104
+ }
4105
+ } else {
4106
+ year += mode === "month" ? dir : dir * 10;
4107
+ }
4108
+ paint();
4109
+ };
4110
+ prev2.addEventListener("click", (e) => {
4111
+ e.stopPropagation();
4112
+ step(-1);
4113
+ });
4114
+ next.addEventListener("click", (e) => {
4115
+ e.stopPropagation();
4116
+ step(1);
4117
+ });
4118
+ monthBtn.addEventListener("click", (e) => {
4119
+ e.stopPropagation();
4120
+ mode = "month";
4121
+ paint();
4122
+ });
4123
+ yearBtn.addEventListener("click", (e) => {
4124
+ e.stopPropagation();
4125
+ mode = "year";
4126
+ paint();
4127
+ });
4128
+ paint();
4129
+ el.append(head, week, grid);
4130
+ }
4033
4131
  function normalizeTimeInput(raw) {
4034
4132
  const t = raw.trim();
4035
4133
  const m = /^(\d{1,2}):(\d{2})$/.exec(t) ?? /^(\d{2})(\d{2})$/.exec(t);
@@ -5311,10 +5409,12 @@ var BASELINE_LEVEL_DEFAULT = 50;
5311
5409
  var PREMARKET_SHADE = withAlpha(WARNING, 0.08);
5312
5410
  var POSTMARKET_SHADE = withAlpha(ACCENT, 0.08);
5313
5411
  var EXTENDED_SHADE = POSTMARKET_SHADE;
5412
+ var DEFAULT_MARGINS = { top: 10, bottom: 10, right: 10 };
5314
5413
  function defaultChartStyle() {
5315
5414
  return {
5316
5415
  chartTypes: {},
5317
5416
  fontSize: 11,
5417
+ margins: { ...DEFAULT_MARGINS },
5318
5418
  gridVert: { visible: true, color: null },
5319
5419
  gridHorz: { visible: true, color: null },
5320
5420
  borderColor: null,
@@ -5438,6 +5538,12 @@ function clampLevel(v) {
5438
5538
  function clampSpacing(v) {
5439
5539
  return v < 0.1 ? 0.1 : v > 10 ? 10 : v;
5440
5540
  }
5541
+ function clampMarginPct(v) {
5542
+ return v < 0 ? 0 : v > 40 ? 40 : v;
5543
+ }
5544
+ function clampMarginBars(v) {
5545
+ return Math.round(v < 0 ? 0 : v > 200 ? 200 : v);
5546
+ }
5441
5547
  function factoryResetConfig(factory, priceStyle = factory.series.style) {
5442
5548
  const bag = {};
5443
5549
  for (const t of chartTypes()) {
@@ -5486,6 +5592,7 @@ function mergeConfig(base, patch) {
5486
5592
  const ps = asObject(p.priceScale);
5487
5593
  const anim = asObject(p.animations);
5488
5594
  const panes = asObject(p.panes);
5595
+ const margins = asObject(p.margins);
5489
5596
  const trades = asObject(p.trades);
5490
5597
  const ts = asObject(p.timeScale);
5491
5598
  const marks = asObject(p.marks);
@@ -5545,6 +5652,11 @@ function mergeConfig(base, patch) {
5545
5652
  panes: {
5546
5653
  separatorColor: isColor(panes.separatorColor) ? panes.separatorColor : base.panes.separatorColor
5547
5654
  },
5655
+ margins: {
5656
+ top: isNum(margins.top) ? clampMarginPct(margins.top) : base.margins.top,
5657
+ bottom: isNum(margins.bottom) ? clampMarginPct(margins.bottom) : base.margins.bottom,
5658
+ right: isNum(margins.right) ? clampMarginBars(margins.right) : base.margins.right
5659
+ },
5548
5660
  trades: {
5549
5661
  visible: isBool(trades.visible) ? trades.visible : base.trades.visible,
5550
5662
  labels: isBool(trades.labels) ? trades.labels : base.trades.labels,
@@ -10874,6 +10986,10 @@ var BUILTIN_SETTINGS_IDS = [
10874
10986
  "canvas.grid",
10875
10987
  "canvas.grid.vertical",
10876
10988
  "canvas.grid.horizontal",
10989
+ "canvas.margins",
10990
+ "canvas.margins.top",
10991
+ "canvas.margins.bottom",
10992
+ "canvas.margins.right",
10877
10993
  "canvas.theme"
10878
10994
  ];
10879
10995
  function settingsIdCatalog(hostSections, markGroups = []) {
@@ -11404,6 +11520,10 @@ var SettingsDialog = class {
11404
11520
  body.append(sid(this.toggleRow("Horizontal", config.grid.horzLines.visible, (v) => this.emit({ grid: { horzLines: { visible: v } } }), [
11405
11521
  this.swatch(config.grid.horzLines.color, (v) => this.emit({ grid: { horzLines: { color: v } } }))
11406
11522
  ]), "canvas.grid.horizontal"));
11523
+ body.append(sid(this.sectionTitle("Margins"), "canvas.margins"));
11524
+ body.append(sid(this.numberRow("Top", config.margins.top, 0, 40, 1, (v) => this.emit({ margins: { top: v } }), "%"), "canvas.margins.top"));
11525
+ body.append(sid(this.numberRow("Bottom", config.margins.bottom, 0, 40, 1, (v) => this.emit({ margins: { bottom: v } }), "%"), "canvas.margins.bottom"));
11526
+ body.append(sid(this.numberRow("Right", config.margins.right, 0, 200, 1, (v) => this.emit({ margins: { right: v } }), "bars"), "canvas.margins.right"));
11407
11527
  if (this.themeControl) {
11408
11528
  const tc = this.themeControl;
11409
11529
  body.append(sid(this.sectionTitle("Theme"), "canvas.theme"));
@@ -11975,23 +12095,16 @@ var SettingsDialog = class {
11975
12095
  this.hintTips.push(dispose);
11976
12096
  return el;
11977
12097
  }
11978
- numberRow(label, value, min, max, step, onChange) {
11979
- return fieldRow({
11980
- label,
11981
- labelSize: "sm",
11982
- className: "vela-sd-row",
11983
- control: buildFieldControl({
11984
- kind: "number",
11985
- value,
11986
- min,
11987
- max,
11988
- step,
11989
- fill: false,
11990
- commit: "live",
11991
- steppers: true,
11992
- onChange
11993
- }).el
11994
- });
12098
+ /** `unit` (optional) trails the input as muted text — "%", "bars". */
12099
+ numberRow(label, value, min, max, step, onChange, unit) {
12100
+ const controls = [buildFieldControl({ kind: "number", value, min, max, step, fill: false, commit: "live", steppers: true, onChange }).el];
12101
+ if (unit) {
12102
+ const u = document.createElement("span");
12103
+ u.textContent = unit;
12104
+ u.style.cssText = "color:var(--vela-fg-muted);";
12105
+ controls.push(u);
12106
+ }
12107
+ return this.rowWith(label, controls);
11995
12108
  }
11996
12109
  /** A dropdown whose option values differ from their display labels. */
11997
12110
  selectRowLabeled(label, value, options, onChange) {
@@ -16877,9 +16990,7 @@ function createProjector(coords, paneOf, paneIdAtY, barsInRange, seriesInRange)
16877
16990
  }
16878
16991
 
16879
16992
  // src/renderers/native/core/autoscale.ts
16880
- var MARGIN_TOP = 2 / 7;
16881
- var MARGIN_BOTTOM = 1 / 7;
16882
- function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0) {
16993
+ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log = false, offsetOf = () => 0, margins = DEFAULT_MARGINS) {
16883
16994
  let min = Infinity;
16884
16995
  let max = -Infinity;
16885
16996
  const consider = (v) => {
@@ -16914,14 +17025,17 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
16914
17025
  const pad = Math.abs(min) * 0.1 || 1;
16915
17026
  return { min: min - pad, max: max + pad, log: log && min - pad > 0 };
16916
17027
  }
17028
+ const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
17029
+ const above = margins.top / 100 / content;
17030
+ const below = margins.bottom / 100 / content;
16917
17031
  if (log && min > 0) {
16918
17032
  const lmin = Math.log(min);
16919
17033
  const lmax = Math.log(max);
16920
17034
  const lspan = lmax - lmin;
16921
- return { min: Math.exp(lmin - lspan * MARGIN_BOTTOM), max: Math.exp(lmax + lspan * MARGIN_TOP), log: true };
17035
+ return { min: Math.exp(lmin - lspan * below), max: Math.exp(lmax + lspan * above), log: true };
16922
17036
  }
16923
17037
  const span = max - min;
16924
- return { min: min - span * MARGIN_BOTTOM, max: max + span * MARGIN_TOP };
17038
+ return { min: min - span * below, max: max + span * above };
16925
17039
  }
16926
17040
  function considerSeries(s, i0, i1, off, consider) {
16927
17041
  if (!seriesInScale(s)) return;
@@ -18722,6 +18836,7 @@ var NativeRenderer = class {
18722
18836
  intro: this.intro.style !== false
18723
18837
  },
18724
18838
  panes: { separatorColor: s.separatorColor ?? t.borderColor },
18839
+ margins: { ...s.margins },
18725
18840
  trades: {
18726
18841
  visible: this.scene.tradeMarkers.visible,
18727
18842
  labels: this.scene.tradeMarkers.labels,
@@ -18834,6 +18949,10 @@ var NativeRenderer = class {
18834
18949
  this.animAutoscale.toggle(next.animations.autoscale);
18835
18950
  this.intro = { style: next.animations.intro ? this.introOnStyle : false, duration: this.intro.duration || INTRO_DURATION_DEFAULT_MS };
18836
18951
  s.separatorColor = keepInherit(s.separatorColor, next.panes.separatorColor, prevTheme.borderColor);
18952
+ if (next.margins.right !== s.margins.right && this.coords.barCount > 0) {
18953
+ this.applyViewport({ barSpacing: this.coords.getViewport().barSpacing, rightOffset: next.margins.right });
18954
+ }
18955
+ s.margins = { ...next.margins };
18837
18956
  this.scene.tradeMarkers = {
18838
18957
  visible: next.trades.visible,
18839
18958
  labels: next.trades.labels,
@@ -19077,7 +19196,7 @@ var NativeRenderer = class {
19077
19196
  /** Glide the view back to the most recent bars, keeping the current zoom (barSpacing). */
19078
19197
  scrollToRealtime() {
19079
19198
  if (this.coords.barCount === 0) return;
19080
- this.glideRightOffset(ZOOM_OUT_MARGIN_BARS);
19199
+ this.glideRightOffset(this.scene.style.margins.right);
19081
19200
  }
19082
19201
  /** Ease rightOffset to `target` at constant zoom (see animTick's scroll glide);
19083
19202
  * instant when the scroll glide is off. Shared by scroll-to-latest and panBy. */
@@ -21019,6 +21138,7 @@ var NativeRenderer = class {
21019
21138
  const pricePane = panes.find((p) => p.kind === "price") ?? null;
21020
21139
  this.chrome.prepare(this.scene, this.coords, this.theme);
21021
21140
  const animating = this.animator.active;
21141
+ const margins = this.scene.style.margins;
21022
21142
  for (const pane of panes) {
21023
21143
  if (pane.manualScale) {
21024
21144
  pane.scaleTarget = pane.manualScale;
@@ -21034,7 +21154,7 @@ var NativeRenderer = class {
21034
21154
  if (or) dr = dr ? { min: Math.min(dr.min, or.min), max: Math.max(dr.max, or.max) } : or;
21035
21155
  }
21036
21156
  const includeCandles = pane.kind === "price" && (!this.scene.candlesHidden || this.priceLayersAnchoredToBars(masterModels) || !this.paneHasMeasurableContent(masterModels, dr));
21037
- pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
21157
+ pane.scaleTarget = computePaneScale(masterModels, this.bars, includeCandles, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
21038
21158
  pane.percentBaseline = pane.kind === "price" ? this.bars[i0]?.close ?? 0 : this.firstVisibleValue(masterModels, i0);
21039
21159
  pane.axisFormat = void 0;
21040
21160
  pane.axisBands = void 0;
@@ -21045,7 +21165,7 @@ var NativeRenderer = class {
21045
21165
  pane.axisFormat = "volume";
21046
21166
  }
21047
21167
  } else if (this.layerNativesOwnPane(pane, masterModels)) {
21048
- pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id));
21168
+ pane.scaleTarget = computePaneScale([], this.bars, true, i0, i1, dr, paneLogScale(this.scene, pane), (id) => this.scene.offsetOf(id), margins);
21049
21169
  pane.percentBaseline = this.bars[i0]?.close ?? 0;
21050
21170
  if (masterModels.every((m) => m.paneAxis != null)) {
21051
21171
  pane.axisFormat = "none";
@@ -21074,7 +21194,7 @@ var NativeRenderer = class {
21074
21194
  continue;
21075
21195
  }
21076
21196
  const mdr = this.chrome.paneDrawingsRange([model], this.scene, false, vr);
21077
- sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id));
21197
+ sl.scaleTarget = computePaneScale([model], this.bars, false, i0, i1, mdr, false, (id) => this.scene.offsetOf(id), margins);
21078
21198
  if (!animating || !sl.initialized) {
21079
21199
  sl.scale = { ...sl.scaleTarget };
21080
21200
  sl.initialized = true;
@@ -21115,13 +21235,13 @@ var NativeRenderer = class {
21115
21235
  for (const pane of this.scene.panes.values()) pane.manualScale = null;
21116
21236
  for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
21117
21237
  const visibleBars = Math.min(n, 200);
21118
- const rightOffset = 6;
21238
+ const rightOffset = this.scene.style.margins.right;
21119
21239
  const v = this.clampViewport(w / ((visibleBars + rightOffset) * this.coords.spacingScale), rightOffset);
21120
21240
  this.coords.setViewport(v);
21121
21241
  this.targetBarSpacing = v.barSpacing;
21122
21242
  }
21123
21243
  /** Re-frame after a series replacement (a symbol/timeframe switch): keep the user's
21124
- * zoom (bar spacing), re-anchor the newest bars at the default right offset.
21244
+ * zoom (bar spacing), re-anchor the newest bars at the configured right margin.
21125
21245
  * `clampViewport`'s fit-all-bars floor deliberately does NOT apply — a progressive
21126
21246
  * head may still be backfilling toward the previous depth, and raising the spacing
21127
21247
  * to its temporary bar count would lose the zoom this exists to keep. */
@@ -21130,7 +21250,7 @@ var NativeRenderer = class {
21130
21250
  this.panVelocity = 0;
21131
21251
  for (const pane of this.scene.panes.values()) pane.manualScale = null;
21132
21252
  for (const sl of this.scene.indicatorScales.values()) sl.manualScale = null;
21133
- const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset: defaultViewport().rightOffset };
21253
+ const v = { barSpacing: clampBarSpacing(this.coords.getViewport().barSpacing), rightOffset: this.scene.style.margins.right };
21134
21254
  this.coords.setViewport(v);
21135
21255
  this.targetBarSpacing = v.barSpacing;
21136
21256
  }
@@ -21707,10 +21827,18 @@ var IndicatorRegistry = class {
21707
21827
  this.records = /* @__PURE__ */ new Map();
21708
21828
  this.counter = 0;
21709
21829
  }
21710
- /** Allocate a unique, stable per-instance id. */
21711
- nextId(prefix = "ind") {
21712
- this.counter += 1;
21713
- return `${prefix}-${this.counter}`;
21830
+ /**
21831
+ * Mint a unique per-instance id. Host-supplied ids share the namespace, so a minted
21832
+ * id skips anything already recorded — and anything `taken` reports live elsewhere
21833
+ * (a handle the orchestrator holds outside the records, e.g. a fail-soft native).
21834
+ */
21835
+ nextId(prefix = "ind", taken = () => false) {
21836
+ let id;
21837
+ do {
21838
+ this.counter += 1;
21839
+ id = `${prefix}-${this.counter}`;
21840
+ } while (this.records.has(id) || taken(id));
21841
+ return id;
21714
21842
  }
21715
21843
  add(record) {
21716
21844
  this.records.set(record.id, record);
@@ -23030,7 +23158,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
23030
23158
  this.engines.set(language, engine);
23031
23159
  }
23032
23160
  addIndicator(source, options = {}) {
23033
- const id = this.registry.nextId();
23161
+ const id = this.claimIndicatorId(options.id);
23034
23162
  const title = options.title ?? "Indicator";
23035
23163
  const handle = new IndicatorHandleImpl(id, title, this, source);
23036
23164
  this.handles.set(id, handle);
@@ -23052,7 +23180,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
23052
23180
  const existing = this.registry.all().find((r) => r.native?.type === type);
23053
23181
  if (existing) return this.handles.get(existing.id) ?? new IndicatorHandleImpl(existing.id, existing.title, this, void 0, type);
23054
23182
  }
23055
- const id = this.registry.nextId("native");
23183
+ const id = this.registry.nextId("native", (candidate) => this.handles.has(candidate));
23056
23184
  const title = descriptor?.title ?? type;
23057
23185
  const handle = new IndicatorHandleImpl(id, title, this, void 0, type);
23058
23186
  this.handles.set(id, handle);
@@ -23330,6 +23458,24 @@ var EngineOrchestrator = class _EngineOrchestrator {
23330
23458
  this.events.clear();
23331
23459
  }
23332
23460
  // ── internals ───────────────────────────────────────────────
23461
+ /**
23462
+ * The id a new script indicator runs under: the host's when supplied, else a minted
23463
+ * one. A host id is opaque but must be a non-empty string, and it must not be live
23464
+ * on this chart — a duplicate is a programming error surfaced synchronously, never
23465
+ * renamed behind the caller's back (the whole point of supplying one is that
23466
+ * `handle.id` equals what was passed). "Live" reads both the records and the handle
23467
+ * map: a fail-soft handle never enters the registry but still owns its id.
23468
+ */
23469
+ claimIndicatorId(requested) {
23470
+ if (requested === void 0) return this.registry.nextId("ind", (candidate) => this.handles.has(candidate));
23471
+ if (typeof requested !== "string" || requested.length === 0) {
23472
+ throw new TypeError("[vela] addIndicator: `id` must be a non-empty string");
23473
+ }
23474
+ if (this.handles.has(requested) || this.registry.get(requested)) {
23475
+ throw new Error(`[vela] addIndicator: indicator id "${requested}" is already live on this chart`);
23476
+ }
23477
+ return requested;
23478
+ }
23333
23479
  async startIndicator(id, source, options, handle) {
23334
23480
  try {
23335
23481
  await this.readyPromise;
@@ -26535,6 +26681,7 @@ function registerClassicIndicators() {
26535
26681
  }
26536
26682
 
26537
26683
  // src/Vela.ts
26684
+ var asError = (err) => err instanceof Error ? err : new Error(String(err));
26538
26685
  var Vela = class {
26539
26686
  constructor(container, options = {}, deps = {}) {
26540
26687
  registerBuiltinChartTypes();
@@ -26658,7 +26805,12 @@ var Vela = class {
26658
26805
  * fetch from — the same relationship `script:run` has to `context:changed`.
26659
26806
  */
26660
26807
  runScript(source, options) {
26661
- const handle = this.addIndicator(source, options);
26808
+ let handle;
26809
+ try {
26810
+ handle = this.addIndicator(source, options);
26811
+ } catch (err) {
26812
+ return Promise.resolve({ ok: false, run: null, error: asError(err), onUpdate: () => () => void 0, remove: () => void 0 });
26813
+ }
26662
26814
  const updates = /* @__PURE__ */ new Set();
26663
26815
  const drop = () => {
26664
26816
  try {
@@ -26702,7 +26854,12 @@ var Vela = class {
26702
26854
  });
26703
26855
  }
26704
26856
  runIndicator(source, options) {
26705
- const handle = this.addIndicator(source, options);
26857
+ let handle;
26858
+ try {
26859
+ handle = this.addIndicator(source, options);
26860
+ } catch (err) {
26861
+ return Promise.resolve({ ok: false, handle: null, error: asError(err), context: null });
26862
+ }
26706
26863
  return new Promise((resolve) => {
26707
26864
  const offReady = handle.on("ready", () => {
26708
26865
  offReady();
@@ -26874,4 +27031,4 @@ function resolveElement(container) {
26874
27031
  return element;
26875
27032
  }
26876
27033
 
26877
- export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MarksControl, MultiProviderFeed, NativeRenderer, RendererControl, SecondClock, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isGroupRow, normalizeSession, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, sharedBarStore, timeframeToMs, tzButtonLabel, tzMenuLabel, tzOffset };
27034
+ export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MarksControl, MultiProviderFeed, NativeRenderer, RendererControl, SecondClock, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isGroupRow, normalizeSession, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, resolveTimezone, sharedBarStore, timeframeToMs, timezoneMenuRows, tzButtonLabel, tzMenuLabel, tzOffset };