@coderyo/renderer-lite 1.0.2 → 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
@@ -6,6 +6,7 @@ import {
6
6
  HistogramSeries as HistogramSeries2,
7
7
  LineSeries as LineSeries2
8
8
  } from "lightweight-charts";
9
+ import { intervalMs as intervalMs2 } from "@coderyo/data";
9
10
  import { lodDecimateBars } from "@coderyo/series";
10
11
 
11
12
  // src/chart-grid.ts
@@ -17,6 +18,12 @@ function gridOptions(showGrid, dark) {
17
18
  };
18
19
  }
19
20
 
21
+ // src/pane-orchestrator.ts
22
+ import {
23
+ DEFAULT_INDICATOR_CONFIG as DEFAULT_INDICATOR_CONFIG2,
24
+ hasVisibleIndicatorPanes as hasVisibleIndicatorPanes2
25
+ } from "@coderyo/indicators";
26
+
20
27
  // src/indicator-panes.ts
21
28
  import {
22
29
  ColorType,
@@ -26,6 +33,7 @@ import {
26
33
  } from "lightweight-charts";
27
34
  import {
28
35
  DEFAULT_INDICATOR_CONFIG,
36
+ hasVisibleIndicatorPanes,
29
37
  boll,
30
38
  kdj,
31
39
  macd,
@@ -33,6 +41,68 @@ import {
33
41
  sma,
34
42
  ema
35
43
  } from "@coderyo/indicators";
44
+
45
+ // src/pane-resize.ts
46
+ function attachPaneResizer(topPane, bottomPane, opts = {}) {
47
+ const minTop = opts.minTopPx ?? 120;
48
+ const minBottom = opts.minBottomPx ?? 60;
49
+ const parent = topPane.parentElement;
50
+ if (!parent) return () => {
51
+ };
52
+ let dragging = false;
53
+ const handle = document.createElement("div");
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
+ };
62
+ bottomPane.insertAdjacentElement("beforebegin", handle);
63
+ const saved = opts.storageKey ? localStorage.getItem(opts.storageKey) : null;
64
+ if (saved) {
65
+ const ratio = Number(saved);
66
+ if (Number.isFinite(ratio) && ratio >= 0.15 && ratio <= 0.85) {
67
+ topPane.style.flex = `${ratio * 10}`;
68
+ bottomPane.style.flex = `${(1 - ratio) * 10}`;
69
+ }
70
+ }
71
+ const onMove = (clientY) => {
72
+ const rect = parent.getBoundingClientRect();
73
+ const y = clientY - rect.top;
74
+ const ratio = Math.min(0.85, Math.max(0.15, y / rect.height));
75
+ const topPx = ratio * rect.height;
76
+ const bottomPx = rect.height - topPx - handle.offsetHeight;
77
+ if (topPx < minTop || bottomPx < minBottom) return;
78
+ topPane.style.flex = `${ratio * 10}`;
79
+ bottomPane.style.flex = `${(1 - ratio) * 10}`;
80
+ if (opts.storageKey) localStorage.setItem(opts.storageKey, String(ratio));
81
+ };
82
+ const stop = () => {
83
+ const wasDragging = dragging;
84
+ dragging = false;
85
+ document.body.style.cursor = "";
86
+ handle.style.background = "#30363d";
87
+ if (wasDragging) window.dispatchEvent(new CustomEvent("tradview:pane-resize"));
88
+ };
89
+ handle.addEventListener("pointerdown", (e) => {
90
+ dragging = true;
91
+ handle.setPointerCapture(e.pointerId);
92
+ document.body.style.cursor = "row-resize";
93
+ });
94
+ handle.addEventListener("pointermove", (e) => {
95
+ if (dragging) {
96
+ onMove(e.clientY);
97
+ window.dispatchEvent(new CustomEvent("tradview:pane-resize"));
98
+ }
99
+ });
100
+ handle.addEventListener("pointerup", stop);
101
+ handle.addEventListener("pointercancel", stop);
102
+ return () => handle.remove();
103
+ }
104
+
105
+ // src/indicator-panes.ts
36
106
  function barsForSource(bars, source) {
37
107
  if (source === "close") return bars;
38
108
  return bars.map((b) => ({ ...b, c: (b.h + b.l + b.c) / 3 }));
@@ -40,6 +110,21 @@ function barsForSource(bars, source) {
40
110
  function toUtcSeconds(tMs) {
41
111
  return Math.floor(tMs / 1e3);
42
112
  }
113
+ function detectIndicatorBarMutation(prevTimes, bars) {
114
+ if (prevTimes.length === 0 || bars.length < prevTimes.length) return "full";
115
+ const prefixLen = Math.min(prevTimes.length, bars.length);
116
+ for (let i = 0; i < prefixLen - 1; i++) {
117
+ if (bars[i].t !== prevTimes[i]) return "full";
118
+ }
119
+ if (bars.length === prevTimes.length) {
120
+ return bars.length > 0 && bars[bars.length - 1].t === prevTimes[prevTimes.length - 1] ? "tail-update" : "full";
121
+ }
122
+ if (bars.length - prevTimes.length > 8) return "full";
123
+ for (let i = 0; i < prevTimes.length; i++) {
124
+ if (bars[i].t !== prevTimes[i]) return "full";
125
+ }
126
+ return "tail-append";
127
+ }
43
128
  function lineData(bars, values) {
44
129
  const out = [];
45
130
  for (let i = 0; i < bars.length; i++) {
@@ -69,19 +154,18 @@ var IndicatorPaneStack = class {
69
154
  this.dark = o.theme !== "light";
70
155
  this.showGrid = o.showGrid ?? false;
71
156
  this.config = o.config ?? DEFAULT_INDICATOR_CONFIG;
157
+ this.onConfigChange = o.onConfigChange;
72
158
  this.root.style.display = "flex";
73
159
  this.root.style.flexDirection = "column";
74
160
  this.root.style.flex = "2";
75
161
  this.root.style.minHeight = "0";
76
162
  this.root.style.overflow = "hidden";
77
- const macdPane = this.createPaneWrap("MACD");
78
- const rsiPane = this.createPaneWrap("RSI");
79
- const kdjPane = this.createPaneWrap("KDJ");
163
+ const macdPane = this.createPaneWrap("MACD", "macd");
164
+ const rsiPane = this.createPaneWrap("RSI", "rsi");
165
+ const kdjPane = this.createPaneWrap("KDJ", "kdj");
80
166
  this.macdWrap = macdPane.wrap;
81
167
  this.rsiWrap = rsiPane.wrap;
82
168
  this.kdjWrap = kdjPane.wrap;
83
- this.root.append(macdPane.wrap, rsiPane.wrap, kdjPane.wrap);
84
- this.applyPaneVisibility();
85
169
  const layout = this.layoutForTheme(this.dark);
86
170
  const grid = gridOptions(this.showGrid, this.dark);
87
171
  this.macdChart = createChart(macdPane.el, { layout, grid, autoSize: true });
@@ -97,6 +181,7 @@ var IndicatorPaneStack = class {
97
181
  this.kdjK = this.kdjChart.addSeries(LineSeries, { color: "#42a5f5", lineWidth: 1 });
98
182
  this.kdjD = this.kdjChart.addSeries(LineSeries, { color: "#ffa726", lineWidth: 1 });
99
183
  this.kdjJ = this.kdjChart.addSeries(LineSeries, { color: "#ef5350", lineWidth: 1 });
184
+ this.applyPaneVisibility();
100
185
  }
101
186
  root;
102
187
  macdChart;
@@ -115,14 +200,50 @@ var IndicatorPaneStack = class {
115
200
  macdWrap;
116
201
  rsiWrap;
117
202
  kdjWrap;
203
+ detachResizers = [];
204
+ lastBarTimes = [];
205
+ onConfigChange;
118
206
  setConfig(config) {
119
207
  this.config = config;
208
+ this.lastBarTimes = [];
209
+ this.applyPaneVisibility();
210
+ if (!this.config.showMacd && !this.config.showRsi && !this.config.showKdj) {
211
+ this.clearBars();
212
+ }
213
+ }
214
+ closePane(id) {
215
+ const patch = id === "macd" ? { showMacd: false } : id === "rsi" ? { showRsi: false } : { showKdj: false };
216
+ this.config = { ...this.config, ...patch };
120
217
  this.applyPaneVisibility();
218
+ this.onConfigChange?.(this.config);
121
219
  }
122
220
  applyPaneVisibility() {
123
- this.macdWrap.style.display = this.config.showMacd ? "" : "none";
124
- this.rsiWrap.style.display = this.config.showRsi ? "" : "none";
125
- this.kdjWrap.style.display = this.config.showKdj ? "" : "none";
221
+ const anyVisible = this.config.showMacd || this.config.showRsi || this.config.showKdj;
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();
126
247
  }
127
248
  clearBars() {
128
249
  this.macdLine.setData([]);
@@ -132,22 +253,87 @@ var IndicatorPaneStack = class {
132
253
  this.kdjK.setData([]);
133
254
  this.kdjD.setData([]);
134
255
  this.kdjJ.setData([]);
256
+ this.lastBarTimes = [];
257
+ }
258
+ warmupLookback() {
259
+ const c = this.config;
260
+ return Math.max(
261
+ c.macdSlow + c.macdSignal,
262
+ c.rsiPeriod,
263
+ c.kdjPeriod + c.kdjKSmooth + c.kdjDSmooth
264
+ ) + 5;
265
+ }
266
+ detectBarMutation(bars) {
267
+ return detectIndicatorBarMutation(this.lastBarTimes, bars);
268
+ }
269
+ pushSeriesUpdates(series, bars, values, fromIndex) {
270
+ for (let i = fromIndex; i < bars.length; i++) {
271
+ const v = values[i];
272
+ if (v == null) continue;
273
+ series.update({ time: toUtcSeconds(bars[i].t), value: v });
274
+ }
275
+ }
276
+ pushHistUpdates(series, bars, values, fromIndex) {
277
+ for (let i = fromIndex; i < bars.length; i++) {
278
+ const v = values[i];
279
+ if (v == null) continue;
280
+ series.update({
281
+ time: toUtcSeconds(bars[i].t),
282
+ value: v,
283
+ color: v >= 0 ? "#26a69a88" : "#ef535088"
284
+ });
285
+ }
135
286
  }
136
287
  setBars(bars) {
137
- if (bars.length === 0) return;
288
+ if (bars.length === 0) {
289
+ this.clearBars();
290
+ return;
291
+ }
292
+ const needMacd = this.config.showMacd;
293
+ const needRsi = this.config.showRsi;
294
+ const needKdj = this.config.showKdj;
295
+ if (!hasVisibleIndicatorPanes(this.config)) {
296
+ return;
297
+ }
298
+ const mutation = this.detectBarMutation(bars);
299
+ this.lastBarTimes = bars.map((b) => b.t);
138
300
  const src = barsForSource(bars, this.config.source);
139
- const m = macd(src, this.config.macdFast, this.config.macdSlow, this.config.macdSignal);
140
- this.macdLine.setData(lineData(bars, m.macd));
141
- this.macdSignal.setData(lineData(bars, m.signal));
142
- this.macdHist.setData(histData(bars, m.histogram));
143
- this.rsiLine.setData(lineData(bars, rsi(src, this.config.rsiPeriod)));
144
- const k = kdj(src, this.config.kdjPeriod, this.config.kdjKSmooth, this.config.kdjDSmooth);
145
- this.kdjK.setData(lineData(bars, k.k));
146
- this.kdjD.setData(lineData(bars, k.d));
147
- this.kdjJ.setData(lineData(bars, k.j));
148
- this.macdChart.timeScale().fitContent();
149
- this.rsiChart.timeScale().fitContent();
150
- this.kdjChart.timeScale().fitContent();
301
+ const m = needMacd ? macd(src, this.config.macdFast, this.config.macdSlow, this.config.macdSignal) : null;
302
+ const r = needRsi ? rsi(src, this.config.rsiPeriod) : null;
303
+ const k = needKdj ? kdj(src, this.config.kdjPeriod, this.config.kdjKSmooth, this.config.kdjDSmooth) : null;
304
+ if (mutation === "full") {
305
+ if (needMacd && m) {
306
+ this.macdLine.setData(lineData(bars, m.macd));
307
+ this.macdSignal.setData(lineData(bars, m.signal));
308
+ this.macdHist.setData(histData(bars, m.histogram));
309
+ this.macdChart.timeScale().fitContent();
310
+ }
311
+ if (needRsi && r) {
312
+ this.rsiLine.setData(lineData(bars, r));
313
+ this.rsiChart.timeScale().fitContent();
314
+ }
315
+ if (needKdj && k) {
316
+ this.kdjK.setData(lineData(bars, k.k));
317
+ this.kdjD.setData(lineData(bars, k.d));
318
+ this.kdjJ.setData(lineData(bars, k.j));
319
+ this.kdjChart.timeScale().fitContent();
320
+ }
321
+ } else {
322
+ const from = mutation === "tail-update" ? Math.max(0, bars.length - 1) : Math.max(0, bars.length - this.warmupLookback());
323
+ if (needMacd && m) {
324
+ this.pushSeriesUpdates(this.macdLine, bars, m.macd, from);
325
+ this.pushSeriesUpdates(this.macdSignal, bars, m.signal, from);
326
+ this.pushHistUpdates(this.macdHist, bars, m.histogram, from);
327
+ }
328
+ if (needRsi && r) {
329
+ this.pushSeriesUpdates(this.rsiLine, bars, r, from);
330
+ }
331
+ if (needKdj && k) {
332
+ this.pushSeriesUpdates(this.kdjK, bars, k.k, from);
333
+ this.pushSeriesUpdates(this.kdjD, bars, k.d, from);
334
+ this.pushSeriesUpdates(this.kdjJ, bars, k.j, from);
335
+ }
336
+ }
151
337
  this.resize();
152
338
  }
153
339
  setTheme(theme) {
@@ -176,11 +362,14 @@ var IndicatorPaneStack = class {
176
362
  this.kdjChart.timeScale().scrollToRealTime();
177
363
  }
178
364
  resize() {
179
- for (const { chart, el } of [
180
- { chart: this.macdChart, el: this.macdChart.chartElement().parentElement },
181
- { chart: this.rsiChart, el: this.rsiChart.chartElement().parentElement },
182
- { chart: this.kdjChart, el: this.kdjChart.chartElement().parentElement }
183
- ]) {
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;
184
373
  if (!el) continue;
185
374
  const w = el.clientWidth;
186
375
  const h = el.clientHeight;
@@ -188,20 +377,39 @@ var IndicatorPaneStack = class {
188
377
  }
189
378
  }
190
379
  destroy() {
380
+ for (const detach of this.detachResizers) detach();
381
+ this.detachResizers.length = 0;
191
382
  this.macdChart.remove();
192
383
  this.rsiChart.remove();
193
384
  this.kdjChart.remove();
194
385
  this.root.replaceChildren();
195
386
  }
196
- createPaneWrap(label) {
387
+ createPaneWrap(label, paneId) {
197
388
  const wrap = document.createElement("div");
389
+ wrap.className = `tv-indicator-pane tv-indicator-pane--${paneId}`;
390
+ wrap.dataset.paneId = paneId;
198
391
  wrap.style.cssText = "flex:1;min-height:72px;width:100%;position:relative;border-top:1px solid #30363d;";
199
392
  const tag = document.createElement("span");
200
393
  tag.textContent = label;
201
394
  tag.style.cssText = "position:absolute;left:6px;top:4px;z-index:2;font-size:10px;color:#8b949e;pointer-events:none;";
395
+ const closeBtn = document.createElement("button");
396
+ closeBtn.type = "button";
397
+ closeBtn.textContent = "\xD7";
398
+ closeBtn.title = `\u95DC\u9589 ${label}`;
399
+ closeBtn.setAttribute("aria-label", `Close ${label}`);
400
+ closeBtn.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;";
401
+ closeBtn.onmouseenter = () => {
402
+ closeBtn.style.color = "#e6edf3";
403
+ closeBtn.style.borderColor = "#484f58";
404
+ };
405
+ closeBtn.onmouseleave = () => {
406
+ closeBtn.style.color = "#8b949e";
407
+ closeBtn.style.borderColor = "#30363d";
408
+ };
409
+ closeBtn.onclick = () => this.closePane(paneId);
202
410
  const el = document.createElement("div");
203
411
  el.style.cssText = "width:100%;height:100%;";
204
- wrap.append(tag, el);
412
+ wrap.append(tag, closeBtn, el);
205
413
  return { wrap, el };
206
414
  }
207
415
  layoutForTheme(dark) {
@@ -210,6 +418,10 @@ var IndicatorPaneStack = class {
210
418
  textColor: dark ? "#e6edf3" : "#24292f"
211
419
  };
212
420
  }
421
+ /** LWC instances for sync-group reassignment. */
422
+ getCharts() {
423
+ return [this.macdChart, this.rsiChart, this.kdjChart];
424
+ }
213
425
  };
214
426
  function maOverlayLine(bars, period = 20, source = "close") {
215
427
  const src = barsForSource(bars, source);
@@ -233,53 +445,6 @@ function bollOverlayLines(bars, period, mult, source = "close") {
233
445
  };
234
446
  }
235
447
 
236
- // src/pane-resize.ts
237
- function attachPaneResizer(topPane, bottomPane, opts = {}) {
238
- const minTop = opts.minTopPx ?? 120;
239
- const minBottom = opts.minBottomPx ?? 60;
240
- const parent = topPane.parentElement;
241
- if (!parent) return () => {
242
- };
243
- const handle = document.createElement("div");
244
- handle.style.cssText = "height:4px;cursor:row-resize;background:#30363d;flex-shrink:0;touch-action:none;";
245
- bottomPane.insertAdjacentElement("beforebegin", handle);
246
- const saved = opts.storageKey ? localStorage.getItem(opts.storageKey) : null;
247
- if (saved) {
248
- const ratio = Number(saved);
249
- if (Number.isFinite(ratio) && ratio > 0 && ratio < 1) {
250
- topPane.style.flex = `${ratio * 10}`;
251
- bottomPane.style.flex = `${(1 - ratio) * 10}`;
252
- }
253
- }
254
- let dragging = false;
255
- const onMove = (clientY) => {
256
- const rect = parent.getBoundingClientRect();
257
- const y = clientY - rect.top;
258
- const ratio = Math.min(0.85, Math.max(0.15, y / rect.height));
259
- const topPx = ratio * rect.height;
260
- const bottomPx = rect.height - topPx - handle.offsetHeight;
261
- if (topPx < minTop || bottomPx < minBottom) return;
262
- topPane.style.flex = `${ratio * 10}`;
263
- bottomPane.style.flex = `${(1 - ratio) * 10}`;
264
- if (opts.storageKey) localStorage.setItem(opts.storageKey, String(ratio));
265
- };
266
- const stop = () => {
267
- dragging = false;
268
- document.body.style.cursor = "";
269
- };
270
- handle.addEventListener("pointerdown", (e) => {
271
- dragging = true;
272
- handle.setPointerCapture(e.pointerId);
273
- document.body.style.cursor = "row-resize";
274
- });
275
- handle.addEventListener("pointermove", (e) => {
276
- if (dragging) onMove(e.clientY);
277
- });
278
- handle.addEventListener("pointerup", stop);
279
- handle.addEventListener("pointercancel", stop);
280
- return () => handle.remove();
281
- }
282
-
283
448
  // src/time-scale-bus.ts
284
449
  var TimeScaleBus = class {
285
450
  charts = [];
@@ -291,6 +456,7 @@ var TimeScaleBus = class {
291
456
  if (this.charts.includes(chart)) return;
292
457
  this.charts.push(chart);
293
458
  chart.timeScale().subscribeVisibleLogicalRangeChange((range) => {
459
+ if (!this.charts.includes(chart)) return;
294
460
  if (this.syncing || !range) return;
295
461
  const tr = chart.timeScale().getVisibleRange();
296
462
  if (tr && typeof tr.from === "number" && typeof tr.to === "number") {
@@ -300,6 +466,10 @@ var TimeScaleBus = class {
300
466
  this.syncFrom(chart, range);
301
467
  });
302
468
  }
469
+ unregister(chart) {
470
+ const i = this.charts.indexOf(chart);
471
+ if (i >= 0) this.charts.splice(i, 1);
472
+ }
303
473
  subscribeTransform(listener) {
304
474
  this.listeners.add(listener);
305
475
  return () => this.listeners.delete(listener);
@@ -368,6 +538,101 @@ var TimeScaleBus = class {
368
538
  }
369
539
  };
370
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
+
613
+ // src/viewport-fit.ts
614
+ import { intervalMs } from "@coderyo/data";
615
+ function defaultBarSpacingForInterval(interval) {
616
+ const ms = intervalMs(interval);
617
+ if (ms <= 1e3) return 3;
618
+ if (ms <= 5e3) return 4;
619
+ if (ms <= 15e3) return 5;
620
+ if (ms <= 3e4) return 6;
621
+ if (ms <= 6e4) return 7;
622
+ if (ms <= 3e5) return 8;
623
+ if (ms <= 9e5) return 9;
624
+ if (ms <= 36e5) return 10;
625
+ if (ms <= 144e5) return 11;
626
+ return 12;
627
+ }
628
+ function resolveBarSpacingForInterval(interval, overrides) {
629
+ const custom = overrides?.[interval];
630
+ if (custom != null && Number.isFinite(custom) && custom > 0) {
631
+ return Math.min(24, Math.max(2, custom));
632
+ }
633
+ return defaultBarSpacingForInterval(interval);
634
+ }
635
+
371
636
  // src/bar-smooth-animator.ts
372
637
  function lerp(a, b, t) {
373
638
  return a + (b - a) * t;
@@ -425,6 +690,13 @@ var BarSmoothAnimator = class {
425
690
  };
426
691
 
427
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
+ }
428
700
  function toUtcSeconds2(tMs) {
429
701
  return Math.floor(tMs / 1e3);
430
702
  }
@@ -435,7 +707,18 @@ function barToVolume(b) {
435
707
  return { time: toUtcSeconds2(b.t), value: b.v ?? 0 };
436
708
  }
437
709
  var PaneOrchestrator = class {
438
- 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;
439
722
  mainChart;
440
723
  volumeChart;
441
724
  mainSeries;
@@ -458,24 +741,70 @@ var PaneOrchestrator = class {
458
741
  barTimesOrdered = [];
459
742
  didInitialFit = false;
460
743
  skipNextInitialFit = false;
744
+ /** setBars ran before the pane had layout size; refit on first real resize. */
745
+ pendingViewportFit = false;
746
+ pendingViewportBars = null;
461
747
  indicatorConfig = null;
748
+ onIndicatorConfigChange;
749
+ currentInterval = "1h";
750
+ autoBarSpacingOnInterval = true;
751
+ barSpacingByInterval;
462
752
  priceLine = null;
463
753
  barAnimator = null;
464
754
  smoothPriceDurationMs = 150;
465
755
  constructor(opts) {
466
756
  this.maxRenderPoints = opts.maxRenderPoints ?? 4e3;
757
+ this.listenPaneResizeEvents = opts.listenPaneResizeEvents !== false;
467
758
  this.dark = opts.theme !== "light";
468
759
  this.showGrid = opts.showGrid ?? false;
469
760
  const layout = this.layoutForTheme(this.dark);
470
761
  const grid = gridOptions(this.showGrid, this.dark);
471
- const mainEl = document.createElement("div");
472
- mainEl.style.cssText = "flex:7;min-height:120px;width:100%;position:relative;";
762
+ this.layeredPanes = !!opts.volumeMount;
473
763
  const volEl = document.createElement("div");
474
- volEl.style.cssText = "flex:2;min-height:64px;width:100%;position:relative;";
475
- opts.container.style.cssText = "display:flex;flex-direction:column;height:100%;width:100%;min-height:240px;overflow:hidden;";
476
- opts.container.append(mainEl, volEl);
477
- attachPaneResizer(mainEl, volEl, { storageKey: "tradview:pane:main-volume" });
478
- 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 });
479
808
  this.volumeChart = createChart2(volEl, {
480
809
  layout,
481
810
  grid,
@@ -531,15 +860,25 @@ var PaneOrchestrator = class {
531
860
  lineWidth: 1,
532
861
  title: "VolMA5"
533
862
  });
534
- this.bus.register(this.mainChart);
535
- this.bus.register(this.volumeChart);
863
+ this.busRegistry.getBusForPane("main").register(this.mainChart);
864
+ this.busRegistry.getBusForPane("volume").register(this.volumeChart);
536
865
  this.indicatorRoot = opts.indicatorRoot;
537
866
  this.indicatorConfig = opts.indicatorConfig ?? null;
867
+ this.onIndicatorConfigChange = opts.onIndicatorConfigChange;
868
+ this.autoBarSpacingOnInterval = opts.autoBarSpacingOnInterval ?? true;
869
+ this.barSpacingByInterval = opts.barSpacingByInterval;
538
870
  this.pinePlots = opts.pinePlots ?? null;
539
871
  this.indicators = this.createIndicatorStack();
540
- this.initOverlay(mainEl);
872
+ this.initOverlay(this.mainEl);
541
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
+ }
542
878
  }
879
+ onPaneResize = () => {
880
+ this.resize();
881
+ };
543
882
  setSmoothPriceUpdate(enabled, durationMs = 150) {
544
883
  this.smoothPriceDurationMs = durationMs;
545
884
  if (enabled) {
@@ -573,12 +912,16 @@ var PaneOrchestrator = class {
573
912
  applyLastBarToSeries(bar) {
574
913
  this.barByTime.set(bar.t, bar);
575
914
  this.mainSeries.update(barToCandle(bar));
576
- this.volumeSeries.update(barToVolume(bar));
915
+ if (this.isVolumeVisible()) {
916
+ this.volumeSeries.update(barToVolume(bar));
917
+ }
577
918
  this.ensurePriceLine(bar.c);
578
919
  if (this.indicatorConfig) {
579
920
  const bars = [...this.barByTime.values()].sort((a, b) => a.t - b.t);
580
921
  this.applyMainOverlays(bars);
581
- this.indicators?.setBars(bars);
922
+ if (hasVisibleIndicatorPanes2(this.indicatorConfig)) {
923
+ this.indicators?.setBars(bars);
924
+ }
582
925
  }
583
926
  }
584
927
  ensurePriceLine(price) {
@@ -595,6 +938,9 @@ var PaneOrchestrator = class {
595
938
  this.priceLine.applyOptions({ price });
596
939
  }
597
940
  }
941
+ setIntervalContext(interval) {
942
+ this.currentInterval = interval;
943
+ }
598
944
  setTheme(theme) {
599
945
  this.dark = theme === "dark";
600
946
  const layout = this.layoutForTheme(this.dark);
@@ -606,17 +952,19 @@ var PaneOrchestrator = class {
606
952
  setIndicatorConfig(config) {
607
953
  this.indicatorConfig = config;
608
954
  if (!config) {
609
- this.indicators = null;
955
+ this.teardownIndicatorStack();
610
956
  this.maSeries.setData([]);
611
957
  this.emaSeries.setData([]);
612
958
  this.bollUpper.setData([]);
613
959
  this.bollMiddle.setData([]);
614
960
  this.bollLower.setData([]);
615
961
  this.volMaSeries.setData([]);
962
+ this.volumeSeries.setData([]);
616
963
  this.emaSeries.applyOptions({ visible: false });
617
964
  this.bollUpper.applyOptions({ visible: false });
618
965
  this.bollMiddle.applyOptions({ visible: false });
619
966
  this.bollLower.applyOptions({ visible: false });
967
+ this.applyVolumeVisibility();
620
968
  return;
621
969
  }
622
970
  if (!this.indicators) this.indicators = this.createIndicatorStack();
@@ -624,20 +972,112 @@ var PaneOrchestrator = class {
624
972
  this.indicators?.setConfig(config);
625
973
  if (bars.length > 0) {
626
974
  this.applyMainOverlays(bars);
627
- this.indicators?.setBars(bars);
975
+ if (hasVisibleIndicatorPanes2(config)) {
976
+ this.indicators?.setBars(bars);
977
+ }
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([]);
628
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
+ });
629
1054
  }
630
1055
  setPinePlots(plots) {
631
1056
  this.pinePlots = plots;
632
1057
  const bars = [...this.barByTime.values()].sort((a, b) => a.t - b.t);
633
1058
  this.syncPinePlotSeries(bars);
634
1059
  }
1060
+ teardownIndicatorStack() {
1061
+ this.indicators?.destroy();
1062
+ this.indicators = null;
1063
+ }
635
1064
  applyMainOverlays(bars) {
636
1065
  const cfg = this.indicatorConfig;
637
1066
  if (!cfg) return;
638
- this.maSeries.applyOptions({ title: `MA${cfg.maPeriod}` });
639
- this.maSeries.setData(maOverlayLine(bars, cfg.maPeriod, cfg.source));
640
- this.volMaSeries.setData(volMaOverlayLine(bars, cfg.volMaPeriod));
1067
+ if (cfg.showMa) {
1068
+ this.maSeries.applyOptions({ visible: true, title: `MA${cfg.maPeriod}` });
1069
+ this.maSeries.setData(maOverlayLine(bars, cfg.maPeriod, cfg.source));
1070
+ } else {
1071
+ this.maSeries.applyOptions({ visible: false });
1072
+ this.maSeries.setData([]);
1073
+ }
1074
+ if (cfg.showVolMa) {
1075
+ this.volMaSeries.applyOptions({ visible: true });
1076
+ this.volMaSeries.setData(volMaOverlayLine(bars, cfg.volMaPeriod));
1077
+ } else {
1078
+ this.volMaSeries.applyOptions({ visible: false });
1079
+ this.volMaSeries.setData([]);
1080
+ }
641
1081
  if (cfg.showEma) {
642
1082
  this.emaSeries.applyOptions({ visible: true, title: `EMA${cfg.emaPeriod}` });
643
1083
  this.emaSeries.setData(emaOverlayLine(bars, cfg.emaPeriod, cfg.source));
@@ -712,10 +1152,16 @@ var PaneOrchestrator = class {
712
1152
  vols.push(barToVolume(b));
713
1153
  }
714
1154
  this.mainSeries.setData(candles);
715
- this.volumeSeries.setData(vols);
1155
+ if (this.isVolumeVisible()) {
1156
+ this.volumeSeries.setData(vols);
1157
+ } else {
1158
+ this.volumeSeries.setData([]);
1159
+ }
716
1160
  if (this.indicatorConfig) {
717
1161
  this.applyMainOverlays(renderBars);
718
- this.indicators?.setBars(renderBars);
1162
+ if (hasVisibleIndicatorPanes2(this.indicatorConfig)) {
1163
+ this.indicators?.setBars(renderBars);
1164
+ }
719
1165
  } else {
720
1166
  this.maSeries.setData([]);
721
1167
  this.emaSeries.setData([]);
@@ -727,15 +1173,35 @@ var PaneOrchestrator = class {
727
1173
  }
728
1174
  if (renderBars.length > 0) {
729
1175
  this.syncChartSize();
730
- if (!this.didInitialFit && !this.skipNextInitialFit) {
731
- this.mainChart.timeScale().fitContent();
732
- this.volumeChart.timeScale().fitContent();
733
- this.indicators?.fitContent();
734
- this.didInitialFit = true;
735
- }
1176
+ this.tryInitialViewportFit(renderBars);
736
1177
  this.skipNextInitialFit = false;
737
1178
  }
738
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
+ }
739
1205
  subscribeCrosshair(listener) {
740
1206
  const handler = (param) => {
741
1207
  if (param.time == null || !param.point) {
@@ -772,17 +1238,56 @@ var PaneOrchestrator = class {
772
1238
  }
773
1239
  createIndicatorStack() {
774
1240
  if (!this.indicatorRoot || !this.indicatorConfig) return null;
775
- return new IndicatorPaneStack(this.indicatorRoot, this.bus, {
1241
+ return new IndicatorPaneStack(this.indicatorRoot, this.busRegistry.getBusForPane("indicator"), {
776
1242
  theme: this.dark ? "dark" : "light",
777
1243
  showGrid: this.showGrid,
778
- config: this.indicatorConfig
1244
+ config: this.indicatorConfig,
1245
+ onConfigChange: (config) => {
1246
+ this.indicatorConfig = config;
1247
+ this.onIndicatorConfigChange?.(config);
1248
+ }
779
1249
  });
780
1250
  }
1251
+ /** After symbol/interval reload: bar spacing for interval + show integrator-loaded span. */
1252
+ applyIntervalBarSpacing(interval) {
1253
+ if (!this.autoBarSpacingOnInterval) return;
1254
+ const iv = interval ?? this.currentInterval;
1255
+ const spacing = resolveBarSpacingForInterval(iv, this.barSpacingByInterval);
1256
+ this.busRegistry.forEachBus((_, bus) => bus.setBarSpacing(spacing));
1257
+ }
1258
+ setBarSpacingPolicy(opts) {
1259
+ if (opts.autoBarSpacingOnInterval !== void 0) {
1260
+ this.autoBarSpacingOnInterval = opts.autoBarSpacingOnInterval;
1261
+ }
1262
+ if (opts.barSpacingByInterval !== void 0) {
1263
+ this.barSpacingByInterval = opts.barSpacingByInterval;
1264
+ }
1265
+ }
1266
+ /** Sync time scale to loaded bars (integrator history); adjust bar spacing only, not bar count. */
1267
+ applyViewAfterDataReload(renderBars) {
1268
+ if (this.autoBarSpacingOnInterval) {
1269
+ this.applyIntervalBarSpacing();
1270
+ } else {
1271
+ this.mainChart.timeScale().fitContent();
1272
+ if (this.isVolumeVisible()) this.volumeChart.timeScale().fitContent();
1273
+ this.indicators?.fitContent();
1274
+ }
1275
+ if (renderBars.length > 0) {
1276
+ const fromMs = renderBars[0].t;
1277
+ const toMs = renderBars[renderBars.length - 1].t + intervalMs2(this.currentInterval);
1278
+ this.bus.setVisibleTimeRange({ fromMs, toMs });
1279
+ }
1280
+ this.scrollToRealtime();
1281
+ }
781
1282
  resetViewState() {
782
1283
  this.didInitialFit = false;
783
1284
  this.skipNextInitialFit = false;
784
- this.bus.visibleFromMs = 0;
785
- 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
+ });
786
1291
  }
787
1292
  /** Skip the next automatic fitContent after setBars (used by reloadHistory). */
788
1293
  preserveViewportOnNextSetBars() {
@@ -810,10 +1315,10 @@ var PaneOrchestrator = class {
810
1315
  if (nearest == null) return;
811
1316
  const i = this.barTimesOrdered.indexOf(nearest.t);
812
1317
  if (i < 0) return;
813
- this.bus.scrollToLogicalPosition(i, (animationMs ?? 0) > 0);
1318
+ this.busRegistry.getBusForPane("main").scrollToLogicalPosition(i, (animationMs ?? 0) > 0);
814
1319
  return;
815
1320
  }
816
- this.bus.scrollToLogicalPosition(idx, (animationMs ?? 0) > 0);
1321
+ this.busRegistry.getBusForPane("main").scrollToLogicalPosition(idx, (animationMs ?? 0) > 0);
817
1322
  }
818
1323
  /** Clear series while symbol/interval data reloads (avoids overlapping candles). */
819
1324
  clearBars() {
@@ -828,13 +1333,13 @@ var PaneOrchestrator = class {
828
1333
  }
829
1334
  fitContent() {
830
1335
  this.mainChart.timeScale().fitContent();
831
- this.volumeChart.timeScale().fitContent();
1336
+ if (this.isVolumeVisible()) this.volumeChart.timeScale().fitContent();
832
1337
  this.indicators?.fitContent();
833
1338
  this.didInitialFit = true;
834
1339
  }
835
1340
  scrollToRealtime() {
836
1341
  this.mainChart.timeScale().scrollToRealTime();
837
- this.volumeChart.timeScale().scrollToRealTime();
1342
+ if (this.isVolumeVisible()) this.volumeChart.timeScale().scrollToRealTime();
838
1343
  this.indicators?.scrollToRealtime();
839
1344
  }
840
1345
  setLogScale(enabled) {
@@ -843,7 +1348,18 @@ var PaneOrchestrator = class {
843
1348
  resize() {
844
1349
  this.syncChartSize();
845
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();
846
1361
  this.indicators?.resize();
1362
+ this.flushPendingViewportFit();
847
1363
  }
848
1364
  getOverlayCanvas() {
849
1365
  return this.overlayCanvas;
@@ -868,6 +1384,10 @@ var PaneOrchestrator = class {
868
1384
  return p ?? null;
869
1385
  }
870
1386
  destroy() {
1387
+ if (this.listenPaneResizeEvents) {
1388
+ window.removeEventListener("tradview:pane-resize", this.onPaneResize);
1389
+ }
1390
+ this.detachMainVolResizer();
871
1391
  this.barAnimator?.cancel();
872
1392
  if (this.priceLine) {
873
1393
  this.mainSeries.removePriceLine(this.priceLine);
@@ -885,8 +1405,10 @@ var PaneOrchestrator = class {
885
1405
  parent.appendChild(canvas);
886
1406
  this.overlayCanvas = canvas;
887
1407
  this.syncOverlaySize();
888
- this.bus.subscribeTransform(() => {
889
- this.syncOverlaySize();
1408
+ this.busRegistry.forEachBus((_, bus) => {
1409
+ bus.subscribeTransform(() => {
1410
+ this.syncOverlaySize();
1411
+ });
890
1412
  });
891
1413
  }
892
1414
  /** Let drawing overlay receive clicks; cursor mode keeps pan/zoom on LWC. */
@@ -903,15 +1425,16 @@ var PaneOrchestrator = class {
903
1425
  this.overlayCanvas.width = rect.width * devicePixelRatio;
904
1426
  this.overlayCanvas.height = rect.height * devicePixelRatio;
905
1427
  }
906
- syncChartSize() {
1428
+ syncChartSize(opts) {
1429
+ const all = opts?.allPanes === true;
907
1430
  const mainEl = this.mainChart.chartElement().parentElement;
908
1431
  const volEl = this.volumeChart.chartElement().parentElement;
909
- if (mainEl) {
1432
+ if (mainEl && (all || this.shouldResizePane("main"))) {
910
1433
  const w = mainEl.clientWidth;
911
1434
  const h = mainEl.clientHeight;
912
1435
  if (w > 0 && h > 0) this.mainChart.resize(w, h);
913
1436
  }
914
- if (volEl) {
1437
+ if (this.isVolumeVisible() && volEl && (all || this.shouldResizePane("volume"))) {
915
1438
  const w = volEl.clientWidth;
916
1439
  const h = volEl.clientHeight;
917
1440
  if (w > 0 && h > 0) this.volumeChart.resize(w, h);
@@ -928,9 +1451,19 @@ export {
928
1451
  IndicatorPaneStack,
929
1452
  PaneOrchestrator,
930
1453
  TimeScaleBus,
1454
+ TimeScaleBusRegistry,
1455
+ attachPaneResizer,
931
1456
  bollOverlayLines,
1457
+ defaultBarSpacingForInterval,
1458
+ detectIndicatorBarMutation,
932
1459
  emaOverlayLine,
1460
+ independentBusKey,
1461
+ isLayeredPaneMount,
933
1462
  maOverlayLine,
1463
+ normalizeSyncGroupId,
1464
+ resolveBarSpacingForInterval,
1465
+ resolveBusMapKey,
1466
+ shouldResizeChartPane,
934
1467
  volMaOverlayLine
935
1468
  };
936
1469
  //# sourceMappingURL=index.js.map