@coderyo/renderer-lite 1.0.3 → 1.1.0

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.
package/dist/index.js CHANGED
@@ -19,7 +19,10 @@ function gridOptions(showGrid, dark) {
19
19
  }
20
20
 
21
21
  // src/pane-orchestrator.ts
22
- import { hasVisibleIndicatorPanes as hasVisibleIndicatorPanes2 } from "@coderyo/indicators";
22
+ import {
23
+ DEFAULT_INDICATOR_CONFIG as DEFAULT_INDICATOR_CONFIG2,
24
+ hasVisibleIndicatorPanes as hasVisibleIndicatorPanes2
25
+ } from "@coderyo/indicators";
23
26
 
24
27
  // src/indicator-panes.ts
25
28
  import {
@@ -46,18 +49,25 @@ function attachPaneResizer(topPane, bottomPane, opts = {}) {
46
49
  const parent = topPane.parentElement;
47
50
  if (!parent) return () => {
48
51
  };
52
+ let dragging = false;
49
53
  const handle = document.createElement("div");
50
- handle.style.cssText = "height:4px;cursor:row-resize;background:#30363d;flex-shrink:0;touch-action:none;";
54
+ handle.dataset.paneResizer = "1";
55
+ handle.style.cssText = "height:6px;cursor:row-resize;background:#30363d;flex-shrink:0;touch-action:none;z-index:5;";
56
+ handle.onmouseenter = () => {
57
+ handle.style.background = "#388bfd";
58
+ };
59
+ handle.onmouseleave = () => {
60
+ if (!dragging) handle.style.background = "#30363d";
61
+ };
51
62
  bottomPane.insertAdjacentElement("beforebegin", handle);
52
63
  const saved = opts.storageKey ? localStorage.getItem(opts.storageKey) : null;
53
64
  if (saved) {
54
65
  const ratio = Number(saved);
55
- if (Number.isFinite(ratio) && ratio > 0 && ratio < 1) {
66
+ if (Number.isFinite(ratio) && ratio >= 0.15 && ratio <= 0.85) {
56
67
  topPane.style.flex = `${ratio * 10}`;
57
68
  bottomPane.style.flex = `${(1 - ratio) * 10}`;
58
69
  }
59
70
  }
60
- let dragging = false;
61
71
  const onMove = (clientY) => {
62
72
  const rect = parent.getBoundingClientRect();
63
73
  const y = clientY - rect.top;
@@ -70,8 +80,11 @@ function attachPaneResizer(topPane, bottomPane, opts = {}) {
70
80
  if (opts.storageKey) localStorage.setItem(opts.storageKey, String(ratio));
71
81
  };
72
82
  const stop = () => {
83
+ const wasDragging = dragging;
73
84
  dragging = false;
74
85
  document.body.style.cursor = "";
86
+ handle.style.background = "#30363d";
87
+ if (wasDragging) window.dispatchEvent(new CustomEvent("tradview:pane-resize"));
75
88
  };
76
89
  handle.addEventListener("pointerdown", (e) => {
77
90
  dragging = true;
@@ -79,7 +92,10 @@ function attachPaneResizer(topPane, bottomPane, opts = {}) {
79
92
  document.body.style.cursor = "row-resize";
80
93
  });
81
94
  handle.addEventListener("pointermove", (e) => {
82
- if (dragging) onMove(e.clientY);
95
+ if (dragging) {
96
+ onMove(e.clientY);
97
+ window.dispatchEvent(new CustomEvent("tradview:pane-resize"));
98
+ }
83
99
  });
84
100
  handle.addEventListener("pointerup", stop);
85
101
  handle.addEventListener("pointercancel", stop);
@@ -150,20 +166,6 @@ var IndicatorPaneStack = class {
150
166
  this.macdWrap = macdPane.wrap;
151
167
  this.rsiWrap = rsiPane.wrap;
152
168
  this.kdjWrap = kdjPane.wrap;
153
- this.root.append(macdPane.wrap, rsiPane.wrap, kdjPane.wrap);
154
- this.detachResizers.push(
155
- attachPaneResizer(macdPane.wrap, rsiPane.wrap, {
156
- storageKey: "tradview:pane:macd-rsi",
157
- minTopPx: 72,
158
- minBottomPx: 72
159
- }),
160
- attachPaneResizer(rsiPane.wrap, kdjPane.wrap, {
161
- storageKey: "tradview:pane:rsi-kdj",
162
- minTopPx: 72,
163
- minBottomPx: 72
164
- })
165
- );
166
- this.applyPaneVisibility();
167
169
  const layout = this.layoutForTheme(this.dark);
168
170
  const grid = gridOptions(this.showGrid, this.dark);
169
171
  this.macdChart = createChart(macdPane.el, { layout, grid, autoSize: true });
@@ -179,6 +181,7 @@ var IndicatorPaneStack = class {
179
181
  this.kdjK = this.kdjChart.addSeries(LineSeries, { color: "#42a5f5", lineWidth: 1 });
180
182
  this.kdjD = this.kdjChart.addSeries(LineSeries, { color: "#ffa726", lineWidth: 1 });
181
183
  this.kdjJ = this.kdjChart.addSeries(LineSeries, { color: "#ef5350", lineWidth: 1 });
184
+ this.applyPaneVisibility();
182
185
  }
183
186
  root;
184
187
  macdChart;
@@ -215,11 +218,32 @@ var IndicatorPaneStack = class {
215
218
  this.onConfigChange?.(this.config);
216
219
  }
217
220
  applyPaneVisibility() {
218
- this.macdWrap.style.display = this.config.showMacd ? "" : "none";
219
- this.rsiWrap.style.display = this.config.showRsi ? "" : "none";
220
- this.kdjWrap.style.display = this.config.showKdj ? "" : "none";
221
221
  const anyVisible = this.config.showMacd || this.config.showRsi || this.config.showKdj;
222
222
  this.root.style.display = anyVisible ? "flex" : "none";
223
+ this.rebuildPaneLayout();
224
+ }
225
+ /** Rebuild flex children and drag handles only between visible panes. */
226
+ rebuildPaneLayout() {
227
+ for (const detach of this.detachResizers) detach();
228
+ this.detachResizers.length = 0;
229
+ this.root.querySelectorAll("[data-pane-resizer]").forEach((el) => el.remove());
230
+ const panes = [];
231
+ if (this.config.showMacd) panes.push({ id: "macd", el: this.macdWrap });
232
+ if (this.config.showRsi) panes.push({ id: "rsi", el: this.rsiWrap });
233
+ if (this.config.showKdj) panes.push({ id: "kdj", el: this.kdjWrap });
234
+ this.root.replaceChildren(...panes.map((p) => p.el));
235
+ for (let i = 0; i < panes.length - 1; i++) {
236
+ const top = panes[i];
237
+ const bottom = panes[i + 1];
238
+ this.detachResizers.push(
239
+ attachPaneResizer(top.el, bottom.el, {
240
+ storageKey: `tradview:pane:${top.id}-${bottom.id}`,
241
+ minTopPx: 72,
242
+ minBottomPx: 72
243
+ })
244
+ );
245
+ }
246
+ this.resize();
223
247
  }
224
248
  clearBars() {
225
249
  this.macdLine.setData([]);
@@ -338,11 +362,14 @@ var IndicatorPaneStack = class {
338
362
  this.kdjChart.timeScale().scrollToRealTime();
339
363
  }
340
364
  resize() {
341
- for (const { chart, el } of [
342
- { chart: this.macdChart, el: this.macdChart.chartElement().parentElement },
343
- { chart: this.rsiChart, el: this.rsiChart.chartElement().parentElement },
344
- { chart: this.kdjChart, el: this.kdjChart.chartElement().parentElement }
345
- ]) {
365
+ const panes = [
366
+ { show: this.config.showMacd, chart: this.macdChart },
367
+ { show: this.config.showRsi, chart: this.rsiChart },
368
+ { show: this.config.showKdj, chart: this.kdjChart }
369
+ ];
370
+ for (const { show, chart } of panes) {
371
+ if (!show || !chart) continue;
372
+ const el = chart.chartElement()?.parentElement;
346
373
  if (!el) continue;
347
374
  const w = el.clientWidth;
348
375
  const h = el.clientHeight;
@@ -360,6 +387,7 @@ var IndicatorPaneStack = class {
360
387
  createPaneWrap(label, paneId) {
361
388
  const wrap = document.createElement("div");
362
389
  wrap.className = `tv-indicator-pane tv-indicator-pane--${paneId}`;
390
+ wrap.dataset.paneId = paneId;
363
391
  wrap.style.cssText = "flex:1;min-height:72px;width:100%;position:relative;border-top:1px solid #30363d;";
364
392
  const tag = document.createElement("span");
365
393
  tag.textContent = label;
@@ -390,6 +418,10 @@ var IndicatorPaneStack = class {
390
418
  textColor: dark ? "#e6edf3" : "#24292f"
391
419
  };
392
420
  }
421
+ /** LWC instances for sync-group reassignment. */
422
+ getCharts() {
423
+ return [this.macdChart, this.rsiChart, this.kdjChart];
424
+ }
393
425
  };
394
426
  function maOverlayLine(bars, period = 20, source = "close") {
395
427
  const src = barsForSource(bars, source);
@@ -424,6 +456,7 @@ var TimeScaleBus = class {
424
456
  if (this.charts.includes(chart)) return;
425
457
  this.charts.push(chart);
426
458
  chart.timeScale().subscribeVisibleLogicalRangeChange((range) => {
459
+ if (!this.charts.includes(chart)) return;
427
460
  if (this.syncing || !range) return;
428
461
  const tr = chart.timeScale().getVisibleRange();
429
462
  if (tr && typeof tr.from === "number" && typeof tr.to === "number") {
@@ -433,6 +466,10 @@ var TimeScaleBus = class {
433
466
  this.syncFrom(chart, range);
434
467
  });
435
468
  }
469
+ unregister(chart) {
470
+ const i = this.charts.indexOf(chart);
471
+ if (i >= 0) this.charts.splice(i, 1);
472
+ }
436
473
  subscribeTransform(listener) {
437
474
  this.listeners.add(listener);
438
475
  return () => this.listeners.delete(listener);
@@ -501,6 +538,78 @@ var TimeScaleBus = class {
501
538
  }
502
539
  };
503
540
 
541
+ // src/time-scale-bus-registry.ts
542
+ function normalizeSyncGroupId(id) {
543
+ if (id == null) return null;
544
+ const trimmed = String(id).trim();
545
+ return trimmed.length > 0 ? trimmed : null;
546
+ }
547
+ var INDEP_PREFIX = "@independent:";
548
+ function independentBusKey(pane) {
549
+ return `${INDEP_PREFIX}${pane}`;
550
+ }
551
+ function resolveBusMapKey(groupId, pane) {
552
+ const norm = normalizeSyncGroupId(groupId);
553
+ return norm ?? independentBusKey(pane);
554
+ }
555
+ var TimeScaleBusRegistry = class {
556
+ buses = /* @__PURE__ */ new Map();
557
+ paneKeys = /* @__PURE__ */ new Map();
558
+ activeKey;
559
+ constructor() {
560
+ for (const pane of ["main", "volume", "indicator"]) {
561
+ const key = independentBusKey(pane);
562
+ this.paneKeys.set(pane, key);
563
+ this.getOrCreateBus(key);
564
+ }
565
+ this.activeKey = this.paneKeys.get("main");
566
+ }
567
+ getOrCreateBus(key) {
568
+ let bus = this.buses.get(key);
569
+ if (!bus) {
570
+ bus = new TimeScaleBus();
571
+ this.buses.set(key, bus);
572
+ }
573
+ return bus;
574
+ }
575
+ getBusKeyForPane(pane) {
576
+ return this.paneKeys.get(pane);
577
+ }
578
+ getBusForPane(pane) {
579
+ return this.getOrCreateBus(this.getBusKeyForPane(pane));
580
+ }
581
+ /** Active bus for IChart viewport APIs (follows last-focused chart pane). */
582
+ get activeBus() {
583
+ return this.getOrCreateBus(this.activeKey);
584
+ }
585
+ getActiveBusKey() {
586
+ return this.activeKey;
587
+ }
588
+ setActivePane(pane) {
589
+ this.activeKey = this.paneKeys.get(pane);
590
+ }
591
+ setPaneSyncGroup(pane, groupId) {
592
+ const nextKey = resolveBusMapKey(groupId, pane);
593
+ const prevKey = this.paneKeys.get(pane);
594
+ this.paneKeys.set(pane, nextKey);
595
+ this.getOrCreateBus(nextKey);
596
+ if (this.activeKey === prevKey) this.activeKey = nextKey;
597
+ return prevKey;
598
+ }
599
+ forEachBus(fn) {
600
+ for (const [key, bus] of this.buses) fn(key, bus);
601
+ }
602
+ moveChart(chart, fromKey, toKey, copyRange = true) {
603
+ if (fromKey === toKey) return;
604
+ const from = this.getOrCreateBus(fromKey);
605
+ const to = this.getOrCreateBus(toKey);
606
+ const range = copyRange ? from.getVisibleRange() : null;
607
+ from.unregister(chart);
608
+ to.register(chart);
609
+ if (range) to.setVisibleTimeRange(range);
610
+ }
611
+ };
612
+
504
613
  // src/viewport-fit.ts
505
614
  import { intervalMs } from "@coderyo/data";
506
615
  function defaultBarSpacingForInterval(interval) {
@@ -581,6 +690,13 @@ var BarSmoothAnimator = class {
581
690
  };
582
691
 
583
692
  // src/pane-orchestrator.ts
693
+ function isLayeredPaneMount(opts) {
694
+ return !!opts.volumeMount;
695
+ }
696
+ function shouldResizeChartPane(focus, pane) {
697
+ if (!focus) return true;
698
+ return focus.has(pane);
699
+ }
584
700
  function toUtcSeconds2(tMs) {
585
701
  return Math.floor(tMs / 1e3);
586
702
  }
@@ -591,7 +707,18 @@ function barToVolume(b) {
591
707
  return { time: toUtcSeconds2(b.t), value: b.v ?? 0 };
592
708
  }
593
709
  var PaneOrchestrator = class {
594
- bus = new TimeScaleBus();
710
+ busRegistry = new TimeScaleBusRegistry();
711
+ /** Active sync group bus (last-focused pane); used by ChartController viewport APIs. */
712
+ get bus() {
713
+ return this.busRegistry.activeBus;
714
+ }
715
+ layeredPanes;
716
+ mainEl;
717
+ volWrap;
718
+ detachMainVolResizer = () => {
719
+ };
720
+ resizeFocusPanes = null;
721
+ listenPaneResizeEvents;
595
722
  mainChart;
596
723
  volumeChart;
597
724
  mainSeries;
@@ -614,6 +741,9 @@ var PaneOrchestrator = class {
614
741
  barTimesOrdered = [];
615
742
  didInitialFit = false;
616
743
  skipNextInitialFit = false;
744
+ /** setBars ran before the pane had layout size; refit on first real resize. */
745
+ pendingViewportFit = false;
746
+ pendingViewportBars = null;
617
747
  indicatorConfig = null;
618
748
  onIndicatorConfigChange;
619
749
  currentInterval = "1h";
@@ -624,18 +754,57 @@ var PaneOrchestrator = class {
624
754
  smoothPriceDurationMs = 150;
625
755
  constructor(opts) {
626
756
  this.maxRenderPoints = opts.maxRenderPoints ?? 4e3;
757
+ this.listenPaneResizeEvents = opts.listenPaneResizeEvents !== false;
627
758
  this.dark = opts.theme !== "light";
628
759
  this.showGrid = opts.showGrid ?? false;
629
760
  const layout = this.layoutForTheme(this.dark);
630
761
  const grid = gridOptions(this.showGrid, this.dark);
631
- const mainEl = document.createElement("div");
632
- mainEl.style.cssText = "flex:7;min-height:120px;width:100%;position:relative;";
762
+ this.layeredPanes = !!opts.volumeMount;
633
763
  const volEl = document.createElement("div");
634
- volEl.style.cssText = "flex:2;min-height:64px;width:100%;position:relative;";
635
- opts.container.style.cssText = "display:flex;flex-direction:column;height:100%;width:100%;min-height:240px;overflow:hidden;";
636
- opts.container.append(mainEl, volEl);
637
- attachPaneResizer(mainEl, volEl, { storageKey: "tradview:pane:main-volume" });
638
- this.mainChart = createChart2(mainEl, { layout, grid, autoSize: true });
764
+ volEl.style.cssText = "width:100%;height:100%;min-height:0;position:relative;";
765
+ if (this.layeredPanes) {
766
+ this.mainEl = opts.container;
767
+ this.mainEl.style.cssText = "width:100%;height:100%;min-height:80px;position:relative;overflow:hidden;";
768
+ this.volWrap = opts.volumeMount;
769
+ this.volWrap.dataset.paneId = "volume";
770
+ this.volWrap.className = "tv-volume-pane tv-volume-pane--layered";
771
+ this.volWrap.replaceChildren();
772
+ this.volWrap.style.cssText = "width:100%;height:100%;min-height:48px;position:relative;overflow:hidden;box-sizing:border-box;";
773
+ const volTag = document.createElement("span");
774
+ volTag.textContent = "Volume";
775
+ volTag.style.cssText = "position:absolute;left:6px;top:4px;z-index:2;font-size:10px;color:#8b949e;pointer-events:none;";
776
+ const volClose = document.createElement("button");
777
+ volClose.type = "button";
778
+ volClose.textContent = "\xD7";
779
+ volClose.title = "\u95DC\u9589\u6210\u4EA4\u91CF";
780
+ volClose.setAttribute("aria-label", "Close volume");
781
+ volClose.style.cssText = "position:absolute;right:6px;top:4px;z-index:3;width:22px;height:22px;padding:0;border:1px solid #30363d;border-radius:4px;background:#21262d;color:#8b949e;cursor:pointer;font-size:14px;line-height:1;";
782
+ volClose.onclick = () => this.closeVolumePane();
783
+ this.volWrap.append(volTag, volClose, volEl);
784
+ } else {
785
+ this.mainEl = document.createElement("div");
786
+ this.mainEl.style.cssText = "flex:7;min-height:120px;width:100%;position:relative;";
787
+ volEl.style.cssText = "flex:1;min-height:0;width:100%;height:100%;position:relative;";
788
+ this.volWrap = document.createElement("div");
789
+ this.volWrap.dataset.paneId = "volume";
790
+ this.volWrap.className = "tv-volume-pane";
791
+ this.volWrap.style.cssText = "flex:2;min-height:48px;width:100%;position:relative;display:flex;flex-direction:column;border-top:1px solid #30363d;";
792
+ const volTag = document.createElement("span");
793
+ volTag.textContent = "Volume";
794
+ volTag.style.cssText = "position:absolute;left:6px;top:4px;z-index:2;font-size:10px;color:#8b949e;pointer-events:none;";
795
+ const volClose = document.createElement("button");
796
+ volClose.type = "button";
797
+ volClose.textContent = "\xD7";
798
+ volClose.title = "\u95DC\u9589\u6210\u4EA4\u91CF";
799
+ volClose.setAttribute("aria-label", "Close volume");
800
+ volClose.style.cssText = "position:absolute;right:6px;top:4px;z-index:3;width:22px;height:22px;padding:0;border:1px solid #30363d;border-radius:4px;background:#21262d;color:#8b949e;cursor:pointer;font-size:14px;line-height:1;";
801
+ volClose.onclick = () => this.closeVolumePane();
802
+ this.volWrap.append(volTag, volClose, volEl);
803
+ opts.container.style.cssText = "display:flex;flex-direction:column;height:100%;width:100%;min-height:200px;overflow:hidden;";
804
+ opts.container.append(this.mainEl, this.volWrap);
805
+ this.rebuildMainVolumeResizer();
806
+ }
807
+ this.mainChart = createChart2(this.mainEl, { layout, grid, autoSize: true });
639
808
  this.volumeChart = createChart2(volEl, {
640
809
  layout,
641
810
  grid,
@@ -691,8 +860,8 @@ var PaneOrchestrator = class {
691
860
  lineWidth: 1,
692
861
  title: "VolMA5"
693
862
  });
694
- this.bus.register(this.mainChart);
695
- this.bus.register(this.volumeChart);
863
+ this.busRegistry.getBusForPane("main").register(this.mainChart);
864
+ this.busRegistry.getBusForPane("volume").register(this.volumeChart);
696
865
  this.indicatorRoot = opts.indicatorRoot;
697
866
  this.indicatorConfig = opts.indicatorConfig ?? null;
698
867
  this.onIndicatorConfigChange = opts.onIndicatorConfigChange;
@@ -700,9 +869,16 @@ var PaneOrchestrator = class {
700
869
  this.barSpacingByInterval = opts.barSpacingByInterval;
701
870
  this.pinePlots = opts.pinePlots ?? null;
702
871
  this.indicators = this.createIndicatorStack();
703
- this.initOverlay(mainEl);
872
+ this.initOverlay(this.mainEl);
704
873
  this.setSmoothPriceUpdate(opts.smoothPriceUpdate ?? false, opts.smoothPriceDurationMs);
874
+ this.applyVolumeVisibility();
875
+ if (opts.listenPaneResizeEvents !== false) {
876
+ window.addEventListener("tradview:pane-resize", this.onPaneResize);
877
+ }
705
878
  }
879
+ onPaneResize = () => {
880
+ this.resize();
881
+ };
706
882
  setSmoothPriceUpdate(enabled, durationMs = 150) {
707
883
  this.smoothPriceDurationMs = durationMs;
708
884
  if (enabled) {
@@ -736,7 +912,9 @@ var PaneOrchestrator = class {
736
912
  applyLastBarToSeries(bar) {
737
913
  this.barByTime.set(bar.t, bar);
738
914
  this.mainSeries.update(barToCandle(bar));
739
- this.volumeSeries.update(barToVolume(bar));
915
+ if (this.isVolumeVisible()) {
916
+ this.volumeSeries.update(barToVolume(bar));
917
+ }
740
918
  this.ensurePriceLine(bar.c);
741
919
  if (this.indicatorConfig) {
742
920
  const bars = [...this.barByTime.values()].sort((a, b) => a.t - b.t);
@@ -781,10 +959,12 @@ var PaneOrchestrator = class {
781
959
  this.bollMiddle.setData([]);
782
960
  this.bollLower.setData([]);
783
961
  this.volMaSeries.setData([]);
962
+ this.volumeSeries.setData([]);
784
963
  this.emaSeries.applyOptions({ visible: false });
785
964
  this.bollUpper.applyOptions({ visible: false });
786
965
  this.bollMiddle.applyOptions({ visible: false });
787
966
  this.bollLower.applyOptions({ visible: false });
967
+ this.applyVolumeVisibility();
788
968
  return;
789
969
  }
790
970
  if (!this.indicators) this.indicators = this.createIndicatorStack();
@@ -796,6 +976,81 @@ var PaneOrchestrator = class {
796
976
  this.indicators?.setBars(bars);
797
977
  }
798
978
  }
979
+ this.applyVolumeVisibility();
980
+ }
981
+ isVolumeVisible() {
982
+ return this.indicatorConfig?.showVolume ?? true;
983
+ }
984
+ closeVolumePane() {
985
+ if (!this.indicatorConfig) {
986
+ this.indicatorConfig = { ...DEFAULT_INDICATOR_CONFIG2, showVolume: false };
987
+ } else {
988
+ this.indicatorConfig = { ...this.indicatorConfig, showVolume: false };
989
+ }
990
+ this.applyVolumeVisibility();
991
+ this.onIndicatorConfigChange?.(this.indicatorConfig);
992
+ }
993
+ applyVolumeVisibility() {
994
+ const show = this.isVolumeVisible();
995
+ this.volWrap.style.display = show ? "" : "none";
996
+ this.rebuildMainVolumeResizer();
997
+ if (!show) {
998
+ this.volumeSeries.setData([]);
999
+ this.volMaSeries.setData([]);
1000
+ }
1001
+ this.syncChartSize();
1002
+ }
1003
+ /** Assign per-pane sync group ids (`''` / omit = independent). Re-registers LWC charts on group change. */
1004
+ setPaneSyncGroups(patch) {
1005
+ const apply = (pane, groupId) => {
1006
+ const prevKey = this.busRegistry.setPaneSyncGroup(pane, groupId);
1007
+ const nextKey = this.busRegistry.getBusKeyForPane(pane);
1008
+ if (pane === "main") {
1009
+ this.busRegistry.moveChart(this.mainChart, prevKey, nextKey);
1010
+ } else if (pane === "volume") {
1011
+ this.busRegistry.moveChart(this.volumeChart, prevKey, nextKey);
1012
+ } else if (this.indicators) {
1013
+ for (const chart of this.indicators.getCharts()) {
1014
+ this.busRegistry.moveChart(chart, prevKey, nextKey);
1015
+ }
1016
+ }
1017
+ };
1018
+ if (patch.main !== void 0) apply("main", patch.main);
1019
+ if (patch.volume !== void 0) apply("volume", patch.volume);
1020
+ if (patch.indicator !== void 0) apply("indicator", patch.indicator);
1021
+ }
1022
+ setActiveSyncPane(pane) {
1023
+ const key = pane === "volume" ? "volume" : pane === "indicator" ? "indicator" : "main";
1024
+ this.busRegistry.setActivePane(key);
1025
+ }
1026
+ /** P2: when set, only these panes get LWC resize (panes in the same sync group still share TimeScaleBus). */
1027
+ setResizeFocusPanes(panes) {
1028
+ this.resizeFocusPanes = panes?.length ? new Set(panes) : null;
1029
+ this.resize();
1030
+ }
1031
+ /** Current resize focus (null = all panes). @internal — not part of public package API. */
1032
+ getResizeFocusPanes() {
1033
+ return this.resizeFocusPanes ? [...this.resizeFocusPanes] : null;
1034
+ }
1035
+ shouldResizePane(pane) {
1036
+ return shouldResizeChartPane(this.resizeFocusPanes, pane);
1037
+ }
1038
+ rebuildMainVolumeResizer() {
1039
+ if (this.layeredPanes) return;
1040
+ this.detachMainVolResizer();
1041
+ const parent = this.mainEl.parentElement;
1042
+ parent?.querySelectorAll(":scope > [data-pane-resizer]").forEach((el) => el.remove());
1043
+ if (!this.isVolumeVisible()) {
1044
+ this.mainEl.style.flex = "1";
1045
+ return;
1046
+ }
1047
+ this.mainEl.style.flex = "";
1048
+ this.volWrap.style.flex = "";
1049
+ this.detachMainVolResizer = attachPaneResizer(this.mainEl, this.volWrap, {
1050
+ storageKey: "tradview:pane:main-volume",
1051
+ minTopPx: 120,
1052
+ minBottomPx: 48
1053
+ });
799
1054
  }
800
1055
  setPinePlots(plots) {
801
1056
  this.pinePlots = plots;
@@ -897,7 +1152,11 @@ var PaneOrchestrator = class {
897
1152
  vols.push(barToVolume(b));
898
1153
  }
899
1154
  this.mainSeries.setData(candles);
900
- this.volumeSeries.setData(vols);
1155
+ if (this.isVolumeVisible()) {
1156
+ this.volumeSeries.setData(vols);
1157
+ } else {
1158
+ this.volumeSeries.setData([]);
1159
+ }
901
1160
  if (this.indicatorConfig) {
902
1161
  this.applyMainOverlays(renderBars);
903
1162
  if (hasVisibleIndicatorPanes2(this.indicatorConfig)) {
@@ -914,13 +1173,35 @@ var PaneOrchestrator = class {
914
1173
  }
915
1174
  if (renderBars.length > 0) {
916
1175
  this.syncChartSize();
917
- if (!this.didInitialFit && !this.skipNextInitialFit) {
918
- this.applyViewAfterDataReload(renderBars);
919
- this.didInitialFit = true;
920
- }
1176
+ this.tryInitialViewportFit(renderBars);
921
1177
  this.skipNextInitialFit = false;
922
1178
  }
923
1179
  }
1180
+ mainPaneHasSize() {
1181
+ const el = this.mainChart.chartElement().parentElement;
1182
+ return (el?.clientWidth ?? 0) > 0 && (el?.clientHeight ?? 0) > 0;
1183
+ }
1184
+ tryInitialViewportFit(renderBars) {
1185
+ if (this.didInitialFit || this.skipNextInitialFit) return;
1186
+ if (!this.mainPaneHasSize()) {
1187
+ this.pendingViewportFit = true;
1188
+ this.pendingViewportBars = renderBars;
1189
+ return;
1190
+ }
1191
+ this.applyViewAfterDataReload(renderBars);
1192
+ this.didInitialFit = true;
1193
+ this.pendingViewportFit = false;
1194
+ this.pendingViewportBars = null;
1195
+ }
1196
+ flushPendingViewportFit() {
1197
+ if (!this.pendingViewportFit || this.skipNextInitialFit || this.didInitialFit) return;
1198
+ const bars = this.pendingViewportBars ?? [...this.barByTime.values()].sort((a, b) => a.t - b.t);
1199
+ if (bars.length === 0 || !this.mainPaneHasSize()) return;
1200
+ this.applyViewAfterDataReload(bars);
1201
+ this.didInitialFit = true;
1202
+ this.pendingViewportFit = false;
1203
+ this.pendingViewportBars = null;
1204
+ }
924
1205
  subscribeCrosshair(listener) {
925
1206
  const handler = (param) => {
926
1207
  if (param.time == null || !param.point) {
@@ -957,7 +1238,7 @@ var PaneOrchestrator = class {
957
1238
  }
958
1239
  createIndicatorStack() {
959
1240
  if (!this.indicatorRoot || !this.indicatorConfig) return null;
960
- return new IndicatorPaneStack(this.indicatorRoot, this.bus, {
1241
+ return new IndicatorPaneStack(this.indicatorRoot, this.busRegistry.getBusForPane("indicator"), {
961
1242
  theme: this.dark ? "dark" : "light",
962
1243
  showGrid: this.showGrid,
963
1244
  config: this.indicatorConfig,
@@ -972,7 +1253,7 @@ var PaneOrchestrator = class {
972
1253
  if (!this.autoBarSpacingOnInterval) return;
973
1254
  const iv = interval ?? this.currentInterval;
974
1255
  const spacing = resolveBarSpacingForInterval(iv, this.barSpacingByInterval);
975
- this.bus.setBarSpacing(spacing);
1256
+ this.busRegistry.forEachBus((_, bus) => bus.setBarSpacing(spacing));
976
1257
  }
977
1258
  setBarSpacingPolicy(opts) {
978
1259
  if (opts.autoBarSpacingOnInterval !== void 0) {
@@ -988,7 +1269,7 @@ var PaneOrchestrator = class {
988
1269
  this.applyIntervalBarSpacing();
989
1270
  } else {
990
1271
  this.mainChart.timeScale().fitContent();
991
- this.volumeChart.timeScale().fitContent();
1272
+ if (this.isVolumeVisible()) this.volumeChart.timeScale().fitContent();
992
1273
  this.indicators?.fitContent();
993
1274
  }
994
1275
  if (renderBars.length > 0) {
@@ -1001,8 +1282,12 @@ var PaneOrchestrator = class {
1001
1282
  resetViewState() {
1002
1283
  this.didInitialFit = false;
1003
1284
  this.skipNextInitialFit = false;
1004
- this.bus.visibleFromMs = 0;
1005
- this.bus.visibleToMs = 0;
1285
+ this.pendingViewportFit = false;
1286
+ this.pendingViewportBars = null;
1287
+ this.busRegistry.forEachBus((_, bus) => {
1288
+ bus.visibleFromMs = 0;
1289
+ bus.visibleToMs = 0;
1290
+ });
1006
1291
  }
1007
1292
  /** Skip the next automatic fitContent after setBars (used by reloadHistory). */
1008
1293
  preserveViewportOnNextSetBars() {
@@ -1030,10 +1315,10 @@ var PaneOrchestrator = class {
1030
1315
  if (nearest == null) return;
1031
1316
  const i = this.barTimesOrdered.indexOf(nearest.t);
1032
1317
  if (i < 0) return;
1033
- this.bus.scrollToLogicalPosition(i, (animationMs ?? 0) > 0);
1318
+ this.busRegistry.getBusForPane("main").scrollToLogicalPosition(i, (animationMs ?? 0) > 0);
1034
1319
  return;
1035
1320
  }
1036
- this.bus.scrollToLogicalPosition(idx, (animationMs ?? 0) > 0);
1321
+ this.busRegistry.getBusForPane("main").scrollToLogicalPosition(idx, (animationMs ?? 0) > 0);
1037
1322
  }
1038
1323
  /** Clear series while symbol/interval data reloads (avoids overlapping candles). */
1039
1324
  clearBars() {
@@ -1048,13 +1333,13 @@ var PaneOrchestrator = class {
1048
1333
  }
1049
1334
  fitContent() {
1050
1335
  this.mainChart.timeScale().fitContent();
1051
- this.volumeChart.timeScale().fitContent();
1336
+ if (this.isVolumeVisible()) this.volumeChart.timeScale().fitContent();
1052
1337
  this.indicators?.fitContent();
1053
1338
  this.didInitialFit = true;
1054
1339
  }
1055
1340
  scrollToRealtime() {
1056
1341
  this.mainChart.timeScale().scrollToRealTime();
1057
- this.volumeChart.timeScale().scrollToRealTime();
1342
+ if (this.isVolumeVisible()) this.volumeChart.timeScale().scrollToRealTime();
1058
1343
  this.indicators?.scrollToRealtime();
1059
1344
  }
1060
1345
  setLogScale(enabled) {
@@ -1063,7 +1348,18 @@ var PaneOrchestrator = class {
1063
1348
  resize() {
1064
1349
  this.syncChartSize();
1065
1350
  this.syncOverlaySize();
1351
+ if (this.shouldResizePane("indicator")) this.indicators?.resize();
1352
+ this.flushPendingViewportFit();
1353
+ }
1354
+ /**
1355
+ * Resize every LWC pane once for viewport fit; does **not** change {@link resizeFocusPanes}.
1356
+ * @internal Used by ChartController data refresh paths.
1357
+ */
1358
+ resizeAllPanes() {
1359
+ this.syncChartSize({ allPanes: true });
1360
+ this.syncOverlaySize();
1066
1361
  this.indicators?.resize();
1362
+ this.flushPendingViewportFit();
1067
1363
  }
1068
1364
  getOverlayCanvas() {
1069
1365
  return this.overlayCanvas;
@@ -1088,6 +1384,10 @@ var PaneOrchestrator = class {
1088
1384
  return p ?? null;
1089
1385
  }
1090
1386
  destroy() {
1387
+ if (this.listenPaneResizeEvents) {
1388
+ window.removeEventListener("tradview:pane-resize", this.onPaneResize);
1389
+ }
1390
+ this.detachMainVolResizer();
1091
1391
  this.barAnimator?.cancel();
1092
1392
  if (this.priceLine) {
1093
1393
  this.mainSeries.removePriceLine(this.priceLine);
@@ -1105,8 +1405,10 @@ var PaneOrchestrator = class {
1105
1405
  parent.appendChild(canvas);
1106
1406
  this.overlayCanvas = canvas;
1107
1407
  this.syncOverlaySize();
1108
- this.bus.subscribeTransform(() => {
1109
- this.syncOverlaySize();
1408
+ this.busRegistry.forEachBus((_, bus) => {
1409
+ bus.subscribeTransform(() => {
1410
+ this.syncOverlaySize();
1411
+ });
1110
1412
  });
1111
1413
  }
1112
1414
  /** Let drawing overlay receive clicks; cursor mode keeps pan/zoom on LWC. */
@@ -1123,15 +1425,16 @@ var PaneOrchestrator = class {
1123
1425
  this.overlayCanvas.width = rect.width * devicePixelRatio;
1124
1426
  this.overlayCanvas.height = rect.height * devicePixelRatio;
1125
1427
  }
1126
- syncChartSize() {
1428
+ syncChartSize(opts) {
1429
+ const all = opts?.allPanes === true;
1127
1430
  const mainEl = this.mainChart.chartElement().parentElement;
1128
1431
  const volEl = this.volumeChart.chartElement().parentElement;
1129
- if (mainEl) {
1432
+ if (mainEl && (all || this.shouldResizePane("main"))) {
1130
1433
  const w = mainEl.clientWidth;
1131
1434
  const h = mainEl.clientHeight;
1132
1435
  if (w > 0 && h > 0) this.mainChart.resize(w, h);
1133
1436
  }
1134
- if (volEl) {
1437
+ if (this.isVolumeVisible() && volEl && (all || this.shouldResizePane("volume"))) {
1135
1438
  const w = volEl.clientWidth;
1136
1439
  const h = volEl.clientHeight;
1137
1440
  if (w > 0 && h > 0) this.volumeChart.resize(w, h);
@@ -1148,12 +1451,19 @@ export {
1148
1451
  IndicatorPaneStack,
1149
1452
  PaneOrchestrator,
1150
1453
  TimeScaleBus,
1454
+ TimeScaleBusRegistry,
1455
+ attachPaneResizer,
1151
1456
  bollOverlayLines,
1152
1457
  defaultBarSpacingForInterval,
1153
1458
  detectIndicatorBarMutation,
1154
1459
  emaOverlayLine,
1460
+ independentBusKey,
1461
+ isLayeredPaneMount,
1155
1462
  maOverlayLine,
1463
+ normalizeSyncGroupId,
1156
1464
  resolveBarSpacingForInterval,
1465
+ resolveBusMapKey,
1466
+ shouldResizeChartPane,
1157
1467
  volMaOverlayLine
1158
1468
  };
1159
1469
  //# sourceMappingURL=index.js.map