@luxalgo/vela 0.6.9 → 0.6.10

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 (45) hide show
  1. package/dist/{DataProvider-8Z95Q-RJ.d.cts → DataProvider-CWmp31dA.d.ts} +49 -1
  2. package/dist/{DataProvider-DExJrfut.d.ts → DataProvider-l_eLMly_.d.cts} +49 -1
  3. package/dist/{chunk-EQCHJZOT.js → chunk-4KZVZ7QQ.js} +1 -1
  4. package/dist/{chunk-6WDDVMBJ.js → chunk-FFOP37FQ.js} +577 -321
  5. package/dist/{chunk-62SVGONC.js → chunk-G77Y7LK2.js} +2 -2
  6. package/dist/{chunk-WFSZBX3R.js → chunk-KG4YT3TI.js} +3 -1
  7. package/dist/{chunk-FCVIG7JG.js → chunk-MHY7MVXH.js} +5 -1
  8. package/dist/{chunk-STHSKXOR.js → chunk-NMTQXNT4.js} +591 -141
  9. package/dist/{contributions-D7PVZO2i.d.ts → contributions-BCz6Dr6a.d.ts} +6 -6
  10. package/dist/{contributions-C1U2Krwg.d.cts → contributions-D9vTzm5p.d.cts} +6 -6
  11. package/dist/index.cjs +580 -318
  12. package/dist/index.d.cts +31 -10
  13. package/dist/index.d.ts +31 -10
  14. package/dist/index.js +4 -4
  15. package/dist/{options-FM0peknS.d.ts → options-DSqHsQyN.d.cts} +6 -6
  16. package/dist/{options-FM0peknS.d.cts → options-DSqHsQyN.d.ts} +6 -6
  17. package/dist/{plugin-DfVqBz9p.d.cts → plugin-Bt8hLR8Z.d.cts} +3 -3
  18. package/dist/{plugin-7bkF32Rk.d.ts → plugin-CH7pfMrE.d.ts} +3 -3
  19. package/dist/plugin.cjs +3 -1
  20. package/dist/plugin.d.cts +4 -4
  21. package/dist/plugin.d.ts +4 -4
  22. package/dist/plugin.js +2 -2
  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/{statusline-zdF4eZLr.d.ts → statusline-5K7y4Ya2.d.ts} +3 -3
  30. package/dist/{statusline-DOPiT6I6.d.cts → statusline-TYLQmoeh.d.cts} +3 -3
  31. package/dist/ui.cjs +7 -1
  32. package/dist/ui.d.cts +3 -1
  33. package/dist/ui.d.ts +3 -1
  34. package/dist/ui.js +3 -3
  35. package/dist/vela.global.js +580 -318
  36. package/dist/vela.global.min.js +50 -48
  37. package/dist/widget.cjs +1052 -340
  38. package/dist/widget.d.cts +17 -6
  39. package/dist/widget.d.ts +17 -6
  40. package/dist/widget.js +7 -7
  41. package/dist/workspace.cjs +1052 -340
  42. package/dist/workspace.d.cts +89 -5
  43. package/dist/workspace.d.ts +89 -5
  44. package/dist/workspace.js +6 -6
  45. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -552,6 +552,8 @@ registerIcon("folder-plus", S('<path d="M1.6 4.4a1 1 0 0 1 1-1h2.7l1.3 1.7h6.8a1
552
552
  registerIcon("folder-minus", S('<path d="M1.6 4.4a1 1 0 0 1 1-1h2.7l1.3 1.7h6.8a1 1 0 0 1 1 1v6.5a1 1 0 0 1-1 1h-10.8a1 1 0 0 1-1-1z"/><path d="M6.2 9.4h3.6"/>'));
553
553
  registerIcon("collapse", S('<path d="M3 8h10"/>'));
554
554
  registerIcon("expand", S('<rect x="2.2" y="2.2" width="11.6" height="11.6" rx="1.4"/><path d="M8 5.4v5.2M5.4 8h5.2"/>'));
555
+ registerIcon("plus", S('<path d="M8 2.8v10.4M2.8 8h10.4"/>'));
556
+ registerIcon("minus", S('<path d="M2.8 8h10.4"/>'));
555
557
  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"/>'));
556
558
  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"/>'));
557
559
  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"/>'));
@@ -559,7 +561,7 @@ registerIcon("star-filled", S('<path d="M8 2.2l1.75 3.55 3.9.55-2.8 2.75.65 3.9L
559
561
  registerIcon("grip", S('<circle cx="6" cy="3.5" r="1"/><circle cx="10" cy="3.5" r="1"/><circle cx="6" cy="8" r="1"/><circle cx="10" cy="8" r="1"/><circle cx="6" cy="12.5" r="1"/><circle cx="10" cy="12.5" r="1"/>', 'fill="currentColor" stroke="none"'));
560
562
  registerIcon("kebab", S('<circle cx="8" cy="3.2" r="1.2"/><circle cx="8" cy="8" r="1.2"/><circle cx="8" cy="12.8" r="1.2"/>', 'fill="currentColor" stroke="none"'));
561
563
  registerIcon("burger", S('<path d="M2.5 4.5h11M2.5 8h11M2.5 11.5h11"/>'));
562
- registerIcon("reset", S('<rect x="6" y="6" width="4" height="4" rx="0.8"/><path d="M2.2 8a5.8 5.8 0 1 0 1.9-4.3L2.2 5.3"/><path d="M2.2 2.2v3.1h3.1"/>'));
564
+ registerIcon("reset", S('<path d="M2 8a6 6 0 1 0 6-6 6.5 6.5 0 0 0-4.5 1.83L2 5.33"/><path d="M2 2v3.33h3.33"/>'));
563
565
  registerIcon("pane-collapse", S('<path d="M2.6 9.4h4v4M13.4 6.6h-4v-4"/><path d="m9.4 6.6 4.2-4.2M2.4 13.6l4.2-4.2"/>'));
564
566
  registerIcon("pane-expand", S('<path d="M9.8 2.4h3.8v3.8M6.2 13.6H2.4V9.8"/><path d="m13.6 2.4-4.4 4.4M2.4 13.6l4.4-4.4"/>'));
565
567
  registerIcon("cursor", svg24('<path d="M5 3l6 16 2-6 6-2z"/>', 'fill="currentColor" stroke="none"'));
@@ -6930,6 +6932,23 @@ function timeframeToMs(timeframe) {
6930
6932
  return 36e5;
6931
6933
  }
6932
6934
 
6935
+ // src/data/symbol-groups.ts
6936
+ function isGroupRow(d) {
6937
+ return d.group != null && d.ticker === d.group;
6938
+ }
6939
+ function groupKeyOf(d) {
6940
+ return `${(d.prefix ?? d.provider ?? "").toLowerCase()}:${(d.group ?? "").toUpperCase()}`;
6941
+ }
6942
+ function groupMembers(pool, groupRow) {
6943
+ const key = groupKeyOf(groupRow);
6944
+ return pool.filter((s) => s.group != null && !isGroupRow(s) && groupKeyOf(s) === key);
6945
+ }
6946
+ function defaultMemberOf(pool, groupRow) {
6947
+ const members = groupMembers(pool, groupRow);
6948
+ const defaults2 = members.filter((m) => m.default);
6949
+ return (defaults2.length === 1 ? defaults2[0] : members[0]) ?? null;
6950
+ }
6951
+
6933
6952
  // src/data/ProviderRegistry.ts
6934
6953
  var normName = (n) => n.trim().toLowerCase();
6935
6954
  var normTicker = (t) => t.trim().toUpperCase();
@@ -7019,17 +7038,24 @@ var ProviderRegistry = class {
7019
7038
  resolve(raw, opts = {}) {
7020
7039
  const { provider, ticker } = this.parse(raw);
7021
7040
  if (provider) {
7022
- if (this.entries.has(provider)) return { provider, ticker };
7041
+ if (this.entries.has(provider)) {
7042
+ const d = this.entries.get(provider).byTicker?.get(normTicker(ticker));
7043
+ return { provider, ticker: d != null && isGroupRow(d) ? this.loadableTicker(provider, d) : ticker };
7044
+ }
7023
7045
  const key = `${provider}:${normTicker(ticker)}`;
7024
7046
  for (const name of this.candidateOrder(opts.default)) {
7025
7047
  const d = this.entries.get(name).prefixIndex?.get(key);
7026
- if (d) return { provider: name, ticker: d.ticker };
7048
+ if (d) return { provider: name, ticker: this.loadableTicker(name, d) };
7027
7049
  }
7028
7050
  return null;
7029
7051
  }
7030
7052
  const norm = normTicker(ticker);
7031
7053
  for (const name of this.candidateOrder(opts.default)) {
7032
- if (this.entries.get(name).index?.has(norm)) return { provider: name, ticker };
7054
+ const e = this.entries.get(name);
7055
+ if (e.index?.has(norm)) {
7056
+ const d = e.byTicker?.get(norm);
7057
+ return { provider: name, ticker: d != null && isGroupRow(d) ? this.loadableTicker(name, d) : ticker };
7058
+ }
7033
7059
  }
7034
7060
  if (opts.lenient && this.entries.size === 1) {
7035
7061
  const only = this.names()[0];
@@ -7038,6 +7064,13 @@ var ProviderRegistry = class {
7038
7064
  }
7039
7065
  return null;
7040
7066
  }
7067
+ /** What a resolved descriptor LOADS: itself — unless it is a GROUP row (listed, never
7068
+ * served), which loads its default member (single `default`, else first listed). A
7069
+ * memberless group keeps its own ticker and fails downstream like any unknown symbol. */
7070
+ loadableTicker(name, d) {
7071
+ if (!isGroupRow(d)) return d.ticker;
7072
+ return defaultMemberOf(this.entries.get(name)?.descriptors ?? [], d)?.ticker ?? d.ticker;
7073
+ }
7041
7074
  /**
7042
7075
  * Resolve now if possible, else resolve later — re-attempting after each provider
7043
7076
  * settles. The promise stays pending until some registered provider can serve the
@@ -7326,6 +7359,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
7326
7359
  /** Invalidates detached async work (backfill loops, in-flight loads, gap heals):
7327
7360
  * bumped by init(), setMarket() and destroy(). */
7328
7361
  this.generation = 0;
7362
+ /** Aborts the in-flight PROGRESSIVE load's source polling on supersession — an
7363
+ * abandoned stream left polling to its own budget starves the browser's per-host
7364
+ * connection pool, and the NEXT symbol's very first fetch with it (measured). */
7365
+ this.progressiveAbort = null;
7329
7366
  /** Awaiters racing a superseded load (setMarket callers) — released on every bump so they never hang. */
7330
7367
  this.supersedeWaiters = [];
7331
7368
  /** `history:complete` fired for the CURRENT load. Each market load re-arms the cycle
@@ -7468,6 +7505,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
7468
7505
  * superseded setMarket awaiters so their promises resolve instead of hanging. */
7469
7506
  bumpGeneration() {
7470
7507
  const gen = ++this.generation;
7508
+ this.progressiveAbort?.abort();
7509
+ this.progressiveAbort = null;
7471
7510
  for (const w of this.supersedeWaiters.splice(0)) w();
7472
7511
  return gen;
7473
7512
  }
@@ -7521,7 +7560,47 @@ var EngineOrchestrator = class _EngineOrchestrator {
7521
7560
  const requested = market.bars ?? 500;
7522
7561
  const initialRange = market.visibleRange;
7523
7562
  const deep = !market.data?.length && initialRange == null && requested > SINGLE_LOAD_BARS;
7524
- if (deep && this.feed.loadRange) {
7563
+ let progressiveServed = false;
7564
+ if (!market.data?.length && initialRange == null && this.feed.loadProgressive) {
7565
+ let painted = false;
7566
+ const paint = (bars, final) => {
7567
+ if (this.generation !== gen || !final && bars.length === 0) return;
7568
+ this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
7569
+ if (!painted && bars.length > 0) {
7570
+ painted = true;
7571
+ if (opts.firstLoad) this.activateBarLayers();
7572
+ if (!final) this.historyState = "backfill";
7573
+ }
7574
+ };
7575
+ const abort = new AbortController();
7576
+ this.progressiveAbort = abort;
7577
+ progressiveServed = await new Promise((firstPaint) => {
7578
+ let signaled = false;
7579
+ const signal = (served) => {
7580
+ if (!signaled) {
7581
+ signaled = true;
7582
+ firstPaint(served);
7583
+ }
7584
+ };
7585
+ abort.signal.addEventListener("abort", () => signal(true), { once: true });
7586
+ this.feed.loadProgressive(market, (bars) => {
7587
+ paint(bars, false);
7588
+ if (painted) signal(true);
7589
+ }, { signal: abort.signal }).then((full) => {
7590
+ if (this.progressiveAbort === abort) this.progressiveAbort = null;
7591
+ if (full == null) return signal(false);
7592
+ if (this.generation !== gen) return signal(true);
7593
+ paint(full, true);
7594
+ this.completeHistory(full.length >= requested ? "depth" : "genesis");
7595
+ signal(true);
7596
+ }).catch(() => {
7597
+ if (this.progressiveAbort === abort) this.progressiveAbort = null;
7598
+ if (this.generation === gen) this.completeHistory("aborted");
7599
+ signal(true);
7600
+ });
7601
+ });
7602
+ }
7603
+ if (progressiveServed) ; else if (deep && this.feed.loadRange) {
7525
7604
  const head = await this.feed.load({ ...market, bars: Math.min(requested, CHUNK_BARS) });
7526
7605
  if (this.generation !== gen) return;
7527
7606
  this.setBarSeries(head);
@@ -9193,6 +9272,28 @@ var CachingDataFeed = class {
9193
9272
  this.store.merge(key, dropForming(bars));
9194
9273
  return bars;
9195
9274
  }
9275
+ /**
9276
+ * Progressive twin of {@link load}: a COLD load streams through the inner feed's
9277
+ * progressive path — batches forwarded verbatim, the FINAL answer cached exactly as
9278
+ * `load` caches — while a cache-covered load answers once through `load` itself (a
9279
+ * warm chart has nothing to stream). Falls back to `load` wholesale when the inner
9280
+ * feed lacks the capability, so callers may prefer this method unconditionally.
9281
+ */
9282
+ async loadProgressive(cfg, onBatch, opts) {
9283
+ if (cfg.data && cfg.data.length > 0) return this.inner.load(cfg);
9284
+ if (!this.inner.loadProgressive) return null;
9285
+ const symbol = cfg.symbol ?? "TEST";
9286
+ const key = cacheKey(symbol, cfg.timeframe ?? "60", cfg.session);
9287
+ const cached = this.store.get(key);
9288
+ const n = cfg.bars ?? 500;
9289
+ const lastCached = cached?.[cached.length - 1];
9290
+ if (cached && lastCached && cached.length >= n - 1 && this.inner.loadRange) return this.load(cfg);
9291
+ this.store.retainSymbol(symbol);
9292
+ const bars = await this.inner.loadProgressive(cfg, onBatch, opts);
9293
+ if (bars == null) return null;
9294
+ this.store.merge(key, dropForming(bars));
9295
+ return bars;
9296
+ }
9196
9297
  /**
9197
9298
  * Cache-backed ranged fetch — the gateway engines use for secondary series
9198
9299
  * (`request.security` HTF/LTF/cross-symbol) and the orchestrator's backward
@@ -9438,6 +9539,17 @@ var MultiProviderFeed = class {
9438
9539
  this.prefetchSymbolInfo(resolved);
9439
9540
  return this.cache.load(canonical(cfg, resolved));
9440
9541
  }
9542
+ /** Progressive twin of {@link load} — same resolution, the cache streams the batches. */
9543
+ async loadProgressive(cfg, onBatch, opts) {
9544
+ if (cfg.data && cfg.data.length > 0) {
9545
+ this.liveBars = cfg.data.map((b) => ({ ...b }));
9546
+ return cfg.data;
9547
+ }
9548
+ const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
9549
+ this.primaryProvider = resolved.provider;
9550
+ this.prefetchSymbolInfo(resolved);
9551
+ return this.cache.loadProgressive(canonical(cfg, resolved), onBatch, opts);
9552
+ }
9441
9553
  /**
9442
9554
  * Synchronous per-symbol metadata for engines (Pine `syminfo.*`), served from the cache
9443
9555
  * warmed by load(). Undefined until the prefetch lands (the engine then synthesizes a
@@ -9525,6 +9637,18 @@ var RegistryFetchFeed = class {
9525
9637
  if (!provider) return Promise.resolve([]);
9526
9638
  return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session });
9527
9639
  }
9640
+ async loadProgressive(cfg, onBatch, opts) {
9641
+ const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
9642
+ const provider = this.registry.get(name ?? "");
9643
+ if (!provider) return [];
9644
+ if (!provider.getBarsProgressive) return null;
9645
+ try {
9646
+ return await provider.getBarsProgressive(ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session }, onBatch, opts);
9647
+ } catch (e) {
9648
+ console.warn(`[vela] progressive fetch failed for ${ticker} ${cfg.timeframe ?? "60"} \u2014 ${e instanceof Error ? e.message : String(e)}`);
9649
+ return [];
9650
+ }
9651
+ }
9528
9652
  loadRange(cfg, range) {
9529
9653
  const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
9530
9654
  const provider = this.registry.get(name ?? "");
@@ -10787,6 +10911,10 @@ var CalloutBubble = class {
10787
10911
  get open() {
10788
10912
  return this.pop?.open ?? false;
10789
10913
  }
10914
+ /** Close the deployed panel, if any. The bubble itself stays. */
10915
+ hidePanel() {
10916
+ this.pop?.hide();
10917
+ }
10790
10918
  destroy() {
10791
10919
  this.pop?.destroy();
10792
10920
  this.pop = null;
@@ -13694,6 +13822,9 @@ var CLOSE_SVG = iconAt("close", LEGEND_ICON_PX2);
13694
13822
  var FOLD_SVG = iconAt("chevron-up", LEGEND_ICON_PX2);
13695
13823
  var UNFOLD_SVG = iconAt("chevron-down", LEGEND_ICON_PX2);
13696
13824
  var OVERVIEW_SVG = iconAt("objects", LEGEND_ICON_PX2);
13825
+ function legendCalloutsDisplay(open2, hasCallouts) {
13826
+ return !open2 && hasCallouts ? "inline-flex" : "none";
13827
+ }
13697
13828
  var InputsUI = class {
13698
13829
  constructor(container, theme, paneBoundsOf) {
13699
13830
  this.container = container;
@@ -13897,7 +14028,7 @@ var InputsUI = class {
13897
14028
  row.callouts = [];
13898
14029
  row.calloutsEl.replaceChildren();
13899
14030
  const views = this.legendCallouts?.(row.id) ?? [];
13900
- row.calloutsEl.style.display = views.length > 0 ? "inline-flex" : "none";
14031
+ row.calloutsEl.style.display = legendCalloutsDisplay(row.highlighted, views.length > 0);
13901
14032
  for (const view of views) {
13902
14033
  const bubble = new CalloutBubble({
13903
14034
  icon: view.icon,
@@ -14381,11 +14512,11 @@ var InputsUI = class {
14381
14512
  row.controlsEl.style.display = open2 || row.hidden ? "inline-flex" : "none";
14382
14513
  if (open2) {
14383
14514
  row.el.appendChild(row.statusEl);
14384
- row.el.appendChild(row.calloutsEl);
14515
+ for (const bubble of row.callouts) bubble.hidePanel();
14385
14516
  } else {
14386
14517
  row.el.insertBefore(row.statusEl, row.valuesEl);
14387
- row.el.insertBefore(row.calloutsEl, row.statusEl);
14388
14518
  }
14519
+ row.calloutsEl.style.display = legendCalloutsDisplay(open2, row.callouts.length > 0);
14389
14520
  for (const child of Array.from(row.controlsEl.children)) {
14390
14521
  if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
14391
14522
  if (child === row.extrasEl) {
@@ -14475,7 +14606,7 @@ var ICONS = {
14475
14606
  };
14476
14607
  var STYLE_ID = "vela-pane-controls";
14477
14608
  var ICON_PX = 12;
14478
- var CLUSTER_PILL = "rgba(0,0,0,0.28)";
14609
+ var CLUSTER_PILL = "rgba(0,0,0,0.65)";
14479
14610
  function ensureStyles() {
14480
14611
  if (typeof document === "undefined" || document.getElementById(STYLE_ID)) return;
14481
14612
  const st = document.createElement("style");
@@ -14495,8 +14626,12 @@ var PaneControls = class {
14495
14626
  this.deps = deps;
14496
14627
  this.clusters = /* @__PURE__ */ new Map();
14497
14628
  this.hoverPaneId = null;
14629
+ /** Mobile: hover clusters are meaningless without a cursor — suppressed; a
14630
+ * collapsed pane's standalone expand chip stays (the only way back up). */
14631
+ this.suspended = false;
14498
14632
  /** Reveal the cluster for the pane under the cursor, resolved from the pointer's y in the plot. */
14499
14633
  this.onPlotMove = (e) => {
14634
+ if (this.suspended) return;
14500
14635
  const rect = this.plot.getBoundingClientRect();
14501
14636
  const y = e.clientY - rect.top;
14502
14637
  let hit = null;
@@ -14568,7 +14703,12 @@ var PaneControls = class {
14568
14703
  }
14569
14704
  if (p.count > 1) {
14570
14705
  cluster.appendChild(
14571
- this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), { role: "maximize" })
14706
+ this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
14707
+ role: "maximize",
14708
+ // Same inverse-chip treatment as the collapsed pane's expand toggle: the
14709
+ // maximized state must read as an active state, not just a swapped glyph.
14710
+ selected: p.maximized
14711
+ })
14572
14712
  );
14573
14713
  }
14574
14714
  }
@@ -14605,17 +14745,18 @@ var PaneControls = class {
14605
14745
  }
14606
14746
  const hovered = id === this.hoverPaneId;
14607
14747
  const hasButtons = cluster.children.length > 0;
14608
- const visible = hasButtons && (hovered || p.collapsed) && p.height > 8;
14748
+ const stateChipRole = p.collapsed ? "collapse" : !this.suspended && p.maximized ? "maximize" : null;
14749
+ const visible = hasButtons && (hovered || stateChipRole != null) && p.height > 8;
14609
14750
  cluster.style.right = `${rightPx}px`;
14610
14751
  cluster.style.top = p.collapsed ? `${p.top + Math.max(1, Math.round((p.height - 24) / 2))}px` : `${p.top + 4}px`;
14611
14752
  cluster.style.display = visible ? "flex" : "none";
14612
14753
  if (!visible) continue;
14613
- const soloExpand = p.collapsed && !hovered;
14614
- cluster.style.background = soloExpand ? "transparent" : CLUSTER_PILL;
14754
+ const soloChip = stateChipRole != null && !hovered;
14755
+ cluster.style.background = soloChip ? "transparent" : CLUSTER_PILL;
14615
14756
  for (const child of cluster.children) {
14616
14757
  const btn2 = child;
14617
14758
  btn2.style.display = "inline-flex";
14618
- btn2.style.visibility = soloExpand && btn2.dataset.role !== "collapse" ? "hidden" : "visible";
14759
+ btn2.style.visibility = soloChip && btn2.dataset.role !== stateChipRole ? "hidden" : "visible";
14619
14760
  }
14620
14761
  }
14621
14762
  }
@@ -14625,6 +14766,14 @@ var PaneControls = class {
14625
14766
  this.hoverPaneId = paneId;
14626
14767
  this.reposition();
14627
14768
  }
14769
+ /** Mobile suppression: no hover clusters (touch has no cursor; the shell's own
14770
+ * chrome covers maximize), while collapsed panes keep their expand chips. */
14771
+ setSuspended(on) {
14772
+ if (on === this.suspended) return;
14773
+ this.suspended = on;
14774
+ if (on) this.hoverPaneId = null;
14775
+ this.reposition();
14776
+ }
14628
14777
  destroy() {
14629
14778
  this.plot.removeEventListener("pointermove", this.onPlotMove);
14630
14779
  this.plot.removeEventListener("pointerleave", this.onPlotLeave);
@@ -14775,160 +14924,6 @@ var AxisScaleButtons = class {
14775
14924
  }
14776
14925
  };
14777
14926
 
14778
- // src/renderers/shared/TableOverlay.ts
14779
- var SIZE_PX3 = {
14780
- auto: 13,
14781
- tiny: 10,
14782
- small: 11,
14783
- normal: 13,
14784
- large: 16,
14785
- huge: 20
14786
- };
14787
- function fontPxOf(size) {
14788
- if (typeof size === "number") return size > 0 ? size : SIZE_PX3.auto;
14789
- return SIZE_PX3[size] ?? SIZE_PX3.auto;
14790
- }
14791
- function tableHasContent(t) {
14792
- return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
14793
- }
14794
- function mergeRenderPlan(t) {
14795
- const span = /* @__PURE__ */ new Map();
14796
- const omit = /* @__PURE__ */ new Set();
14797
- for (const m of t.merges) {
14798
- span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
14799
- for (let r = m.startRow; r <= m.endRow; r += 1) {
14800
- for (let c = m.startCol; c <= m.endCol; c += 1) {
14801
- if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
14802
- }
14803
- }
14804
- }
14805
- for (let r = 0; r < t.rows; r += 1) {
14806
- for (let c = 0; c < t.columns; c += 1) {
14807
- if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
14808
- }
14809
- }
14810
- for (const key of span.keys()) omit.delete(key);
14811
- return { span, omit };
14812
- }
14813
- var TableOverlay = class {
14814
- constructor(container, theme, paneBounds) {
14815
- this.container = container;
14816
- this.theme = theme;
14817
- this.paneBounds = paneBounds;
14818
- this.lastTables = [];
14819
- if (getComputedStyle(container).position === "static") container.style.position = "relative";
14820
- this.root = document.createElement("div");
14821
- Object.assign(this.root.style, {
14822
- position: "absolute",
14823
- inset: "0",
14824
- pointerEvents: "none",
14825
- overflow: "hidden",
14826
- zIndex: "3"
14827
- });
14828
- container.appendChild(this.root);
14829
- }
14830
- update(tables) {
14831
- this.lastTables = tables;
14832
- this.root.replaceChildren();
14833
- for (const t of tables) {
14834
- if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
14835
- }
14836
- }
14837
- /** Re-render at the current pane geometry — after layout settles or on resize. */
14838
- reposition() {
14839
- if (this.root.isConnected) this.update(this.lastTables);
14840
- }
14841
- /** Show/hide the whole overlay. Tables anchor to pane corners, not to bars, so unlike the
14842
- * series content they DON'T vanish with an emptied chart — the loading state hides them. */
14843
- setVisible(visible) {
14844
- this.root.style.display = visible ? "" : "none";
14845
- }
14846
- destroy() {
14847
- this.root.remove();
14848
- }
14849
- renderTable(t) {
14850
- const b = this.paneBounds(t.paneId);
14851
- const wrap = document.createElement("div");
14852
- wrap.style.position = "absolute";
14853
- if (t.frameColor && t.frameWidth > 0) wrap.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
14854
- this.anchor(wrap, t.position, b);
14855
- const table = document.createElement("table");
14856
- Object.assign(table.style, {
14857
- borderCollapse: "collapse",
14858
- background: t.bgColor ?? "transparent",
14859
- fontFamily: this.theme.fontFamily || "sans-serif",
14860
- border: "none",
14861
- tableLayout: "auto",
14862
- // Re-enable pointer events on the table only (root is none) so cell tooltips work.
14863
- pointerEvents: "auto"
14864
- });
14865
- const { span, omit } = mergeRenderPlan(t);
14866
- const plotW = Math.max(0, (this.root.clientWidth || this.container.clientWidth) - b.rightAxis);
14867
- const paneH = b.height;
14868
- const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
14869
- for (let r = 0; r < t.rows; r += 1) {
14870
- const tr = document.createElement("tr");
14871
- for (let c = 0; c < t.columns; c += 1) {
14872
- if (omit.has(`${r}:${c}`)) continue;
14873
- const cell = t.cells[r]?.[c] ?? null;
14874
- const td = document.createElement("td");
14875
- if (cell === null) {
14876
- Object.assign(td.style, { padding: "0", border: "none" });
14877
- tr.appendChild(td);
14878
- continue;
14879
- }
14880
- const sp = span.get(`${r}:${c}`);
14881
- if (sp) {
14882
- if (sp.cs > 1) td.colSpan = sp.cs;
14883
- if (sp.rs > 1) td.rowSpan = sp.rs;
14884
- }
14885
- Object.assign(td.style, {
14886
- border: cellBorder,
14887
- padding: "2px 6px",
14888
- background: cell.bgColor ?? "transparent",
14889
- color: cell.textColor ?? this.theme.textColor,
14890
- textAlign: cell.hAlign,
14891
- verticalAlign: cell.vAlign === "top" ? "top" : cell.vAlign === "bottom" ? "bottom" : "middle",
14892
- fontSize: `${fontPxOf(cell.textSize)}px`,
14893
- fontFamily: cell.fontFamily === "monospace" ? "monospace" : "inherit",
14894
- fontWeight: cell.bold ? "bold" : "normal",
14895
- fontStyle: cell.italic ? "italic" : "normal",
14896
- // Pine cell text never wraps; `\n` still breaks lines. Wrapping
14897
- // used to collapse unicode sparklines and ━━━ dividers.
14898
- whiteSpace: "pre"
14899
- });
14900
- if (cell.width) td.style.width = `${cell.width / 100 * plotW}px`;
14901
- if (cell.height) td.style.height = `${cell.height / 100 * paneH}px`;
14902
- if (cell.tooltip) td.title = cell.tooltip;
14903
- td.textContent = cell.text ?? "";
14904
- tr.appendChild(td);
14905
- }
14906
- table.appendChild(tr);
14907
- }
14908
- wrap.appendChild(table);
14909
- return wrap;
14910
- }
14911
- /**
14912
- * Position the wrapper at a Pine `position.*` corner/edge of the table's PANE
14913
- * (not the whole chart), inset past the right price axis so it never overlaps
14914
- * the Y-axis labels.
14915
- */
14916
- anchor(el, position, b) {
14917
- const m = 6;
14918
- const containerH = this.root.clientHeight || this.container.clientHeight;
14919
- const paneBottom = b.top + b.height;
14920
- if (position.startsWith("top")) el.style.top = `${b.top + m}px`;
14921
- else if (position.startsWith("bottom")) el.style.bottom = `${Math.max(0, containerH - paneBottom) + m}px`;
14922
- else el.style.top = `${b.top + b.height / 2}px`;
14923
- if (position.endsWith("left")) el.style.left = `${m}px`;
14924
- else if (position.endsWith("right")) el.style.right = `${b.rightAxis + m}px`;
14925
- else el.style.left = `calc(50% - ${b.rightAxis / 2}px)`;
14926
- const tx = position.endsWith("center") ? "-50%" : "0";
14927
- const ty = position.startsWith("middle") ? "-50%" : "0";
14928
- if (tx !== "0" || ty !== "0") el.style.transform = `translate(${tx}, ${ty})`;
14929
- }
14930
- };
14931
-
14932
14927
  // src/renderers/native/capabilities.ts
14933
14928
  var NATIVE_CAPABILITIES = {
14934
14929
  panes: true,
@@ -14949,7 +14944,7 @@ var NATIVE_CAPABILITIES = {
14949
14944
  drawingDepth: true,
14950
14945
  // drawings share the series' z space (backend-composited interleave layers)
14951
14946
  tables: true,
14952
- // reuses the DOM TableOverlay
14947
+ // canvas-painted into the owning indicator's interleave slice
14953
14948
  trades: true,
14954
14949
  // strategy order-fill markers (arrows + labels + fill-price ticks)
14955
14950
  inputsUI: true
@@ -14980,6 +14975,9 @@ function candleTier(spacing) {
14980
14975
  if (spacing < CANDLE_BODY_MIN_SPACING) return "wick";
14981
14976
  return "full";
14982
14977
  }
14978
+ function snapY(yCss, dpr) {
14979
+ return Math.round(yCss * dpr) / dpr;
14980
+ }
14983
14981
  function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
14984
14982
  const wickDev = Math.max(1, Math.round(wickWidth(spacing) * dpr));
14985
14983
  const wickLeftDev = Math.round(xCss * dpr - wickDev / 2);
@@ -15786,11 +15784,9 @@ var WebGL2Backend = class {
15786
15784
  };
15787
15785
  b.alpha = this.modelAlpha;
15788
15786
  for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
15789
- for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
15790
15787
  if (isPrice) {
15791
15788
  for (const m of scene.indicators.values()) {
15792
15789
  for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
15793
- for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
15794
15790
  }
15795
15791
  }
15796
15792
  const drawCandles = isPrice && !scene.candlesHidden;
@@ -15806,6 +15802,7 @@ var WebGL2Backend = class {
15806
15802
  b.alpha = this.modelAlpha;
15807
15803
  const off = scene.offsetOf(m.id);
15808
15804
  const mp = effPane(m);
15805
+ for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, mp, coords, i0, i1, off);
15809
15806
  for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
15810
15807
  }
15811
15808
  if (drawCandles && !candleDrawn) {
@@ -15813,14 +15810,18 @@ var WebGL2Backend = class {
15813
15810
  b.alpha = this.candleStructureAlpha;
15814
15811
  this.emitPriceSeries(b, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
15815
15812
  }
15816
- drawSlicesUpTo(Infinity);
15817
15813
  b.alpha = this.modelAlpha;
15818
15814
  if (isPrice) {
15815
+ for (const m of scene.indicators.values()) {
15816
+ const off = scene.offsetOf(m.id);
15817
+ for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, off);
15818
+ }
15819
15819
  for (const m of scene.indicators.values()) {
15820
15820
  const off = scene.offsetOf(m.id);
15821
15821
  for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
15822
15822
  }
15823
15823
  }
15824
+ drawSlicesUpTo(Infinity);
15824
15825
  for (const m of models) {
15825
15826
  const mp = effPane(m);
15826
15827
  for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
@@ -16215,12 +16216,12 @@ var WebGL2Backend = class {
16215
16216
  if (drawBody) {
16216
16217
  const oY = coords.priceToY(bar.open, pane.scale, pane.bounds);
16217
16218
  const cY = coords.priceToY(bar.close, pane.scale, pane.bounds);
16218
- bodyTop = Math.min(oY, cY);
16219
- bodyH = Math.max(1, Math.abs(cY - oY));
16219
+ bodyTop = snapY(Math.min(oY, cY), coords.dpr);
16220
+ bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - bodyTop);
16220
16221
  }
16221
16222
  if (cs.wickVisible) {
16222
- const hY = coords.priceToY(bar.high, pane.scale, pane.bounds);
16223
- const lY = coords.priceToY(bar.low, pane.scale, pane.bounds);
16223
+ const hY = snapY(coords.priceToY(bar.high, pane.scale, pane.bounds), coords.dpr);
16224
+ const lY = snapY(coords.priceToY(bar.low, pane.scale, pane.bounds), coords.dpr);
16224
16225
  b.alpha = this.candleStructureAlpha;
16225
16226
  const wCol = parseColor((isUp ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : bodyColorStr));
16226
16227
  if (drawBody) {
@@ -17553,9 +17554,10 @@ var SceneGraph = class {
17553
17554
  * so each indicator arrives behind the candles (and behind older indicators);
17554
17555
  * `setIndicatorZ`/`bringToFront`/`sendToBack` change it. */
17555
17556
  this.seriesZ = /* @__PURE__ */ new Map();
17556
- /** Per-pane raster layers of user drawings interleaved into the series stack each is a
17557
+ /** Per-pane raster layers of drawings interleaved into the series stack (each
17558
+ * indicator's Pine drawings at its model's z, plus in-stack user drawings) — each a
17557
17559
  * prepainted canvas the backend composites just before the series carrying `beforeZ`.
17558
- * Rebuilt by the renderer per data frame; empty when every drawing sits over the stack. */
17560
+ * Rebuilt by the renderer per data frame. */
17559
17561
  this.drawingSlices = /* @__PURE__ */ new Map();
17560
17562
  /** Per-model index offset: the chart bar index of the model's `anchorTime` — its
17561
17563
  * index-aligned payloads (dense series arrays, `bar_index` drawings) count from that
@@ -17790,11 +17792,9 @@ var Canvas2dBackend = class {
17790
17792
  };
17791
17793
  ctx.globalAlpha = this.modelAlpha;
17792
17794
  for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
17793
- for (const m of models) for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, effPane(m), coords, i0, i1, scene.offsetOf(m.id));
17794
17795
  if (isPrice) {
17795
17796
  for (const m of scene.indicators.values()) {
17796
17797
  for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
17797
- for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
17798
17798
  }
17799
17799
  }
17800
17800
  const slices = scene.drawingSlices.get(pane.id) ?? [];
@@ -17818,6 +17818,7 @@ var Canvas2dBackend = class {
17818
17818
  ctx.globalAlpha = this.modelAlpha;
17819
17819
  const off = scene.offsetOf(m.id);
17820
17820
  const mp = effPane(m);
17821
+ for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, mp, coords, i0, i1, off);
17821
17822
  for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
17822
17823
  }
17823
17824
  if (drawCandles && !candleDrawn) {
@@ -17825,14 +17826,18 @@ var Canvas2dBackend = class {
17825
17826
  ctx.globalAlpha = this.candleStructureAlpha;
17826
17827
  this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
17827
17828
  }
17828
- drawSlicesUpTo(Infinity);
17829
17829
  if (isPrice) {
17830
17830
  ctx.globalAlpha = this.modelAlpha;
17831
+ for (const m of scene.indicators.values()) {
17832
+ const off = scene.offsetOf(m.id);
17833
+ for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, off);
17834
+ }
17831
17835
  for (const m of scene.indicators.values()) {
17832
17836
  const off = scene.offsetOf(m.id);
17833
17837
  for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
17834
17838
  }
17835
17839
  }
17840
+ drawSlicesUpTo(Infinity);
17836
17841
  ctx.globalAlpha = this.modelAlpha;
17837
17842
  for (const m of models) {
17838
17843
  const mp = effPane(m);
@@ -18076,13 +18081,13 @@ var Canvas2dBackend = class {
18076
18081
  if (drawBody) {
18077
18082
  const oY = coords.priceToY(b.open, pane.scale, pane.bounds);
18078
18083
  const cY = coords.priceToY(b.close, pane.scale, pane.bounds);
18079
- top = Math.min(oY, cY);
18080
- bodyH = Math.max(1, Math.abs(cY - oY));
18084
+ top = snapY(Math.min(oY, cY), coords.dpr);
18085
+ bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - top);
18081
18086
  }
18082
18087
  if (cs.wickVisible) {
18083
18088
  const wick = (up ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : color);
18084
- const hY = coords.priceToY(b.high, pane.scale, pane.bounds);
18085
- const lY = coords.priceToY(b.low, pane.scale, pane.bounds);
18089
+ const hY = snapY(coords.priceToY(b.high, pane.scale, pane.bounds), coords.dpr);
18090
+ const lY = snapY(coords.priceToY(b.low, pane.scale, pane.bounds), coords.dpr);
18086
18091
  ctx.globalAlpha = this.candleStructureAlpha;
18087
18092
  ctx.strokeStyle = wick;
18088
18093
  ctx.lineWidth = g.wickW;
@@ -18529,6 +18534,19 @@ function autoFontSize(lines, boxW, boxH, bold) {
18529
18534
 
18530
18535
  // src/renderers/shared/DrawingSceneRenderer.ts
18531
18536
  var EMPTY_DRAWING_SET = { lines: [], boxes: [], labels: [], polylines: [], linefills: [] };
18537
+ function modelDrawingSet(m, overlay) {
18538
+ const want = (d) => Boolean(d.overlay) === overlay;
18539
+ return {
18540
+ lines: (m.lines ?? []).filter(want),
18541
+ boxes: (m.boxes ?? []).filter(want),
18542
+ labels: (m.labels ?? []).filter(want),
18543
+ polylines: (m.polylines ?? []).filter(want),
18544
+ linefills: (m.linefills ?? []).filter(want)
18545
+ };
18546
+ }
18547
+ function drawingSetEmpty(s) {
18548
+ return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
18549
+ }
18532
18550
  function fontSizePx(size) {
18533
18551
  return size === "auto" ? 12 : namedFontSize(size);
18534
18552
  }
@@ -19381,10 +19399,8 @@ var ChromeRenderer = class {
19381
19399
  this.ctx = null;
19382
19400
  // The color for axis tick labels — the host-passed surface text, set each frame in render().
19383
19401
  this.axisTextColor = DARK_THEME.textColor;
19384
- // Shared Pine-drawing renderer (line/box/label/polyline/linefill); widthCache persists.
19402
+ // Shared Pine-drawing renderer, used here for autoscale geometry only; widthCache persists.
19385
19403
  this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
19386
- // Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per render).
19387
- this.labelTips = [];
19388
19404
  }
19389
19405
  mount(canvas) {
19390
19406
  this.canvas = canvas;
@@ -19408,8 +19424,8 @@ var ChromeRenderer = class {
19408
19424
  */
19409
19425
  paneDrawingsRange(ownModels, scene, isPricePane, vr) {
19410
19426
  let dr = null;
19411
- for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(this.ownDrawings(m), vr, scene.offsetOf(m.id)));
19412
- if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(this.overlayDrawings(m), vr, scene.offsetOf(m.id)));
19427
+ for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, false), vr, scene.offsetOf(m.id)));
19428
+ if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, true), vr, scene.offsetOf(m.id)));
19413
19429
  return dr;
19414
19430
  }
19415
19431
  /** Clear the chrome canvas and draw drawings + axes + current-price line.
@@ -19426,7 +19442,6 @@ var ChromeRenderer = class {
19426
19442
  const dataW = coords.width;
19427
19443
  const dataH = coords.height;
19428
19444
  this.axisTextColor = surface?.textColor ?? theme.textColor;
19429
- this.labelTips = [];
19430
19445
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
19431
19446
  ctx.clearRect(0, 0, fullW, fullH);
19432
19447
  if (surface && (fullW > dataW || fullH > dataH)) {
@@ -19442,17 +19457,6 @@ var ChromeRenderer = class {
19442
19457
  return;
19443
19458
  }
19444
19459
  const pricePane = panes.find((p) => p.kind === "price") ?? null;
19445
- for (const pane of panes) {
19446
- if (pane.collapsed) continue;
19447
- for (const m of scene.indicatorsForPane(pane.id)) {
19448
- const sc = scene.scaleFor(m, pane);
19449
- const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
19450
- this.renderDrawings(ctx, coords, this.ownDrawings(m), mp, dataW, scene.offsetOf(m.id));
19451
- }
19452
- }
19453
- if (pricePane) {
19454
- for (const m of scene.indicators.values()) this.renderDrawings(ctx, coords, this.overlayDrawings(m), pricePane, dataW, scene.offsetOf(m.id));
19455
- }
19456
19460
  if (pricePane && !pricePane.collapsed && scene.tradeMarkers.visible) {
19457
19461
  for (const m of scene.indicators.values()) {
19458
19462
  if (m.trades?.length) this.renderTrades(ctx, coords, scene, theme, m.trades, pricePane, dataW);
@@ -19468,25 +19472,6 @@ var ChromeRenderer = class {
19468
19472
  this.canvas = null;
19469
19473
  this.ctx = null;
19470
19474
  }
19471
- // ── Pine-drawing helpers (own vs force_overlay routing) ──
19472
- ownDrawings(m) {
19473
- return {
19474
- lines: (m.lines ?? []).filter((d) => !d.overlay),
19475
- boxes: (m.boxes ?? []).filter((d) => !d.overlay),
19476
- labels: (m.labels ?? []).filter((d) => !d.overlay),
19477
- polylines: (m.polylines ?? []).filter((d) => !d.overlay),
19478
- linefills: (m.linefills ?? []).filter((d) => !d.overlay)
19479
- };
19480
- }
19481
- overlayDrawings(m) {
19482
- return {
19483
- lines: (m.lines ?? []).filter((d) => d.overlay),
19484
- boxes: (m.boxes ?? []).filter((d) => d.overlay),
19485
- labels: (m.labels ?? []).filter((d) => d.overlay),
19486
- polylines: (m.polylines ?? []).filter((d) => d.overlay),
19487
- linefills: (m.linefills ?? []).filter((d) => d.overlay)
19488
- };
19489
- }
19490
19475
  drawingsRange(set, vr, indexOffset = 0) {
19491
19476
  this.drawScene.setSet(set, indexOffset);
19492
19477
  if (this.drawScene.isEmpty()) return null;
@@ -19520,34 +19505,6 @@ var ChromeRenderer = class {
19520
19505
  );
19521
19506
  ctx.restore();
19522
19507
  }
19523
- renderDrawings(ctx, coords, set, pane, dataW, indexOffset = 0) {
19524
- this.drawScene.setSet(set, indexOffset);
19525
- if (this.drawScene.isEmpty()) return;
19526
- ctx.save();
19527
- ctx.translate(0, pane.bounds.top);
19528
- ctx.beginPath();
19529
- ctx.rect(0, 0, dataW, pane.bounds.height);
19530
- ctx.clip();
19531
- this.drawScene.render(
19532
- ctx,
19533
- dataW,
19534
- pane.bounds.height,
19535
- (l) => coords.logicalToX(l),
19536
- (price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
19537
- );
19538
- ctx.restore();
19539
- for (const r of this.drawScene.labelTipRegions()) {
19540
- this.labelTips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
19541
- }
19542
- }
19543
- /** Tooltip of the topmost label under a plot-space point, or null. Fed by the last render. */
19544
- labelTooltipAt(x, y) {
19545
- for (let i = this.labelTips.length - 1; i >= 0; i -= 1) {
19546
- const r = this.labelTips[i];
19547
- if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
19548
- }
19549
- return null;
19550
- }
19551
19508
  // ── axes ──
19552
19509
  drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
19553
19510
  ctx.strokeStyle = scene.style.borderColor ?? theme.borderColor;
@@ -23956,17 +23913,17 @@ function glyphIcon(glyph) {
23956
23913
  return textGlyph(String(glyph), 15);
23957
23914
  }
23958
23915
  function stampSizeIcon(size) {
23959
- return textGlyph("\u25CF", (SIZE_PX4[String(size)] ?? 13) + 4);
23916
+ return textGlyph("\u25CF", (SIZE_PX3[String(size)] ?? 13) + 4);
23960
23917
  }
23961
23918
  function sizeIcon(size) {
23962
23919
  return textGlyph(String(size).charAt(0).toUpperCase(), 15);
23963
23920
  }
23964
- var SIZE_PX4 = { small: 10, normal: 13, large: 16, huge: 20 };
23921
+ var SIZE_PX3 = { small: 10, normal: 13, large: 16, huge: 20 };
23965
23922
  function numbersSizeIcon(size) {
23966
- return textGlyph("12", (SIZE_PX4[String(size)] ?? 13) - 1, 16.5, 'font-weight="600"');
23923
+ return textGlyph("12", (SIZE_PX3[String(size)] ?? 13) - 1, 16.5, 'font-weight="600"');
23967
23924
  }
23968
23925
  function labelSizeIcon(size) {
23969
- return textGlyph("T", (SIZE_PX4[String(size)] ?? 13) + 2);
23926
+ return textGlyph("T", (SIZE_PX3[String(size)] ?? 13) + 2);
23970
23927
  }
23971
23928
  function capitalize(s) {
23972
23929
  return s.charAt(0).toUpperCase() + s.slice(1);
@@ -24130,7 +24087,7 @@ var DrawingToolbar = class {
24130
24087
  this.root.replaceChildren();
24131
24088
  this.groupCells.clear();
24132
24089
  this.groupIcons.clear();
24133
- this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.onArm(null));
24090
+ this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.onCursorClick());
24134
24091
  this.root.appendChild(this.cursorBtn);
24135
24092
  if (this.def.groups.length > 0) this.root.appendChild(this.divider());
24136
24093
  for (const g of this.def.groups) {
@@ -24234,6 +24191,14 @@ var DrawingToolbar = class {
24234
24191
  this.magnetIcon = icon2;
24235
24192
  return cell;
24236
24193
  }
24194
+ /** Cursor returns to select/idle: an active measure/eraser mode exits through its own
24195
+ * toggle callback (disarming a tool via `onArm(null)` alone can't — the host treats a
24196
+ * null arm as a no-op side effect of entering those modes), then the tool disarms. */
24197
+ onCursorClick() {
24198
+ if (this.measureActive) this.onMeasure();
24199
+ if (this.eraserActive) this.onEraser();
24200
+ this.onArm(null);
24201
+ }
24237
24202
  /** Clicking the icon arms the group's last-used tool (it does NOT open the flyout). */
24238
24203
  onGroupIconClick(group) {
24239
24204
  const type = this.lastUsed.get(group.id) ?? group.tools[0]?.type;
@@ -25565,6 +25530,315 @@ var UserDrawingController = class {
25565
25530
  }
25566
25531
  };
25567
25532
 
25533
+ // src/renderers/shared/TableOverlay.ts
25534
+ var SIZE_PX4 = {
25535
+ auto: 13,
25536
+ tiny: 10,
25537
+ small: 11,
25538
+ normal: 13,
25539
+ large: 16,
25540
+ huge: 20
25541
+ };
25542
+ function fontPxOf(size) {
25543
+ if (typeof size === "number") return size > 0 ? size : SIZE_PX4.auto;
25544
+ return SIZE_PX4[size] ?? SIZE_PX4.auto;
25545
+ }
25546
+ function tableHasContent(t) {
25547
+ return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
25548
+ }
25549
+ function mergeRenderPlan(t) {
25550
+ const span = /* @__PURE__ */ new Map();
25551
+ const omit = /* @__PURE__ */ new Set();
25552
+ for (const m of t.merges) {
25553
+ span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
25554
+ for (let r = m.startRow; r <= m.endRow; r += 1) {
25555
+ for (let c = m.startCol; c <= m.endCol; c += 1) {
25556
+ if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
25557
+ }
25558
+ }
25559
+ }
25560
+ for (let r = 0; r < t.rows; r += 1) {
25561
+ for (let c = 0; c < t.columns; c += 1) {
25562
+ if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
25563
+ }
25564
+ }
25565
+ for (const key of span.keys()) omit.delete(key);
25566
+ return { span, omit };
25567
+ }
25568
+
25569
+ // src/renderers/shared/TableCanvasRenderer.ts
25570
+ var PAD_X = 6;
25571
+ var PAD_Y = 2;
25572
+ var MARGIN = 6;
25573
+ var LINE_HEIGHT = 1.2;
25574
+ function paintTable(ctx, t, args, tips) {
25575
+ if (!tableHasContent(t)) return;
25576
+ const layout = layoutTable(ctx, t, args);
25577
+ if (!layout || layout.w <= 0 || layout.h <= 0) return;
25578
+ const fw = t.frameColor && t.frameWidth > 0 ? t.frameWidth : 0;
25579
+ const { x, y } = anchorOrigin(t.position, layout.w + 2 * fw, layout.h + 2 * fw, args);
25580
+ const x0 = x + fw;
25581
+ const y0 = y + fw;
25582
+ if (t.bgColor) {
25583
+ ctx.fillStyle = t.bgColor;
25584
+ ctx.fillRect(x0, y0, layout.w, layout.h);
25585
+ }
25586
+ if (fw > 0 && t.frameColor) {
25587
+ ctx.strokeStyle = t.frameColor;
25588
+ ctx.lineWidth = fw;
25589
+ ctx.strokeRect(x + fw / 2, y + fw / 2, layout.w + fw, layout.h + fw);
25590
+ }
25591
+ const colX = [0];
25592
+ for (const w of layout.colW) colX.push(colX[colX.length - 1] + w);
25593
+ const rowY = [0];
25594
+ for (const h of layout.rowH) rowY.push(rowY[rowY.length - 1] + h);
25595
+ const prevBaseline = ctx.textBaseline;
25596
+ const prevAlign = ctx.textAlign;
25597
+ ctx.textBaseline = "middle";
25598
+ for (const box of layout.boxes) {
25599
+ const rx = x0 + colX[box.c];
25600
+ const ry = y0 + rowY[box.r];
25601
+ const rw = colX[box.c + box.cs] - colX[box.c];
25602
+ const rh = rowY[box.r + box.rs] - rowY[box.r];
25603
+ const cell = box.cell;
25604
+ if (cell.bgColor) {
25605
+ ctx.fillStyle = cell.bgColor;
25606
+ ctx.fillRect(rx, ry, rw, rh);
25607
+ }
25608
+ const text = cell.text ?? "";
25609
+ if (text.length > 0) {
25610
+ const px = fontPxOf(cell.textSize);
25611
+ ctx.font = cellFont(cell, px, args.theme);
25612
+ ctx.fillStyle = cell.textColor ?? args.theme.textColor;
25613
+ const lines = text.split("\n");
25614
+ const blockH = lines.length * px * LINE_HEIGHT;
25615
+ const blockTop = cell.vAlign === "top" ? ry + PAD_Y : cell.vAlign === "bottom" ? ry + rh - PAD_Y - blockH : ry + (rh - blockH) / 2;
25616
+ const tx = cell.hAlign === "left" ? rx + PAD_X : cell.hAlign === "right" ? rx + rw - PAD_X : rx + rw / 2;
25617
+ ctx.textAlign = cell.hAlign;
25618
+ lines.forEach((line, i) => ctx.fillText(line, tx, blockTop + (i + 0.5) * px * LINE_HEIGHT));
25619
+ }
25620
+ if (cell.tooltip) tips.push({ left: rx, top: ry, right: rx + rw, bottom: ry + rh, text: cell.tooltip });
25621
+ }
25622
+ ctx.textBaseline = prevBaseline;
25623
+ ctx.textAlign = prevAlign;
25624
+ if (t.borderColor && t.borderWidth > 0) {
25625
+ ctx.strokeStyle = t.borderColor;
25626
+ ctx.lineWidth = t.borderWidth;
25627
+ const seen = /* @__PURE__ */ new Set();
25628
+ ctx.beginPath();
25629
+ const edge = (ax, ay, bx, by) => {
25630
+ const key = `${ax},${ay},${bx},${by}`;
25631
+ if (seen.has(key)) return;
25632
+ seen.add(key);
25633
+ ctx.moveTo(ax, ay);
25634
+ ctx.lineTo(bx, by);
25635
+ };
25636
+ for (const box of layout.boxes) {
25637
+ const l = Math.round(x0 + colX[box.c]);
25638
+ const r = Math.round(x0 + colX[box.c + box.cs]);
25639
+ const tp = Math.round(y0 + rowY[box.r]);
25640
+ const bt = Math.round(y0 + rowY[box.r + box.rs]);
25641
+ edge(l, tp, r, tp);
25642
+ edge(l, bt, r, bt);
25643
+ edge(l, tp, l, bt);
25644
+ edge(r, tp, r, bt);
25645
+ }
25646
+ ctx.stroke();
25647
+ }
25648
+ }
25649
+ function layoutTable(ctx, t, args) {
25650
+ const { span, omit } = mergeRenderPlan(t);
25651
+ const colW = new Array(t.columns).fill(0);
25652
+ const rowH = new Array(t.rows).fill(0);
25653
+ const boxes = [];
25654
+ for (let r = 0; r < t.rows; r += 1) {
25655
+ for (let c = 0; c < t.columns; c += 1) {
25656
+ if (omit.has(`${r}:${c}`)) continue;
25657
+ const cell = t.cells[r]?.[c];
25658
+ if (cell == null) continue;
25659
+ const sp = span.get(`${r}:${c}`);
25660
+ boxes.push({ cell, r, c, cs: Math.min(sp?.cs ?? 1, t.columns - c), rs: Math.min(sp?.rs ?? 1, t.rows - r) });
25661
+ }
25662
+ }
25663
+ if (boxes.length === 0) return null;
25664
+ const sizeOf = (cell) => {
25665
+ const px = fontPxOf(cell.textSize);
25666
+ ctx.font = cellFont(cell, px, args.theme);
25667
+ const lines = (cell.text ?? "").split("\n");
25668
+ let maxW = 0;
25669
+ for (const line of lines) maxW = Math.max(maxW, ctx.measureText(line).width);
25670
+ let w2 = Math.ceil(maxW) + 2 * PAD_X;
25671
+ let h2 = Math.ceil(lines.length * px * LINE_HEIGHT) + 2 * PAD_Y;
25672
+ if (cell.width) w2 = Math.max(w2, cell.width / 100 * args.plotWidth);
25673
+ if (cell.height) h2 = Math.max(h2, cell.height / 100 * args.paneHeight);
25674
+ return { w: w2, h: h2 };
25675
+ };
25676
+ const spanning = [];
25677
+ for (const box of boxes) {
25678
+ const { w: w2, h: h2 } = sizeOf(box.cell);
25679
+ if (box.cs === 1) colW[box.c] = Math.max(colW[box.c], w2);
25680
+ if (box.rs === 1) rowH[box.r] = Math.max(rowH[box.r], h2);
25681
+ if (box.cs > 1 || box.rs > 1) spanning.push({ box, w: w2, h: h2 });
25682
+ }
25683
+ for (const { box, w: w2, h: h2 } of spanning) {
25684
+ if (box.cs > 1) {
25685
+ let sum = 0;
25686
+ for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
25687
+ if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
25688
+ }
25689
+ if (box.rs > 1) {
25690
+ let sum = 0;
25691
+ for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
25692
+ if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
25693
+ }
25694
+ }
25695
+ let w = 0;
25696
+ for (const cw of colW) w += cw;
25697
+ let h = 0;
25698
+ for (const rh of rowH) h += rh;
25699
+ return { colW, rowH, w, h, boxes };
25700
+ }
25701
+ function anchorOrigin(position, totalW, totalH, args) {
25702
+ let y;
25703
+ if (position.startsWith("top")) y = MARGIN;
25704
+ else if (position.startsWith("bottom")) y = args.paneHeight - MARGIN - totalH;
25705
+ else y = args.paneHeight / 2 - totalH / 2;
25706
+ let x;
25707
+ if (position.endsWith("left")) x = MARGIN;
25708
+ else if (position.endsWith("right")) x = args.plotWidth - MARGIN - totalW;
25709
+ else x = args.plotWidth / 2 - totalW / 2;
25710
+ return { x, y };
25711
+ }
25712
+ function cellFont(cell, px, theme) {
25713
+ const family = cell.fontFamily === "monospace" ? "monospace" : theme.fontFamily || "sans-serif";
25714
+ return `${cell.italic ? "italic " : ""}${cell.bold ? "bold " : ""}${px}px ${family}`;
25715
+ }
25716
+
25717
+ // src/renderers/native/drawings/IndicatorDrawingSlices.ts
25718
+ function indicatorSliceKey(z, boundaries) {
25719
+ return boundaries.find((b) => b > z) ?? Infinity;
25720
+ }
25721
+ var IndicatorDrawingSlices = class {
25722
+ constructor() {
25723
+ this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
25724
+ /** Slice canvas cache, keyed `paneId|beforeZ` — same lifecycle as the user-drawing cache. */
25725
+ this.sliceCache = /* @__PURE__ */ new Map();
25726
+ /** Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per prepare). */
25727
+ this.tips = [];
25728
+ }
25729
+ /**
25730
+ * Rebuild the per-indicator drawing slices for this data frame. `ref` is the data
25731
+ * canvas the slices must match pixel-for-pixel (the backend composites them 1:1).
25732
+ * Runs from the renderer's data paint, just before the backend composites the scene.
25733
+ */
25734
+ prepare(scene, coords, theme, ref) {
25735
+ this.tips = [];
25736
+ const out = /* @__PURE__ */ new Map();
25737
+ if (ref.width === 0 || ref.height === 0) {
25738
+ this.sliceCache.clear();
25739
+ return out;
25740
+ }
25741
+ this.drawScene.setDeps({
25742
+ timeToLogical: (ms) => coords.timeToLogical(ms),
25743
+ barAt: (logical) => {
25744
+ const b = scene.bars[Math.round(logical)];
25745
+ return b ? { high: b.high, low: b.low } : null;
25746
+ },
25747
+ theme
25748
+ });
25749
+ const dpr = coords.dpr;
25750
+ const dataW = coords.width;
25751
+ const buckets = /* @__PURE__ */ new Map();
25752
+ const add = (paneId, beforeZ, entry) => {
25753
+ const key = `${paneId}|${beforeZ}`;
25754
+ const bucket = buckets.get(key);
25755
+ if (bucket) bucket.entries.push(entry);
25756
+ else buckets.set(key, { paneId, beforeZ, entries: [entry] });
25757
+ };
25758
+ for (const pane of scene.orderedPanes()) {
25759
+ if (pane.collapsed) continue;
25760
+ const boundaries = scene.seriesBoundaries(pane.id);
25761
+ for (const m of scene.orderedIndicatorsForPane(pane.id)) {
25762
+ const set = modelDrawingSet(m, false);
25763
+ const tables = (m.tables ?? []).filter((t) => !t.overlay);
25764
+ if (drawingSetEmpty(set) && tables.length === 0) continue;
25765
+ const sc = scene.scaleFor(m, pane);
25766
+ const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
25767
+ const beforeZ = indicatorSliceKey(scene.zOf(m.id), boundaries);
25768
+ add(pane.id, beforeZ, { set, tables, pane: mp, indexOffset: scene.offsetOf(m.id) });
25769
+ }
25770
+ if (pane.kind === "price") {
25771
+ for (const m of scene.indicators.values()) {
25772
+ const set = modelDrawingSet(m, true);
25773
+ const tables = (m.tables ?? []).filter((t) => t.overlay === true);
25774
+ if (drawingSetEmpty(set) && tables.length === 0) continue;
25775
+ add(pane.id, Infinity, { set, tables, pane, indexOffset: scene.offsetOf(m.id) });
25776
+ }
25777
+ }
25778
+ }
25779
+ for (const [key, { paneId, beforeZ, entries }] of buckets) {
25780
+ let canvas = this.sliceCache.get(key);
25781
+ if (!canvas) {
25782
+ canvas = document.createElement("canvas");
25783
+ this.sliceCache.set(key, canvas);
25784
+ }
25785
+ if (canvas.width !== ref.width || canvas.height !== ref.height) {
25786
+ canvas.width = ref.width;
25787
+ canvas.height = ref.height;
25788
+ }
25789
+ const ctx = canvas.getContext("2d");
25790
+ if (!ctx) continue;
25791
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
25792
+ ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
25793
+ for (const e of entries) this.paintEntry(ctx, e, coords, dataW, theme);
25794
+ const slices = out.get(paneId) ?? [];
25795
+ slices.push({ beforeZ, canvas });
25796
+ out.set(paneId, slices);
25797
+ }
25798
+ for (const key of [...this.sliceCache.keys()]) if (!buckets.has(key)) this.sliceCache.delete(key);
25799
+ for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
25800
+ return out;
25801
+ }
25802
+ paintEntry(ctx, e, coords, dataW, theme) {
25803
+ const { pane } = e;
25804
+ const paneTips = [];
25805
+ ctx.save();
25806
+ ctx.translate(0, pane.bounds.top);
25807
+ ctx.beginPath();
25808
+ ctx.rect(0, 0, dataW, pane.bounds.height);
25809
+ ctx.clip();
25810
+ this.drawScene.setSet(e.set, e.indexOffset);
25811
+ this.drawScene.render(
25812
+ ctx,
25813
+ dataW,
25814
+ pane.bounds.height,
25815
+ (l) => coords.logicalToX(l),
25816
+ (price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
25817
+ );
25818
+ paneTips.push(...this.drawScene.labelTipRegions());
25819
+ for (const t of e.tables) paintTable(ctx, t, { paneHeight: pane.bounds.height, plotWidth: dataW, theme }, paneTips);
25820
+ ctx.restore();
25821
+ for (const r of paneTips) {
25822
+ this.tips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
25823
+ }
25824
+ }
25825
+ /** Tooltip of the topmost label or table cell under a plot-space point, or null. Fed by the last prepare. */
25826
+ labelTooltipAt(x, y) {
25827
+ for (let i = this.tips.length - 1; i >= 0; i -= 1) {
25828
+ const r = this.tips[i];
25829
+ if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
25830
+ }
25831
+ return null;
25832
+ }
25833
+ };
25834
+ function mergeSlices(indicator, user) {
25835
+ const out = /* @__PURE__ */ new Map();
25836
+ for (const [paneId, slices] of indicator) out.set(paneId, [...slices]);
25837
+ for (const [paneId, slices] of user) out.set(paneId, [...out.get(paneId) ?? [], ...slices]);
25838
+ for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
25839
+ return out;
25840
+ }
25841
+
25568
25842
  // src/renderers/native/drawings/Projector.ts
25569
25843
  function createProjector(coords, paneOf, paneIdAtY, barsInRange) {
25570
25844
  return {
@@ -26444,6 +26718,11 @@ var NativeRenderer = class {
26444
26718
  this.vpvrRenderer = new VpvrRenderer();
26445
26719
  this.resizeObserver = null;
26446
26720
  this.dprMedia = null;
26721
+ /** Plot size in INTEGER device px, as last reported by the resize observer's
26722
+ * device-pixel-content-box — the browser's own statement of how many device pixels
26723
+ * it paints the plot into. `null` until the first report or where the box type is
26724
+ * unsupported (WebKit); syncSize then falls back to rounding the client rect. */
26725
+ this.plotDeviceSize = null;
26447
26726
  this.coords = new CoordinateSystem();
26448
26727
  this.scene = new SceneGraph();
26449
26728
  // chosen at mount (WebGL2 if available, else canvas2d)
@@ -26451,6 +26730,8 @@ var NativeRenderer = class {
26451
26730
  this.glowAmount = 0;
26452
26731
  // WebGL2 neon-glow intensity (canvas2d ignores it)
26453
26732
  this.chrome = new ChromeRenderer();
26733
+ /** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
26734
+ this.indicatorSlices = new IndicatorDrawingSlices();
26454
26735
  /** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
26455
26736
  this.labelTooltip = null;
26456
26737
  this.crosshairLayer = new CrosshairRenderer();
@@ -26599,7 +26880,6 @@ var NativeRenderer = class {
26599
26880
  this.toggleVisibleCbs = /* @__PURE__ */ new Set();
26600
26881
  this.moveIndicatorCbs = /* @__PURE__ */ new Set();
26601
26882
  this.priceStyleCbs = /* @__PURE__ */ new Set();
26602
- this.tableOverlays = /* @__PURE__ */ new Map();
26603
26883
  this.name = "native";
26604
26884
  this.features = ["logScale", "currentPriceLine", "priceLabel", "countdown", "upColor", "downColor", "glow", "animZoom", "animPan", "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", "indicatorTitles", "indicatorValues"];
26605
26885
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
@@ -26913,7 +27193,6 @@ var NativeRenderer = class {
26913
27193
  * hidden) on clear so a re-show picks up the current theme.
26914
27194
  */
26915
27195
  setLoading(loading) {
26916
- for (const overlay of this.tableOverlays.values()) overlay.setVisible(!loading);
26917
27196
  if (!loading || !this.wrapper) {
26918
27197
  this.loadingEl?.remove();
26919
27198
  this.loadingEl = null;
@@ -27575,7 +27854,7 @@ var NativeRenderer = class {
27575
27854
  this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
27576
27855
  this.labelTooltip = new LabelTooltip(this.plot, {
27577
27856
  theme: () => this.chromeTheme(),
27578
- lookup: (x, y) => this.chrome.labelTooltipAt(x, y)
27857
+ lookup: (x, y) => this.indicatorSlices.labelTooltipAt(x, y)
27579
27858
  });
27580
27859
  this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
27581
27860
  projector: () => this.drawingProjector(),
@@ -27660,6 +27939,7 @@ var NativeRenderer = class {
27660
27939
  this.emitPaneAction({ type: "maximize", paneId, maximized });
27661
27940
  }
27662
27941
  });
27942
+ this.paneControls.setSuspended(this.layoutMode === "mobile");
27663
27943
  this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
27664
27944
  panes: () => this.axisScaleViews(),
27665
27945
  rightAxis: () => this.rightAxisW,
@@ -27669,8 +27949,19 @@ var NativeRenderer = class {
27669
27949
  if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
27670
27950
  }
27671
27951
  });
27672
- this.resizeObserver = new ResizeObserver(() => this.resize());
27952
+ this.resizeObserver = new ResizeObserver((entries) => {
27953
+ for (const e of entries) {
27954
+ if (e.target !== this.plot) continue;
27955
+ const s = e.devicePixelContentBoxSize?.[0];
27956
+ if (s) this.plotDeviceSize = { width: s.inlineSize, height: s.blockSize };
27957
+ }
27958
+ this.resize();
27959
+ });
27673
27960
  this.resizeObserver.observe(this.wrapper);
27961
+ try {
27962
+ this.resizeObserver.observe(this.plot, { box: "device-pixel-content-box" });
27963
+ } catch {
27964
+ }
27674
27965
  this.watchDpr();
27675
27966
  this.syncSize();
27676
27967
  }
@@ -27795,8 +28086,6 @@ var NativeRenderer = class {
27795
28086
  this.inputsUI?.destroy();
27796
28087
  this.paneControls?.destroy();
27797
28088
  this.axisScaleButtons?.destroy();
27798
- for (const overlay of this.tableOverlays.values()) overlay.destroy();
27799
- this.tableOverlays.clear();
27800
28089
  this.resizeObserver?.disconnect();
27801
28090
  this.resizeObserver = null;
27802
28091
  this.dprMedia?.removeEventListener("change", this.onDprChange);
@@ -27825,6 +28114,7 @@ var NativeRenderer = class {
27825
28114
  this.attributionEl = null;
27826
28115
  this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
27827
28116
  this.mountContainer?.style.removeProperty("--vela-scale-gutter");
28117
+ this.mountContainer?.style.removeProperty("--vela-bottom-gutter");
27828
28118
  this.mountContainer?.style.removeProperty("--vela-price-pane-top");
27829
28119
  this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
27830
28120
  this.mountContainer = null;
@@ -27917,7 +28207,6 @@ var NativeRenderer = class {
27917
28207
  ensurePane(pane) {
27918
28208
  this.scene.ensurePane(pane.id, pane.kind, pane.order, pane.heightWeight ?? (pane.kind === "price" ? 3 : 1));
27919
28209
  this.layoutPanes();
27920
- this.repositionTables();
27921
28210
  this.paneControls?.refresh();
27922
28211
  this.scheduler.invalidate(4 /* Full */);
27923
28212
  }
@@ -27939,17 +28228,14 @@ var NativeRenderer = class {
27939
28228
  if (!model.ownScale) this.scene.dropIndicatorScale(handle.id);
27940
28229
  this.inputsUI.setPane(handle.id, paneId);
27941
28230
  this.refreshAnchorOffset(model);
27942
- this.syncTables(model);
27943
28231
  this.refreshAxisWidth();
27944
28232
  this.layoutPanes();
27945
- this.repositionTables();
27946
28233
  this.paneControls?.refresh();
27947
28234
  this.scheduler.invalidate(4 /* Full */);
27948
28235
  }
27949
28236
  orderPanes(orderedIds) {
27950
28237
  this.scene.orderPanes(orderedIds);
27951
28238
  this.layoutPanes();
27952
- this.repositionTables();
27953
28239
  this.paneControls?.refresh();
27954
28240
  this.scheduler.invalidate(4 /* Full */);
27955
28241
  }
@@ -27958,7 +28244,6 @@ var NativeRenderer = class {
27958
28244
  if (!pane || pane.collapsed === collapsed) return;
27959
28245
  pane.collapsed = collapsed;
27960
28246
  this.layoutPanes();
27961
- this.repositionTables();
27962
28247
  this.paneControls?.refresh();
27963
28248
  this.scheduler.invalidate(4 /* Full */);
27964
28249
  }
@@ -27966,7 +28251,6 @@ var NativeRenderer = class {
27966
28251
  if (paneId !== null && !this.scene.panes.has(paneId)) paneId = null;
27967
28252
  this.maximizedPaneId = paneId;
27968
28253
  this.layoutPanes();
27969
- this.repositionTables();
27970
28254
  this.paneControls?.refresh();
27971
28255
  this.scheduler.invalidate(4 /* Full */);
27972
28256
  }
@@ -28053,7 +28337,6 @@ var NativeRenderer = class {
28053
28337
  native: !!model.native,
28054
28338
  ...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
28055
28339
  });
28056
- this.syncTables(model);
28057
28340
  if (model.native?.type === "volume") {
28058
28341
  this.volumeActive = true;
28059
28342
  this.volumeHidden = false;
@@ -28077,7 +28360,6 @@ var NativeRenderer = class {
28077
28360
  }
28078
28361
  }
28079
28362
  applyPatch(model, patch);
28080
- this.syncTables(model);
28081
28363
  this.scheduler.invalidate(3 /* Light */);
28082
28364
  }
28083
28365
  removeIndicator(handle) {
@@ -28096,8 +28378,6 @@ var NativeRenderer = class {
28096
28378
  this.scene.forgetAnchorOffset(handle.id);
28097
28379
  this.scene.dropIndicatorScale(handle.id);
28098
28380
  this.inputsUI.remove(handle.id);
28099
- this.tableOverlays.get(handle.id)?.destroy();
28100
- this.tableOverlays.delete(handle.id);
28101
28381
  this.refreshAxisWidth();
28102
28382
  this.paneControls?.refresh();
28103
28383
  this.scheduler.invalidate(4 /* Full */);
@@ -28141,8 +28421,6 @@ var NativeRenderer = class {
28141
28421
  }
28142
28422
  if (!visible) {
28143
28423
  this.scene.indicators.delete(handle.id);
28144
- this.tableOverlays.get(handle.id)?.destroy();
28145
- this.tableOverlays.delete(handle.id);
28146
28424
  }
28147
28425
  this.inputsUI.setVisible(handle.id, visible);
28148
28426
  this.scheduler.invalidate(4 /* Full */);
@@ -28188,6 +28466,7 @@ var NativeRenderer = class {
28188
28466
  this.userDrawings?.setLayoutMode(mode);
28189
28467
  this.settingsDialog?.setLayoutMode(mode);
28190
28468
  this.inputsUI?.setLayoutMode(mode);
28469
+ this.paneControls?.setSuspended(mode === "mobile");
28191
28470
  if (this.scrollButton) {
28192
28471
  const px = mode === "mobile" ? SCROLL_BTN_SIZE_TOUCH : SCROLL_BTN_SIZE;
28193
28472
  this.scrollButton.style.width = `${px}px`;
@@ -28583,7 +28862,6 @@ var NativeRenderer = class {
28583
28862
  /** Relayout + repaint + refresh the hover buttons after a collapse/maximize/order change. */
28584
28863
  afterPaneLayoutChange() {
28585
28864
  this.layoutPanes();
28586
- this.repositionTables();
28587
28865
  this.paneControls?.refresh();
28588
28866
  this.scheduler.invalidate(4 /* Full */);
28589
28867
  }
@@ -28664,7 +28942,6 @@ var NativeRenderer = class {
28664
28942
  above.heightWeight = next.above;
28665
28943
  below.heightWeight = next.below;
28666
28944
  this.layoutPanes();
28667
- this.repositionTables();
28668
28945
  this.scheduler.invalidate(4 /* Full */);
28669
28946
  }
28670
28947
  /** Double-click a separator → split the two adjacent panes evenly (each gets half of
@@ -28679,7 +28956,6 @@ var NativeRenderer = class {
28679
28956
  above.heightWeight = half;
28680
28957
  below.heightWeight = half;
28681
28958
  this.layoutPanes();
28682
- this.repositionTables();
28683
28959
  this.scheduler.invalidate(4 /* Full */);
28684
28960
  }
28685
28961
  // ── keyboard navigation / accessibility (item 11) ──
@@ -28958,7 +29234,10 @@ var NativeRenderer = class {
28958
29234
  const liveActual = li >= 0 ? this.bars[li] : void 0;
28959
29235
  const easeLive = !!liveActual && this.liveEaseTime === liveActual.time && (liveActual.high !== this.liveEaseHigh || liveActual.low !== this.liveEaseLow || liveActual.close !== this.liveEaseClose);
28960
29236
  if (easeLive && liveActual) this.bars[li] = { ...liveActual, high: this.liveEaseHigh, low: this.liveEaseLow, close: this.liveEaseClose };
28961
- this.scene.drawingSlices = this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map();
29237
+ this.scene.drawingSlices = mergeSlices(
29238
+ this.indicatorSlices.prepare(this.scene, this.coords, this.theme, this.dataCanvas),
29239
+ this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map()
29240
+ );
28962
29241
  this.backdropRenderer.render(this.scene, this.coords, this.theme, gridAlpha);
28963
29242
  this.backend.render(this.scene, this.coords, this.theme);
28964
29243
  this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
@@ -29396,27 +29675,6 @@ var NativeRenderer = class {
29396
29675
  }
29397
29676
  return maxVol;
29398
29677
  }
29399
- /** Create/update/destroy an indicator's DOM table overlay (anchored off real pane geometry). */
29400
- syncTables(model) {
29401
- const tables = model.tables ?? [];
29402
- let overlay = this.tableOverlays.get(model.id);
29403
- if (tables.length === 0) {
29404
- if (overlay) {
29405
- overlay.destroy();
29406
- this.tableOverlays.delete(model.id);
29407
- }
29408
- return;
29409
- }
29410
- if (!overlay) {
29411
- overlay = new TableOverlay(this.plot, this.theme, (id) => this.paneBoundsFor(id));
29412
- overlay.setVisible(this.loadingEl === null);
29413
- this.tableOverlays.set(model.id, overlay);
29414
- }
29415
- overlay.update(tables);
29416
- }
29417
- repositionTables() {
29418
- for (const overlay of this.tableOverlays.values()) overlay.reposition();
29419
- }
29420
29678
  layoutPanes() {
29421
29679
  const panes = this.scene.orderedPanes();
29422
29680
  const dataHeight = this.coords.height;
@@ -29467,6 +29725,7 @@ var NativeRenderer = class {
29467
29725
  const visible = maxPane ? [maxPane] : this.scene.orderedPanes().filter((p) => !p.collapsed);
29468
29726
  const paneBottom = visible.length ? Math.max(...visible.map((p) => p.bounds.top + p.bounds.height)) : dataHeight;
29469
29727
  this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM + Math.max(0, dataHeight - paneBottom);
29728
+ this.mountContainer?.style.setProperty("--vela-bottom-gutter", `${TIME_AXIS_H + Math.max(0, dataHeight - paneBottom)}px`);
29470
29729
  this.scrollBtnRightPx = this.rightAxisW + SCROLL_BTN_RIGHT_INSET;
29471
29730
  if (this.scrollButton) {
29472
29731
  this.scrollButton.style.bottom = `${this.scrollBtnBottomPx}px`;
@@ -29559,30 +29818,33 @@ var NativeRenderer = class {
29559
29818
  if (w <= 0 || h <= 0) return;
29560
29819
  const dpr = window.devicePixelRatio || 1;
29561
29820
  this.plot.style.left = `${this.toolbarGutter}px`;
29562
- const pw = Math.max(1, w - this.toolbarGutter);
29563
- const ph = h;
29564
- this.dataCanvas.width = Math.round(pw * dpr);
29565
- this.dataCanvas.height = Math.round(ph * dpr);
29566
- this.backdropCanvas.width = this.dataCanvas.width;
29567
- this.backdropCanvas.height = this.dataCanvas.height;
29568
- this.volumeCanvas.width = this.dataCanvas.width;
29569
- this.volumeCanvas.height = this.dataCanvas.height;
29570
- for (const l of this.extLayers) {
29571
- l.canvas.width = this.dataCanvas.width;
29572
- l.canvas.height = this.dataCanvas.height;
29573
- }
29574
- this.vpvrCanvas.width = this.dataCanvas.width;
29575
- this.vpvrCanvas.height = this.dataCanvas.height;
29576
- this.chromeCanvas.width = this.dataCanvas.width;
29577
- this.chromeCanvas.height = this.dataCanvas.height;
29578
- this.drawingsCanvas.width = this.dataCanvas.width;
29579
- this.drawingsCanvas.height = this.dataCanvas.height;
29580
- this.cursorCanvas.width = this.dataCanvas.width;
29581
- this.cursorCanvas.height = this.dataCanvas.height;
29821
+ const rect = this.plot.getBoundingClientRect();
29822
+ let bw = Math.max(1, Math.round(rect.width * dpr));
29823
+ let bh = Math.max(1, Math.round(rect.height * dpr));
29824
+ const dev = this.plotDeviceSize;
29825
+ if (dev && Math.abs(dev.width - rect.width * dpr) <= 1 && Math.abs(dev.height - rect.height * dpr) <= 1) {
29826
+ bw = Math.max(1, dev.width);
29827
+ bh = Math.max(1, dev.height);
29828
+ }
29829
+ const pw = bw / dpr;
29830
+ const ph = bh / dpr;
29831
+ const size = (canvas) => {
29832
+ canvas.width = bw;
29833
+ canvas.height = bh;
29834
+ canvas.style.width = `${pw}px`;
29835
+ canvas.style.height = `${ph}px`;
29836
+ };
29837
+ size(this.dataCanvas);
29838
+ size(this.backdropCanvas);
29839
+ size(this.volumeCanvas);
29840
+ for (const l of this.extLayers) size(l.canvas);
29841
+ size(this.vpvrCanvas);
29842
+ size(this.chromeCanvas);
29843
+ size(this.drawingsCanvas);
29844
+ size(this.cursorCanvas);
29582
29845
  this.coords.setSize(Math.max(1, pw - this.rightAxisW), Math.max(1, ph - TIME_AXIS_H), dpr);
29583
29846
  this.scene.crosshair = null;
29584
29847
  this.layoutPanes();
29585
- this.repositionTables();
29586
29848
  this.userDrawings?.onResize();
29587
29849
  if (!this.didInitialFit && this.coords.barCount > 0) {
29588
29850
  this.fitContent();