@luxalgo/vela 0.8.1 → 0.8.3

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 (49) hide show
  1. package/README.md +1 -1
  2. package/dist/{DataProvider-DwFIoV4r.d.ts → DataProvider-oH78tYXL.d.ts} +1 -1
  3. package/dist/{DataProvider-BrlxntPN.d.cts → DataProvider-wbtJ3-Sa.d.cts} +1 -1
  4. package/dist/{chunk-DBDXKZUF.js → chunk-2IPXYM4B.js} +306 -177
  5. package/dist/{chunk-NELQJCGK.js → chunk-G7B7ZCBF.js} +167 -10
  6. package/dist/{chunk-ATGTCWHJ.js → chunk-HMHA733L.js} +473 -196
  7. package/dist/{chunk-EZ5FWVLA.js → chunk-KWI3YDBW.js} +343 -17
  8. package/dist/{chunk-LMBYEGUR.js → chunk-TO3GIK7Z.js} +71 -6
  9. package/dist/{chunk-BZQM2XO7.js → chunk-VHGACEHO.js} +62 -1
  10. package/dist/{contributions-tjNExo1o.d.ts → contributions-BhOBRenr.d.cts} +79 -12
  11. package/dist/{contributions-CtlptxR8.d.cts → contributions-HGqnNb_P.d.ts} +79 -12
  12. package/dist/index.cjs +733 -113
  13. package/dist/index.d.cts +34 -14
  14. package/dist/index.d.ts +34 -14
  15. package/dist/index.js +5 -5
  16. package/dist/{options-BnEPUuVV.d.ts → options-lMUwmRwh.d.cts} +69 -4
  17. package/dist/{options-BnEPUuVV.d.cts → options-lMUwmRwh.d.ts} +69 -4
  18. package/dist/{plugin-e2WUEtiI.d.ts → plugin-Cr2Pj8q8.d.ts} +4 -4
  19. package/dist/{plugin-BWxJRKzS.d.cts → plugin-Do5J5xuF.d.cts} +4 -4
  20. package/dist/plugin.cjs +38 -6
  21. package/dist/plugin.d.cts +5 -5
  22. package/dist/plugin.d.ts +5 -5
  23. package/dist/plugin.js +3 -3
  24. package/dist/providers/binance.d.cts +2 -2
  25. package/dist/providers/binance.d.ts +2 -2
  26. package/dist/providers/coinbase.d.cts +2 -2
  27. package/dist/providers/coinbase.d.ts +2 -2
  28. package/dist/providers/hyperliquid.d.cts +2 -2
  29. package/dist/providers/hyperliquid.d.ts +2 -2
  30. package/dist/{side-panel-HF0IAzwf.d.cts → side-panel-_2UK1gSm.d.cts} +58 -3
  31. package/dist/{side-panel-HF0IAzwf.d.ts → side-panel-_2UK1gSm.d.ts} +58 -3
  32. package/dist/{statusline-model-DFY7tuHj.d.cts → statusline-model-B-2xGlB_.d.cts} +3 -3
  33. package/dist/{statusline-model-zKXtGIjj.d.ts → statusline-model-CY1wzxHg.d.ts} +3 -3
  34. package/dist/ui.cjs +298 -13
  35. package/dist/ui.d.cts +132 -74
  36. package/dist/ui.d.ts +132 -74
  37. package/dist/ui.js +3 -3
  38. package/dist/vela.global.js +734 -114
  39. package/dist/vela.global.min.js +86 -52
  40. package/dist/widget.cjs +1227 -211
  41. package/dist/widget.d.cts +36 -12
  42. package/dist/widget.d.ts +36 -12
  43. package/dist/widget.js +7 -7
  44. package/dist/workspace.cjs +1226 -211
  45. package/dist/workspace.d.cts +47 -20
  46. package/dist/workspace.d.ts +47 -20
  47. package/dist/workspace.js +6 -6
  48. package/package.json +2 -2
  49. /package/dist/{chunk-JU4CWEBF.js → chunk-W5YPYZ2P.js} +0 -0
@@ -1,6 +1,6 @@
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-EZ5FWVLA.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, DatePicker, eventDismissedPopover, toggleSelectList, normalizeDateInput, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-NELQJCGK.js';
3
- import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, INFO, BULLISH, BEARISH, isDarkColor, injectStyles, SERIES_LINE, 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-BZQM2XO7.js';
1
+ import { deserializeDrawing, chartTypes, rendererLayers, isLineLikeSeries, seriesShownOn, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, normalizeTimezone, settingsRowVisible, normalizeSettingsRow, Magnifier, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, nativeInstanceChannel, drawingTypes, settingsRowValueKeys, TIMEZONES, tzMenuLabel, 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, FibRetracement, stableSeriesId } from './chunk-KWI3YDBW.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, DatePicker, eventDismissedPopover, toggleSelectList, normalizeDateInput, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-G7B7ZCBF.js';
3
+ import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, INFO, BULLISH, BEARISH, announceSurface, holdForExit, isDarkColor, injectStyles, SERIES_LINE, 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-VHGACEHO.js';
4
4
 
5
5
  // src/core/events/EventBus.ts
6
6
  var TypedEventBus = class {
@@ -1885,100 +1885,6 @@ var SecondClock = class _SecondClock {
1885
1885
  }
1886
1886
  };
1887
1887
 
1888
- // src/core/timezones.ts
1889
- var TIMEZONES = [
1890
- { value: "Etc/UTC", label: "UTC" },
1891
- { value: "Etc/GMT+12", label: "International Date Line West" },
1892
- { value: "Pacific/Pago_Pago", label: "Pago Pago" },
1893
- { value: "Pacific/Honolulu", label: "Honolulu" },
1894
- { value: "Pacific/Marquesas", label: "Marquesas Islands" },
1895
- { value: "America/Anchorage", label: "Anchorage" },
1896
- { value: "America/Los_Angeles", label: "Los Angeles" },
1897
- { value: "America/Phoenix", label: "Phoenix" },
1898
- { value: "America/Denver", label: "Denver" },
1899
- { value: "America/Chicago", label: "Chicago" },
1900
- { value: "America/Mexico_City", label: "Mexico City" },
1901
- { value: "America/New_York", label: "New York" },
1902
- { value: "America/Bogota", label: "Bogot\xE1" },
1903
- { value: "America/Caracas", label: "Caracas" },
1904
- { value: "America/Santiago", label: "Santiago" },
1905
- { value: "America/St_Johns", label: "St. John's" },
1906
- { value: "America/Sao_Paulo", label: "S\xE3o Paulo" },
1907
- { value: "America/Argentina/Buenos_Aires", label: "Buenos Aires" },
1908
- { value: "America/Noronha", label: "Fernando de Noronha" },
1909
- { value: "Atlantic/Azores", label: "Azores" },
1910
- { value: "Atlantic/Reykjavik", label: "Reykjavik" },
1911
- { value: "Europe/London", label: "London" },
1912
- { value: "Europe/Paris", label: "Paris" },
1913
- { value: "Europe/Berlin", label: "Berlin" },
1914
- { value: "Europe/Athens", label: "Athens" },
1915
- { value: "Africa/Cairo", label: "Cairo" },
1916
- { value: "Africa/Johannesburg", label: "Johannesburg" },
1917
- { value: "Europe/Moscow", label: "Moscow" },
1918
- { value: "Europe/Istanbul", label: "Istanbul" },
1919
- { value: "Asia/Tehran", label: "Tehran" },
1920
- { value: "Asia/Dubai", label: "Dubai" },
1921
- { value: "Asia/Kabul", label: "Kabul" },
1922
- { value: "Asia/Karachi", label: "Karachi" },
1923
- { value: "Asia/Kolkata", label: "Mumbai" },
1924
- { value: "Asia/Kathmandu", label: "Kathmandu" },
1925
- { value: "Asia/Dhaka", label: "Dhaka" },
1926
- { value: "Asia/Yangon", label: "Yangon" },
1927
- { value: "Asia/Bangkok", label: "Bangkok" },
1928
- { value: "Asia/Shanghai", label: "Shanghai" },
1929
- { value: "Asia/Hong_Kong", label: "Hong Kong" },
1930
- { value: "Asia/Singapore", label: "Singapore" },
1931
- { value: "Australia/Eucla", label: "Eucla" },
1932
- { value: "Asia/Tokyo", label: "Tokyo" },
1933
- { value: "Asia/Seoul", label: "Seoul" },
1934
- { value: "Australia/Adelaide", label: "Adelaide" },
1935
- { value: "Australia/Sydney", label: "Sydney" },
1936
- { value: "Australia/Lord_Howe", label: "Lord Howe Island" },
1937
- { value: "Pacific/Noumea", label: "Noum\xE9a" },
1938
- { value: "Pacific/Auckland", label: "Auckland" },
1939
- { value: "Pacific/Chatham", label: "Chatham Islands" },
1940
- { value: "Pacific/Apia", label: "Apia" },
1941
- { value: "Pacific/Kiritimati", label: "Kiritimati" }
1942
- ];
1943
- var EXCHANGE_TIMEZONE = "exchange";
1944
- function isExchangeTimezone(zone) {
1945
- return zone === EXCHANGE_TIMEZONE;
1946
- }
1947
- function resolveTimezone(zone, exchangeZone) {
1948
- if (!isExchangeTimezone(zone)) return zone;
1949
- return exchangeZone && exchangeZone !== "" ? exchangeZone : "Etc/UTC";
1950
- }
1951
- function normalizeTimezone(zone) {
1952
- return zone === "UTC" || zone === "Etc/UTC" || zone === "Etc/GMT" ? "Etc/UTC" : zone;
1953
- }
1954
- function timezoneMenuRows(current) {
1955
- const active = normalizeTimezone(current);
1956
- const [utc, ...zones] = TIMEZONES.map((t) => ({ value: t.value, label: tzMenuLabel(t.value, t.label), checked: t.value === active }));
1957
- return [utc, { value: EXCHANGE_TIMEZONE, label: "Exchange", checked: isExchangeTimezone(current) }, ...zones];
1958
- }
1959
- function tzOffset(zone, date = /* @__PURE__ */ new Date()) {
1960
- try {
1961
- const parts = new Intl.DateTimeFormat("en-US", { timeZone: zone, timeZoneName: "shortOffset" }).formatToParts(date);
1962
- const raw = parts.find((p) => p.type === "timeZoneName")?.value ?? "";
1963
- const m = raw.match(/GMT(?:(\+|-)(\d{1,2})(?::(\d{2}))?)?/);
1964
- if (!m || !m[1]) return "UTC";
1965
- const sign = m[1] === "-" ? "-" : "+";
1966
- const hrs = m[2] ?? "0";
1967
- const mins = m[3] ?? "";
1968
- return mins ? `UTC${sign}${hrs}:${mins}` : `UTC${sign}${hrs}`;
1969
- } catch {
1970
- return "UTC";
1971
- }
1972
- }
1973
- function tzMenuLabel(zone, location) {
1974
- if (normalizeTimezone(zone) === "Etc/UTC") return location;
1975
- return `(${tzOffset(zone)}) ${location}`;
1976
- }
1977
- function tzButtonLabel(zone) {
1978
- if (normalizeTimezone(zone) === "Etc/UTC") return "UTC";
1979
- return tzOffset(zone);
1980
- }
1981
-
1982
1888
  // src/core/drawings/effectiveColor.ts
1983
1889
  var VALID_FILL = VALID;
1984
1890
  var INVALID_FILL = INVALID;
@@ -2602,6 +2508,7 @@ function chromeHint(text, opts) {
2602
2508
  // src/renderers/native/drawings/DrawingToolbar.ts
2603
2509
  var TOOLBAR_WIDTH = 44;
2604
2510
  var TOOLBAR_COLLAPSED_WIDTH = 16;
2511
+ var FLYOUT_GAP = 3;
2605
2512
  var DrawingToolbar = class {
2606
2513
  constructor(host, theme, onArm, onMagnet = () => {
2607
2514
  }, onMeasure = () => {
@@ -2631,6 +2538,9 @@ var DrawingToolbar = class {
2631
2538
  // group id (or MAGNET_ID) whose flyout is open
2632
2539
  this.flyoutCell = null;
2633
2540
  // the cell the open flyout is anchored to
2541
+ /** Closed flyouts still playing their exit animation, by owner id — reopening the same
2542
+ * owner takes its element back instead of stacking a second one on it. */
2543
+ this.exitingFlyouts = /* @__PURE__ */ new Map();
2634
2544
  this.groupCells = /* @__PURE__ */ new Map();
2635
2545
  // the composite cell (hover/active bg + flyout anchor)
2636
2546
  this.groupIcons = /* @__PURE__ */ new Map();
@@ -2739,6 +2649,7 @@ var DrawingToolbar = class {
2739
2649
  }
2740
2650
  destroy() {
2741
2651
  this.closeFlyout();
2652
+ for (const { exit } of [...this.exitingFlyouts.values()]) exit.finish();
2742
2653
  for (const dispose of this.tipDisposers.splice(0)) dispose();
2743
2654
  this.root.remove();
2744
2655
  }
@@ -2966,16 +2877,22 @@ var DrawingToolbar = class {
2966
2877
  * outside-dismiss. Callers fill it with items. */
2967
2878
  beginFlyout(ownerId, cell) {
2968
2879
  const t = this.theme;
2969
- const fly = document.createElement("div");
2880
+ const back = this.exitingFlyouts.get(ownerId);
2881
+ if (back) {
2882
+ this.exitingFlyouts.delete(ownerId);
2883
+ back.exit.cancel();
2884
+ back.fly.replaceChildren();
2885
+ }
2886
+ const fly = back?.fly ?? document.createElement("div");
2970
2887
  fly.className = "vela-dtb-flyout";
2971
- fly.style.cssText = `position:absolute;z-index:23;display:flex;flex-direction:column;gap:2px;padding:4px;border-radius:0 8px 8px 0;background:var(--vela-surface-elev);border:1px solid ${this.borderColor};border-left:none;box-shadow:var(--vela-shadow);pointer-events:auto;overflow-y:auto;overscroll-behavior:contain;`;
2888
+ fly.style.cssText = `position:absolute;z-index:23;display:flex;flex-direction:column;gap:2px;padding:4px;border-radius:var(--vela-radius-md);background:var(--vela-surface);border:1px solid ${this.borderOverride ?? "var(--vela-border-strong)"};box-shadow:var(--vela-shadow);pointer-events:auto;overflow-y:auto;overscroll-behavior:contain;`;
2972
2889
  applyChromeTokens(fly, t);
2973
2890
  this.host.appendChild(fly);
2974
2891
  const r = cell.getBoundingClientRect();
2975
2892
  const rootR = this.root.getBoundingClientRect();
2976
2893
  const hostR = this.host.getBoundingClientRect();
2977
2894
  const top = r.top - hostR.top;
2978
- fly.style.left = `${rootR.right - hostR.left - 1}px`;
2895
+ fly.style.left = `${rootR.right - hostR.left + FLYOUT_GAP}px`;
2979
2896
  fly.style.top = `${top}px`;
2980
2897
  fly.style.maxHeight = `${Math.max(120, hostR.height - top - 8)}px`;
2981
2898
  this.flyout = fly;
@@ -2991,6 +2908,7 @@ var DrawingToolbar = class {
2991
2908
  if (!cell) return;
2992
2909
  const fly = this.beginFlyout(group.id, cell);
2993
2910
  const sections = group.sections ?? [{ label: "", tools: group.tools }];
2911
+ const badge = sections.some((s) => s.tools.some((tool) => !!tool.icon));
2994
2912
  for (let si = 0; si < sections.length; si++) {
2995
2913
  const section = sections[si];
2996
2914
  if (section.label) {
@@ -3001,6 +2919,7 @@ var DrawingToolbar = class {
3001
2919
  fly.appendChild(
3002
2920
  this.makeFlyoutItem({
3003
2921
  icon: tool.icon,
2922
+ badge,
3004
2923
  label: tool.label,
3005
2924
  selected: tool.type === this.active,
3006
2925
  shortcut: this.shortcuts.get(tool.type),
@@ -3013,6 +2932,7 @@ var DrawingToolbar = class {
3013
2932
  );
3014
2933
  }
3015
2934
  }
2935
+ announceSurface(fly, true, "menu", cell);
3016
2936
  }
3017
2937
  openMagnetFlyout() {
3018
2938
  const cell = this.magnetCell;
@@ -3036,6 +2956,7 @@ var DrawingToolbar = class {
3036
2956
  })
3037
2957
  );
3038
2958
  }
2959
+ announceSurface(fly, true, "menu", cell);
3039
2960
  }
3040
2961
  flyoutHeader(text) {
3041
2962
  const t = this.theme;
@@ -3049,21 +2970,24 @@ var DrawingToolbar = class {
3049
2970
  div.style.cssText = `height:1px;margin:4px 8px;background:${this.borderColor};`;
3050
2971
  return div;
3051
2972
  }
3052
- /** A flyout row, left to right: optional leading icon, label, a check when it's the selected
3053
- * entry, an optional shortcut hint, and — at the far right — the favorite star (tool rows).
3054
- * Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
2973
+ /** A flyout row, left to right: optional leading icon badge, label, a check when it's the
2974
+ * selected entry, an optional shortcut hint, and — at the far right — the favorite star (tool
2975
+ * rows). Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
3055
2976
  makeFlyoutItem(opts) {
3056
2977
  const t = this.theme;
3057
2978
  const item = document.createElement("button");
3058
2979
  item.type = "button";
3059
2980
  item.className = "vela-dtb-item";
3060
2981
  item.setAttribute("aria-label", opts.label);
3061
- item.style.cssText = `display:flex;align-items:center;gap:8px;padding:5px 10px 5px 8px;cursor:pointer;color:${t.textColor};border-radius:var(--vela-radius-sm);font:13px ${t.fontFamily};white-space:nowrap;min-width:148px;`;
3062
- if (opts.icon) {
3063
- const icon2 = document.createElement("span");
3064
- icon2.style.cssText = "width:18px;height:18px;display:flex;align-items:center;justify-content:center;flex:none;";
3065
- icon2.innerHTML = sizedIcon(opts.icon);
3066
- item.appendChild(icon2);
2982
+ const badged = opts.badge === true || !!opts.icon;
2983
+ const box = badged ? "gap:10px;padding:4px 10px 4px 6px;" : "gap:8px;padding:5px 10px 5px 8px;";
2984
+ item.style.cssText = `display:flex;align-items:center;${box}cursor:pointer;color:${t.textColor};border-radius:var(--vela-radius-sm);font:13px ${t.fontFamily};white-space:nowrap;min-width:148px;`;
2985
+ if (opts.selected) item.dataset.selected = "1";
2986
+ if (badged) {
2987
+ const badge = document.createElement("span");
2988
+ badge.className = "vela-dtb-badge";
2989
+ if (opts.icon) badge.innerHTML = opts.icon;
2990
+ item.appendChild(badge);
3067
2991
  }
3068
2992
  const label = document.createElement("span");
3069
2993
  label.textContent = opts.label;
@@ -3104,9 +3028,16 @@ var DrawingToolbar = class {
3104
3028
  }
3105
3029
  closeFlyout() {
3106
3030
  this.starEls.clear();
3107
- if (this.flyout) {
3108
- this.flyout.remove();
3031
+ const fly = this.flyout;
3032
+ if (fly) {
3109
3033
  this.flyout = null;
3034
+ announceSurface(fly, false, "menu", this.flyoutCell);
3035
+ const owner = this.flyoutOwnerId ?? "";
3036
+ const exit = holdForExit(fly, () => {
3037
+ if (this.exitingFlyouts.get(owner)?.fly === fly) this.exitingFlyouts.delete(owner);
3038
+ fly.remove();
3039
+ });
3040
+ if (exit) this.exitingFlyouts.set(owner, { fly, exit });
3110
3041
  this.flyoutCell?.classList.remove("vela-open");
3111
3042
  this.flyoutCell = null;
3112
3043
  this.flyoutOwnerId = null;
@@ -3190,6 +3121,11 @@ function ensureStyles() {
3190
3121
  .vela-dtb[data-collapsed='1'] .vela-dtb-collapse .vela-dtb-hit{width:14px;}
3191
3122
  .vela-dtb-item{background:transparent;border:none;transition:background var(--vela-dur-fast) ease;}
3192
3123
  .vela-dtb-item:hover{background:var(--vela-hover-strong);}
3124
+ .vela-dtb-badge{width:24px;height:24px;padding:3px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;flex:none;border-radius:var(--vela-radius-md);background:var(--vela-hover);border:1px solid var(--vela-border);color:var(--vela-fg-muted);transition:background var(--vela-dur-fast) ease,border-color var(--vela-dur-fast) ease,color var(--vela-dur-fast) ease;}
3125
+ .vela-dtb-badge:empty{background:transparent;border-color:transparent;}
3126
+ .vela-dtb-badge svg{width:100%;height:100%;}
3127
+ .vela-dtb-item:hover .vela-dtb-badge:not(:empty){background:var(--vela-active);color:var(--vela-fg-bright);}
3128
+ .vela-dtb-item[data-selected='1'] .vela-dtb-badge:not(:empty){background:color-mix(in srgb,var(--vela-fg-bright) 83%,var(--vela-surface));border-color:color-mix(in srgb,var(--vela-fg-bright) 40%,transparent);color:var(--vela-surface);}
3193
3129
  .vela-dtb-star{width:26px;height:22px;margin:-3px -5px -3px 0;padding:3px 5px;box-sizing:border-box;display:flex;align-items:center;justify-content:center;flex:none;opacity:0;color:inherit;border-radius:var(--vela-radius-sm);transition:opacity .1s ease,color .1s ease,background .1s ease;}
3194
3130
  .vela-dtb-star svg{width:16px;height:16px;}
3195
3131
  .vela-dtb-item:hover .vela-dtb-star{opacity:.55;}
@@ -4075,7 +4011,10 @@ var LEGEND_ICON_PX2 = 16;
4075
4011
  var LEGEND_CTL_PX = 18;
4076
4012
  var LEGEND_ROW_PAD_Y = 2;
4077
4013
  var LEGEND_ROW_PAD_X = 6;
4014
+ var LEGEND_LEFT_PX = 10;
4015
+ var LEGEND_SCALE_GAP_PX = 6;
4078
4016
  var LEGEND_TITLE_VALUES_GAP_PX = 6;
4017
+ var LEGEND_VALUES_GAP_PX = 5;
4079
4018
  var LEGEND_TITLE_STATUS_GAP_PX = 8;
4080
4019
  var LEGEND_ACTION_GAP_PX = 6;
4081
4020
  var LEGEND_CTL_CSS = `cursor:pointer;display:inline-flex;align-items:center;justify-content:center;border:none;line-height:0;padding:0;width:${LEGEND_CTL_PX}px;height:${LEGEND_CTL_PX}px;border-radius:3px;box-sizing:border-box;flex:none;`;
@@ -4361,14 +4300,15 @@ var InputsUI = class {
4361
4300
  row.valuesEl.replaceChildren();
4362
4301
  return;
4363
4302
  }
4364
- row.valuesEl.style.display = "inline-flex";
4303
+ row.valuesEl.style.display = "block";
4365
4304
  row.valuesEl.replaceChildren();
4366
- for (const v of row.plotValues) {
4305
+ row.plotValues.forEach((v, i) => {
4367
4306
  const span = document.createElement("span");
4368
4307
  span.textContent = v.value;
4369
4308
  span.style.color = v.color;
4309
+ if (i > 0) span.style.marginLeft = `${LEGEND_VALUES_GAP_PX}px`;
4370
4310
  row.valuesEl.appendChild(span);
4371
- }
4311
+ });
4372
4312
  }
4373
4313
  // ── legend move/merge (menu + drag) ─────────────────────────────────────
4374
4314
  /** Open the "Move to" menu for a row, anchored under its move button. */
@@ -4469,7 +4409,7 @@ var InputsUI = class {
4469
4409
  if (!lg) {
4470
4410
  lg = document.createElement("div");
4471
4411
  lg.dataset.velaPane = paneId;
4472
- lg.style.cssText = "position:absolute;left:10px;z-index:5;display:flex;flex-direction:column;align-items:flex-start;gap:0;pointer-events:none;font:12px -apple-system,Segoe UI,sans-serif;";
4412
+ lg.style.cssText = `position:absolute;left:${LEGEND_LEFT_PX}px;z-index:5;display:flex;flex-direction:column;align-items:flex-start;gap:0;pointer-events:none;font:12px -apple-system,Segoe UI,sans-serif;`;
4473
4413
  applyChromeTokens(lg, this.theme);
4474
4414
  this.positionLegend(lg, paneId);
4475
4415
  this.container.appendChild(lg);
@@ -4481,6 +4421,7 @@ var InputsUI = class {
4481
4421
  const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
4482
4422
  lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
4483
4423
  lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
4424
+ lg.style.maxWidth = `calc(100% - ${(bounds.rightAxis ?? 0) + LEGEND_LEFT_PX + LEGEND_SCALE_GAP_PX}px)`;
4484
4425
  const collapsed = this.paneCollapse.has(paneId);
4485
4426
  const masterId = this.paneCollapse.get(paneId) ?? null;
4486
4427
  for (const row of this.rows.values()) {
@@ -4572,7 +4513,7 @@ var InputsUI = class {
4572
4513
  }
4573
4514
  ensureDialogStyles();
4574
4515
  const el = document.createElement("div");
4575
- el.style.cssText = `pointer-events:auto;display:flex;align-items:center;background:${this.idleRowFill()};border-radius:4px;padding:${LEGEND_ROW_PAD_Y}px ${LEGEND_ROW_PAD_X}px;margin-left:-${LEGEND_ROW_PAD_X}px;min-height:${LEGEND_ROW_MIN_H}px;box-sizing:border-box;color:${this.theme.textColor};user-select:none;-webkit-user-select:none;`;
4516
+ el.style.cssText = `pointer-events:auto;display:flex;align-items:center;background:${this.idleRowFill()};border-radius:4px;padding:${LEGEND_ROW_PAD_Y}px ${LEGEND_ROW_PAD_X}px;margin-left:-${LEGEND_ROW_PAD_X}px;max-width:calc(100% + ${LEGEND_ROW_PAD_X}px);min-height:${LEGEND_ROW_MIN_H}px;box-sizing:border-box;color:${this.theme.textColor};user-select:none;-webkit-user-select:none;`;
4576
4517
  el.addEventListener("mouseleave", () => {
4577
4518
  if (this.selectedId !== id) this.setRowHighlighted(id, false);
4578
4519
  });
@@ -4600,7 +4541,7 @@ var InputsUI = class {
4600
4541
  const statusEl = document.createElement("span");
4601
4542
  statusEl.style.cssText = "display:none;box-sizing:border-box;flex:none;";
4602
4543
  const titleWrap = document.createElement("span");
4603
- titleWrap.style.cssText = "white-space:nowrap;";
4544
+ titleWrap.style.cssText = "white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis;";
4604
4545
  titleWrap.addEventListener("mouseenter", () => this.setRowHighlighted(id, true));
4605
4546
  const titleEl = document.createElement("span");
4606
4547
  titleEl.textContent = title;
@@ -4618,7 +4559,7 @@ var InputsUI = class {
4618
4559
  el.appendChild(calloutsEl);
4619
4560
  el.appendChild(statusEl);
4620
4561
  const valuesEl = document.createElement("span");
4621
- valuesEl.style.cssText = `display:none;align-items:center;gap:5px;margin-left:${LEGEND_TITLE_VALUES_GAP_PX}px;white-space:nowrap;font-variant-numeric:tabular-nums;`;
4562
+ valuesEl.style.cssText = `display:none;flex:1 1 0%;min-width:0;overflow:hidden;text-overflow:ellipsis;margin-left:${LEGEND_TITLE_VALUES_GAP_PX}px;white-space:nowrap;font-variant-numeric:tabular-nums;`;
4622
4563
  el.appendChild(valuesEl);
4623
4564
  const controlsEl = document.createElement("span");
4624
4565
  controlsEl.style.cssText = `display:none;align-items:center;gap:${LEGEND_ACTION_GAP_PX}px;flex:none;margin-left:${LEGEND_ACTION_GAP_PX}px;`;
@@ -5791,15 +5732,23 @@ var Batch = class {
5791
5732
  this.seg(x + w, y + h, x, y + h, lineWidth, c);
5792
5733
  this.seg(x, y + h, x, y, lineWidth, c);
5793
5734
  }
5794
- /** Filled circle (triangle fan). */
5735
+ /**
5736
+ * Filled circle (triangle fan), feathered like a line: the rim is padded by {@link AA_PAD} and
5737
+ * edgeDist runs from 0 at the center to the padded radius at the rim, with edgeHalf = r. The
5738
+ * distance grows linearly along each spoke, so the shader's line feather gives a round soft edge.
5739
+ */
5795
5740
  circle(cx, cy, r, c, segments = 16) {
5796
- let px = cx + r;
5741
+ const ext = r + AA_PAD;
5742
+ let px = cx + ext;
5797
5743
  let py = cy;
5744
+ this.ensure(segments * 3);
5798
5745
  for (let i = 1; i <= segments; i += 1) {
5799
5746
  const a = i / segments * Math.PI * 2;
5800
- const nx = cx + Math.cos(a) * r;
5801
- const ny = cy + Math.sin(a) * r;
5802
- this.tri(cx, cy, c, px, py, c, nx, ny, c);
5747
+ const nx = cx + Math.cos(a) * ext;
5748
+ const ny = cy + Math.sin(a) * ext;
5749
+ this.v(cx, cy, c, 0, r);
5750
+ this.v(px, py, c, ext, r);
5751
+ this.v(nx, ny, c, ext, r);
5803
5752
  px = nx;
5804
5753
  py = ny;
5805
5754
  }
@@ -5980,7 +5929,7 @@ var WebGL2Backend = class {
5980
5929
  }
5981
5930
  mount(canvas) {
5982
5931
  this.canvas = canvas;
5983
- this.gl = canvas.getContext("webgl2", { alpha: true, antialias: true, depth: false, stencil: false, premultipliedAlpha: true });
5932
+ this.gl = canvas.getContext("webgl2", { alpha: true, antialias: false, depth: false, stencil: false, premultipliedAlpha: true });
5984
5933
  if (!this.gl) return;
5985
5934
  if (!this.initGL()) {
5986
5935
  this.gl = null;
@@ -7205,6 +7154,11 @@ var InputController = class {
7205
7154
  /** When true (the default), the separators between stacked panes are draggable to
7206
7155
  * resize the panes above/below. When false, a press there is a normal data-area pan. */
7207
7156
  this.paneResize = true;
7157
+ /** When true (the default), the user drawings take part in pointer input: they claim a
7158
+ * press over a drawing or with a tool armed, and see hover moves. When false, every press
7159
+ * is a plain pan/click and hover leaves them alone; a drawing gesture already under way
7160
+ * still finishes. */
7161
+ this.drawings = true;
7208
7162
  this.el = null;
7209
7163
  this.dragging = false;
7210
7164
  this.moved = false;
@@ -7268,18 +7222,18 @@ var InputController = class {
7268
7222
  this.onModifier = (e) => {
7269
7223
  if (e.repeat || e.key !== "Shift" && e.key !== "Control" && e.key !== "Meta") return;
7270
7224
  if (Number.isNaN(this.cursorX)) return;
7271
- if (this.dragging && this.region !== "drawing") return;
7225
+ if (this.dragging ? this.region !== "drawing" : !this.drawings) return;
7272
7226
  this.deps.drawingsPointerMove?.(this.cursorX, this.cursorY, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
7273
7227
  };
7274
7228
  this.onDown = (e) => {
7275
7229
  if (e.button === 1) {
7276
7230
  const { x: x2, y: y2 } = this.local(e);
7277
- this.middleDeleted = this.deps.drawingsDeleteAt?.(x2, y2) ?? false;
7231
+ this.middleDeleted = this.drawings && (this.deps.drawingsDeleteAt?.(x2, y2) ?? false);
7278
7232
  if (this.middleDeleted) e.preventDefault();
7279
7233
  return;
7280
7234
  }
7281
7235
  if (e.button === 2) {
7282
- this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
7236
+ this.rightCancelled = this.drawings && (this.deps.drawingsCancelPlacement?.() ?? false);
7283
7237
  if (this.rightCancelled) e.preventDefault();
7284
7238
  return;
7285
7239
  }
@@ -7301,20 +7255,20 @@ var InputController = class {
7301
7255
  this.startY = y;
7302
7256
  this.prevPressDrawing = this.lastPressDrawing;
7303
7257
  this.lastPressDrawing = false;
7304
- if (this.deps.drawingsClaim?.(x, y)) {
7258
+ if (this.drawings && this.deps.drawingsClaim?.(x, y)) {
7305
7259
  this.region = "drawing";
7306
7260
  this.lastPressDrawing = true;
7307
7261
  this.deps.drawingsPointerDown?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
7308
7262
  this.capture(e.pointerId);
7309
7263
  return;
7310
7264
  }
7311
- if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
7265
+ if (this.drawings && e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
7312
7266
  this.region = "drawing";
7313
7267
  this.lastPressDrawing = true;
7314
7268
  this.capture(e.pointerId);
7315
7269
  return;
7316
7270
  }
7317
- if ((e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
7271
+ if (this.drawings && (e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
7318
7272
  this.region = "drawing";
7319
7273
  this.lastPressDrawing = true;
7320
7274
  this.capture(e.pointerId);
@@ -7391,7 +7345,7 @@ var InputController = class {
7391
7345
  const drawCursor = this.deps.drawingsCursor?.(x, y);
7392
7346
  const r = this.regionAt(x, y);
7393
7347
  this.el.style.cursor = drawCursor ?? (r === "price" ? "ns-resize" : r === "time" ? "ew-resize" : r === "separator" ? "row-resize" : "");
7394
- this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
7348
+ if (this.drawings) this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
7395
7349
  }
7396
7350
  if (e.pointerType !== "touch") this.deps.onPointerMove(x, y);
7397
7351
  };
@@ -7432,7 +7386,7 @@ var InputController = class {
7432
7386
  this.deps.drawingsPointerUp?.(x, y, this.snapMode(e));
7433
7387
  } else if (tapRelease && this.region === "data") {
7434
7388
  this.deps.onClick(x, y);
7435
- } else if (this.dragging && this.region === "data") {
7389
+ } else if (this.dragging && this.region === "data" && wasTouch) {
7436
7390
  const stale = e.timeStamp - this.lastT > FLING_STALE_MS;
7437
7391
  if (!stale && Math.abs(this.vx) > FLING_MIN_SPEED) {
7438
7392
  const pitch = this.deps.getCoords().pxPerBar();
@@ -7633,7 +7587,7 @@ var InputController = class {
7633
7587
  }
7634
7588
  /** Shared double-click / double-tap routing, by the region under the point. */
7635
7589
  doubleActivate(x, y) {
7636
- if (this.deps.drawingsDblClick?.(x, y)) return;
7590
+ if (this.drawings && this.deps.drawingsDblClick?.(x, y)) return;
7637
7591
  const region = this.regionAt(x, y);
7638
7592
  if (region === "price") this.deps.resetPriceScale(x, y);
7639
7593
  else if (region === "separator") this.deps.resetPaneSize(y);
@@ -9683,10 +9637,13 @@ function priceTicks(min, max, target = 6) {
9683
9637
  const norm = raw / mag;
9684
9638
  const step = (norm < 1.5 ? 1 : norm < 3 ? 2 : norm < 7 ? 5 : 10) * mag;
9685
9639
  const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);
9686
- const out = [];
9687
9640
  const start = Math.ceil(min / step) * step;
9688
- for (let v = start; v <= max + step * 1e-6; v += step) {
9689
- out.push(Number(v.toFixed(decimals)));
9641
+ if (!(step > 0) || start + step === start) return [];
9642
+ const count = Math.floor((max - start) / step + 1e-6) + 1;
9643
+ if (!(count > 0) || count > 1e3) return [];
9644
+ const out = [];
9645
+ for (let i = 0; i < count; i += 1) {
9646
+ out.push(Number((start + i * step).toFixed(decimals)));
9690
9647
  }
9691
9648
  return out;
9692
9649
  }
@@ -9884,6 +9841,100 @@ function calendarTicks(zFrom, zTo, months, offsetMs) {
9884
9841
  return out;
9885
9842
  }
9886
9843
 
9844
+ // src/renderers/native/chrome/priceAxisTicks.ts
9845
+ var PriceAxisTickSource = class {
9846
+ constructor() {
9847
+ this.fn = null;
9848
+ this.memo = /* @__PURE__ */ new WeakMap();
9849
+ this.warned = false;
9850
+ }
9851
+ /** The host function, or null for the built-in ticks. */
9852
+ get hook() {
9853
+ return this.fn;
9854
+ }
9855
+ /** Set (or clear) the host function. Drops every memoized result, so setting the same
9856
+ * function again recomputes — how a host refreshes ticks that depend on its own state. */
9857
+ setHook(fn) {
9858
+ this.fn = fn;
9859
+ this.memo = /* @__PURE__ */ new WeakMap();
9860
+ }
9861
+ /** The pane's master-scale ticks for this frame. An unscaled pane (`axisFormat: 'none'`)
9862
+ * gets none, and the host is never asked for it. */
9863
+ ticksFor(scene, pane, coords) {
9864
+ const format = pane.axisFormat;
9865
+ if (format === "none") return [];
9866
+ const pct = percentScaleFor(scene, pane);
9867
+ const height = pane.bounds.height;
9868
+ const fn = this.fn;
9869
+ if (!fn || !(height > 0)) return paneAxisTicks(pane.scale, height, pct, scene.priceMintick, format);
9870
+ const s = pane.scale;
9871
+ const m = this.memo.get(pane);
9872
+ const fontSize = scene.style.fontSize;
9873
+ if (m && m.kind === pane.kind && m.min === s.min && m.max === s.max && m.log === !!s.log && m.invert === !!s.invert && m.height === height && m.baseline === pct?.baseline && m.indexed === !!pct?.indexed && m.mintick === scene.priceMintick && m.format === format && m.fontSize === fontSize) {
9874
+ return m.ticks;
9875
+ }
9876
+ const defaults = paneAxisTicks(s, height, pct, scene.priceMintick, format);
9877
+ const ticks = this.callHook(fn, scene, pane, coords, defaults, pct?.baseline, pct ? pct.indexed ? "indexed" : "percent" : "price");
9878
+ this.memo.set(pane, {
9879
+ kind: pane.kind,
9880
+ min: s.min,
9881
+ max: s.max,
9882
+ log: !!s.log,
9883
+ invert: !!s.invert,
9884
+ height,
9885
+ baseline: pct?.baseline,
9886
+ indexed: !!pct?.indexed,
9887
+ mintick: scene.priceMintick,
9888
+ format,
9889
+ fontSize,
9890
+ ticks
9891
+ });
9892
+ return ticks;
9893
+ }
9894
+ callHook(fn, scene, pane, coords, defaults, baseline, mode) {
9895
+ const scale = { min: pane.scale.min, max: pane.scale.max, log: !!pane.scale.log, invert: !!pane.scale.invert };
9896
+ const bounds = { top: 0, height: pane.bounds.height };
9897
+ const ctx = {
9898
+ pane: { id: pane.id, kind: pane.kind },
9899
+ min: Math.min(scale.min, scale.max),
9900
+ max: Math.max(scale.min, scale.max),
9901
+ log: !!scale.log,
9902
+ height: bounds.height,
9903
+ mode,
9904
+ baseline,
9905
+ mintick: scene.priceMintick,
9906
+ fontSize: scene.style.fontSize,
9907
+ priceToY: (price) => coords.priceToY(price, scale, bounds),
9908
+ defaults
9909
+ };
9910
+ let out;
9911
+ try {
9912
+ out = fn(ctx);
9913
+ } catch (err) {
9914
+ this.warnOnce("threw", err);
9915
+ return defaults;
9916
+ }
9917
+ if (out == null) return defaults;
9918
+ if (!Array.isArray(out)) {
9919
+ this.warnOnce("returned a non-array", out);
9920
+ return defaults;
9921
+ }
9922
+ const ticks = [];
9923
+ for (const t of out) {
9924
+ if (!t || typeof t !== "object") continue;
9925
+ const { price, label, major } = t;
9926
+ if (typeof price !== "number" || !Number.isFinite(price) || typeof label !== "string") continue;
9927
+ ticks.push(typeof major === "boolean" ? { price, label, major } : { price, label });
9928
+ }
9929
+ return ticks;
9930
+ }
9931
+ warnOnce(what, detail) {
9932
+ if (this.warned) return;
9933
+ this.warned = true;
9934
+ console.warn(`[vela] the priceAxisTicks function ${what} \u2014 the default ticks are used instead.`, detail);
9935
+ }
9936
+ };
9937
+
9887
9938
  // src/renderers/native/chrome/countdown.ts
9888
9939
  function countdownText(barOpen, barMs, now2) {
9889
9940
  if (!(barMs > 0)) return null;
@@ -10236,8 +10287,11 @@ function standaloneSvg(markup, ink, px) {
10236
10287
  }
10237
10288
 
10238
10289
  // src/renderers/native/chrome/ChromeRenderer.ts
10290
+ var MINOR_LABEL_ALPHA = 0.5;
10239
10291
  var ChromeRenderer = class {
10240
- constructor() {
10292
+ /** `axisTicks` is shared with the backdrop layer, so price labels and gridlines agree. */
10293
+ constructor(axisTicks = new PriceAxisTickSource()) {
10294
+ this.axisTicks = axisTicks;
10241
10295
  this.canvas = null;
10242
10296
  this.ctx = null;
10243
10297
  // The color for axis tick labels — the host-passed surface text, set each frame in render().
@@ -10342,7 +10396,8 @@ var ChromeRenderer = class {
10342
10396
  this.drawTimeAxis(ctx, scene, coords, theme, dataW, dataH, fullH);
10343
10397
  this.drawMarkLane(ctx, scene, coords, theme, dataW, dataH);
10344
10398
  }
10345
- /** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge. */
10399
+ /** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge,
10400
+ * but clipped to the data area so a glyph on the edge bar never paints over the price scale. */
10346
10401
  drawMarkLane(ctx, scene, coords, theme, dataW, dataH) {
10347
10402
  if (!scene.marks.visible || scene.timelineMarks.length === 0) {
10348
10403
  this.markLayout = { glyphs: [], stacks: /* @__PURE__ */ new Map() };
@@ -10360,6 +10415,10 @@ var ChromeRenderer = class {
10360
10415
  expanded: scene.marksExpandedStack
10361
10416
  });
10362
10417
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
10418
+ ctx.save();
10419
+ ctx.beginPath();
10420
+ ctx.rect(0, 0, dataW, dataH);
10421
+ ctx.clip();
10363
10422
  paintMarkLane(ctx, this.markLayout, {
10364
10423
  axisY: dataH,
10365
10424
  background: theme.background,
@@ -10373,6 +10432,7 @@ var ChromeRenderer = class {
10373
10432
  flashKey: scene.marksFlash && scene.marksFlash.until > nowMs ? scene.marksFlash.key : null,
10374
10433
  nowMs
10375
10434
  });
10435
+ ctx.restore();
10376
10436
  }
10377
10437
  barTimes(scene) {
10378
10438
  if (this.barTimesSrc !== scene.bars || this.barTimesCache.length !== scene.bars.length) {
@@ -10429,13 +10489,19 @@ var ChromeRenderer = class {
10429
10489
  if (!scene.showAxisLabels) return;
10430
10490
  ctx.fillStyle = this.axisTextColor;
10431
10491
  ctx.textAlign = "left";
10492
+ const font = `${scene.style.fontSize}px ${theme.fontFamily}`;
10493
+ const majorFont = `600 ${font}`;
10494
+ const alpha = ctx.globalAlpha;
10432
10495
  for (const pane of panes) {
10433
10496
  if (pane.collapsed) continue;
10434
- const pct = percentScaleFor(scene, pane);
10435
- for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, scene.priceMintick, pane.axisFormat)) {
10497
+ for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
10436
10498
  const y = coords.priceToY(t.price, pane.scale, pane.bounds);
10437
10499
  if (y < pane.bounds.top + 6 || y > pane.bounds.top + pane.bounds.height - 4) continue;
10500
+ if (t.major === true) ctx.font = majorFont;
10501
+ else if (t.major === false) ctx.globalAlpha = alpha * MINOR_LABEL_ALPHA;
10438
10502
  ctx.fillText(t.label, dataW + 6, y);
10503
+ if (t.major === true) ctx.font = font;
10504
+ else if (t.major === false) ctx.globalAlpha = alpha;
10439
10505
  }
10440
10506
  if (pane.axisBands) {
10441
10507
  for (const b of pane.axisBands) {
@@ -10677,15 +10743,23 @@ var CrosshairRenderer = class {
10677
10743
  constructor() {
10678
10744
  this.canvas = null;
10679
10745
  this.ctx = null;
10746
+ this.shade = null;
10680
10747
  }
10681
10748
  mount(canvas) {
10682
10749
  this.canvas = canvas;
10683
10750
  this.ctx = canvas.getContext("2d");
10684
10751
  }
10752
+ /** The plot area the last frame veiled for `crosshairOverride.shadeRight` — from the
10753
+ * picked bar's right edge to the price scale, full plot height (the union when both the
10754
+ * local and a synced crosshair veil) — or null when no veil was painted. */
10755
+ get shadeRightArea() {
10756
+ return this.shade;
10757
+ }
10685
10758
  /** Clear the cursor canvas and (re)draw the crosshair lines + axis chips. The optional
10686
10759
  * `separatorHoverY` highlights the draggable pane separator under the cursor;
10687
10760
  * `external` is a SYNCED ghost crosshair (another chart's pointer, pixel-resolved). */
10688
10761
  render(scene, coords, theme, separatorHoverY = null, external = null) {
10762
+ this.shade = null;
10689
10763
  const ctx = this.ctx;
10690
10764
  const canvas = this.canvas;
10691
10765
  if (!ctx || !canvas) return;
@@ -10708,12 +10782,7 @@ var CrosshairRenderer = class {
10708
10782
  const x = crisp(coords.logicalToX(logical), ov?.width ?? cs.width);
10709
10783
  if (vertical && ov?.shadeRight) {
10710
10784
  const from = Math.max(0, Math.round(coords.logicalToX(logical + 0.5)));
10711
- if (from < dataW) {
10712
- ctx.fillStyle = ov.shadeRight.color;
10713
- ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
10714
- ctx.fillRect(from, 0, dataW - from, dataH);
10715
- ctx.globalAlpha = 1;
10716
- }
10785
+ if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
10717
10786
  }
10718
10787
  ctx.strokeStyle = ov?.color ?? cs.color ?? theme.textColor;
10719
10788
  ctx.lineWidth = ov?.width ?? cs.width;
@@ -10749,6 +10818,7 @@ var CrosshairRenderer = class {
10749
10818
  destroy() {
10750
10819
  this.canvas = null;
10751
10820
  this.ctx = null;
10821
+ this.shade = null;
10752
10822
  }
10753
10823
  /** The synced ghost: a dimmed vertical line at the bar the renderer resolved as
10754
10824
  * CONTAINING the foreign time (+ horizontal line when a comparable price came
@@ -10768,12 +10838,7 @@ var CrosshairRenderer = class {
10768
10838
  if (ov?.vertical === false) return;
10769
10839
  if (ov?.shadeRight) {
10770
10840
  const from = Math.max(0, Math.round(coords.logicalToX(Math.round(coords.xToLogical(ext.x)) + 0.5)));
10771
- if (from < dataW) {
10772
- ctx.fillStyle = ov.shadeRight.color;
10773
- ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
10774
- ctx.fillRect(from, 0, dataW - from, dataH);
10775
- ctx.globalAlpha = 1;
10776
- }
10841
+ if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
10777
10842
  }
10778
10843
  if (ext.line === false || x < 0 || x > dataW) return;
10779
10844
  ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
@@ -10809,6 +10874,15 @@ var CrosshairRenderer = class {
10809
10874
  this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
10810
10875
  ctx.globalAlpha = 1;
10811
10876
  }
10877
+ /** Paint the `shadeRight` veil from `from` to the price scale and record the veiled area. */
10878
+ paintShade(ctx, shade, from, dataW, dataH) {
10879
+ ctx.fillStyle = shade.color;
10880
+ ctx.globalAlpha = shade.opacity ?? 1;
10881
+ ctx.fillRect(from, 0, dataW - from, dataH);
10882
+ ctx.globalAlpha = 1;
10883
+ const x = this.shade ? Math.min(this.shade.x, from) : from;
10884
+ this.shade = { x, width: dataW - x, height: dataH };
10885
+ }
10812
10886
  /** A soft band + a brighter crisp center line over the hovered separator, so it reads as
10813
10887
  * a draggable handle (the cursor is already `row-resize`). Spans the full width (data +
10814
10888
  * scale gutter) to match the separator itself. Theme-derived (textColor). */
@@ -13846,6 +13920,12 @@ var DrawingInteraction = class {
13846
13920
  activeDragId() {
13847
13921
  return this.state.kind === "pressed" && this.state.moved ? this.state.id : null;
13848
13922
  }
13923
+ /** The store drawings a live drag is moving — the pressed one and its riders — or none
13924
+ * before the drag passes the slop. A Ctrl-drag moves copies, so its sources stay put. */
13925
+ movingIds() {
13926
+ if (this.state.kind !== "pressed" || !this.state.moved || this.state.clones) return /* @__PURE__ */ new Set();
13927
+ return /* @__PURE__ */ new Set([this.state.id, ...this.state.riders.map((r) => r.id)]);
13928
+ }
13849
13929
  /** The drawing under a press that has not been released yet (drag or click undecided), or null. */
13850
13930
  pressedId() {
13851
13931
  return this.state.kind === "pressed" ? this.state.id : null;
@@ -14220,15 +14300,16 @@ var DrawingSettingsDialog = class {
14220
14300
  }
14221
14301
  open(drawing, actions, kind) {
14222
14302
  this.close();
14223
- const snapshot = clonePlain(drawing.serialize());
14303
+ const live = actions.resolve() ?? drawing;
14304
+ const snapshot = clonePlain(live.serialize());
14224
14305
  const grid = fieldGrid({ variant: "inputs" });
14225
14306
  grid.style.padding = "16px 20px";
14226
14307
  grid.style.overflowY = "auto";
14227
14308
  grid.style.overflowX = "hidden";
14228
14309
  grid.style.flex = "1 1 auto";
14229
- if (kind === "position" && drawing instanceof PositionTool) this.buildPosition(grid, drawing, actions);
14230
- else if (kind === "frvp" && drawing instanceof FixedRangeVolumeProfile) this.buildFrvp(grid, drawing, actions);
14231
- else if (kind === "levels") this.buildLevels(grid, drawing, actions);
14310
+ if (kind === "position" && live instanceof PositionTool) this.buildPosition(grid, live, actions);
14311
+ else if (kind === "frvp" && live instanceof FixedRangeVolumeProfile) this.buildFrvp(grid, live, actions);
14312
+ else if (kind === "levels") this.buildLevels(grid, live, actions);
14232
14313
  else return;
14233
14314
  const ui = new Dialog({
14234
14315
  host: this.host,
@@ -14504,6 +14585,17 @@ var DrawingSettingsDialog = class {
14504
14585
  }
14505
14586
  }));
14506
14587
  }
14588
+ if (drawing instanceof FibRetracement) {
14589
+ const fib = drawing;
14590
+ grid.appendChild(fieldRow({
14591
+ label: "Reverse",
14592
+ bool: true,
14593
+ toggle: {
14594
+ checked: fib.reverse,
14595
+ onChange: (v) => actions.patch({ reverse: v })
14596
+ }
14597
+ }));
14598
+ }
14507
14599
  levels.forEach((lv, i) => {
14508
14600
  const row = document.createElement("div");
14509
14601
  row.className = "vela-field-span";
@@ -14864,7 +14956,7 @@ var DrawingSettingsPopup = class {
14864
14956
  this.reposition();
14865
14957
  return;
14866
14958
  }
14867
- const text = drawing.text;
14959
+ const text = (actions.resolve() ?? drawing).text;
14868
14960
  const panel = document.createElement("div");
14869
14961
  panel.style.cssText = `padding:6px;border-top:1px solid var(--vela-border);display:flex;flex-direction:column;gap:4px;`;
14870
14962
  const ta = new TextArea({
@@ -15657,6 +15749,8 @@ var UserDrawingController = class {
15657
15749
  this.drawings = [];
15658
15750
  /** Ids painted on an interleave layer this frame — the top canvas paints only their handles. */
15659
15751
  this.sliced = /* @__PURE__ */ new Set();
15752
+ /** Interleaved drawings mid-drag: painted on the top canvas, left out of their slice. */
15753
+ this.lifted = /* @__PURE__ */ new Set();
15660
15754
  /** Cached slice canvases, keyed `paneId|beforeZ`, reused across frames to avoid churn. */
15661
15755
  this.sliceCache = /* @__PURE__ */ new Map();
15662
15756
  /** Series boundaries per pane as of the last `prepareSlices` — lets a repaint between data
@@ -15744,7 +15838,7 @@ var UserDrawingController = class {
15744
15838
  selectedIds: () => this.selectedIds,
15745
15839
  emit: (i) => this.emit(i),
15746
15840
  changed: () => {
15747
- this.invalidateSlices();
15841
+ this.syncLift();
15748
15842
  this.render();
15749
15843
  },
15750
15844
  openSettings: (id, x, y) => this.openSettingsById(id, x, y),
@@ -16033,6 +16127,12 @@ var UserDrawingController = class {
16033
16127
  this.render();
16034
16128
  }
16035
16129
  }
16130
+ /** Drop the hover (its handles) — the pointer no longer reaches the drawings. */
16131
+ clearHover() {
16132
+ if (this.hoveredId == null) return;
16133
+ this.hoveredId = null;
16134
+ this.render();
16135
+ }
16036
16136
  pointerUp(x, y, snap = "off") {
16037
16137
  if (this.eraserMode) {
16038
16138
  this.erasing = false;
@@ -16370,6 +16470,20 @@ var UserDrawingController = class {
16370
16470
  /** A drawing that paints inside the series stack changed (content, not hover): its pixels
16371
16471
  * live in the backend composite, so this layer alone can't show the change — ask for a
16372
16472
  * data frame, which re-runs `prepareSlices` before the backend composites. */
16473
+ /** A drawing being dragged leaves its interleave slice for the top canvas, so each pointer
16474
+ * move repaints one 2D layer instead of re-rasterizing and re-uploading a plot-sized slice
16475
+ * texture through the data frame. The slices rebuild once as the drag starts and once as it
16476
+ * ends (release, cancel), when the lifted set changes. */
16477
+ syncLift() {
16478
+ const moving = this.interaction.movingIds();
16479
+ let same = moving.size === this.lifted.size;
16480
+ if (same) {
16481
+ for (const id of moving) if (!this.lifted.has(id)) same = false;
16482
+ }
16483
+ if (same) return;
16484
+ this.lifted = new Set(moving);
16485
+ this.invalidateSlices();
16486
+ }
16373
16487
  invalidateSlices() {
16374
16488
  if (this.sliced.size > 0 || this.drawings.some((d) => this.isInterleaved(d))) this.deps.requestDataPaint();
16375
16489
  }
@@ -16388,7 +16502,7 @@ var UserDrawingController = class {
16388
16502
  const theme = this.deps.theme();
16389
16503
  const buckets = /* @__PURE__ */ new Map();
16390
16504
  for (const d of this.drawings) {
16391
- if (!d.visible) continue;
16505
+ if (!d.visible || this.lifted.has(d.id)) continue;
16392
16506
  const beforeZ = sliceKeyFor(d.zIndex, this.lastBounds.get(d.paneId) ?? []);
16393
16507
  if (beforeZ === null) continue;
16394
16508
  const key = `${d.paneId}|${beforeZ}`;
@@ -16440,8 +16554,9 @@ var UserDrawingController = class {
16440
16554
  mutedLabel: edited instanceof TextLabel ? edited.id : null
16441
16555
  };
16442
16556
  this.painter.seriesLook = this.deps.seriesLook();
16443
- this.painter.paintAll(ctx, this.drawings.filter((d) => !this.isInterleaved(d)), proj, this.deps.theme(), targets);
16444
- this.painter.paintHighlights(ctx, this.drawings.filter((d) => this.isInterleaved(d)), proj, handleIdsFor(targets));
16557
+ const onTop = (d) => !this.isInterleaved(d) || this.lifted.has(d.id);
16558
+ this.painter.paintAll(ctx, this.drawings.filter(onTop), proj, this.deps.theme(), targets);
16559
+ this.painter.paintHighlights(ctx, this.drawings.filter((d) => !onTop(d)), proj, handleIdsFor(targets));
16445
16560
  const clones = this.interaction.dragClones();
16446
16561
  if (clones) this.painter.paintAll(ctx, clones, proj, this.deps.theme(), { selected: new Set(clones.map((c) => c.id)) });
16447
16562
  this.layoutTextEditor();
@@ -17008,9 +17123,10 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
17008
17123
  consider(drawings.max);
17009
17124
  }
17010
17125
  if (min === Infinity || max === -Infinity) return { min: 0, max: 1 };
17011
- if (min === max) {
17012
- const pad = Math.abs(min) * 0.1 || 1;
17013
- return { min: min - pad, max: max + pad, log: log && min - pad > 0 };
17126
+ if (max - min <= Math.max(Math.abs(min), Math.abs(max)) * 1e-12) {
17127
+ const mid = (min + max) / 2;
17128
+ const pad = Math.abs(mid) * 0.1 || 1;
17129
+ return { min: mid - pad, max: mid + pad, log: log && mid - pad > 0 };
17014
17130
  }
17015
17131
  const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
17016
17132
  const above = margins.top / 100 / content;
@@ -17514,7 +17630,9 @@ function bandEdgeSlot(logical) {
17514
17630
  return Math.ceil(logical) - 0.5;
17515
17631
  }
17516
17632
  var BackdropRenderer = class {
17517
- constructor() {
17633
+ /** `axisTicks` is shared with the chrome layer, so gridlines and price labels agree. */
17634
+ constructor(axisTicks = new PriceAxisTickSource()) {
17635
+ this.axisTicks = axisTicks;
17518
17636
  this.canvas = null;
17519
17637
  this.ctx = null;
17520
17638
  }
@@ -17591,9 +17709,8 @@ var BackdropRenderer = class {
17591
17709
  if (scene.showGrid && gridHorz.visible && !pane.collapsed) {
17592
17710
  ctx.globalAlpha = gridAlpha;
17593
17711
  ctx.strokeStyle = horzColor;
17594
- const pct = percentScaleFor(scene, pane);
17595
17712
  ctx.beginPath();
17596
- for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, void 0, pane.axisFormat)) {
17713
+ for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
17597
17714
  const y = Math.round(coords.priceToY(t.price, pane.scale, pane.bounds)) + 0.5;
17598
17715
  if (y < pane.bounds.top || y > pane.bounds.top + pane.bounds.height) continue;
17599
17716
  ctx.moveTo(0, y);
@@ -18183,8 +18300,16 @@ var NativeRenderer = class {
18183
18300
  this.toolbarGutter = 0;
18184
18301
  // px reserved on the left for the docked drawings toolbar (0 when hidden)
18185
18302
  this.mountContainer = null;
18303
+ /** The host-styleable twin of the `shadeRight` veil (`.vela-shade-right`), directly under
18304
+ * the cursor canvas; hidden whenever the crosshair layer paints no veil. */
18305
+ this.shadeRightEl = null;
18306
+ /** The veil geometry last written to {@link shadeRightEl} ('' = hidden) — the DOM is
18307
+ * touched only when it changes, never on a plain pointer move. */
18308
+ this.shadeRightSig = "";
18186
18309
  this.userDrawings = null;
18187
- this.backdropRenderer = new BackdropRenderer();
18310
+ /** One price-axis tick source for the gridlines (backdrop) and the labels (chrome). */
18311
+ this.priceAxisTicks = new PriceAxisTickSource();
18312
+ this.backdropRenderer = new BackdropRenderer(this.priceAxisTicks);
18188
18313
  this.volumeRenderer = new VolumeRenderer();
18189
18314
  /** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
18190
18315
  /**
@@ -18217,7 +18342,7 @@ var NativeRenderer = class {
18217
18342
  this.backendMode = "auto";
18218
18343
  this.glowAmount = 0;
18219
18344
  // WebGL2 neon-glow intensity (canvas2d ignores it)
18220
- this.chrome = new ChromeRenderer();
18345
+ this.chrome = new ChromeRenderer(this.priceAxisTicks);
18221
18346
  /** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
18222
18347
  this.indicatorSlices = new IndicatorDrawingSlices();
18223
18348
  /** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
@@ -18334,6 +18459,8 @@ var NativeRenderer = class {
18334
18459
  // pane just below it
18335
18460
  this.resizeSplitStart = null;
18336
18461
  // the two panes' shared span when the drag began
18462
+ /** The user drawings take part in pointer input (the runtime `drawingsInteractive` feature). */
18463
+ this.drawingsInteractive = true;
18337
18464
  this.hoverSeparatorY = null;
18338
18465
  // pixel y of the separator under the cursor (drives its hover highlight)
18339
18466
  // ── pane management (merge / reorder / collapse / maximize) ──
@@ -18389,8 +18516,9 @@ var NativeRenderer = class {
18389
18516
  this.toggleVisibleCbs = /* @__PURE__ */ new Set();
18390
18517
  this.moveIndicatorCbs = /* @__PURE__ */ new Set();
18391
18518
  this.priceStyleCbs = /* @__PURE__ */ new Set();
18519
+ this.priceStyleWillChangeCbs = /* @__PURE__ */ new Set();
18392
18520
  this.name = "native";
18393
- this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animScroll", "animAutoscale", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "marks", "indicatorTitles", "indicatorValues", "crosshairOverride"];
18521
+ this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "animScroll", "animAutoscale", "animLiveBar", "intro", "zoomAnchor", "axisDrag", "paneResize", "candleZOrder", "candleVisible", "seriesOrder", "highlights", "sessionZones", "gridlines", "axisLabels", "scaleMode", "invertScale", "paneScales", "autoScale", "timezone", "keyboard", "historyChords", "priceStyle", "priceBaseline", "baselinePrice", "settings", "attribution", "dialogHost", "tradeMarkers", "marks", "indicatorTitles", "indicatorValues", "crosshairOverride", "drawingsInteractive", "priceAxisTicks"];
18394
18522
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
18395
18523
  * so moving onto the button doesn't count as leaving). */
18396
18524
  this.onScrollProximityMove = (e) => {
@@ -18434,6 +18562,7 @@ var NativeRenderer = class {
18434
18562
  this.scene.priceStyle = opts.priceStyle;
18435
18563
  this.scene.basePainting = basePaintingOf(opts.priceStyle);
18436
18564
  this.scene.candleOverride = candleOverrideFor(opts.priceStyle, this.scene.style.chartTypes);
18565
+ if (typeof opts.priceAxisTicks === "function") this.priceAxisTicks.setHook(opts.priceAxisTicks);
18437
18566
  }
18438
18567
  this.theme = this.deriveTheme(DARK_THEME);
18439
18568
  }
@@ -18572,6 +18701,15 @@ var NativeRenderer = class {
18572
18701
  case "crosshairOverride":
18573
18702
  this.scene.crosshairOverride = sanitizeCrosshairOverride(value);
18574
18703
  break;
18704
+ case "drawingsInteractive":
18705
+ this.drawingsInteractive = value !== false;
18706
+ if (this.input) this.input.drawings = this.drawingsInteractive;
18707
+ if (!this.drawingsInteractive) this.userDrawings?.clearHover();
18708
+ return;
18709
+ // affects the next gesture only — clearHover repaints what it drops
18710
+ case "priceAxisTicks":
18711
+ this.priceAxisTicks.setHook(typeof value === "function" ? value : null);
18712
+ break;
18575
18713
  case "keyboard":
18576
18714
  this.setKeyboardEnabled(Boolean(value));
18577
18715
  return;
@@ -18687,6 +18825,10 @@ var NativeRenderer = class {
18687
18825
  return { visible: this.scene.marks.visible, groups: { ...this.scene.marks.groups } };
18688
18826
  case "crosshairOverride":
18689
18827
  return this.scene.crosshairOverride ? { ...this.scene.crosshairOverride } : null;
18828
+ case "drawingsInteractive":
18829
+ return this.drawingsInteractive;
18830
+ case "priceAxisTicks":
18831
+ return this.priceAxisTicks.hook;
18690
18832
  case "keyboard":
18691
18833
  return this.keyboardEnabled;
18692
18834
  case "historyChords":
@@ -19360,6 +19502,12 @@ var NativeRenderer = class {
19360
19502
  Object.assign(this.drawingsCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
19361
19503
  this.cursorCanvas = document.createElement("canvas");
19362
19504
  Object.assign(this.cursorCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
19505
+ this.shadeRightEl = document.createElement("div");
19506
+ this.shadeRightEl.className = "vela-shade-right";
19507
+ this.shadeRightEl.setAttribute("aria-hidden", "true");
19508
+ this.shadeRightEl.hidden = true;
19509
+ Object.assign(this.shadeRightEl.style, { position: "absolute", top: "0", left: "0", width: "0", height: "0", pointerEvents: "none", display: "none" });
19510
+ this.shadeRightSig = "";
19363
19511
  this.overlayRoot = document.createElement("div");
19364
19512
  Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
19365
19513
  this.plot = document.createElement("div");
@@ -19367,7 +19515,7 @@ var NativeRenderer = class {
19367
19515
  this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
19368
19516
  const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
19369
19517
  const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
19370
- this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
19518
+ this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.shadeRightEl, this.cursorCanvas, this.overlayRoot);
19371
19519
  this.layerOrderSig = "";
19372
19520
  this.wrapper.appendChild(this.plot);
19373
19521
  this.factoryConfig = this.getConfig();
@@ -19425,13 +19573,14 @@ var NativeRenderer = class {
19425
19573
  drawingsPointerDown: (x, y, snap, shift2, mod) => this.userDrawings?.pointerDown(x, y, snap, shift2, mod),
19426
19574
  drawingsPointerMove: (x, y, snap, shift2, mod) => this.userDrawings?.pointerMove(x, y, snap, shift2, mod),
19427
19575
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
19428
- drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
19576
+ drawingsCursor: (x, y) => (this.drawingsInteractive ? this.userDrawings?.cursorAt(x, y) : null) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
19429
19577
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
19430
19578
  drawingsClearTransient: () => this.userDrawings?.clearTransient()
19431
19579
  });
19432
19580
  this.input.rightEdgeZoom = this.zoomAnchorMode === "right";
19433
19581
  this.input.axisDrag = this.axisDragEnabled;
19434
19582
  this.input.paneResize = this.paneResizeEnabled;
19583
+ this.input.drawings = this.drawingsInteractive;
19435
19584
  this.input.attach(this.dataCanvas);
19436
19585
  if (this.settingsEnabled) this.setSettingsEnabled(true);
19437
19586
  this.syncCountdownTimer();
@@ -19740,6 +19889,8 @@ var NativeRenderer = class {
19740
19889
  this.backend.destroy();
19741
19890
  this.chrome.destroy();
19742
19891
  this.crosshairLayer.destroy();
19892
+ this.shadeRightEl?.remove();
19893
+ this.shadeRightEl = null;
19743
19894
  this.attributionEl?.remove();
19744
19895
  this.attributionEl = null;
19745
19896
  this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
@@ -20086,14 +20237,21 @@ var NativeRenderer = class {
20086
20237
  this.priceStyleCbs.add(cb);
20087
20238
  return () => this.priceStyleCbs.delete(cb);
20088
20239
  }
20240
+ onPriceStyleWillChange(cb) {
20241
+ this.priceStyleWillChangeCbs.add(cb);
20242
+ return () => this.priceStyleWillChangeCbs.delete(cb);
20243
+ }
20089
20244
  /**
20090
20245
  * THE single write path for the base price style at runtime (feature set / settings dialog /
20091
- * config template — the constructor seeds the field directly, pre-listeners). Updates the
20092
- * scene, eases any reveal layer toward the new style's target, and notifies the core —
20093
- * which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
20246
+ * config template — the constructor seeds the field directly, pre-listeners). Announces the
20247
+ * switch while the scene still holds the old style (a listener may paint/capture that frame),
20248
+ * then updates the scene, eases any reveal layer toward the new style's target, and notifies
20249
+ * the core — which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
20094
20250
  */
20095
20251
  setPriceStyle(style) {
20096
20252
  if (style === this.scene.priceStyle) return;
20253
+ const from = this.scene.priceStyle;
20254
+ for (const cb of this.priceStyleWillChangeCbs) cb(from, style);
20097
20255
  this.scene.priceStyle = style;
20098
20256
  this.scene.basePainting = basePaintingOf(style);
20099
20257
  this.scene.candleOverride = candleOverrideFor(style, this.scene.style.chartTypes);
@@ -20323,7 +20481,7 @@ var NativeRenderer = class {
20323
20481
  if (this.easeLiveBar(dtMs)) active = true;
20324
20482
  this.skeletonClockMs += dtMs;
20325
20483
  this.paintData();
20326
- this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
20484
+ this.renderCrosshairLayer();
20327
20485
  this.updateLegendValues();
20328
20486
  this.emitViewportChange();
20329
20487
  return active;
@@ -20898,10 +21056,31 @@ var NativeRenderer = class {
20898
21056
  this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
20899
21057
  this.trackMarkPopover();
20900
21058
  }
20901
- this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
21059
+ this.renderCrosshairLayer();
20902
21060
  if (!repaintsData(level) && this.paintedData) this.repaintCursorLayers();
20903
21061
  this.updateLegendValues();
20904
21062
  }
21063
+ /** Repaint the L2 crosshair canvas and keep the `.vela-shade-right` element on the veil it painted. */
21064
+ renderCrosshairLayer() {
21065
+ this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
21066
+ this.syncShadeRight();
21067
+ }
21068
+ /** Position (or hide) the `.vela-shade-right` element over the veil of the last crosshair frame. */
21069
+ syncShadeRight() {
21070
+ const el = this.shadeRightEl;
21071
+ if (!el) return;
21072
+ const area = this.crosshairLayer.shadeRightArea;
21073
+ const sig = area ? `${area.x},${area.width},${area.height}` : "";
21074
+ if (sig === this.shadeRightSig) return;
21075
+ this.shadeRightSig = sig;
21076
+ if (!area) {
21077
+ el.hidden = true;
21078
+ el.style.display = "none";
21079
+ return;
21080
+ }
21081
+ Object.assign(el.style, { left: `${area.x}px`, width: `${area.width}px`, height: `${area.height}px`, display: "" });
21082
+ el.hidden = false;
21083
+ }
20905
21084
  /** Repaint the SDK layers that opted into cursor tracking (their own canvas only). */
20906
21085
  repaintCursorLayers() {
20907
21086
  const pane = this.scene.panes.get(PRICE_PANE_ID);
@@ -21695,7 +21874,7 @@ var NativeRenderer = class {
21695
21874
  if (this.animator.active) {
21696
21875
  this.computeScales();
21697
21876
  this.paintData();
21698
- this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
21877
+ this.renderCrosshairLayer();
21699
21878
  } else {
21700
21879
  this.scheduler.flushNow(4 /* Full */);
21701
21880
  }
@@ -21904,7 +22083,7 @@ var IndicatorHandleImpl = class {
21904
22083
  this.controller.applyProps(this.id, values);
21905
22084
  }
21906
22085
  updateCode(source) {
21907
- this.controller.updateCode(this.id, source);
22086
+ return this.controller.updateCode(this.id, source);
21908
22087
  }
21909
22088
  setVisible(visible) {
21910
22089
  this.controller.setVisible(this.id, visible);
@@ -22335,6 +22514,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22335
22514
  this.activeEngineStyle = null;
22336
22515
  /** The chart's current price style (tracked from the renderer's change events + initial read). */
22337
22516
  this.priceStyle = "candles";
22517
+ this.priceStyleWillChangeUnsub = null;
22338
22518
  this.unresolvedUnsub = null;
22339
22519
  /** Latest chart visible range (left/right bar times), fed to viewport-dependent scripts. */
22340
22520
  this.visibleRange = null;
@@ -22433,6 +22613,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
22433
22613
  this.renderer.onThemeSelect?.((name) => this.setTheme(resolveTheme(name)));
22434
22614
  this.viewportUnsub = this.renderer.onViewportChange((range) => this.onViewportChange(range));
22435
22615
  this.renderer.onPriceStyleChange?.((style) => this.syncPriceStyle(style));
22616
+ this.priceStyleWillChangeUnsub = this.renderer.onPriceStyleWillChange?.((from, to) => this.events.emit("priceStyle:change", { from, to })) ?? null;
22436
22617
  const initialStyle = this.renderer.readFeature("priceStyle");
22437
22618
  if (typeof initialStyle === "string") this.priceStyle = initialStyle;
22438
22619
  this.barTransform = barTransformFor(initialStyle);
@@ -23719,20 +23900,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
23719
23900
  /**
23720
23901
  * IndicatorController: replace a SCRIPT indicator's source in place (see
23721
23902
  * {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
23722
- * computing until the new source has compiled, so a broken edit costs the user
23723
- * nothing but an `error` event. Natives have no script — warn and leave them alone.
23903
+ * computing until the new source has compiled; then the new code runs on TRIAL until
23904
+ * its first model lands, and a failure before that puts the previous code back.
23905
+ * Natives have no script — warn and leave them alone.
23724
23906
  */
23725
23907
  updateCode(id, source) {
23726
23908
  const record = this.registry.get(id);
23727
23909
  const handle = this.handles.get(id);
23728
- if (!record || !handle) return;
23910
+ if (!record || !handle) return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator is no longer on the chart.`) });
23729
23911
  if (record.native) {
23730
23912
  console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
23731
- return;
23913
+ return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`) });
23732
23914
  }
23733
- if (source === record.source && record.pendingSource === void 0) return;
23734
- record.pendingSource = source;
23735
- void this.swapSource(id, source, handle);
23915
+ const busy = record.pendingSource !== void 0 || record.codeTrial !== void 0;
23916
+ if (!busy && source === record.source) return Promise.resolve({ ok: true, error: null });
23917
+ return new Promise((resolve) => {
23918
+ (record.codeWaiters ?? (record.codeWaiters = [])).push(resolve);
23919
+ if (record.pendingSource === source || record.pendingSource === void 0 && record.codeTrial && record.source === source) return;
23920
+ record.pendingSource = source;
23921
+ void this.swapSource(id, source, handle);
23922
+ });
23736
23923
  }
23737
23924
  async swapSource(id, source, handle) {
23738
23925
  const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
@@ -23743,26 +23930,41 @@ var EngineOrchestrator = class _EngineOrchestrator {
23743
23930
  const record2 = this.registry.get(id);
23744
23931
  if (record2?.pendingSource === source) {
23745
23932
  record2.pendingSource = void 0;
23746
- this.fail(id, handle, err);
23933
+ this.rejectCode(id, record2, handle, err);
23747
23934
  }
23748
23935
  return;
23749
23936
  }
23750
23937
  const record = this.registry.get(id);
23751
23938
  if (!record || record.pendingSource !== source) return;
23752
23939
  record.pendingSource = void 0;
23940
+ const previous = record.codeTrial?.previous ?? {
23941
+ source: record.source,
23942
+ engine: record.engine,
23943
+ prepared: record.prepared,
23944
+ inputValues: record.inputValues,
23945
+ propValues: record.propValues,
23946
+ inputs: [...handle.inputs],
23947
+ props: [...handle.props]
23948
+ };
23949
+ record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues, record.prepared?.inputs);
23950
+ record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues, record.prepared?.props);
23753
23951
  record.source = source;
23754
23952
  record.engine = engine;
23755
23953
  record.prepared = prepared;
23756
23954
  handle.setSource(source);
23757
- record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
23758
- record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
23759
23955
  handle.setSchema(prepared.inputs);
23760
23956
  handle.setPropsSchema(prepared.props ?? []);
23761
23957
  record.session?.stop();
23762
23958
  record.session = void 0;
23763
23959
  record.pendingStructural = true;
23764
23960
  record.pendingCause = "code";
23765
- if (record.hidden) return;
23961
+ if (record.hidden) {
23962
+ record.codeTrial = void 0;
23963
+ if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
23964
+ this.settleCode(record, { ok: true, error: null });
23965
+ return;
23966
+ }
23967
+ record.codeTrial = { previous };
23766
23968
  if (record.renderHandle) {
23767
23969
  this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
23768
23970
  this.setLoading(record, true);
@@ -23770,6 +23972,46 @@ var EngineOrchestrator = class _EngineOrchestrator {
23770
23972
  this.mountLoadingPlaceholder(id, record);
23771
23973
  }
23772
23974
  this.executeIndicator(id, handle);
23975
+ if (this.bars.length === 0 && record.codeTrial) {
23976
+ record.codeTrial = void 0;
23977
+ this.settleCode(record, { ok: true, error: null });
23978
+ }
23979
+ }
23980
+ /** Resolve every `updateCode` caller waiting on this record's chain of edits. */
23981
+ settleCode(record, result) {
23982
+ const waiters = record.codeWaiters;
23983
+ record.codeWaiters = void 0;
23984
+ if (waiters) for (const resolve of waiters) resolve(result);
23985
+ }
23986
+ /**
23987
+ * An update failed — at prepare, or on trial before its first model. A trial puts the
23988
+ * previous code back (source, engine, prepared script, values, schema) and restarts it;
23989
+ * its visuals were never dropped, so the chart keeps painting throughout. Then the
23990
+ * failure reports the usual way and every waiting caller learns it.
23991
+ */
23992
+ rejectCode(id, record, handle, err) {
23993
+ const trial = record.codeTrial;
23994
+ record.codeTrial = void 0;
23995
+ this.fail(id, handle, err);
23996
+ if (trial) {
23997
+ const p = trial.previous;
23998
+ record.session?.stop();
23999
+ record.session = void 0;
24000
+ record.source = p.source;
24001
+ record.engine = p.engine;
24002
+ record.prepared = p.prepared;
24003
+ record.inputValues = p.inputValues;
24004
+ record.propValues = p.propValues;
24005
+ handle.setSource(p.source);
24006
+ handle.setSchema(p.inputs);
24007
+ handle.setPropsSchema(p.props);
24008
+ record.pendingStructural = true;
24009
+ record.pendingCause = void 0;
24010
+ if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
24011
+ if (!record.hidden) this.executeIndicator(id, handle);
24012
+ }
24013
+ const error = err instanceof Error ? err : new Error(String(err));
24014
+ this.settleCode(record, { ok: false, error });
23773
24015
  }
23774
24016
  /** IndicatorController: tear down an indicator and (if now empty) its pane. */
23775
24017
  /** Live handles of every indicator on the chart (script + native), insertion order. */
@@ -23784,6 +24026,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
23784
24026
  removeIndicator(id) {
23785
24027
  const record = this.registry.remove(id);
23786
24028
  if (record?.native?.type === "volume") this.volumeOptedOut = true;
24029
+ if (record) this.settleCode(record, { ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator was removed.`) });
23787
24030
  record?.session?.stop();
23788
24031
  if (record?.native?.started) record.native.instance.stop();
23789
24032
  this.handles.delete(id);
@@ -23811,6 +24054,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
23811
24054
  if (!visible) {
23812
24055
  record.session?.stop();
23813
24056
  record.session = void 0;
24057
+ if (record.codeTrial) {
24058
+ record.codeTrial = void 0;
24059
+ this.settleCode(record, { ok: true, error: null });
24060
+ }
23814
24061
  if (record.native?.started) record.native.instance.suspend();
23815
24062
  if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
23816
24063
  else if (record.native) this.mountHiddenNativeRow(id, record);
@@ -23897,7 +24144,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
23897
24144
  if (this.viewportTimer != null) clearTimeout(this.viewportTimer);
23898
24145
  this.viewportUnsub?.();
23899
24146
  this.paneActionUnsub?.();
24147
+ this.priceStyleWillChangeUnsub?.();
24148
+ this.priceStyleWillChangeUnsub = null;
23900
24149
  for (const record of this.registry.all()) {
24150
+ this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
23901
24151
  record.session?.stop();
23902
24152
  if (record.native?.started) record.native.instance.stop();
23903
24153
  }
@@ -23966,7 +24216,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
23966
24216
  const record = this.registry.get(id);
23967
24217
  if (!record || !record.engine || !record.prepared || record.hidden) return;
23968
24218
  const mode = this.config.live && !record.prepared.reactsToViewport && record.engine.capabilities.streaming ? "live" : "static";
23969
- record.session = record.engine.execute(
24219
+ const prepared = record.prepared;
24220
+ const session = record.engine.execute(
23970
24221
  {
23971
24222
  prepared: record.prepared,
23972
24223
  market: this.market(),
@@ -23987,6 +24238,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
23987
24238
  const cause = record.pendingCause ?? "history";
23988
24239
  if (!this.applyModel(id, model)) return;
23989
24240
  record.pendingCause = void 0;
24241
+ if (record.codeTrial) {
24242
+ record.codeTrial = void 0;
24243
+ this.settleCode(record, { ok: true, error: null });
24244
+ }
23990
24245
  this.emitContextChanged(id);
23991
24246
  this.emitScriptRun(id, cause, first);
23992
24247
  },
@@ -23997,9 +24252,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
23997
24252
  handle.emit("alert", { id: a.id, message: a.message, title: a.title, time: a.time });
23998
24253
  },
23999
24254
  onWarning: (w) => this.events.emit("warning", w),
24000
- onError: (err) => this.fail(id, handle, err)
24255
+ onError: (err) => {
24256
+ if (record.codeTrial) this.rejectCode(id, record, handle, err);
24257
+ else this.fail(id, handle, err);
24258
+ }
24001
24259
  }
24002
24260
  );
24261
+ if (record.prepared !== prepared) {
24262
+ session.stop();
24263
+ return;
24264
+ }
24265
+ record.session = session;
24003
24266
  this.emitContextChanged(id);
24004
24267
  }
24005
24268
  /**
@@ -24500,7 +24763,7 @@ function yieldToPaint() {
24500
24763
  }
24501
24764
  return new Promise((resolve) => setTimeout(resolve, 0));
24502
24765
  }
24503
- function valuesOnSchema(schema, previous) {
24766
+ function valuesOnSchema(schema, previous, oldSchema = []) {
24504
24767
  const out = {};
24505
24768
  const declared = /* @__PURE__ */ new Set();
24506
24769
  for (const s of schema) {
@@ -24508,9 +24771,22 @@ function valuesOnSchema(schema, previous) {
24508
24771
  declared.add(s.key);
24509
24772
  declared.add(s.title);
24510
24773
  }
24511
- for (const [k, v] of Object.entries(previous)) if (declared.has(k)) out[k] = v;
24774
+ const oldDefaults = /* @__PURE__ */ new Map();
24775
+ for (const s of oldSchema) {
24776
+ oldDefaults.set(s.key, s.defval);
24777
+ if (!oldDefaults.has(s.title)) oldDefaults.set(s.title, s.defval);
24778
+ }
24779
+ for (const [k, v] of Object.entries(previous)) {
24780
+ if (!declared.has(k)) continue;
24781
+ if (oldDefaults.has(k) && sameInputValue(oldDefaults.get(k), v)) continue;
24782
+ out[k] = v;
24783
+ }
24512
24784
  return out;
24513
24785
  }
24786
+ function sameInputValue(a, b) {
24787
+ if (a === b) return true;
24788
+ return typeof a === "object" && a !== null && typeof b === "object" && b !== null && JSON.stringify(a) === JSON.stringify(b);
24789
+ }
24514
24790
  function blankedModel(model) {
24515
24791
  return {
24516
24792
  ...model,
@@ -28533,7 +28809,8 @@ var Vela = class {
28533
28809
  glow: options.glow ?? 0,
28534
28810
  upColor: options.upColor ?? BULLISH,
28535
28811
  downColor: options.downColor ?? BEARISH,
28536
- priceStyle: options.priceStyle ?? "candles"
28812
+ priceStyle: options.priceStyle ?? "candles",
28813
+ priceAxisTicks: options.priceAxis?.ticks ?? null
28537
28814
  };
28538
28815
  const RendererClass = options.renderer ?? NativeRenderer;
28539
28816
  if (typeof RendererClass !== "function") {
@@ -28877,4 +29154,4 @@ function resolveElement(container) {
28877
29154
  return element;
28878
29155
  }
28879
29156
 
28880
- export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MarksControl, MultiProviderFeed, NativeRenderer, RendererControl, ReplayControl, SecondClock, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isExchangeTimezone, isGroupRow, normalizeSession, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, resolveTimezone, sharedBarStore, timeframeToMs, timezoneMenuRows, tzButtonLabel, tzMenuLabel, tzOffset };
29157
+ export { BUILTIN_PRICE_STYLES, BarStore, CANDLE_OVERRIDE_KEYS, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MarksControl, MultiProviderFeed, NativeRenderer, RendererControl, ReplayControl, SecondClock, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isGroupRow, normalizeSession, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, sharedBarStore, timeframeToMs };