@luxalgo/vela 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/NOTICE +1 -1
  2. package/README.md +16 -8
  3. package/dist/{DataProvider-BrlxntPN.d.cts → DataProvider-COGDs9Xg.d.cts} +1 -1
  4. package/dist/{DataProvider-DwFIoV4r.d.ts → DataProvider-DpOWZD8F.d.ts} +1 -1
  5. package/dist/{chunk-KA77UTRF.js → chunk-27UTYE7T.js} +25 -2
  6. package/dist/{chunk-EZ5FWVLA.js → chunk-DPAA3SRT.js} +94 -13
  7. package/dist/{chunk-BZQM2XO7.js → chunk-HKNO24GD.js} +13 -1
  8. package/dist/{chunk-2IAETM2I.js → chunk-NLDTVFOI.js} +79 -6
  9. package/dist/{chunk-E3GU6AUI.js → chunk-R2JMNIHY.js} +68 -23
  10. package/dist/{chunk-QBPUT5SU.js → chunk-T3DZIR5Y.js} +211 -54
  11. package/dist/{contributions-tjNExo1o.d.ts → contributions-Cgm9RJYP.d.cts} +38 -11
  12. package/dist/{contributions-CtlptxR8.d.cts → contributions-bWgwyiVi.d.ts} +38 -11
  13. package/dist/index.cjs +317 -62
  14. package/dist/index.d.cts +8 -8
  15. package/dist/index.d.ts +8 -8
  16. package/dist/index.js +4 -4
  17. package/dist/{options-BnEPUuVV.d.ts → options-NAUHiQEC.d.cts} +3 -3
  18. package/dist/{options-BnEPUuVV.d.cts → options-NAUHiQEC.d.ts} +3 -3
  19. package/dist/{plugin-BWxJRKzS.d.cts → plugin-CjjzZX2F.d.cts} +4 -4
  20. package/dist/{plugin-e2WUEtiI.d.ts → plugin-CqxWoDKS.d.ts} +4 -4
  21. package/dist/plugin.cjs +20 -6
  22. package/dist/plugin.d.cts +5 -5
  23. package/dist/plugin.d.ts +5 -5
  24. package/dist/plugin.js +2 -2
  25. package/dist/providers/binance.d.cts +2 -2
  26. package/dist/providers/binance.d.ts +2 -2
  27. package/dist/providers/coinbase.d.cts +2 -2
  28. package/dist/providers/coinbase.d.ts +2 -2
  29. package/dist/providers/hyperliquid.d.cts +2 -2
  30. package/dist/providers/hyperliquid.d.ts +2 -2
  31. package/dist/{side-panel-HF0IAzwf.d.cts → side-panel-wMNfgyZb.d.cts} +13 -0
  32. package/dist/{side-panel-HF0IAzwf.d.ts → side-panel-wMNfgyZb.d.ts} +13 -0
  33. package/dist/{statusline-model-zKXtGIjj.d.ts → statusline-model-Dih6FGWP.d.ts} +3 -3
  34. package/dist/{statusline-model-DFY7tuHj.d.cts → statusline-model-Dpfpq54A.d.cts} +3 -3
  35. package/dist/ui.cjs +115 -5
  36. package/dist/ui.d.cts +28 -2
  37. package/dist/ui.d.ts +28 -2
  38. package/dist/ui.js +3 -3
  39. package/dist/vela.global.js +317 -62
  40. package/dist/vela.global.min.js +61 -68
  41. package/dist/widget.cjs +476 -85
  42. package/dist/widget.d.cts +20 -10
  43. package/dist/widget.d.ts +20 -10
  44. package/dist/widget.js +7 -7
  45. package/dist/workspace.cjs +476 -85
  46. package/dist/workspace.d.cts +6 -6
  47. package/dist/workspace.d.ts +6 -6
  48. package/dist/workspace.js +6 -6
  49. package/package.json +3 -3
package/NOTICE CHANGED
@@ -1,6 +1,6 @@
1
1
  Vela
2
2
  Copyright (c) 2026 LuxAlgo
3
- https://luxalgo.com/vela
3
+ https://velacharts.dev
4
4
 
5
5
  This product includes the Vela charting library, developed by LuxAlgo.
6
6
 
package/README.md CHANGED
@@ -2,9 +2,17 @@
2
2
 
3
3
  <div align="center">
4
4
 
5
- <img src=".github/banner.png" alt="Vela™ — fast, extensible financial charts for the web" width="100%">
5
+ <a href="https://velacharts.dev">
6
+ <picture>
7
+ <source media="(prefers-color-scheme: dark)" srcset=".github/banner.png">
8
+ <source media="(prefers-color-scheme: light)" srcset=".github/banner-light.png">
9
+ <img src=".github/banner.png" alt="Vela™ by LuxAlgo" width="100%">
10
+ </picture>
11
+ </a>
6
12
 
7
- <p><strong>Fast, extensible financial charts for the web.</strong><br>
13
+ # Vela™
14
+
15
+ <p><strong>Fast, extensible financial charts for the web: the open-source core of <a href="https://vela.luxalgo.com/chart">Vela</a> by LuxAlgo.</strong><br>
8
16
  Headless core · native WebGL2 renderer · batteries-included workspace · plugin SDK</p>
9
17
 
10
18
  [![npm version][npm-version-img]][npm-link]
@@ -12,9 +20,9 @@
12
20
  [![License][license-img]][license-link]
13
21
 
14
22
  <p>
15
- <a href="https://luxalgo.com/vela">Homepage</a> ·
23
+ <a href="https://velacharts.dev">Homepage</a> ·
16
24
  <a href="#quick-start">Quick start</a> ·
17
- <a href="docs/index.md">Documentation</a> ·
25
+ <a href="https://docs.luxalgo.com/vela">Documentation</a> ·
18
26
  <a href="#extending-plugin-sdk">Plugin SDK</a> ·
19
27
  <a href="CHANGELOG.md">Changelog</a> ·
20
28
  <a href="#license-and-attribution">License</a>
@@ -41,7 +49,7 @@ touching the rest.
41
49
  - **`@luxalgo/vela/workspace`**: the full chart app. One chart or a grid of them under
42
50
  one shared topbar (symbol / timeframe / style / indicators), status line, symbol
43
51
  watermark, bottom bar (ranges, clock, timezone), object tree, keyboard-first UX
44
- (type a letter for symbol search, a digit for timeframe entry, `?` for the shortcuts
52
+ (type a letter or `0` for symbol search, a digit 1–9 for timeframe entry, `?` for the shortcuts
45
53
  panel), named cells, sync groups, and one persisted state document.
46
54
  - **`@luxalgo/vela/ui`**: the component kit the app is built on. Design tokens, overlay
47
55
  chrome ([Zag.js](https://zagjs.com) menu/dialog/drawer/tooltip), form primitives, and
@@ -108,7 +116,7 @@ alongside them.
108
116
 
109
117
  Custom scripts still run through pluggable engines. Vela™ **ships none**: install the
110
118
  addon for the language you want, or write one against the public `ScriptingEngine` port.
111
- Pine Script lives in [`@luxalgo/vela-pinets`](https://github.com/LuxAlgo/Vela-pinets)
119
+ Pine Script® lives in [`@luxalgo/vela-pinets`](https://github.com/LuxAlgo/Vela-pinets)
112
120
  (`npm i @luxalgo/vela-pinets pinets`), which is **AGPL-3.0** because the PineTS runtime it
113
121
  executes is. Vela™ itself stays Apache-2.0 and carries no Pine code:
114
122
 
@@ -198,10 +206,10 @@ include it per Apache-2.0 §4(d):
198
206
  This is the same licensing model used by other popular charting libraries, and we're
199
207
  grateful when the watermark stays in a visible spot.
200
208
 
201
- No scripting engine ships with this package; the Pine Script addon
209
+ No scripting engine ships with this package; the Pine Script® addon
202
210
  (`@luxalgo/vela-pinets`) is AGPL-3.0 and licensed separately (see *Indicators*).
203
211
 
204
- [homepage]: https://luxalgo.com/vela
212
+ [homepage]: https://velacharts.dev
205
213
 
206
214
  [npm-version-img]: https://img.shields.io/npm/v/%40luxalgo%2Fvela.svg
207
215
  [npm-downloads-img]: https://img.shields.io/npm/dm/%40luxalgo%2Fvela.svg
@@ -1,4 +1,4 @@
1
- import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-BnEPUuVV.cjs';
1
+ import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-NAUHiQEC.cjs';
2
2
 
3
3
  /**
4
4
  * Symbol metadata an engine may need (e.g. Pine `syminfo.*`). Free-form beyond
@@ -1,4 +1,4 @@
1
- import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-BnEPUuVV.js';
1
+ import { M as MarketConfig, O as OHLCV, U as Unsubscribe } from './options-NAUHiQEC.js';
2
2
 
3
3
  /**
4
4
  * Symbol metadata an engine may need (e.g. Pine `syminfo.*`). Free-form beyond
@@ -1,5 +1,5 @@
1
- import { normalizeProps, nextUid, runMachine, spreadProps } from './chunk-2IAETM2I.js';
2
- import { injectStyles } from './chunk-BZQM2XO7.js';
1
+ import { normalizeProps, nextUid, runMachine, spreadProps } from './chunk-NLDTVFOI.js';
2
+ import { injectStyles, announceSurface } from './chunk-HKNO24GD.js';
3
3
  import * as tooltip from '@zag-js/tooltip';
4
4
  import * as dialog from '@zag-js/dialog';
5
5
 
@@ -343,6 +343,8 @@ function swipeDirection(dx, dy) {
343
343
  }
344
344
  var Drawer = class {
345
345
  constructor(opts = {}) {
346
+ this.opener = null;
347
+ this.wasOpen = false;
346
348
  const doc = (opts.host ?? document.body).ownerDocument;
347
349
  injectStyles(DRAWER_STYLE_ID, DRAWER_CSS, doc);
348
350
  const host = opts.host ?? doc.body;
@@ -370,12 +372,16 @@ var Drawer = class {
370
372
  const mid = String(this.ctrl.props.id);
371
373
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
372
374
  const api = this.ctrl.connect(service);
375
+ if (this.wasOpen && !api.open) this.announce(false);
373
376
  spreadProps(this.backdrop, api.getBackdropProps(), mid);
374
377
  spreadProps(this.positioner, api.getPositionerProps(), mid);
375
378
  spreadProps(this.panel, api.getContentProps(), mid);
376
379
  spreadProps(this.titleEl, api.getTitleProps(), mid);
377
380
  this.backdrop.style.display = api.open ? "" : "none";
378
381
  this.positioner.style.display = api.open ? "" : "none";
382
+ if (api.open && !this.wasOpen) this.announce(true);
383
+ if (this.wasOpen && !api.open) this.restoreOpener();
384
+ this.wasOpen = api.open;
379
385
  });
380
386
  }
381
387
  /** Any element between `from` and the panel that has already been scrolled down —
@@ -484,10 +490,22 @@ var Drawer = class {
484
490
  setTitle(title) {
485
491
  this.titleEl.textContent = title;
486
492
  }
493
+ /** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
494
+ * teardown that outruns the machine's close notification). */
495
+ restoreOpener() {
496
+ const opener = this.opener;
497
+ this.opener = null;
498
+ if (opener?.isConnected) opener.focus({ preventScroll: true });
499
+ }
500
+ announce(open) {
501
+ const opener = this.opener === this.panel.ownerDocument.body ? null : this.opener;
502
+ announceSurface(this.panel, open, "drawer", opener);
503
+ }
487
504
  get open() {
488
505
  return this.ctrl.connect(this.handle.service).open;
489
506
  }
490
507
  show() {
508
+ this.opener = this.panel.ownerDocument.activeElement;
491
509
  this.ctrl.connect(this.handle.service).setOpen(true);
492
510
  }
493
511
  hide() {
@@ -495,6 +513,11 @@ var Drawer = class {
495
513
  }
496
514
  destroy() {
497
515
  this.handle.stop();
516
+ if (this.wasOpen) {
517
+ this.wasOpen = false;
518
+ this.announce(false);
519
+ }
520
+ this.restoreOpener();
498
521
  this.backdrop.remove();
499
522
  this.positioner.remove();
500
523
  }
@@ -1,4 +1,4 @@
1
- import { svg24, NEUTRAL, BEARISH, WARNING, BULLISH, INFO, ACCENT_BRIGHT, VALID, INVALID, MARKER, ACCENT, injectStyles, iconEl } from './chunk-BZQM2XO7.js';
1
+ import { svg24, NEUTRAL, BEARISH, WARNING, BULLISH, INFO, ACCENT_BRIGHT, VALID, INVALID, MARKER, ACCENT, injectStyles, iconEl, announceSurface } from './chunk-HKNO24GD.js';
2
2
 
3
3
  // src/core/model/series.ts
4
4
  function isLineLikeSeries(spec) {
@@ -2778,6 +2778,10 @@ var FibRatios = class extends Drawing {
2778
2778
 
2779
2779
  // src/core/drawings/types/FibLevels.ts
2780
2780
  var FibLevels = class extends FibRatios {
2781
+ /** Price of a level for the two anchor prices; subclasses override to change which anchor ratio 0 sits on. */
2782
+ levelPrice(ratio, p1, p2) {
2783
+ return p1 + ratio * (p2 - p1);
2784
+ }
2781
2785
  /** Per-level pixel line + price for the ENABLED levels, spanning the anchors' time range. */
2782
2786
  levelLines(proj) {
2783
2787
  const a = this.anchors[0];
@@ -2787,11 +2791,10 @@ var FibLevels = class extends FibRatios {
2787
2791
  const xb = proj.xOf(b.time);
2788
2792
  const x1 = Math.min(xa, xb);
2789
2793
  const x2 = Math.max(xa, xb);
2790
- const delta = b.price - a.price;
2791
2794
  const out = [];
2792
2795
  for (const lv of this.levels) {
2793
2796
  if (!lv.enabled) continue;
2794
- const price = a.price + lv.ratio * delta;
2797
+ const price = this.levelPrice(lv.ratio, a.price, b.price);
2795
2798
  const y = proj.yOf(price, this.paneId);
2796
2799
  if (y == null) continue;
2797
2800
  out.push({ ratio: lv.ratio, color: lv.color, label: lv.label, price, x1, x2, y });
@@ -2831,8 +2834,7 @@ var FibLevels = class extends FibRatios {
2831
2834
  const a = this.anchors[0];
2832
2835
  const b = this.anchors[1];
2833
2836
  if (!a || !b) return null;
2834
- const delta = b.price - a.price;
2835
- const prices = this.levels.filter((l) => l.enabled).map((l) => a.price + l.ratio * delta);
2837
+ const prices = this.levels.filter((l) => l.enabled).map((l) => this.levelPrice(l.ratio, a.price, b.price));
2836
2838
  if (prices.length === 0) return null;
2837
2839
  return { min: Math.min(...prices), max: Math.max(...prices) };
2838
2840
  }
@@ -2918,13 +2920,24 @@ function cycleLevels(count, enabledCount = count) {
2918
2920
  // src/core/drawings/types/FibRetracement.ts
2919
2921
  var LEVELS = fibLevels([0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]);
2920
2922
  var FibRetracement = class extends FibLevels {
2921
- constructor() {
2922
- super(...arguments);
2923
+ constructor(init) {
2924
+ super(init);
2923
2925
  this.type = "fibretracement";
2926
+ if (this.reverse === void 0) this.reverse = false;
2924
2927
  }
2925
2928
  defaultLevels() {
2926
2929
  return LEVELS;
2927
2930
  }
2931
+ levelPrice(ratio, p1, p2) {
2932
+ return this.reverse ? p1 + ratio * (p2 - p1) : p2 + ratio * (p1 - p2);
2933
+ }
2934
+ writeProps() {
2935
+ return { ...super.writeProps(), reverse: this.reverse };
2936
+ }
2937
+ readProps(props) {
2938
+ super.readProps(props);
2939
+ this.reverse = typeof props.reverse === "boolean" ? props.reverse : true;
2940
+ }
2928
2941
  };
2929
2942
 
2930
2943
  // src/core/drawings/types/FibExtension.ts
@@ -6023,10 +6036,12 @@ function legendActions() {
6023
6036
  }
6024
6037
  function legendActionsProviderFor(chart, context) {
6025
6038
  return (indicatorId) => {
6026
- const handle = chart.indicators().find((h) => h.id === indicatorId);
6039
+ const find = () => chart.indicators().find((h) => h.id === indicatorId);
6040
+ const infoOf = (h) => ({ id: h.id, title: h.title, ...h.source !== void 0 ? { source: h.source } : {} });
6041
+ const handle = find();
6027
6042
  if (!handle) return [];
6028
- const info = { id: handle.id, title: handle.title, ...handle.source !== void 0 ? { source: handle.source } : {} };
6029
- return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(), info) }));
6043
+ const info = infoOf(handle);
6044
+ return legendActions().filter((d) => !d.when || d.when(info)).map((d) => ({ id: d.id, icon: d.icon, tooltip: d.tooltip, run: () => d.run(context(), infoOf(find() ?? handle)) }));
6030
6045
  };
6031
6046
  }
6032
6047
  var calloutRegistry = /* @__PURE__ */ new Map();
@@ -6177,6 +6192,32 @@ var CSS = `
6177
6192
  .vela-panel-pin .vela-icon { width: 15px; height: 15px; }
6178
6193
  .vela-panel-pin:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
6179
6194
  .vela-panel-pin[data-on='1'] { color: var(--vela-fg-bright); }
6195
+ .vela-panel-max {
6196
+ all: unset;
6197
+ cursor: pointer;
6198
+ width: 26px;
6199
+ height: 26px;
6200
+ flex: none;
6201
+ display: inline-flex;
6202
+ align-items: center;
6203
+ justify-content: center;
6204
+ border-radius: 4px;
6205
+ color: var(--vela-fg-muted);
6206
+ }
6207
+ .vela-panel-max .vela-icon { width: 15px; height: 15px; }
6208
+ .vela-panel-max:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
6209
+ /* MAXIMIZED: the panel covers the whole dock host \u2014 every chart of the shell \u2014 whatever
6210
+ its placement; the width handle and the pin have nothing to do meanwhile. */
6211
+ .vela-panel[data-maximized] {
6212
+ position: absolute;
6213
+ inset: 0;
6214
+ width: auto;
6215
+ max-width: none;
6216
+ z-index: 26;
6217
+ border-left: none;
6218
+ }
6219
+ .vela-panel[data-maximized] .vela-panel-resizer,
6220
+ .vela-panel[data-maximized] .vela-panel-pin { display: none; }
6180
6221
  .vela-panel-body { flex: 1; overflow: auto; padding: 8px; }
6181
6222
  .vela-panel-body::-webkit-scrollbar { width: 8px; }
6182
6223
  .vela-panel-body::-webkit-scrollbar-thumb { background: var(--vela-scroll); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
@@ -6229,6 +6270,7 @@ var CSS = `
6229
6270
  border-left: none;
6230
6271
  }
6231
6272
  [data-layout='mobile'] .vela-panel-resizer { display: none; }
6273
+ [data-layout='mobile'] .vela-panel-max { display: none; }
6232
6274
  `;
6233
6275
  function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_MAX_WIDTH) {
6234
6276
  const lo = Number.isFinite(min) && min > 0 ? min : DEFAULT_PANEL_MIN_WIDTH;
@@ -6252,6 +6294,10 @@ var SidePanel = class {
6252
6294
  * never on a programmatic {@link setOverlay}, for the same reason as widths. */
6253
6295
  this.onPlacementChange = null;
6254
6296
  this.pin = null;
6297
+ this.maxButton = null;
6298
+ this.maximizedOn = false;
6299
+ /** Set while the close is announced, so a listener's own close does not re-enter. */
6300
+ this.closing = false;
6255
6301
  const doc = host.ownerDocument;
6256
6302
  injectStyles(STYLE_ID, CSS, doc);
6257
6303
  this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
@@ -6286,7 +6332,13 @@ var SidePanel = class {
6286
6332
  });
6287
6333
  this.refreshPin();
6288
6334
  }
6289
- header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], close);
6335
+ if (opts.maximizable) {
6336
+ this.maxButton = doc.createElement("button");
6337
+ this.maxButton.className = "vela-panel-max";
6338
+ this.maxButton.addEventListener("click", () => this.setMaximized(!this.maximizedOn));
6339
+ this.refreshMaxButton();
6340
+ }
6341
+ header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], ...this.maxButton ? [this.maxButton] : [], close);
6290
6342
  this.body = doc.createElement("div");
6291
6343
  this.body.className = "vela-panel-body";
6292
6344
  this.el.append(header, this.body);
@@ -6298,9 +6350,37 @@ var SidePanel = class {
6298
6350
  }
6299
6351
  /** Open/close the panel — a bare call flips it. */
6300
6352
  toggle(open = this.el.hidden) {
6301
- if (open === !this.el.hidden) return;
6353
+ if (open === !this.el.hidden || !open && this.closing) return;
6354
+ if (!open) {
6355
+ this.closing = true;
6356
+ announceSurface(this.el, false, "panel");
6357
+ this.closing = false;
6358
+ }
6302
6359
  this.el.hidden = !open;
6360
+ if (!open) this.setMaximized(false);
6303
6361
  this.onOpenChange?.(open);
6362
+ if (open && !this.el.hidden) announceSurface(this.el, true, "panel");
6363
+ }
6364
+ /** Whether the panel covers every chart right now. */
6365
+ get maximized() {
6366
+ return this.maximizedOn;
6367
+ }
6368
+ /** Maximize the panel over the shell's charts, or restore it. A no-op on a panel not
6369
+ * declared `maximizable`. */
6370
+ setMaximized(maximized) {
6371
+ if (!this.maxButton || maximized === this.maximizedOn) return;
6372
+ this.maximizedOn = maximized;
6373
+ if (maximized) this.el.dataset.maximized = "1";
6374
+ else delete this.el.dataset.maximized;
6375
+ this.refreshMaxButton();
6376
+ }
6377
+ refreshMaxButton() {
6378
+ if (!this.maxButton) return;
6379
+ const doc = this.maxButton.ownerDocument;
6380
+ this.maxButton.replaceChildren(iconEl(this.maximizedOn ? "restore" : "maximize", doc));
6381
+ this.maxButton.title = this.maximizedOn ? "Restore" : "Maximize";
6382
+ this.maxButton.setAttribute("aria-label", this.maxButton.title);
6383
+ this.maxButton.setAttribute("aria-pressed", this.maximizedOn ? "true" : "false");
6304
6384
  }
6305
6385
  /** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
6306
6386
  * `mount` receives exactly this element. Subclasses use the protected `body`. */
@@ -6352,6 +6432,7 @@ var SidePanel = class {
6352
6432
  this.pin.setAttribute("aria-pressed", pinned ? "true" : "false");
6353
6433
  }
6354
6434
  destroy() {
6435
+ if (this.open) announceSurface(this.el, false, "panel");
6355
6436
  this.el.remove();
6356
6437
  }
6357
6438
  /** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
@@ -6396,4 +6477,4 @@ var SidePanel = class {
6396
6477
  }
6397
6478
  };
6398
6479
 
6399
- export { AnchoredVwap, ArrowMark, Callout, CalloutBase, Comment, DEDEKIND_CURVATURE_OPTIONS, DEFAULT_DRAWING_COLOR, DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_ORDER, DEFAULT_PANEL_WIDTH, DIRECTION_OPTIONS, DedekindTessellation, Drawing, FibRatios, FibSpiral, FixedRangeVolumeProfile, GANN_SQUARE_ARCS, GLYPH_OPTIONS, GannSquare, GlyphStamp, LINE_STYLE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, MAGNIFIER_TIMEFRAME_OPTIONS, MachFigure, Magnifier, MeasureBox, Note, OVERRIDABLE_TOPBAR_IDS, PatternDrawing, PositionTool, PriceLabel, PriceNote, RadialFib, RegressionChannel, STAMP_SIZE_OPTIONS, SegmentDrawing, SidePanel, Signpost, TEXT_SIZE_OPTIONS, TOPBAR_BUILTIN_IDS, TOPBAR_DEFAULT_LEFT, TOPBAR_DEFAULT_RIGHT, TextLabel, chartType, chartTypes, clampPanelWidth, createDrawing, deserializeDrawing, drawingTypes, foldBaseModulation, formatDuration, getDrawingType, getNativeIndicator, inputDeltas, inputVisible, isLineLikeSeries, legendActions, legendActionsProviderFor, legendCallouts, legendCalloutsProviderFor, lineSegmentIntersection, magnifierTimeframeLabel, mobilePlacement, nativeIndicatorDescriptors, nativeIndicatorTypes, nativeInstanceChannel, normalizeSettingsRow, pinnedTopbarActionIds, registerChartType, registerDefaultEngine, registerDrawingType, registerLegendAction, registerLegendCallout, registerNativeIndicator, registerRendererDefaults, registerRendererLayer, registerSidePanel, registerStatePersistence, registerSymbolRanking, registerWidgetAction, registerWidgetAttachment, rendererDefaults, rendererLayers, resetDrawingSettings, resolveEngines, resolveTopbarComposition, seriesInScale, seriesShownOn, settingsRowValueKeys, settingsRowVisible, sidePanels, stableSeriesId, statePersistenceHandlers, symbolRanking, tickerModifierIds, topbarActionOverride, topbarHas, unregisterChartType, unregisterDefaultEngine, unregisterLegendAction, unregisterLegendCallout, unregisterNativeIndicator, unregisterRendererDefaults, unregisterRendererLayer, unregisterSidePanel, unregisterStatePersistence, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments };
6480
+ export { AnchoredVwap, ArrowMark, Callout, CalloutBase, Comment, DEDEKIND_CURVATURE_OPTIONS, DEFAULT_DRAWING_COLOR, DEFAULT_PANEL_MAX_WIDTH, DEFAULT_PANEL_MIN_WIDTH, DEFAULT_PANEL_ORDER, DEFAULT_PANEL_WIDTH, DIRECTION_OPTIONS, DedekindTessellation, Drawing, FibRatios, FibRetracement, FibSpiral, FixedRangeVolumeProfile, GANN_SQUARE_ARCS, GLYPH_OPTIONS, GannSquare, GlyphStamp, LINE_STYLE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, MAGNIFIER_TIMEFRAME_OPTIONS, MachFigure, Magnifier, MeasureBox, Note, OVERRIDABLE_TOPBAR_IDS, PatternDrawing, PositionTool, PriceLabel, PriceNote, RadialFib, RegressionChannel, STAMP_SIZE_OPTIONS, SegmentDrawing, SidePanel, Signpost, TEXT_SIZE_OPTIONS, TOPBAR_BUILTIN_IDS, TOPBAR_DEFAULT_LEFT, TOPBAR_DEFAULT_RIGHT, TextLabel, chartType, chartTypes, clampPanelWidth, createDrawing, deserializeDrawing, drawingTypes, foldBaseModulation, formatDuration, getDrawingType, getNativeIndicator, inputDeltas, inputVisible, isLineLikeSeries, legendActions, legendActionsProviderFor, legendCallouts, legendCalloutsProviderFor, lineSegmentIntersection, magnifierTimeframeLabel, mobilePlacement, nativeIndicatorDescriptors, nativeIndicatorTypes, nativeInstanceChannel, normalizeSettingsRow, pinnedTopbarActionIds, registerChartType, registerDefaultEngine, registerDrawingType, registerLegendAction, registerLegendCallout, registerNativeIndicator, registerRendererDefaults, registerRendererLayer, registerSidePanel, registerStatePersistence, registerSymbolRanking, registerWidgetAction, registerWidgetAttachment, rendererDefaults, rendererLayers, resetDrawingSettings, resolveEngines, resolveTopbarComposition, seriesInScale, seriesShownOn, settingsRowValueKeys, settingsRowVisible, sidePanels, stableSeriesId, statePersistenceHandlers, symbolRanking, tickerModifierIds, topbarActionOverride, topbarHas, unregisterChartType, unregisterDefaultEngine, unregisterLegendAction, unregisterLegendCallout, unregisterNativeIndicator, unregisterRendererDefaults, unregisterRendererLayer, unregisterSidePanel, unregisterStatePersistence, unregisterWidgetAction, unregisterWidgetAttachment, widgetActions, widgetAttachments };
@@ -146,6 +146,7 @@ registerIcon("expand", S('<rect x="2.2" y="2.2" width="11.6" height="11.6" rx="1
146
146
  registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
147
147
  registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
148
148
  registerIcon("maximize", S('<path d="M2.5 6V3a.5.5 0 0 1 .5-.5h3M10 2.5h3a.5.5 0 0 1 .5.5v3M13.5 10v3a.5.5 0 0 1-.5.5h-3M6 13.5H3a.5.5 0 0 1-.5-.5v-3"/>'));
149
+ registerIcon("restore", S('<path d="M6 2.5v3a.5.5 0 0 1-.5.5h-3M13.5 6h-3a.5.5 0 0 1-.5-.5v-3M10 13.5v-3a.5.5 0 0 1 .5-.5h3M2.5 10h3a.5.5 0 0 1 .5.5v3"/>'));
149
150
  registerIcon("restore", S('<path d="M6.2 2.5v3.7H2.5M9.8 13.5V9.8h3.7M13.5 6.2H9.8V2.5M2.5 9.8h3.7v3.7"/>'));
150
151
  registerIcon("star", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>'));
151
152
  registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L8 11.1l-3.5 1.85.65-3.9-2.8-2.75 3.9-.55z"/>', 'fill="currentColor"'));
@@ -270,4 +271,15 @@ function iconEl(id, doc = document) {
270
271
  return span;
271
272
  }
272
273
 
273
- export { ACCENT, ACCENT_BRIGHT, BEARISH, BULLISH, CATEGORICAL, CHIP_PLATE, CROSSHAIR, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, HIGHLIGHT, INFO, INVALID, MARKER, NEUTRAL, SERIES_LINE, SESSION_OFF, SESSION_POST, SESSION_PRE, SLATE, SLATE_DEEP, TRADE_EXIT, TRADE_LONG, TRADE_SHORT, VALID, WARNING, categoricalColor, icon, iconAt, iconEl, iconMarkup, injectStyles, isDarkColor, mix, overlayScrollbarCss, registerIcon, svg16, svg24, svg24Solid, withAlpha };
274
+ // src/ui/surface-events.ts
275
+ var SURFACE_OPEN_EVENT = "vela:surface-open";
276
+ var SURFACE_CLOSE_EVENT = "vela:surface-close";
277
+ function announceSurface(el, open, kind, trigger = null) {
278
+ const View = el.ownerDocument.defaultView;
279
+ if (!View) return;
280
+ el.dispatchEvent(
281
+ new View.CustomEvent(open ? SURFACE_OPEN_EVENT : SURFACE_CLOSE_EVENT, { bubbles: true, composed: true, detail: { kind, trigger } })
282
+ );
283
+ }
284
+
285
+ export { ACCENT, ACCENT_BRIGHT, BEARISH, BULLISH, CATEGORICAL, CHIP_PLATE, CROSSHAIR, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, HIGHLIGHT, INFO, INVALID, MARKER, NEUTRAL, SERIES_LINE, SESSION_OFF, SESSION_POST, SESSION_PRE, SLATE, SLATE_DEEP, SURFACE_CLOSE_EVENT, SURFACE_OPEN_EVENT, TRADE_EXIT, TRADE_LONG, TRADE_SHORT, VALID, WARNING, announceSurface, categoricalColor, icon, iconAt, iconEl, iconMarkup, injectStyles, isDarkColor, mix, overlayScrollbarCss, registerIcon, svg16, svg24, svg24Solid, withAlpha };
@@ -1,4 +1,4 @@
1
- import { overlayScrollbarCss, isDarkColor, mix, HIGHLIGHT, ACCENT_BRIGHT, ACCENT, withAlpha, injectStyles, iconAt, iconEl, BULLISH, BEARISH, WARNING, NEUTRAL } from './chunk-BZQM2XO7.js';
1
+ import { overlayScrollbarCss, isDarkColor, mix, HIGHLIGHT, ACCENT_BRIGHT, ACCENT, withAlpha, injectStyles, announceSurface, iconAt, iconEl, BULLISH, BEARISH, WARNING, NEUTRAL } from './chunk-HKNO24GD.js';
2
2
  import { VanillaMachine, normalizeProps, spreadProps } from '@zag-js/vanilla';
3
3
  export { normalizeProps, spreadProps } from '@zag-js/vanilla';
4
4
  import * as menu from '@zag-js/menu';
@@ -290,6 +290,8 @@ var Surface = class _Surface {
290
290
  /** Trigger rect captured when this level opened — the list stays put if the
291
291
  * trigger then moves (favorite chips shifting the caret). */
292
292
  this.pinnedAnchor = null;
293
+ /** The open state last announced — see {@link announce}. */
294
+ this.shown = false;
293
295
  this.doc = doc;
294
296
  this.host = opts.host;
295
297
  this.onSelect = opts.onSelect;
@@ -303,6 +305,7 @@ var Surface = class _Surface {
303
305
  this.positioner.appendChild(this.list);
304
306
  this.host.appendChild(this.positioner);
305
307
  const trigger = opts.trigger;
308
+ this.opener = trigger ?? opts.opener ?? null;
306
309
  this.ctrl = menuController({
307
310
  items: [],
308
311
  id: opts.id,
@@ -324,15 +327,21 @@ var Surface = class _Surface {
324
327
  if (opts.triggerId && trigger) trigger.id = opts.triggerId;
325
328
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
326
329
  const api = this.ctrl.connect(service);
330
+ if (this.shown && !api.open) this.announce(false);
327
331
  if (trigger) spreadProps(trigger, api.getTriggerProps(), this.mid);
328
332
  spreadProps(this.positioner, api.getPositionerProps(), this.mid);
329
333
  spreadProps(this.list, api.getContentProps(), this.mid);
330
334
  this.project(api);
335
+ if (api.open && !this.shown) this.announce(true);
331
336
  });
332
337
  }
333
338
  get api() {
334
339
  return this.ctrl.connect(this.handle.service);
335
340
  }
341
+ announce(open2) {
342
+ this.shown = open2;
343
+ announceSurface(this.list, open2, "menu", this.opener);
344
+ }
336
345
  setItems(items) {
337
346
  this.items = items;
338
347
  for (const sub of this.subs.values()) sub.destroy();
@@ -350,6 +359,7 @@ var Surface = class _Surface {
350
359
  for (const sub of this.subs.values()) sub.destroy();
351
360
  this.subs.clear();
352
361
  this.handle.stop();
362
+ if (this.shown) this.announce(false);
353
363
  this.positioner.remove();
354
364
  }
355
365
  byId(id) {
@@ -443,6 +453,7 @@ var Surface = class _Surface {
443
453
  const sub = new _Surface(doc, {
444
454
  host: this.host,
445
455
  placement: "right-start",
456
+ opener: li,
446
457
  onSelect: this.onSelect,
447
458
  onFavorite: this.onFavorite,
448
459
  checkmarks: this.checkmarks,
@@ -662,16 +673,21 @@ var Popover = class {
662
673
  this.hide();
663
674
  };
664
675
  const onReflow = () => this.place();
665
- setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
676
+ setTimeout(() => {
677
+ if (this.onOutside === onOutside) document.addEventListener("pointerdown", onOutside, true);
678
+ }, 0);
666
679
  document.addEventListener("keydown", onKey, true);
667
680
  window.addEventListener("resize", onReflow, true);
668
681
  document.addEventListener("scroll", onReflow, true);
669
682
  this.onOutside = onOutside;
670
683
  this.onKey = onKey;
671
684
  this.onReflow = onReflow;
685
+ announceSurface(this.el, true, "popover", this.trigger);
672
686
  }
673
687
  hide() {
674
688
  if (!this.shown) return;
689
+ this.shown = false;
690
+ announceSurface(this.el, false, "popover", this.trigger);
675
691
  if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
676
692
  if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
677
693
  if (this.onReflow) {
@@ -691,7 +707,6 @@ var Popover = class {
691
707
  } else {
692
708
  this.el.remove();
693
709
  }
694
- this.shown = false;
695
710
  if (open === this) open = null;
696
711
  this.ctrl.onClose?.();
697
712
  }
@@ -1377,6 +1392,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
1377
1392
  `;
1378
1393
  var Dialog = class {
1379
1394
  constructor(opts = {}) {
1395
+ this.opener = null;
1396
+ this.wasOpen = false;
1380
1397
  const doc = (opts.host ?? document.body).ownerDocument;
1381
1398
  injectStyles(DIALOG_STYLE_ID, DIALOG_CSS, doc);
1382
1399
  const host = opts.host ?? doc.body;
@@ -1473,6 +1490,7 @@ var Dialog = class {
1473
1490
  const mid = String(this.ctrl.props.id);
1474
1491
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
1475
1492
  const api = this.ctrl.connect(service);
1493
+ if (this.wasOpen && !api.open) this.announce(false);
1476
1494
  spreadProps(this.backdrop, api.getBackdropProps(), mid);
1477
1495
  spreadProps(this.positioner, api.getPositionerProps(), mid);
1478
1496
  spreadProps(this.panel, api.getContentProps(), mid);
@@ -1480,12 +1498,27 @@ var Dialog = class {
1480
1498
  spreadProps(close, api.getCloseTriggerProps(), mid);
1481
1499
  this.backdrop.style.display = api.open ? "" : "none";
1482
1500
  this.positioner.style.display = api.open ? "" : "none";
1501
+ if (api.open && !this.wasOpen) this.announce(true);
1502
+ if (this.wasOpen && !api.open) this.restoreOpener();
1503
+ this.wasOpen = api.open;
1483
1504
  });
1484
1505
  }
1506
+ /** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
1507
+ * teardown that outruns the machine's close notification). */
1508
+ restoreOpener() {
1509
+ const opener = this.opener;
1510
+ this.opener = null;
1511
+ if (opener?.isConnected) opener.focus({ preventScroll: true });
1512
+ }
1513
+ announce(open2) {
1514
+ const opener = this.opener === this.panel.ownerDocument.body ? null : this.opener;
1515
+ announceSurface(this.panel, open2, "dialog", opener);
1516
+ }
1485
1517
  get open() {
1486
1518
  return this.ctrl.connect(this.handle.service).open;
1487
1519
  }
1488
1520
  show() {
1521
+ this.opener = this.panel.ownerDocument.activeElement;
1489
1522
  this.ctrl.connect(this.handle.service).setOpen(true);
1490
1523
  }
1491
1524
  hide() {
@@ -1500,6 +1533,11 @@ var Dialog = class {
1500
1533
  destroy() {
1501
1534
  if (this.open) this.hide();
1502
1535
  this.handle.stop();
1536
+ if (this.wasOpen) {
1537
+ this.wasOpen = false;
1538
+ this.announce(false);
1539
+ }
1540
+ this.restoreOpener();
1503
1541
  this.backdrop.remove();
1504
1542
  this.positioner.remove();
1505
1543
  }
@@ -2506,14 +2544,49 @@ function buildColorPicker(color, theme, onChange, options = {}) {
2506
2544
  const knob = document.createElement("div");
2507
2545
  knob.style.cssText = "position:absolute;top:50%;width:15px;height:15px;border-radius:50%;background:var(--vela-selected-bg);box-shadow:0 1px 3px rgba(0,0,0,0.55);transform:translate(-50%,-50%);pointer-events:none;";
2508
2546
  track.appendChild(knob);
2509
- const pctBox = document.createElement("div");
2510
- pctBox.style.cssText = `min-width:42px;text-align:center;font:var(--vela-font-size-md) ${theme.fontFamily};color:var(--vela-fg);border:1px solid var(--vela-border);border-radius:5px;padding:3px 4px;`;
2547
+ const pctBox = document.createElement("input");
2548
+ pctBox.type = "text";
2549
+ pctBox.inputMode = "numeric";
2550
+ pctBox.title = "Opacity (%)";
2551
+ pctBox.style.cssText = `width:46px;box-sizing:border-box;text-align:center;font:var(--vela-font-size-md) ${theme.fontFamily};color:var(--vela-fg);background:transparent;border:1px solid var(--vela-border);border-radius:5px;padding:3px 4px;outline:none;`;
2552
+ pctBox.addEventListener("focus", () => {
2553
+ pctBox.style.borderColor = "var(--vela-border-strong)";
2554
+ pctBox.select();
2555
+ });
2511
2556
  opRow.append(track, pctBox);
2512
2557
  const paintOpacity = () => {
2513
2558
  track.style.background = `linear-gradient(to right, ${curHex}00, ${curHex}ff), ${CHECKER}`;
2514
2559
  knob.style.left = `${curAlpha * 100}%`;
2515
- pctBox.textContent = `${Math.round(curAlpha * 100)}%`;
2560
+ pctBox.value = `${Math.round(curAlpha * 100)}%`;
2516
2561
  };
2562
+ const commitTyped = () => {
2563
+ pctBox.style.borderColor = "var(--vela-border)";
2564
+ const n = parseFloat(pctBox.value.replace("%", ""));
2565
+ if (Number.isFinite(n)) {
2566
+ const next = Math.max(0, Math.min(100, n)) / 100;
2567
+ if (Math.round(next * 100) !== Math.round(curAlpha * 100)) {
2568
+ curAlpha = next;
2569
+ paintOpacity();
2570
+ emit();
2571
+ return;
2572
+ }
2573
+ }
2574
+ paintOpacity();
2575
+ };
2576
+ pctBox.addEventListener("change", commitTyped);
2577
+ pctBox.addEventListener("blur", () => {
2578
+ pctBox.style.borderColor = "var(--vela-border)";
2579
+ paintOpacity();
2580
+ });
2581
+ pctBox.addEventListener("keydown", (e) => {
2582
+ if (e.key === "Escape") return;
2583
+ e.stopPropagation();
2584
+ if (e.key === "Enter") {
2585
+ e.preventDefault();
2586
+ commitTyped();
2587
+ pctBox.blur();
2588
+ }
2589
+ });
2517
2590
  let dragging = false;
2518
2591
  const onDrag = (clientX) => {
2519
2592
  const r = track.getBoundingClientRect();