@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
@@ -1,6 +1,6 @@
1
- import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-EQCHJZOT.js';
2
- import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, CalloutBubble, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-FCVIG7JG.js';
3
- import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-WFSZBX3R.js';
1
+ import { deserializeDrawing, chartTypes, rendererLayers, foldBaseModulation, registerChartType, rendererDefaults, getDrawingType, settingsRowVisible, normalizeSettingsRow, TextLabel, Callout, resetDrawingSettings, registerNativeIndicator, chartType, getNativeIndicator, nativeIndicatorDescriptors, drawingTypes, settingsRowValueKeys, formatDuration, RegressionChannel, FixedRangeVolumeProfile, SegmentDrawing, ArrowMark, GlyphStamp, RadialFib, FibSpiral, DedekindTessellation, MachFigure, GannSquare, FibRatios, MeasureBox, PositionTool, AnchoredVwap, PatternDrawing, Comment, PriceNote, Signpost, Note, PriceLabel, GANN_SQUARE_ARCS, lineSegmentIntersection, DEFAULT_DRAWING_COLOR, GLYPH_OPTIONS, STAMP_SIZE_OPTIONS, LINE_STYLE_OPTIONS, DEDEKIND_CURVATURE_OPTIONS, MACH_NUMBER_OPTIONS, MACH_WAVE_COUNT_OPTIONS, TEXT_SIZE_OPTIONS, createDrawing, inputVisible, tickerModifierIds, CalloutBase, DIRECTION_OPTIONS } from './chunk-4KZVZ7QQ.js';
2
+ import { themeTokens, Dialog, closeOpenPopovers, closeWidthPopover, fieldSection, buildFieldControl, fieldSeparator, fieldGrid, fieldRow, blendOver, splitColor, CalloutBubble, Menu, TextArea, Popover, NumberInput, buildColorPicker, isPopoverOpen, eventDismissedPopover, toggleSelectList, openPopoverTrigger, fieldGridColumns, FIELD_GAP_PX, STATIC_TOKENS } from './chunk-MHY7MVXH.js';
3
+ import { icon, iconAt, withAlpha, WARNING, ACCENT, NEUTRAL, BEARISH, BULLISH, isDarkColor, SERIES_LINE, SLATE, CROSSHAIR, TRADE_EXIT, TRADE_SHORT, TRADE_LONG, overlayScrollbarCss, SLATE_DEEP, VALID, INVALID, FIELD_FOCUS_CSS, FIELD_FOCUS_RING, svg24, svg24Solid } from './chunk-KG4YT3TI.js';
4
4
 
5
5
  // src/core/events/EventBus.ts
6
6
  var TypedEventBus = class {
@@ -696,6 +696,23 @@ var BarStore = class {
696
696
  };
697
697
  var sharedBarStore = new BarStore();
698
698
 
699
+ // src/data/symbol-groups.ts
700
+ function isGroupRow(d) {
701
+ return d.group != null && d.ticker === d.group;
702
+ }
703
+ function groupKeyOf(d) {
704
+ return `${(d.prefix ?? d.provider ?? "").toLowerCase()}:${(d.group ?? "").toUpperCase()}`;
705
+ }
706
+ function groupMembers(pool, groupRow) {
707
+ const key = groupKeyOf(groupRow);
708
+ return pool.filter((s) => s.group != null && !isGroupRow(s) && groupKeyOf(s) === key);
709
+ }
710
+ function defaultMemberOf(pool, groupRow) {
711
+ const members = groupMembers(pool, groupRow);
712
+ const defaults = members.filter((m) => m.default);
713
+ return (defaults.length === 1 ? defaults[0] : members[0]) ?? null;
714
+ }
715
+
699
716
  // src/data/ProviderRegistry.ts
700
717
  var normName = (n) => n.trim().toLowerCase();
701
718
  var normTicker = (t) => t.trim().toUpperCase();
@@ -785,17 +802,24 @@ var ProviderRegistry = class {
785
802
  resolve(raw, opts = {}) {
786
803
  const { provider, ticker } = this.parse(raw);
787
804
  if (provider) {
788
- if (this.entries.has(provider)) return { provider, ticker };
805
+ if (this.entries.has(provider)) {
806
+ const d = this.entries.get(provider).byTicker?.get(normTicker(ticker));
807
+ return { provider, ticker: d != null && isGroupRow(d) ? this.loadableTicker(provider, d) : ticker };
808
+ }
789
809
  const key = `${provider}:${normTicker(ticker)}`;
790
810
  for (const name of this.candidateOrder(opts.default)) {
791
811
  const d = this.entries.get(name).prefixIndex?.get(key);
792
- if (d) return { provider: name, ticker: d.ticker };
812
+ if (d) return { provider: name, ticker: this.loadableTicker(name, d) };
793
813
  }
794
814
  return null;
795
815
  }
796
816
  const norm = normTicker(ticker);
797
817
  for (const name of this.candidateOrder(opts.default)) {
798
- if (this.entries.get(name).index?.has(norm)) return { provider: name, ticker };
818
+ const e = this.entries.get(name);
819
+ if (e.index?.has(norm)) {
820
+ const d = e.byTicker?.get(norm);
821
+ return { provider: name, ticker: d != null && isGroupRow(d) ? this.loadableTicker(name, d) : ticker };
822
+ }
799
823
  }
800
824
  if (opts.lenient && this.entries.size === 1) {
801
825
  const only = this.names()[0];
@@ -804,6 +828,13 @@ var ProviderRegistry = class {
804
828
  }
805
829
  return null;
806
830
  }
831
+ /** What a resolved descriptor LOADS: itself — unless it is a GROUP row (listed, never
832
+ * served), which loads its default member (single `default`, else first listed). A
833
+ * memberless group keeps its own ticker and fails downstream like any unknown symbol. */
834
+ loadableTicker(name, d) {
835
+ if (!isGroupRow(d)) return d.ticker;
836
+ return defaultMemberOf(this.entries.get(name)?.descriptors ?? [], d)?.ticker ?? d.ticker;
837
+ }
807
838
  /**
808
839
  * Resolve now if possible, else resolve later — re-attempting after each provider
809
840
  * settles. The promise stays pending until some registered provider can serve the
@@ -947,6 +978,28 @@ var CachingDataFeed = class {
947
978
  this.store.merge(key, dropForming(bars));
948
979
  return bars;
949
980
  }
981
+ /**
982
+ * Progressive twin of {@link load}: a COLD load streams through the inner feed's
983
+ * progressive path — batches forwarded verbatim, the FINAL answer cached exactly as
984
+ * `load` caches — while a cache-covered load answers once through `load` itself (a
985
+ * warm chart has nothing to stream). Falls back to `load` wholesale when the inner
986
+ * feed lacks the capability, so callers may prefer this method unconditionally.
987
+ */
988
+ async loadProgressive(cfg, onBatch, opts) {
989
+ if (cfg.data && cfg.data.length > 0) return this.inner.load(cfg);
990
+ if (!this.inner.loadProgressive) return null;
991
+ const symbol = cfg.symbol ?? "TEST";
992
+ const key = cacheKey(symbol, cfg.timeframe ?? "60", cfg.session);
993
+ const cached = this.store.get(key);
994
+ const n = cfg.bars ?? 500;
995
+ const lastCached = cached?.[cached.length - 1];
996
+ if (cached && lastCached && cached.length >= n - 1 && this.inner.loadRange) return this.load(cfg);
997
+ this.store.retainSymbol(symbol);
998
+ const bars = await this.inner.loadProgressive(cfg, onBatch, opts);
999
+ if (bars == null) return null;
1000
+ this.store.merge(key, dropForming(bars));
1001
+ return bars;
1002
+ }
950
1003
  /**
951
1004
  * Cache-backed ranged fetch — the gateway engines use for secondary series
952
1005
  * (`request.security` HTF/LTF/cross-symbol) and the orchestrator's backward
@@ -1192,6 +1245,17 @@ var MultiProviderFeed = class {
1192
1245
  this.prefetchSymbolInfo(resolved);
1193
1246
  return this.cache.load(canonical(cfg, resolved));
1194
1247
  }
1248
+ /** Progressive twin of {@link load} — same resolution, the cache streams the batches. */
1249
+ async loadProgressive(cfg, onBatch, opts) {
1250
+ if (cfg.data && cfg.data.length > 0) {
1251
+ this.liveBars = cfg.data.map((b) => ({ ...b }));
1252
+ return cfg.data;
1253
+ }
1254
+ const resolved = await this.registry.whenResolvable(rawSymbol(cfg), { default: this.primaryProvider });
1255
+ this.primaryProvider = resolved.provider;
1256
+ this.prefetchSymbolInfo(resolved);
1257
+ return this.cache.loadProgressive(canonical(cfg, resolved), onBatch, opts);
1258
+ }
1195
1259
  /**
1196
1260
  * Synchronous per-symbol metadata for engines (Pine `syminfo.*`), served from the cache
1197
1261
  * warmed by load(). Undefined until the prefetch lands (the engine then synthesizes a
@@ -1279,6 +1343,18 @@ var RegistryFetchFeed = class {
1279
1343
  if (!provider) return Promise.resolve([]);
1280
1344
  return safeBars(provider, ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session });
1281
1345
  }
1346
+ async loadProgressive(cfg, onBatch, opts) {
1347
+ const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
1348
+ const provider = this.registry.get(name ?? "");
1349
+ if (!provider) return [];
1350
+ if (!provider.getBarsProgressive) return null;
1351
+ try {
1352
+ return await provider.getBarsProgressive(ticker, cfg.timeframe ?? "60", { limit: cfg.bars ?? 500, session: cfg.session }, onBatch, opts);
1353
+ } catch (e) {
1354
+ console.warn(`[vela] progressive fetch failed for ${ticker} ${cfg.timeframe ?? "60"} \u2014 ${e instanceof Error ? e.message : String(e)}`);
1355
+ return [];
1356
+ }
1357
+ }
1282
1358
  loadRange(cfg, range) {
1283
1359
  const { provider: name, ticker } = parseSymbol(cfg.symbol ?? "");
1284
1360
  const provider = this.registry.get(name ?? "");
@@ -2426,7 +2502,7 @@ var DrawingToolbar = class {
2426
2502
  this.root.replaceChildren();
2427
2503
  this.groupCells.clear();
2428
2504
  this.groupIcons.clear();
2429
- this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.onArm(null));
2505
+ this.cursorBtn = this.makeButton(CURSOR_ICON, "Cursor", () => this.onCursorClick());
2430
2506
  this.root.appendChild(this.cursorBtn);
2431
2507
  if (this.def.groups.length > 0) this.root.appendChild(this.divider());
2432
2508
  for (const g of this.def.groups) {
@@ -2530,6 +2606,14 @@ var DrawingToolbar = class {
2530
2606
  this.magnetIcon = icon2;
2531
2607
  return cell;
2532
2608
  }
2609
+ /** Cursor returns to select/idle: an active measure/eraser mode exits through its own
2610
+ * toggle callback (disarming a tool via `onArm(null)` alone can't — the host treats a
2611
+ * null arm as a no-op side effect of entering those modes), then the tool disarms. */
2612
+ onCursorClick() {
2613
+ if (this.measureActive) this.onMeasure();
2614
+ if (this.eraserActive) this.onEraser();
2615
+ this.onArm(null);
2616
+ }
2533
2617
  /** Clicking the icon arms the group's last-used tool (it does NOT open the flyout). */
2534
2618
  onGroupIconClick(group) {
2535
2619
  const type = this.lastUsed.get(group.id) ?? group.tools[0]?.type;
@@ -3819,6 +3903,9 @@ var CLOSE_SVG = iconAt("close", LEGEND_ICON_PX2);
3819
3903
  var FOLD_SVG = iconAt("chevron-up", LEGEND_ICON_PX2);
3820
3904
  var UNFOLD_SVG = iconAt("chevron-down", LEGEND_ICON_PX2);
3821
3905
  var OVERVIEW_SVG = iconAt("objects", LEGEND_ICON_PX2);
3906
+ function legendCalloutsDisplay(open, hasCallouts) {
3907
+ return !open && hasCallouts ? "inline-flex" : "none";
3908
+ }
3822
3909
  var InputsUI = class {
3823
3910
  constructor(container, theme, paneBoundsOf) {
3824
3911
  this.container = container;
@@ -4022,7 +4109,7 @@ var InputsUI = class {
4022
4109
  row.callouts = [];
4023
4110
  row.calloutsEl.replaceChildren();
4024
4111
  const views = this.legendCallouts?.(row.id) ?? [];
4025
- row.calloutsEl.style.display = views.length > 0 ? "inline-flex" : "none";
4112
+ row.calloutsEl.style.display = legendCalloutsDisplay(row.highlighted, views.length > 0);
4026
4113
  for (const view of views) {
4027
4114
  const bubble = new CalloutBubble({
4028
4115
  icon: view.icon,
@@ -4506,11 +4593,11 @@ var InputsUI = class {
4506
4593
  row.controlsEl.style.display = open || row.hidden ? "inline-flex" : "none";
4507
4594
  if (open) {
4508
4595
  row.el.appendChild(row.statusEl);
4509
- row.el.appendChild(row.calloutsEl);
4596
+ for (const bubble of row.callouts) bubble.hidePanel();
4510
4597
  } else {
4511
4598
  row.el.insertBefore(row.statusEl, row.valuesEl);
4512
- row.el.insertBefore(row.calloutsEl, row.statusEl);
4513
4599
  }
4600
+ row.calloutsEl.style.display = legendCalloutsDisplay(open, row.callouts.length > 0);
4514
4601
  for (const child of Array.from(row.controlsEl.children)) {
4515
4602
  if (!(child instanceof HTMLElement) || child === row.eyeEl) continue;
4516
4603
  if (child === row.extrasEl) {
@@ -4600,7 +4687,7 @@ var ICONS = {
4600
4687
  };
4601
4688
  var STYLE_ID2 = "vela-pane-controls";
4602
4689
  var ICON_PX = 12;
4603
- var CLUSTER_PILL = "rgba(0,0,0,0.28)";
4690
+ var CLUSTER_PILL = "rgba(0,0,0,0.65)";
4604
4691
  function ensureStyles2() {
4605
4692
  if (typeof document === "undefined" || document.getElementById(STYLE_ID2)) return;
4606
4693
  const st = document.createElement("style");
@@ -4620,8 +4707,12 @@ var PaneControls = class {
4620
4707
  this.deps = deps;
4621
4708
  this.clusters = /* @__PURE__ */ new Map();
4622
4709
  this.hoverPaneId = null;
4710
+ /** Mobile: hover clusters are meaningless without a cursor — suppressed; a
4711
+ * collapsed pane's standalone expand chip stays (the only way back up). */
4712
+ this.suspended = false;
4623
4713
  /** Reveal the cluster for the pane under the cursor, resolved from the pointer's y in the plot. */
4624
4714
  this.onPlotMove = (e) => {
4715
+ if (this.suspended) return;
4625
4716
  const rect = this.plot.getBoundingClientRect();
4626
4717
  const y = e.clientY - rect.top;
4627
4718
  let hit = null;
@@ -4693,7 +4784,12 @@ var PaneControls = class {
4693
4784
  }
4694
4785
  if (p.count > 1) {
4695
4786
  cluster.appendChild(
4696
- this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), { role: "maximize" })
4787
+ this.button(p.maximized ? ICONS.restore : ICONS.maximize, p.maximized ? "Restore pane" : "Maximize pane", false, () => this.deps.onToggleMaximize(p.id), {
4788
+ role: "maximize",
4789
+ // Same inverse-chip treatment as the collapsed pane's expand toggle: the
4790
+ // maximized state must read as an active state, not just a swapped glyph.
4791
+ selected: p.maximized
4792
+ })
4697
4793
  );
4698
4794
  }
4699
4795
  }
@@ -4730,17 +4826,18 @@ var PaneControls = class {
4730
4826
  }
4731
4827
  const hovered = id === this.hoverPaneId;
4732
4828
  const hasButtons = cluster.children.length > 0;
4733
- const visible = hasButtons && (hovered || p.collapsed) && p.height > 8;
4829
+ const stateChipRole = p.collapsed ? "collapse" : !this.suspended && p.maximized ? "maximize" : null;
4830
+ const visible = hasButtons && (hovered || stateChipRole != null) && p.height > 8;
4734
4831
  cluster.style.right = `${rightPx}px`;
4735
4832
  cluster.style.top = p.collapsed ? `${p.top + Math.max(1, Math.round((p.height - 24) / 2))}px` : `${p.top + 4}px`;
4736
4833
  cluster.style.display = visible ? "flex" : "none";
4737
4834
  if (!visible) continue;
4738
- const soloExpand = p.collapsed && !hovered;
4739
- cluster.style.background = soloExpand ? "transparent" : CLUSTER_PILL;
4835
+ const soloChip = stateChipRole != null && !hovered;
4836
+ cluster.style.background = soloChip ? "transparent" : CLUSTER_PILL;
4740
4837
  for (const child of cluster.children) {
4741
4838
  const btn2 = child;
4742
4839
  btn2.style.display = "inline-flex";
4743
- btn2.style.visibility = soloExpand && btn2.dataset.role !== "collapse" ? "hidden" : "visible";
4840
+ btn2.style.visibility = soloChip && btn2.dataset.role !== stateChipRole ? "hidden" : "visible";
4744
4841
  }
4745
4842
  }
4746
4843
  }
@@ -4750,6 +4847,14 @@ var PaneControls = class {
4750
4847
  this.hoverPaneId = paneId;
4751
4848
  this.reposition();
4752
4849
  }
4850
+ /** Mobile suppression: no hover clusters (touch has no cursor; the shell's own
4851
+ * chrome covers maximize), while collapsed panes keep their expand chips. */
4852
+ setSuspended(on) {
4853
+ if (on === this.suspended) return;
4854
+ this.suspended = on;
4855
+ if (on) this.hoverPaneId = null;
4856
+ this.reposition();
4857
+ }
4753
4858
  destroy() {
4754
4859
  this.plot.removeEventListener("pointermove", this.onPlotMove);
4755
4860
  this.plot.removeEventListener("pointerleave", this.onPlotLeave);
@@ -4900,160 +5005,6 @@ var AxisScaleButtons = class {
4900
5005
  }
4901
5006
  };
4902
5007
 
4903
- // src/renderers/shared/TableOverlay.ts
4904
- var SIZE_PX = {
4905
- auto: 13,
4906
- tiny: 10,
4907
- small: 11,
4908
- normal: 13,
4909
- large: 16,
4910
- huge: 20
4911
- };
4912
- function fontPxOf(size) {
4913
- if (typeof size === "number") return size > 0 ? size : SIZE_PX.auto;
4914
- return SIZE_PX[size] ?? SIZE_PX.auto;
4915
- }
4916
- function tableHasContent(t) {
4917
- return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
4918
- }
4919
- function mergeRenderPlan(t) {
4920
- const span = /* @__PURE__ */ new Map();
4921
- const omit = /* @__PURE__ */ new Set();
4922
- for (const m of t.merges) {
4923
- span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
4924
- for (let r = m.startRow; r <= m.endRow; r += 1) {
4925
- for (let c = m.startCol; c <= m.endCol; c += 1) {
4926
- if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
4927
- }
4928
- }
4929
- }
4930
- for (let r = 0; r < t.rows; r += 1) {
4931
- for (let c = 0; c < t.columns; c += 1) {
4932
- if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
4933
- }
4934
- }
4935
- for (const key of span.keys()) omit.delete(key);
4936
- return { span, omit };
4937
- }
4938
- var TableOverlay = class {
4939
- constructor(container, theme, paneBounds) {
4940
- this.container = container;
4941
- this.theme = theme;
4942
- this.paneBounds = paneBounds;
4943
- this.lastTables = [];
4944
- if (getComputedStyle(container).position === "static") container.style.position = "relative";
4945
- this.root = document.createElement("div");
4946
- Object.assign(this.root.style, {
4947
- position: "absolute",
4948
- inset: "0",
4949
- pointerEvents: "none",
4950
- overflow: "hidden",
4951
- zIndex: "3"
4952
- });
4953
- container.appendChild(this.root);
4954
- }
4955
- update(tables) {
4956
- this.lastTables = tables;
4957
- this.root.replaceChildren();
4958
- for (const t of tables) {
4959
- if (tableHasContent(t)) this.root.appendChild(this.renderTable(t));
4960
- }
4961
- }
4962
- /** Re-render at the current pane geometry — after layout settles or on resize. */
4963
- reposition() {
4964
- if (this.root.isConnected) this.update(this.lastTables);
4965
- }
4966
- /** Show/hide the whole overlay. Tables anchor to pane corners, not to bars, so unlike the
4967
- * series content they DON'T vanish with an emptied chart — the loading state hides them. */
4968
- setVisible(visible) {
4969
- this.root.style.display = visible ? "" : "none";
4970
- }
4971
- destroy() {
4972
- this.root.remove();
4973
- }
4974
- renderTable(t) {
4975
- const b = this.paneBounds(t.paneId);
4976
- const wrap = document.createElement("div");
4977
- wrap.style.position = "absolute";
4978
- if (t.frameColor && t.frameWidth > 0) wrap.style.border = `${t.frameWidth}px solid ${t.frameColor}`;
4979
- this.anchor(wrap, t.position, b);
4980
- const table = document.createElement("table");
4981
- Object.assign(table.style, {
4982
- borderCollapse: "collapse",
4983
- background: t.bgColor ?? "transparent",
4984
- fontFamily: this.theme.fontFamily || "sans-serif",
4985
- border: "none",
4986
- tableLayout: "auto",
4987
- // Re-enable pointer events on the table only (root is none) so cell tooltips work.
4988
- pointerEvents: "auto"
4989
- });
4990
- const { span, omit } = mergeRenderPlan(t);
4991
- const plotW = Math.max(0, (this.root.clientWidth || this.container.clientWidth) - b.rightAxis);
4992
- const paneH = b.height;
4993
- const cellBorder = t.borderColor && t.borderWidth > 0 ? `${t.borderWidth}px solid ${t.borderColor}` : "none";
4994
- for (let r = 0; r < t.rows; r += 1) {
4995
- const tr = document.createElement("tr");
4996
- for (let c = 0; c < t.columns; c += 1) {
4997
- if (omit.has(`${r}:${c}`)) continue;
4998
- const cell = t.cells[r]?.[c] ?? null;
4999
- const td = document.createElement("td");
5000
- if (cell === null) {
5001
- Object.assign(td.style, { padding: "0", border: "none" });
5002
- tr.appendChild(td);
5003
- continue;
5004
- }
5005
- const sp = span.get(`${r}:${c}`);
5006
- if (sp) {
5007
- if (sp.cs > 1) td.colSpan = sp.cs;
5008
- if (sp.rs > 1) td.rowSpan = sp.rs;
5009
- }
5010
- Object.assign(td.style, {
5011
- border: cellBorder,
5012
- padding: "2px 6px",
5013
- background: cell.bgColor ?? "transparent",
5014
- color: cell.textColor ?? this.theme.textColor,
5015
- textAlign: cell.hAlign,
5016
- verticalAlign: cell.vAlign === "top" ? "top" : cell.vAlign === "bottom" ? "bottom" : "middle",
5017
- fontSize: `${fontPxOf(cell.textSize)}px`,
5018
- fontFamily: cell.fontFamily === "monospace" ? "monospace" : "inherit",
5019
- fontWeight: cell.bold ? "bold" : "normal",
5020
- fontStyle: cell.italic ? "italic" : "normal",
5021
- // Pine cell text never wraps; `\n` still breaks lines. Wrapping
5022
- // used to collapse unicode sparklines and ━━━ dividers.
5023
- whiteSpace: "pre"
5024
- });
5025
- if (cell.width) td.style.width = `${cell.width / 100 * plotW}px`;
5026
- if (cell.height) td.style.height = `${cell.height / 100 * paneH}px`;
5027
- if (cell.tooltip) td.title = cell.tooltip;
5028
- td.textContent = cell.text ?? "";
5029
- tr.appendChild(td);
5030
- }
5031
- table.appendChild(tr);
5032
- }
5033
- wrap.appendChild(table);
5034
- return wrap;
5035
- }
5036
- /**
5037
- * Position the wrapper at a Pine `position.*` corner/edge of the table's PANE
5038
- * (not the whole chart), inset past the right price axis so it never overlaps
5039
- * the Y-axis labels.
5040
- */
5041
- anchor(el, position, b) {
5042
- const m = 6;
5043
- const containerH = this.root.clientHeight || this.container.clientHeight;
5044
- const paneBottom = b.top + b.height;
5045
- if (position.startsWith("top")) el.style.top = `${b.top + m}px`;
5046
- else if (position.startsWith("bottom")) el.style.bottom = `${Math.max(0, containerH - paneBottom) + m}px`;
5047
- else el.style.top = `${b.top + b.height / 2}px`;
5048
- if (position.endsWith("left")) el.style.left = `${m}px`;
5049
- else if (position.endsWith("right")) el.style.right = `${b.rightAxis + m}px`;
5050
- else el.style.left = `calc(50% - ${b.rightAxis / 2}px)`;
5051
- const tx = position.endsWith("center") ? "-50%" : "0";
5052
- const ty = position.startsWith("middle") ? "-50%" : "0";
5053
- if (tx !== "0" || ty !== "0") el.style.transform = `translate(${tx}, ${ty})`;
5054
- }
5055
- };
5056
-
5057
5008
  // src/renderers/native/capabilities.ts
5058
5009
  var NATIVE_CAPABILITIES = {
5059
5010
  panes: true,
@@ -5074,7 +5025,7 @@ var NATIVE_CAPABILITIES = {
5074
5025
  drawingDepth: true,
5075
5026
  // drawings share the series' z space (backend-composited interleave layers)
5076
5027
  tables: true,
5077
- // reuses the DOM TableOverlay
5028
+ // canvas-painted into the owning indicator's interleave slice
5078
5029
  trades: true,
5079
5030
  // strategy order-fill markers (arrows + labels + fill-price ticks)
5080
5031
  inputsUI: true
@@ -5105,6 +5056,9 @@ function candleTier(spacing) {
5105
5056
  if (spacing < CANDLE_BODY_MIN_SPACING) return "wick";
5106
5057
  return "full";
5107
5058
  }
5059
+ function snapY(yCss, dpr) {
5060
+ return Math.round(yCss * dpr) / dpr;
5061
+ }
5108
5062
  function candleGeometry(xCss, spacing, dpr, bodyScale = 1) {
5109
5063
  const wickDev = Math.max(1, Math.round(wickWidth(spacing) * dpr));
5110
5064
  const wickLeftDev = Math.round(xCss * dpr - wickDev / 2);
@@ -5911,11 +5865,9 @@ var WebGL2Backend = class {
5911
5865
  };
5912
5866
  b.alpha = this.modelAlpha;
5913
5867
  for (const m of models) for (const bgSpan of m.backgrounds) if (bgSpan.overlay !== true) this.emitBackground(b, bgSpan, pane, coords);
5914
- 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));
5915
5868
  if (isPrice) {
5916
5869
  for (const m of scene.indicators.values()) {
5917
5870
  for (const bgSpan of m.backgrounds) if (bgSpan.overlay === true) this.emitBackground(b, bgSpan, pane, coords);
5918
- for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
5919
5871
  }
5920
5872
  }
5921
5873
  const drawCandles = isPrice && !scene.candlesHidden;
@@ -5931,6 +5883,7 @@ var WebGL2Backend = class {
5931
5883
  b.alpha = this.modelAlpha;
5932
5884
  const off = scene.offsetOf(m.id);
5933
5885
  const mp = effPane(m);
5886
+ for (const f of m.fills) if (f.overlay !== true) this.emitFill(b, m, f, mp, coords, i0, i1, off);
5934
5887
  for (const s of m.series) if (s.overlay !== true) this.emitSeries(b, s, mp, coords, i0, i1, theme, off);
5935
5888
  }
5936
5889
  if (drawCandles && !candleDrawn) {
@@ -5938,14 +5891,18 @@ var WebGL2Backend = class {
5938
5891
  b.alpha = this.candleStructureAlpha;
5939
5892
  this.emitPriceSeries(b, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
5940
5893
  }
5941
- drawSlicesUpTo(Infinity);
5942
5894
  b.alpha = this.modelAlpha;
5943
5895
  if (isPrice) {
5896
+ for (const m of scene.indicators.values()) {
5897
+ const off = scene.offsetOf(m.id);
5898
+ for (const f of m.fills) if (f.overlay === true) this.emitFill(b, m, f, pane, coords, i0, i1, off);
5899
+ }
5944
5900
  for (const m of scene.indicators.values()) {
5945
5901
  const off = scene.offsetOf(m.id);
5946
5902
  for (const s of m.series) if (s.overlay === true) this.emitSeries(b, s, pane, coords, i0, i1, theme, off);
5947
5903
  }
5948
5904
  }
5905
+ drawSlicesUpTo(Infinity);
5949
5906
  for (const m of models) {
5950
5907
  const mp = effPane(m);
5951
5908
  for (const pl of m.priceLines) this.emitHline(b, pl, mp, coords, dataW, theme);
@@ -6340,12 +6297,12 @@ var WebGL2Backend = class {
6340
6297
  if (drawBody) {
6341
6298
  const oY = coords.priceToY(bar.open, pane.scale, pane.bounds);
6342
6299
  const cY = coords.priceToY(bar.close, pane.scale, pane.bounds);
6343
- bodyTop = Math.min(oY, cY);
6344
- bodyH = Math.max(1, Math.abs(cY - oY));
6300
+ bodyTop = snapY(Math.min(oY, cY), coords.dpr);
6301
+ bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - bodyTop);
6345
6302
  }
6346
6303
  if (cs.wickVisible) {
6347
- const hY = coords.priceToY(bar.high, pane.scale, pane.bounds);
6348
- const lY = coords.priceToY(bar.low, pane.scale, pane.bounds);
6304
+ const hY = snapY(coords.priceToY(bar.high, pane.scale, pane.bounds), coords.dpr);
6305
+ const lY = snapY(coords.priceToY(bar.low, pane.scale, pane.bounds), coords.dpr);
6349
6306
  b.alpha = this.candleStructureAlpha;
6350
6307
  const wCol = parseColor((isUp ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : bodyColorStr));
6351
6308
  if (drawBody) {
@@ -7678,9 +7635,10 @@ var SceneGraph = class {
7678
7635
  * so each indicator arrives behind the candles (and behind older indicators);
7679
7636
  * `setIndicatorZ`/`bringToFront`/`sendToBack` change it. */
7680
7637
  this.seriesZ = /* @__PURE__ */ new Map();
7681
- /** Per-pane raster layers of user drawings interleaved into the series stack each is a
7638
+ /** Per-pane raster layers of drawings interleaved into the series stack (each
7639
+ * indicator's Pine drawings at its model's z, plus in-stack user drawings) — each a
7682
7640
  * prepainted canvas the backend composites just before the series carrying `beforeZ`.
7683
- * Rebuilt by the renderer per data frame; empty when every drawing sits over the stack. */
7641
+ * Rebuilt by the renderer per data frame. */
7684
7642
  this.drawingSlices = /* @__PURE__ */ new Map();
7685
7643
  /** Per-model index offset: the chart bar index of the model's `anchorTime` — its
7686
7644
  * index-aligned payloads (dense series arrays, `bar_index` drawings) count from that
@@ -7915,11 +7873,9 @@ var Canvas2dBackend = class {
7915
7873
  };
7916
7874
  ctx.globalAlpha = this.modelAlpha;
7917
7875
  for (const m of models) for (const bg of m.backgrounds) if (bg.overlay !== true) this.drawBackground(ctx, bg, pane, coords);
7918
- 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));
7919
7876
  if (isPrice) {
7920
7877
  for (const m of scene.indicators.values()) {
7921
7878
  for (const bg of m.backgrounds) if (bg.overlay === true) this.drawBackground(ctx, bg, pane, coords);
7922
- for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, scene.offsetOf(m.id));
7923
7879
  }
7924
7880
  }
7925
7881
  const slices = scene.drawingSlices.get(pane.id) ?? [];
@@ -7943,6 +7899,7 @@ var Canvas2dBackend = class {
7943
7899
  ctx.globalAlpha = this.modelAlpha;
7944
7900
  const off = scene.offsetOf(m.id);
7945
7901
  const mp = effPane(m);
7902
+ for (const f of m.fills) if (f.overlay !== true) this.drawFill(ctx, m, f, mp, coords, i0, i1, off);
7946
7903
  for (const s of m.series) if (s.overlay !== true) this.drawSeries(ctx, s, mp, coords, i0, i1, theme, off);
7947
7904
  }
7948
7905
  if (drawCandles && !candleDrawn) {
@@ -7950,14 +7907,18 @@ var Canvas2dBackend = class {
7950
7907
  ctx.globalAlpha = this.candleStructureAlpha;
7951
7908
  this.drawPriceSeries(ctx, scene, i0, i1, coords, pane, theme, barColorMap, dataW);
7952
7909
  }
7953
- drawSlicesUpTo(Infinity);
7954
7910
  if (isPrice) {
7955
7911
  ctx.globalAlpha = this.modelAlpha;
7912
+ for (const m of scene.indicators.values()) {
7913
+ const off = scene.offsetOf(m.id);
7914
+ for (const f of m.fills) if (f.overlay === true) this.drawFill(ctx, m, f, pane, coords, i0, i1, off);
7915
+ }
7956
7916
  for (const m of scene.indicators.values()) {
7957
7917
  const off = scene.offsetOf(m.id);
7958
7918
  for (const s of m.series) if (s.overlay === true) this.drawSeries(ctx, s, pane, coords, i0, i1, theme, off);
7959
7919
  }
7960
7920
  }
7921
+ drawSlicesUpTo(Infinity);
7961
7922
  ctx.globalAlpha = this.modelAlpha;
7962
7923
  for (const m of models) {
7963
7924
  const mp = effPane(m);
@@ -8201,13 +8162,13 @@ var Canvas2dBackend = class {
8201
8162
  if (drawBody) {
8202
8163
  const oY = coords.priceToY(b.open, pane.scale, pane.bounds);
8203
8164
  const cY = coords.priceToY(b.close, pane.scale, pane.bounds);
8204
- top = Math.min(oY, cY);
8205
- bodyH = Math.max(1, Math.abs(cY - oY));
8165
+ top = snapY(Math.min(oY, cY), coords.dpr);
8166
+ bodyH = Math.max(1 / coords.dpr, snapY(Math.max(oY, cY), coords.dpr) - top);
8206
8167
  }
8207
8168
  if (cs.wickVisible) {
8208
8169
  const wick = (up ? cs.wickUpColor : cs.wickDownColor) ?? (drawBody ? dir : color);
8209
- const hY = coords.priceToY(b.high, pane.scale, pane.bounds);
8210
- const lY = coords.priceToY(b.low, pane.scale, pane.bounds);
8170
+ const hY = snapY(coords.priceToY(b.high, pane.scale, pane.bounds), coords.dpr);
8171
+ const lY = snapY(coords.priceToY(b.low, pane.scale, pane.bounds), coords.dpr);
8211
8172
  ctx.globalAlpha = this.candleStructureAlpha;
8212
8173
  ctx.strokeStyle = wick;
8213
8174
  ctx.lineWidth = g.wickW;
@@ -8654,6 +8615,19 @@ function autoFontSize(lines, boxW, boxH, bold) {
8654
8615
 
8655
8616
  // src/renderers/shared/DrawingSceneRenderer.ts
8656
8617
  var EMPTY_DRAWING_SET = { lines: [], boxes: [], labels: [], polylines: [], linefills: [] };
8618
+ function modelDrawingSet(m, overlay) {
8619
+ const want = (d) => Boolean(d.overlay) === overlay;
8620
+ return {
8621
+ lines: (m.lines ?? []).filter(want),
8622
+ boxes: (m.boxes ?? []).filter(want),
8623
+ labels: (m.labels ?? []).filter(want),
8624
+ polylines: (m.polylines ?? []).filter(want),
8625
+ linefills: (m.linefills ?? []).filter(want)
8626
+ };
8627
+ }
8628
+ function drawingSetEmpty(s) {
8629
+ return !s.lines.length && !s.boxes.length && !s.labels.length && !s.polylines.length && !s.linefills.length;
8630
+ }
8657
8631
  function fontSizePx(size) {
8658
8632
  return size === "auto" ? 12 : namedFontSize(size);
8659
8633
  }
@@ -9506,10 +9480,8 @@ var ChromeRenderer = class {
9506
9480
  this.ctx = null;
9507
9481
  // The color for axis tick labels — the host-passed surface text, set each frame in render().
9508
9482
  this.axisTextColor = DARK_THEME.textColor;
9509
- // Shared Pine-drawing renderer (line/box/label/polyline/linefill); widthCache persists.
9483
+ // Shared Pine-drawing renderer, used here for autoscale geometry only; widthCache persists.
9510
9484
  this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
9511
- // Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per render).
9512
- this.labelTips = [];
9513
9485
  }
9514
9486
  mount(canvas) {
9515
9487
  this.canvas = canvas;
@@ -9533,8 +9505,8 @@ var ChromeRenderer = class {
9533
9505
  */
9534
9506
  paneDrawingsRange(ownModels, scene, isPricePane, vr) {
9535
9507
  let dr = null;
9536
- for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(this.ownDrawings(m), vr, scene.offsetOf(m.id)));
9537
- if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(this.overlayDrawings(m), vr, scene.offsetOf(m.id)));
9508
+ for (const m of ownModels) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, false), vr, scene.offsetOf(m.id)));
9509
+ if (isPricePane) for (const m of scene.indicators.values()) dr = unionRange(dr, this.drawingsRange(modelDrawingSet(m, true), vr, scene.offsetOf(m.id)));
9538
9510
  return dr;
9539
9511
  }
9540
9512
  /** Clear the chrome canvas and draw drawings + axes + current-price line.
@@ -9551,7 +9523,6 @@ var ChromeRenderer = class {
9551
9523
  const dataW = coords.width;
9552
9524
  const dataH = coords.height;
9553
9525
  this.axisTextColor = surface?.textColor ?? theme.textColor;
9554
- this.labelTips = [];
9555
9526
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
9556
9527
  ctx.clearRect(0, 0, fullW, fullH);
9557
9528
  if (surface && (fullW > dataW || fullH > dataH)) {
@@ -9567,17 +9538,6 @@ var ChromeRenderer = class {
9567
9538
  return;
9568
9539
  }
9569
9540
  const pricePane = panes.find((p) => p.kind === "price") ?? null;
9570
- for (const pane of panes) {
9571
- if (pane.collapsed) continue;
9572
- for (const m of scene.indicatorsForPane(pane.id)) {
9573
- const sc = scene.scaleFor(m, pane);
9574
- const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
9575
- this.renderDrawings(ctx, coords, this.ownDrawings(m), mp, dataW, scene.offsetOf(m.id));
9576
- }
9577
- }
9578
- if (pricePane) {
9579
- for (const m of scene.indicators.values()) this.renderDrawings(ctx, coords, this.overlayDrawings(m), pricePane, dataW, scene.offsetOf(m.id));
9580
- }
9581
9541
  if (pricePane && !pricePane.collapsed && scene.tradeMarkers.visible) {
9582
9542
  for (const m of scene.indicators.values()) {
9583
9543
  if (m.trades?.length) this.renderTrades(ctx, coords, scene, theme, m.trades, pricePane, dataW);
@@ -9593,25 +9553,6 @@ var ChromeRenderer = class {
9593
9553
  this.canvas = null;
9594
9554
  this.ctx = null;
9595
9555
  }
9596
- // ── Pine-drawing helpers (own vs force_overlay routing) ──
9597
- ownDrawings(m) {
9598
- return {
9599
- lines: (m.lines ?? []).filter((d) => !d.overlay),
9600
- boxes: (m.boxes ?? []).filter((d) => !d.overlay),
9601
- labels: (m.labels ?? []).filter((d) => !d.overlay),
9602
- polylines: (m.polylines ?? []).filter((d) => !d.overlay),
9603
- linefills: (m.linefills ?? []).filter((d) => !d.overlay)
9604
- };
9605
- }
9606
- overlayDrawings(m) {
9607
- return {
9608
- lines: (m.lines ?? []).filter((d) => d.overlay),
9609
- boxes: (m.boxes ?? []).filter((d) => d.overlay),
9610
- labels: (m.labels ?? []).filter((d) => d.overlay),
9611
- polylines: (m.polylines ?? []).filter((d) => d.overlay),
9612
- linefills: (m.linefills ?? []).filter((d) => d.overlay)
9613
- };
9614
- }
9615
9556
  drawingsRange(set, vr, indexOffset = 0) {
9616
9557
  this.drawScene.setSet(set, indexOffset);
9617
9558
  if (this.drawScene.isEmpty()) return null;
@@ -9645,34 +9586,6 @@ var ChromeRenderer = class {
9645
9586
  );
9646
9587
  ctx.restore();
9647
9588
  }
9648
- renderDrawings(ctx, coords, set, pane, dataW, indexOffset = 0) {
9649
- this.drawScene.setSet(set, indexOffset);
9650
- if (this.drawScene.isEmpty()) return;
9651
- ctx.save();
9652
- ctx.translate(0, pane.bounds.top);
9653
- ctx.beginPath();
9654
- ctx.rect(0, 0, dataW, pane.bounds.height);
9655
- ctx.clip();
9656
- this.drawScene.render(
9657
- ctx,
9658
- dataW,
9659
- pane.bounds.height,
9660
- (l) => coords.logicalToX(l),
9661
- (price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
9662
- );
9663
- ctx.restore();
9664
- for (const r of this.drawScene.labelTipRegions()) {
9665
- this.labelTips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
9666
- }
9667
- }
9668
- /** Tooltip of the topmost label under a plot-space point, or null. Fed by the last render. */
9669
- labelTooltipAt(x, y) {
9670
- for (let i = this.labelTips.length - 1; i >= 0; i -= 1) {
9671
- const r = this.labelTips[i];
9672
- if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
9673
- }
9674
- return null;
9675
- }
9676
9589
  // ── axes ──
9677
9590
  drawPriceAxes(ctx, scene, coords, theme, dataW, panes) {
9678
9591
  ctx.strokeStyle = scene.style.borderColor ?? theme.borderColor;
@@ -13991,17 +13904,17 @@ function glyphIcon(glyph) {
13991
13904
  return textGlyph(String(glyph), 15);
13992
13905
  }
13993
13906
  function stampSizeIcon(size) {
13994
- return textGlyph("\u25CF", (SIZE_PX2[String(size)] ?? 13) + 4);
13907
+ return textGlyph("\u25CF", (SIZE_PX[String(size)] ?? 13) + 4);
13995
13908
  }
13996
13909
  function sizeIcon(size) {
13997
13910
  return textGlyph(String(size).charAt(0).toUpperCase(), 15);
13998
13911
  }
13999
- var SIZE_PX2 = { small: 10, normal: 13, large: 16, huge: 20 };
13912
+ var SIZE_PX = { small: 10, normal: 13, large: 16, huge: 20 };
14000
13913
  function numbersSizeIcon(size) {
14001
- return textGlyph("12", (SIZE_PX2[String(size)] ?? 13) - 1, 16.5, 'font-weight="600"');
13914
+ return textGlyph("12", (SIZE_PX[String(size)] ?? 13) - 1, 16.5, 'font-weight="600"');
14002
13915
  }
14003
13916
  function labelSizeIcon(size) {
14004
- return textGlyph("T", (SIZE_PX2[String(size)] ?? 13) + 2);
13917
+ return textGlyph("T", (SIZE_PX[String(size)] ?? 13) + 2);
14005
13918
  }
14006
13919
  function capitalize(s) {
14007
13920
  return s.charAt(0).toUpperCase() + s.slice(1);
@@ -14976,6 +14889,315 @@ var UserDrawingController = class {
14976
14889
  }
14977
14890
  };
14978
14891
 
14892
+ // src/renderers/shared/TableOverlay.ts
14893
+ var SIZE_PX2 = {
14894
+ auto: 13,
14895
+ tiny: 10,
14896
+ small: 11,
14897
+ normal: 13,
14898
+ large: 16,
14899
+ huge: 20
14900
+ };
14901
+ function fontPxOf(size) {
14902
+ if (typeof size === "number") return size > 0 ? size : SIZE_PX2.auto;
14903
+ return SIZE_PX2[size] ?? SIZE_PX2.auto;
14904
+ }
14905
+ function tableHasContent(t) {
14906
+ return t.cells.some((row) => row?.some((c) => c != null && !c.merged));
14907
+ }
14908
+ function mergeRenderPlan(t) {
14909
+ const span = /* @__PURE__ */ new Map();
14910
+ const omit = /* @__PURE__ */ new Set();
14911
+ for (const m of t.merges) {
14912
+ span.set(`${m.startRow}:${m.startCol}`, { cs: m.endCol - m.startCol + 1, rs: m.endRow - m.startRow + 1 });
14913
+ for (let r = m.startRow; r <= m.endRow; r += 1) {
14914
+ for (let c = m.startCol; c <= m.endCol; c += 1) {
14915
+ if (r !== m.startRow || c !== m.startCol) omit.add(`${r}:${c}`);
14916
+ }
14917
+ }
14918
+ }
14919
+ for (let r = 0; r < t.rows; r += 1) {
14920
+ for (let c = 0; c < t.columns; c += 1) {
14921
+ if (t.cells[r]?.[c]?.merged && !span.has(`${r}:${c}`)) omit.add(`${r}:${c}`);
14922
+ }
14923
+ }
14924
+ for (const key of span.keys()) omit.delete(key);
14925
+ return { span, omit };
14926
+ }
14927
+
14928
+ // src/renderers/shared/TableCanvasRenderer.ts
14929
+ var PAD_X = 6;
14930
+ var PAD_Y = 2;
14931
+ var MARGIN = 6;
14932
+ var LINE_HEIGHT = 1.2;
14933
+ function paintTable(ctx, t, args, tips) {
14934
+ if (!tableHasContent(t)) return;
14935
+ const layout = layoutTable(ctx, t, args);
14936
+ if (!layout || layout.w <= 0 || layout.h <= 0) return;
14937
+ const fw = t.frameColor && t.frameWidth > 0 ? t.frameWidth : 0;
14938
+ const { x, y } = anchorOrigin(t.position, layout.w + 2 * fw, layout.h + 2 * fw, args);
14939
+ const x0 = x + fw;
14940
+ const y0 = y + fw;
14941
+ if (t.bgColor) {
14942
+ ctx.fillStyle = t.bgColor;
14943
+ ctx.fillRect(x0, y0, layout.w, layout.h);
14944
+ }
14945
+ if (fw > 0 && t.frameColor) {
14946
+ ctx.strokeStyle = t.frameColor;
14947
+ ctx.lineWidth = fw;
14948
+ ctx.strokeRect(x + fw / 2, y + fw / 2, layout.w + fw, layout.h + fw);
14949
+ }
14950
+ const colX = [0];
14951
+ for (const w of layout.colW) colX.push(colX[colX.length - 1] + w);
14952
+ const rowY = [0];
14953
+ for (const h of layout.rowH) rowY.push(rowY[rowY.length - 1] + h);
14954
+ const prevBaseline = ctx.textBaseline;
14955
+ const prevAlign = ctx.textAlign;
14956
+ ctx.textBaseline = "middle";
14957
+ for (const box of layout.boxes) {
14958
+ const rx = x0 + colX[box.c];
14959
+ const ry = y0 + rowY[box.r];
14960
+ const rw = colX[box.c + box.cs] - colX[box.c];
14961
+ const rh = rowY[box.r + box.rs] - rowY[box.r];
14962
+ const cell = box.cell;
14963
+ if (cell.bgColor) {
14964
+ ctx.fillStyle = cell.bgColor;
14965
+ ctx.fillRect(rx, ry, rw, rh);
14966
+ }
14967
+ const text = cell.text ?? "";
14968
+ if (text.length > 0) {
14969
+ const px = fontPxOf(cell.textSize);
14970
+ ctx.font = cellFont(cell, px, args.theme);
14971
+ ctx.fillStyle = cell.textColor ?? args.theme.textColor;
14972
+ const lines = text.split("\n");
14973
+ const blockH = lines.length * px * LINE_HEIGHT;
14974
+ const blockTop = cell.vAlign === "top" ? ry + PAD_Y : cell.vAlign === "bottom" ? ry + rh - PAD_Y - blockH : ry + (rh - blockH) / 2;
14975
+ const tx = cell.hAlign === "left" ? rx + PAD_X : cell.hAlign === "right" ? rx + rw - PAD_X : rx + rw / 2;
14976
+ ctx.textAlign = cell.hAlign;
14977
+ lines.forEach((line, i) => ctx.fillText(line, tx, blockTop + (i + 0.5) * px * LINE_HEIGHT));
14978
+ }
14979
+ if (cell.tooltip) tips.push({ left: rx, top: ry, right: rx + rw, bottom: ry + rh, text: cell.tooltip });
14980
+ }
14981
+ ctx.textBaseline = prevBaseline;
14982
+ ctx.textAlign = prevAlign;
14983
+ if (t.borderColor && t.borderWidth > 0) {
14984
+ ctx.strokeStyle = t.borderColor;
14985
+ ctx.lineWidth = t.borderWidth;
14986
+ const seen = /* @__PURE__ */ new Set();
14987
+ ctx.beginPath();
14988
+ const edge = (ax, ay, bx, by) => {
14989
+ const key = `${ax},${ay},${bx},${by}`;
14990
+ if (seen.has(key)) return;
14991
+ seen.add(key);
14992
+ ctx.moveTo(ax, ay);
14993
+ ctx.lineTo(bx, by);
14994
+ };
14995
+ for (const box of layout.boxes) {
14996
+ const l = Math.round(x0 + colX[box.c]);
14997
+ const r = Math.round(x0 + colX[box.c + box.cs]);
14998
+ const tp = Math.round(y0 + rowY[box.r]);
14999
+ const bt = Math.round(y0 + rowY[box.r + box.rs]);
15000
+ edge(l, tp, r, tp);
15001
+ edge(l, bt, r, bt);
15002
+ edge(l, tp, l, bt);
15003
+ edge(r, tp, r, bt);
15004
+ }
15005
+ ctx.stroke();
15006
+ }
15007
+ }
15008
+ function layoutTable(ctx, t, args) {
15009
+ const { span, omit } = mergeRenderPlan(t);
15010
+ const colW = new Array(t.columns).fill(0);
15011
+ const rowH = new Array(t.rows).fill(0);
15012
+ const boxes = [];
15013
+ for (let r = 0; r < t.rows; r += 1) {
15014
+ for (let c = 0; c < t.columns; c += 1) {
15015
+ if (omit.has(`${r}:${c}`)) continue;
15016
+ const cell = t.cells[r]?.[c];
15017
+ if (cell == null) continue;
15018
+ const sp = span.get(`${r}:${c}`);
15019
+ boxes.push({ cell, r, c, cs: Math.min(sp?.cs ?? 1, t.columns - c), rs: Math.min(sp?.rs ?? 1, t.rows - r) });
15020
+ }
15021
+ }
15022
+ if (boxes.length === 0) return null;
15023
+ const sizeOf = (cell) => {
15024
+ const px = fontPxOf(cell.textSize);
15025
+ ctx.font = cellFont(cell, px, args.theme);
15026
+ const lines = (cell.text ?? "").split("\n");
15027
+ let maxW = 0;
15028
+ for (const line of lines) maxW = Math.max(maxW, ctx.measureText(line).width);
15029
+ let w2 = Math.ceil(maxW) + 2 * PAD_X;
15030
+ let h2 = Math.ceil(lines.length * px * LINE_HEIGHT) + 2 * PAD_Y;
15031
+ if (cell.width) w2 = Math.max(w2, cell.width / 100 * args.plotWidth);
15032
+ if (cell.height) h2 = Math.max(h2, cell.height / 100 * args.paneHeight);
15033
+ return { w: w2, h: h2 };
15034
+ };
15035
+ const spanning = [];
15036
+ for (const box of boxes) {
15037
+ const { w: w2, h: h2 } = sizeOf(box.cell);
15038
+ if (box.cs === 1) colW[box.c] = Math.max(colW[box.c], w2);
15039
+ if (box.rs === 1) rowH[box.r] = Math.max(rowH[box.r], h2);
15040
+ if (box.cs > 1 || box.rs > 1) spanning.push({ box, w: w2, h: h2 });
15041
+ }
15042
+ for (const { box, w: w2, h: h2 } of spanning) {
15043
+ if (box.cs > 1) {
15044
+ let sum = 0;
15045
+ for (let c = box.c; c < box.c + box.cs; c += 1) sum += colW[c];
15046
+ if (w2 > sum) for (let c = box.c; c < box.c + box.cs; c += 1) colW[c] += (w2 - sum) / box.cs;
15047
+ }
15048
+ if (box.rs > 1) {
15049
+ let sum = 0;
15050
+ for (let r = box.r; r < box.r + box.rs; r += 1) sum += rowH[r];
15051
+ if (h2 > sum) for (let r = box.r; r < box.r + box.rs; r += 1) rowH[r] += (h2 - sum) / box.rs;
15052
+ }
15053
+ }
15054
+ let w = 0;
15055
+ for (const cw of colW) w += cw;
15056
+ let h = 0;
15057
+ for (const rh of rowH) h += rh;
15058
+ return { colW, rowH, w, h, boxes };
15059
+ }
15060
+ function anchorOrigin(position, totalW, totalH, args) {
15061
+ let y;
15062
+ if (position.startsWith("top")) y = MARGIN;
15063
+ else if (position.startsWith("bottom")) y = args.paneHeight - MARGIN - totalH;
15064
+ else y = args.paneHeight / 2 - totalH / 2;
15065
+ let x;
15066
+ if (position.endsWith("left")) x = MARGIN;
15067
+ else if (position.endsWith("right")) x = args.plotWidth - MARGIN - totalW;
15068
+ else x = args.plotWidth / 2 - totalW / 2;
15069
+ return { x, y };
15070
+ }
15071
+ function cellFont(cell, px, theme) {
15072
+ const family = cell.fontFamily === "monospace" ? "monospace" : theme.fontFamily || "sans-serif";
15073
+ return `${cell.italic ? "italic " : ""}${cell.bold ? "bold " : ""}${px}px ${family}`;
15074
+ }
15075
+
15076
+ // src/renderers/native/drawings/IndicatorDrawingSlices.ts
15077
+ function indicatorSliceKey(z, boundaries) {
15078
+ return boundaries.find((b) => b > z) ?? Infinity;
15079
+ }
15080
+ var IndicatorDrawingSlices = class {
15081
+ constructor() {
15082
+ this.drawScene = new DrawingSceneRenderer({ timeToLogical: () => 0, barAt: () => null, theme: {} });
15083
+ /** Slice canvas cache, keyed `paneId|beforeZ` — same lifecycle as the user-drawing cache. */
15084
+ this.sliceCache = /* @__PURE__ */ new Map();
15085
+ /** Tooltip hit-rects of every label drawn this frame, in plot coords (rebuilt per prepare). */
15086
+ this.tips = [];
15087
+ }
15088
+ /**
15089
+ * Rebuild the per-indicator drawing slices for this data frame. `ref` is the data
15090
+ * canvas the slices must match pixel-for-pixel (the backend composites them 1:1).
15091
+ * Runs from the renderer's data paint, just before the backend composites the scene.
15092
+ */
15093
+ prepare(scene, coords, theme, ref) {
15094
+ this.tips = [];
15095
+ const out = /* @__PURE__ */ new Map();
15096
+ if (ref.width === 0 || ref.height === 0) {
15097
+ this.sliceCache.clear();
15098
+ return out;
15099
+ }
15100
+ this.drawScene.setDeps({
15101
+ timeToLogical: (ms) => coords.timeToLogical(ms),
15102
+ barAt: (logical) => {
15103
+ const b = scene.bars[Math.round(logical)];
15104
+ return b ? { high: b.high, low: b.low } : null;
15105
+ },
15106
+ theme
15107
+ });
15108
+ const dpr = coords.dpr;
15109
+ const dataW = coords.width;
15110
+ const buckets = /* @__PURE__ */ new Map();
15111
+ const add = (paneId, beforeZ, entry) => {
15112
+ const key = `${paneId}|${beforeZ}`;
15113
+ const bucket = buckets.get(key);
15114
+ if (bucket) bucket.entries.push(entry);
15115
+ else buckets.set(key, { paneId, beforeZ, entries: [entry] });
15116
+ };
15117
+ for (const pane of scene.orderedPanes()) {
15118
+ if (pane.collapsed) continue;
15119
+ const boundaries = scene.seriesBoundaries(pane.id);
15120
+ for (const m of scene.orderedIndicatorsForPane(pane.id)) {
15121
+ const set = modelDrawingSet(m, false);
15122
+ const tables = (m.tables ?? []).filter((t) => !t.overlay);
15123
+ if (drawingSetEmpty(set) && tables.length === 0) continue;
15124
+ const sc = scene.scaleFor(m, pane);
15125
+ const mp = sc === pane.scale ? pane : { ...pane, scale: sc };
15126
+ const beforeZ = indicatorSliceKey(scene.zOf(m.id), boundaries);
15127
+ add(pane.id, beforeZ, { set, tables, pane: mp, indexOffset: scene.offsetOf(m.id) });
15128
+ }
15129
+ if (pane.kind === "price") {
15130
+ for (const m of scene.indicators.values()) {
15131
+ const set = modelDrawingSet(m, true);
15132
+ const tables = (m.tables ?? []).filter((t) => t.overlay === true);
15133
+ if (drawingSetEmpty(set) && tables.length === 0) continue;
15134
+ add(pane.id, Infinity, { set, tables, pane, indexOffset: scene.offsetOf(m.id) });
15135
+ }
15136
+ }
15137
+ }
15138
+ for (const [key, { paneId, beforeZ, entries }] of buckets) {
15139
+ let canvas = this.sliceCache.get(key);
15140
+ if (!canvas) {
15141
+ canvas = document.createElement("canvas");
15142
+ this.sliceCache.set(key, canvas);
15143
+ }
15144
+ if (canvas.width !== ref.width || canvas.height !== ref.height) {
15145
+ canvas.width = ref.width;
15146
+ canvas.height = ref.height;
15147
+ }
15148
+ const ctx = canvas.getContext("2d");
15149
+ if (!ctx) continue;
15150
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
15151
+ ctx.clearRect(0, 0, canvas.width / dpr, canvas.height / dpr);
15152
+ for (const e of entries) this.paintEntry(ctx, e, coords, dataW, theme);
15153
+ const slices = out.get(paneId) ?? [];
15154
+ slices.push({ beforeZ, canvas });
15155
+ out.set(paneId, slices);
15156
+ }
15157
+ for (const key of [...this.sliceCache.keys()]) if (!buckets.has(key)) this.sliceCache.delete(key);
15158
+ for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
15159
+ return out;
15160
+ }
15161
+ paintEntry(ctx, e, coords, dataW, theme) {
15162
+ const { pane } = e;
15163
+ const paneTips = [];
15164
+ ctx.save();
15165
+ ctx.translate(0, pane.bounds.top);
15166
+ ctx.beginPath();
15167
+ ctx.rect(0, 0, dataW, pane.bounds.height);
15168
+ ctx.clip();
15169
+ this.drawScene.setSet(e.set, e.indexOffset);
15170
+ this.drawScene.render(
15171
+ ctx,
15172
+ dataW,
15173
+ pane.bounds.height,
15174
+ (l) => coords.logicalToX(l),
15175
+ (price) => coords.priceToY(price, pane.scale, pane.bounds) - pane.bounds.top
15176
+ );
15177
+ paneTips.push(...this.drawScene.labelTipRegions());
15178
+ for (const t of e.tables) paintTable(ctx, t, { paneHeight: pane.bounds.height, plotWidth: dataW, theme }, paneTips);
15179
+ ctx.restore();
15180
+ for (const r of paneTips) {
15181
+ this.tips.push({ ...r, top: r.top + pane.bounds.top, bottom: r.bottom + pane.bounds.top });
15182
+ }
15183
+ }
15184
+ /** Tooltip of the topmost label or table cell under a plot-space point, or null. Fed by the last prepare. */
15185
+ labelTooltipAt(x, y) {
15186
+ for (let i = this.tips.length - 1; i >= 0; i -= 1) {
15187
+ const r = this.tips[i];
15188
+ if (x >= r.left && x <= r.right && y >= r.top && y <= r.bottom) return r.text;
15189
+ }
15190
+ return null;
15191
+ }
15192
+ };
15193
+ function mergeSlices(indicator, user) {
15194
+ const out = /* @__PURE__ */ new Map();
15195
+ for (const [paneId, slices] of indicator) out.set(paneId, [...slices]);
15196
+ for (const [paneId, slices] of user) out.set(paneId, [...out.get(paneId) ?? [], ...slices]);
15197
+ for (const slices of out.values()) slices.sort((a, b) => a.beforeZ - b.beforeZ);
15198
+ return out;
15199
+ }
15200
+
14979
15201
  // src/renderers/native/drawings/Projector.ts
14980
15202
  function createProjector(coords, paneOf, paneIdAtY, barsInRange) {
14981
15203
  return {
@@ -15829,6 +16051,11 @@ var NativeRenderer = class {
15829
16051
  this.vpvrRenderer = new VpvrRenderer();
15830
16052
  this.resizeObserver = null;
15831
16053
  this.dprMedia = null;
16054
+ /** Plot size in INTEGER device px, as last reported by the resize observer's
16055
+ * device-pixel-content-box — the browser's own statement of how many device pixels
16056
+ * it paints the plot into. `null` until the first report or where the box type is
16057
+ * unsupported (WebKit); syncSize then falls back to rounding the client rect. */
16058
+ this.plotDeviceSize = null;
15832
16059
  this.coords = new CoordinateSystem();
15833
16060
  this.scene = new SceneGraph();
15834
16061
  // chosen at mount (WebGL2 if available, else canvas2d)
@@ -15836,6 +16063,8 @@ var NativeRenderer = class {
15836
16063
  this.glowAmount = 0;
15837
16064
  // WebGL2 neon-glow intensity (canvas2d ignores it)
15838
16065
  this.chrome = new ChromeRenderer();
16066
+ /** Prepaints each indicator's Pine drawings into interleave slices at the model's z. */
16067
+ this.indicatorSlices = new IndicatorDrawingSlices();
15839
16068
  /** Hover tooltips for Pine labels (canvas hit-rects collected by the chrome layer). */
15840
16069
  this.labelTooltip = null;
15841
16070
  this.crosshairLayer = new CrosshairRenderer();
@@ -15984,7 +16213,6 @@ var NativeRenderer = class {
15984
16213
  this.toggleVisibleCbs = /* @__PURE__ */ new Set();
15985
16214
  this.moveIndicatorCbs = /* @__PURE__ */ new Set();
15986
16215
  this.priceStyleCbs = /* @__PURE__ */ new Set();
15987
- this.tableOverlays = /* @__PURE__ */ new Map();
15988
16216
  this.name = "native";
15989
16217
  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"];
15990
16218
  /** Track cursor proximity to the scroll button on the plot (bubbles from the button too,
@@ -16298,7 +16526,6 @@ var NativeRenderer = class {
16298
16526
  * hidden) on clear so a re-show picks up the current theme.
16299
16527
  */
16300
16528
  setLoading(loading) {
16301
- for (const overlay of this.tableOverlays.values()) overlay.setVisible(!loading);
16302
16529
  if (!loading || !this.wrapper) {
16303
16530
  this.loadingEl?.remove();
16304
16531
  this.loadingEl = null;
@@ -16960,7 +17187,7 @@ var NativeRenderer = class {
16960
17187
  this.plot.addEventListener("pointerleave", this.onScrollProximityLeave);
16961
17188
  this.labelTooltip = new LabelTooltip(this.plot, {
16962
17189
  theme: () => this.chromeTheme(),
16963
- lookup: (x, y) => this.chrome.labelTooltipAt(x, y)
17190
+ lookup: (x, y) => this.indicatorSlices.labelTooltipAt(x, y)
16964
17191
  });
16965
17192
  this.userDrawings = new UserDrawingController(this.wrapper, this.plot, this.drawingsCanvas, {
16966
17193
  projector: () => this.drawingProjector(),
@@ -17045,6 +17272,7 @@ var NativeRenderer = class {
17045
17272
  this.emitPaneAction({ type: "maximize", paneId, maximized });
17046
17273
  }
17047
17274
  });
17275
+ this.paneControls.setSuspended(this.layoutMode === "mobile");
17048
17276
  this.axisScaleButtons = new AxisScaleButtons(this.plot, theme, {
17049
17277
  panes: () => this.axisScaleViews(),
17050
17278
  rightAxis: () => this.rightAxisW,
@@ -17054,8 +17282,19 @@ var NativeRenderer = class {
17054
17282
  if (pane) this.setPaneLog(paneId, !paneLogScale(this.scene, pane));
17055
17283
  }
17056
17284
  });
17057
- this.resizeObserver = new ResizeObserver(() => this.resize());
17285
+ this.resizeObserver = new ResizeObserver((entries) => {
17286
+ for (const e of entries) {
17287
+ if (e.target !== this.plot) continue;
17288
+ const s = e.devicePixelContentBoxSize?.[0];
17289
+ if (s) this.plotDeviceSize = { width: s.inlineSize, height: s.blockSize };
17290
+ }
17291
+ this.resize();
17292
+ });
17058
17293
  this.resizeObserver.observe(this.wrapper);
17294
+ try {
17295
+ this.resizeObserver.observe(this.plot, { box: "device-pixel-content-box" });
17296
+ } catch {
17297
+ }
17059
17298
  this.watchDpr();
17060
17299
  this.syncSize();
17061
17300
  }
@@ -17180,8 +17419,6 @@ var NativeRenderer = class {
17180
17419
  this.inputsUI?.destroy();
17181
17420
  this.paneControls?.destroy();
17182
17421
  this.axisScaleButtons?.destroy();
17183
- for (const overlay of this.tableOverlays.values()) overlay.destroy();
17184
- this.tableOverlays.clear();
17185
17422
  this.resizeObserver?.disconnect();
17186
17423
  this.resizeObserver = null;
17187
17424
  this.dprMedia?.removeEventListener("change", this.onDprChange);
@@ -17210,6 +17447,7 @@ var NativeRenderer = class {
17210
17447
  this.attributionEl = null;
17211
17448
  this.mountContainer?.style.removeProperty("--vela-toolbar-gutter");
17212
17449
  this.mountContainer?.style.removeProperty("--vela-scale-gutter");
17450
+ this.mountContainer?.style.removeProperty("--vela-bottom-gutter");
17213
17451
  this.mountContainer?.style.removeProperty("--vela-price-pane-top");
17214
17452
  this.mountContainer?.style.removeProperty("--vela-price-pane-bottom");
17215
17453
  this.mountContainer = null;
@@ -17302,7 +17540,6 @@ var NativeRenderer = class {
17302
17540
  ensurePane(pane) {
17303
17541
  this.scene.ensurePane(pane.id, pane.kind, pane.order, pane.heightWeight ?? (pane.kind === "price" ? 3 : 1));
17304
17542
  this.layoutPanes();
17305
- this.repositionTables();
17306
17543
  this.paneControls?.refresh();
17307
17544
  this.scheduler.invalidate(4 /* Full */);
17308
17545
  }
@@ -17324,17 +17561,14 @@ var NativeRenderer = class {
17324
17561
  if (!model.ownScale) this.scene.dropIndicatorScale(handle.id);
17325
17562
  this.inputsUI.setPane(handle.id, paneId);
17326
17563
  this.refreshAnchorOffset(model);
17327
- this.syncTables(model);
17328
17564
  this.refreshAxisWidth();
17329
17565
  this.layoutPanes();
17330
- this.repositionTables();
17331
17566
  this.paneControls?.refresh();
17332
17567
  this.scheduler.invalidate(4 /* Full */);
17333
17568
  }
17334
17569
  orderPanes(orderedIds) {
17335
17570
  this.scene.orderPanes(orderedIds);
17336
17571
  this.layoutPanes();
17337
- this.repositionTables();
17338
17572
  this.paneControls?.refresh();
17339
17573
  this.scheduler.invalidate(4 /* Full */);
17340
17574
  }
@@ -17343,7 +17577,6 @@ var NativeRenderer = class {
17343
17577
  if (!pane || pane.collapsed === collapsed) return;
17344
17578
  pane.collapsed = collapsed;
17345
17579
  this.layoutPanes();
17346
- this.repositionTables();
17347
17580
  this.paneControls?.refresh();
17348
17581
  this.scheduler.invalidate(4 /* Full */);
17349
17582
  }
@@ -17351,7 +17584,6 @@ var NativeRenderer = class {
17351
17584
  if (paneId !== null && !this.scene.panes.has(paneId)) paneId = null;
17352
17585
  this.maximizedPaneId = paneId;
17353
17586
  this.layoutPanes();
17354
- this.repositionTables();
17355
17587
  this.paneControls?.refresh();
17356
17588
  this.scheduler.invalidate(4 /* Full */);
17357
17589
  }
@@ -17438,7 +17670,6 @@ var NativeRenderer = class {
17438
17670
  native: !!model.native,
17439
17671
  ...model.props ? { props: model.props, propValues: model.propValues ?? {} } : {}
17440
17672
  });
17441
- this.syncTables(model);
17442
17673
  if (model.native?.type === "volume") {
17443
17674
  this.volumeActive = true;
17444
17675
  this.volumeHidden = false;
@@ -17462,7 +17693,6 @@ var NativeRenderer = class {
17462
17693
  }
17463
17694
  }
17464
17695
  applyPatch(model, patch);
17465
- this.syncTables(model);
17466
17696
  this.scheduler.invalidate(3 /* Light */);
17467
17697
  }
17468
17698
  removeIndicator(handle) {
@@ -17481,8 +17711,6 @@ var NativeRenderer = class {
17481
17711
  this.scene.forgetAnchorOffset(handle.id);
17482
17712
  this.scene.dropIndicatorScale(handle.id);
17483
17713
  this.inputsUI.remove(handle.id);
17484
- this.tableOverlays.get(handle.id)?.destroy();
17485
- this.tableOverlays.delete(handle.id);
17486
17714
  this.refreshAxisWidth();
17487
17715
  this.paneControls?.refresh();
17488
17716
  this.scheduler.invalidate(4 /* Full */);
@@ -17526,8 +17754,6 @@ var NativeRenderer = class {
17526
17754
  }
17527
17755
  if (!visible) {
17528
17756
  this.scene.indicators.delete(handle.id);
17529
- this.tableOverlays.get(handle.id)?.destroy();
17530
- this.tableOverlays.delete(handle.id);
17531
17757
  }
17532
17758
  this.inputsUI.setVisible(handle.id, visible);
17533
17759
  this.scheduler.invalidate(4 /* Full */);
@@ -17573,6 +17799,7 @@ var NativeRenderer = class {
17573
17799
  this.userDrawings?.setLayoutMode(mode);
17574
17800
  this.settingsDialog?.setLayoutMode(mode);
17575
17801
  this.inputsUI?.setLayoutMode(mode);
17802
+ this.paneControls?.setSuspended(mode === "mobile");
17576
17803
  if (this.scrollButton) {
17577
17804
  const px = mode === "mobile" ? SCROLL_BTN_SIZE_TOUCH : SCROLL_BTN_SIZE;
17578
17805
  this.scrollButton.style.width = `${px}px`;
@@ -17968,7 +18195,6 @@ var NativeRenderer = class {
17968
18195
  /** Relayout + repaint + refresh the hover buttons after a collapse/maximize/order change. */
17969
18196
  afterPaneLayoutChange() {
17970
18197
  this.layoutPanes();
17971
- this.repositionTables();
17972
18198
  this.paneControls?.refresh();
17973
18199
  this.scheduler.invalidate(4 /* Full */);
17974
18200
  }
@@ -18049,7 +18275,6 @@ var NativeRenderer = class {
18049
18275
  above.heightWeight = next.above;
18050
18276
  below.heightWeight = next.below;
18051
18277
  this.layoutPanes();
18052
- this.repositionTables();
18053
18278
  this.scheduler.invalidate(4 /* Full */);
18054
18279
  }
18055
18280
  /** Double-click a separator → split the two adjacent panes evenly (each gets half of
@@ -18064,7 +18289,6 @@ var NativeRenderer = class {
18064
18289
  above.heightWeight = half;
18065
18290
  below.heightWeight = half;
18066
18291
  this.layoutPanes();
18067
- this.repositionTables();
18068
18292
  this.scheduler.invalidate(4 /* Full */);
18069
18293
  }
18070
18294
  // ── keyboard navigation / accessibility (item 11) ──
@@ -18343,7 +18567,10 @@ var NativeRenderer = class {
18343
18567
  const liveActual = li >= 0 ? this.bars[li] : void 0;
18344
18568
  const easeLive = !!liveActual && this.liveEaseTime === liveActual.time && (liveActual.high !== this.liveEaseHigh || liveActual.low !== this.liveEaseLow || liveActual.close !== this.liveEaseClose);
18345
18569
  if (easeLive && liveActual) this.bars[li] = { ...liveActual, high: this.liveEaseHigh, low: this.liveEaseLow, close: this.liveEaseClose };
18346
- this.scene.drawingSlices = this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map();
18570
+ this.scene.drawingSlices = mergeSlices(
18571
+ this.indicatorSlices.prepare(this.scene, this.coords, this.theme, this.dataCanvas),
18572
+ this.userDrawings?.prepareSlices(this.scene.orderedPanes().map((p) => p.id)) ?? /* @__PURE__ */ new Map()
18573
+ );
18347
18574
  this.backdropRenderer.render(this.scene, this.coords, this.theme, gridAlpha);
18348
18575
  this.backend.render(this.scene, this.coords, this.theme);
18349
18576
  this.chrome.render(this.scene, this.coords, this.theme, this.axisSurface());
@@ -18781,27 +19008,6 @@ var NativeRenderer = class {
18781
19008
  }
18782
19009
  return maxVol;
18783
19010
  }
18784
- /** Create/update/destroy an indicator's DOM table overlay (anchored off real pane geometry). */
18785
- syncTables(model) {
18786
- const tables = model.tables ?? [];
18787
- let overlay = this.tableOverlays.get(model.id);
18788
- if (tables.length === 0) {
18789
- if (overlay) {
18790
- overlay.destroy();
18791
- this.tableOverlays.delete(model.id);
18792
- }
18793
- return;
18794
- }
18795
- if (!overlay) {
18796
- overlay = new TableOverlay(this.plot, this.theme, (id) => this.paneBoundsFor(id));
18797
- overlay.setVisible(this.loadingEl === null);
18798
- this.tableOverlays.set(model.id, overlay);
18799
- }
18800
- overlay.update(tables);
18801
- }
18802
- repositionTables() {
18803
- for (const overlay of this.tableOverlays.values()) overlay.reposition();
18804
- }
18805
19011
  layoutPanes() {
18806
19012
  const panes = this.scene.orderedPanes();
18807
19013
  const dataHeight = this.coords.height;
@@ -18852,6 +19058,7 @@ var NativeRenderer = class {
18852
19058
  const visible = maxPane ? [maxPane] : this.scene.orderedPanes().filter((p) => !p.collapsed);
18853
19059
  const paneBottom = visible.length ? Math.max(...visible.map((p) => p.bounds.top + p.bounds.height)) : dataHeight;
18854
19060
  this.scrollBtnBottomPx = SCROLL_BTN_BOTTOM + Math.max(0, dataHeight - paneBottom);
19061
+ this.mountContainer?.style.setProperty("--vela-bottom-gutter", `${TIME_AXIS_H + Math.max(0, dataHeight - paneBottom)}px`);
18855
19062
  this.scrollBtnRightPx = this.rightAxisW + SCROLL_BTN_RIGHT_INSET;
18856
19063
  if (this.scrollButton) {
18857
19064
  this.scrollButton.style.bottom = `${this.scrollBtnBottomPx}px`;
@@ -18944,30 +19151,33 @@ var NativeRenderer = class {
18944
19151
  if (w <= 0 || h <= 0) return;
18945
19152
  const dpr = window.devicePixelRatio || 1;
18946
19153
  this.plot.style.left = `${this.toolbarGutter}px`;
18947
- const pw = Math.max(1, w - this.toolbarGutter);
18948
- const ph = h;
18949
- this.dataCanvas.width = Math.round(pw * dpr);
18950
- this.dataCanvas.height = Math.round(ph * dpr);
18951
- this.backdropCanvas.width = this.dataCanvas.width;
18952
- this.backdropCanvas.height = this.dataCanvas.height;
18953
- this.volumeCanvas.width = this.dataCanvas.width;
18954
- this.volumeCanvas.height = this.dataCanvas.height;
18955
- for (const l of this.extLayers) {
18956
- l.canvas.width = this.dataCanvas.width;
18957
- l.canvas.height = this.dataCanvas.height;
18958
- }
18959
- this.vpvrCanvas.width = this.dataCanvas.width;
18960
- this.vpvrCanvas.height = this.dataCanvas.height;
18961
- this.chromeCanvas.width = this.dataCanvas.width;
18962
- this.chromeCanvas.height = this.dataCanvas.height;
18963
- this.drawingsCanvas.width = this.dataCanvas.width;
18964
- this.drawingsCanvas.height = this.dataCanvas.height;
18965
- this.cursorCanvas.width = this.dataCanvas.width;
18966
- this.cursorCanvas.height = this.dataCanvas.height;
19154
+ const rect = this.plot.getBoundingClientRect();
19155
+ let bw = Math.max(1, Math.round(rect.width * dpr));
19156
+ let bh = Math.max(1, Math.round(rect.height * dpr));
19157
+ const dev = this.plotDeviceSize;
19158
+ if (dev && Math.abs(dev.width - rect.width * dpr) <= 1 && Math.abs(dev.height - rect.height * dpr) <= 1) {
19159
+ bw = Math.max(1, dev.width);
19160
+ bh = Math.max(1, dev.height);
19161
+ }
19162
+ const pw = bw / dpr;
19163
+ const ph = bh / dpr;
19164
+ const size = (canvas) => {
19165
+ canvas.width = bw;
19166
+ canvas.height = bh;
19167
+ canvas.style.width = `${pw}px`;
19168
+ canvas.style.height = `${ph}px`;
19169
+ };
19170
+ size(this.dataCanvas);
19171
+ size(this.backdropCanvas);
19172
+ size(this.volumeCanvas);
19173
+ for (const l of this.extLayers) size(l.canvas);
19174
+ size(this.vpvrCanvas);
19175
+ size(this.chromeCanvas);
19176
+ size(this.drawingsCanvas);
19177
+ size(this.cursorCanvas);
18967
19178
  this.coords.setSize(Math.max(1, pw - this.rightAxisW), Math.max(1, ph - TIME_AXIS_H), dpr);
18968
19179
  this.scene.crosshair = null;
18969
19180
  this.layoutPanes();
18970
- this.repositionTables();
18971
19181
  this.userDrawings?.onResize();
18972
19182
  if (!this.didInitialFit && this.coords.barCount > 0) {
18973
19183
  this.fitContent();
@@ -19359,6 +19569,10 @@ var EngineOrchestrator = class _EngineOrchestrator {
19359
19569
  /** Invalidates detached async work (backfill loops, in-flight loads, gap heals):
19360
19570
  * bumped by init(), setMarket() and destroy(). */
19361
19571
  this.generation = 0;
19572
+ /** Aborts the in-flight PROGRESSIVE load's source polling on supersession — an
19573
+ * abandoned stream left polling to its own budget starves the browser's per-host
19574
+ * connection pool, and the NEXT symbol's very first fetch with it (measured). */
19575
+ this.progressiveAbort = null;
19362
19576
  /** Awaiters racing a superseded load (setMarket callers) — released on every bump so they never hang. */
19363
19577
  this.supersedeWaiters = [];
19364
19578
  /** `history:complete` fired for the CURRENT load. Each market load re-arms the cycle
@@ -19501,6 +19715,8 @@ var EngineOrchestrator = class _EngineOrchestrator {
19501
19715
  * superseded setMarket awaiters so their promises resolve instead of hanging. */
19502
19716
  bumpGeneration() {
19503
19717
  const gen = ++this.generation;
19718
+ this.progressiveAbort?.abort();
19719
+ this.progressiveAbort = null;
19504
19720
  for (const w of this.supersedeWaiters.splice(0)) w();
19505
19721
  return gen;
19506
19722
  }
@@ -19554,7 +19770,47 @@ var EngineOrchestrator = class _EngineOrchestrator {
19554
19770
  const requested = market.bars ?? 500;
19555
19771
  const initialRange = market.visibleRange;
19556
19772
  const deep = !market.data?.length && initialRange == null && requested > SINGLE_LOAD_BARS;
19557
- if (deep && this.feed.loadRange) {
19773
+ let progressiveServed = false;
19774
+ if (!market.data?.length && initialRange == null && this.feed.loadProgressive) {
19775
+ let painted = false;
19776
+ const paint = (bars, final) => {
19777
+ if (this.generation !== gen || !final && bars.length === 0) return;
19778
+ this.setBarSeries(bars, painted ? { preserveView: true } : void 0);
19779
+ if (!painted && bars.length > 0) {
19780
+ painted = true;
19781
+ if (opts.firstLoad) this.activateBarLayers();
19782
+ if (!final) this.historyState = "backfill";
19783
+ }
19784
+ };
19785
+ const abort = new AbortController();
19786
+ this.progressiveAbort = abort;
19787
+ progressiveServed = await new Promise((firstPaint) => {
19788
+ let signaled = false;
19789
+ const signal = (served) => {
19790
+ if (!signaled) {
19791
+ signaled = true;
19792
+ firstPaint(served);
19793
+ }
19794
+ };
19795
+ abort.signal.addEventListener("abort", () => signal(true), { once: true });
19796
+ this.feed.loadProgressive(market, (bars) => {
19797
+ paint(bars, false);
19798
+ if (painted) signal(true);
19799
+ }, { signal: abort.signal }).then((full) => {
19800
+ if (this.progressiveAbort === abort) this.progressiveAbort = null;
19801
+ if (full == null) return signal(false);
19802
+ if (this.generation !== gen) return signal(true);
19803
+ paint(full, true);
19804
+ this.completeHistory(full.length >= requested ? "depth" : "genesis");
19805
+ signal(true);
19806
+ }).catch(() => {
19807
+ if (this.progressiveAbort === abort) this.progressiveAbort = null;
19808
+ if (this.generation === gen) this.completeHistory("aborted");
19809
+ signal(true);
19810
+ });
19811
+ });
19812
+ }
19813
+ if (progressiveServed) ; else if (deep && this.feed.loadRange) {
19558
19814
  const head = await this.feed.load({ ...market, bars: Math.min(requested, CHUNK_BARS) });
19559
19815
  if (this.generation !== gen) return;
19560
19816
  this.setBarSeries(head);
@@ -21374,4 +21630,4 @@ function resolveElement(container) {
21374
21630
  return element;
21375
21631
  }
21376
21632
 
21377
- export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MultiProviderFeed, NativeRenderer, RendererControl, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultToolbar, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, sharedBarStore, timeframeToMs, tzButtonLabel, tzMenuLabel, tzOffset };
21633
+ export { BUILTIN_PRICE_STYLES, BarStore, CachingDataFeed, DARK_THEME, DataControl, DrawingStore, DrawingToolbar, DrawingsControl, LEGEND_AT_TOP_ATTR, LIGHT_THEME, MultiProviderFeed, NativeRenderer, RendererControl, TIMEZONES, TypedEventBus, Vela, applyAttributionMarkTheme, buildToolbar, createAttributionMark, createCustomMark, defaultMemberOf, defaultToolbar, groupKeyOf, groupMembers, isGroupRow, normalizeTimezone, parseSymbol, priceStyleIds, registerBuiltinChartTypes, resolveTheme, sharedBarStore, timeframeToMs, tzButtonLabel, tzMenuLabel, tzOffset };