@luxalgo/vela 0.8.1 → 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 (48) hide show
  1. package/README.md +1 -1
  2. package/dist/{DataProvider-BrlxntPN.d.cts → DataProvider-COGDs9Xg.d.cts} +1 -1
  3. package/dist/{DataProvider-DwFIoV4r.d.ts → DataProvider-DpOWZD8F.d.ts} +1 -1
  4. package/dist/{chunk-LMBYEGUR.js → chunk-27UTYE7T.js} +25 -2
  5. package/dist/{chunk-EZ5FWVLA.js → chunk-DPAA3SRT.js} +94 -13
  6. package/dist/{chunk-BZQM2XO7.js → chunk-HKNO24GD.js} +13 -1
  7. package/dist/{chunk-NELQJCGK.js → chunk-NLDTVFOI.js} +41 -3
  8. package/dist/{chunk-DBDXKZUF.js → chunk-R2JMNIHY.js} +53 -13
  9. package/dist/{chunk-ATGTCWHJ.js → chunk-T3DZIR5Y.js} +171 -32
  10. package/dist/{contributions-tjNExo1o.d.ts → contributions-Cgm9RJYP.d.cts} +38 -11
  11. package/dist/{contributions-CtlptxR8.d.cts → contributions-bWgwyiVi.d.ts} +38 -11
  12. package/dist/index.cjs +239 -37
  13. package/dist/index.d.cts +8 -8
  14. package/dist/index.d.ts +8 -8
  15. package/dist/index.js +4 -4
  16. package/dist/{options-BnEPUuVV.d.ts → options-NAUHiQEC.d.cts} +3 -3
  17. package/dist/{options-BnEPUuVV.d.cts → options-NAUHiQEC.d.ts} +3 -3
  18. package/dist/{plugin-BWxJRKzS.d.cts → plugin-CjjzZX2F.d.cts} +4 -4
  19. package/dist/{plugin-e2WUEtiI.d.ts → plugin-CqxWoDKS.d.ts} +4 -4
  20. package/dist/plugin.cjs +20 -6
  21. package/dist/plugin.d.cts +5 -5
  22. package/dist/plugin.d.ts +5 -5
  23. package/dist/plugin.js +2 -2
  24. package/dist/providers/binance.d.cts +2 -2
  25. package/dist/providers/binance.d.ts +2 -2
  26. package/dist/providers/coinbase.d.cts +2 -2
  27. package/dist/providers/coinbase.d.ts +2 -2
  28. package/dist/providers/hyperliquid.d.cts +2 -2
  29. package/dist/providers/hyperliquid.d.ts +2 -2
  30. package/dist/{side-panel-HF0IAzwf.d.cts → side-panel-wMNfgyZb.d.cts} +13 -0
  31. package/dist/{side-panel-HF0IAzwf.d.ts → side-panel-wMNfgyZb.d.ts} +13 -0
  32. package/dist/{statusline-model-zKXtGIjj.d.ts → statusline-model-Dih6FGWP.d.ts} +3 -3
  33. package/dist/{statusline-model-DFY7tuHj.d.cts → statusline-model-Dpfpq54A.d.cts} +3 -3
  34. package/dist/ui.cjs +77 -2
  35. package/dist/ui.d.cts +28 -2
  36. package/dist/ui.d.ts +28 -2
  37. package/dist/ui.js +3 -3
  38. package/dist/vela.global.js +239 -37
  39. package/dist/vela.global.min.js +52 -52
  40. package/dist/widget.cjs +383 -50
  41. package/dist/widget.d.cts +20 -10
  42. package/dist/widget.d.ts +20 -10
  43. package/dist/widget.js +7 -7
  44. package/dist/workspace.cjs +383 -50
  45. package/dist/workspace.d.cts +5 -5
  46. package/dist/workspace.d.ts +5 -5
  47. package/dist/workspace.js +6 -6
  48. package/package.json +2 -2
@@ -1256,6 +1256,7 @@ registerIcon("expand", S('<rect x="2.2" y="2.2" width="11.6" height="11.6" rx="1
1256
1256
  registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
1257
1257
  registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
1258
1258
  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"/>'));
1259
+ 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"/>'));
1259
1260
  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"/>'));
1260
1261
  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"/>'));
1261
1262
  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"'));
@@ -1337,6 +1338,17 @@ function nextUid(prefix) {
1337
1338
  return `${prefix}-${++uid}`;
1338
1339
  }
1339
1340
 
1341
+ // src/ui/surface-events.ts
1342
+ var SURFACE_OPEN_EVENT = "vela:surface-open";
1343
+ var SURFACE_CLOSE_EVENT = "vela:surface-close";
1344
+ function announceSurface(el, open2, kind, trigger = null) {
1345
+ const View = el.ownerDocument.defaultView;
1346
+ if (!View) return;
1347
+ el.dispatchEvent(
1348
+ new View.CustomEvent(open2 ? SURFACE_OPEN_EVENT : SURFACE_CLOSE_EVENT, { bubbles: true, composed: true, detail: { kind, trigger } })
1349
+ );
1350
+ }
1351
+
1340
1352
  // src/ui/keymap.ts
1341
1353
  var KEY_ALIASES = {
1342
1354
  esc: "escape",
@@ -1698,6 +1710,8 @@ var Surface = class _Surface {
1698
1710
  /** Trigger rect captured when this level opened — the list stays put if the
1699
1711
  * trigger then moves (favorite chips shifting the caret). */
1700
1712
  this.pinnedAnchor = null;
1713
+ /** The open state last announced — see {@link announce}. */
1714
+ this.shown = false;
1701
1715
  this.doc = doc;
1702
1716
  this.host = opts.host;
1703
1717
  this.onSelect = opts.onSelect;
@@ -1711,6 +1725,7 @@ var Surface = class _Surface {
1711
1725
  this.positioner.appendChild(this.list);
1712
1726
  this.host.appendChild(this.positioner);
1713
1727
  const trigger = opts.trigger;
1728
+ this.opener = trigger ?? opts.opener ?? null;
1714
1729
  this.ctrl = menuController({
1715
1730
  items: [],
1716
1731
  id: opts.id,
@@ -1732,15 +1747,21 @@ var Surface = class _Surface {
1732
1747
  if (opts.triggerId && trigger) trigger.id = opts.triggerId;
1733
1748
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
1734
1749
  const api = this.ctrl.connect(service);
1750
+ if (this.shown && !api.open) this.announce(false);
1735
1751
  if (trigger) vanilla.spreadProps(trigger, api.getTriggerProps(), this.mid);
1736
1752
  vanilla.spreadProps(this.positioner, api.getPositionerProps(), this.mid);
1737
1753
  vanilla.spreadProps(this.list, api.getContentProps(), this.mid);
1738
1754
  this.project(api);
1755
+ if (api.open && !this.shown) this.announce(true);
1739
1756
  });
1740
1757
  }
1741
1758
  get api() {
1742
1759
  return this.ctrl.connect(this.handle.service);
1743
1760
  }
1761
+ announce(open2) {
1762
+ this.shown = open2;
1763
+ announceSurface(this.list, open2, "menu", this.opener);
1764
+ }
1744
1765
  setItems(items) {
1745
1766
  this.items = items;
1746
1767
  for (const sub2 of this.subs.values()) sub2.destroy();
@@ -1758,6 +1779,7 @@ var Surface = class _Surface {
1758
1779
  for (const sub2 of this.subs.values()) sub2.destroy();
1759
1780
  this.subs.clear();
1760
1781
  this.handle.stop();
1782
+ if (this.shown) this.announce(false);
1761
1783
  this.positioner.remove();
1762
1784
  }
1763
1785
  byId(id) {
@@ -1851,6 +1873,7 @@ var Surface = class _Surface {
1851
1873
  const sub2 = new _Surface(doc, {
1852
1874
  host: this.host,
1853
1875
  placement: "right-start",
1876
+ opener: li,
1854
1877
  onSelect: this.onSelect,
1855
1878
  onFavorite: this.onFavorite,
1856
1879
  checkmarks: this.checkmarks,
@@ -2070,16 +2093,21 @@ var Popover = class {
2070
2093
  this.hide();
2071
2094
  };
2072
2095
  const onReflow = () => this.place();
2073
- setTimeout(() => document.addEventListener("pointerdown", onOutside, true), 0);
2096
+ setTimeout(() => {
2097
+ if (this.onOutside === onOutside) document.addEventListener("pointerdown", onOutside, true);
2098
+ }, 0);
2074
2099
  document.addEventListener("keydown", onKey, true);
2075
2100
  window.addEventListener("resize", onReflow, true);
2076
2101
  document.addEventListener("scroll", onReflow, true);
2077
2102
  this.onOutside = onOutside;
2078
2103
  this.onKey = onKey;
2079
2104
  this.onReflow = onReflow;
2105
+ announceSurface(this.el, true, "popover", this.trigger);
2080
2106
  }
2081
2107
  hide() {
2082
2108
  if (!this.shown) return;
2109
+ this.shown = false;
2110
+ announceSurface(this.el, false, "popover", this.trigger);
2083
2111
  if (this.onOutside) document.removeEventListener("pointerdown", this.onOutside, true);
2084
2112
  if (this.onKey) document.removeEventListener("keydown", this.onKey, true);
2085
2113
  if (this.onReflow) {
@@ -2099,7 +2127,6 @@ var Popover = class {
2099
2127
  } else {
2100
2128
  this.el.remove();
2101
2129
  }
2102
- this.shown = false;
2103
2130
  if (open === this) open = null;
2104
2131
  this.ctrl.onClose?.();
2105
2132
  }
@@ -2305,6 +2332,8 @@ ${overlayScrollbarCss(".vela-dialog *")}
2305
2332
  `;
2306
2333
  var Dialog = class {
2307
2334
  constructor(opts = {}) {
2335
+ this.opener = null;
2336
+ this.wasOpen = false;
2308
2337
  const doc = (opts.host ?? document.body).ownerDocument;
2309
2338
  injectStyles(DIALOG_STYLE_ID, DIALOG_CSS, doc);
2310
2339
  const host = opts.host ?? doc.body;
@@ -2401,6 +2430,7 @@ var Dialog = class {
2401
2430
  const mid = String(this.ctrl.props.id);
2402
2431
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
2403
2432
  const api = this.ctrl.connect(service);
2433
+ if (this.wasOpen && !api.open) this.announce(false);
2404
2434
  vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
2405
2435
  vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
2406
2436
  vanilla.spreadProps(this.panel, api.getContentProps(), mid);
@@ -2408,12 +2438,27 @@ var Dialog = class {
2408
2438
  vanilla.spreadProps(close, api.getCloseTriggerProps(), mid);
2409
2439
  this.backdrop.style.display = api.open ? "" : "none";
2410
2440
  this.positioner.style.display = api.open ? "" : "none";
2441
+ if (api.open && !this.wasOpen) this.announce(true);
2442
+ if (this.wasOpen && !api.open) this.restoreOpener();
2443
+ this.wasOpen = api.open;
2411
2444
  });
2412
2445
  }
2446
+ /** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
2447
+ * teardown that outruns the machine's close notification). */
2448
+ restoreOpener() {
2449
+ const opener = this.opener;
2450
+ this.opener = null;
2451
+ if (opener?.isConnected) opener.focus({ preventScroll: true });
2452
+ }
2453
+ announce(open2) {
2454
+ const opener = this.opener === this.panel.ownerDocument.body ? null : this.opener;
2455
+ announceSurface(this.panel, open2, "dialog", opener);
2456
+ }
2413
2457
  get open() {
2414
2458
  return this.ctrl.connect(this.handle.service).open;
2415
2459
  }
2416
2460
  show() {
2461
+ this.opener = this.panel.ownerDocument.activeElement;
2417
2462
  this.ctrl.connect(this.handle.service).setOpen(true);
2418
2463
  }
2419
2464
  hide() {
@@ -2428,6 +2473,11 @@ var Dialog = class {
2428
2473
  destroy() {
2429
2474
  if (this.open) this.hide();
2430
2475
  this.handle.stop();
2476
+ if (this.wasOpen) {
2477
+ this.wasOpen = false;
2478
+ this.announce(false);
2479
+ }
2480
+ this.restoreOpener();
2431
2481
  this.backdrop.remove();
2432
2482
  this.positioner.remove();
2433
2483
  }
@@ -2556,6 +2606,8 @@ function swipeDirection(dx, dy) {
2556
2606
  }
2557
2607
  var Drawer = class {
2558
2608
  constructor(opts = {}) {
2609
+ this.opener = null;
2610
+ this.wasOpen = false;
2559
2611
  const doc = (opts.host ?? document.body).ownerDocument;
2560
2612
  injectStyles(DRAWER_STYLE_ID, DRAWER_CSS, doc);
2561
2613
  const host = opts.host ?? doc.body;
@@ -2583,12 +2635,16 @@ var Drawer = class {
2583
2635
  const mid = String(this.ctrl.props.id);
2584
2636
  this.handle = runMachine(this.ctrl.machine, this.ctrl.props, (service) => {
2585
2637
  const api = this.ctrl.connect(service);
2638
+ if (this.wasOpen && !api.open) this.announce(false);
2586
2639
  vanilla.spreadProps(this.backdrop, api.getBackdropProps(), mid);
2587
2640
  vanilla.spreadProps(this.positioner, api.getPositionerProps(), mid);
2588
2641
  vanilla.spreadProps(this.panel, api.getContentProps(), mid);
2589
2642
  vanilla.spreadProps(this.titleEl, api.getTitleProps(), mid);
2590
2643
  this.backdrop.style.display = api.open ? "" : "none";
2591
2644
  this.positioner.style.display = api.open ? "" : "none";
2645
+ if (api.open && !this.wasOpen) this.announce(true);
2646
+ if (this.wasOpen && !api.open) this.restoreOpener();
2647
+ this.wasOpen = api.open;
2592
2648
  });
2593
2649
  }
2594
2650
  /** Any element between `from` and the panel that has already been scrolled down —
@@ -2697,10 +2753,22 @@ var Drawer = class {
2697
2753
  setTitle(title) {
2698
2754
  this.titleEl.textContent = title;
2699
2755
  }
2756
+ /** Closing hands focus back to whatever opened it (Escape, the ✕, hide(), or a
2757
+ * teardown that outruns the machine's close notification). */
2758
+ restoreOpener() {
2759
+ const opener = this.opener;
2760
+ this.opener = null;
2761
+ if (opener?.isConnected) opener.focus({ preventScroll: true });
2762
+ }
2763
+ announce(open2) {
2764
+ const opener = this.opener === this.panel.ownerDocument.body ? null : this.opener;
2765
+ announceSurface(this.panel, open2, "drawer", opener);
2766
+ }
2700
2767
  get open() {
2701
2768
  return this.ctrl.connect(this.handle.service).open;
2702
2769
  }
2703
2770
  show() {
2771
+ this.opener = this.panel.ownerDocument.activeElement;
2704
2772
  this.ctrl.connect(this.handle.service).setOpen(true);
2705
2773
  }
2706
2774
  hide() {
@@ -2708,6 +2776,11 @@ var Drawer = class {
2708
2776
  }
2709
2777
  destroy() {
2710
2778
  this.handle.stop();
2779
+ if (this.wasOpen) {
2780
+ this.wasOpen = false;
2781
+ this.announce(false);
2782
+ }
2783
+ this.restoreOpener();
2711
2784
  this.backdrop.remove();
2712
2785
  this.positioner.remove();
2713
2786
  }
@@ -4998,6 +5071,7 @@ var LayoutPicker = class {
4998
5071
  const panel = doc.createElement("div");
4999
5072
  panel.className = "vela-lp";
5000
5073
  this.layer.appendChild(panel);
5074
+ this.panel = panel;
5001
5075
  const cols = doc.createElement("div");
5002
5076
  cols.className = "vela-lp-cols";
5003
5077
  panel.appendChild(cols);
@@ -5079,10 +5153,12 @@ var LayoutPicker = class {
5079
5153
  this.doc.addEventListener("pointerdown", this.onDocPointerDown, true);
5080
5154
  this.doc.addEventListener("keydown", this.onDocKeydown, true);
5081
5155
  this.opts.onOpenChange?.(true);
5156
+ if (this.isOpen) announceSurface(this.panel, true, "popover", this.opts.trigger);
5082
5157
  }
5083
5158
  close() {
5084
5159
  if (!this.isOpen) return;
5085
5160
  this.isOpen = false;
5161
+ announceSurface(this.panel, false, "popover", this.opts.trigger);
5086
5162
  this.layer.style.display = "none";
5087
5163
  this.opts.trigger.setAttribute("aria-expanded", "false");
5088
5164
  this.doc.removeEventListener("pointerdown", this.onDocPointerDown, true);
@@ -5288,10 +5364,12 @@ function legendActions() {
5288
5364
  }
5289
5365
  function legendActionsProviderFor(chart, context) {
5290
5366
  return (indicatorId) => {
5291
- const handle = chart.indicators().find((h) => h.id === indicatorId);
5367
+ const find = () => chart.indicators().find((h) => h.id === indicatorId);
5368
+ const infoOf = (h) => ({ id: h.id, title: h.title, ...h.source !== void 0 ? { source: h.source } : {} });
5369
+ const handle = find();
5292
5370
  if (!handle) return [];
5293
- const info = { id: handle.id, title: handle.title, ...handle.source !== void 0 ? { source: handle.source } : {} };
5294
- 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) }));
5371
+ const info = infoOf(handle);
5372
+ 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)) }));
5295
5373
  };
5296
5374
  }
5297
5375
  var calloutRegistry = /* @__PURE__ */ new Map();
@@ -9489,6 +9567,10 @@ var FibRatios = class extends Drawing {
9489
9567
 
9490
9568
  // src/core/drawings/types/FibLevels.ts
9491
9569
  var FibLevels = class extends FibRatios {
9570
+ /** Price of a level for the two anchor prices; subclasses override to change which anchor ratio 0 sits on. */
9571
+ levelPrice(ratio, p1, p2) {
9572
+ return p1 + ratio * (p2 - p1);
9573
+ }
9492
9574
  /** Per-level pixel line + price for the ENABLED levels, spanning the anchors' time range. */
9493
9575
  levelLines(proj) {
9494
9576
  const a = this.anchors[0];
@@ -9498,11 +9580,10 @@ var FibLevels = class extends FibRatios {
9498
9580
  const xb = proj.xOf(b.time);
9499
9581
  const x1 = Math.min(xa, xb);
9500
9582
  const x2 = Math.max(xa, xb);
9501
- const delta = b.price - a.price;
9502
9583
  const out = [];
9503
9584
  for (const lv of this.levels) {
9504
9585
  if (!lv.enabled) continue;
9505
- const price = a.price + lv.ratio * delta;
9586
+ const price = this.levelPrice(lv.ratio, a.price, b.price);
9506
9587
  const y = proj.yOf(price, this.paneId);
9507
9588
  if (y == null) continue;
9508
9589
  out.push({ ratio: lv.ratio, color: lv.color, label: lv.label, price, x1, x2, y });
@@ -9542,8 +9623,7 @@ var FibLevels = class extends FibRatios {
9542
9623
  const a = this.anchors[0];
9543
9624
  const b = this.anchors[1];
9544
9625
  if (!a || !b) return null;
9545
- const delta = b.price - a.price;
9546
- const prices = this.levels.filter((l) => l.enabled).map((l) => a.price + l.ratio * delta);
9626
+ const prices = this.levels.filter((l) => l.enabled).map((l) => this.levelPrice(l.ratio, a.price, b.price));
9547
9627
  if (prices.length === 0) return null;
9548
9628
  return { min: Math.min(...prices), max: Math.max(...prices) };
9549
9629
  }
@@ -9629,13 +9709,24 @@ function cycleLevels(count, enabledCount = count) {
9629
9709
  // src/core/drawings/types/FibRetracement.ts
9630
9710
  var LEVELS = fibLevels([0, 0.236, 0.382, 0.5, 0.618, 0.786, 1]);
9631
9711
  var FibRetracement = class extends FibLevels {
9632
- constructor() {
9633
- super(...arguments);
9712
+ constructor(init) {
9713
+ super(init);
9634
9714
  this.type = "fibretracement";
9715
+ if (this.reverse === void 0) this.reverse = false;
9635
9716
  }
9636
9717
  defaultLevels() {
9637
9718
  return LEVELS;
9638
9719
  }
9720
+ levelPrice(ratio, p1, p2) {
9721
+ return this.reverse ? p1 + ratio * (p2 - p1) : p2 + ratio * (p1 - p2);
9722
+ }
9723
+ writeProps() {
9724
+ return { ...super.writeProps(), reverse: this.reverse };
9725
+ }
9726
+ readProps(props) {
9727
+ super.readProps(props);
9728
+ this.reverse = typeof props.reverse === "boolean" ? props.reverse : true;
9729
+ }
9639
9730
  };
9640
9731
 
9641
9732
  // src/core/drawings/types/FibExtension.ts
@@ -12762,6 +12853,32 @@ var CSS5 = `
12762
12853
  .vela-panel-pin .vela-icon { width: 15px; height: 15px; }
12763
12854
  .vela-panel-pin:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
12764
12855
  .vela-panel-pin[data-on='1'] { color: var(--vela-fg-bright); }
12856
+ .vela-panel-max {
12857
+ all: unset;
12858
+ cursor: pointer;
12859
+ width: 26px;
12860
+ height: 26px;
12861
+ flex: none;
12862
+ display: inline-flex;
12863
+ align-items: center;
12864
+ justify-content: center;
12865
+ border-radius: 4px;
12866
+ color: var(--vela-fg-muted);
12867
+ }
12868
+ .vela-panel-max .vela-icon { width: 15px; height: 15px; }
12869
+ .vela-panel-max:hover { background: var(--vela-hover); color: var(--vela-fg-bright); }
12870
+ /* MAXIMIZED: the panel covers the whole dock host \u2014 every chart of the shell \u2014 whatever
12871
+ its placement; the width handle and the pin have nothing to do meanwhile. */
12872
+ .vela-panel[data-maximized] {
12873
+ position: absolute;
12874
+ inset: 0;
12875
+ width: auto;
12876
+ max-width: none;
12877
+ z-index: 26;
12878
+ border-left: none;
12879
+ }
12880
+ .vela-panel[data-maximized] .vela-panel-resizer,
12881
+ .vela-panel[data-maximized] .vela-panel-pin { display: none; }
12765
12882
  .vela-panel-body { flex: 1; overflow: auto; padding: 8px; }
12766
12883
  .vela-panel-body::-webkit-scrollbar { width: 8px; }
12767
12884
  .vela-panel-body::-webkit-scrollbar-thumb { background: var(--vela-scroll); border-radius: 4px; border: 2px solid transparent; background-clip: padding-box; }
@@ -12814,6 +12931,7 @@ var CSS5 = `
12814
12931
  border-left: none;
12815
12932
  }
12816
12933
  [data-layout='mobile'] .vela-panel-resizer { display: none; }
12934
+ [data-layout='mobile'] .vela-panel-max { display: none; }
12817
12935
  `;
12818
12936
  function clampPanelWidth(px, min = DEFAULT_PANEL_MIN_WIDTH, max = DEFAULT_PANEL_MAX_WIDTH) {
12819
12937
  const lo = Number.isFinite(min) && min > 0 ? min : DEFAULT_PANEL_MIN_WIDTH;
@@ -12837,6 +12955,10 @@ var SidePanel = class {
12837
12955
  * never on a programmatic {@link setOverlay}, for the same reason as widths. */
12838
12956
  this.onPlacementChange = null;
12839
12957
  this.pin = null;
12958
+ this.maxButton = null;
12959
+ this.maximizedOn = false;
12960
+ /** Set while the close is announced, so a listener's own close does not re-enter. */
12961
+ this.closing = false;
12840
12962
  const doc = host.ownerDocument;
12841
12963
  injectStyles(STYLE_ID4, CSS5, doc);
12842
12964
  this.minWidth = opts.resizable ? opts.minWidth ?? DEFAULT_PANEL_MIN_WIDTH : 1;
@@ -12871,7 +12993,13 @@ var SidePanel = class {
12871
12993
  });
12872
12994
  this.refreshPin();
12873
12995
  }
12874
- header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], close);
12996
+ if (opts.maximizable) {
12997
+ this.maxButton = doc.createElement("button");
12998
+ this.maxButton.className = "vela-panel-max";
12999
+ this.maxButton.addEventListener("click", () => this.setMaximized(!this.maximizedOn));
13000
+ this.refreshMaxButton();
13001
+ }
13002
+ header.append(this.heading, this.slot, ...this.pin ? [this.pin] : [], ...this.maxButton ? [this.maxButton] : [], close);
12875
13003
  this.body = doc.createElement("div");
12876
13004
  this.body.className = "vela-panel-body";
12877
13005
  this.el.append(header, this.body);
@@ -12883,9 +13011,37 @@ var SidePanel = class {
12883
13011
  }
12884
13012
  /** Open/close the panel — a bare call flips it. */
12885
13013
  toggle(open2 = this.el.hidden) {
12886
- if (open2 === !this.el.hidden) return;
13014
+ if (open2 === !this.el.hidden || !open2 && this.closing) return;
13015
+ if (!open2) {
13016
+ this.closing = true;
13017
+ announceSurface(this.el, false, "panel");
13018
+ this.closing = false;
13019
+ }
12887
13020
  this.el.hidden = !open2;
13021
+ if (!open2) this.setMaximized(false);
12888
13022
  this.onOpenChange?.(open2);
13023
+ if (open2 && !this.el.hidden) announceSurface(this.el, true, "panel");
13024
+ }
13025
+ /** Whether the panel covers every chart right now. */
13026
+ get maximized() {
13027
+ return this.maximizedOn;
13028
+ }
13029
+ /** Maximize the panel over the shell's charts, or restore it. A no-op on a panel not
13030
+ * declared `maximizable`. */
13031
+ setMaximized(maximized) {
13032
+ if (!this.maxButton || maximized === this.maximizedOn) return;
13033
+ this.maximizedOn = maximized;
13034
+ if (maximized) this.el.dataset.maximized = "1";
13035
+ else delete this.el.dataset.maximized;
13036
+ this.refreshMaxButton();
13037
+ }
13038
+ refreshMaxButton() {
13039
+ if (!this.maxButton) return;
13040
+ const doc = this.maxButton.ownerDocument;
13041
+ this.maxButton.replaceChildren(iconEl(this.maximizedOn ? "restore" : "maximize", doc));
13042
+ this.maxButton.title = this.maximizedOn ? "Restore" : "Maximize";
13043
+ this.maxButton.setAttribute("aria-label", this.maxButton.title);
13044
+ this.maxButton.setAttribute("aria-pressed", this.maximizedOn ? "true" : "false");
12889
13045
  }
12890
13046
  /** The scrolling body, for a panel filled from OUTSIDE the class — a contributed panel's
12891
13047
  * `mount` receives exactly this element. Subclasses use the protected `body`. */
@@ -12937,6 +13093,7 @@ var SidePanel = class {
12937
13093
  this.pin.setAttribute("aria-pressed", pinned ? "true" : "false");
12938
13094
  }
12939
13095
  destroy() {
13096
+ if (this.open) announceSurface(this.el, false, "panel");
12940
13097
  this.el.remove();
12941
13098
  }
12942
13099
  /** The drag handle on the panel's inner (left) edge — the panel is docked right, so dragging
@@ -14353,19 +14510,22 @@ var PanelDock = class {
14353
14510
  /**
14354
14511
  * (Re)build the CONTRIBUTED panels from the registry — call once after the built-ins, and
14355
14512
  * again on `refreshActions()` so a late registration appears. Contributed panels that are
14356
- * gone from the registry are dropped; the ones still there are rebuilt, so a replaced
14357
- * descriptor takes effect.
14513
+ * gone from the registry are dropped and a REPLACED descriptor is rebuilt; a panel whose
14514
+ * descriptor is unchanged stays mounted as it is, so its content and state survive.
14358
14515
  */
14359
14516
  refresh() {
14360
14517
  const openBefore = this.openId;
14361
- for (const entry of [...this.entries]) if (entry.contributed) this.drop(entry);
14362
- for (const desc of sidePanels()) {
14518
+ const registered = sidePanels();
14519
+ for (const entry of [...this.entries]) if (entry.contributed && !registered.includes(entry.desc)) this.drop(entry);
14520
+ for (const desc of registered) {
14521
+ if (this.entries.some((e) => e.desc === desc)) continue;
14363
14522
  const panel = new SidePanel(this.host, desc.title, `vela-panel-${desc.id}`, {
14364
14523
  width: desc.width,
14365
14524
  resizable: desc.resizable,
14366
14525
  minWidth: desc.minWidth,
14367
14526
  maxWidth: desc.maxWidth,
14368
- overlay: desc.overlay
14527
+ overlay: desc.overlay,
14528
+ maximizable: desc.maximizable
14369
14529
  });
14370
14530
  const entry = {
14371
14531
  id: desc.id,
@@ -14373,10 +14533,19 @@ var PanelDock = class {
14373
14533
  icon: desc.icon,
14374
14534
  order: desc.order ?? DEFAULT_PANEL_ORDER,
14375
14535
  panel,
14376
- contributed: true
14536
+ contributed: true,
14537
+ desc
14377
14538
  };
14378
14539
  try {
14379
- entry.handle = desc.mount(this.deps.context(), panel.content, { slot: panel.headerSlot, setTitle: (t) => panel.setTitle(t) }) ?? void 0;
14540
+ const header = {
14541
+ slot: panel.headerSlot,
14542
+ setTitle: (t) => panel.setTitle(t),
14543
+ get maximized() {
14544
+ return panel.maximized;
14545
+ },
14546
+ setMaximized: (on) => panel.setMaximized(on)
14547
+ };
14548
+ entry.handle = desc.mount(this.deps.context(), panel.content, header) ?? void 0;
14380
14549
  if (this.chart) entry.handle?.onChart?.(this.chart);
14381
14550
  } catch (err) {
14382
14551
  console.warn(`[vela] side panel "${desc.id}" failed to mount`, err);
@@ -18119,6 +18288,7 @@ var DrawingToolbar = class {
18119
18288
  );
18120
18289
  }
18121
18290
  }
18291
+ announceSurface(fly, true, "menu", cell);
18122
18292
  }
18123
18293
  openMagnetFlyout() {
18124
18294
  const cell = this.magnetCell;
@@ -18142,6 +18312,7 @@ var DrawingToolbar = class {
18142
18312
  })
18143
18313
  );
18144
18314
  }
18315
+ announceSurface(fly, true, "menu", cell);
18145
18316
  }
18146
18317
  flyoutHeader(text) {
18147
18318
  const t = this.theme;
@@ -18210,9 +18381,11 @@ var DrawingToolbar = class {
18210
18381
  }
18211
18382
  closeFlyout() {
18212
18383
  this.starEls.clear();
18213
- if (this.flyout) {
18214
- this.flyout.remove();
18384
+ const fly = this.flyout;
18385
+ if (fly) {
18215
18386
  this.flyout = null;
18387
+ announceSurface(fly, false, "menu", this.flyoutCell);
18388
+ fly.remove();
18216
18389
  this.flyoutCell?.classList.remove("vela-open");
18217
18390
  this.flyoutCell = null;
18218
18391
  this.flyoutOwnerId = null;
@@ -18881,7 +19054,7 @@ var IndicatorHandleImpl = class {
18881
19054
  this.controller.applyProps(this.id, values);
18882
19055
  }
18883
19056
  updateCode(source) {
18884
- this.controller.updateCode(this.id, source);
19057
+ return this.controller.updateCode(this.id, source);
18885
19058
  }
18886
19059
  setVisible(visible) {
18887
19060
  this.controller.setVisible(this.id, visible);
@@ -20744,20 +20917,26 @@ var EngineOrchestrator = class _EngineOrchestrator {
20744
20917
  /**
20745
20918
  * IndicatorController: replace a SCRIPT indicator's source in place (see
20746
20919
  * {@link IndicatorHandle.updateCode}). Prepare-first: the running session keeps
20747
- * computing until the new source has compiled, so a broken edit costs the user
20748
- * nothing but an `error` event. Natives have no script — warn and leave them alone.
20920
+ * computing until the new source has compiled; then the new code runs on TRIAL until
20921
+ * its first model lands, and a failure before that puts the previous code back.
20922
+ * Natives have no script — warn and leave them alone.
20749
20923
  */
20750
20924
  updateCode(id, source) {
20751
20925
  const record = this.registry.get(id);
20752
20926
  const handle = this.handles.get(id);
20753
- if (!record || !handle) return;
20927
+ if (!record || !handle) return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator is no longer on the chart.`) });
20754
20928
  if (record.native) {
20755
20929
  console.warn(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`);
20756
- return;
20930
+ return Promise.resolve({ ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 a native indicator has no script to update.`) });
20757
20931
  }
20758
- if (source === record.source && record.pendingSource === void 0) return;
20759
- record.pendingSource = source;
20760
- void this.swapSource(id, source, handle);
20932
+ const busy = record.pendingSource !== void 0 || record.codeTrial !== void 0;
20933
+ if (!busy && source === record.source) return Promise.resolve({ ok: true, error: null });
20934
+ return new Promise((resolve) => {
20935
+ (record.codeWaiters ?? (record.codeWaiters = [])).push(resolve);
20936
+ if (record.pendingSource === source || record.pendingSource === void 0 && record.codeTrial && record.source === source) return;
20937
+ record.pendingSource = source;
20938
+ void this.swapSource(id, source, handle);
20939
+ });
20761
20940
  }
20762
20941
  async swapSource(id, source, handle) {
20763
20942
  const engine = this.registry.get(id)?.engine ?? this.engineFor(this.registry.get(id)?.options?.language);
@@ -20768,26 +20947,41 @@ var EngineOrchestrator = class _EngineOrchestrator {
20768
20947
  const record2 = this.registry.get(id);
20769
20948
  if (record2?.pendingSource === source) {
20770
20949
  record2.pendingSource = void 0;
20771
- this.fail(id, handle, err);
20950
+ this.rejectCode(id, record2, handle, err);
20772
20951
  }
20773
20952
  return;
20774
20953
  }
20775
20954
  const record = this.registry.get(id);
20776
20955
  if (!record || record.pendingSource !== source) return;
20777
20956
  record.pendingSource = void 0;
20957
+ const previous = record.codeTrial?.previous ?? {
20958
+ source: record.source,
20959
+ engine: record.engine,
20960
+ prepared: record.prepared,
20961
+ inputValues: record.inputValues,
20962
+ propValues: record.propValues,
20963
+ inputs: [...handle.inputs],
20964
+ props: [...handle.props]
20965
+ };
20966
+ record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues, record.prepared?.inputs);
20967
+ record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues, record.prepared?.props);
20778
20968
  record.source = source;
20779
20969
  record.engine = engine;
20780
20970
  record.prepared = prepared;
20781
20971
  handle.setSource(source);
20782
- record.inputValues = valuesOnSchema(prepared.inputs, record.inputValues);
20783
- record.propValues = valuesOnSchema(prepared.props ?? [], record.propValues);
20784
20972
  handle.setSchema(prepared.inputs);
20785
20973
  handle.setPropsSchema(prepared.props ?? []);
20786
20974
  record.session?.stop();
20787
20975
  record.session = void 0;
20788
20976
  record.pendingStructural = true;
20789
20977
  record.pendingCause = "code";
20790
- if (record.hidden) return;
20978
+ if (record.hidden) {
20979
+ record.codeTrial = void 0;
20980
+ if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
20981
+ this.settleCode(record, { ok: true, error: null });
20982
+ return;
20983
+ }
20984
+ record.codeTrial = { previous };
20791
20985
  if (record.renderHandle) {
20792
20986
  this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
20793
20987
  this.setLoading(record, true);
@@ -20795,6 +20989,46 @@ var EngineOrchestrator = class _EngineOrchestrator {
20795
20989
  this.mountLoadingPlaceholder(id, record);
20796
20990
  }
20797
20991
  this.executeIndicator(id, handle);
20992
+ if (this.bars.length === 0 && record.codeTrial) {
20993
+ record.codeTrial = void 0;
20994
+ this.settleCode(record, { ok: true, error: null });
20995
+ }
20996
+ }
20997
+ /** Resolve every `updateCode` caller waiting on this record's chain of edits. */
20998
+ settleCode(record, result) {
20999
+ const waiters = record.codeWaiters;
21000
+ record.codeWaiters = void 0;
21001
+ if (waiters) for (const resolve of waiters) resolve(result);
21002
+ }
21003
+ /**
21004
+ * An update failed — at prepare, or on trial before its first model. A trial puts the
21005
+ * previous code back (source, engine, prepared script, values, schema) and restarts it;
21006
+ * its visuals were never dropped, so the chart keeps painting throughout. Then the
21007
+ * failure reports the usual way and every waiting caller learns it.
21008
+ */
21009
+ rejectCode(id, record, handle, err) {
21010
+ const trial = record.codeTrial;
21011
+ record.codeTrial = void 0;
21012
+ this.fail(id, handle, err);
21013
+ if (trial) {
21014
+ const p = trial.previous;
21015
+ record.session?.stop();
21016
+ record.session = void 0;
21017
+ record.source = p.source;
21018
+ record.engine = p.engine;
21019
+ record.prepared = p.prepared;
21020
+ record.inputValues = p.inputValues;
21021
+ record.propValues = p.propValues;
21022
+ handle.setSource(p.source);
21023
+ handle.setSchema(p.inputs);
21024
+ handle.setPropsSchema(p.props);
21025
+ record.pendingStructural = true;
21026
+ record.pendingCause = void 0;
21027
+ if (record.renderHandle) this.renderer.setIndicatorInputs(record.renderHandle, record.inputValues, record.propValues);
21028
+ if (!record.hidden) this.executeIndicator(id, handle);
21029
+ }
21030
+ const error = err instanceof Error ? err : new Error(String(err));
21031
+ this.settleCode(record, { ok: false, error });
20798
21032
  }
20799
21033
  /** IndicatorController: tear down an indicator and (if now empty) its pane. */
20800
21034
  /** Live handles of every indicator on the chart (script + native), insertion order. */
@@ -20809,6 +21043,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
20809
21043
  removeIndicator(id) {
20810
21044
  const record = this.registry.remove(id);
20811
21045
  if (record?.native?.type === "volume") this.volumeOptedOut = true;
21046
+ if (record) this.settleCode(record, { ok: false, error: new Error(`[vela] updateCode("${id}") \u2014 the indicator was removed.`) });
20812
21047
  record?.session?.stop();
20813
21048
  if (record?.native?.started) record.native.instance.stop();
20814
21049
  this.handles.delete(id);
@@ -20836,6 +21071,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
20836
21071
  if (!visible) {
20837
21072
  record.session?.stop();
20838
21073
  record.session = void 0;
21074
+ if (record.codeTrial) {
21075
+ record.codeTrial = void 0;
21076
+ this.settleCode(record, { ok: true, error: null });
21077
+ }
20839
21078
  if (record.native?.started) record.native.instance.suspend();
20840
21079
  if (record.renderHandle) this.renderer.setIndicatorVisible?.(record.renderHandle, false);
20841
21080
  else if (record.native) this.mountHiddenNativeRow(id, record);
@@ -20923,6 +21162,7 @@ var EngineOrchestrator = class _EngineOrchestrator {
20923
21162
  this.viewportUnsub?.();
20924
21163
  this.paneActionUnsub?.();
20925
21164
  for (const record of this.registry.all()) {
21165
+ this.settleCode(record, { ok: false, error: new Error("[vela] updateCode \u2014 the chart was destroyed.") });
20926
21166
  record.session?.stop();
20927
21167
  if (record.native?.started) record.native.instance.stop();
20928
21168
  }
@@ -20991,7 +21231,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
20991
21231
  const record = this.registry.get(id);
20992
21232
  if (!record || !record.engine || !record.prepared || record.hidden) return;
20993
21233
  const mode = this.config.live && !record.prepared.reactsToViewport && record.engine.capabilities.streaming ? "live" : "static";
20994
- record.session = record.engine.execute(
21234
+ const prepared = record.prepared;
21235
+ const session = record.engine.execute(
20995
21236
  {
20996
21237
  prepared: record.prepared,
20997
21238
  market: this.market(),
@@ -21012,6 +21253,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
21012
21253
  const cause = record.pendingCause ?? "history";
21013
21254
  if (!this.applyModel(id, model)) return;
21014
21255
  record.pendingCause = void 0;
21256
+ if (record.codeTrial) {
21257
+ record.codeTrial = void 0;
21258
+ this.settleCode(record, { ok: true, error: null });
21259
+ }
21015
21260
  this.emitContextChanged(id);
21016
21261
  this.emitScriptRun(id, cause, first);
21017
21262
  },
@@ -21022,9 +21267,17 @@ var EngineOrchestrator = class _EngineOrchestrator {
21022
21267
  handle.emit("alert", { id: a.id, message: a.message, title: a.title, time: a.time });
21023
21268
  },
21024
21269
  onWarning: (w) => this.events.emit("warning", w),
21025
- onError: (err) => this.fail(id, handle, err)
21270
+ onError: (err) => {
21271
+ if (record.codeTrial) this.rejectCode(id, record, handle, err);
21272
+ else this.fail(id, handle, err);
21273
+ }
21026
21274
  }
21027
21275
  );
21276
+ if (record.prepared !== prepared) {
21277
+ session.stop();
21278
+ return;
21279
+ }
21280
+ record.session = session;
21028
21281
  this.emitContextChanged(id);
21029
21282
  }
21030
21283
  /**
@@ -21525,7 +21778,7 @@ function yieldToPaint() {
21525
21778
  }
21526
21779
  return new Promise((resolve) => setTimeout(resolve, 0));
21527
21780
  }
21528
- function valuesOnSchema(schema, previous) {
21781
+ function valuesOnSchema(schema, previous, oldSchema = []) {
21529
21782
  const out = {};
21530
21783
  const declared = /* @__PURE__ */ new Set();
21531
21784
  for (const s of schema) {
@@ -21533,9 +21786,22 @@ function valuesOnSchema(schema, previous) {
21533
21786
  declared.add(s.key);
21534
21787
  declared.add(s.title);
21535
21788
  }
21536
- for (const [k, v] of Object.entries(previous)) if (declared.has(k)) out[k] = v;
21789
+ const oldDefaults = /* @__PURE__ */ new Map();
21790
+ for (const s of oldSchema) {
21791
+ oldDefaults.set(s.key, s.defval);
21792
+ if (!oldDefaults.has(s.title)) oldDefaults.set(s.title, s.defval);
21793
+ }
21794
+ for (const [k, v] of Object.entries(previous)) {
21795
+ if (!declared.has(k)) continue;
21796
+ if (oldDefaults.has(k) && sameInputValue(oldDefaults.get(k), v)) continue;
21797
+ out[k] = v;
21798
+ }
21537
21799
  return out;
21538
21800
  }
21801
+ function sameInputValue(a, b) {
21802
+ if (a === b) return true;
21803
+ return typeof a === "object" && a !== null && typeof b === "object" && b !== null && JSON.stringify(a) === JSON.stringify(b);
21804
+ }
21539
21805
  function blankedModel(model) {
21540
21806
  return {
21541
21807
  ...model,
@@ -24465,15 +24731,23 @@ var Batch = class {
24465
24731
  this.seg(x + w, y + h, x, y + h, lineWidth, c);
24466
24732
  this.seg(x, y + h, x, y, lineWidth, c);
24467
24733
  }
24468
- /** Filled circle (triangle fan). */
24734
+ /**
24735
+ * Filled circle (triangle fan), feathered like a line: the rim is padded by {@link AA_PAD} and
24736
+ * edgeDist runs from 0 at the center to the padded radius at the rim, with edgeHalf = r. The
24737
+ * distance grows linearly along each spoke, so the shader's line feather gives a round soft edge.
24738
+ */
24469
24739
  circle(cx, cy, r, c, segments = 16) {
24470
- let px = cx + r;
24740
+ const ext = r + AA_PAD;
24741
+ let px = cx + ext;
24471
24742
  let py = cy;
24743
+ this.ensure(segments * 3);
24472
24744
  for (let i = 1; i <= segments; i += 1) {
24473
24745
  const a = i / segments * Math.PI * 2;
24474
- const nx = cx + Math.cos(a) * r;
24475
- const ny = cy + Math.sin(a) * r;
24476
- this.tri(cx, cy, c, px, py, c, nx, ny, c);
24746
+ const nx = cx + Math.cos(a) * ext;
24747
+ const ny = cy + Math.sin(a) * ext;
24748
+ this.v(cx, cy, c, 0, r);
24749
+ this.v(px, py, c, ext, r);
24750
+ this.v(nx, ny, c, ext, r);
24477
24751
  px = nx;
24478
24752
  py = ny;
24479
24753
  }
@@ -24654,7 +24928,7 @@ var WebGL2Backend = class {
24654
24928
  }
24655
24929
  mount(canvas) {
24656
24930
  this.canvas = canvas;
24657
- this.gl = canvas.getContext("webgl2", { alpha: true, antialias: true, depth: false, stencil: false, premultipliedAlpha: true });
24931
+ this.gl = canvas.getContext("webgl2", { alpha: true, antialias: false, depth: false, stencil: false, premultipliedAlpha: true });
24658
24932
  if (!this.gl) return;
24659
24933
  if (!this.initGL()) {
24660
24934
  this.gl = null;
@@ -26106,7 +26380,7 @@ var InputController = class {
26106
26380
  this.deps.drawingsPointerUp?.(x, y, this.snapMode(e));
26107
26381
  } else if (tapRelease && this.region === "data") {
26108
26382
  this.deps.onClick(x, y);
26109
- } else if (this.dragging && this.region === "data") {
26383
+ } else if (this.dragging && this.region === "data" && wasTouch) {
26110
26384
  const stale = e.timeStamp - this.lastT > FLING_STALE_MS;
26111
26385
  if (!stale && Math.abs(this.vx) > FLING_MIN_SPEED) {
26112
26386
  const pitch = this.deps.getCoords().pxPerBar();
@@ -32472,6 +32746,12 @@ var DrawingInteraction = class {
32472
32746
  activeDragId() {
32473
32747
  return this.state.kind === "pressed" && this.state.moved ? this.state.id : null;
32474
32748
  }
32749
+ /** The store drawings a live drag is moving — the pressed one and its riders — or none
32750
+ * before the drag passes the slop. A Ctrl-drag moves copies, so its sources stay put. */
32751
+ movingIds() {
32752
+ if (this.state.kind !== "pressed" || !this.state.moved || this.state.clones) return /* @__PURE__ */ new Set();
32753
+ return /* @__PURE__ */ new Set([this.state.id, ...this.state.riders.map((r) => r.id)]);
32754
+ }
32475
32755
  /** The drawing under a press that has not been released yet (drag or click undecided), or null. */
32476
32756
  pressedId() {
32477
32757
  return this.state.kind === "pressed" ? this.state.id : null;
@@ -33130,6 +33410,17 @@ var DrawingSettingsDialog = class {
33130
33410
  }
33131
33411
  }));
33132
33412
  }
33413
+ if (drawing instanceof FibRetracement) {
33414
+ const fib = drawing;
33415
+ grid.appendChild(fieldRow({
33416
+ label: "Reverse",
33417
+ bool: true,
33418
+ toggle: {
33419
+ checked: fib.reverse,
33420
+ onChange: (v) => actions.patch({ reverse: v })
33421
+ }
33422
+ }));
33423
+ }
33133
33424
  levels.forEach((lv, i) => {
33134
33425
  const row = document.createElement("div");
33135
33426
  row.className = "vela-field-span";
@@ -34283,6 +34574,8 @@ var UserDrawingController = class {
34283
34574
  this.drawings = [];
34284
34575
  /** Ids painted on an interleave layer this frame — the top canvas paints only their handles. */
34285
34576
  this.sliced = /* @__PURE__ */ new Set();
34577
+ /** Interleaved drawings mid-drag: painted on the top canvas, left out of their slice. */
34578
+ this.lifted = /* @__PURE__ */ new Set();
34286
34579
  /** Cached slice canvases, keyed `paneId|beforeZ`, reused across frames to avoid churn. */
34287
34580
  this.sliceCache = /* @__PURE__ */ new Map();
34288
34581
  /** Series boundaries per pane as of the last `prepareSlices` — lets a repaint between data
@@ -34370,7 +34663,7 @@ var UserDrawingController = class {
34370
34663
  selectedIds: () => this.selectedIds,
34371
34664
  emit: (i) => this.emit(i),
34372
34665
  changed: () => {
34373
- this.invalidateSlices();
34666
+ this.syncLift();
34374
34667
  this.render();
34375
34668
  },
34376
34669
  openSettings: (id, x, y) => this.openSettingsById(id, x, y),
@@ -34996,6 +35289,20 @@ var UserDrawingController = class {
34996
35289
  /** A drawing that paints inside the series stack changed (content, not hover): its pixels
34997
35290
  * live in the backend composite, so this layer alone can't show the change — ask for a
34998
35291
  * data frame, which re-runs `prepareSlices` before the backend composites. */
35292
+ /** A drawing being dragged leaves its interleave slice for the top canvas, so each pointer
35293
+ * move repaints one 2D layer instead of re-rasterizing and re-uploading a plot-sized slice
35294
+ * texture through the data frame. The slices rebuild once as the drag starts and once as it
35295
+ * ends (release, cancel), when the lifted set changes. */
35296
+ syncLift() {
35297
+ const moving = this.interaction.movingIds();
35298
+ let same = moving.size === this.lifted.size;
35299
+ if (same) {
35300
+ for (const id of moving) if (!this.lifted.has(id)) same = false;
35301
+ }
35302
+ if (same) return;
35303
+ this.lifted = new Set(moving);
35304
+ this.invalidateSlices();
35305
+ }
34999
35306
  invalidateSlices() {
35000
35307
  if (this.sliced.size > 0 || this.drawings.some((d) => this.isInterleaved(d))) this.deps.requestDataPaint();
35001
35308
  }
@@ -35014,7 +35321,7 @@ var UserDrawingController = class {
35014
35321
  const theme = this.deps.theme();
35015
35322
  const buckets = /* @__PURE__ */ new Map();
35016
35323
  for (const d of this.drawings) {
35017
- if (!d.visible) continue;
35324
+ if (!d.visible || this.lifted.has(d.id)) continue;
35018
35325
  const beforeZ = sliceKeyFor(d.zIndex, this.lastBounds.get(d.paneId) ?? []);
35019
35326
  if (beforeZ === null) continue;
35020
35327
  const key = `${d.paneId}|${beforeZ}`;
@@ -35066,8 +35373,9 @@ var UserDrawingController = class {
35066
35373
  mutedLabel: edited instanceof TextLabel ? edited.id : null
35067
35374
  };
35068
35375
  this.painter.seriesLook = this.deps.seriesLook();
35069
- this.painter.paintAll(ctx, this.drawings.filter((d) => !this.isInterleaved(d)), proj, this.deps.theme(), targets);
35070
- this.painter.paintHighlights(ctx, this.drawings.filter((d) => this.isInterleaved(d)), proj, handleIdsFor(targets));
35376
+ const onTop = (d) => !this.isInterleaved(d) || this.lifted.has(d.id);
35377
+ this.painter.paintAll(ctx, this.drawings.filter(onTop), proj, this.deps.theme(), targets);
35378
+ this.painter.paintHighlights(ctx, this.drawings.filter((d) => !onTop(d)), proj, handleIdsFor(targets));
35071
35379
  const clones = this.interaction.dragClones();
35072
35380
  if (clones) this.painter.paintAll(ctx, clones, proj, this.deps.theme(), { selected: new Set(clones.map((c) => c.id)) });
35073
35381
  this.layoutTextEditor();
@@ -44723,6 +45031,9 @@ var CSS21 = `
44723
45031
  .vela-statusline .vela-sl-market { align-self: center; display: inline-flex; }
44724
45032
  .vela-statusline .vela-sl-market > [hidden] { display: none !important; }
44725
45033
  .vela-statusline .vela-sl-replay-badge, .vela-statusline .vela-sl-replay-badge svg { display: block; width: 16px; height: 16px; }
45034
+ /* Tabular figures: every digit has one advance, so a readout whose text changes only in
45035
+ * its digits keeps its width \u2014 render() skips the ladder for those (see widthKey). */
45036
+ .vela-statusline .vela-sl-values { font-variant-numeric: tabular-nums; }
44726
45037
  .vela-statusline .vela-sl-ohlc { display: flex; gap: var(--vela-space-1); color: var(--vela-fg-muted); }
44727
45038
  .vela-statusline .vela-sl-ohlc b { color: var(--vela-fg); font-weight: 500; }
44728
45039
  /* The change value wears the SAME ink as the OHLC values (set inline per render) \u2014
@@ -44840,6 +45151,10 @@ var Statusline = class {
44840
45151
  this.fitRO = null;
44841
45152
  /** The ladder rung currently applied — see {@link fit}. */
44842
45153
  this.layout = { stacked: false, level: "full" };
45154
+ /** The readout {@link widthKey} the applied rung was fitted to. */
45155
+ this.fitKey = null;
45156
+ /** Web fonts landing after the last fit change every width without a resize. */
45157
+ this.onFontsLoaded = () => this.fit();
44843
45158
  this.onContextMenu = (e) => {
44844
45159
  if (!this.menu || !this.menuHooks) return;
44845
45160
  e.preventDefault();
@@ -44903,6 +45218,7 @@ var Statusline = class {
44903
45218
  this.fitRO = new ResizeObserver(() => this.fit());
44904
45219
  this.fitRO.observe(host);
44905
45220
  }
45221
+ doc.fonts?.addEventListener("loadingdone", this.onFontsLoaded);
44906
45222
  this.marketTip = new Tooltip(this.marketEl, { content: MARKET_LABELS.open, placement: "bottom" });
44907
45223
  this.eyeTip = new Tooltip(this.eyeEl, { content: "Show chart", placement: "bottom" });
44908
45224
  this.setMarketStatus("open");
@@ -44955,6 +45271,7 @@ var Statusline = class {
44955
45271
  * (detached host, node tests) nothing overflows, so the widest rung stays.
44956
45272
  */
44957
45273
  fit() {
45274
+ this.fitKey = this.widthKey();
44958
45275
  this.syncParts();
44959
45276
  const seg = segmentVisibility(this.parts, this.chartHidden);
44960
45277
  const overflows = () => this.el.scrollWidth > this.el.clientWidth;
@@ -45111,6 +45428,7 @@ var Statusline = class {
45111
45428
  this.detach();
45112
45429
  this.fitRO?.disconnect();
45113
45430
  this.fitRO = null;
45431
+ this.el.ownerDocument.fonts?.removeEventListener("loadingdone", this.onFontsLoaded);
45114
45432
  this.el.removeEventListener("contextmenu", this.onContextMenu);
45115
45433
  this.menu?.destroy();
45116
45434
  this.menu = null;
@@ -45126,7 +45444,22 @@ var Statusline = class {
45126
45444
  }
45127
45445
  render() {
45128
45446
  if (this.menuHooks) this.setChartHidden(!this.menuHooks.chartVisible());
45129
- this.fit();
45447
+ if (this.widthKey() === this.fitKey) this.renderValues();
45448
+ else this.fit();
45449
+ }
45450
+ /** Everything the readout's width depends on, at every rung: its shape, and each
45451
+ * value's sign, digit count and decimals (the digits themselves are tabular — see
45452
+ * the CSS; the grouping commas follow from the digit count). `toFixed` carries all
45453
+ * three at a fraction of the locale formatter's cost — this runs per pointer move.
45454
+ * The parts, the host's size and the fonts re-fit through their own paths. */
45455
+ widthKey() {
45456
+ const bar = this.hoverBar ?? this.lastBar;
45457
+ if (!bar) return this.readout;
45458
+ const dp = decimalsFor(bar.close);
45459
+ const diff = bar.close - bar.open;
45460
+ const text = [bar.open, bar.high, bar.low, bar.close, diff].map((v) => v.toFixed(dp));
45461
+ text.push((diff / bar.open * 100).toFixed(2));
45462
+ return `${this.readout} ${text.join(" ")}`.replace(/\d/g, "0");
45130
45463
  }
45131
45464
  /** Write the value readout for the current bar at the current ladder level. */
45132
45465
  renderValues() {