@luxalgo/vela 0.8.2 → 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 (48) hide show
  1. package/dist/{DataProvider-DpOWZD8F.d.ts → DataProvider-oH78tYXL.d.ts} +1 -1
  2. package/dist/{DataProvider-COGDs9Xg.d.cts → DataProvider-wbtJ3-Sa.d.cts} +1 -1
  3. package/dist/{chunk-R2JMNIHY.js → chunk-2IPXYM4B.js} +258 -169
  4. package/dist/{chunk-NLDTVFOI.js → chunk-G7B7ZCBF.js} +129 -10
  5. package/dist/{chunk-T3DZIR5Y.js → chunk-HMHA733L.js} +306 -168
  6. package/dist/{chunk-DPAA3SRT.js → chunk-KWI3YDBW.js} +254 -9
  7. package/dist/{chunk-27UTYE7T.js → chunk-TO3GIK7Z.js} +49 -7
  8. package/dist/{chunk-HKNO24GD.js → chunk-VHGACEHO.js} +50 -1
  9. package/dist/{contributions-Cgm9RJYP.d.cts → contributions-BhOBRenr.d.cts} +44 -4
  10. package/dist/{contributions-bWgwyiVi.d.ts → contributions-HGqnNb_P.d.ts} +44 -4
  11. package/dist/index.cjs +497 -79
  12. package/dist/index.d.cts +34 -14
  13. package/dist/index.d.ts +34 -14
  14. package/dist/index.js +5 -5
  15. package/dist/{options-NAUHiQEC.d.ts → options-lMUwmRwh.d.cts} +66 -1
  16. package/dist/{options-NAUHiQEC.d.cts → options-lMUwmRwh.d.ts} +66 -1
  17. package/dist/{plugin-CqxWoDKS.d.ts → plugin-Cr2Pj8q8.d.ts} +4 -4
  18. package/dist/{plugin-CjjzZX2F.d.cts → plugin-Do5J5xuF.d.cts} +4 -4
  19. package/dist/plugin.cjs +18 -0
  20. package/dist/plugin.d.cts +5 -5
  21. package/dist/plugin.d.ts +5 -5
  22. package/dist/plugin.js +3 -3
  23. package/dist/providers/binance.d.cts +2 -2
  24. package/dist/providers/binance.d.ts +2 -2
  25. package/dist/providers/coinbase.d.cts +2 -2
  26. package/dist/providers/coinbase.d.ts +2 -2
  27. package/dist/providers/hyperliquid.d.cts +2 -2
  28. package/dist/providers/hyperliquid.d.ts +2 -2
  29. package/dist/{side-panel-wMNfgyZb.d.cts → side-panel-_2UK1gSm.d.cts} +45 -3
  30. package/dist/{side-panel-wMNfgyZb.d.ts → side-panel-_2UK1gSm.d.ts} +45 -3
  31. package/dist/{statusline-model-Dpfpq54A.d.cts → statusline-model-B-2xGlB_.d.cts} +3 -3
  32. package/dist/{statusline-model-Dih6FGWP.d.ts → statusline-model-CY1wzxHg.d.ts} +3 -3
  33. package/dist/ui.cjs +224 -14
  34. package/dist/ui.d.cts +119 -87
  35. package/dist/ui.d.ts +119 -87
  36. package/dist/ui.js +3 -3
  37. package/dist/vela.global.js +497 -79
  38. package/dist/vela.global.min.js +86 -52
  39. package/dist/widget.cjs +850 -167
  40. package/dist/widget.d.cts +24 -10
  41. package/dist/widget.d.ts +24 -10
  42. package/dist/widget.js +7 -7
  43. package/dist/workspace.cjs +849 -167
  44. package/dist/workspace.d.cts +47 -20
  45. package/dist/workspace.d.ts +47 -20
  46. package/dist/workspace.js +6 -6
  47. package/package.json +1 -1
  48. /package/dist/{chunk-JU4CWEBF.js → chunk-W5YPYZ2P.js} +0 -0
package/dist/index.cjs CHANGED
@@ -7888,6 +7888,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
7888
7888
  this.activeEngineStyle = null;
7889
7889
  /** The chart's current price style (tracked from the renderer's change events + initial read). */
7890
7890
  this.priceStyle = "candles";
7891
+ this.priceStyleWillChangeUnsub = null;
7891
7892
  this.unresolvedUnsub = null;
7892
7893
  /** Latest chart visible range (left/right bar times), fed to viewport-dependent scripts. */
7893
7894
  this.visibleRange = null;
@@ -7986,6 +7987,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
7986
7987
  this.renderer.onThemeSelect?.((name) => this.setTheme(resolveTheme(name)));
7987
7988
  this.viewportUnsub = this.renderer.onViewportChange((range) => this.onViewportChange(range));
7988
7989
  this.renderer.onPriceStyleChange?.((style) => this.syncPriceStyle(style));
7990
+ this.priceStyleWillChangeUnsub = this.renderer.onPriceStyleWillChange?.((from, to) => this.events.emit("priceStyle:change", { from, to })) ?? null;
7989
7991
  const initialStyle = this.renderer.readFeature("priceStyle");
7990
7992
  if (typeof initialStyle === "string") this.priceStyle = initialStyle;
7991
7993
  this.barTransform = barTransformFor(initialStyle);
@@ -9516,6 +9518,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
9516
9518
  if (this.viewportTimer != null) clearTimeout(this.viewportTimer);
9517
9519
  this.viewportUnsub?.();
9518
9520
  this.paneActionUnsub?.();
9521
+ this.priceStyleWillChangeUnsub?.();
9522
+ this.priceStyleWillChangeUnsub = null;
9519
9523
  for (const record of this.registry.all()) {
9520
9524
  this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
9521
9525
  record.session?.stop();
@@ -11811,6 +11815,55 @@ function announceSurface(el, open2, kind, trigger = null) {
11811
11815
  new View.CustomEvent(open2 ? SURFACE_OPEN_EVENT : SURFACE_CLOSE_EVENT, { bubbles: true, composed: true, detail: { kind, trigger } })
11812
11816
  );
11813
11817
  }
11818
+
11819
+ // src/ui/surface-exit.ts
11820
+ var CLOSING_ATTR = "data-closing";
11821
+ var SURFACE_EXIT_CAP_MS = 1e3;
11822
+ function animationsOf(els) {
11823
+ return els.flatMap((el) => el.getAnimations({ subtree: true }));
11824
+ }
11825
+ function ends(a) {
11826
+ return Number.isFinite(a.effect?.getComputedTiming().endTime ?? Number.POSITIVE_INFINITY);
11827
+ }
11828
+ function holdForExit(els, done, opts = {}) {
11829
+ const list = Array.isArray(els) ? els : [els];
11830
+ if (list.length === 0 || !list.every((el) => typeof el.getAnimations === "function")) {
11831
+ done();
11832
+ return null;
11833
+ }
11834
+ const before = new Set(animationsOf(list));
11835
+ for (const el of list) el.setAttribute(CLOSING_ATTR, "");
11836
+ const exit = animationsOf(list).filter((a) => !before.has(a) && ends(a));
11837
+ if (exit.length === 0) {
11838
+ for (const el of list) el.removeAttribute(CLOSING_ATTR);
11839
+ done();
11840
+ return null;
11841
+ }
11842
+ const frozen = [...list, ...opts.inert ?? []];
11843
+ const wasInert = frozen.map((el) => el.inert);
11844
+ for (const el of frozen) el.inert = true;
11845
+ let settled = false;
11846
+ const settle = (complete) => {
11847
+ if (settled) return;
11848
+ settled = true;
11849
+ clearTimeout(cap);
11850
+ for (const el of list) el.removeAttribute(CLOSING_ATTR);
11851
+ frozen.forEach((el, i) => {
11852
+ el.inert = wasInert[i];
11853
+ });
11854
+ if (complete) done();
11855
+ };
11856
+ const cap = setTimeout(() => settle(true), SURFACE_EXIT_CAP_MS);
11857
+ let pending = exit.length;
11858
+ const one = () => {
11859
+ if (--pending === 0) settle(true);
11860
+ };
11861
+ for (const a of exit) a.finished.then(one, one);
11862
+ return {
11863
+ cancel: () => settle(false),
11864
+ finish: () => settle(true)
11865
+ };
11866
+ }
11814
11867
  function menuController(opts) {
11815
11868
  return {
11816
11869
  machine: menu__namespace.machine,
@@ -11892,6 +11945,35 @@ var MENU_CSS = `
11892
11945
  color: var(--vela-fg-bright);
11893
11946
  }
11894
11947
  .vela-menu-item[data-checkmark] { color: var(--vela-fg-bright); }
11948
+ /* Badge mode (iconBadges): the row icon sits in a rounded tile and the row grows to fit
11949
+ it. An icon-less row of the same level keeps an invisible slot, so labels align. */
11950
+ .vela-menu[data-badges] > .vela-menu-item { padding: 5px 10px 5px 6px; }
11951
+ .vela-menu-item .vela-menu-badge {
11952
+ width: 24px;
11953
+ height: 24px;
11954
+ padding: 3px;
11955
+ flex: none;
11956
+ box-sizing: border-box;
11957
+ display: inline-flex;
11958
+ align-items: center;
11959
+ justify-content: center;
11960
+ border-radius: var(--vela-radius-md);
11961
+ background: var(--vela-hover);
11962
+ border: 1px solid var(--vela-border);
11963
+ color: var(--vela-fg-muted);
11964
+ transition: background var(--vela-dur-fast) var(--vela-ease), border-color var(--vela-dur-fast) var(--vela-ease), color var(--vela-dur-fast) var(--vela-ease);
11965
+ }
11966
+ .vela-menu-item .vela-menu-badge:empty { background: transparent; border-color: transparent; }
11967
+ .vela-menu-item .vela-menu-badge .vela-icon { color: inherit; }
11968
+ .vela-menu-item[data-highlighted] .vela-menu-badge:not(:empty) { color: var(--vela-fg-bright); }
11969
+ /* The selected tile inverts: a bright fill under the surface-colored glyph. After the
11970
+ hover rule, so a selected row keeps its tile while highlighted. */
11971
+ .vela-menu-item[data-checked] .vela-menu-badge:not(:empty),
11972
+ .vela-menu-item[data-checkmark] .vela-menu-badge:not(:empty) {
11973
+ background: color-mix(in srgb, var(--vela-fg-bright) 83%, var(--vela-surface));
11974
+ border-color: color-mix(in srgb, var(--vela-fg-bright) 40%, transparent);
11975
+ color: var(--vela-surface);
11976
+ }
11895
11977
  /* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
11896
11978
  same control language as the settings dialog's toggles. */
11897
11979
  .vela-menu-switch {
@@ -11958,11 +12040,14 @@ var Surface = class _Surface {
11958
12040
  this.pinnedAnchor = null;
11959
12041
  /** The open state last announced — see {@link announce}. */
11960
12042
  this.shown = false;
12043
+ /** The list's exit animation after a close; a reopen cancels it. */
12044
+ this.exit = null;
11961
12045
  this.doc = doc;
11962
12046
  this.host = opts.host;
11963
12047
  this.onSelect = opts.onSelect;
11964
12048
  this.onFavorite = opts.onFavorite;
11965
12049
  this.checkmarks = opts.checkmarks === true;
12050
+ this.iconBadges = opts.iconBadges === true;
11966
12051
  this.positioner = doc.createElement("div");
11967
12052
  this.positioner.className = "vela-ui-layer";
11968
12053
  this.list = doc.createElement("ul");
@@ -11993,14 +12078,30 @@ var Surface = class _Surface {
11993
12078
  if (opts.triggerId && trigger) trigger.id = opts.triggerId;
11994
12079
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
11995
12080
  const api = this.ctrl.connect(service);
11996
- if (this.shown && !api.open) this.announce(false);
12081
+ const closing = this.shown && !api.open;
12082
+ if (closing) this.announce(false);
12083
+ if (api.open && this.exit) {
12084
+ this.exit.cancel();
12085
+ this.exit = null;
12086
+ }
11997
12087
  if (trigger) vanilla.spreadProps(trigger, api.getTriggerProps(), this.mid);
11998
12088
  vanilla.spreadProps(this.positioner, api.getPositionerProps(), this.mid);
11999
12089
  vanilla.spreadProps(this.list, api.getContentProps(), this.mid);
12000
12090
  this.project(api);
12091
+ if (closing) this.holdExit();
12001
12092
  if (api.open && !this.shown) this.announce(true);
12002
12093
  });
12003
12094
  }
12095
+ /** Keep the just-closed list up through its exit animation. `spreadProps` re-applies
12096
+ * `hidden` only when the machine's value for it changes, so the list stays shown until
12097
+ * the exit ends — or until a reopen, which projects `hidden: false` itself. */
12098
+ holdExit() {
12099
+ this.list.hidden = false;
12100
+ this.exit = holdForExit(this.list, () => {
12101
+ this.exit = null;
12102
+ if (!this.shown) this.list.hidden = true;
12103
+ });
12104
+ }
12004
12105
  get api() {
12005
12106
  return this.ctrl.connect(this.handle.service);
12006
12107
  }
@@ -12024,6 +12125,7 @@ var Surface = class _Surface {
12024
12125
  destroy() {
12025
12126
  for (const sub2 of this.subs.values()) sub2.destroy();
12026
12127
  this.subs.clear();
12128
+ this.exit?.finish();
12027
12129
  this.handle.stop();
12028
12130
  if (this.shown) this.announce(false);
12029
12131
  this.positioner.remove();
@@ -12050,6 +12152,10 @@ var Surface = class _Surface {
12050
12152
  const doc = this.doc;
12051
12153
  this.list.replaceChildren();
12052
12154
  const markable = this.checkmarks && this.items.some((i) => !(i.submenu && i.submenu.length > 0) && !i.toggle && i.checked !== void 0);
12155
+ const iconic = this.items.some((i) => !!i.icon);
12156
+ const badged = this.iconBadges && iconic;
12157
+ if (badged) this.list.dataset.badges = "1";
12158
+ else delete this.list.dataset.badges;
12053
12159
  for (const item of this.items) {
12054
12160
  if (item.separatorBefore) {
12055
12161
  const sep = doc.createElement("li");
@@ -12075,7 +12181,16 @@ var Surface = class _Surface {
12075
12181
  } else if (!branch && !item.toggle && item.checked) {
12076
12182
  li.dataset.checked = "1";
12077
12183
  }
12078
- if (item.icon) li.appendChild(iconEl(item.icon, doc));
12184
+ if (badged) {
12185
+ const badge = doc.createElement("span");
12186
+ badge.className = "vela-menu-badge";
12187
+ if (item.icon) badge.appendChild(iconEl(item.icon, doc));
12188
+ li.appendChild(badge);
12189
+ } else if (item.icon) {
12190
+ li.appendChild(iconEl(item.icon, doc));
12191
+ } else if (iconic) {
12192
+ li.appendChild(iconEl("", doc));
12193
+ }
12079
12194
  const label = doc.createElement("span");
12080
12195
  label.className = "vela-menu-label";
12081
12196
  label.textContent = item.label;
@@ -12123,6 +12238,7 @@ var Surface = class _Surface {
12123
12238
  onSelect: this.onSelect,
12124
12239
  onFavorite: this.onFavorite,
12125
12240
  checkmarks: this.checkmarks,
12241
+ iconBadges: this.iconBadges,
12126
12242
  id: `${this.mid}--${item.id}`
12127
12243
  });
12128
12244
  sub2.setItems(item.submenu ?? []);
@@ -12148,7 +12264,8 @@ var Menu = class {
12148
12264
  id: opts.id,
12149
12265
  minWidth: opts.minWidth,
12150
12266
  onFavorite: opts.onFavorite,
12151
- checkmarks: opts.checkmarks
12267
+ checkmarks: opts.checkmarks,
12268
+ iconBadges: opts.iconBadges
12152
12269
  });
12153
12270
  this.root.setItems(opts.items);
12154
12271
  }
@@ -12252,6 +12369,7 @@ var POPOVER_CSS = `
12252
12369
 
12253
12370
  // src/ui/components/popover/view.ts
12254
12371
  var open = null;
12372
+ var leaving = /* @__PURE__ */ new Set();
12255
12373
  function closeOpenPopovers() {
12256
12374
  open?.hide();
12257
12375
  }
@@ -12279,6 +12397,8 @@ var Popover = class {
12279
12397
  this.shown = false;
12280
12398
  /** The pending removal of a fading-out shell; a show() that reuses the shell cancels it. */
12281
12399
  this.leaveTimer = null;
12400
+ /** The shell's exit animation before it leaves the DOM; a show() that reuses the shell cancels it. */
12401
+ this.exit = null;
12282
12402
  const doc = opts.trigger.ownerDocument;
12283
12403
  injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
12284
12404
  this.trigger = opts.trigger;
@@ -12315,6 +12435,10 @@ var Popover = class {
12315
12435
  clearTimeout(this.leaveTimer);
12316
12436
  this.leaveTimer = null;
12317
12437
  }
12438
+ this.exit?.cancel();
12439
+ this.exit = null;
12440
+ leaving.delete(this);
12441
+ for (const p of [...leaving]) if (p.trigger === this.trigger) p.exit?.finish();
12318
12442
  ensureUIHost(this.el, this.inheritsTokens ? void 0 : this.theme);
12319
12443
  if (this.fadeMs > 0) {
12320
12444
  this.el.style.transition = `opacity ${this.fadeMs}ms ease`;
@@ -12371,7 +12495,12 @@ var Popover = class {
12371
12495
  this.el.remove();
12372
12496
  }, this.fadeMs);
12373
12497
  } else {
12374
- this.el.remove();
12498
+ this.exit = holdForExit(this.el, () => {
12499
+ this.exit = null;
12500
+ leaving.delete(this);
12501
+ this.el.remove();
12502
+ });
12503
+ if (this.exit) leaving.add(this);
12375
12504
  }
12376
12505
  if (open === this) open = null;
12377
12506
  this.ctrl.onClose?.();
@@ -12383,6 +12512,7 @@ var Popover = class {
12383
12512
  }
12384
12513
  destroy() {
12385
12514
  this.hide();
12515
+ this.exit?.finish();
12386
12516
  }
12387
12517
  reposition() {
12388
12518
  if (this.shown) this.place();
@@ -13075,6 +13205,9 @@ var Dialog = class {
13075
13205
  constructor(opts = {}) {
13076
13206
  this.opener = null;
13077
13207
  this.wasOpen = false;
13208
+ /** The panel and scrim's exit animation after a close; a reopen cancels it. */
13209
+ this.exit = null;
13210
+ this.destroyed = false;
13078
13211
  const doc = (opts.host ?? document.body).ownerDocument;
13079
13212
  injectStyles(DIALOG_STYLE_ID, DIALOG_CSS, doc);
13080
13213
  const host = opts.host ?? doc.body;
@@ -13171,19 +13304,57 @@ var Dialog = class {
13171
13304
  const mid = String(this.ctrl.props.id);
13172
13305
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
13173
13306
  const api = this.ctrl.connect(service);
13174
- if (this.wasOpen && !api.open) this.announce(false);
13307
+ const closing = this.wasOpen && !api.open;
13308
+ if (closing) this.announce(false);
13175
13309
  vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
13176
13310
  vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
13177
13311
  vanilla.spreadProps(this.panel, api.getContentProps(), mid);
13178
13312
  vanilla.spreadProps(title, api.getTitleProps(), mid);
13179
13313
  vanilla.spreadProps(close, api.getCloseTriggerProps(), mid);
13180
- this.backdrop.style.display = api.open ? "" : "none";
13181
- this.positioner.style.display = api.open ? "" : "none";
13314
+ if (api.open) {
13315
+ this.exit?.cancel();
13316
+ this.exit = null;
13317
+ this.setShown(true);
13318
+ } else if (closing) {
13319
+ this.holdExit();
13320
+ } else if (!this.exit) {
13321
+ this.setShown(false);
13322
+ }
13182
13323
  if (api.open && !this.wasOpen) this.announce(true);
13183
13324
  if (this.wasOpen && !api.open) this.restoreOpener();
13184
13325
  this.wasOpen = api.open;
13185
13326
  });
13186
13327
  }
13328
+ setShown(shown) {
13329
+ this.backdrop.style.display = shown ? "" : "none";
13330
+ this.positioner.style.display = shown ? "" : "none";
13331
+ }
13332
+ /** Keep the just-closed dialog up through its exit animation, then hide it — or
13333
+ * remove it, when it was destroyed meanwhile. `spreadProps` re-applies the close
13334
+ * props' `hidden` only when its value changes, so lifting it keeps the panel and
13335
+ * scrim up until the exit ends. The positioner spans the viewport: inert, it lets
13336
+ * clicks through to the page while the panel animates out. */
13337
+ holdExit() {
13338
+ const held = [this.panel, this.backdrop];
13339
+ for (const el of held) el.hidden = false;
13340
+ this.exit = holdForExit(
13341
+ held,
13342
+ () => {
13343
+ this.exit = null;
13344
+ if (this.destroyed) {
13345
+ this.detach();
13346
+ } else if (!this.open) {
13347
+ for (const el of held) el.hidden = true;
13348
+ this.setShown(false);
13349
+ }
13350
+ },
13351
+ { inert: [this.positioner] }
13352
+ );
13353
+ }
13354
+ detach() {
13355
+ this.backdrop.remove();
13356
+ this.positioner.remove();
13357
+ }
13187
13358
  /** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
13188
13359
  * teardown that outruns the machine's close notification). */
13189
13360
  restoreOpener() {
@@ -13212,15 +13383,16 @@ var Dialog = class {
13212
13383
  return el?.closest(".vela-popover, .vela-menu") != null;
13213
13384
  }
13214
13385
  destroy() {
13386
+ if (this.wasOpen && !this.open) this.handle.flush();
13215
13387
  if (this.open) this.hide();
13216
13388
  this.handle.stop();
13389
+ this.destroyed = true;
13217
13390
  if (this.wasOpen) {
13218
13391
  this.wasOpen = false;
13219
13392
  this.announce(false);
13220
13393
  }
13221
13394
  this.restoreOpener();
13222
- this.backdrop.remove();
13223
- this.positioner.remove();
13395
+ if (!this.exit) this.detach();
13224
13396
  }
13225
13397
  };
13226
13398
 
@@ -15672,7 +15844,10 @@ var LEGEND_ICON_PX2 = 16;
15672
15844
  var LEGEND_CTL_PX = 18;
15673
15845
  var LEGEND_ROW_PAD_Y = 2;
15674
15846
  var LEGEND_ROW_PAD_X = 6;
15847
+ var LEGEND_LEFT_PX = 10;
15848
+ var LEGEND_SCALE_GAP_PX = 6;
15675
15849
  var LEGEND_TITLE_VALUES_GAP_PX = 6;
15850
+ var LEGEND_VALUES_GAP_PX = 5;
15676
15851
  var LEGEND_TITLE_STATUS_GAP_PX = 8;
15677
15852
  var LEGEND_ACTION_GAP_PX = 6;
15678
15853
  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;`;
@@ -15958,14 +16133,15 @@ var InputsUI = class {
15958
16133
  row.valuesEl.replaceChildren();
15959
16134
  return;
15960
16135
  }
15961
- row.valuesEl.style.display = "inline-flex";
16136
+ row.valuesEl.style.display = "block";
15962
16137
  row.valuesEl.replaceChildren();
15963
- for (const v of row.plotValues) {
16138
+ row.plotValues.forEach((v, i) => {
15964
16139
  const span = document.createElement("span");
15965
16140
  span.textContent = v.value;
15966
16141
  span.style.color = v.color;
16142
+ if (i > 0) span.style.marginLeft = `${LEGEND_VALUES_GAP_PX}px`;
15967
16143
  row.valuesEl.appendChild(span);
15968
- }
16144
+ });
15969
16145
  }
15970
16146
  // ── legend move/merge (menu + drag) ─────────────────────────────────────
15971
16147
  /** Open the "Move to" menu for a row, anchored under its move button. */
@@ -16066,7 +16242,7 @@ var InputsUI = class {
16066
16242
  if (!lg) {
16067
16243
  lg = document.createElement("div");
16068
16244
  lg.dataset.velaPane = paneId;
16069
- 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;";
16245
+ 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;`;
16070
16246
  applyChromeTokens(lg, this.theme);
16071
16247
  this.positionLegend(lg, paneId);
16072
16248
  this.container.appendChild(lg);
@@ -16078,6 +16254,7 @@ var InputsUI = class {
16078
16254
  const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
16079
16255
  lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
16080
16256
  lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
16257
+ lg.style.maxWidth = `calc(100% - ${(bounds.rightAxis ?? 0) + LEGEND_LEFT_PX + LEGEND_SCALE_GAP_PX}px)`;
16081
16258
  const collapsed = this.paneCollapse.has(paneId);
16082
16259
  const masterId = this.paneCollapse.get(paneId) ?? null;
16083
16260
  for (const row of this.rows.values()) {
@@ -16169,7 +16346,7 @@ var InputsUI = class {
16169
16346
  }
16170
16347
  ensureDialogStyles();
16171
16348
  const el = document.createElement("div");
16172
- 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;`;
16349
+ 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;`;
16173
16350
  el.addEventListener("mouseleave", () => {
16174
16351
  if (this.selectedId !== id) this.setRowHighlighted(id, false);
16175
16352
  });
@@ -16197,7 +16374,7 @@ var InputsUI = class {
16197
16374
  const statusEl = document.createElement("span");
16198
16375
  statusEl.style.cssText = "display:none;box-sizing:border-box;flex:none;";
16199
16376
  const titleWrap = document.createElement("span");
16200
- titleWrap.style.cssText = "white-space:nowrap;";
16377
+ titleWrap.style.cssText = "white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis;";
16201
16378
  titleWrap.addEventListener("mouseenter", () => this.setRowHighlighted(id, true));
16202
16379
  const titleEl = document.createElement("span");
16203
16380
  titleEl.textContent = title;
@@ -16215,7 +16392,7 @@ var InputsUI = class {
16215
16392
  el.appendChild(calloutsEl);
16216
16393
  el.appendChild(statusEl);
16217
16394
  const valuesEl = document.createElement("span");
16218
- 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;`;
16395
+ 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;`;
16219
16396
  el.appendChild(valuesEl);
16220
16397
  const controlsEl = document.createElement("span");
16221
16398
  controlsEl.style.cssText = `display:none;align-items:center;gap:${LEGEND_ACTION_GAP_PX}px;flex:none;margin-left:${LEGEND_ACTION_GAP_PX}px;`;
@@ -18810,6 +18987,11 @@ var InputController = class {
18810
18987
  /** When true (the default), the separators between stacked panes are draggable to
18811
18988
  * resize the panes above/below. When false, a press there is a normal data-area pan. */
18812
18989
  this.paneResize = true;
18990
+ /** When true (the default), the user drawings take part in pointer input: they claim a
18991
+ * press over a drawing or with a tool armed, and see hover moves. When false, every press
18992
+ * is a plain pan/click and hover leaves them alone; a drawing gesture already under way
18993
+ * still finishes. */
18994
+ this.drawings = true;
18813
18995
  this.el = null;
18814
18996
  this.dragging = false;
18815
18997
  this.moved = false;
@@ -18873,18 +19055,18 @@ var InputController = class {
18873
19055
  this.onModifier = (e) => {
18874
19056
  if (e.repeat || e.key !== "Shift" && e.key !== "Control" && e.key !== "Meta") return;
18875
19057
  if (Number.isNaN(this.cursorX)) return;
18876
- if (this.dragging && this.region !== "drawing") return;
19058
+ if (this.dragging ? this.region !== "drawing" : !this.drawings) return;
18877
19059
  this.deps.drawingsPointerMove?.(this.cursorX, this.cursorY, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
18878
19060
  };
18879
19061
  this.onDown = (e) => {
18880
19062
  if (e.button === 1) {
18881
19063
  const { x: x2, y: y2 } = this.local(e);
18882
- this.middleDeleted = this.deps.drawingsDeleteAt?.(x2, y2) ?? false;
19064
+ this.middleDeleted = this.drawings && (this.deps.drawingsDeleteAt?.(x2, y2) ?? false);
18883
19065
  if (this.middleDeleted) e.preventDefault();
18884
19066
  return;
18885
19067
  }
18886
19068
  if (e.button === 2) {
18887
- this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
19069
+ this.rightCancelled = this.drawings && (this.deps.drawingsCancelPlacement?.() ?? false);
18888
19070
  if (this.rightCancelled) e.preventDefault();
18889
19071
  return;
18890
19072
  }
@@ -18906,20 +19088,20 @@ var InputController = class {
18906
19088
  this.startY = y;
18907
19089
  this.prevPressDrawing = this.lastPressDrawing;
18908
19090
  this.lastPressDrawing = false;
18909
- if (this.deps.drawingsClaim?.(x, y)) {
19091
+ if (this.drawings && this.deps.drawingsClaim?.(x, y)) {
18910
19092
  this.region = "drawing";
18911
19093
  this.lastPressDrawing = true;
18912
19094
  this.deps.drawingsPointerDown?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
18913
19095
  this.capture(e.pointerId);
18914
19096
  return;
18915
19097
  }
18916
- if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
19098
+ if (this.drawings && e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
18917
19099
  this.region = "drawing";
18918
19100
  this.lastPressDrawing = true;
18919
19101
  this.capture(e.pointerId);
18920
19102
  return;
18921
19103
  }
18922
- if ((e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
19104
+ if (this.drawings && (e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
18923
19105
  this.region = "drawing";
18924
19106
  this.lastPressDrawing = true;
18925
19107
  this.capture(e.pointerId);
@@ -18996,7 +19178,7 @@ var InputController = class {
18996
19178
  const drawCursor = this.deps.drawingsCursor?.(x, y);
18997
19179
  const r = this.regionAt(x, y);
18998
19180
  this.el.style.cursor = drawCursor ?? (r === "price" ? "ns-resize" : r === "time" ? "ew-resize" : r === "separator" ? "row-resize" : "");
18999
- this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
19181
+ if (this.drawings) this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
19000
19182
  }
19001
19183
  if (e.pointerType !== "touch") this.deps.onPointerMove(x, y);
19002
19184
  };
@@ -19238,7 +19420,7 @@ var InputController = class {
19238
19420
  }
19239
19421
  /** Shared double-click / double-tap routing, by the region under the point. */
19240
19422
  doubleActivate(x, y) {
19241
- if (this.deps.drawingsDblClick?.(x, y)) return;
19423
+ if (this.drawings && this.deps.drawingsDblClick?.(x, y)) return;
19242
19424
  const region = this.regionAt(x, y);
19243
19425
  if (region === "price") this.deps.resetPriceScale(x, y);
19244
19426
  else if (region === "separator") this.deps.resetPaneSize(y);
@@ -21252,10 +21434,13 @@ function priceTicks(min, max, target = 6) {
21252
21434
  const norm = raw / mag;
21253
21435
  const step = (norm < 1.5 ? 1 : norm < 3 ? 2 : norm < 7 ? 5 : 10) * mag;
21254
21436
  const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);
21255
- const out = [];
21256
21437
  const start = Math.ceil(min / step) * step;
21257
- for (let v = start; v <= max + step * 1e-6; v += step) {
21258
- out.push(Number(v.toFixed(decimals)));
21438
+ if (!(step > 0) || start + step === start) return [];
21439
+ const count = Math.floor((max - start) / step + 1e-6) + 1;
21440
+ if (!(count > 0) || count > 1e3) return [];
21441
+ const out = [];
21442
+ for (let i = 0; i < count; i += 1) {
21443
+ out.push(Number((start + i * step).toFixed(decimals)));
21259
21444
  }
21260
21445
  return out;
21261
21446
  }
@@ -21453,6 +21638,100 @@ function calendarTicks(zFrom, zTo, months, offsetMs) {
21453
21638
  return out;
21454
21639
  }
21455
21640
 
21641
+ // src/renderers/native/chrome/priceAxisTicks.ts
21642
+ var PriceAxisTickSource = class {
21643
+ constructor() {
21644
+ this.fn = null;
21645
+ this.memo = /* @__PURE__ */ new WeakMap();
21646
+ this.warned = false;
21647
+ }
21648
+ /** The host function, or null for the built-in ticks. */
21649
+ get hook() {
21650
+ return this.fn;
21651
+ }
21652
+ /** Set (or clear) the host function. Drops every memoized result, so setting the same
21653
+ * function again recomputes — how a host refreshes ticks that depend on its own state. */
21654
+ setHook(fn) {
21655
+ this.fn = fn;
21656
+ this.memo = /* @__PURE__ */ new WeakMap();
21657
+ }
21658
+ /** The pane's master-scale ticks for this frame. An unscaled pane (`axisFormat: 'none'`)
21659
+ * gets none, and the host is never asked for it. */
21660
+ ticksFor(scene, pane, coords) {
21661
+ const format = pane.axisFormat;
21662
+ if (format === "none") return [];
21663
+ const pct = percentScaleFor(scene, pane);
21664
+ const height = pane.bounds.height;
21665
+ const fn = this.fn;
21666
+ if (!fn || !(height > 0)) return paneAxisTicks(pane.scale, height, pct, scene.priceMintick, format);
21667
+ const s = pane.scale;
21668
+ const m = this.memo.get(pane);
21669
+ const fontSize = scene.style.fontSize;
21670
+ 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) {
21671
+ return m.ticks;
21672
+ }
21673
+ const defaults2 = paneAxisTicks(s, height, pct, scene.priceMintick, format);
21674
+ const ticks = this.callHook(fn, scene, pane, coords, defaults2, pct?.baseline, pct ? pct.indexed ? "indexed" : "percent" : "price");
21675
+ this.memo.set(pane, {
21676
+ kind: pane.kind,
21677
+ min: s.min,
21678
+ max: s.max,
21679
+ log: !!s.log,
21680
+ invert: !!s.invert,
21681
+ height,
21682
+ baseline: pct?.baseline,
21683
+ indexed: !!pct?.indexed,
21684
+ mintick: scene.priceMintick,
21685
+ format,
21686
+ fontSize,
21687
+ ticks
21688
+ });
21689
+ return ticks;
21690
+ }
21691
+ callHook(fn, scene, pane, coords, defaults2, baseline, mode) {
21692
+ const scale = { min: pane.scale.min, max: pane.scale.max, log: !!pane.scale.log, invert: !!pane.scale.invert };
21693
+ const bounds = { top: 0, height: pane.bounds.height };
21694
+ const ctx = {
21695
+ pane: { id: pane.id, kind: pane.kind },
21696
+ min: Math.min(scale.min, scale.max),
21697
+ max: Math.max(scale.min, scale.max),
21698
+ log: !!scale.log,
21699
+ height: bounds.height,
21700
+ mode,
21701
+ baseline,
21702
+ mintick: scene.priceMintick,
21703
+ fontSize: scene.style.fontSize,
21704
+ priceToY: (price) => coords.priceToY(price, scale, bounds),
21705
+ defaults: defaults2
21706
+ };
21707
+ let out;
21708
+ try {
21709
+ out = fn(ctx);
21710
+ } catch (err) {
21711
+ this.warnOnce("threw", err);
21712
+ return defaults2;
21713
+ }
21714
+ if (out == null) return defaults2;
21715
+ if (!Array.isArray(out)) {
21716
+ this.warnOnce("returned a non-array", out);
21717
+ return defaults2;
21718
+ }
21719
+ const ticks = [];
21720
+ for (const t of out) {
21721
+ if (!t || typeof t !== "object") continue;
21722
+ const { price, label, major } = t;
21723
+ if (typeof price !== "number" || !Number.isFinite(price) || typeof label !== "string") continue;
21724
+ ticks.push(typeof major === "boolean" ? { price, label, major } : { price, label });
21725
+ }
21726
+ return ticks;
21727
+ }
21728
+ warnOnce(what, detail) {
21729
+ if (this.warned) return;
21730
+ this.warned = true;
21731
+ console.warn(`[vela] the priceAxisTicks function ${what} \u2014 the default ticks are used instead.`, detail);
21732
+ }
21733
+ };
21734
+
21456
21735
  // src/renderers/native/chrome/countdown.ts
21457
21736
  function countdownText(barOpen, barMs, now2) {
21458
21737
  if (!(barMs > 0)) return null;
@@ -21805,8 +22084,11 @@ function standaloneSvg(markup, ink, px) {
21805
22084
  }
21806
22085
 
21807
22086
  // src/renderers/native/chrome/ChromeRenderer.ts
22087
+ var MINOR_LABEL_ALPHA = 0.5;
21808
22088
  var ChromeRenderer = class {
21809
- constructor() {
22089
+ /** `axisTicks` is shared with the backdrop layer, so price labels and gridlines agree. */
22090
+ constructor(axisTicks = new PriceAxisTickSource()) {
22091
+ this.axisTicks = axisTicks;
21810
22092
  this.canvas = null;
21811
22093
  this.ctx = null;
21812
22094
  // The color for axis tick labels — the host-passed surface text, set each frame in render().
@@ -21911,7 +22193,8 @@ var ChromeRenderer = class {
21911
22193
  this.drawTimeAxis(ctx, scene, coords, theme, dataW, dataH, fullH);
21912
22194
  this.drawMarkLane(ctx, scene, coords, theme, dataW, dataH);
21913
22195
  }
21914
- /** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge. */
22196
+ /** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge,
22197
+ * but clipped to the data area so a glyph on the edge bar never paints over the price scale. */
21915
22198
  drawMarkLane(ctx, scene, coords, theme, dataW, dataH) {
21916
22199
  if (!scene.marks.visible || scene.timelineMarks.length === 0) {
21917
22200
  this.markLayout = { glyphs: [], stacks: /* @__PURE__ */ new Map() };
@@ -21929,6 +22212,10 @@ var ChromeRenderer = class {
21929
22212
  expanded: scene.marksExpandedStack
21930
22213
  });
21931
22214
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
22215
+ ctx.save();
22216
+ ctx.beginPath();
22217
+ ctx.rect(0, 0, dataW, dataH);
22218
+ ctx.clip();
21932
22219
  paintMarkLane(ctx, this.markLayout, {
21933
22220
  axisY: dataH,
21934
22221
  background: theme.background,
@@ -21942,6 +22229,7 @@ var ChromeRenderer = class {
21942
22229
  flashKey: scene.marksFlash && scene.marksFlash.until > nowMs ? scene.marksFlash.key : null,
21943
22230
  nowMs
21944
22231
  });
22232
+ ctx.restore();
21945
22233
  }
21946
22234
  barTimes(scene) {
21947
22235
  if (this.barTimesSrc !== scene.bars || this.barTimesCache.length !== scene.bars.length) {
@@ -21998,13 +22286,19 @@ var ChromeRenderer = class {
21998
22286
  if (!scene.showAxisLabels) return;
21999
22287
  ctx.fillStyle = this.axisTextColor;
22000
22288
  ctx.textAlign = "left";
22289
+ const font = `${scene.style.fontSize}px ${theme.fontFamily}`;
22290
+ const majorFont = `600 ${font}`;
22291
+ const alpha = ctx.globalAlpha;
22001
22292
  for (const pane of panes) {
22002
22293
  if (pane.collapsed) continue;
22003
- const pct = percentScaleFor(scene, pane);
22004
- for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, scene.priceMintick, pane.axisFormat)) {
22294
+ for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
22005
22295
  const y = coords.priceToY(t.price, pane.scale, pane.bounds);
22006
22296
  if (y < pane.bounds.top + 6 || y > pane.bounds.top + pane.bounds.height - 4) continue;
22297
+ if (t.major === true) ctx.font = majorFont;
22298
+ else if (t.major === false) ctx.globalAlpha = alpha * MINOR_LABEL_ALPHA;
22007
22299
  ctx.fillText(t.label, dataW + 6, y);
22300
+ if (t.major === true) ctx.font = font;
22301
+ else if (t.major === false) ctx.globalAlpha = alpha;
22008
22302
  }
22009
22303
  if (pane.axisBands) {
22010
22304
  for (const b of pane.axisBands) {
@@ -22246,15 +22540,23 @@ var CrosshairRenderer = class {
22246
22540
  constructor() {
22247
22541
  this.canvas = null;
22248
22542
  this.ctx = null;
22543
+ this.shade = null;
22249
22544
  }
22250
22545
  mount(canvas) {
22251
22546
  this.canvas = canvas;
22252
22547
  this.ctx = canvas.getContext("2d");
22253
22548
  }
22549
+ /** The plot area the last frame veiled for `crosshairOverride.shadeRight` — from the
22550
+ * picked bar's right edge to the price scale, full plot height (the union when both the
22551
+ * local and a synced crosshair veil) — or null when no veil was painted. */
22552
+ get shadeRightArea() {
22553
+ return this.shade;
22554
+ }
22254
22555
  /** Clear the cursor canvas and (re)draw the crosshair lines + axis chips. The optional
22255
22556
  * `separatorHoverY` highlights the draggable pane separator under the cursor;
22256
22557
  * `external` is a SYNCED ghost crosshair (another chart's pointer, pixel-resolved). */
22257
22558
  render(scene, coords, theme, separatorHoverY = null, external = null) {
22559
+ this.shade = null;
22258
22560
  const ctx = this.ctx;
22259
22561
  const canvas = this.canvas;
22260
22562
  if (!ctx || !canvas) return;
@@ -22277,12 +22579,7 @@ var CrosshairRenderer = class {
22277
22579
  const x = crisp(coords.logicalToX(logical), ov?.width ?? cs.width);
22278
22580
  if (vertical && ov?.shadeRight) {
22279
22581
  const from = Math.max(0, Math.round(coords.logicalToX(logical + 0.5)));
22280
- if (from < dataW) {
22281
- ctx.fillStyle = ov.shadeRight.color;
22282
- ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
22283
- ctx.fillRect(from, 0, dataW - from, dataH);
22284
- ctx.globalAlpha = 1;
22285
- }
22582
+ if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
22286
22583
  }
22287
22584
  ctx.strokeStyle = ov?.color ?? cs.color ?? theme.textColor;
22288
22585
  ctx.lineWidth = ov?.width ?? cs.width;
@@ -22318,6 +22615,7 @@ var CrosshairRenderer = class {
22318
22615
  destroy() {
22319
22616
  this.canvas = null;
22320
22617
  this.ctx = null;
22618
+ this.shade = null;
22321
22619
  }
22322
22620
  /** The synced ghost: a dimmed vertical line at the bar the renderer resolved as
22323
22621
  * CONTAINING the foreign time (+ horizontal line when a comparable price came
@@ -22337,12 +22635,7 @@ var CrosshairRenderer = class {
22337
22635
  if (ov?.vertical === false) return;
22338
22636
  if (ov?.shadeRight) {
22339
22637
  const from = Math.max(0, Math.round(coords.logicalToX(Math.round(coords.xToLogical(ext.x)) + 0.5)));
22340
- if (from < dataW) {
22341
- ctx.fillStyle = ov.shadeRight.color;
22342
- ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
22343
- ctx.fillRect(from, 0, dataW - from, dataH);
22344
- ctx.globalAlpha = 1;
22345
- }
22638
+ if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
22346
22639
  }
22347
22640
  if (ext.line === false || x < 0 || x > dataW) return;
22348
22641
  ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
@@ -22378,6 +22671,15 @@ var CrosshairRenderer = class {
22378
22671
  this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
22379
22672
  ctx.globalAlpha = 1;
22380
22673
  }
22674
+ /** Paint the `shadeRight` veil from `from` to the price scale and record the veiled area. */
22675
+ paintShade(ctx, shade, from, dataW, dataH) {
22676
+ ctx.fillStyle = shade.color;
22677
+ ctx.globalAlpha = shade.opacity ?? 1;
22678
+ ctx.fillRect(from, 0, dataW - from, dataH);
22679
+ ctx.globalAlpha = 1;
22680
+ const x = this.shade ? Math.min(this.shade.x, from) : from;
22681
+ this.shade = { x, width: dataW - x, height: dataH };
22682
+ }
22381
22683
  /** A soft band + a brighter crisp center line over the hovered separator, so it reads as
22382
22684
  * a draggable handle (the cursor is already `row-resize`). Spans the full width (data +
22383
22685
  * scale gutter) to match the separator itself. Theme-derived (textColor). */
@@ -25873,15 +26175,16 @@ var DrawingSettingsDialog = class {
25873
26175
  }
25874
26176
  open(drawing, actions, kind) {
25875
26177
  this.close();
25876
- const snapshot = clonePlain(drawing.serialize());
26178
+ const live = actions.resolve() ?? drawing;
26179
+ const snapshot = clonePlain(live.serialize());
25877
26180
  const grid = fieldGrid({ variant: "inputs" });
25878
26181
  grid.style.padding = "16px 20px";
25879
26182
  grid.style.overflowY = "auto";
25880
26183
  grid.style.overflowX = "hidden";
25881
26184
  grid.style.flex = "1 1 auto";
25882
- if (kind === "position" && drawing instanceof PositionTool) this.buildPosition(grid, drawing, actions);
25883
- else if (kind === "frvp" && drawing instanceof FixedRangeVolumeProfile) this.buildFrvp(grid, drawing, actions);
25884
- else if (kind === "levels") this.buildLevels(grid, drawing, actions);
26185
+ if (kind === "position" && live instanceof PositionTool) this.buildPosition(grid, live, actions);
26186
+ else if (kind === "frvp" && live instanceof FixedRangeVolumeProfile) this.buildFrvp(grid, live, actions);
26187
+ else if (kind === "levels") this.buildLevels(grid, live, actions);
25885
26188
  else return;
25886
26189
  const ui = new Dialog({
25887
26190
  host: this.host,
@@ -26528,7 +26831,7 @@ var DrawingSettingsPopup = class {
26528
26831
  this.reposition();
26529
26832
  return;
26530
26833
  }
26531
- const text = drawing.text;
26834
+ const text = (actions.resolve() ?? drawing).text;
26532
26835
  const panel = document.createElement("div");
26533
26836
  panel.style.cssText = `padding:6px;border-top:1px solid var(--vela-border);display:flex;flex-direction:column;gap:4px;`;
26534
26837
  const ta = new TextArea({
@@ -27124,6 +27427,7 @@ function sized(svg, size = ICON) {
27124
27427
  // src/renderers/native/drawings/DrawingToolbar.ts
27125
27428
  var TOOLBAR_WIDTH = 44;
27126
27429
  var TOOLBAR_COLLAPSED_WIDTH = 16;
27430
+ var FLYOUT_GAP = 3;
27127
27431
  var DrawingToolbar = class {
27128
27432
  constructor(host, theme, onArm, onMagnet = () => {
27129
27433
  }, onMeasure = () => {
@@ -27153,6 +27457,9 @@ var DrawingToolbar = class {
27153
27457
  // group id (or MAGNET_ID) whose flyout is open
27154
27458
  this.flyoutCell = null;
27155
27459
  // the cell the open flyout is anchored to
27460
+ /** Closed flyouts still playing their exit animation, by owner id — reopening the same
27461
+ * owner takes its element back instead of stacking a second one on it. */
27462
+ this.exitingFlyouts = /* @__PURE__ */ new Map();
27156
27463
  this.groupCells = /* @__PURE__ */ new Map();
27157
27464
  // the composite cell (hover/active bg + flyout anchor)
27158
27465
  this.groupIcons = /* @__PURE__ */ new Map();
@@ -27261,6 +27568,7 @@ var DrawingToolbar = class {
27261
27568
  }
27262
27569
  destroy() {
27263
27570
  this.closeFlyout();
27571
+ for (const { exit } of [...this.exitingFlyouts.values()]) exit.finish();
27264
27572
  for (const dispose of this.tipDisposers.splice(0)) dispose();
27265
27573
  this.root.remove();
27266
27574
  }
@@ -27488,16 +27796,22 @@ var DrawingToolbar = class {
27488
27796
  * outside-dismiss. Callers fill it with items. */
27489
27797
  beginFlyout(ownerId, cell) {
27490
27798
  const t = this.theme;
27491
- const fly = document.createElement("div");
27799
+ const back = this.exitingFlyouts.get(ownerId);
27800
+ if (back) {
27801
+ this.exitingFlyouts.delete(ownerId);
27802
+ back.exit.cancel();
27803
+ back.fly.replaceChildren();
27804
+ }
27805
+ const fly = back?.fly ?? document.createElement("div");
27492
27806
  fly.className = "vela-dtb-flyout";
27493
- 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;`;
27807
+ 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;`;
27494
27808
  applyChromeTokens(fly, t);
27495
27809
  this.host.appendChild(fly);
27496
27810
  const r = cell.getBoundingClientRect();
27497
27811
  const rootR = this.root.getBoundingClientRect();
27498
27812
  const hostR = this.host.getBoundingClientRect();
27499
27813
  const top = r.top - hostR.top;
27500
- fly.style.left = `${rootR.right - hostR.left - 1}px`;
27814
+ fly.style.left = `${rootR.right - hostR.left + FLYOUT_GAP}px`;
27501
27815
  fly.style.top = `${top}px`;
27502
27816
  fly.style.maxHeight = `${Math.max(120, hostR.height - top - 8)}px`;
27503
27817
  this.flyout = fly;
@@ -27513,6 +27827,7 @@ var DrawingToolbar = class {
27513
27827
  if (!cell) return;
27514
27828
  const fly = this.beginFlyout(group.id, cell);
27515
27829
  const sections = group.sections ?? [{ label: "", tools: group.tools }];
27830
+ const badge = sections.some((s) => s.tools.some((tool) => !!tool.icon));
27516
27831
  for (let si = 0; si < sections.length; si++) {
27517
27832
  const section = sections[si];
27518
27833
  if (section.label) {
@@ -27523,6 +27838,7 @@ var DrawingToolbar = class {
27523
27838
  fly.appendChild(
27524
27839
  this.makeFlyoutItem({
27525
27840
  icon: tool.icon,
27841
+ badge,
27526
27842
  label: tool.label,
27527
27843
  selected: tool.type === this.active,
27528
27844
  shortcut: this.shortcuts.get(tool.type),
@@ -27573,21 +27889,24 @@ var DrawingToolbar = class {
27573
27889
  div.style.cssText = `height:1px;margin:4px 8px;background:${this.borderColor};`;
27574
27890
  return div;
27575
27891
  }
27576
- /** A flyout row, left to right: optional leading icon, label, a check when it's the selected
27577
- * entry, an optional shortcut hint, and — at the far right — the favorite star (tool rows).
27578
- * Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
27892
+ /** A flyout row, left to right: optional leading icon badge, label, a check when it's the
27893
+ * selected entry, an optional shortcut hint, and — at the far right — the favorite star (tool
27894
+ * rows). Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
27579
27895
  makeFlyoutItem(opts) {
27580
27896
  const t = this.theme;
27581
27897
  const item = document.createElement("button");
27582
27898
  item.type = "button";
27583
27899
  item.className = "vela-dtb-item";
27584
27900
  item.setAttribute("aria-label", opts.label);
27585
- 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;`;
27586
- if (opts.icon) {
27587
- const icon2 = document.createElement("span");
27588
- icon2.style.cssText = "width:18px;height:18px;display:flex;align-items:center;justify-content:center;flex:none;";
27589
- icon2.innerHTML = sizedIcon(opts.icon);
27590
- item.appendChild(icon2);
27901
+ const badged = opts.badge === true || !!opts.icon;
27902
+ const box = badged ? "gap:10px;padding:4px 10px 4px 6px;" : "gap:8px;padding:5px 10px 5px 8px;";
27903
+ 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;`;
27904
+ if (opts.selected) item.dataset.selected = "1";
27905
+ if (badged) {
27906
+ const badge = document.createElement("span");
27907
+ badge.className = "vela-dtb-badge";
27908
+ if (opts.icon) badge.innerHTML = opts.icon;
27909
+ item.appendChild(badge);
27591
27910
  }
27592
27911
  const label = document.createElement("span");
27593
27912
  label.textContent = opts.label;
@@ -27632,7 +27951,12 @@ var DrawingToolbar = class {
27632
27951
  if (fly) {
27633
27952
  this.flyout = null;
27634
27953
  announceSurface(fly, false, "menu", this.flyoutCell);
27635
- fly.remove();
27954
+ const owner = this.flyoutOwnerId ?? "";
27955
+ const exit = holdForExit(fly, () => {
27956
+ if (this.exitingFlyouts.get(owner)?.fly === fly) this.exitingFlyouts.delete(owner);
27957
+ fly.remove();
27958
+ });
27959
+ if (exit) this.exitingFlyouts.set(owner, { fly, exit });
27636
27960
  this.flyoutCell?.classList.remove("vela-open");
27637
27961
  this.flyoutCell = null;
27638
27962
  this.flyoutOwnerId = null;
@@ -27716,6 +28040,11 @@ function ensureStyles4() {
27716
28040
  .vela-dtb[data-collapsed='1'] .vela-dtb-collapse .vela-dtb-hit{width:14px;}
27717
28041
  .vela-dtb-item{background:transparent;border:none;transition:background var(--vela-dur-fast) ease;}
27718
28042
  .vela-dtb-item:hover{background:var(--vela-hover-strong);}
28043
+ .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;}
28044
+ .vela-dtb-badge:empty{background:transparent;border-color:transparent;}
28045
+ .vela-dtb-badge svg{width:100%;height:100%;}
28046
+ .vela-dtb-item:hover .vela-dtb-badge:not(:empty){background:var(--vela-active);color:var(--vela-fg-bright);}
28047
+ .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);}
27719
28048
  .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;}
27720
28049
  .vela-dtb-star svg{width:16px;height:16px;}
27721
28050
  .vela-dtb-item:hover .vela-dtb-star{opacity:.55;}
@@ -28335,6 +28664,12 @@ var UserDrawingController = class {
28335
28664
  this.render();
28336
28665
  }
28337
28666
  }
28667
+ /** Drop the hover (its handles) — the pointer no longer reaches the drawings. */
28668
+ clearHover() {
28669
+ if (this.hoveredId == null) return;
28670
+ this.hoveredId = null;
28671
+ this.render();
28672
+ }
28338
28673
  pointerUp(x, y, snap = "off") {
28339
28674
  if (this.eraserMode) {
28340
28675
  this.erasing = false;
@@ -29325,9 +29660,10 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
29325
29660
  consider(drawings.max);
29326
29661
  }
29327
29662
  if (min === Infinity || max === -Infinity) return { min: 0, max: 1 };
29328
- if (min === max) {
29329
- const pad = Math.abs(min) * 0.1 || 1;
29330
- return { min: min - pad, max: max + pad, log: log && min - pad > 0 };
29663
+ if (max - min <= Math.max(Math.abs(min), Math.abs(max)) * 1e-12) {
29664
+ const mid = (min + max) / 2;
29665
+ const pad = Math.abs(mid) * 0.1 || 1;
29666
+ return { min: mid - pad, max: mid + pad, log: log && mid - pad > 0 };
29331
29667
  }
29332
29668
  const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
29333
29669
  const above = margins.top / 100 / content;
@@ -29831,7 +30167,9 @@ function bandEdgeSlot(logical) {
29831
30167
  return Math.ceil(logical) - 0.5;
29832
30168
  }
29833
30169
  var BackdropRenderer = class {
29834
- constructor() {
30170
+ /** `axisTicks` is shared with the chrome layer, so gridlines and price labels agree. */
30171
+ constructor(axisTicks = new PriceAxisTickSource()) {
30172
+ this.axisTicks = axisTicks;
29835
30173
  this.canvas = null;
29836
30174
  this.ctx = null;
29837
30175
  }
@@ -29908,9 +30246,8 @@ var BackdropRenderer = class {
29908
30246
  if (scene.showGrid && gridHorz.visible && !pane.collapsed) {
29909
30247
  ctx.globalAlpha = gridAlpha;
29910
30248
  ctx.strokeStyle = horzColor;
29911
- const pct = percentScaleFor(scene, pane);
29912
30249
  ctx.beginPath();
29913
- for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, void 0, pane.axisFormat)) {
30250
+ for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
29914
30251
  const y = Math.round(coords.priceToY(t.price, pane.scale, pane.bounds)) + 0.5;
29915
30252
  if (y < pane.bounds.top || y > pane.bounds.top + pane.bounds.height) continue;
29916
30253
  ctx.moveTo(0, y);
@@ -30526,8 +30863,16 @@ var NativeRenderer = class {
30526
30863
  this.toolbarGutter = 0;
30527
30864
  // px reserved on the left for the docked drawings toolbar (0 when hidden)
30528
30865
  this.mountContainer = null;
30866
+ /** The host-styleable twin of the `shadeRight` veil (`.vela-shade-right`), directly under
30867
+ * the cursor canvas; hidden whenever the crosshair layer paints no veil. */
30868
+ this.shadeRightEl = null;
30869
+ /** The veil geometry last written to {@link shadeRightEl} ('' = hidden) — the DOM is
30870
+ * touched only when it changes, never on a plain pointer move. */
30871
+ this.shadeRightSig = "";
30529
30872
  this.userDrawings = null;
30530
- this.backdropRenderer = new BackdropRenderer();
30873
+ /** One price-axis tick source for the gridlines (backdrop) and the labels (chrome). */
30874
+ this.priceAxisTicks = new PriceAxisTickSource();
30875
+ this.backdropRenderer = new BackdropRenderer(this.priceAxisTicks);
30531
30876
  this.volumeRenderer = new VolumeRenderer();
30532
30877
  /** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
30533
30878
  /**
@@ -30560,7 +30905,7 @@ var NativeRenderer = class {
30560
30905
  this.backendMode = "auto";
30561
30906
  this.glowAmount = 0;
30562
30907
  // WebGL2 neon-glow intensity (canvas2d ignores it)
30563
- this.chrome = new ChromeRenderer();
30908
+ this.chrome = new ChromeRenderer(this.priceAxisTicks);
30564
30909
  /** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
30565
30910
  this.indicatorSlices = new IndicatorDrawingSlices();
30566
30911
  /** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
@@ -30677,6 +31022,8 @@ var NativeRenderer = class {
30677
31022
  // pane just below it
30678
31023
  this.resizeSplitStart = null;
30679
31024
  // the two panes' shared span when the drag began
31025
+ /** The user drawings take part in pointer input (the runtime `drawingsInteractive` feature). */
31026
+ this.drawingsInteractive = true;
30680
31027
  this.hoverSeparatorY = null;
30681
31028
  // pixel y of the separator under the cursor (drives its hover highlight)
30682
31029
  // ── pane management (merge / reorder / collapse / maximize) ──
@@ -30732,8 +31079,9 @@ var NativeRenderer = class {
30732
31079
  this.toggleVisibleCbs = /* @__PURE__ */ new Set();
30733
31080
  this.moveIndicatorCbs = /* @__PURE__ */ new Set();
30734
31081
  this.priceStyleCbs = /* @__PURE__ */ new Set();
31082
+ this.priceStyleWillChangeCbs = /* @__PURE__ */ new Set();
30735
31083
  this.name = "native";
30736
- 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"];
31084
+ 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"];
30737
31085
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
30738
31086
  * so moving onto the button doesn't count as leaving). */
30739
31087
  this.onScrollProximityMove = (e) => {
@@ -30777,6 +31125,7 @@ var NativeRenderer = class {
30777
31125
  this.scene.priceStyle = opts.priceStyle;
30778
31126
  this.scene.basePainting = basePaintingOf(opts.priceStyle);
30779
31127
  this.scene.candleOverride = candleOverrideFor(opts.priceStyle, this.scene.style.chartTypes);
31128
+ if (typeof opts.priceAxisTicks === "function") this.priceAxisTicks.setHook(opts.priceAxisTicks);
30780
31129
  }
30781
31130
  this.theme = this.deriveTheme(DARK_THEME);
30782
31131
  }
@@ -30915,6 +31264,15 @@ var NativeRenderer = class {
30915
31264
  case "crosshairOverride":
30916
31265
  this.scene.crosshairOverride = sanitizeCrosshairOverride(value);
30917
31266
  break;
31267
+ case "drawingsInteractive":
31268
+ this.drawingsInteractive = value !== false;
31269
+ if (this.input) this.input.drawings = this.drawingsInteractive;
31270
+ if (!this.drawingsInteractive) this.userDrawings?.clearHover();
31271
+ return;
31272
+ // affects the next gesture only — clearHover repaints what it drops
31273
+ case "priceAxisTicks":
31274
+ this.priceAxisTicks.setHook(typeof value === "function" ? value : null);
31275
+ break;
30918
31276
  case "keyboard":
30919
31277
  this.setKeyboardEnabled(Boolean(value));
30920
31278
  return;
@@ -31030,6 +31388,10 @@ var NativeRenderer = class {
31030
31388
  return { visible: this.scene.marks.visible, groups: { ...this.scene.marks.groups } };
31031
31389
  case "crosshairOverride":
31032
31390
  return this.scene.crosshairOverride ? { ...this.scene.crosshairOverride } : null;
31391
+ case "drawingsInteractive":
31392
+ return this.drawingsInteractive;
31393
+ case "priceAxisTicks":
31394
+ return this.priceAxisTicks.hook;
31033
31395
  case "keyboard":
31034
31396
  return this.keyboardEnabled;
31035
31397
  case "historyChords":
@@ -31703,6 +32065,12 @@ var NativeRenderer = class {
31703
32065
  Object.assign(this.drawingsCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
31704
32066
  this.cursorCanvas = document.createElement("canvas");
31705
32067
  Object.assign(this.cursorCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
32068
+ this.shadeRightEl = document.createElement("div");
32069
+ this.shadeRightEl.className = "vela-shade-right";
32070
+ this.shadeRightEl.setAttribute("aria-hidden", "true");
32071
+ this.shadeRightEl.hidden = true;
32072
+ Object.assign(this.shadeRightEl.style, { position: "absolute", top: "0", left: "0", width: "0", height: "0", pointerEvents: "none", display: "none" });
32073
+ this.shadeRightSig = "";
31706
32074
  this.overlayRoot = document.createElement("div");
31707
32075
  Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
31708
32076
  this.plot = document.createElement("div");
@@ -31710,7 +32078,7 @@ var NativeRenderer = class {
31710
32078
  this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
31711
32079
  const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
31712
32080
  const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
31713
- this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
32081
+ this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.shadeRightEl, this.cursorCanvas, this.overlayRoot);
31714
32082
  this.layerOrderSig = "";
31715
32083
  this.wrapper.appendChild(this.plot);
31716
32084
  this.factoryConfig = this.getConfig();
@@ -31768,13 +32136,14 @@ var NativeRenderer = class {
31768
32136
  drawingsPointerDown: (x, y, snap, shift2, mod) => this.userDrawings?.pointerDown(x, y, snap, shift2, mod),
31769
32137
  drawingsPointerMove: (x, y, snap, shift2, mod) => this.userDrawings?.pointerMove(x, y, snap, shift2, mod),
31770
32138
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
31771
- drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
32139
+ drawingsCursor: (x, y) => (this.drawingsInteractive ? this.userDrawings?.cursorAt(x, y) : null) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
31772
32140
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
31773
32141
  drawingsClearTransient: () => this.userDrawings?.clearTransient()
31774
32142
  });
31775
32143
  this.input.rightEdgeZoom = this.zoomAnchorMode === "right";
31776
32144
  this.input.axisDrag = this.axisDragEnabled;
31777
32145
  this.input.paneResize = this.paneResizeEnabled;
32146
+ this.input.drawings = this.drawingsInteractive;
31778
32147
  this.input.attach(this.dataCanvas);
31779
32148
  if (this.settingsEnabled) this.setSettingsEnabled(true);
31780
32149
  this.syncCountdownTimer();
@@ -32083,6 +32452,8 @@ var NativeRenderer = class {
32083
32452
  this.backend.destroy();
32084
32453
  this.chrome.destroy();
32085
32454
  this.crosshairLayer.destroy();
32455
+ this.shadeRightEl?.remove();
32456
+ this.shadeRightEl = null;
32086
32457
  this.attributionEl?.remove();
32087
32458
  this.attributionEl = null;
32088
32459
  this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
@@ -32429,14 +32800,21 @@ var NativeRenderer = class {
32429
32800
  this.priceStyleCbs.add(cb);
32430
32801
  return () => this.priceStyleCbs.delete(cb);
32431
32802
  }
32803
+ onPriceStyleWillChange(cb) {
32804
+ this.priceStyleWillChangeCbs.add(cb);
32805
+ return () => this.priceStyleWillChangeCbs.delete(cb);
32806
+ }
32432
32807
  /**
32433
32808
  * THE single write path for the base price style at runtime (feature set / settings dialog /
32434
- * config template — the constructor seeds the field directly, pre-listeners). Updates the
32435
- * scene, eases any reveal layer toward the new style's target, and notifies the core —
32436
- * which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
32809
+ * config template — the constructor seeds the field directly, pre-listeners). Announces the
32810
+ * switch while the scene still holds the old style (a listener may paint/capture that frame),
32811
+ * then updates the scene, eases any reveal layer toward the new style's target, and notifies
32812
+ * the core — which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
32437
32813
  */
32438
32814
  setPriceStyle(style) {
32439
32815
  if (style === this.scene.priceStyle) return;
32816
+ const from = this.scene.priceStyle;
32817
+ for (const cb of this.priceStyleWillChangeCbs) cb(from, style);
32440
32818
  this.scene.priceStyle = style;
32441
32819
  this.scene.basePainting = basePaintingOf(style);
32442
32820
  this.scene.candleOverride = candleOverrideFor(style, this.scene.style.chartTypes);
@@ -32666,7 +33044,7 @@ var NativeRenderer = class {
32666
33044
  if (this.easeLiveBar(dtMs)) active = true;
32667
33045
  this.skeletonClockMs += dtMs;
32668
33046
  this.paintData();
32669
- this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
33047
+ this.renderCrosshairLayer();
32670
33048
  this.updateLegendValues();
32671
33049
  this.emitViewportChange();
32672
33050
  return active;
@@ -33241,10 +33619,31 @@ var NativeRenderer = class {
33241
33619
  this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
33242
33620
  this.trackMarkPopover();
33243
33621
  }
33244
- this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
33622
+ this.renderCrosshairLayer();
33245
33623
  if (!repaintsData(level) && this.paintedData) this.repaintCursorLayers();
33246
33624
  this.updateLegendValues();
33247
33625
  }
33626
+ /** Repaint the L2 crosshair canvas and keep the `.vela-shade-right` element on the veil it painted. */
33627
+ renderCrosshairLayer() {
33628
+ this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
33629
+ this.syncShadeRight();
33630
+ }
33631
+ /** Position (or hide) the `.vela-shade-right` element over the veil of the last crosshair frame. */
33632
+ syncShadeRight() {
33633
+ const el = this.shadeRightEl;
33634
+ if (!el) return;
33635
+ const area = this.crosshairLayer.shadeRightArea;
33636
+ const sig = area ? `${area.x},${area.width},${area.height}` : "";
33637
+ if (sig === this.shadeRightSig) return;
33638
+ this.shadeRightSig = sig;
33639
+ if (!area) {
33640
+ el.hidden = true;
33641
+ el.style.display = "none";
33642
+ return;
33643
+ }
33644
+ Object.assign(el.style, { left: `${area.x}px`, width: `${area.width}px`, height: `${area.height}px`, display: "" });
33645
+ el.hidden = false;
33646
+ }
33248
33647
  /** Repaint the SDK layers that opted into cursor tracking (their own canvas only). */
33249
33648
  repaintCursorLayers() {
33250
33649
  const pane = this.scene.panes.get(PRICE_PANE_ID);
@@ -34038,7 +34437,7 @@ var NativeRenderer = class {
34038
34437
  if (this.animator.active) {
34039
34438
  this.computeScales();
34040
34439
  this.paintData();
34041
- this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
34440
+ this.renderCrosshairLayer();
34042
34441
  } else {
34043
34442
  this.scheduler.flushNow(4 /* Full */);
34044
34443
  }
@@ -38082,7 +38481,8 @@ var Vela = class {
38082
38481
  glow: options.glow ?? 0,
38083
38482
  upColor: options.upColor ?? BULLISH,
38084
38483
  downColor: options.downColor ?? BEARISH,
38085
- priceStyle: options.priceStyle ?? "candles"
38484
+ priceStyle: options.priceStyle ?? "candles",
38485
+ priceAxisTicks: options.priceAxis?.ticks ?? null
38086
38486
  };
38087
38487
  const RendererClass = options.renderer ?? NativeRenderer;
38088
38488
  if (typeof RendererClass !== "function") {
@@ -38568,6 +38968,23 @@ function resolveEngines(overrides) {
38568
38968
  return { ...Object.fromEntries(defaultEngines), ...overrides };
38569
38969
  }
38570
38970
 
38971
+ // src/widget/context-menu-model.ts
38972
+ var CONTEXT_MENU_BUILTIN_ORDER = {
38973
+ body: { "reset-view": -30, "remove-drawings": -20, "remove-indicators": -10, settings: 1e3 },
38974
+ "price-axis": {
38975
+ auto: -80,
38976
+ invert: -70,
38977
+ "scale:regular": -60,
38978
+ "scale:percent": -50,
38979
+ "scale:indexed": -40,
38980
+ "scale:log": -30,
38981
+ labels: -20,
38982
+ levels: -10,
38983
+ settings: 1e3
38984
+ },
38985
+ "time-axis": { timezone: -10, settings: 1e3 }
38986
+ };
38987
+
38571
38988
  // src/widget/side-panel.ts
38572
38989
  var DEFAULT_PANEL_WIDTH = 280;
38573
38990
  var DEFAULT_PANEL_MIN_WIDTH = 200;
@@ -38586,6 +39003,7 @@ exports.BULLISH = BULLISH;
38586
39003
  exports.BarStore = BarStore;
38587
39004
  exports.CATEGORICAL = CATEGORICAL;
38588
39005
  exports.CHIP_PLATE = CHIP_PLATE;
39006
+ exports.CONTEXT_MENU_BUILTIN_ORDER = CONTEXT_MENU_BUILTIN_ORDER;
38589
39007
  exports.CROSSHAIR = CROSSHAIR;
38590
39008
  exports.CachingDataFeed = CachingDataFeed;
38591
39009
  exports.DARK_THEME = DARK_THEME;