@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/widget.cjs CHANGED
@@ -1565,6 +1565,55 @@ var Tooltip = class {
1565
1565
  this.trigger.removeAttribute("data-scope");
1566
1566
  }
1567
1567
  };
1568
+
1569
+ // src/ui/surface-exit.ts
1570
+ var CLOSING_ATTR = "data-closing";
1571
+ var SURFACE_EXIT_CAP_MS = 1e3;
1572
+ function animationsOf(els) {
1573
+ return els.flatMap((el) => el.getAnimations({ subtree: true }));
1574
+ }
1575
+ function ends(a) {
1576
+ return Number.isFinite(a.effect?.getComputedTiming().endTime ?? Number.POSITIVE_INFINITY);
1577
+ }
1578
+ function holdForExit(els, done, opts = {}) {
1579
+ const list = Array.isArray(els) ? els : [els];
1580
+ if (list.length === 0 || !list.every((el) => typeof el.getAnimations === "function")) {
1581
+ done();
1582
+ return null;
1583
+ }
1584
+ const before = new Set(animationsOf(list));
1585
+ for (const el of list) el.setAttribute(CLOSING_ATTR, "");
1586
+ const exit = animationsOf(list).filter((a) => !before.has(a) && ends(a));
1587
+ if (exit.length === 0) {
1588
+ for (const el of list) el.removeAttribute(CLOSING_ATTR);
1589
+ done();
1590
+ return null;
1591
+ }
1592
+ const frozen = [...list, ...opts.inert ?? []];
1593
+ const wasInert = frozen.map((el) => el.inert);
1594
+ for (const el of frozen) el.inert = true;
1595
+ let settled = false;
1596
+ const settle = (complete) => {
1597
+ if (settled) return;
1598
+ settled = true;
1599
+ clearTimeout(cap);
1600
+ for (const el of list) el.removeAttribute(CLOSING_ATTR);
1601
+ frozen.forEach((el, i) => {
1602
+ el.inert = wasInert[i];
1603
+ });
1604
+ if (complete) done();
1605
+ };
1606
+ const cap = setTimeout(() => settle(true), SURFACE_EXIT_CAP_MS);
1607
+ let pending = exit.length;
1608
+ const one = () => {
1609
+ if (--pending === 0) settle(true);
1610
+ };
1611
+ for (const a of exit) a.finished.then(one, one);
1612
+ return {
1613
+ cancel: () => settle(false),
1614
+ finish: () => settle(true)
1615
+ };
1616
+ }
1568
1617
  function menuController(opts) {
1569
1618
  return {
1570
1619
  machine: menu__namespace.machine,
@@ -1646,6 +1695,35 @@ var MENU_CSS = `
1646
1695
  color: var(--vela-fg-bright);
1647
1696
  }
1648
1697
  .vela-menu-item[data-checkmark] { color: var(--vela-fg-bright); }
1698
+ /* Badge mode (iconBadges): the row icon sits in a rounded tile and the row grows to fit
1699
+ it. An icon-less row of the same level keeps an invisible slot, so labels align. */
1700
+ .vela-menu[data-badges] > .vela-menu-item { padding: 5px 10px 5px 6px; }
1701
+ .vela-menu-item .vela-menu-badge {
1702
+ width: 24px;
1703
+ height: 24px;
1704
+ padding: 3px;
1705
+ flex: none;
1706
+ box-sizing: border-box;
1707
+ display: inline-flex;
1708
+ align-items: center;
1709
+ justify-content: center;
1710
+ border-radius: var(--vela-radius-md);
1711
+ background: var(--vela-hover);
1712
+ border: 1px solid var(--vela-border);
1713
+ color: var(--vela-fg-muted);
1714
+ 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);
1715
+ }
1716
+ .vela-menu-item .vela-menu-badge:empty { background: transparent; border-color: transparent; }
1717
+ .vela-menu-item .vela-menu-badge .vela-icon { color: inherit; }
1718
+ .vela-menu-item[data-highlighted] .vela-menu-badge:not(:empty) { color: var(--vela-fg-bright); }
1719
+ /* The selected tile inverts: a bright fill under the surface-colored glyph. After the
1720
+ hover rule, so a selected row keeps its tile while highlighted. */
1721
+ .vela-menu-item[data-checked] .vela-menu-badge:not(:empty),
1722
+ .vela-menu-item[data-checkmark] .vela-menu-badge:not(:empty) {
1723
+ background: color-mix(in srgb, var(--vela-fg-bright) 83%, var(--vela-surface));
1724
+ border-color: color-mix(in srgb, var(--vela-fg-bright) 40%, transparent);
1725
+ color: var(--vela-surface);
1726
+ }
1649
1727
  /* Switch rows (boolean settings in a dropdown): a right-aligned toggle pill \u2014 the
1650
1728
  same control language as the settings dialog's toggles. */
1651
1729
  .vela-menu-switch {
@@ -1712,11 +1790,14 @@ var Surface = class _Surface {
1712
1790
  this.pinnedAnchor = null;
1713
1791
  /** The open state last announced — see {@link announce}. */
1714
1792
  this.shown = false;
1793
+ /** The list's exit animation after a close; a reopen cancels it. */
1794
+ this.exit = null;
1715
1795
  this.doc = doc;
1716
1796
  this.host = opts.host;
1717
1797
  this.onSelect = opts.onSelect;
1718
1798
  this.onFavorite = opts.onFavorite;
1719
1799
  this.checkmarks = opts.checkmarks === true;
1800
+ this.iconBadges = opts.iconBadges === true;
1720
1801
  this.positioner = doc.createElement("div");
1721
1802
  this.positioner.className = "vela-ui-layer";
1722
1803
  this.list = doc.createElement("ul");
@@ -1747,14 +1828,30 @@ var Surface = class _Surface {
1747
1828
  if (opts.triggerId && trigger) trigger.id = opts.triggerId;
1748
1829
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
1749
1830
  const api = this.ctrl.connect(service);
1750
- if (this.shown && !api.open) this.announce(false);
1831
+ const closing = this.shown && !api.open;
1832
+ if (closing) this.announce(false);
1833
+ if (api.open && this.exit) {
1834
+ this.exit.cancel();
1835
+ this.exit = null;
1836
+ }
1751
1837
  if (trigger) vanilla.spreadProps(trigger, api.getTriggerProps(), this.mid);
1752
1838
  vanilla.spreadProps(this.positioner, api.getPositionerProps(), this.mid);
1753
1839
  vanilla.spreadProps(this.list, api.getContentProps(), this.mid);
1754
1840
  this.project(api);
1841
+ if (closing) this.holdExit();
1755
1842
  if (api.open && !this.shown) this.announce(true);
1756
1843
  });
1757
1844
  }
1845
+ /** Keep the just-closed list up through its exit animation. `spreadProps` re-applies
1846
+ * `hidden` only when the machine's value for it changes, so the list stays shown until
1847
+ * the exit ends — or until a reopen, which projects `hidden: false` itself. */
1848
+ holdExit() {
1849
+ this.list.hidden = false;
1850
+ this.exit = holdForExit(this.list, () => {
1851
+ this.exit = null;
1852
+ if (!this.shown) this.list.hidden = true;
1853
+ });
1854
+ }
1758
1855
  get api() {
1759
1856
  return this.ctrl.connect(this.handle.service);
1760
1857
  }
@@ -1778,6 +1875,7 @@ var Surface = class _Surface {
1778
1875
  destroy() {
1779
1876
  for (const sub2 of this.subs.values()) sub2.destroy();
1780
1877
  this.subs.clear();
1878
+ this.exit?.finish();
1781
1879
  this.handle.stop();
1782
1880
  if (this.shown) this.announce(false);
1783
1881
  this.positioner.remove();
@@ -1804,6 +1902,10 @@ var Surface = class _Surface {
1804
1902
  const doc = this.doc;
1805
1903
  this.list.replaceChildren();
1806
1904
  const markable = this.checkmarks && this.items.some((i) => !(i.submenu && i.submenu.length > 0) && !i.toggle && i.checked !== void 0);
1905
+ const iconic = this.items.some((i) => !!i.icon);
1906
+ const badged = this.iconBadges && iconic;
1907
+ if (badged) this.list.dataset.badges = "1";
1908
+ else delete this.list.dataset.badges;
1807
1909
  for (const item of this.items) {
1808
1910
  if (item.separatorBefore) {
1809
1911
  const sep = doc.createElement("li");
@@ -1829,7 +1931,16 @@ var Surface = class _Surface {
1829
1931
  } else if (!branch && !item.toggle && item.checked) {
1830
1932
  li.dataset.checked = "1";
1831
1933
  }
1832
- if (item.icon) li.appendChild(iconEl(item.icon, doc));
1934
+ if (badged) {
1935
+ const badge = doc.createElement("span");
1936
+ badge.className = "vela-menu-badge";
1937
+ if (item.icon) badge.appendChild(iconEl(item.icon, doc));
1938
+ li.appendChild(badge);
1939
+ } else if (item.icon) {
1940
+ li.appendChild(iconEl(item.icon, doc));
1941
+ } else if (iconic) {
1942
+ li.appendChild(iconEl("", doc));
1943
+ }
1833
1944
  const label = doc.createElement("span");
1834
1945
  label.className = "vela-menu-label";
1835
1946
  label.textContent = item.label;
@@ -1877,6 +1988,7 @@ var Surface = class _Surface {
1877
1988
  onSelect: this.onSelect,
1878
1989
  onFavorite: this.onFavorite,
1879
1990
  checkmarks: this.checkmarks,
1991
+ iconBadges: this.iconBadges,
1880
1992
  id: `${this.mid}--${item.id}`
1881
1993
  });
1882
1994
  sub2.setItems(item.submenu ?? []);
@@ -1902,7 +2014,8 @@ var Menu = class {
1902
2014
  id: opts.id,
1903
2015
  minWidth: opts.minWidth,
1904
2016
  onFavorite: opts.onFavorite,
1905
- checkmarks: opts.checkmarks
2017
+ checkmarks: opts.checkmarks,
2018
+ iconBadges: opts.iconBadges
1906
2019
  });
1907
2020
  this.root.setItems(opts.items);
1908
2021
  }
@@ -2006,6 +2119,7 @@ var POPOVER_CSS = `
2006
2119
 
2007
2120
  // src/ui/components/popover/view.ts
2008
2121
  var open = null;
2122
+ var leaving = /* @__PURE__ */ new Set();
2009
2123
  function closeOpenPopovers() {
2010
2124
  open?.hide();
2011
2125
  }
@@ -2033,6 +2147,8 @@ var Popover = class {
2033
2147
  this.shown = false;
2034
2148
  /** The pending removal of a fading-out shell; a show() that reuses the shell cancels it. */
2035
2149
  this.leaveTimer = null;
2150
+ /** The shell's exit animation before it leaves the DOM; a show() that reuses the shell cancels it. */
2151
+ this.exit = null;
2036
2152
  const doc = opts.trigger.ownerDocument;
2037
2153
  injectStyles(POPOVER_STYLE_ID, POPOVER_CSS, doc);
2038
2154
  this.trigger = opts.trigger;
@@ -2069,6 +2185,10 @@ var Popover = class {
2069
2185
  clearTimeout(this.leaveTimer);
2070
2186
  this.leaveTimer = null;
2071
2187
  }
2188
+ this.exit?.cancel();
2189
+ this.exit = null;
2190
+ leaving.delete(this);
2191
+ for (const p of [...leaving]) if (p.trigger === this.trigger) p.exit?.finish();
2072
2192
  ensureUIHost(this.el, this.inheritsTokens ? void 0 : this.theme);
2073
2193
  if (this.fadeMs > 0) {
2074
2194
  this.el.style.transition = `opacity ${this.fadeMs}ms ease`;
@@ -2125,7 +2245,12 @@ var Popover = class {
2125
2245
  this.el.remove();
2126
2246
  }, this.fadeMs);
2127
2247
  } else {
2128
- this.el.remove();
2248
+ this.exit = holdForExit(this.el, () => {
2249
+ this.exit = null;
2250
+ leaving.delete(this);
2251
+ this.el.remove();
2252
+ });
2253
+ if (this.exit) leaving.add(this);
2129
2254
  }
2130
2255
  if (open === this) open = null;
2131
2256
  this.ctrl.onClose?.();
@@ -2137,6 +2262,7 @@ var Popover = class {
2137
2262
  }
2138
2263
  destroy() {
2139
2264
  this.hide();
2265
+ this.exit?.finish();
2140
2266
  }
2141
2267
  reposition() {
2142
2268
  if (this.shown) this.place();
@@ -2334,6 +2460,9 @@ var Dialog = class {
2334
2460
  constructor(opts = {}) {
2335
2461
  this.opener = null;
2336
2462
  this.wasOpen = false;
2463
+ /** The panel and scrim's exit animation after a close; a reopen cancels it. */
2464
+ this.exit = null;
2465
+ this.destroyed = false;
2337
2466
  const doc = (opts.host ?? document.body).ownerDocument;
2338
2467
  injectStyles(DIALOG_STYLE_ID, DIALOG_CSS, doc);
2339
2468
  const host = opts.host ?? doc.body;
@@ -2430,19 +2559,57 @@ var Dialog = class {
2430
2559
  const mid = String(this.ctrl.props.id);
2431
2560
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
2432
2561
  const api = this.ctrl.connect(service);
2433
- if (this.wasOpen && !api.open) this.announce(false);
2562
+ const closing = this.wasOpen && !api.open;
2563
+ if (closing) this.announce(false);
2434
2564
  vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
2435
2565
  vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
2436
2566
  vanilla.spreadProps(this.panel, api.getContentProps(), mid);
2437
2567
  vanilla.spreadProps(title, api.getTitleProps(), mid);
2438
2568
  vanilla.spreadProps(close, api.getCloseTriggerProps(), mid);
2439
- this.backdrop.style.display = api.open ? "" : "none";
2440
- this.positioner.style.display = api.open ? "" : "none";
2569
+ if (api.open) {
2570
+ this.exit?.cancel();
2571
+ this.exit = null;
2572
+ this.setShown(true);
2573
+ } else if (closing) {
2574
+ this.holdExit();
2575
+ } else if (!this.exit) {
2576
+ this.setShown(false);
2577
+ }
2441
2578
  if (api.open && !this.wasOpen) this.announce(true);
2442
2579
  if (this.wasOpen && !api.open) this.restoreOpener();
2443
2580
  this.wasOpen = api.open;
2444
2581
  });
2445
2582
  }
2583
+ setShown(shown) {
2584
+ this.backdrop.style.display = shown ? "" : "none";
2585
+ this.positioner.style.display = shown ? "" : "none";
2586
+ }
2587
+ /** Keep the just-closed dialog up through its exit animation, then hide it — or
2588
+ * remove it, when it was destroyed meanwhile. `spreadProps` re-applies the close
2589
+ * props' `hidden` only when its value changes, so lifting it keeps the panel and
2590
+ * scrim up until the exit ends. The positioner spans the viewport: inert, it lets
2591
+ * clicks through to the page while the panel animates out. */
2592
+ holdExit() {
2593
+ const held = [this.panel, this.backdrop];
2594
+ for (const el of held) el.hidden = false;
2595
+ this.exit = holdForExit(
2596
+ held,
2597
+ () => {
2598
+ this.exit = null;
2599
+ if (this.destroyed) {
2600
+ this.detach();
2601
+ } else if (!this.open) {
2602
+ for (const el of held) el.hidden = true;
2603
+ this.setShown(false);
2604
+ }
2605
+ },
2606
+ { inert: [this.positioner] }
2607
+ );
2608
+ }
2609
+ detach() {
2610
+ this.backdrop.remove();
2611
+ this.positioner.remove();
2612
+ }
2446
2613
  /** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
2447
2614
  * teardown that outruns the machine's close notification). */
2448
2615
  restoreOpener() {
@@ -2471,15 +2638,16 @@ var Dialog = class {
2471
2638
  return el?.closest(".vela-popover, .vela-menu") != null;
2472
2639
  }
2473
2640
  destroy() {
2641
+ if (this.wasOpen && !this.open) this.handle.flush();
2474
2642
  if (this.open) this.hide();
2475
2643
  this.handle.stop();
2644
+ this.destroyed = true;
2476
2645
  if (this.wasOpen) {
2477
2646
  this.wasOpen = false;
2478
2647
  this.announce(false);
2479
2648
  }
2480
2649
  this.restoreOpener();
2481
- this.backdrop.remove();
2482
- this.positioner.remove();
2650
+ if (!this.exit) this.detach();
2483
2651
  }
2484
2652
  };
2485
2653
  function drawerController(opts = {}) {
@@ -2608,6 +2776,9 @@ var Drawer = class {
2608
2776
  constructor(opts = {}) {
2609
2777
  this.opener = null;
2610
2778
  this.wasOpen = false;
2779
+ /** The sheet and scrim's exit animation after a close; a reopen cancels it. */
2780
+ this.exit = null;
2781
+ this.destroyed = false;
2611
2782
  const doc = (opts.host ?? document.body).ownerDocument;
2612
2783
  injectStyles(DRAWER_STYLE_ID, DRAWER_CSS, doc);
2613
2784
  const host = opts.host ?? doc.body;
@@ -2635,13 +2806,21 @@ var Drawer = class {
2635
2806
  const mid = String(this.ctrl.props.id);
2636
2807
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
2637
2808
  const api = this.ctrl.connect(service);
2638
- if (this.wasOpen && !api.open) this.announce(false);
2809
+ const closing = this.wasOpen && !api.open;
2810
+ if (closing) this.announce(false);
2639
2811
  vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
2640
2812
  vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
2641
2813
  vanilla.spreadProps(this.panel, api.getContentProps(), mid);
2642
2814
  vanilla.spreadProps(this.titleEl, api.getTitleProps(), mid);
2643
- this.backdrop.style.display = api.open ? "" : "none";
2644
- this.positioner.style.display = api.open ? "" : "none";
2815
+ if (api.open) {
2816
+ this.exit?.cancel();
2817
+ this.exit = null;
2818
+ this.setShown(true);
2819
+ } else if (closing) {
2820
+ this.holdExit();
2821
+ } else if (!this.exit) {
2822
+ this.setShown(false);
2823
+ }
2645
2824
  if (api.open && !this.wasOpen) this.announce(true);
2646
2825
  if (this.wasOpen && !api.open) this.restoreOpener();
2647
2826
  this.wasOpen = api.open;
@@ -2753,6 +2932,36 @@ var Drawer = class {
2753
2932
  setTitle(title) {
2754
2933
  this.titleEl.textContent = title;
2755
2934
  }
2935
+ setShown(shown) {
2936
+ this.backdrop.style.display = shown ? "" : "none";
2937
+ this.positioner.style.display = shown ? "" : "none";
2938
+ }
2939
+ /** Keep the just-closed sheet up through its exit animation, then hide it — or
2940
+ * remove it, when it was destroyed meanwhile. `spreadProps` re-applies the close
2941
+ * props' `hidden` only when its value changes, so lifting it keeps the sheet and
2942
+ * scrim up until the exit ends. The positioner spans the host: inert, it lets
2943
+ * clicks through to the page while the sheet animates out. */
2944
+ holdExit() {
2945
+ const held = [this.panel, this.backdrop];
2946
+ for (const el of held) el.hidden = false;
2947
+ this.exit = holdForExit(
2948
+ held,
2949
+ () => {
2950
+ this.exit = null;
2951
+ if (this.destroyed) {
2952
+ this.detach();
2953
+ } else if (!this.open) {
2954
+ for (const el of held) el.hidden = true;
2955
+ this.setShown(false);
2956
+ }
2957
+ },
2958
+ { inert: [this.positioner] }
2959
+ );
2960
+ }
2961
+ detach() {
2962
+ this.backdrop.remove();
2963
+ this.positioner.remove();
2964
+ }
2756
2965
  /** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
2757
2966
  * teardown that outruns the machine's close notification). */
2758
2967
  restoreOpener() {
@@ -2775,14 +2984,15 @@ var Drawer = class {
2775
2984
  this.ctrl.connect(this.handle.service).setOpen(false);
2776
2985
  }
2777
2986
  destroy() {
2987
+ if (this.wasOpen && !this.open) this.handle.flush();
2778
2988
  this.handle.stop();
2989
+ this.destroyed = true;
2779
2990
  if (this.wasOpen) {
2780
2991
  this.wasOpen = false;
2781
2992
  this.announce(false);
2782
2993
  }
2783
2994
  this.restoreOpener();
2784
- this.backdrop.remove();
2785
- this.positioner.remove();
2995
+ if (!this.exit) this.detach();
2786
2996
  }
2787
2997
  };
2788
2998
 
@@ -4918,11 +5128,11 @@ var DatePicker = class {
4918
5128
  };
4919
5129
 
4920
5130
  // src/widget/layout-picker.ts
4921
- var STYLE_ID = "vela-widget-layout-picker-v14";
5131
+ var STYLE_ID = "vela-widget-layout-picker-v15";
4922
5132
  var CSS2 = `
4923
5133
  .vela-lp-layer { position: absolute; z-index: var(--vela-z-menu); }
4924
5134
  .vela-lp {
4925
- background: var(--vela-surface-elev);
5135
+ background: var(--vela-surface);
4926
5136
  color: var(--vela-fg);
4927
5137
  border: 1px solid var(--vela-border-strong);
4928
5138
  border-radius: 8px;
@@ -5051,6 +5261,8 @@ var LayoutPicker = class {
5051
5261
  constructor(opts) {
5052
5262
  this.squares = [];
5053
5263
  this.isOpen = false;
5264
+ /** The card's exit animation after a close; a reopen cancels it. */
5265
+ this.exit = null;
5054
5266
  /** Hover preview (1-based rows/cols), null = show current shape. */
5055
5267
  this.hover = null;
5056
5268
  this.onDocPointerDown = (e) => {
@@ -5145,6 +5357,8 @@ var LayoutPicker = class {
5145
5357
  open() {
5146
5358
  if (this.isOpen) return;
5147
5359
  this.isOpen = true;
5360
+ this.exit?.cancel();
5361
+ this.exit = null;
5148
5362
  this.hover = null;
5149
5363
  this.refresh();
5150
5364
  this.layer.style.display = "";
@@ -5159,7 +5373,10 @@ var LayoutPicker = class {
5159
5373
  if (!this.isOpen) return;
5160
5374
  this.isOpen = false;
5161
5375
  announceSurface(this.panel, false, "popover", this.opts.trigger);
5162
- this.layer.style.display = "none";
5376
+ this.exit = holdForExit(this.panel, () => {
5377
+ this.exit = null;
5378
+ if (!this.isOpen) this.layer.style.display = "none";
5379
+ });
5163
5380
  this.opts.trigger.setAttribute("aria-expanded", "false");
5164
5381
  this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
5165
5382
  this.doc.removeEventListener("keydown", this.onDocKeydown, true);
@@ -5174,6 +5391,7 @@ var LayoutPicker = class {
5174
5391
  }
5175
5392
  destroy() {
5176
5393
  this.close();
5394
+ this.exit?.finish();
5177
5395
  this.infoTip.destroy();
5178
5396
  this.layer.remove();
5179
5397
  }
@@ -5240,7 +5458,7 @@ var LayoutPicker = class {
5240
5458
  const hostRect = this.opts.host.getBoundingClientRect();
5241
5459
  const trigRect = this.opts.trigger.getBoundingClientRect();
5242
5460
  let left = trigRect.left - hostRect.left;
5243
- const top = trigRect.bottom - hostRect.top + 4;
5461
+ const top = trigRect.bottom - hostRect.top + 8;
5244
5462
  const width = this.layer.offsetWidth;
5245
5463
  if (left + width > hostRect.width - 8) left = Math.max(8, hostRect.width - 8 - width);
5246
5464
  this.layer.style.left = `${left}px`;
@@ -6271,7 +6489,8 @@ var Topbar = class {
6271
6489
  triggerId: "vela-topbar-style",
6272
6490
  host,
6273
6491
  items: this.styleItems(),
6274
- onSelect: (id) => opts.onPriceStyle(id)
6492
+ onSelect: (id) => opts.onPriceStyle(id),
6493
+ iconBadges: true
6275
6494
  });
6276
6495
  }
6277
6496
  setSymbol(symbol) {
@@ -6337,8 +6556,14 @@ var Topbar = class {
6337
6556
  renderLayoutButton(doc) {
6338
6557
  if (!this.layoutButton) return;
6339
6558
  this.layoutButton.replaceChildren();
6340
- if (iconMarkup("layout")) this.layoutButton.appendChild(iconEl("layout", doc));
6341
- else this.layoutButton.appendChild(doc.createTextNode(this.layoutId ?? ""));
6559
+ const glyph = this.opts.layout?.glyph?.();
6560
+ if (glyph || iconMarkup("layout")) {
6561
+ const icon2 = iconEl("layout", doc);
6562
+ if (glyph) icon2.innerHTML = glyph;
6563
+ this.layoutButton.appendChild(icon2);
6564
+ } else {
6565
+ this.layoutButton.appendChild(doc.createTextNode(this.layoutId ?? ""));
6566
+ }
6342
6567
  this.layoutButton.setAttribute("aria-label", `Layout \u2014 ${this.layoutId ?? ""}`);
6343
6568
  }
6344
6569
  renderStyleButton(doc) {
@@ -13013,6 +13238,11 @@ var SidePanel = class {
13013
13238
  this.maximizedOn = false;
13014
13239
  /** Set while the close is announced, so a listener's own close does not re-enter. */
13015
13240
  this.closing = false;
13241
+ /** Open as far as the dock and the host are concerned — a closed panel can still be on
13242
+ * screen while its exit animation runs. */
13243
+ this.isOpen = false;
13244
+ /** The exit animation of a close; a reopen cancels it. */
13245
+ this.exit = null;
13016
13246
  const doc = host.ownerDocument;
13017
13247
  injectStyles(STYLE_ID4, CSS5, doc);
13018
13248
  this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
@@ -13061,20 +13291,44 @@ var SidePanel = class {
13061
13291
  host.appendChild(this.el);
13062
13292
  }
13063
13293
  get open() {
13064
- return !this.el.hidden;
13294
+ return this.isOpen;
13065
13295
  }
13066
- /** Open/close the panel — a bare call flips it. */
13067
- toggle(open2 = this.el.hidden) {
13068
- if (open2 === !this.el.hidden || !open2 && this.closing) return;
13296
+ /**
13297
+ * Open/close the panel — a bare call flips it. The panel reports closed at once, but a
13298
+ * close keeps it on screen, marked `data-closing`, while a host exit animation runs.
13299
+ * `instant` skips that, and also cuts short the exit of a panel already closing: a panel
13300
+ * handing the dock to another leaves at once.
13301
+ */
13302
+ toggle(open2 = !this.isOpen, instant = false) {
13303
+ if (open2 === this.isOpen) {
13304
+ if (!open2 && instant) this.exit?.finish();
13305
+ return;
13306
+ }
13307
+ if (!open2 && this.closing) return;
13069
13308
  if (!open2) {
13070
13309
  this.closing = true;
13071
13310
  announceSurface(this.el, false, "panel");
13072
13311
  this.closing = false;
13073
13312
  }
13074
- this.el.hidden = !open2;
13075
- if (!open2) this.setMaximized(false);
13313
+ this.isOpen = open2;
13314
+ if (open2) {
13315
+ if (this.exit) {
13316
+ this.exit.cancel();
13317
+ this.exit = null;
13318
+ this.setMaximized(false);
13319
+ }
13320
+ this.el.hidden = false;
13321
+ } else {
13322
+ const hide = () => {
13323
+ this.exit = null;
13324
+ this.el.hidden = true;
13325
+ this.setMaximized(false);
13326
+ };
13327
+ if (instant) hide();
13328
+ else this.exit = holdForExit(this.el, hide);
13329
+ }
13076
13330
  this.onOpenChange?.(open2);
13077
- if (open2 && !this.el.hidden) announceSurface(this.el, true, "panel");
13331
+ if (open2 && this.isOpen) announceSurface(this.el, true, "panel");
13078
13332
  }
13079
13333
  /** Whether the panel covers every chart right now. */
13080
13334
  get maximized() {
@@ -13148,6 +13402,7 @@ var SidePanel = class {
13148
13402
  }
13149
13403
  destroy() {
13150
13404
  if (this.open) announceSurface(this.el, false, "panel");
13405
+ this.exit?.finish();
13151
13406
  this.el.remove();
13152
13407
  }
13153
13408
  /** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
@@ -13452,8 +13707,8 @@ var ObjectTree = class extends SidePanel {
13452
13707
  set groups(next) {
13453
13708
  if (this.chart) this.groupsPerChart.set(this.chart, next);
13454
13709
  }
13455
- toggle(open2 = this.el.hidden) {
13456
- super.toggle(open2);
13710
+ toggle(open2 = !this.open, instant = false) {
13711
+ super.toggle(open2, instant);
13457
13712
  if (open2) this.refresh();
13458
13713
  }
13459
13714
  setSymbol(symbol) {
@@ -14487,8 +14742,8 @@ var DataWindow = class extends SidePanel {
14487
14742
  this.unsubs = [];
14488
14743
  injectStyles(STYLE_ID6, CSS7, host.ownerDocument);
14489
14744
  }
14490
- toggle(open2 = this.el.hidden) {
14491
- super.toggle(open2);
14745
+ toggle(open2 = !this.open, instant = false) {
14746
+ super.toggle(open2, instant);
14492
14747
  if (open2) this.refresh();
14493
14748
  }
14494
14749
  /** (Re)bind to a chart instance — called after every widget rebuild. */
@@ -14658,7 +14913,7 @@ var PanelDock = class {
14658
14913
  }
14659
14914
  this.pinned = new Set(state.pinned ?? []);
14660
14915
  for (const entry of this.entries) entry.panel.setOverlay(!this.pinned.has(entry.id));
14661
- for (const entry of this.entries) entry.panel.toggle(entry.id === state.open);
14916
+ for (const entry of this.entries) entry.panel.toggle(entry.id === state.open, true);
14662
14917
  this.pendingOpen = state.open && !this.entries.some((e) => e.id === state.open) ? state.open : null;
14663
14918
  }
14664
14919
  /** Drop the contributed panels (the shell destroys its own). */
@@ -14674,7 +14929,7 @@ var PanelDock = class {
14674
14929
  if (this.pinned.has(entry.id)) entry.panel.setOverlay(false);
14675
14930
  entry.panel.onOpenChange = (open2) => {
14676
14931
  if (open2) {
14677
- for (const other of this.entries) if (other !== entry) other.panel.toggle(false);
14932
+ for (const other of this.entries) if (other !== entry) other.panel.toggle(false, true);
14678
14933
  this.pendingOpen = null;
14679
14934
  }
14680
14935
  this.deps.chrome.setPanelActive(entry.id, open2);
@@ -16378,7 +16633,11 @@ var MoreDrawer = class {
16378
16633
  row(doc, label, opts) {
16379
16634
  const el = doc.createElement("div");
16380
16635
  el.className = "vela-md-row";
16381
- if (opts.icon) el.appendChild(iconEl(opts.icon, doc));
16636
+ if (opts.icon) {
16637
+ const icon2 = iconEl(opts.icon, doc);
16638
+ if (opts.glyph) icon2.innerHTML = opts.glyph;
16639
+ el.appendChild(icon2);
16640
+ }
16382
16641
  const text = doc.createElement("span");
16383
16642
  text.className = "vela-md-row-label";
16384
16643
  text.textContent = label;
@@ -16420,7 +16679,7 @@ var MoreDrawer = class {
16420
16679
  if (this.opts.layout) {
16421
16680
  const shape = this.opts.layout.shape();
16422
16681
  const value = shape ? `${shape.cols} \xD7 ${shape.rows}` : this.opts.layout.presets().find((p) => p.checked)?.label;
16423
- list.appendChild(this.row(doc, "Layout", { icon: "layout", value, chevron: true, onClick: () => this.show("layout") }));
16682
+ list.appendChild(this.row(doc, "Layout", { icon: "layout", glyph: this.opts.layout.glyph?.(), value, chevron: true, onClick: () => this.show("layout") }));
16424
16683
  }
16425
16684
  for (const act of this.opts.primaryActions?.() ?? []) list.appendChild(this.actionRow(doc, act));
16426
16685
  for (const panel of this.opts.panels()) {
@@ -16598,8 +16857,8 @@ var SETTINGS_SECTION = {
16598
16857
  "price-axis": "Scales and lines",
16599
16858
  "time-axis": "Scales and lines"
16600
16859
  };
16601
- function settingsItem(zone, label) {
16602
- return { id: `settings:${SETTINGS_SECTION[zone]}`, label, separatorBefore: true };
16860
+ function settingsItem(zone, label, icon2) {
16861
+ return { id: `settings:${SETTINGS_SECTION[zone]}`, label, ...icon2 ? { icon: icon2 } : {}, separatorBefore: true };
16603
16862
  }
16604
16863
  function settingsSectionOf(id) {
16605
16864
  return id.slice("settings:".length) || void 0;
@@ -16679,12 +16938,39 @@ function timeAxisItems(timezone) {
16679
16938
  }
16680
16939
  function bodyItems(counts) {
16681
16940
  return [
16682
- { id: "reset-view", label: "Reset chart view" },
16683
- { id: "remove-drawings", label: "Remove drawings", disabled: counts.drawings === 0, separatorBefore: true },
16684
- { id: "remove-indicators", label: "Remove indicators", disabled: counts.indicators === 0 },
16685
- settingsItem("body", "Settings\u2026")
16941
+ { id: "reset-view", label: "Reset chart view", icon: "reset" },
16942
+ { id: "remove-drawings", label: "Remove drawings", icon: "eraser", disabled: counts.drawings === 0, separatorBefore: true },
16943
+ { id: "remove-indicators", label: "Remove indicators", icon: "indicators", disabled: counts.indicators === 0 },
16944
+ settingsItem("body", "Settings\u2026", "gear")
16686
16945
  ];
16687
16946
  }
16947
+ var CONTEXT_MENU_BUILTIN_ORDER = {
16948
+ body: { "reset-view": -30, "remove-drawings": -20, "remove-indicators": -10, settings: 1e3 },
16949
+ "price-axis": {
16950
+ auto: -80,
16951
+ invert: -70,
16952
+ "scale:regular": -60,
16953
+ "scale:percent": -50,
16954
+ "scale:indexed": -40,
16955
+ "scale:log": -30,
16956
+ labels: -20,
16957
+ levels: -10,
16958
+ settings: 1e3
16959
+ },
16960
+ "time-axis": { timezone: -10, settings: 1e3 }
16961
+ };
16962
+ function composeMenu(zone, builtin, contributed) {
16963
+ const ranks = CONTEXT_MENU_BUILTIN_ORDER[zone];
16964
+ const rows = [];
16965
+ let group = 0;
16966
+ for (const item of builtin) {
16967
+ if (item.separatorBefore) group += 1;
16968
+ rows.push({ item, rank: ranks[item.id.startsWith("settings") ? "settings" : item.id] ?? 0, group });
16969
+ }
16970
+ for (const { item, order } of contributed) rows.push({ item, rank: order ?? 0, group: -1 });
16971
+ rows.sort((a, b) => a.rank - b.rank);
16972
+ return rows.map(({ item, group: g }, i) => ({ ...item, separatorBefore: i > 0 && rows[i - 1].group !== g }));
16973
+ }
16688
16974
 
16689
16975
  // src/widget/price-scale-drawer.ts
16690
16976
  var STYLE_ID17 = "vela-widget-price-scale-drawer";
@@ -17959,6 +18245,7 @@ function chromeHint(text, opts) {
17959
18245
  // src/renderers/native/drawings/DrawingToolbar.ts
17960
18246
  var TOOLBAR_WIDTH = 44;
17961
18247
  var TOOLBAR_COLLAPSED_WIDTH = 16;
18248
+ var FLYOUT_GAP = 3;
17962
18249
  var DrawingToolbar = class {
17963
18250
  constructor(host, theme, onArm, onMagnet = () => {
17964
18251
  }, onMeasure = () => {
@@ -17988,6 +18275,9 @@ var DrawingToolbar = class {
17988
18275
  // group id (or MAGNET_ID) whose flyout is open
17989
18276
  this.flyoutCell = null;
17990
18277
  // the cell the open flyout is anchored to
18278
+ /** Closed flyouts still playing their exit animation, by owner id — reopening the same
18279
+ * owner takes its element back instead of stacking a second one on it. */
18280
+ this.exitingFlyouts = /* @__PURE__ */ new Map();
17991
18281
  this.groupCells = /* @__PURE__ */ new Map();
17992
18282
  // the composite cell (hover/active bg + flyout anchor)
17993
18283
  this.groupIcons = /* @__PURE__ */ new Map();
@@ -18096,6 +18386,7 @@ var DrawingToolbar = class {
18096
18386
  }
18097
18387
  destroy() {
18098
18388
  this.closeFlyout();
18389
+ for (const { exit } of [...this.exitingFlyouts.values()]) exit.finish();
18099
18390
  for (const dispose of this.tipDisposers.splice(0)) dispose();
18100
18391
  this.root.remove();
18101
18392
  }
@@ -18323,16 +18614,22 @@ var DrawingToolbar = class {
18323
18614
  * outside-dismiss. Callers fill it with items. */
18324
18615
  beginFlyout(ownerId, cell) {
18325
18616
  const t = this.theme;
18326
- const fly = document.createElement("div");
18617
+ const back = this.exitingFlyouts.get(ownerId);
18618
+ if (back) {
18619
+ this.exitingFlyouts.delete(ownerId);
18620
+ back.exit.cancel();
18621
+ back.fly.replaceChildren();
18622
+ }
18623
+ const fly = back?.fly ?? document.createElement("div");
18327
18624
  fly.className = "vela-dtb-flyout";
18328
- 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;`;
18625
+ 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;`;
18329
18626
  applyChromeTokens(fly, t);
18330
18627
  this.host.appendChild(fly);
18331
18628
  const r = cell.getBoundingClientRect();
18332
18629
  const rootR = this.root.getBoundingClientRect();
18333
18630
  const hostR = this.host.getBoundingClientRect();
18334
18631
  const top = r.top - hostR.top;
18335
- fly.style.left = `${rootR.right - hostR.left - 1}px`;
18632
+ fly.style.left = `${rootR.right - hostR.left + FLYOUT_GAP}px`;
18336
18633
  fly.style.top = `${top}px`;
18337
18634
  fly.style.maxHeight = `${Math.max(120, hostR.height - top - 8)}px`;
18338
18635
  this.flyout = fly;
@@ -18348,6 +18645,7 @@ var DrawingToolbar = class {
18348
18645
  if (!cell) return;
18349
18646
  const fly = this.beginFlyout(group.id, cell);
18350
18647
  const sections = group.sections ?? [{ label: "", tools: group.tools }];
18648
+ const badge = sections.some((s) => s.tools.some((tool) => !!tool.icon));
18351
18649
  for (let si = 0; si < sections.length; si++) {
18352
18650
  const section = sections[si];
18353
18651
  if (section.label) {
@@ -18358,6 +18656,7 @@ var DrawingToolbar = class {
18358
18656
  fly.appendChild(
18359
18657
  this.makeFlyoutItem({
18360
18658
  icon: tool.icon,
18659
+ badge,
18361
18660
  label: tool.label,
18362
18661
  selected: tool.type === this.active,
18363
18662
  shortcut: this.shortcuts.get(tool.type),
@@ -18408,21 +18707,24 @@ var DrawingToolbar = class {
18408
18707
  div.style.cssText = `height:1px;margin:4px 8px;background:${this.borderColor};`;
18409
18708
  return div;
18410
18709
  }
18411
- /** A flyout row, left to right: optional leading icon, label, a check when it's the selected
18412
- * entry, an optional shortcut hint, and — at the far right — the favorite star (tool rows).
18413
- * Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
18710
+ /** A flyout row, left to right: optional leading icon badge, label, a check when it's the
18711
+ * selected entry, an optional shortcut hint, and — at the far right — the favorite star (tool
18712
+ * rows). Hover tint is CSS (`.vela-dtb-item:hover`), so navigating the menu stays smooth. */
18414
18713
  makeFlyoutItem(opts) {
18415
18714
  const t = this.theme;
18416
18715
  const item = document.createElement("button");
18417
18716
  item.type = "button";
18418
18717
  item.className = "vela-dtb-item";
18419
18718
  item.setAttribute("aria-label", opts.label);
18420
- 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;`;
18421
- if (opts.icon) {
18422
- const icon2 = document.createElement("span");
18423
- icon2.style.cssText = "width:18px;height:18px;display:flex;align-items:center;justify-content:center;flex:none;";
18424
- icon2.innerHTML = sizedIcon(opts.icon);
18425
- item.appendChild(icon2);
18719
+ const badged = opts.badge === true || !!opts.icon;
18720
+ const box = badged ? "gap:10px;padding:4px 10px 4px 6px;" : "gap:8px;padding:5px 10px 5px 8px;";
18721
+ 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;`;
18722
+ if (opts.selected) item.dataset.selected = "1";
18723
+ if (badged) {
18724
+ const badge = document.createElement("span");
18725
+ badge.className = "vela-dtb-badge";
18726
+ if (opts.icon) badge.innerHTML = opts.icon;
18727
+ item.appendChild(badge);
18426
18728
  }
18427
18729
  const label = document.createElement("span");
18428
18730
  label.textContent = opts.label;
@@ -18467,7 +18769,12 @@ var DrawingToolbar = class {
18467
18769
  if (fly) {
18468
18770
  this.flyout = null;
18469
18771
  announceSurface(fly, false, "menu", this.flyoutCell);
18470
- fly.remove();
18772
+ const owner = this.flyoutOwnerId ?? "";
18773
+ const exit = holdForExit(fly, () => {
18774
+ if (this.exitingFlyouts.get(owner)?.fly === fly) this.exitingFlyouts.delete(owner);
18775
+ fly.remove();
18776
+ });
18777
+ if (exit) this.exitingFlyouts.set(owner, { fly, exit });
18471
18778
  this.flyoutCell?.classList.remove("vela-open");
18472
18779
  this.flyoutCell = null;
18473
18780
  this.flyoutOwnerId = null;
@@ -18551,6 +18858,11 @@ function ensureStyles() {
18551
18858
  .vela-dtb[data-collapsed='1'] .vela-dtb-collapse .vela-dtb-hit{width:14px;}
18552
18859
  .vela-dtb-item{background:transparent;border:none;transition:background var(--vela-dur-fast) ease;}
18553
18860
  .vela-dtb-item:hover{background:var(--vela-hover-strong);}
18861
+ .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;}
18862
+ .vela-dtb-badge:empty{background:transparent;border-color:transparent;}
18863
+ .vela-dtb-badge svg{width:100%;height:100%;}
18864
+ .vela-dtb-item:hover .vela-dtb-badge:not(:empty){background:var(--vela-active);color:var(--vela-fg-bright);}
18865
+ .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);}
18554
18866
  .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;}
18555
18867
  .vela-dtb-star svg{width:16px;height:16px;}
18556
18868
  .vela-dtb-item:hover .vela-dtb-star{opacity:.55;}
@@ -18918,15 +19230,48 @@ function syncTargets(originId, setting, cellIds) {
18918
19230
  function rangesWithin(a, b, epsMs) {
18919
19231
  return Math.abs(a.from - b.from) <= epsMs && Math.abs(a.to - b.to) <= epsMs;
18920
19232
  }
18921
- var STYLE_SYNC_CONFIG_KEYS = ["layout", "panes", "grid", "priceScale", "crosshair"];
18922
- function styleConfigSlice(config) {
18923
- if (config == null || typeof config !== "object") return null;
18924
- const doc = config;
18925
- const out = {};
19233
+ var STYLE_SYNC_CONFIG_KEYS = [
19234
+ "layout",
19235
+ "panes",
19236
+ "grid",
19237
+ "margins",
19238
+ "priceScale",
19239
+ "crosshair",
19240
+ "animations",
19241
+ "candles",
19242
+ "bars",
19243
+ "line",
19244
+ "area",
19245
+ "baseline",
19246
+ "sessions"
19247
+ ];
19248
+ function styleConfigPatch(origin, follower) {
19249
+ const from = asRecord(origin);
19250
+ const to = asRecord(follower);
19251
+ if (!from || !to) return null;
19252
+ const patch = {};
18926
19253
  for (const key of STYLE_SYNC_CONFIG_KEYS) {
18927
- if (doc[key] != null && typeof doc[key] === "object") out[key] = doc[key];
19254
+ const block = asRecord(from[key]);
19255
+ if (block && JSON.stringify(block) !== JSON.stringify(to[key])) patch[key] = block;
18928
19256
  }
18929
- return Object.keys(out).length > 0 ? out : null;
19257
+ const spacing = asRecord(from.series)?.spacing;
19258
+ if (typeof spacing === "number" && spacing !== asRecord(to.series)?.spacing) patch.series = { spacing };
19259
+ const fromTypes = asRecord(from.chartTypes) ?? {};
19260
+ const toTypes = asRecord(to.chartTypes) ?? {};
19261
+ const types = {};
19262
+ for (const typeId of /* @__PURE__ */ new Set([...Object.keys(fromTypes), ...Object.keys(toTypes)])) {
19263
+ const src = asRecord(fromTypes[typeId]) ?? {};
19264
+ const dst = asRecord(toTypes[typeId]) ?? {};
19265
+ for (const key of CANDLE_OVERRIDE_KEYS) {
19266
+ const value = src[key] ?? null;
19267
+ if (value !== (dst[key] ?? null)) (types[typeId] ?? (types[typeId] = {}))[key] = value;
19268
+ }
19269
+ }
19270
+ if (Object.keys(types).length > 0) patch.chartTypes = types;
19271
+ return Object.keys(patch).length > 0 ? patch : null;
19272
+ }
19273
+ function asRecord(v) {
19274
+ return v != null && typeof v === "object" ? v : null;
18930
19275
  }
18931
19276
 
18932
19277
  // src/widget/persist.ts
@@ -19601,6 +19946,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
19601
19946
  this.activeEngineStyle = null;
19602
19947
  /** The chart's current price style (tracked from the renderer's change events + initial read). */
19603
19948
  this.priceStyle = "candles";
19949
+ this.priceStyleWillChangeUnsub = null;
19604
19950
  this.unresolvedUnsub = null;
19605
19951
  /** Latest chart visible range (left/right bar times), fed to viewport-dependent scripts. */
19606
19952
  this.visibleRange = null;
@@ -19699,6 +20045,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
19699
20045
  this.renderer.onThemeSelect?.((name) => this.setTheme(resolveTheme(name)));
19700
20046
  this.viewportUnsub = this.renderer.onViewportChange((range) => this.onViewportChange(range));
19701
20047
  this.renderer.onPriceStyleChange?.((style) => this.syncPriceStyle(style));
20048
+ this.priceStyleWillChangeUnsub = this.renderer.onPriceStyleWillChange?.((from, to) => this.events.emit("priceStyle:change", { from, to })) ?? null;
19702
20049
  const initialStyle = this.renderer.readFeature("priceStyle");
19703
20050
  if (typeof initialStyle === "string") this.priceStyle = initialStyle;
19704
20051
  this.barTransform = barTransformFor(initialStyle);
@@ -21229,6 +21576,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
21229
21576
  if (this.viewportTimer != null) clearTimeout(this.viewportTimer);
21230
21577
  this.viewportUnsub?.();
21231
21578
  this.paneActionUnsub?.();
21579
+ this.priceStyleWillChangeUnsub?.();
21580
+ this.priceStyleWillChangeUnsub = null;
21232
21581
  for (const record of this.registry.all()) {
21233
21582
  this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
21234
21583
  record.session?.stop();
@@ -23434,7 +23783,10 @@ var LEGEND_ICON_PX2 = 16;
23434
23783
  var LEGEND_CTL_PX = 18;
23435
23784
  var LEGEND_ROW_PAD_Y = 2;
23436
23785
  var LEGEND_ROW_PAD_X = 6;
23786
+ var LEGEND_LEFT_PX = 10;
23787
+ var LEGEND_SCALE_GAP_PX = 6;
23437
23788
  var LEGEND_TITLE_VALUES_GAP_PX = 6;
23789
+ var LEGEND_VALUES_GAP_PX = 5;
23438
23790
  var LEGEND_TITLE_STATUS_GAP_PX = 8;
23439
23791
  var LEGEND_ACTION_GAP_PX = 6;
23440
23792
  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;`;
@@ -23720,14 +24072,15 @@ var InputsUI = class {
23720
24072
  row.valuesEl.replaceChildren();
23721
24073
  return;
23722
24074
  }
23723
- row.valuesEl.style.display = "inline-flex";
24075
+ row.valuesEl.style.display = "block";
23724
24076
  row.valuesEl.replaceChildren();
23725
- for (const v of row.plotValues) {
24077
+ row.plotValues.forEach((v, i) => {
23726
24078
  const span = document.createElement("span");
23727
24079
  span.textContent = v.value;
23728
24080
  span.style.color = v.color;
24081
+ if (i > 0) span.style.marginLeft = `${LEGEND_VALUES_GAP_PX}px`;
23729
24082
  row.valuesEl.appendChild(span);
23730
- }
24083
+ });
23731
24084
  }
23732
24085
  // ── legend move/merge (menu + drag) ─────────────────────────────────────
23733
24086
  /** Open the "Move to" menu for a row, anchored under its move button. */
@@ -23828,7 +24181,7 @@ var InputsUI = class {
23828
24181
  if (!lg) {
23829
24182
  lg = document.createElement("div");
23830
24183
  lg.dataset.velaPane = paneId;
23831
- 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;";
24184
+ 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;`;
23832
24185
  applyChromeTokens(lg, this.theme);
23833
24186
  this.positionLegend(lg, paneId);
23834
24187
  this.container.appendChild(lg);
@@ -23840,6 +24193,7 @@ var InputsUI = class {
23840
24193
  const bounds = this.paneBoundsOf ? this.paneBoundsOf(paneId) : { top: 0, height: Infinity };
23841
24194
  lg.toggleAttribute(LEGEND_AT_TOP_ATTR, bounds.top === 0);
23842
24195
  lg.style.display = bounds.height < 4 || !this.titlesVisible ? "none" : "flex";
24196
+ lg.style.maxWidth = `calc(100% - ${(bounds.rightAxis ?? 0) + LEGEND_LEFT_PX + LEGEND_SCALE_GAP_PX}px)`;
23843
24197
  const collapsed = this.paneCollapse.has(paneId);
23844
24198
  const masterId = this.paneCollapse.get(paneId) ?? null;
23845
24199
  for (const row of this.rows.values()) {
@@ -23931,7 +24285,7 @@ var InputsUI = class {
23931
24285
  }
23932
24286
  ensureDialogStyles();
23933
24287
  const el = document.createElement("div");
23934
- 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;`;
24288
+ 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;`;
23935
24289
  el.addEventListener("mouseleave", () => {
23936
24290
  if (this.selectedId !== id) this.setRowHighlighted(id, false);
23937
24291
  });
@@ -23959,7 +24313,7 @@ var InputsUI = class {
23959
24313
  const statusEl = document.createElement("span");
23960
24314
  statusEl.style.cssText = "display:none;box-sizing:border-box;flex:none;";
23961
24315
  const titleWrap = document.createElement("span");
23962
- titleWrap.style.cssText = "white-space:nowrap;";
24316
+ titleWrap.style.cssText = "white-space:nowrap;min-width:0;overflow:hidden;text-overflow:ellipsis;";
23963
24317
  titleWrap.addEventListener("mouseenter", () => this.setRowHighlighted(id, true));
23964
24318
  const titleEl = document.createElement("span");
23965
24319
  titleEl.textContent = title;
@@ -23977,7 +24331,7 @@ var InputsUI = class {
23977
24331
  el.appendChild(calloutsEl);
23978
24332
  el.appendChild(statusEl);
23979
24333
  const valuesEl = document.createElement("span");
23980
- 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;`;
24334
+ 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;`;
23981
24335
  el.appendChild(valuesEl);
23982
24336
  const controlsEl = document.createElement("span");
23983
24337
  controlsEl.style.cssText = `display:none;align-items:center;gap:${LEGEND_ACTION_GAP_PX}px;flex:none;margin-left:${LEGEND_ACTION_GAP_PX}px;`;
@@ -26221,6 +26575,11 @@ var InputController = class {
26221
26575
  /** When true (the default), the separators between stacked panes are draggable to
26222
26576
  * resize the panes above/below. When false, a press there is a normal data-area pan. */
26223
26577
  this.paneResize = true;
26578
+ /** When true (the default), the user drawings take part in pointer input: they claim a
26579
+ * press over a drawing or with a tool armed, and see hover moves. When false, every press
26580
+ * is a plain pan/click and hover leaves them alone; a drawing gesture already under way
26581
+ * still finishes. */
26582
+ this.drawings = true;
26224
26583
  this.el = null;
26225
26584
  this.dragging = false;
26226
26585
  this.moved = false;
@@ -26284,18 +26643,18 @@ var InputController = class {
26284
26643
  this.onModifier = (e) => {
26285
26644
  if (e.repeat || e.key !== "Shift" && e.key !== "Control" && e.key !== "Meta") return;
26286
26645
  if (Number.isNaN(this.cursorX)) return;
26287
- if (this.dragging && this.region !== "drawing") return;
26646
+ if (this.dragging ? this.region !== "drawing" : !this.drawings) return;
26288
26647
  this.deps.drawingsPointerMove?.(this.cursorX, this.cursorY, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
26289
26648
  };
26290
26649
  this.onDown = (e) => {
26291
26650
  if (e.button === 1) {
26292
26651
  const { x: x2, y: y2 } = this.local(e);
26293
- this.middleDeleted = this.deps.drawingsDeleteAt?.(x2, y2) ?? false;
26652
+ this.middleDeleted = this.drawings && (this.deps.drawingsDeleteAt?.(x2, y2) ?? false);
26294
26653
  if (this.middleDeleted) e.preventDefault();
26295
26654
  return;
26296
26655
  }
26297
26656
  if (e.button === 2) {
26298
- this.rightCancelled = this.deps.drawingsCancelPlacement?.() ?? false;
26657
+ this.rightCancelled = this.drawings && (this.deps.drawingsCancelPlacement?.() ?? false);
26299
26658
  if (this.rightCancelled) e.preventDefault();
26300
26659
  return;
26301
26660
  }
@@ -26317,20 +26676,20 @@ var InputController = class {
26317
26676
  this.startY = y;
26318
26677
  this.prevPressDrawing = this.lastPressDrawing;
26319
26678
  this.lastPressDrawing = false;
26320
- if (this.deps.drawingsClaim?.(x, y)) {
26679
+ if (this.drawings && this.deps.drawingsClaim?.(x, y)) {
26321
26680
  this.region = "drawing";
26322
26681
  this.lastPressDrawing = true;
26323
26682
  this.deps.drawingsPointerDown?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
26324
26683
  this.capture(e.pointerId);
26325
26684
  return;
26326
26685
  }
26327
- if (e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
26686
+ if (this.drawings && e.shiftKey && this.regionAt(x, y) === "data" && this.deps.drawingsMeasureStart?.(x, y, this.snapMode(e))) {
26328
26687
  this.region = "drawing";
26329
26688
  this.lastPressDrawing = true;
26330
26689
  this.capture(e.pointerId);
26331
26690
  return;
26332
26691
  }
26333
- if ((e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
26692
+ if (this.drawings && (e.ctrlKey || e.metaKey) && this.regionAt(x, y) === "data" && this.deps.drawingsMarqueeStart?.(x, y)) {
26334
26693
  this.region = "drawing";
26335
26694
  this.lastPressDrawing = true;
26336
26695
  this.capture(e.pointerId);
@@ -26407,7 +26766,7 @@ var InputController = class {
26407
26766
  const drawCursor = this.deps.drawingsCursor?.(x, y);
26408
26767
  const r = this.regionAt(x, y);
26409
26768
  this.el.style.cursor = drawCursor ?? (r === "price" ? "ns-resize" : r === "time" ? "ew-resize" : r === "separator" ? "row-resize" : "");
26410
- this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
26769
+ if (this.drawings) this.deps.drawingsPointerMove?.(x, y, this.snapMode(e), e.shiftKey, e.ctrlKey || e.metaKey);
26411
26770
  }
26412
26771
  if (e.pointerType !== "touch") this.deps.onPointerMove(x, y);
26413
26772
  };
@@ -26649,7 +27008,7 @@ var InputController = class {
26649
27008
  }
26650
27009
  /** Shared double-click / double-tap routing, by the region under the point. */
26651
27010
  doubleActivate(x, y) {
26652
- if (this.deps.drawingsDblClick?.(x, y)) return;
27011
+ if (this.drawings && this.deps.drawingsDblClick?.(x, y)) return;
26653
27012
  const region = this.regionAt(x, y);
26654
27013
  if (region === "price") this.deps.resetPriceScale(x, y);
26655
27014
  else if (region === "separator") this.deps.resetPaneSize(y);
@@ -28663,10 +29022,13 @@ function priceTicks(min, max, target = 6) {
28663
29022
  const norm = raw / mag;
28664
29023
  const step = (norm < 1.5 ? 1 : norm < 3 ? 2 : norm < 7 ? 5 : 10) * mag;
28665
29024
  const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);
28666
- const out = [];
28667
29025
  const start = Math.ceil(min / step) * step;
28668
- for (let v = start; v <= max + step * 1e-6; v += step) {
28669
- out.push(Number(v.toFixed(decimals)));
29026
+ if (!(step > 0) || start + step === start) return [];
29027
+ const count = Math.floor((max - start) / step + 1e-6) + 1;
29028
+ if (!(count > 0) || count > 1e3) return [];
29029
+ const out = [];
29030
+ for (let i = 0; i < count; i += 1) {
29031
+ out.push(Number((start + i * step).toFixed(decimals)));
28670
29032
  }
28671
29033
  return out;
28672
29034
  }
@@ -28864,6 +29226,100 @@ function calendarTicks(zFrom, zTo, months, offsetMs) {
28864
29226
  return out;
28865
29227
  }
28866
29228
 
29229
+ // src/renderers/native/chrome/priceAxisTicks.ts
29230
+ var PriceAxisTickSource = class {
29231
+ constructor() {
29232
+ this.fn = null;
29233
+ this.memo = /* @__PURE__ */ new WeakMap();
29234
+ this.warned = false;
29235
+ }
29236
+ /** The host function, or null for the built-in ticks. */
29237
+ get hook() {
29238
+ return this.fn;
29239
+ }
29240
+ /** Set (or clear) the host function. Drops every memoized result, so setting the same
29241
+ * function again recomputes — how a host refreshes ticks that depend on its own state. */
29242
+ setHook(fn) {
29243
+ this.fn = fn;
29244
+ this.memo = /* @__PURE__ */ new WeakMap();
29245
+ }
29246
+ /** The pane's master-scale ticks for this frame. An unscaled pane (`axisFormat: 'none'`)
29247
+ * gets none, and the host is never asked for it. */
29248
+ ticksFor(scene, pane, coords) {
29249
+ const format = pane.axisFormat;
29250
+ if (format === "none") return [];
29251
+ const pct = percentScaleFor(scene, pane);
29252
+ const height = pane.bounds.height;
29253
+ const fn = this.fn;
29254
+ if (!fn || !(height > 0)) return paneAxisTicks(pane.scale, height, pct, scene.priceMintick, format);
29255
+ const s = pane.scale;
29256
+ const m = this.memo.get(pane);
29257
+ const fontSize = scene.style.fontSize;
29258
+ 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) {
29259
+ return m.ticks;
29260
+ }
29261
+ const defaults2 = paneAxisTicks(s, height, pct, scene.priceMintick, format);
29262
+ const ticks = this.callHook(fn, scene, pane, coords, defaults2, pct?.baseline, pct ? pct.indexed ? "indexed" : "percent" : "price");
29263
+ this.memo.set(pane, {
29264
+ kind: pane.kind,
29265
+ min: s.min,
29266
+ max: s.max,
29267
+ log: !!s.log,
29268
+ invert: !!s.invert,
29269
+ height,
29270
+ baseline: pct?.baseline,
29271
+ indexed: !!pct?.indexed,
29272
+ mintick: scene.priceMintick,
29273
+ format,
29274
+ fontSize,
29275
+ ticks
29276
+ });
29277
+ return ticks;
29278
+ }
29279
+ callHook(fn, scene, pane, coords, defaults2, baseline, mode) {
29280
+ const scale = { min: pane.scale.min, max: pane.scale.max, log: !!pane.scale.log, invert: !!pane.scale.invert };
29281
+ const bounds = { top: 0, height: pane.bounds.height };
29282
+ const ctx = {
29283
+ pane: { id: pane.id, kind: pane.kind },
29284
+ min: Math.min(scale.min, scale.max),
29285
+ max: Math.max(scale.min, scale.max),
29286
+ log: !!scale.log,
29287
+ height: bounds.height,
29288
+ mode,
29289
+ baseline,
29290
+ mintick: scene.priceMintick,
29291
+ fontSize: scene.style.fontSize,
29292
+ priceToY: (price) => coords.priceToY(price, scale, bounds),
29293
+ defaults: defaults2
29294
+ };
29295
+ let out;
29296
+ try {
29297
+ out = fn(ctx);
29298
+ } catch (err) {
29299
+ this.warnOnce("threw", err);
29300
+ return defaults2;
29301
+ }
29302
+ if (out == null) return defaults2;
29303
+ if (!Array.isArray(out)) {
29304
+ this.warnOnce("returned a non-array", out);
29305
+ return defaults2;
29306
+ }
29307
+ const ticks = [];
29308
+ for (const t of out) {
29309
+ if (!t || typeof t !== "object") continue;
29310
+ const { price, label, major } = t;
29311
+ if (typeof price !== "number" || !Number.isFinite(price) || typeof label !== "string") continue;
29312
+ ticks.push(typeof major === "boolean" ? { price, label, major } : { price, label });
29313
+ }
29314
+ return ticks;
29315
+ }
29316
+ warnOnce(what, detail) {
29317
+ if (this.warned) return;
29318
+ this.warned = true;
29319
+ console.warn(`[vela] the priceAxisTicks function ${what} \u2014 the default ticks are used instead.`, detail);
29320
+ }
29321
+ };
29322
+
28867
29323
  // src/renderers/native/chrome/countdown.ts
28868
29324
  function countdownText(barOpen, barMs, now2) {
28869
29325
  if (!(barMs > 0)) return null;
@@ -29216,8 +29672,11 @@ function standaloneSvg(markup, ink, px) {
29216
29672
  }
29217
29673
 
29218
29674
  // src/renderers/native/chrome/ChromeRenderer.ts
29675
+ var MINOR_LABEL_ALPHA = 0.5;
29219
29676
  var ChromeRenderer = class {
29220
- constructor() {
29677
+ /** `axisTicks` is shared with the backdrop layer, so price labels and gridlines agree. */
29678
+ constructor(axisTicks = new PriceAxisTickSource()) {
29679
+ this.axisTicks = axisTicks;
29221
29680
  this.canvas = null;
29222
29681
  this.ctx = null;
29223
29682
  // The color for axis tick labels — the host-passed surface text, set each frame in render().
@@ -29322,7 +29781,8 @@ var ChromeRenderer = class {
29322
29781
  this.drawTimeAxis(ctx, scene, coords, theme, dataW, dataH, fullH);
29323
29782
  this.drawMarkLane(ctx, scene, coords, theme, dataW, dataH);
29324
29783
  }
29325
- /** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge. */
29784
+ /** The timeline-mark lane — after the axis, so the tokens read over the plot's bottom edge,
29785
+ * but clipped to the data area so a glyph on the edge bar never paints over the price scale. */
29326
29786
  drawMarkLane(ctx, scene, coords, theme, dataW, dataH) {
29327
29787
  if (!scene.marks.visible || scene.timelineMarks.length === 0) {
29328
29788
  this.markLayout = { glyphs: [], stacks: /* @__PURE__ */ new Map() };
@@ -29340,6 +29800,10 @@ var ChromeRenderer = class {
29340
29800
  expanded: scene.marksExpandedStack
29341
29801
  });
29342
29802
  const nowMs = typeof performance !== "undefined" ? performance.now() : Date.now();
29803
+ ctx.save();
29804
+ ctx.beginPath();
29805
+ ctx.rect(0, 0, dataW, dataH);
29806
+ ctx.clip();
29343
29807
  paintMarkLane(ctx, this.markLayout, {
29344
29808
  axisY: dataH,
29345
29809
  background: theme.background,
@@ -29353,6 +29817,7 @@ var ChromeRenderer = class {
29353
29817
  flashKey: scene.marksFlash && scene.marksFlash.until > nowMs ? scene.marksFlash.key : null,
29354
29818
  nowMs
29355
29819
  });
29820
+ ctx.restore();
29356
29821
  }
29357
29822
  barTimes(scene) {
29358
29823
  if (this.barTimesSrc !== scene.bars || this.barTimesCache.length !== scene.bars.length) {
@@ -29409,13 +29874,19 @@ var ChromeRenderer = class {
29409
29874
  if (!scene.showAxisLabels) return;
29410
29875
  ctx.fillStyle = this.axisTextColor;
29411
29876
  ctx.textAlign = "left";
29877
+ const font = `${scene.style.fontSize}px ${theme.fontFamily}`;
29878
+ const majorFont = `600 ${font}`;
29879
+ const alpha = ctx.globalAlpha;
29412
29880
  for (const pane of panes) {
29413
29881
  if (pane.collapsed) continue;
29414
- const pct = percentScaleFor(scene, pane);
29415
- for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, scene.priceMintick, pane.axisFormat)) {
29882
+ for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
29416
29883
  const y = coords.priceToY(t.price, pane.scale, pane.bounds);
29417
29884
  if (y < pane.bounds.top + 6 || y > pane.bounds.top + pane.bounds.height - 4) continue;
29885
+ if (t.major === true) ctx.font = majorFont;
29886
+ else if (t.major === false) ctx.globalAlpha = alpha * MINOR_LABEL_ALPHA;
29418
29887
  ctx.fillText(t.label, dataW + 6, y);
29888
+ if (t.major === true) ctx.font = font;
29889
+ else if (t.major === false) ctx.globalAlpha = alpha;
29419
29890
  }
29420
29891
  if (pane.axisBands) {
29421
29892
  for (const b of pane.axisBands) {
@@ -29657,15 +30128,23 @@ var CrosshairRenderer = class {
29657
30128
  constructor() {
29658
30129
  this.canvas = null;
29659
30130
  this.ctx = null;
30131
+ this.shade = null;
29660
30132
  }
29661
30133
  mount(canvas) {
29662
30134
  this.canvas = canvas;
29663
30135
  this.ctx = canvas.getContext("2d");
29664
30136
  }
30137
+ /** The plot area the last frame veiled for `crosshairOverride.shadeRight` — from the
30138
+ * picked bar's right edge to the price scale, full plot height (the union when both the
30139
+ * local and a synced crosshair veil) — or null when no veil was painted. */
30140
+ get shadeRightArea() {
30141
+ return this.shade;
30142
+ }
29665
30143
  /** Clear the cursor canvas and (re)draw the crosshair lines + axis chips. The optional
29666
30144
  * `separatorHoverY` highlights the draggable pane separator under the cursor;
29667
30145
  * `external` is a SYNCED ghost crosshair (another chart's pointer, pixel-resolved). */
29668
30146
  render(scene, coords, theme, separatorHoverY = null, external = null) {
30147
+ this.shade = null;
29669
30148
  const ctx = this.ctx;
29670
30149
  const canvas = this.canvas;
29671
30150
  if (!ctx || !canvas) return;
@@ -29688,12 +30167,7 @@ var CrosshairRenderer = class {
29688
30167
  const x = crisp(coords.logicalToX(logical), ov?.width ?? cs.width);
29689
30168
  if (vertical && ov?.shadeRight) {
29690
30169
  const from = Math.max(0, Math.round(coords.logicalToX(logical + 0.5)));
29691
- if (from < dataW) {
29692
- ctx.fillStyle = ov.shadeRight.color;
29693
- ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
29694
- ctx.fillRect(from, 0, dataW - from, dataH);
29695
- ctx.globalAlpha = 1;
29696
- }
30170
+ if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
29697
30171
  }
29698
30172
  ctx.strokeStyle = ov?.color ?? cs.color ?? theme.textColor;
29699
30173
  ctx.lineWidth = ov?.width ?? cs.width;
@@ -29729,6 +30203,7 @@ var CrosshairRenderer = class {
29729
30203
  destroy() {
29730
30204
  this.canvas = null;
29731
30205
  this.ctx = null;
30206
+ this.shade = null;
29732
30207
  }
29733
30208
  /** The synced ghost: a dimmed vertical line at the bar the renderer resolved as
29734
30209
  * CONTAINING the foreign time (+ horizontal line when a comparable price came
@@ -29748,12 +30223,7 @@ var CrosshairRenderer = class {
29748
30223
  if (ov?.vertical === false) return;
29749
30224
  if (ov?.shadeRight) {
29750
30225
  const from = Math.max(0, Math.round(coords.logicalToX(Math.round(coords.xToLogical(ext.x)) + 0.5)));
29751
- if (from < dataW) {
29752
- ctx.fillStyle = ov.shadeRight.color;
29753
- ctx.globalAlpha = ov.shadeRight.opacity ?? 1;
29754
- ctx.fillRect(from, 0, dataW - from, dataH);
29755
- ctx.globalAlpha = 1;
29756
- }
30226
+ if (from < dataW) this.paintShade(ctx, ov.shadeRight, from, dataW, dataH);
29757
30227
  }
29758
30228
  if (ext.line === false || x < 0 || x > dataW) return;
29759
30229
  ctx.font = `${scene.style.fontSize}px ${theme.fontFamily}`;
@@ -29789,6 +30259,15 @@ var CrosshairRenderer = class {
29789
30259
  this.chip(ctx, x, dataH + 1, formatTimeStamp(ext.time, scene.timezone, coords.barInterval), chipBg, "center", true, theme.background);
29790
30260
  ctx.globalAlpha = 1;
29791
30261
  }
30262
+ /** Paint the `shadeRight` veil from `from` to the price scale and record the veiled area. */
30263
+ paintShade(ctx, shade, from, dataW, dataH) {
30264
+ ctx.fillStyle = shade.color;
30265
+ ctx.globalAlpha = shade.opacity ?? 1;
30266
+ ctx.fillRect(from, 0, dataW - from, dataH);
30267
+ ctx.globalAlpha = 1;
30268
+ const x = this.shade ? Math.min(this.shade.x, from) : from;
30269
+ this.shade = { x, width: dataW - x, height: dataH };
30270
+ }
29792
30271
  /** A soft band + a brighter crisp center line over the hovered separator, so it reads as
29793
30272
  * a draggable handle (the cursor is already `row-resize`). Spans the full width (data +
29794
30273
  * scale gutter) to match the separator itself. Theme-derived (textColor). */
@@ -33194,15 +33673,16 @@ var DrawingSettingsDialog = class {
33194
33673
  }
33195
33674
  open(drawing, actions, kind) {
33196
33675
  this.close();
33197
- const snapshot = clonePlain(drawing.serialize());
33676
+ const live = actions.resolve() ?? drawing;
33677
+ const snapshot = clonePlain(live.serialize());
33198
33678
  const grid = fieldGrid({ variant: "inputs" });
33199
33679
  grid.style.padding = "16px 20px";
33200
33680
  grid.style.overflowY = "auto";
33201
33681
  grid.style.overflowX = "hidden";
33202
33682
  grid.style.flex = "1 1 auto";
33203
- if (kind === "position" && drawing instanceof PositionTool) this.buildPosition(grid, drawing, actions);
33204
- else if (kind === "frvp" && drawing instanceof FixedRangeVolumeProfile) this.buildFrvp(grid, drawing, actions);
33205
- else if (kind === "levels") this.buildLevels(grid, drawing, actions);
33683
+ if (kind === "position" && live instanceof PositionTool) this.buildPosition(grid, live, actions);
33684
+ else if (kind === "frvp" && live instanceof FixedRangeVolumeProfile) this.buildFrvp(grid, live, actions);
33685
+ else if (kind === "levels") this.buildLevels(grid, live, actions);
33206
33686
  else return;
33207
33687
  const ui = new Dialog({
33208
33688
  host: this.host,
@@ -33849,7 +34329,7 @@ var DrawingSettingsPopup = class {
33849
34329
  this.reposition();
33850
34330
  return;
33851
34331
  }
33852
- const text = drawing.text;
34332
+ const text = (actions.resolve() ?? drawing).text;
33853
34333
  const panel = document.createElement("div");
33854
34334
  panel.style.cssText = `padding:6px;border-top:1px solid var(--vela-border);display:flex;flex-direction:column;gap:4px;`;
33855
34335
  const ta = new TextArea({
@@ -35020,6 +35500,12 @@ var UserDrawingController = class {
35020
35500
  this.render();
35021
35501
  }
35022
35502
  }
35503
+ /** Drop the hover (its handles) — the pointer no longer reaches the drawings. */
35504
+ clearHover() {
35505
+ if (this.hoveredId == null) return;
35506
+ this.hoveredId = null;
35507
+ this.render();
35508
+ }
35023
35509
  pointerUp(x, y, snap = "off") {
35024
35510
  if (this.eraserMode) {
35025
35511
  this.erasing = false;
@@ -36010,9 +36496,10 @@ function computePaneScale(models, bars, includeCandles, i0, i1, drawings, log =
36010
36496
  consider(drawings.max);
36011
36497
  }
36012
36498
  if (min === Infinity || max === -Infinity) return { min: 0, max: 1 };
36013
- if (min === max) {
36014
- const pad = Math.abs(min) * 0.1 || 1;
36015
- return { min: min - pad, max: max + pad, log: log && min - pad > 0 };
36499
+ if (max - min <= Math.max(Math.abs(min), Math.abs(max)) * 1e-12) {
36500
+ const mid = (min + max) / 2;
36501
+ const pad = Math.abs(mid) * 0.1 || 1;
36502
+ return { min: mid - pad, max: mid + pad, log: log && mid - pad > 0 };
36016
36503
  }
36017
36504
  const content = Math.max(0.1, 1 - (margins.top + margins.bottom) / 100);
36018
36505
  const above = margins.top / 100 / content;
@@ -36516,7 +37003,9 @@ function bandEdgeSlot(logical) {
36516
37003
  return Math.ceil(logical) - 0.5;
36517
37004
  }
36518
37005
  var BackdropRenderer = class {
36519
- constructor() {
37006
+ /** `axisTicks` is shared with the chrome layer, so gridlines and price labels agree. */
37007
+ constructor(axisTicks = new PriceAxisTickSource()) {
37008
+ this.axisTicks = axisTicks;
36520
37009
  this.canvas = null;
36521
37010
  this.ctx = null;
36522
37011
  }
@@ -36593,9 +37082,8 @@ var BackdropRenderer = class {
36593
37082
  if (scene.showGrid && gridHorz.visible && !pane.collapsed) {
36594
37083
  ctx.globalAlpha = gridAlpha;
36595
37084
  ctx.strokeStyle = horzColor;
36596
- const pct = percentScaleFor(scene, pane);
36597
37085
  ctx.beginPath();
36598
- for (const t of paneAxisTicks(pane.scale, pane.bounds.height, pct, void 0, pane.axisFormat)) {
37086
+ for (const t of this.axisTicks.ticksFor(scene, pane, coords)) {
36599
37087
  const y = Math.round(coords.priceToY(t.price, pane.scale, pane.bounds)) + 0.5;
36600
37088
  if (y < pane.bounds.top || y > pane.bounds.top + pane.bounds.height) continue;
36601
37089
  ctx.moveTo(0, y);
@@ -37082,8 +37570,16 @@ var NativeRenderer = class {
37082
37570
  this.toolbarGutter = 0;
37083
37571
  // px reserved on the left for the docked drawings toolbar (0 when hidden)
37084
37572
  this.mountContainer = null;
37573
+ /** The host-styleable twin of the `shadeRight` veil (`.vela-shade-right`), directly under
37574
+ * the cursor canvas; hidden whenever the crosshair layer paints no veil. */
37575
+ this.shadeRightEl = null;
37576
+ /** The veil geometry last written to {@link shadeRightEl} ('' = hidden) — the DOM is
37577
+ * touched only when it changes, never on a plain pointer move. */
37578
+ this.shadeRightSig = "";
37085
37579
  this.userDrawings = null;
37086
- this.backdropRenderer = new BackdropRenderer();
37580
+ /** One price-axis tick source for the gridlines (backdrop) and the labels (chrome). */
37581
+ this.priceAxisTicks = new PriceAxisTickSource();
37582
+ this.backdropRenderer = new BackdropRenderer(this.priceAxisTicks);
37087
37583
  this.volumeRenderer = new VolumeRenderer();
37088
37584
  /** SDK renderer layers instantiated at mount ({@link registerRendererLayer}). */
37089
37585
  /**
@@ -37116,7 +37612,7 @@ var NativeRenderer = class {
37116
37612
  this.backendMode = "auto";
37117
37613
  this.glowAmount = 0;
37118
37614
  // WebGL2 neon-glow intensity (canvas2d ignores it)
37119
- this.chrome = new ChromeRenderer();
37615
+ this.chrome = new ChromeRenderer(this.priceAxisTicks);
37120
37616
  /** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
37121
37617
  this.indicatorSlices = new IndicatorDrawingSlices();
37122
37618
  /** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
@@ -37233,6 +37729,8 @@ var NativeRenderer = class {
37233
37729
  // pane just below it
37234
37730
  this.resizeSplitStart = null;
37235
37731
  // the two panes' shared span when the drag began
37732
+ /** The user drawings take part in pointer input (the runtime `drawingsInteractive` feature). */
37733
+ this.drawingsInteractive = true;
37236
37734
  this.hoverSeparatorY = null;
37237
37735
  // pixel y of the separator under the cursor (drives its hover highlight)
37238
37736
  // ── pane management (merge / reorder / collapse / maximize) ──
@@ -37288,8 +37786,9 @@ var NativeRenderer = class {
37288
37786
  this.toggleVisibleCbs = /* @__PURE__ */ new Set();
37289
37787
  this.moveIndicatorCbs = /* @__PURE__ */ new Set();
37290
37788
  this.priceStyleCbs = /* @__PURE__ */ new Set();
37789
+ this.priceStyleWillChangeCbs = /* @__PURE__ */ new Set();
37291
37790
  this.name = "native";
37292
- 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"];
37791
+ 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"];
37293
37792
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
37294
37793
  * so moving onto the button doesn't count as leaving). */
37295
37794
  this.onScrollProximityMove = (e) => {
@@ -37333,6 +37832,7 @@ var NativeRenderer = class {
37333
37832
  this.scene.priceStyle = opts.priceStyle;
37334
37833
  this.scene.basePainting = basePaintingOf(opts.priceStyle);
37335
37834
  this.scene.candleOverride = candleOverrideFor(opts.priceStyle, this.scene.style.chartTypes);
37835
+ if (typeof opts.priceAxisTicks === "function") this.priceAxisTicks.setHook(opts.priceAxisTicks);
37336
37836
  }
37337
37837
  this.theme = this.deriveTheme(DARK_THEME);
37338
37838
  }
@@ -37471,6 +37971,15 @@ var NativeRenderer = class {
37471
37971
  case "crosshairOverride":
37472
37972
  this.scene.crosshairOverride = sanitizeCrosshairOverride(value);
37473
37973
  break;
37974
+ case "drawingsInteractive":
37975
+ this.drawingsInteractive = value !== false;
37976
+ if (this.input) this.input.drawings = this.drawingsInteractive;
37977
+ if (!this.drawingsInteractive) this.userDrawings?.clearHover();
37978
+ return;
37979
+ // affects the next gesture only — clearHover repaints what it drops
37980
+ case "priceAxisTicks":
37981
+ this.priceAxisTicks.setHook(typeof value === "function" ? value : null);
37982
+ break;
37474
37983
  case "keyboard":
37475
37984
  this.setKeyboardEnabled(Boolean(value));
37476
37985
  return;
@@ -37586,6 +38095,10 @@ var NativeRenderer = class {
37586
38095
  return { visible: this.scene.marks.visible, groups: { ...this.scene.marks.groups } };
37587
38096
  case "crosshairOverride":
37588
38097
  return this.scene.crosshairOverride ? { ...this.scene.crosshairOverride } : null;
38098
+ case "drawingsInteractive":
38099
+ return this.drawingsInteractive;
38100
+ case "priceAxisTicks":
38101
+ return this.priceAxisTicks.hook;
37589
38102
  case "keyboard":
37590
38103
  return this.keyboardEnabled;
37591
38104
  case "historyChords":
@@ -38259,6 +38772,12 @@ var NativeRenderer = class {
38259
38772
  Object.assign(this.drawingsCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
38260
38773
  this.cursorCanvas = document.createElement("canvas");
38261
38774
  Object.assign(this.cursorCanvas.style, { position: "absolute", inset: "0", width: "100%", height: "100%", pointerEvents: "none" });
38775
+ this.shadeRightEl = document.createElement("div");
38776
+ this.shadeRightEl.className = "vela-shade-right";
38777
+ this.shadeRightEl.setAttribute("aria-hidden", "true");
38778
+ this.shadeRightEl.hidden = true;
38779
+ Object.assign(this.shadeRightEl.style, { position: "absolute", top: "0", left: "0", width: "0", height: "0", pointerEvents: "none", display: "none" });
38780
+ this.shadeRightSig = "";
38262
38781
  this.overlayRoot = document.createElement("div");
38263
38782
  Object.assign(this.overlayRoot.style, { position: "absolute", inset: "0", pointerEvents: "none" });
38264
38783
  this.plot = document.createElement("div");
@@ -38266,7 +38785,7 @@ var NativeRenderer = class {
38266
38785
  this.extLayers = rendererLayers().map((def) => ({ def, instance: def.create(), canvas: this.createLayerCanvas(), channel: def.id, owner: null }));
38267
38786
  const below = this.extLayers.filter((l) => l.def.placement === "below-data").map((l) => l.canvas);
38268
38787
  const above = this.extLayers.filter((l) => l.def.placement !== "below-data").map((l) => l.canvas);
38269
- this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.cursorCanvas, this.overlayRoot);
38788
+ this.plot.append(this.backdropCanvas, ...below, this.dataCanvas, this.volumeCanvas, this.vpvrCanvas, ...above, this.chromeCanvas, this.drawingsCanvas, this.shadeRightEl, this.cursorCanvas, this.overlayRoot);
38270
38789
  this.layerOrderSig = "";
38271
38790
  this.wrapper.appendChild(this.plot);
38272
38791
  this.factoryConfig = this.getConfig();
@@ -38324,13 +38843,14 @@ var NativeRenderer = class {
38324
38843
  drawingsPointerDown: (x, y, snap, shift2, mod) => this.userDrawings?.pointerDown(x, y, snap, shift2, mod),
38325
38844
  drawingsPointerMove: (x, y, snap, shift2, mod) => this.userDrawings?.pointerMove(x, y, snap, shift2, mod),
38326
38845
  drawingsPointerUp: (x, y, snap) => this.userDrawings?.pointerUp(x, y, snap),
38327
- drawingsCursor: (x, y) => this.userDrawings?.cursorAt(x, y) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
38846
+ drawingsCursor: (x, y) => (this.drawingsInteractive ? this.userDrawings?.cursorAt(x, y) : null) ?? (this.chrome.markGlyphAt(x, y) ? "pointer" : null),
38328
38847
  drawingsDblClick: (x, y) => this.userDrawings?.dblClick(x, y) ?? false,
38329
38848
  drawingsClearTransient: () => this.userDrawings?.clearTransient()
38330
38849
  });
38331
38850
  this.input.rightEdgeZoom = this.zoomAnchorMode === "right";
38332
38851
  this.input.axisDrag = this.axisDragEnabled;
38333
38852
  this.input.paneResize = this.paneResizeEnabled;
38853
+ this.input.drawings = this.drawingsInteractive;
38334
38854
  this.input.attach(this.dataCanvas);
38335
38855
  if (this.settingsEnabled) this.setSettingsEnabled(true);
38336
38856
  this.syncCountdownTimer();
@@ -38639,6 +39159,8 @@ var NativeRenderer = class {
38639
39159
  this.backend.destroy();
38640
39160
  this.chrome.destroy();
38641
39161
  this.crosshairLayer.destroy();
39162
+ this.shadeRightEl?.remove();
39163
+ this.shadeRightEl = null;
38642
39164
  this.attributionEl?.remove();
38643
39165
  this.attributionEl = null;
38644
39166
  this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
@@ -38985,14 +39507,21 @@ var NativeRenderer = class {
38985
39507
  this.priceStyleCbs.add(cb);
38986
39508
  return () => this.priceStyleCbs.delete(cb);
38987
39509
  }
39510
+ onPriceStyleWillChange(cb) {
39511
+ this.priceStyleWillChangeCbs.add(cb);
39512
+ return () => this.priceStyleWillChangeCbs.delete(cb);
39513
+ }
38988
39514
  /**
38989
39515
  * THE single write path for the base price style at runtime (feature set / settings dialog /
38990
- * config template — the constructor seeds the field directly, pre-listeners). Updates the
38991
- * scene, eases any reveal layer toward the new style's target, and notifies the core —
38992
- * which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
39516
+ * config template — the constructor seeds the field directly, pre-listeners). Announces the
39517
+ * switch while the scene still holds the old style (a listener may paint/capture that frame),
39518
+ * then updates the scene, eases any reveal layer toward the new style's target, and notifies
39519
+ * the core — which owns the DATA side of styles that need one (a chart type's SeriesDataEngine).
38993
39520
  */
38994
39521
  setPriceStyle(style) {
38995
39522
  if (style === this.scene.priceStyle) return;
39523
+ const from = this.scene.priceStyle;
39524
+ for (const cb of this.priceStyleWillChangeCbs) cb(from, style);
38996
39525
  this.scene.priceStyle = style;
38997
39526
  this.scene.basePainting = basePaintingOf(style);
38998
39527
  this.scene.candleOverride = candleOverrideFor(style, this.scene.style.chartTypes);
@@ -39222,7 +39751,7 @@ var NativeRenderer = class {
39222
39751
  if (this.easeLiveBar(dtMs)) active = true;
39223
39752
  this.skeletonClockMs += dtMs;
39224
39753
  this.paintData();
39225
- this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
39754
+ this.renderCrosshairLayer();
39226
39755
  this.updateLegendValues();
39227
39756
  this.emitViewportChange();
39228
39757
  return active;
@@ -39797,10 +40326,31 @@ var NativeRenderer = class {
39797
40326
  this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
39798
40327
  this.trackMarkPopover();
39799
40328
  }
39800
- this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
40329
+ this.renderCrosshairLayer();
39801
40330
  if (!repaintsData(level) && this.paintedData) this.repaintCursorLayers();
39802
40331
  this.updateLegendValues();
39803
40332
  }
40333
+ /** Repaint the L2 crosshair canvas and keep the `.vela-shade-right` element on the veil it painted. */
40334
+ renderCrosshairLayer() {
40335
+ this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
40336
+ this.syncShadeRight();
40337
+ }
40338
+ /** Position (or hide) the `.vela-shade-right` element over the veil of the last crosshair frame. */
40339
+ syncShadeRight() {
40340
+ const el = this.shadeRightEl;
40341
+ if (!el) return;
40342
+ const area = this.crosshairLayer.shadeRightArea;
40343
+ const sig = area ? `${area.x},${area.width},${area.height}` : "";
40344
+ if (sig === this.shadeRightSig) return;
40345
+ this.shadeRightSig = sig;
40346
+ if (!area) {
40347
+ el.hidden = true;
40348
+ el.style.display = "none";
40349
+ return;
40350
+ }
40351
+ Object.assign(el.style, { left: `${area.x}px`, width: `${area.width}px`, height: `${area.height}px`, display: "" });
40352
+ el.hidden = false;
40353
+ }
39804
40354
  /** Repaint the SDK layers that opted into cursor tracking (their own canvas only). */
39805
40355
  repaintCursorLayers() {
39806
40356
  const pane = this.scene.panes.get(PRICE_PANE_ID2);
@@ -40594,7 +41144,7 @@ var NativeRenderer = class {
40594
41144
  if (this.animator.active) {
40595
41145
  this.computeScales();
40596
41146
  this.paintData();
40597
- this.crosshairLayer.render(this.scene, this.coords, this.theme, this.hoverSeparatorY, this.externalCrossPx());
41147
+ this.renderCrosshairLayer();
40598
41148
  } else {
40599
41149
  this.scheduler.flushNow(4 /* Full */);
40600
41150
  }
@@ -44609,7 +45159,8 @@ var Vela = class {
44609
45159
  glow: options.glow ?? 0,
44610
45160
  upColor: options.upColor ?? BULLISH,
44611
45161
  downColor: options.downColor ?? BEARISH,
44612
- priceStyle: options.priceStyle ?? "candles"
45162
+ priceStyle: options.priceStyle ?? "candles",
45163
+ priceAxisTicks: options.priceAxis?.ticks ?? null
44613
45164
  };
44614
45165
  const RendererClass = options.renderer ?? NativeRenderer;
44615
45166
  if (typeof RendererClass !== "function") {
@@ -46127,16 +46678,25 @@ var CellControls = class {
46127
46678
  // src/widget/context-menu.ts
46128
46679
  var PRICE_AXIS_W = 60;
46129
46680
  var TIME_AXIS_H3 = 26;
46681
+ var OFF_PLOT = { price: null, time: null, paneKind: null };
46130
46682
  var ChartContextMenu = class {
46131
46683
  constructor(host, cbs) {
46132
46684
  this.cbs = cbs;
46133
46685
  this.chart = null;
46686
+ this.offCrosshair = null;
46687
+ /** The bound chart's latest crosshair report — a right-click always follows a pointer
46688
+ * move to its spot, so this is where it landed. */
46689
+ this.crosshair = OFF_PLOT;
46690
+ /** {@link crosshair} as it stood when the menu opened: the pointer moves on through the
46691
+ * menu before a row is picked, and `run` must see the right-clicked spot. */
46692
+ this.pointer = OFF_PLOT;
46134
46693
  this.lastZone = "body";
46135
46694
  /** The pane whose scale the open price-axis menu targets (null ⇒ the main scale). */
46136
46695
  this.lastPane = null;
46137
46696
  this.onContextMenu = (e) => {
46138
46697
  e.preventDefault();
46139
46698
  if (!this.chart) return;
46699
+ this.pointer = this.crosshair;
46140
46700
  this.lastZone = this.zoneOf(e);
46141
46701
  this.lastPane = this.lastZone === "price-axis" ? this.paneAt(e) : null;
46142
46702
  this.menu.setItems(this.itemsFor(this.lastZone));
@@ -46150,15 +46710,23 @@ var ChartContextMenu = class {
46150
46710
  // Pointer-anchored action menu: checked state reads as a leading ✓, not a
46151
46711
  // washed row (which would read as hover in a menu with no trigger button).
46152
46712
  checkmarks: true,
46713
+ iconBadges: true,
46153
46714
  onSelect: (id) => this.run(id)
46154
46715
  });
46155
46716
  host.addEventListener("contextmenu", this.onContextMenu);
46156
46717
  }
46157
46718
  /** (Re)bind to a chart instance — called after every widget rebuild. */
46158
46719
  onChart(chart) {
46720
+ this.offCrosshair?.();
46159
46721
  this.chart = chart;
46722
+ this.crosshair = OFF_PLOT;
46723
+ this.offCrosshair = chart.renderer.onCrosshairMove((e) => {
46724
+ this.crosshair = { price: e.price, time: e.time, paneKind: e.paneKind ?? null };
46725
+ });
46160
46726
  }
46161
46727
  destroy() {
46728
+ this.offCrosshair?.();
46729
+ this.offCrosshair = null;
46162
46730
  this.host.removeEventListener("contextmenu", this.onContextMenu);
46163
46731
  this.menu.destroy();
46164
46732
  }
@@ -46177,49 +46745,52 @@ var ChartContextMenu = class {
46177
46745
  flag(feature) {
46178
46746
  return Boolean(this.chart?.renderer.get(feature));
46179
46747
  }
46180
- contributed(zone) {
46748
+ /** A fresh widget context carrying the pointer captured at open. Copied by property
46749
+ * descriptor: the context's getters must stay LIVE, and a spread would freeze them. */
46750
+ context() {
46181
46751
  const ctx = this.cbs.getContext?.();
46182
- return widgetActions(`context:${zone}`, ctx).map((a, i) => ({
46183
- id: `action:${a.id}`,
46184
- label: a.label,
46185
- icon: a.icon,
46186
- separatorBefore: i === 0
46752
+ if (!ctx) return void 0;
46753
+ return Object.create(Object.getPrototypeOf(ctx), {
46754
+ ...Object.getOwnPropertyDescriptors(ctx),
46755
+ pointer: { value: this.pointer, enumerable: true }
46756
+ });
46757
+ }
46758
+ contributed(zone) {
46759
+ return widgetActions(`context:${zone}`, this.context()).map((a) => ({
46760
+ item: { id: `action:${a.id}`, label: a.label, icon: a.icon },
46761
+ order: a.order
46187
46762
  }));
46188
46763
  }
46189
46764
  itemsFor(zone) {
46765
+ return composeMenu(zone, this.builtinItems(zone), this.contributed(zone));
46766
+ }
46767
+ builtinItems(zone) {
46190
46768
  if (zone === "price-axis") {
46191
46769
  const pane = this.lastPane;
46192
- return [
46193
- ...priceAxisItems({
46194
- auto: this.chart?.renderer.get("autoScale") !== false,
46195
- invert: pane ? pane.invert : this.flag("invertScale"),
46196
- choice: scaleChoiceOf(pane ?? { mode: String(this.chart?.renderer.get("scaleMode") ?? "price"), log: this.flag("logScale") }),
46197
- axisLabels: this.flag("axisLabels"),
46198
- priceLabel: this.flag("priceLabel"),
46199
- countdown: this.flag("countdown"),
46200
- priceLine: this.flag("currentPriceLine")
46201
- }),
46202
- ...this.contributed(zone)
46203
- ];
46770
+ return priceAxisItems({
46771
+ auto: this.chart?.renderer.get("autoScale") !== false,
46772
+ invert: pane ? pane.invert : this.flag("invertScale"),
46773
+ choice: scaleChoiceOf(pane ?? { mode: String(this.chart?.renderer.get("scaleMode") ?? "price"), log: this.flag("logScale") }),
46774
+ axisLabels: this.flag("axisLabels"),
46775
+ priceLabel: this.flag("priceLabel"),
46776
+ countdown: this.flag("countdown"),
46777
+ priceLine: this.flag("currentPriceLine")
46778
+ });
46204
46779
  }
46205
46780
  if (zone === "time-axis") {
46206
- const tz = this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC");
46207
- return [...timeAxisItems(tz), ...this.contributed("time-axis")];
46781
+ return timeAxisItems(this.cbs.timezone?.() ?? String(this.chart?.renderer.get("timezone") ?? "Etc/UTC"));
46208
46782
  }
46209
46783
  const chart = this.chart;
46210
- return [
46211
- ...bodyItems({
46212
- drawings: chart?.drawings.supported ? chart.drawings.all().length : 0,
46213
- indicators: chart?.indicators().length ?? 0
46214
- }),
46215
- ...this.contributed("body")
46216
- ];
46784
+ return bodyItems({
46785
+ drawings: chart?.drawings.supported ? chart.drawings.all().length : 0,
46786
+ indicators: chart?.indicators().length ?? 0
46787
+ });
46217
46788
  }
46218
46789
  run(id) {
46219
46790
  const chart = this.chart;
46220
46791
  if (!chart) return;
46221
46792
  if (id.startsWith("action:")) {
46222
- const ctx = this.cbs.getContext?.();
46793
+ const ctx = this.context();
46223
46794
  if (ctx) widgetActions(`context:${this.lastZone}`, ctx).find((a) => a.id === id.slice("action:".length))?.run(ctx);
46224
46795
  return;
46225
46796
  }
@@ -46343,8 +46914,8 @@ function seedDefaults(opts) {
46343
46914
  };
46344
46915
  }
46345
46916
  function cellChartDefaults(opts) {
46346
- const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings } = opts;
46347
- return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings };
46917
+ const { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis } = opts;
46918
+ return { renderer, defaultLanguage, currentPriceLine, logScale, animations, glow, upColor, downColor, drawings, settings, priceAxis };
46348
46919
  }
46349
46920
  function cellDrawings(opt) {
46350
46921
  if (opt === false) return false;
@@ -46914,12 +47485,14 @@ var ChartCell = class {
46914
47485
  this.watermarkOn = visible;
46915
47486
  this.watermark?.setVisible(visible);
46916
47487
  this.deps.onStateDirty();
47488
+ this.deps.onStatusPrefsChanged(this.id);
46917
47489
  }
46918
47490
  /** Show/hide the "Replay" line under this cell's watermark while it replays (persisted per cell). */
46919
47491
  setReplayWatermarkVisible(visible) {
46920
47492
  this.replayWatermarkOn = visible;
46921
47493
  this.watermark?.setReplayVisible(visible);
46922
47494
  this.deps.onStateDirty();
47495
+ this.deps.onStatusPrefsChanged(this.id);
46923
47496
  }
46924
47497
  /** Show/hide this cell's indicator titles — the in-chart legend rows (persisted per cell). */
46925
47498
  setIndicatorTitlesVisible(visible) {
@@ -46940,18 +47513,20 @@ var ChartCell = class {
46940
47513
  this.statusline?.setPartVisible(part, visible);
46941
47514
  this.deps.onStatusPrefsChanged(this.id);
46942
47515
  }
46943
- /** This cell's Status line tab prefs as one bundle (see {@link CellStatusPrefs}). */
47516
+ /** This cell's Status line tab and watermark prefs as one bundle (see {@link CellStatusPrefs}). */
46944
47517
  statusPrefs() {
46945
47518
  const sl = this.statusline;
46946
47519
  return {
46947
47520
  parts: sl ? { logo: sl.partVisible("logo"), name: sl.partVisible("name"), market: sl.partVisible("market"), ohlc: sl.partVisible("ohlc"), change: sl.partVisible("change") } : null,
46948
47521
  indicatorTitles: this.indicatorTitlesOn,
46949
- indicatorValues: this.indicatorValuesOn
47522
+ indicatorValues: this.indicatorValuesOn,
47523
+ watermark: this.watermarkOn,
47524
+ replayWatermark: this.replayWatermarkOn
46950
47525
  };
46951
47526
  }
46952
- /** Converge this cell's Status line tab prefs to `prefs` — the follower half of
46953
- * the workspace's style link. Idempotent: matching values change nothing, so a
46954
- * propagated echo dies on its own. */
47527
+ /** Converge this cell's Status line tab and watermark prefs to `prefs` — the
47528
+ * follower half of the workspace's style link. Idempotent: matching values change
47529
+ * nothing, so a propagated echo dies on its own. */
46955
47530
  applyStatusPrefs(prefs) {
46956
47531
  if (prefs.parts && this.statusline) {
46957
47532
  for (const part of Object.keys(prefs.parts)) {
@@ -46960,6 +47535,8 @@ var ChartCell = class {
46960
47535
  }
46961
47536
  if (prefs.indicatorTitles !== this.indicatorTitlesOn) this.setIndicatorTitlesVisible(prefs.indicatorTitles);
46962
47537
  if (prefs.indicatorValues !== this.indicatorValuesOn) this.setIndicatorValuesVisible(prefs.indicatorValues);
47538
+ if (prefs.watermark !== this.watermarkOn) this.setWatermarkVisible(prefs.watermark);
47539
+ if (prefs.replayWatermark !== this.replayWatermarkOn) this.setReplayWatermarkVisible(prefs.replayWatermark);
46963
47540
  }
46964
47541
  /** The LIVE chart of this cell — never cache it across a layout change (the cell's
46965
47542
  * identity is what endures; the chart dies with the cell). */
@@ -47864,6 +48441,55 @@ function occupancyGrid(def) {
47864
48441
  const cols = def.cols.length;
47865
48442
  return def.rows.map((_, r) => def.cols.map((_2, c) => def.cells[r * cols + c]?.id ?? `\xB7${r}x${c}`));
47866
48443
  }
48444
+ var GLYPH_MIN = 1.5;
48445
+ var GLYPH_SPAN = 13;
48446
+ function glyphStops(weights, n) {
48447
+ const w = Array.from({ length: n }, (_, i) => {
48448
+ const v = weights[i];
48449
+ return v !== void 0 && Number.isFinite(v) && v > 0 ? v : 1;
48450
+ });
48451
+ const total = w.reduce((a, b) => a + b, 0);
48452
+ const stops = [GLYPH_MIN];
48453
+ let acc = 0;
48454
+ for (const v of w) {
48455
+ acc += v;
48456
+ stops.push(Math.round((GLYPH_MIN + GLYPH_SPAN * acc / total) * 100) / 100);
48457
+ }
48458
+ return stops;
48459
+ }
48460
+ function layoutGlyph(def) {
48461
+ const grid = occupancyGrid(def);
48462
+ const rows = grid.length;
48463
+ const cols = grid.reduce((n, row) => Math.max(n, row.length), 0);
48464
+ const xs = glyphStops(def.cols, cols);
48465
+ const ys = glyphStops(def.rows, rows);
48466
+ const at = (r, c) => grid[r]?.[c];
48467
+ const seams = [];
48468
+ for (let c = 1; c < cols; c += 1) {
48469
+ let start = -1;
48470
+ for (let r = 0; r <= rows; r += 1) {
48471
+ const split = r < rows && at(r, c - 1) !== at(r, c);
48472
+ if (split && start < 0) start = r;
48473
+ if (!split && start >= 0) {
48474
+ seams.push(`M${xs[c]} ${ys[start]}V${ys[r]}`);
48475
+ start = -1;
48476
+ }
48477
+ }
48478
+ }
48479
+ for (let r = 1; r < rows; r += 1) {
48480
+ let start = -1;
48481
+ for (let c = 0; c <= cols; c += 1) {
48482
+ const split = c < cols && at(r - 1, c) !== at(r, c);
48483
+ if (split && start < 0) start = c;
48484
+ if (!split && start >= 0) {
48485
+ seams.push(`M${xs[start]} ${ys[r]}H${xs[c]}`);
48486
+ start = -1;
48487
+ }
48488
+ }
48489
+ }
48490
+ const frame = `<rect x="${GLYPH_MIN}" y="${GLYPH_MIN}" width="${GLYPH_SPAN}" height="${GLYPH_SPAN}" rx="1.5"/>`;
48491
+ return svg16(seams.length > 0 ? `${frame}<path d="${seams.join("")}"/>` : frame);
48492
+ }
47867
48493
  function gridStyles(def, trackSizes) {
47868
48494
  const cols = trackSizes?.cols?.length === def.cols.length ? trackSizes.cols : def.cols;
47869
48495
  const rows = trackSizes?.rows?.length === def.rows.length ? trackSizes.rows : def.rows;
@@ -48395,7 +49021,8 @@ var VelaWorkspace = class {
48395
49021
  onToggleSync: (id) => {
48396
49022
  const kind = id;
48397
49023
  this.sync.set(kind, this.syncOpts[kind] ? false : true);
48398
- }
49024
+ },
49025
+ glyph: () => layoutGlyph(this.def)
48399
49026
  },
48400
49027
  getContext: () => this.context()
48401
49028
  });
@@ -48894,6 +49521,7 @@ var VelaWorkspace = class {
48894
49521
  this.applyGrid();
48895
49522
  this.buildCells();
48896
49523
  this.alignNewCellStyles(preexisting);
49524
+ this.alignNewCellDrawings(preexisting);
48897
49525
  this.syncCellPresentation();
48898
49526
  this.refreshCellControls();
48899
49527
  this.topbar.setLayout(next.id);
@@ -49310,6 +49938,7 @@ var VelaWorkspace = class {
49310
49938
  const chart = cell.chart;
49311
49939
  chart.on("indicator:error", ({ error }) => this.toastHost.show(`[${cell.id}] ${error.message}`, "error", 5e3));
49312
49940
  chart.on("script:run", (run) => this.events.emit("script:run", { ...run, cell: cell.id }));
49941
+ chart.on("priceStyle:change", ({ from, to }) => this.events.emit("cell:priceStyle", { id: cell.id, from, to }));
49313
49942
  chart.on("alert", (alert) => {
49314
49943
  const source = [parseSymbol(cell.symbol).ticker || cell.symbol, timeframeLabel(cell.timeframe), alert.indicator].filter(Boolean).join(" ");
49315
49944
  this.alerts.unshift({ cellId: cell.id, source, title: alert.title ?? "Alert", message: alert.message, time: alert.time });
@@ -49427,12 +50056,66 @@ var VelaWorkspace = class {
49427
50056
  }
49428
50057
  }
49429
50058
  /**
49430
- * Mirror an origin cell's presentation — the Canvas + Scales-and-lines slice of
49431
- * its renderer config plus its Status line tab prefs — onto its same-group
49432
- * followers (the style link). Loop-safe two ways: the busy guard eats the
49433
- * followers' SYNCHRONOUS echoes (their `applyConfig` re-fires `onConfigChanged`
49434
- * in the same tick), and the equality short-circuits leave already-converged
49435
- * followers untouched, so nothing re-emits once the group agrees.
50059
+ * Bring cells minted by a layout change into their drawings group: with the link
50060
+ * on, a NEW cell (fresh slot, or one returning from the pool that missed edits
50061
+ * while dormant) receives the drawings of a pre-existing group peer — the active
50062
+ * cell when it is one. A drawing the arriving cell already holds a linked copy of
50063
+ * is refreshed in place; any other is copied and linked like a freshly synced
50064
+ * drawing, so later edits and removals follow both ways. The arrival is the cell's
50065
+ * starting state, not an edit: it stays out of the cell's undo timeline, and the
50066
+ * busy guard keeps the copies' own events from fanning back out.
50067
+ */
50068
+ alignNewCellDrawings(preexisting) {
50069
+ const setting = this.syncOpts.drawings;
50070
+ if (!setting) return;
50071
+ const ids = [...this.cellsById.keys()];
50072
+ for (const id of ids) {
50073
+ if (preexisting.has(id)) continue;
50074
+ const cell = this.cellsById.get(id);
50075
+ if (!cell?.chart.drawings.supported) continue;
50076
+ const peers = syncTargets(id, setting, ids).filter((p) => preexisting.has(p));
50077
+ if (peers.length === 0) continue;
50078
+ const sourceId = this.activeId && peers.includes(this.activeId) ? this.activeId : peers[0];
50079
+ const source = this.cellsById.get(sourceId);
50080
+ if (!source) continue;
50081
+ const drawings = cell.chart.drawings;
50082
+ this.drawingSyncBusy = true;
50083
+ try {
50084
+ cell.history.silently(() => {
50085
+ const held = new Set(drawings.all().map((d) => d.id));
50086
+ for (const doc of source.chart.drawings.all()) {
50087
+ const group = this.drawingLinks.get(`${sourceId}\0${doc.id}`) ?? /* @__PURE__ */ new Map([[sourceId, doc.id]]);
50088
+ const peerId = group.get(id);
50089
+ if (peerId != null && held.has(peerId)) {
50090
+ drawings.update(peerId, { anchors: doc.anchors, style: doc.style, text: doc.text, props: doc.props });
50091
+ continue;
50092
+ }
50093
+ const copy = drawings.add(doc.type, {
50094
+ paneId: doc.paneId,
50095
+ anchors: doc.anchors,
50096
+ style: doc.style,
50097
+ text: doc.text,
50098
+ props: doc.props,
50099
+ zIndex: doc.zIndex
50100
+ });
50101
+ if (!copy) continue;
50102
+ if (peerId != null) this.drawingLinks.delete(`${id}\0${peerId}`);
50103
+ group.set(id, copy.id);
50104
+ for (const [cellId, dId] of group) this.drawingLinks.set(`${cellId}\0${dId}`, group);
50105
+ }
50106
+ });
50107
+ } finally {
50108
+ this.drawingSyncBusy = false;
50109
+ }
50110
+ }
50111
+ }
50112
+ /**
50113
+ * Mirror an origin cell's presentation — the style-link slice of its renderer
50114
+ * config ({@link styleConfigPatch}) plus its Status line and watermark prefs —
50115
+ * onto its same-group followers (the style link). Loop-safe two ways: the busy
50116
+ * guard eats the followers' SYNCHRONOUS echoes (their `applyConfig` re-fires
50117
+ * `onConfigChanged` in the same tick), and the equality short-circuits leave
50118
+ * already-converged followers untouched, so nothing re-emits once the group agrees.
49436
50119
  */
49437
50120
  propagateStylePrefs(originId) {
49438
50121
  if (this.styleSyncBusy || this.destroyed) return;
@@ -49440,17 +50123,15 @@ var VelaWorkspace = class {
49440
50123
  if (targets.length === 0) return;
49441
50124
  const origin = this.cellsById.get(originId);
49442
50125
  if (!origin) return;
49443
- const slice = styleConfigSlice(origin.chart.renderer.getConfig());
49444
- const sliceJson = slice ? JSON.stringify(slice) : null;
50126
+ const config = origin.chart.renderer.getConfig();
49445
50127
  const prefs = origin.statusPrefs();
49446
50128
  this.styleSyncBusy = true;
49447
50129
  try {
49448
50130
  for (const id of targets) {
49449
50131
  const cell = this.cellsById.get(id);
49450
50132
  if (!cell) continue;
49451
- if (slice && sliceJson !== JSON.stringify(styleConfigSlice(cell.chart.renderer.getConfig()))) {
49452
- cell.chart.renderer.applyConfig(slice);
49453
- }
50133
+ const patch = styleConfigPatch(config, cell.chart.renderer.getConfig());
50134
+ if (patch) cell.chart.renderer.applyConfig(patch);
49454
50135
  cell.applyStatusPrefs(prefs);
49455
50136
  }
49456
50137
  } finally {
@@ -49771,7 +50452,8 @@ var VelaWorkspace = class {
49771
50452
  onToggleSync: (id) => {
49772
50453
  const kind = id;
49773
50454
  this.sync.set(kind, this.syncOpts[kind] ? false : true);
49774
- }
50455
+ },
50456
+ glyph: () => layoutGlyph(this.def)
49775
50457
  },
49776
50458
  onOpenChange: (open2) => this.trackDialog(open2)
49777
50459
  }));
@@ -50035,6 +50717,7 @@ var VelaWidget = class {
50035
50717
  };
50036
50718
 
50037
50719
  exports.Bottombar = Bottombar;
50720
+ exports.CONTEXT_MENU_BUILTIN_ORDER = CONTEXT_MENU_BUILTIN_ORDER;
50038
50721
  exports.ChartContextMenu = ChartContextMenu;
50039
50722
  exports.DEFAULT_PANEL_MAX_WIDTH = DEFAULT_PANEL_MAX_WIDTH;
50040
50723
  exports.DEFAULT_PANEL_MIN_WIDTH = DEFAULT_PANEL_MIN_WIDTH;