@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
@@ -7864,6 +7864,7 @@ var Vela = (function (exports) {
7864
7864
  this.activeEngineStyle = null;
7865
7865
  /** The chart's current price style (tracked from the renderer's change events + initial read). */
7866
7866
  this.priceStyle = "candles";
7867
+ this.priceStyleWillChangeUnsub = null;
7867
7868
  this.unresolvedUnsub = null;
7868
7869
  /** Latest chart visible range (left/right bar times), fed to viewport-dependent scripts. */
7869
7870
  this.visibleRange = null;
@@ -7962,6 +7963,7 @@ var Vela = (function (exports) {
7962
7963
  this.renderer.onThemeSelect?.((name) => this.setTheme(resolveTheme(name)));
7963
7964
  this.viewportUnsub = this.renderer.onViewportChange((range) => this.onViewportChange(range));
7964
7965
  this.renderer.onPriceStyleChange?.((style) => this.syncPriceStyle(style));
7966
+ this.priceStyleWillChangeUnsub = this.renderer.onPriceStyleWillChange?.((from, to) => this.events.emit("priceStyle:change", { from, to })) ?? null;
7965
7967
  const initialStyle = this.renderer.readFeature("priceStyle");
7966
7968
  if (typeof initialStyle === "string") this.priceStyle = initialStyle;
7967
7969
  this.barTransform = barTransformFor(initialStyle);
@@ -9492,6 +9494,8 @@ var Vela = (function (exports) {
9492
9494
  if (this.viewportTimer != null) clearTimeout(this.viewportTimer);
9493
9495
  this.viewportUnsub?.();
9494
9496
  this.paneActionUnsub?.();
9497
+ this.priceStyleWillChangeUnsub?.();
9498
+ this.priceStyleWillChangeUnsub = null;
9495
9499
  for (const record of this.registry.all()) {
9496
9500
  this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
9497
9501
  record.session?.stop();
@@ -13705,6 +13709,55 @@ ${STATIC_DECLS}
13705
13709
  );
13706
13710
  }
13707
13711
 
13712
+ // src/ui/surface-exit.ts
13713
+ var CLOSING_ATTR = "data-closing";
13714
+ var SURFACE_EXIT_CAP_MS = 1e3;
13715
+ function animationsOf(els) {
13716
+ return els.flatMap((el) => el.getAnimations({ subtree: true }));
13717
+ }
13718
+ function ends(a) {
13719
+ return Number.isFinite(a.effect?.getComputedTiming().endTime ?? Number.POSITIVE_INFINITY);
13720
+ }
13721
+ function holdForExit(els, done, opts = {}) {
13722
+ const list = Array.isArray(els) ? els : [els];
13723
+ if (list.length === 0 || !list.every((el) => typeof el.getAnimations === "function")) {
13724
+ done();
13725
+ return null;
13726
+ }
13727
+ const before = new Set(animationsOf(list));
13728
+ for (const el of list) el.setAttribute(CLOSING_ATTR, "");
13729
+ const exit = animationsOf(list).filter((a) => !before.has(a) && ends(a));
13730
+ if (exit.length === 0) {
13731
+ for (const el of list) el.removeAttribute(CLOSING_ATTR);
13732
+ done();
13733
+ return null;
13734
+ }
13735
+ const frozen = [...list, ...opts.inert ?? []];
13736
+ const wasInert = frozen.map((el) => el.inert);
13737
+ for (const el of frozen) el.inert = true;
13738
+ let settled = false;
13739
+ const settle = (complete) => {
13740
+ if (settled) return;
13741
+ settled = true;
13742
+ clearTimeout(cap);
13743
+ for (const el of list) el.removeAttribute(CLOSING_ATTR);
13744
+ frozen.forEach((el, i) => {
13745
+ el.inert = wasInert[i];
13746
+ });
13747
+ if (complete) done();
13748
+ };
13749
+ const cap = setTimeout(() => settle(true), SURFACE_EXIT_CAP_MS);
13750
+ let pending = exit.length;
13751
+ const one = () => {
13752
+ if (--pending === 0) settle(true);
13753
+ };
13754
+ for (const a of exit) a.finished.then(one, one);
13755
+ return {
13756
+ cancel: () => settle(false),
13757
+ finish: () => settle(true)
13758
+ };
13759
+ }
13760
+
13708
13761
  // node_modules/@zag-js/anatomy/dist/create-anatomy.mjs
13709
13762
  var createAnatomy = (name, parts3 = []) => ({
13710
13763
  parts: (...values) => {
@@ -18117,6 +18170,35 @@ ${STATIC_DECLS}
18117
18170
  color: var(--vela-fg-bright);
18118
18171
  }
18119
18172
  .vela-menu-item[data-checkmark] { color: var(--vela-fg-bright); }
18173
+ /* Badge mode (iconBadges): the row icon sits in a rounded tile and the row grows to fit
18174
+ it. An icon-less row of the same level keeps an invisible slot, so labels align. */
18175
+ .vela-menu[data-badges] > .vela-menu-item { padding: 5px 10px 5px 6px; }
18176
+ .vela-menu-item .vela-menu-badge {
18177
+ width: 24px;
18178
+ height: 24px;
18179
+ padding: 3px;
18180
+ flex: none;
18181
+ box-sizing: border-box;
18182
+ display: inline-flex;
18183
+ align-items: center;
18184
+ justify-content: center;
18185
+ border-radius: var(--vela-radius-md);
18186
+ background: var(--vela-hover);
18187
+ border: 1px solid var(--vela-border);
18188
+ color: var(--vela-fg-muted);
18189
+ 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);
18190
+ }
18191
+ .vela-menu-item .vela-menu-badge:empty { background: transparent; border-color: transparent; }
18192
+ .vela-menu-item .vela-menu-badge .vela-icon { color: inherit; }
18193
+ .vela-menu-item[data-highlighted] .vela-menu-badge:not(:empty) { color: var(--vela-fg-bright); }
18194
+ /* The selected tile inverts: a bright fill under the surface-colored glyph. After the
18195
+ hover rule, so a selected row keeps its tile while highlighted. */
18196
+ .vela-menu-item[data-checked] .vela-menu-badge:not(:empty),
18197
+ .vela-menu-item[data-checkmark] .vela-menu-badge:not(:empty) {
18198
+ background: color-mix(in srgb, var(--vela-fg-bright) 83%, var(--vela-surface));
18199
+ border-color: color-mix(in srgb, var(--vela-fg-bright) 40%, transparent);
18200
+ color: var(--vela-surface);
18201
+ }
18120
18202
  /* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
18121
18203
  same control language as the settings dialog's toggles. */
18122
18204
  .vela-menu-switch {
@@ -18185,11 +18267,14 @@ ${STATIC_DECLS}
18185
18267
  this.pinnedAnchor = null;
18186
18268
  /** The open state last announced — see {@link announce}. */
18187
18269
  this.shown = false;
18270
+ /** The list's exit animation after a close; a reopen cancels it. */
18271
+ this.exit = null;
18188
18272
  this.doc = doc;
18189
18273
  this.host = opts.host;
18190
18274
  this.onSelect = opts.onSelect;
18191
18275
  this.onFavorite = opts.onFavorite;
18192
18276
  this.checkmarks = opts.checkmarks === true;
18277
+ this.iconBadges = opts.iconBadges === true;
18193
18278
  this.positioner = doc.createElement("div");
18194
18279
  this.positioner.className = "vela-ui-layer";
18195
18280
  this.list = doc.createElement("ul");
@@ -18220,14 +18305,30 @@ ${STATIC_DECLS}
18220
18305
  if (opts.triggerId && trigger) trigger.id = opts.triggerId;
18221
18306
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
18222
18307
  const api = this.ctrl.connect(service);
18223
- if (this.shown && !api.open) this.announce(false);
18308
+ const closing = this.shown && !api.open;
18309
+ if (closing) this.announce(false);
18310
+ if (api.open && this.exit) {
18311
+ this.exit.cancel();
18312
+ this.exit = null;
18313
+ }
18224
18314
  if (trigger) spreadProps(trigger, api.getTriggerProps(), this.mid);
18225
18315
  spreadProps(this.positioner, api.getPositionerProps(), this.mid);
18226
18316
  spreadProps(this.list, api.getContentProps(), this.mid);
18227
18317
  this.project(api);
18318
+ if (closing) this.holdExit();
18228
18319
  if (api.open && !this.shown) this.announce(true);
18229
18320
  });
18230
18321
  }
18322
+ /** Keep the just-closed list up through its exit animation. `spreadProps` re-applies
18323
+ * `hidden` only when the machine's value for it changes, so the list stays shown until
18324
+ * the exit ends — or until a reopen, which projects `hidden: false` itself. */
18325
+ holdExit() {
18326
+ this.list.hidden = false;
18327
+ this.exit = holdForExit(this.list, () => {
18328
+ this.exit = null;
18329
+ if (!this.shown) this.list.hidden = true;
18330
+ });
18331
+ }
18231
18332
  get api() {
18232
18333
  return this.ctrl.connect(this.handle.service);
18233
18334
  }
@@ -18251,6 +18352,7 @@ ${STATIC_DECLS}
18251
18352
  destroy() {
18252
18353
  for (const sub2 of this.subs.values()) sub2.destroy();
18253
18354
  this.subs.clear();
18355
+ this.exit?.finish();
18254
18356
  this.handle.stop();
18255
18357
  if (this.shown) this.announce(false);
18256
18358
  this.positioner.remove();
@@ -18277,6 +18379,10 @@ ${STATIC_DECLS}
18277
18379
  const doc = this.doc;
18278
18380
  this.list.replaceChildren();
18279
18381
  const markable = this.checkmarks && this.items.some((i) => !(i.submenu && i.submenu.length > 0) && !i.toggle && i.checked !== void 0);
18382
+ const iconic = this.items.some((i) => !!i.icon);
18383
+ const badged = this.iconBadges && iconic;
18384
+ if (badged) this.list.dataset.badges = "1";
18385
+ else delete this.list.dataset.badges;
18280
18386
  for (const item of this.items) {
18281
18387
  if (item.separatorBefore) {
18282
18388
  const sep = doc.createElement("li");
@@ -18302,7 +18408,16 @@ ${STATIC_DECLS}
18302
18408
  } else if (!branch && !item.toggle && item.checked) {
18303
18409
  li.dataset.checked = "1";
18304
18410
  }
18305
- if (item.icon) li.appendChild(iconEl(item.icon, doc));
18411
+ if (badged) {
18412
+ const badge = doc.createElement("span");
18413
+ badge.className = "vela-menu-badge";
18414
+ if (item.icon) badge.appendChild(iconEl(item.icon, doc));
18415
+ li.appendChild(badge);
18416
+ } else if (item.icon) {
18417
+ li.appendChild(iconEl(item.icon, doc));
18418
+ } else if (iconic) {
18419
+ li.appendChild(iconEl("", doc));
18420
+ }
18306
18421
  const label = doc.createElement("span");
18307
18422
  label.className = "vela-menu-label";
18308
18423
  label.textContent = item.label;
@@ -18350,6 +18465,7 @@ ${STATIC_DECLS}
18350
18465
  onSelect: this.onSelect,
18351
18466
  onFavorite: this.onFavorite,
18352
18467
  checkmarks: this.checkmarks,
18468
+ iconBadges: this.iconBadges,
18353
18469
  id: `${this.mid}--${item.id}`
18354
18470
  });
18355
18471
  sub2.setItems(item.submenu ?? []);
@@ -18375,7 +18491,8 @@ ${STATIC_DECLS}
18375
18491
  id: opts.id,
18376
18492
  minWidth: opts.minWidth,
18377
18493
  onFavorite: opts.onFavorite,
18378
- checkmarks: opts.checkmarks
18494
+ checkmarks: opts.checkmarks,
18495
+ iconBadges: opts.iconBadges
18379
18496
  });
18380
18497
  this.root.setItems(opts.items);
18381
18498
  }
@@ -18479,6 +18596,7 @@ ${STATIC_DECLS}
18479
18596
 
18480
18597
  // src/ui/components/popover/view.ts
18481
18598
  var open = null;
18599
+ var leaving = /* @__PURE__ */ new Set();
18482
18600
  function closeOpenPopovers() {
18483
18601
  open?.hide();
18484
18602
  }
@@ -18506,6 +18624,8 @@ ${STATIC_DECLS}
18506
18624
  this.shown = false;
18507
18625
  /** The pending removal of a fading-out shell; a show() that reuses the shell cancels it. */
18508
18626
  this.leaveTimer = null;
18627
+ /** The shell's exit animation before it leaves the DOM; a show() that reuses the shell cancels it. */
18628
+ this.exit = null;
18509
18629
  const doc = opts.trigger.ownerDocument;
18510
18630
  injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
18511
18631
  this.trigger = opts.trigger;
@@ -18542,6 +18662,10 @@ ${STATIC_DECLS}
18542
18662
  clearTimeout(this.leaveTimer);
18543
18663
  this.leaveTimer = null;
18544
18664
  }
18665
+ this.exit?.cancel();
18666
+ this.exit = null;
18667
+ leaving.delete(this);
18668
+ for (const p of [...leaving]) if (p.trigger === this.trigger) p.exit?.finish();
18545
18669
  ensureUIHost(this.el, this.inheritsTokens ? void 0 : this.theme);
18546
18670
  if (this.fadeMs > 0) {
18547
18671
  this.el.style.transition = `opacity ${this.fadeMs}ms ease`;
@@ -18598,7 +18722,12 @@ ${STATIC_DECLS}
18598
18722
  this.el.remove();
18599
18723
  }, this.fadeMs);
18600
18724
  } else {
18601
- this.el.remove();
18725
+ this.exit = holdForExit(this.el, () => {
18726
+ this.exit = null;
18727
+ leaving.delete(this);
18728
+ this.el.remove();
18729
+ });
18730
+ if (this.exit) leaving.add(this);
18602
18731
  }
18603
18732
  if (open === this) open = null;
18604
18733
  this.ctrl.onClose?.();
@@ -18610,6 +18739,7 @@ ${STATIC_DECLS}
18610
18739
  }
18611
18740
  destroy() {
18612
18741
  this.hide();
18742
+ this.exit?.finish();
18613
18743
  }
18614
18744
  reposition() {
18615
18745
  if (this.shown) this.place();
@@ -20491,6 +20621,9 @@ ${overlayScrollbarCss(".vela-dialog *")}
20491
20621
  constructor(opts = {}) {
20492
20622
  this.opener = null;
20493
20623
  this.wasOpen = false;
20624
+ /** The panel and scrim's exit animation after a close; a reopen cancels it. */
20625
+ this.exit = null;
20626
+ this.destroyed = false;
20494
20627
  const doc = (opts.host ?? document.body).ownerDocument;
20495
20628
  injectStyles(DIALOG_STYLE_ID, DIALOG_CSS, doc);
20496
20629
  const host = opts.host ?? doc.body;
@@ -20587,19 +20720,57 @@ ${overlayScrollbarCss(".vela-dialog *")}
20587
20720
  const mid = String(this.ctrl.props.id);
20588
20721
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
20589
20722
  const api = this.ctrl.connect(service);
20590
- if (this.wasOpen && !api.open) this.announce(false);
20723
+ const closing = this.wasOpen && !api.open;
20724
+ if (closing) this.announce(false);
20591
20725
  spreadProps(this.backdrop, api.getBackdropProps(), mid);
20592
20726
  spreadProps(this.positioner, api.getPositionerProps(), mid);
20593
20727
  spreadProps(this.panel, api.getContentProps(), mid);
20594
20728
  spreadProps(title, api.getTitleProps(), mid);
20595
20729
  spreadProps(close, api.getCloseTriggerProps(), mid);
20596
- this.backdrop.style.display = api.open ? "" : "none";
20597
- this.positioner.style.display = api.open ? "" : "none";
20730
+ if (api.open) {
20731
+ this.exit?.cancel();
20732
+ this.exit = null;
20733
+ this.setShown(true);
20734
+ } else if (closing) {
20735
+ this.holdExit();
20736
+ } else if (!this.exit) {
20737
+ this.setShown(false);
20738
+ }
20598
20739
  if (api.open && !this.wasOpen) this.announce(true);
20599
20740
  if (this.wasOpen && !api.open) this.restoreOpener();
20600
20741
  this.wasOpen = api.open;
20601
20742
  });
20602
20743
  }
20744
+ setShown(shown) {
20745
+ this.backdrop.style.display = shown ? "" : "none";
20746
+ this.positioner.style.display = shown ? "" : "none";
20747
+ }
20748
+ /** Keep the just-closed dialog up through its exit animation, then hide it — or
20749
+ * remove it, when it was destroyed meanwhile. `spreadProps` re-applies the close
20750
+ * props' `hidden` only when its value changes, so lifting it keeps the panel and
20751
+ * scrim up until the exit ends. The positioner spans the viewport: inert, it lets
20752
+ * clicks through to the page while the panel animates out. */
20753
+ holdExit() {
20754
+ const held = [this.panel, this.backdrop];
20755
+ for (const el of held) el.hidden = false;
20756
+ this.exit = holdForExit(
20757
+ held,
20758
+ () => {
20759
+ this.exit = null;
20760
+ if (this.destroyed) {
20761
+ this.detach();
20762
+ } else if (!this.open) {
20763
+ for (const el of held) el.hidden = true;
20764
+ this.setShown(false);
20765
+ }
20766
+ },
20767
+ { inert: [this.positioner] }
20768
+ );
20769
+ }
20770
+ detach() {
20771
+ this.backdrop.remove();
20772
+ this.positioner.remove();
20773
+ }
20603
20774
  /** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
20604
20775
  * teardown that outruns the machine's close notification). */
20605
20776
  restoreOpener() {
@@ -20628,15 +20799,16 @@ ${overlayScrollbarCss(".vela-dialog *")}
20628
20799
  return el?.closest(".vela-popover, .vela-menu") != null;
20629
20800
  }
20630
20801
  destroy() {
20802
+ if (this.wasOpen && !this.open) this.handle.flush();
20631
20803
  if (this.open) this.hide();
20632
20804
  this.handle.stop();
20805
+ this.destroyed = true;
20633
20806
  if (this.wasOpen) {
20634
20807
  this.wasOpen = false;
20635
20808
  this.announce(false);
20636
20809
  }
20637
20810
  this.restoreOpener();
20638
- this.backdrop.remove();
20639
- this.positioner.remove();
20811
+ if (!this.exit) this.detach();
20640
20812
  }
20641
20813
  };
20642
20814
 
@@ -23088,7 +23260,10 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23088
23260
  var LEGEND_CTL_PX = 18;
23089
23261
  var LEGEND_ROW_PAD_Y = 2;
23090
23262
  var LEGEND_ROW_PAD_X = 6;
23263
+ var LEGEND_LEFT_PX = 10;
23264
+ var LEGEND_SCALE_GAP_PX = 6;
23091
23265
  var LEGEND_TITLE_VALUES_GAP_PX = 6;
23266
+ var LEGEND_VALUES_GAP_PX = 5;
23092
23267
  var LEGEND_TITLE_STATUS_GAP_PX = 8;
23093
23268
  var LEGEND_ACTION_GAP_PX = 6;
23094
23269
  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;`;
@@ -23374,14 +23549,15 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23374
23549
  row.valuesEl.replaceChildren();
23375
23550
  return;
23376
23551
  }
23377
- row.valuesEl.style.display = "inline-flex";
23552
+ row.valuesEl.style.display = "block";
23378
23553
  row.valuesEl.replaceChildren();
23379
- for (const v of row.plotValues) {
23554
+ row.plotValues.forEach((v, i) => {
23380
23555
  const span = document.createElement("span");
23381
23556
  span.textContent = v.value;
23382
23557
  span.style.color = v.color;
23558
+ if (i > 0) span.style.marginLeft = `${LEGEND_VALUES_GAP_PX}px`;
23383
23559
  row.valuesEl.appendChild(span);
23384
- }
23560
+ });
23385
23561
  }
23386
23562
  // ── legend move/merge (menu + drag) ─────────────────────────────────────
23387
23563
  /** Open the "Move to" menu for a row, anchored under its move button. */
@@ -23482,7 +23658,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23482
23658
  if (!lg) {
23483
23659
  lg = document.createElement("div");
23484
23660
  lg.dataset.velaPane = paneId;
23485
- 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;";
23661
+ 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;`;
23486
23662
  applyChromeTokens(lg, this.theme);
23487
23663
  this.positionLegend(lg, paneId);
23488
23664
  this.container.appendChild(lg);
@@ -23494,6 +23670,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23494
23670
  const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
23495
23671
  lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
23496
23672
  lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
23673
+ lg.style.maxWidth = `calc(100% - ${(bounds.rightAxis ?? 0) + LEGEND_LEFT_PX + LEGEND_SCALE_GAP_PX}px)`;
23497
23674
  const collapsed = this.paneCollapse.has(paneId);
23498
23675
  const masterId = this.paneCollapse.get(paneId) ?? null;
23499
23676
  for (const row of this.rows.values()) {
@@ -23585,7 +23762,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23585
23762
  }
23586
23763
  ensureDialogStyles();
23587
23764
  const el = document.createElement("div");
23588
- 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;`;
23765
+ 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;`;
23589
23766
  el.addEventListener("mouseleave", () => {
23590
23767
  if (this.selectedId !== id) this.setRowHighlighted(id, false);
23591
23768
  });
@@ -23613,7 +23790,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23613
23790
  const statusEl = document.createElement("span");
23614
23791
  statusEl.style.cssText = "display:none;box-sizing:border-box;flex:none;";
23615
23792
  const titleWrap = document.createElement("span");
23616
- titleWrap.style.cssText = "white-space:nowrap;";
23793
+ titleWrap.style.cssText = "white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis;";
23617
23794
  titleWrap.addEventListener("mouseenter", () => this.setRowHighlighted(id, true));
23618
23795
  const titleEl = document.createElement("span");
23619
23796
  titleEl.textContent = title;
@@ -23631,7 +23808,7 @@ ${overlayScrollbarCss(".vela-dialog.vela-ind-dialog *", 9)}
23631
23808
  el.appendChild(calloutsEl);
23632
23809
  el.appendChild(statusEl);
23633
23810
  const valuesEl = document.createElement("span");
23634
- 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;`;
23811
+ 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;`;
23635
23812
  el.appendChild(valuesEl);
23636
23813
  const controlsEl = document.createElement("span");
23637
23814
  controlsEl.style.cssText = `display:none;align-items:center;gap:${LEGEND_ACTION_GAP_PX}px;flex:none;margin-left:${LEGEND_ACTION_GAP_PX}px;`;
@@ -26226,6 +26403,11 @@ void main() {
26226
26403
  /** When true (the default), the separators between stacked panes are draggable to
26227
26404
  * resize the panes above/below. When false, a press there is a normal data-area pan. */
26228
26405
  this.paneResize = true;
26406
+ /** When true (the default), the user drawings take part in pointer input: they claim a
26407
+ * press over a drawing or with a tool armed, and see hover moves. When false, every press
26408
+ * is a plain pan/click and hover leaves them alone; a drawing gesture already under way
26409
+ * still finishes. */
26410
+ this.drawings = true;
26229
26411
  this.el = null;
26230
26412
  this.dragging = false;
26231
26413
  this.moved = false;
@@ -26289,18 +26471,18 @@ void main() {
26289
26471
  this.onModifier = (e) => {
26290
26472
  if (e.repeat || e.key !== "Shift" && e.key !== "Control" && e.key !== "Meta") return;
26291
26473
  if (Number.isNaN(this.cursorX)) return;
26292
- if (this.dragging && this.region !== "drawing") return;
26474
+ if (this.dragging ? this.region !== "drawing" : !this.drawings) return;
26293
26475
  this.deps.drawingsPointerMove?.(this.cursorX, this.cursorY, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
26294
26476
  };
26295
26477
  this.onDown = (e) => {
26296
26478
  if (e.button === 1) {
26297
26479
  const { x: x2, y: y2 } = this.local(e);
26298
- this.middleDeleted = this.deps.drawingsDeleteAt?.(x2, y2) ?? false;
26480
+ this.middleDeleted = this.drawings && (this.deps.drawingsDeleteAt?.(x2, y2) ?? false);
26299
26481
  if (this.middleDeleted) e.preventDefault();
26300
26482
  return;
26301
26483
  }
26302
26484
  if (e.button === 2) {
26303
- this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
26485
+ this.rightCancelled = this.drawings && (this.deps.drawingsCancelPlacement?.() ?? false);
26304
26486
  if (this.rightCancelled) e.preventDefault();
26305
26487
  return;
26306
26488
  }
@@ -26322,20 +26504,20 @@ void main() {
26322
26504
  this.startY = y;
26323
26505
  this.prevPressDrawing = this.lastPressDrawing;
26324
26506
  this.lastPressDrawing = false;
26325
- if (this.deps.drawingsClaim?.(x, y)) {
26507
+ if (this.drawings && this.deps.drawingsClaim?.(x, y)) {
26326
26508
  this.region = "drawing";
26327
26509
  this.lastPressDrawing = true;
26328
26510
  this.deps.drawingsPointerDown?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
26329
26511
  this.capture(e.pointerId);
26330
26512
  return;
26331
26513
  }
26332
- if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
26514
+ if (this.drawings && e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
26333
26515
  this.region = "drawing";
26334
26516
  this.lastPressDrawing = true;
26335
26517
  this.capture(e.pointerId);
26336
26518
  return;
26337
26519
  }
26338
- if ((e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
26520
+ if (this.drawings && (e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
26339
26521
  this.region = "drawing";
26340
26522
  this.lastPressDrawing = true;
26341
26523
  this.capture(e.pointerId);
@@ -26412,7 +26594,7 @@ void main() {
26412
26594
  const drawCursor = this.deps.drawingsCursor?.(x, y);
26413
26595
  const r = this.regionAt(x, y);
26414
26596
  this.el.style.cursor = drawCursor ?? (r === "price" ? "ns-resize" : r === "time" ? "ew-resize" : r === "separator" ? "row-resize" : "");
26415
- this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
26597
+ if (this.drawings) this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
26416
26598
  }
26417
26599
  if (e.pointerType !== "touch") this.deps.onPointerMove(x, y);
26418
26600
  };
@@ -26654,7 +26836,7 @@ void main() {
26654
26836
  }
26655
26837
  /** Shared double-click / double-tap routing, by the region under the point. */
26656
26838
  doubleActivate(x, y) {
26657
- if (this.deps.drawingsDblClick?.(x, y)) return;
26839
+ if (this.drawings && this.deps.drawingsDblClick?.(x, y)) return;
26658
26840
  const region = this.regionAt(x, y);
26659
26841
  if (region === "price") this.deps.resetPriceScale(x, y);
26660
26842
  else if (region === "separator") this.deps.resetPaneSize(y);
@@ -28668,10 +28850,13 @@ void main() {
28668
28850
  const norm = raw / mag;
28669
28851
  const step = (norm < 1.5 ? 1 : norm < 3 ? 2 : norm < 7 ? 5 : 10) * mag;
28670
28852
  const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);
28671
- const out = [];
28672
28853
  const start = Math.ceil(min2 / step) * step;
28673
- for (let v = start; v <= max2 + step * 1e-6; v += step) {
28674
- out.push(Number(v.toFixed(decimals)));
28854
+ if (!(step > 0) || start + step === start) return [];
28855
+ const count = Math.floor((max2 - start) / step + 1e-6) + 1;
28856
+ if (!(count > 0) || count > 1e3) return [];
28857
+ const out = [];
28858
+ for (let i = 0; i < count; i += 1) {
28859
+ out.push(Number((start + i * step).toFixed(decimals)));
28675
28860
  }
28676
28861
  return out;
28677
28862
  }
@@ -28869,6 +29054,100 @@ void main() {
28869
29054
  return out;
28870
29055
  }
28871
29056
 
29057
+ // src/renderers/native/chrome/priceAxisTicks.ts
29058
+ var PriceAxisTickSource = class {
29059
+ constructor() {
29060
+ this.fn = null;
29061
+ this.memo = /* @__PURE__ */ new WeakMap();
29062
+ this.warned = false;
29063
+ }
29064
+ /** The host function, or null for the built-in ticks. */
29065
+ get hook() {
29066
+ return this.fn;
29067
+ }
29068
+ /** Set (or clear) the host function. Drops every memoized result, so setting the same
29069
+ * function again recomputes — how a host refreshes ticks that depend on its own state. */
29070
+ setHook(fn) {
29071
+ this.fn = fn;
29072
+ this.memo = /* @__PURE__ */ new WeakMap();
29073
+ }
29074
+ /** The pane's master-scale ticks for this frame. An unscaled pane (`axisFormat: 'none'`)
29075
+ * gets none, and the host is never asked for it. */
29076
+ ticksFor(scene, pane, coords) {
29077
+ const format = pane.axisFormat;
29078
+ if (format === "none") return [];
29079
+ const pct = percentScaleFor(scene, pane);
29080
+ const height = pane.bounds.height;
29081
+ const fn = this.fn;
29082
+ if (!fn || !(height > 0)) return paneAxisTicks(pane.scale, height, pct, scene.priceMintick, format);
29083
+ const s = pane.scale;
29084
+ const m = this.memo.get(pane);
29085
+ const fontSize = scene.style.fontSize;
29086
+ 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) {
29087
+ return m.ticks;
29088
+ }
29089
+ const defaults2 = paneAxisTicks(s, height, pct, scene.priceMintick, format);
29090
+ const ticks = this.callHook(fn, scene, pane, coords, defaults2, pct?.baseline, pct ? pct.indexed ? "indexed" : "percent" : "price");
29091
+ this.memo.set(pane, {
29092
+ kind: pane.kind,
29093
+ min: s.min,
29094
+ max: s.max,
29095
+ log: !!s.log,
29096
+ invert: !!s.invert,
29097
+ height,
29098
+ baseline: pct?.baseline,
29099
+ indexed: !!pct?.indexed,
29100
+ mintick: scene.priceMintick,
29101
+ format,
29102
+ fontSize,
29103
+ ticks
29104
+ });
29105
+ return ticks;
29106
+ }
29107
+ callHook(fn, scene, pane, coords, defaults2, baseline, mode) {
29108
+ const scale = { min: pane.scale.min, max: pane.scale.max, log: !!pane.scale.log, invert: !!pane.scale.invert };
29109
+ const bounds = { top: 0, height: pane.bounds.height };
29110
+ const ctx = {
29111
+ pane: { id: pane.id, kind: pane.kind },
29112
+ min: Math.min(scale.min, scale.max),
29113
+ max: Math.max(scale.min, scale.max),
29114
+ log: !!scale.log,
29115
+ height: bounds.height,
29116
+ mode,
29117
+ baseline,
29118
+ mintick: scene.priceMintick,
29119
+ fontSize: scene.style.fontSize,
29120
+ priceToY: (price) => coords.priceToY(price, scale, bounds),
29121
+ defaults: defaults2
29122
+ };
29123
+ let out;
29124
+ try {
29125
+ out = fn(ctx);
29126
+ } catch (err) {
29127
+ this.warnOnce("threw", err);
29128
+ return defaults2;
29129
+ }
29130
+ if (out == null) return defaults2;
29131
+ if (!Array.isArray(out)) {
29132
+ this.warnOnce("returned a non-array", out);
29133
+ return defaults2;
29134
+ }
29135
+ const ticks = [];
29136
+ for (const t of out) {
29137
+ if (!t || typeof t !== "object") continue;
29138
+ const { price, label, major } = t;
29139
+ if (typeof price !== "number" || !Number.isFinite(price) || typeof label !== "string") continue;
29140
+ ticks.push(typeof major === "boolean" ? { price, label, major } : { price, label });
29141
+ }
29142
+ return ticks;
29143
+ }
29144
+ warnOnce(what, detail) {
29145
+ if (this.warned) return;
29146
+ this.warned = true;
29147
+ console.warn(`[vela] the priceAxisTicks function ${what} \u2014 the default ticks are used instead.`, detail);
29148
+ }
29149
+ };
29150
+
28872
29151
  // src/renderers/native/chrome/countdown.ts
28873
29152
  function countdownText(barOpen, barMs, now2) {
28874
29153
  if (!(barMs > 0)) return null;
@@ -29221,8 +29500,11 @@ void main() {
29221
29500
  }
29222
29501
 
29223
29502
  // src/renderers/native/chrome/ChromeRenderer.ts
29503
+ var MINOR_LABEL_ALPHA = 0.5;
29224
29504
  var ChromeRenderer = class {
29225
- constructor() {
29505
+ /** `axisTicks` is shared with the backdrop layer, so price labels and gridlines agree. */
29506
+ constructor(axisTicks = new PriceAxisTickSource()) {
29507
+ this.axisTicks = axisTicks;
29226
29508
  this.canvas = null;
29227
29509
  this.ctx = null;
29228
29510
  // The color for axis tick labels — the host-passed surface text, set each frame in render().
@@ -29327,7 +29609,8 @@ void main() {
29327
29609
  this.drawTimeAxis(ctx, scene, coords, theme, dataW, dataH, fullH);
29328
29610
  this.drawMarkLane(ctx, scene, coords, theme, dataW, dataH);
29329
29611
  }
29330
- /** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge. */
29612
+ /** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge,
29613
+ * but clipped to the data area so a glyph on the edge bar never paints over the price scale. */
29331
29614
  drawMarkLane(ctx, scene, coords, theme, dataW, dataH) {
29332
29615
  if (!scene.marks.visible || scene.timelineMarks.length === 0) {
29333
29616
  this.markLayout = { glyphs: [], stacks: /* @__PURE__ */ new Map() };
@@ -29345,6 +29628,10 @@ void main() {
29345
29628
  expanded: scene.marksExpandedStack
29346
29629
  });
29347
29630
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
29631
+ ctx.save();
29632
+ ctx.beginPath();
29633
+ ctx.rect(0, 0, dataW, dataH);
29634
+ ctx.clip();
29348
29635
  paintMarkLane(ctx, this.markLayout, {
29349
29636
  axisY: dataH,
29350
29637
  background: theme.background,
@@ -29358,6 +29645,7 @@ void main() {
29358
29645
  flashKey: scene.marksFlash && scene.marksFlash.until > nowMs ? scene.marksFlash.key : null,
29359
29646
  nowMs
29360
29647
  });
29648
+ ctx.restore();
29361
29649
  }
29362
29650
  barTimes(scene) {
29363
29651
  if (this.barTimesSrc !== scene.bars || this.barTimesCache.length !== scene.bars.length) {
@@ -29414,13 +29702,19 @@ void main() {
29414
29702
  if (!scene.showAxisLabels) return;
29415
29703
  ctx.fillStyle = this.axisTextColor;
29416
29704
  ctx.textAlign = "left";
29705
+ const font = `${scene.style.fontSize}px ${theme.fontFamily}`;
29706
+ const majorFont = `600 ${font}`;
29707
+ const alpha = ctx.globalAlpha;
29417
29708
  for (const pane of panes) {
29418
29709
  if (pane.collapsed) continue;
29419
- const pct = percentScaleFor(scene, pane);
29420
- for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, scene.priceMintick, pane.axisFormat)) {
29710
+ for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
29421
29711
  const y = coords.priceToY(t.price, pane.scale, pane.bounds);
29422
29712
  if (y < pane.bounds.top + 6 || y > pane.bounds.top + pane.bounds.height - 4) continue;
29713
+ if (t.major === true) ctx.font = majorFont;
29714
+ else if (t.major === false) ctx.globalAlpha = alpha * MINOR_LABEL_ALPHA;
29423
29715
  ctx.fillText(t.label, dataW + 6, y);
29716
+ if (t.major === true) ctx.font = font;
29717
+ else if (t.major === false) ctx.globalAlpha = alpha;
29424
29718
  }
29425
29719
  if (pane.axisBands) {
29426
29720
  for (const b of pane.axisBands) {
@@ -29662,15 +29956,23 @@ void main() {
29662
29956
  constructor() {
29663
29957
  this.canvas = null;
29664
29958
  this.ctx = null;
29959
+ this.shade = null;
29665
29960
  }
29666
29961
  mount(canvas) {
29667
29962
  this.canvas = canvas;
29668
29963
  this.ctx = canvas.getContext("2d");
29669
29964
  }
29965
+ /** The plot area the last frame veiled for `crosshairOverride.shadeRight` — from the
29966
+ * picked bar's right edge to the price scale, full plot height (the union when both the
29967
+ * local and a synced crosshair veil) — or null when no veil was painted. */
29968
+ get shadeRightArea() {
29969
+ return this.shade;
29970
+ }
29670
29971
  /** Clear the cursor canvas and (re)draw the crosshair lines + axis chips. The optional
29671
29972
  * `separatorHoverY` highlights the draggable pane separator under the cursor;
29672
29973
  * `external` is a SYNCED ghost crosshair (another chart's pointer, pixel-resolved). */
29673
29974
  render(scene, coords, theme, separatorHoverY = null, external = null) {
29975
+ this.shade = null;
29674
29976
  const ctx = this.ctx;
29675
29977
  const canvas = this.canvas;
29676
29978
  if (!ctx || !canvas) return;
@@ -29693,12 +29995,7 @@ void main() {
29693
29995
  const x = crisp(coords.logicalToX(logical), ov?.width ?? cs.width);
29694
29996
  if (vertical && ov?.shadeRight) {
29695
29997
  const from = Math.max(0, Math.round(coords.logicalToX(logical + 0.5)));
29696
- if (from < dataW) {
29697
- ctx.fillStyle = ov.shadeRight.color;
29698
- ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
29699
- ctx.fillRect(from, 0, dataW - from, dataH);
29700
- ctx.globalAlpha = 1;
29701
- }
29998
+ if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
29702
29999
  }
29703
30000
  ctx.strokeStyle = ov?.color ?? cs.color ?? theme.textColor;
29704
30001
  ctx.lineWidth = ov?.width ?? cs.width;
@@ -29734,6 +30031,7 @@ void main() {
29734
30031
  destroy() {
29735
30032
  this.canvas = null;
29736
30033
  this.ctx = null;
30034
+ this.shade = null;
29737
30035
  }
29738
30036
  /** The synced ghost: a dimmed vertical line at the bar the renderer resolved as
29739
30037
  * CONTAINING the foreign time (+ horizontal line when a comparable price came
@@ -29753,12 +30051,7 @@ void main() {
29753
30051
  if (ov?.vertical === false) return;
29754
30052
  if (ov?.shadeRight) {
29755
30053
  const from = Math.max(0, Math.round(coords.logicalToX(Math.round(coords.xToLogical(ext.x)) + 0.5)));
29756
- if (from < dataW) {
29757
- ctx.fillStyle = ov.shadeRight.color;
29758
- ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
29759
- ctx.fillRect(from, 0, dataW - from, dataH);
29760
- ctx.globalAlpha = 1;
29761
- }
30054
+ if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
29762
30055
  }
29763
30056
  if (ext.line === false || x < 0 || x > dataW) return;
29764
30057
  ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
@@ -29794,6 +30087,15 @@ void main() {
29794
30087
  this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
29795
30088
  ctx.globalAlpha = 1;
29796
30089
  }
30090
+ /** Paint the `shadeRight` veil from `from` to the price scale and record the veiled area. */
30091
+ paintShade(ctx, shade, from, dataW, dataH) {
30092
+ ctx.fillStyle = shade.color;
30093
+ ctx.globalAlpha = shade.opacity ?? 1;
30094
+ ctx.fillRect(from, 0, dataW - from, dataH);
30095
+ ctx.globalAlpha = 1;
30096
+ const x = this.shade ? Math.min(this.shade.x, from) : from;
30097
+ this.shade = { x, width: dataW - x, height: dataH };
30098
+ }
29797
30099
  /** A soft band + a brighter crisp center line over the hovered separator, so it reads as
29798
30100
  * a draggable handle (the cursor is already `row-resize`). Spans the full width (data +
29799
30101
  * scale gutter) to match the separator itself. Theme-derived (textColor). */
@@ -33289,15 +33591,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33289
33591
  }
33290
33592
  open(drawing, actions, kind) {
33291
33593
  this.close();
33292
- const snapshot2 = clonePlain(drawing.serialize());
33594
+ const live = actions.resolve() ?? drawing;
33595
+ const snapshot2 = clonePlain(live.serialize());
33293
33596
  const grid = fieldGrid({ variant: "inputs" });
33294
33597
  grid.style.padding = "16px 20px";
33295
33598
  grid.style.overflowY = "auto";
33296
33599
  grid.style.overflowX = "hidden";
33297
33600
  grid.style.flex = "1 1 auto";
33298
- if (kind === "position" && drawing instanceof PositionTool) this.buildPosition(grid, drawing, actions);
33299
- else if (kind === "frvp" && drawing instanceof FixedRangeVolumeProfile) this.buildFrvp(grid, drawing, actions);
33300
- else if (kind === "levels") this.buildLevels(grid, drawing, actions);
33601
+ if (kind === "position" && live instanceof PositionTool) this.buildPosition(grid, live, actions);
33602
+ else if (kind === "frvp" && live instanceof FixedRangeVolumeProfile) this.buildFrvp(grid, live, actions);
33603
+ else if (kind === "levels") this.buildLevels(grid, live, actions);
33301
33604
  else return;
33302
33605
  const ui = new Dialog({
33303
33606
  host: this.host,
@@ -33944,7 +34247,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
33944
34247
  this.reposition();
33945
34248
  return;
33946
34249
  }
33947
- const text = drawing.text;
34250
+ const text = (actions.resolve() ?? drawing).text;
33948
34251
  const panel = document.createElement("div");
33949
34252
  panel.style.cssText = `padding:6px;border-top:1px solid var(--vela-border);display:flex;flex-direction:column;gap:4px;`;
33950
34253
  const ta = new TextArea({
@@ -34540,6 +34843,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34540
34843
  // src/renderers/native/drawings/DrawingToolbar.ts
34541
34844
  var TOOLBAR_WIDTH = 44;
34542
34845
  var TOOLBAR_COLLAPSED_WIDTH = 16;
34846
+ var FLYOUT_GAP = 3;
34543
34847
  var DrawingToolbar = class {
34544
34848
  constructor(host, theme, onArm, onMagnet = () => {
34545
34849
  }, onMeasure = () => {
@@ -34569,6 +34873,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34569
34873
  // group id (or MAGNET_ID) whose flyout is open
34570
34874
  this.flyoutCell = null;
34571
34875
  // the cell the open flyout is anchored to
34876
+ /** Closed flyouts still playing their exit animation, by owner id — reopening the same
34877
+ * owner takes its element back instead of stacking a second one on it. */
34878
+ this.exitingFlyouts = /* @__PURE__ */ new Map();
34572
34879
  this.groupCells = /* @__PURE__ */ new Map();
34573
34880
  // the composite cell (hover/active bg + flyout anchor)
34574
34881
  this.groupIcons = /* @__PURE__ */ new Map();
@@ -34677,6 +34984,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34677
34984
  }
34678
34985
  destroy() {
34679
34986
  this.closeFlyout();
34987
+ for (const { exit } of [...this.exitingFlyouts.values()]) exit.finish();
34680
34988
  for (const dispose of this.tipDisposers.splice(0)) dispose();
34681
34989
  this.root.remove();
34682
34990
  }
@@ -34904,16 +35212,22 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34904
35212
  * outside-dismiss. Callers fill it with items. */
34905
35213
  beginFlyout(ownerId, cell) {
34906
35214
  const t = this.theme;
34907
- const fly = document.createElement("div");
35215
+ const back = this.exitingFlyouts.get(ownerId);
35216
+ if (back) {
35217
+ this.exitingFlyouts.delete(ownerId);
35218
+ back.exit.cancel();
35219
+ back.fly.replaceChildren();
35220
+ }
35221
+ const fly = back?.fly ?? document.createElement("div");
34908
35222
  fly.className = "vela-dtb-flyout";
34909
- 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;`;
35223
+ 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;`;
34910
35224
  applyChromeTokens(fly, t);
34911
35225
  this.host.appendChild(fly);
34912
35226
  const r = cell.getBoundingClientRect();
34913
35227
  const rootR = this.root.getBoundingClientRect();
34914
35228
  const hostR = this.host.getBoundingClientRect();
34915
35229
  const top = r.top - hostR.top;
34916
- fly.style.left = `${rootR.right - hostR.left - 1}px`;
35230
+ fly.style.left = `${rootR.right - hostR.left + FLYOUT_GAP}px`;
34917
35231
  fly.style.top = `${top}px`;
34918
35232
  fly.style.maxHeight = `${Math.max(120, hostR.height - top - 8)}px`;
34919
35233
  this.flyout = fly;
@@ -34929,6 +35243,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34929
35243
  if (!cell) return;
34930
35244
  const fly = this.beginFlyout(group.id, cell);
34931
35245
  const sections = group.sections ?? [{ label: "", tools: group.tools }];
35246
+ const badge = sections.some((s) => s.tools.some((tool) => !!tool.icon));
34932
35247
  for (let si = 0; si < sections.length; si++) {
34933
35248
  const section = sections[si];
34934
35249
  if (section.label) {
@@ -34939,6 +35254,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34939
35254
  fly.appendChild(
34940
35255
  this.makeFlyoutItem({
34941
35256
  icon: tool.icon,
35257
+ badge,
34942
35258
  label: tool.label,
34943
35259
  selected: tool.type === this.active,
34944
35260
  shortcut: this.shortcuts.get(tool.type),
@@ -34989,21 +35305,24 @@ ${overlayScrollbarCss(".vela-sd-pane")}
34989
35305
  div.style.cssText = `height:1px;margin:4px 8px;background:${this.borderColor};`;
34990
35306
  return div;
34991
35307
  }
34992
- /** A flyout row, left to right: optional leading icon, label, a check when it's the selected
34993
- * entry, an optional shortcut hint, and — at the far right — the favorite star (tool rows).
34994
- * Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
35308
+ /** A flyout row, left to right: optional leading icon badge, label, a check when it's the
35309
+ * selected entry, an optional shortcut hint, and — at the far right — the favorite star (tool
35310
+ * rows). Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
34995
35311
  makeFlyoutItem(opts) {
34996
35312
  const t = this.theme;
34997
35313
  const item = document.createElement("button");
34998
35314
  item.type = "button";
34999
35315
  item.className = "vela-dtb-item";
35000
35316
  item.setAttribute("aria-label", opts.label);
35001
- 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;`;
35002
- if (opts.icon) {
35003
- const icon2 = document.createElement("span");
35004
- icon2.style.cssText = "width:18px;height:18px;display:flex;align-items:center;justify-content:center;flex:none;";
35005
- icon2.innerHTML = sizedIcon(opts.icon);
35006
- item.appendChild(icon2);
35317
+ const badged = opts.badge === true || !!opts.icon;
35318
+ const box = badged ? "gap:10px;padding:4px 10px 4px 6px;" : "gap:8px;padding:5px 10px 5px 8px;";
35319
+ 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;`;
35320
+ if (opts.selected) item.dataset.selected = "1";
35321
+ if (badged) {
35322
+ const badge = document.createElement("span");
35323
+ badge.className = "vela-dtb-badge";
35324
+ if (opts.icon) badge.innerHTML = opts.icon;
35325
+ item.appendChild(badge);
35007
35326
  }
35008
35327
  const label = document.createElement("span");
35009
35328
  label.textContent = opts.label;
@@ -35048,7 +35367,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35048
35367
  if (fly) {
35049
35368
  this.flyout = null;
35050
35369
  announceSurface(fly, false, "menu", this.flyoutCell);
35051
- fly.remove();
35370
+ const owner = this.flyoutOwnerId ?? "";
35371
+ const exit = holdForExit(fly, () => {
35372
+ if (this.exitingFlyouts.get(owner)?.fly === fly) this.exitingFlyouts.delete(owner);
35373
+ fly.remove();
35374
+ });
35375
+ if (exit) this.exitingFlyouts.set(owner, { fly, exit });
35052
35376
  this.flyoutCell?.classList.remove("vela-open");
35053
35377
  this.flyoutCell = null;
35054
35378
  this.flyoutOwnerId = null;
@@ -35132,6 +35456,11 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35132
35456
  .vela-dtb[data-collapsed='1'] .vela-dtb-collapse .vela-dtb-hit{width:14px;}
35133
35457
  .vela-dtb-item{background:transparent;border:none;transition:background var(--vela-dur-fast) ease;}
35134
35458
  .vela-dtb-item:hover{background:var(--vela-hover-strong);}
35459
+ .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;}
35460
+ .vela-dtb-badge:empty{background:transparent;border-color:transparent;}
35461
+ .vela-dtb-badge svg{width:100%;height:100%;}
35462
+ .vela-dtb-item:hover .vela-dtb-badge:not(:empty){background:var(--vela-active);color:var(--vela-fg-bright);}
35463
+ .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);}
35135
35464
  .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;}
35136
35465
  .vela-dtb-star svg{width:16px;height:16px;}
35137
35466
  .vela-dtb-item:hover .vela-dtb-star{opacity:.55;}
@@ -35751,6 +36080,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
35751
36080
  this.render();
35752
36081
  }
35753
36082
  }
36083
+ /** Drop the hover (its handles) — the pointer no longer reaches the drawings. */
36084
+ clearHover() {
36085
+ if (this.hoveredId == null) return;
36086
+ this.hoveredId = null;
36087
+ this.render();
36088
+ }
35754
36089
  pointerUp(x, y, snap = "off") {
35755
36090
  if (this.eraserMode) {
35756
36091
  this.erasing = false;
@@ -36741,9 +37076,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
36741
37076
  consider(drawings.max);
36742
37077
  }
36743
37078
  if (min2 === Infinity || max2 === -Infinity) return { min: 0, max: 1 };
36744
- if (min2 === max2) {
36745
- const pad = Math.abs(min2) * 0.1 || 1;
36746
- return { min: min2 - pad, max: max2 + pad, log: log && min2 - pad > 0 };
37079
+ if (max2 - min2 <= Math.max(Math.abs(min2), Math.abs(max2)) * 1e-12) {
37080
+ const mid = (min2 + max2) / 2;
37081
+ const pad = Math.abs(mid) * 0.1 || 1;
37082
+ return { min: mid - pad, max: mid + pad, log: log && mid - pad > 0 };
36747
37083
  }
36748
37084
  const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
36749
37085
  const above = margins.top / 100 / content;
@@ -37247,7 +37583,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37247
37583
  return Math.ceil(logical) - 0.5;
37248
37584
  }
37249
37585
  var BackdropRenderer = class {
37250
- constructor() {
37586
+ /** `axisTicks` is shared with the chrome layer, so gridlines and price labels agree. */
37587
+ constructor(axisTicks = new PriceAxisTickSource()) {
37588
+ this.axisTicks = axisTicks;
37251
37589
  this.canvas = null;
37252
37590
  this.ctx = null;
37253
37591
  }
@@ -37324,9 +37662,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37324
37662
  if (scene.showGrid && gridHorz.visible && !pane.collapsed) {
37325
37663
  ctx.globalAlpha = gridAlpha;
37326
37664
  ctx.strokeStyle = horzColor;
37327
- const pct = percentScaleFor(scene, pane);
37328
37665
  ctx.beginPath();
37329
- for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, void 0, pane.axisFormat)) {
37666
+ for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
37330
37667
  const y = Math.round(coords.priceToY(t.price, pane.scale, pane.bounds)) + 0.5;
37331
37668
  if (y < pane.bounds.top || y > pane.bounds.top + pane.bounds.height) continue;
37332
37669
  ctx.moveTo(0, y);
@@ -37942,8 +38279,16 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37942
38279
  this.toolbarGutter = 0;
37943
38280
  // px reserved on the left for the docked drawings toolbar (0 when hidden)
37944
38281
  this.mountContainer = null;
38282
+ /** The host-styleable twin of the `shadeRight` veil (`.vela-shade-right`), directly under
38283
+ * the cursor canvas; hidden whenever the crosshair layer paints no veil. */
38284
+ this.shadeRightEl = null;
38285
+ /** The veil geometry last written to {@link shadeRightEl} ('' = hidden) — the DOM is
38286
+ * touched only when it changes, never on a plain pointer move. */
38287
+ this.shadeRightSig = "";
37945
38288
  this.userDrawings = null;
37946
- this.backdropRenderer = new BackdropRenderer();
38289
+ /** One price-axis tick source for the gridlines (backdrop) and the labels (chrome). */
38290
+ this.priceAxisTicks = new PriceAxisTickSource();
38291
+ this.backdropRenderer = new BackdropRenderer(this.priceAxisTicks);
37947
38292
  this.volumeRenderer = new VolumeRenderer();
37948
38293
  /** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
37949
38294
  /**
@@ -37976,7 +38321,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
37976
38321
  this.backendMode = "auto";
37977
38322
  this.glowAmount = 0;
37978
38323
  // WebGL2 neon-glow intensity (canvas2d ignores it)
37979
- this.chrome = new ChromeRenderer();
38324
+ this.chrome = new ChromeRenderer(this.priceAxisTicks);
37980
38325
  /** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
37981
38326
  this.indicatorSlices = new IndicatorDrawingSlices();
37982
38327
  /** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
@@ -38093,6 +38438,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38093
38438
  // pane just below it
38094
38439
  this.resizeSplitStart = null;
38095
38440
  // the two panes' shared span when the drag began
38441
+ /** The user drawings take part in pointer input (the runtime `drawingsInteractive` feature). */
38442
+ this.drawingsInteractive = true;
38096
38443
  this.hoverSeparatorY = null;
38097
38444
  // pixel y of the separator under the cursor (drives its hover highlight)
38098
38445
  // ── pane management (merge / reorder / collapse / maximize) ──
@@ -38148,8 +38495,9 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38148
38495
  this.toggleVisibleCbs = /* @__PURE__ */ new Set();
38149
38496
  this.moveIndicatorCbs = /* @__PURE__ */ new Set();
38150
38497
  this.priceStyleCbs = /* @__PURE__ */ new Set();
38498
+ this.priceStyleWillChangeCbs = /* @__PURE__ */ new Set();
38151
38499
  this.name = "native";
38152
- 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"];
38500
+ 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"];
38153
38501
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
38154
38502
  * so moving onto the button doesn't count as leaving). */
38155
38503
  this.onScrollProximityMove = (e) => {
@@ -38193,6 +38541,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38193
38541
  this.scene.priceStyle = opts.priceStyle;
38194
38542
  this.scene.basePainting = basePaintingOf(opts.priceStyle);
38195
38543
  this.scene.candleOverride = candleOverrideFor(opts.priceStyle, this.scene.style.chartTypes);
38544
+ if (typeof opts.priceAxisTicks === "function") this.priceAxisTicks.setHook(opts.priceAxisTicks);
38196
38545
  }
38197
38546
  this.theme = this.deriveTheme(DARK_THEME);
38198
38547
  }
@@ -38331,6 +38680,15 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38331
38680
  case "crosshairOverride":
38332
38681
  this.scene.crosshairOverride = sanitizeCrosshairOverride(value);
38333
38682
  break;
38683
+ case "drawingsInteractive":
38684
+ this.drawingsInteractive = value !== false;
38685
+ if (this.input) this.input.drawings = this.drawingsInteractive;
38686
+ if (!this.drawingsInteractive) this.userDrawings?.clearHover();
38687
+ return;
38688
+ // affects the next gesture only — clearHover repaints what it drops
38689
+ case "priceAxisTicks":
38690
+ this.priceAxisTicks.setHook(typeof value === "function" ? value : null);
38691
+ break;
38334
38692
  case "keyboard":
38335
38693
  this.setKeyboardEnabled(Boolean(value));
38336
38694
  return;
@@ -38446,6 +38804,10 @@ ${overlayScrollbarCss(".vela-sd-pane")}
38446
38804
  return { visible: this.scene.marks.visible, groups: { ...this.scene.marks.groups } };
38447
38805
  case "crosshairOverride":
38448
38806
  return this.scene.crosshairOverride ? { ...this.scene.crosshairOverride } : null;
38807
+ case "drawingsInteractive":
38808
+ return this.drawingsInteractive;
38809
+ case "priceAxisTicks":
38810
+ return this.priceAxisTicks.hook;
38449
38811
  case "keyboard":
38450
38812
  return this.keyboardEnabled;
38451
38813
  case "historyChords":
@@ -39119,6 +39481,12 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39119
39481
  Object.assign(this.drawingsCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
39120
39482
  this.cursorCanvas = document.createElement("canvas");
39121
39483
  Object.assign(this.cursorCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
39484
+ this.shadeRightEl = document.createElement("div");
39485
+ this.shadeRightEl.className = "vela-shade-right";
39486
+ this.shadeRightEl.setAttribute("aria-hidden", "true");
39487
+ this.shadeRightEl.hidden = true;
39488
+ Object.assign(this.shadeRightEl.style, { position: "absolute", top: "0", left: "0", width: "0", height: "0", pointerEvents: "none", display: "none" });
39489
+ this.shadeRightSig = "";
39122
39490
  this.overlayRoot = document.createElement("div");
39123
39491
  Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
39124
39492
  this.plot = document.createElement("div");
@@ -39126,7 +39494,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39126
39494
  this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
39127
39495
  const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
39128
39496
  const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
39129
- this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
39497
+ this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.shadeRightEl, this.cursorCanvas, this.overlayRoot);
39130
39498
  this.layerOrderSig = "";
39131
39499
  this.wrapper.appendChild(this.plot);
39132
39500
  this.factoryConfig = this.getConfig();
@@ -39184,13 +39552,14 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39184
39552
  drawingsPointerDown: (x, y, snap, shift4, mod) => this.userDrawings?.pointerDown(x, y, snap, shift4, mod),
39185
39553
  drawingsPointerMove: (x, y, snap, shift4, mod) => this.userDrawings?.pointerMove(x, y, snap, shift4, mod),
39186
39554
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
39187
- drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
39555
+ drawingsCursor: (x, y) => (this.drawingsInteractive ? this.userDrawings?.cursorAt(x, y) : null) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
39188
39556
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
39189
39557
  drawingsClearTransient: () => this.userDrawings?.clearTransient()
39190
39558
  });
39191
39559
  this.input.rightEdgeZoom = this.zoomAnchorMode === "right";
39192
39560
  this.input.axisDrag = this.axisDragEnabled;
39193
39561
  this.input.paneResize = this.paneResizeEnabled;
39562
+ this.input.drawings = this.drawingsInteractive;
39194
39563
  this.input.attach(this.dataCanvas);
39195
39564
  if (this.settingsEnabled) this.setSettingsEnabled(true);
39196
39565
  this.syncCountdownTimer();
@@ -39499,6 +39868,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39499
39868
  this.backend.destroy();
39500
39869
  this.chrome.destroy();
39501
39870
  this.crosshairLayer.destroy();
39871
+ this.shadeRightEl?.remove();
39872
+ this.shadeRightEl = null;
39502
39873
  this.attributionEl?.remove();
39503
39874
  this.attributionEl = null;
39504
39875
  this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
@@ -39845,14 +40216,21 @@ ${overlayScrollbarCss(".vela-sd-pane")}
39845
40216
  this.priceStyleCbs.add(cb);
39846
40217
  return () => this.priceStyleCbs.delete(cb);
39847
40218
  }
40219
+ onPriceStyleWillChange(cb) {
40220
+ this.priceStyleWillChangeCbs.add(cb);
40221
+ return () => this.priceStyleWillChangeCbs.delete(cb);
40222
+ }
39848
40223
  /**
39849
40224
  * THE single write path for the base price style at runtime (feature set / settings dialog /
39850
- * config template — the constructor seeds the field directly, pre-listeners). Updates the
39851
- * scene, eases any reveal layer toward the new style's target, and notifies the core —
39852
- * which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
40225
+ * config template — the constructor seeds the field directly, pre-listeners). Announces the
40226
+ * switch while the scene still holds the old style (a listener may paint/capture that frame),
40227
+ * then updates the scene, eases any reveal layer toward the new style's target, and notifies
40228
+ * the core — which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
39853
40229
  */
39854
40230
  setPriceStyle(style) {
39855
40231
  if (style === this.scene.priceStyle) return;
40232
+ const from = this.scene.priceStyle;
40233
+ for (const cb of this.priceStyleWillChangeCbs) cb(from, style);
39856
40234
  this.scene.priceStyle = style;
39857
40235
  this.scene.basePainting = basePaintingOf(style);
39858
40236
  this.scene.candleOverride = candleOverrideFor(style, this.scene.style.chartTypes);
@@ -40082,7 +40460,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
40082
40460
  if (this.easeLiveBar(dtMs)) active = true;
40083
40461
  this.skeletonClockMs += dtMs;
40084
40462
  this.paintData();
40085
- this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
40463
+ this.renderCrosshairLayer();
40086
40464
  this.updateLegendValues();
40087
40465
  this.emitViewportChange();
40088
40466
  return active;
@@ -40657,10 +41035,31 @@ ${overlayScrollbarCss(".vela-sd-pane")}
40657
41035
  this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
40658
41036
  this.trackMarkPopover();
40659
41037
  }
40660
- this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
41038
+ this.renderCrosshairLayer();
40661
41039
  if (!repaintsData(level) && this.paintedData) this.repaintCursorLayers();
40662
41040
  this.updateLegendValues();
40663
41041
  }
41042
+ /** Repaint the L2 crosshair canvas and keep the `.vela-shade-right` element on the veil it painted. */
41043
+ renderCrosshairLayer() {
41044
+ this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
41045
+ this.syncShadeRight();
41046
+ }
41047
+ /** Position (or hide) the `.vela-shade-right` element over the veil of the last crosshair frame. */
41048
+ syncShadeRight() {
41049
+ const el = this.shadeRightEl;
41050
+ if (!el) return;
41051
+ const area = this.crosshairLayer.shadeRightArea;
41052
+ const sig = area ? `${area.x},${area.width},${area.height}` : "";
41053
+ if (sig === this.shadeRightSig) return;
41054
+ this.shadeRightSig = sig;
41055
+ if (!area) {
41056
+ el.hidden = true;
41057
+ el.style.display = "none";
41058
+ return;
41059
+ }
41060
+ Object.assign(el.style, { left: `${area.x}px`, width: `${area.width}px`, height: `${area.height}px`, display: "" });
41061
+ el.hidden = false;
41062
+ }
40664
41063
  /** Repaint the SDK layers that opted into cursor tracking (their own canvas only). */
40665
41064
  repaintCursorLayers() {
40666
41065
  const pane = this.scene.panes.get(PRICE_PANE_ID);
@@ -41454,7 +41853,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
41454
41853
  if (this.animator.active) {
41455
41854
  this.computeScales();
41456
41855
  this.paintData();
41457
- this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
41856
+ this.renderCrosshairLayer();
41458
41857
  } else {
41459
41858
  this.scheduler.flushNow(4 /* Full */);
41460
41859
  }
@@ -45498,7 +45897,8 @@ ${overlayScrollbarCss(".vela-sd-pane")}
45498
45897
  glow: options.glow ?? 0,
45499
45898
  upColor: options.upColor ?? BULLISH,
45500
45899
  downColor: options.downColor ?? BEARISH,
45501
- priceStyle: options.priceStyle ?? "candles"
45900
+ priceStyle: options.priceStyle ?? "candles",
45901
+ priceAxisTicks: options.priceAxis?.ticks ?? null
45502
45902
  };
45503
45903
  const RendererClass = options.renderer ?? NativeRenderer;
45504
45904
  if (typeof RendererClass !== "function") {
@@ -45984,6 +46384,23 @@ ${overlayScrollbarCss(".vela-sd-pane")}
45984
46384
  return { ...Object.fromEntries(defaultEngines), ...overrides };
45985
46385
  }
45986
46386
 
46387
+ // src/widget/context-menu-model.ts
46388
+ var CONTEXT_MENU_BUILTIN_ORDER = {
46389
+ body: { "reset-view": -30, "remove-drawings": -20, "remove-indicators": -10, settings: 1e3 },
46390
+ "price-axis": {
46391
+ auto: -80,
46392
+ invert: -70,
46393
+ "scale:regular": -60,
46394
+ "scale:percent": -50,
46395
+ "scale:indexed": -40,
46396
+ "scale:log": -30,
46397
+ labels: -20,
46398
+ levels: -10,
46399
+ settings: 1e3
46400
+ },
46401
+ "time-axis": { timezone: -10, settings: 1e3 }
46402
+ };
46403
+
45987
46404
  // src/widget/side-panel.ts
45988
46405
  var DEFAULT_PANEL_WIDTH = 280;
45989
46406
  var DEFAULT_PANEL_MIN_WIDTH = 200;
@@ -47233,6 +47650,7 @@ ${overlayScrollbarCss(".vela-sd-pane")}
47233
47650
  exports.BinanceProvider = BinanceProvider;
47234
47651
  exports.CATEGORICAL = CATEGORICAL;
47235
47652
  exports.CHIP_PLATE = CHIP_PLATE;
47653
+ exports.CONTEXT_MENU_BUILTIN_ORDER = CONTEXT_MENU_BUILTIN_ORDER;
47236
47654
  exports.CROSSHAIR = CROSSHAIR;
47237
47655
  exports.CachingDataFeed = CachingDataFeed;
47238
47656
  exports.CoinbaseProvider = CoinbaseProvider;