@coderyo/renderer-lite 1.0.3 → 1.1.1
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.d.ts +142 -3
- package/dist/index.js +518 -58
- package/dist/index.js.map +1 -1
- package/package.json +16 -15
package/dist/index.js
CHANGED
|
@@ -7,6 +7,106 @@ import {
|
|
|
7
7
|
LineSeries as LineSeries2
|
|
8
8
|
} from "lightweight-charts";
|
|
9
9
|
import { intervalMs as intervalMs2 } from "@coderyo/data";
|
|
10
|
+
|
|
11
|
+
// src/time-scale-prepend.ts
|
|
12
|
+
function buildSliceTimes(sortedTimes, renderFromMs, renderToMs) {
|
|
13
|
+
return sortedTimes.filter((t) => t >= renderFromMs && t <= renderToMs);
|
|
14
|
+
}
|
|
15
|
+
function countPrependSliceDelta(beforeSlice, afterSlice) {
|
|
16
|
+
const before = new Set(beforeSlice);
|
|
17
|
+
let delta = 0;
|
|
18
|
+
for (const t of afterSlice) {
|
|
19
|
+
if (!before.has(t)) delta += 1;
|
|
20
|
+
}
|
|
21
|
+
return delta;
|
|
22
|
+
}
|
|
23
|
+
function logicalIndexToBarTimeMs(sliceTimes, logicalIndex) {
|
|
24
|
+
if (sliceTimes.length === 0) return null;
|
|
25
|
+
const idx = Math.min(Math.max(0, Math.floor(logicalIndex)), sliceTimes.length - 1);
|
|
26
|
+
return sliceTimes[idx] ?? null;
|
|
27
|
+
}
|
|
28
|
+
function deriveRenderRange(visibleFromMs, visibleToMs, sortedTimes, intervalMs3) {
|
|
29
|
+
if (visibleToMs <= visibleFromMs && sortedTimes.length > 0) {
|
|
30
|
+
const fromMs = sortedTimes[0];
|
|
31
|
+
const toMs = sortedTimes[sortedTimes.length - 1];
|
|
32
|
+
const bufferMs2 = intervalMs3 * 20;
|
|
33
|
+
return { renderFromMs: fromMs - bufferMs2, renderToMs: toMs + bufferMs2 };
|
|
34
|
+
}
|
|
35
|
+
const span = visibleToMs - visibleFromMs;
|
|
36
|
+
const bufferMs = Math.max(span * 0.1, intervalMs3 * 50);
|
|
37
|
+
return {
|
|
38
|
+
renderFromMs: visibleFromMs - bufferMs,
|
|
39
|
+
renderToMs: visibleToMs + bufferMs
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function logicalRangeForVisibleWindow(sliceTimes, visibleFromMs, visibleToMs) {
|
|
43
|
+
if (sliceTimes.length === 0 || visibleToMs <= visibleFromMs) return null;
|
|
44
|
+
let fromIdx = 0;
|
|
45
|
+
let toIdx = sliceTimes.length - 1;
|
|
46
|
+
for (let i = 0; i < sliceTimes.length; i++) {
|
|
47
|
+
if (sliceTimes[i] >= visibleFromMs) {
|
|
48
|
+
fromIdx = i;
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (let i = sliceTimes.length - 1; i >= 0; i--) {
|
|
53
|
+
if (sliceTimes[i] <= visibleToMs) {
|
|
54
|
+
toIdx = i;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (toIdx < fromIdx) return null;
|
|
59
|
+
return { from: fromIdx, to: toIdx };
|
|
60
|
+
}
|
|
61
|
+
function computePrependSliceDeltaForViewport(input) {
|
|
62
|
+
const { visibleFromMs, visibleToMs, intervalMs: intervalMs3, sortedTimesBefore, sortedTimesAfter } = input;
|
|
63
|
+
const renderBefore = deriveRenderRange(visibleFromMs, visibleToMs, sortedTimesBefore, intervalMs3);
|
|
64
|
+
const renderAfter = deriveRenderRange(visibleFromMs, visibleToMs, sortedTimesAfter, intervalMs3);
|
|
65
|
+
const beforeSlice = buildSliceTimes(
|
|
66
|
+
sortedTimesBefore,
|
|
67
|
+
renderBefore.renderFromMs,
|
|
68
|
+
renderBefore.renderToMs
|
|
69
|
+
);
|
|
70
|
+
const afterSlice = buildSliceTimes(
|
|
71
|
+
sortedTimesAfter,
|
|
72
|
+
renderAfter.renderFromMs,
|
|
73
|
+
renderAfter.renderToMs
|
|
74
|
+
);
|
|
75
|
+
return countPrependSliceDelta(beforeSlice, afterSlice);
|
|
76
|
+
}
|
|
77
|
+
function compensatePrependOnRegistry(opts) {
|
|
78
|
+
const { registry, sortedTimesBefore, sortedTimesAfter, intervalMs: intervalMs3, referenceChart } = opts;
|
|
79
|
+
registry.forEachBus((_, bus) => {
|
|
80
|
+
compensatePrependOnBus(bus, {
|
|
81
|
+
sortedTimesBefore,
|
|
82
|
+
sortedTimesAfter,
|
|
83
|
+
intervalMs: intervalMs3,
|
|
84
|
+
referenceChart
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
function compensatePrependOnBus(bus, opts) {
|
|
89
|
+
const range = bus.getVisibleRange();
|
|
90
|
+
if (!range) return;
|
|
91
|
+
const delta = computePrependSliceDeltaForViewport({
|
|
92
|
+
sortedTimesBefore: opts.sortedTimesBefore,
|
|
93
|
+
sortedTimesAfter: opts.sortedTimesAfter,
|
|
94
|
+
visibleFromMs: range.fromMs,
|
|
95
|
+
visibleToMs: range.toMs,
|
|
96
|
+
intervalMs: opts.intervalMs
|
|
97
|
+
});
|
|
98
|
+
if (delta <= 0) return;
|
|
99
|
+
const { renderFromMs, renderToMs } = deriveRenderRange(
|
|
100
|
+
range.fromMs,
|
|
101
|
+
range.toMs,
|
|
102
|
+
opts.sortedTimesAfter,
|
|
103
|
+
opts.intervalMs
|
|
104
|
+
);
|
|
105
|
+
const sliceAfter = buildSliceTimes(opts.sortedTimesAfter, renderFromMs, renderToMs);
|
|
106
|
+
bus.compensatePrependLogicalRange(delta, opts.referenceChart, sliceAfter);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/pane-orchestrator.ts
|
|
10
110
|
import { lodDecimateBars } from "@coderyo/series";
|
|
11
111
|
|
|
12
112
|
// src/chart-grid.ts
|
|
@@ -19,7 +119,10 @@ function gridOptions(showGrid, dark) {
|
|
|
19
119
|
}
|
|
20
120
|
|
|
21
121
|
// src/pane-orchestrator.ts
|
|
22
|
-
import {
|
|
122
|
+
import {
|
|
123
|
+
DEFAULT_INDICATOR_CONFIG as DEFAULT_INDICATOR_CONFIG2,
|
|
124
|
+
hasVisibleIndicatorPanes as hasVisibleIndicatorPanes2
|
|
125
|
+
} from "@coderyo/indicators";
|
|
23
126
|
|
|
24
127
|
// src/indicator-panes.ts
|
|
25
128
|
import {
|
|
@@ -46,18 +149,25 @@ function attachPaneResizer(topPane, bottomPane, opts = {}) {
|
|
|
46
149
|
const parent = topPane.parentElement;
|
|
47
150
|
if (!parent) return () => {
|
|
48
151
|
};
|
|
152
|
+
let dragging = false;
|
|
49
153
|
const handle = document.createElement("div");
|
|
50
|
-
handle.
|
|
154
|
+
handle.dataset.paneResizer = "1";
|
|
155
|
+
handle.style.cssText = "height:6px;cursor:row-resize;background:#30363d;flex-shrink:0;touch-action:none;z-index:5;";
|
|
156
|
+
handle.onmouseenter = () => {
|
|
157
|
+
handle.style.background = "#388bfd";
|
|
158
|
+
};
|
|
159
|
+
handle.onmouseleave = () => {
|
|
160
|
+
if (!dragging) handle.style.background = "#30363d";
|
|
161
|
+
};
|
|
51
162
|
bottomPane.insertAdjacentElement("beforebegin", handle);
|
|
52
163
|
const saved = opts.storageKey ? localStorage.getItem(opts.storageKey) : null;
|
|
53
164
|
if (saved) {
|
|
54
165
|
const ratio = Number(saved);
|
|
55
|
-
if (Number.isFinite(ratio) && ratio
|
|
166
|
+
if (Number.isFinite(ratio) && ratio >= 0.15 && ratio <= 0.85) {
|
|
56
167
|
topPane.style.flex = `${ratio * 10}`;
|
|
57
168
|
bottomPane.style.flex = `${(1 - ratio) * 10}`;
|
|
58
169
|
}
|
|
59
170
|
}
|
|
60
|
-
let dragging = false;
|
|
61
171
|
const onMove = (clientY) => {
|
|
62
172
|
const rect = parent.getBoundingClientRect();
|
|
63
173
|
const y = clientY - rect.top;
|
|
@@ -70,8 +180,11 @@ function attachPaneResizer(topPane, bottomPane, opts = {}) {
|
|
|
70
180
|
if (opts.storageKey) localStorage.setItem(opts.storageKey, String(ratio));
|
|
71
181
|
};
|
|
72
182
|
const stop = () => {
|
|
183
|
+
const wasDragging = dragging;
|
|
73
184
|
dragging = false;
|
|
74
185
|
document.body.style.cursor = "";
|
|
186
|
+
handle.style.background = "#30363d";
|
|
187
|
+
if (wasDragging) window.dispatchEvent(new CustomEvent("tradview:pane-resize"));
|
|
75
188
|
};
|
|
76
189
|
handle.addEventListener("pointerdown", (e) => {
|
|
77
190
|
dragging = true;
|
|
@@ -79,7 +192,10 @@ function attachPaneResizer(topPane, bottomPane, opts = {}) {
|
|
|
79
192
|
document.body.style.cursor = "row-resize";
|
|
80
193
|
});
|
|
81
194
|
handle.addEventListener("pointermove", (e) => {
|
|
82
|
-
if (dragging)
|
|
195
|
+
if (dragging) {
|
|
196
|
+
onMove(e.clientY);
|
|
197
|
+
window.dispatchEvent(new CustomEvent("tradview:pane-resize"));
|
|
198
|
+
}
|
|
83
199
|
});
|
|
84
200
|
handle.addEventListener("pointerup", stop);
|
|
85
201
|
handle.addEventListener("pointercancel", stop);
|
|
@@ -150,20 +266,6 @@ var IndicatorPaneStack = class {
|
|
|
150
266
|
this.macdWrap = macdPane.wrap;
|
|
151
267
|
this.rsiWrap = rsiPane.wrap;
|
|
152
268
|
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
269
|
const layout = this.layoutForTheme(this.dark);
|
|
168
270
|
const grid = gridOptions(this.showGrid, this.dark);
|
|
169
271
|
this.macdChart = createChart(macdPane.el, { layout, grid, autoSize: true });
|
|
@@ -179,6 +281,7 @@ var IndicatorPaneStack = class {
|
|
|
179
281
|
this.kdjK = this.kdjChart.addSeries(LineSeries, { color: "#42a5f5", lineWidth: 1 });
|
|
180
282
|
this.kdjD = this.kdjChart.addSeries(LineSeries, { color: "#ffa726", lineWidth: 1 });
|
|
181
283
|
this.kdjJ = this.kdjChart.addSeries(LineSeries, { color: "#ef5350", lineWidth: 1 });
|
|
284
|
+
this.applyPaneVisibility();
|
|
182
285
|
}
|
|
183
286
|
root;
|
|
184
287
|
macdChart;
|
|
@@ -215,11 +318,32 @@ var IndicatorPaneStack = class {
|
|
|
215
318
|
this.onConfigChange?.(this.config);
|
|
216
319
|
}
|
|
217
320
|
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
321
|
const anyVisible = this.config.showMacd || this.config.showRsi || this.config.showKdj;
|
|
222
322
|
this.root.style.display = anyVisible ? "flex" : "none";
|
|
323
|
+
this.rebuildPaneLayout();
|
|
324
|
+
}
|
|
325
|
+
/** Rebuild flex children and drag handles only between visible panes. */
|
|
326
|
+
rebuildPaneLayout() {
|
|
327
|
+
for (const detach of this.detachResizers) detach();
|
|
328
|
+
this.detachResizers.length = 0;
|
|
329
|
+
this.root.querySelectorAll("[data-pane-resizer]").forEach((el) => el.remove());
|
|
330
|
+
const panes = [];
|
|
331
|
+
if (this.config.showMacd) panes.push({ id: "macd", el: this.macdWrap });
|
|
332
|
+
if (this.config.showRsi) panes.push({ id: "rsi", el: this.rsiWrap });
|
|
333
|
+
if (this.config.showKdj) panes.push({ id: "kdj", el: this.kdjWrap });
|
|
334
|
+
this.root.replaceChildren(...panes.map((p) => p.el));
|
|
335
|
+
for (let i = 0; i < panes.length - 1; i++) {
|
|
336
|
+
const top = panes[i];
|
|
337
|
+
const bottom = panes[i + 1];
|
|
338
|
+
this.detachResizers.push(
|
|
339
|
+
attachPaneResizer(top.el, bottom.el, {
|
|
340
|
+
storageKey: `tradview:pane:${top.id}-${bottom.id}`,
|
|
341
|
+
minTopPx: 72,
|
|
342
|
+
minBottomPx: 72
|
|
343
|
+
})
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
this.resize();
|
|
223
347
|
}
|
|
224
348
|
clearBars() {
|
|
225
349
|
this.macdLine.setData([]);
|
|
@@ -338,11 +462,14 @@ var IndicatorPaneStack = class {
|
|
|
338
462
|
this.kdjChart.timeScale().scrollToRealTime();
|
|
339
463
|
}
|
|
340
464
|
resize() {
|
|
341
|
-
|
|
342
|
-
{
|
|
343
|
-
{
|
|
344
|
-
{
|
|
345
|
-
]
|
|
465
|
+
const panes = [
|
|
466
|
+
{ show: this.config.showMacd, chart: this.macdChart },
|
|
467
|
+
{ show: this.config.showRsi, chart: this.rsiChart },
|
|
468
|
+
{ show: this.config.showKdj, chart: this.kdjChart }
|
|
469
|
+
];
|
|
470
|
+
for (const { show, chart } of panes) {
|
|
471
|
+
if (!show || !chart) continue;
|
|
472
|
+
const el = chart.chartElement()?.parentElement;
|
|
346
473
|
if (!el) continue;
|
|
347
474
|
const w = el.clientWidth;
|
|
348
475
|
const h = el.clientHeight;
|
|
@@ -360,6 +487,7 @@ var IndicatorPaneStack = class {
|
|
|
360
487
|
createPaneWrap(label, paneId) {
|
|
361
488
|
const wrap = document.createElement("div");
|
|
362
489
|
wrap.className = `tv-indicator-pane tv-indicator-pane--${paneId}`;
|
|
490
|
+
wrap.dataset.paneId = paneId;
|
|
363
491
|
wrap.style.cssText = "flex:1;min-height:72px;width:100%;position:relative;border-top:1px solid #30363d;";
|
|
364
492
|
const tag = document.createElement("span");
|
|
365
493
|
tag.textContent = label;
|
|
@@ -390,6 +518,10 @@ var IndicatorPaneStack = class {
|
|
|
390
518
|
textColor: dark ? "#e6edf3" : "#24292f"
|
|
391
519
|
};
|
|
392
520
|
}
|
|
521
|
+
/** LWC instances for sync-group reassignment. */
|
|
522
|
+
getCharts() {
|
|
523
|
+
return [this.macdChart, this.rsiChart, this.kdjChart];
|
|
524
|
+
}
|
|
393
525
|
};
|
|
394
526
|
function maOverlayLine(bars, period = 20, source = "close") {
|
|
395
527
|
const src = barsForSource(bars, source);
|
|
@@ -424,6 +556,7 @@ var TimeScaleBus = class {
|
|
|
424
556
|
if (this.charts.includes(chart)) return;
|
|
425
557
|
this.charts.push(chart);
|
|
426
558
|
chart.timeScale().subscribeVisibleLogicalRangeChange((range) => {
|
|
559
|
+
if (!this.charts.includes(chart)) return;
|
|
427
560
|
if (this.syncing || !range) return;
|
|
428
561
|
const tr = chart.timeScale().getVisibleRange();
|
|
429
562
|
if (tr && typeof tr.from === "number" && typeof tr.to === "number") {
|
|
@@ -433,6 +566,10 @@ var TimeScaleBus = class {
|
|
|
433
566
|
this.syncFrom(chart, range);
|
|
434
567
|
});
|
|
435
568
|
}
|
|
569
|
+
unregister(chart) {
|
|
570
|
+
const i = this.charts.indexOf(chart);
|
|
571
|
+
if (i >= 0) this.charts.splice(i, 1);
|
|
572
|
+
}
|
|
436
573
|
subscribeTransform(listener) {
|
|
437
574
|
this.listeners.add(listener);
|
|
438
575
|
return () => this.listeners.delete(listener);
|
|
@@ -480,6 +617,35 @@ var TimeScaleBus = class {
|
|
|
480
617
|
this.syncing = false;
|
|
481
618
|
this.emit();
|
|
482
619
|
}
|
|
620
|
+
/**
|
|
621
|
+
* DESIGN §10.4.1: after historical prepend, shift every pane's logical range by Δ
|
|
622
|
+
* so canonical `visibleFromMs` / `visibleToMs` (and crosshair time mapping) stay fixed.
|
|
623
|
+
*/
|
|
624
|
+
compensatePrependLogicalRange(delta, referenceChart, sliceTimes) {
|
|
625
|
+
const d = Math.max(0, Math.floor(delta));
|
|
626
|
+
if (d === 0 || this.charts.length === 0) return;
|
|
627
|
+
const ref = referenceChart && this.charts.includes(referenceChart) ? referenceChart : this.charts[0];
|
|
628
|
+
const ts = ref.timeScale();
|
|
629
|
+
let current = typeof ts.getVisibleLogicalRange === "function" ? ts.getVisibleLogicalRange() : null;
|
|
630
|
+
if (!current && sliceTimes?.length && this.visibleToMs > this.visibleFromMs) {
|
|
631
|
+
current = logicalRangeForVisibleWindow(
|
|
632
|
+
sliceTimes,
|
|
633
|
+
this.visibleFromMs,
|
|
634
|
+
this.visibleToMs
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
if (!current) return;
|
|
638
|
+
const next = {
|
|
639
|
+
from: current.from + d,
|
|
640
|
+
to: current.to + d
|
|
641
|
+
};
|
|
642
|
+
this.syncing = true;
|
|
643
|
+
for (const chart of this.charts) {
|
|
644
|
+
chart.timeScale().setVisibleLogicalRange(next);
|
|
645
|
+
}
|
|
646
|
+
this.syncing = false;
|
|
647
|
+
this.emit();
|
|
648
|
+
}
|
|
483
649
|
syncFrom(source, range) {
|
|
484
650
|
this.syncing = true;
|
|
485
651
|
for (const chart of this.charts) {
|
|
@@ -501,6 +667,78 @@ var TimeScaleBus = class {
|
|
|
501
667
|
}
|
|
502
668
|
};
|
|
503
669
|
|
|
670
|
+
// src/time-scale-bus-registry.ts
|
|
671
|
+
function normalizeSyncGroupId(id) {
|
|
672
|
+
if (id == null) return null;
|
|
673
|
+
const trimmed = String(id).trim();
|
|
674
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
675
|
+
}
|
|
676
|
+
var INDEP_PREFIX = "@independent:";
|
|
677
|
+
function independentBusKey(pane) {
|
|
678
|
+
return `${INDEP_PREFIX}${pane}`;
|
|
679
|
+
}
|
|
680
|
+
function resolveBusMapKey(groupId, pane) {
|
|
681
|
+
const norm = normalizeSyncGroupId(groupId);
|
|
682
|
+
return norm ?? independentBusKey(pane);
|
|
683
|
+
}
|
|
684
|
+
var TimeScaleBusRegistry = class {
|
|
685
|
+
buses = /* @__PURE__ */ new Map();
|
|
686
|
+
paneKeys = /* @__PURE__ */ new Map();
|
|
687
|
+
activeKey;
|
|
688
|
+
constructor() {
|
|
689
|
+
for (const pane of ["main", "volume", "indicator"]) {
|
|
690
|
+
const key = independentBusKey(pane);
|
|
691
|
+
this.paneKeys.set(pane, key);
|
|
692
|
+
this.getOrCreateBus(key);
|
|
693
|
+
}
|
|
694
|
+
this.activeKey = this.paneKeys.get("main");
|
|
695
|
+
}
|
|
696
|
+
getOrCreateBus(key) {
|
|
697
|
+
let bus = this.buses.get(key);
|
|
698
|
+
if (!bus) {
|
|
699
|
+
bus = new TimeScaleBus();
|
|
700
|
+
this.buses.set(key, bus);
|
|
701
|
+
}
|
|
702
|
+
return bus;
|
|
703
|
+
}
|
|
704
|
+
getBusKeyForPane(pane) {
|
|
705
|
+
return this.paneKeys.get(pane);
|
|
706
|
+
}
|
|
707
|
+
getBusForPane(pane) {
|
|
708
|
+
return this.getOrCreateBus(this.getBusKeyForPane(pane));
|
|
709
|
+
}
|
|
710
|
+
/** Active bus for IChart viewport APIs (follows last-focused chart pane). */
|
|
711
|
+
get activeBus() {
|
|
712
|
+
return this.getOrCreateBus(this.activeKey);
|
|
713
|
+
}
|
|
714
|
+
getActiveBusKey() {
|
|
715
|
+
return this.activeKey;
|
|
716
|
+
}
|
|
717
|
+
setActivePane(pane) {
|
|
718
|
+
this.activeKey = this.paneKeys.get(pane);
|
|
719
|
+
}
|
|
720
|
+
setPaneSyncGroup(pane, groupId) {
|
|
721
|
+
const nextKey = resolveBusMapKey(groupId, pane);
|
|
722
|
+
const prevKey = this.paneKeys.get(pane);
|
|
723
|
+
this.paneKeys.set(pane, nextKey);
|
|
724
|
+
this.getOrCreateBus(nextKey);
|
|
725
|
+
if (this.activeKey === prevKey) this.activeKey = nextKey;
|
|
726
|
+
return prevKey;
|
|
727
|
+
}
|
|
728
|
+
forEachBus(fn) {
|
|
729
|
+
for (const [key, bus] of this.buses) fn(key, bus);
|
|
730
|
+
}
|
|
731
|
+
moveChart(chart, fromKey, toKey, copyRange = true) {
|
|
732
|
+
if (fromKey === toKey) return;
|
|
733
|
+
const from = this.getOrCreateBus(fromKey);
|
|
734
|
+
const to = this.getOrCreateBus(toKey);
|
|
735
|
+
const range = copyRange ? from.getVisibleRange() : null;
|
|
736
|
+
from.unregister(chart);
|
|
737
|
+
to.register(chart);
|
|
738
|
+
if (range) to.setVisibleTimeRange(range);
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
|
|
504
742
|
// src/viewport-fit.ts
|
|
505
743
|
import { intervalMs } from "@coderyo/data";
|
|
506
744
|
function defaultBarSpacingForInterval(interval) {
|
|
@@ -581,6 +819,13 @@ var BarSmoothAnimator = class {
|
|
|
581
819
|
};
|
|
582
820
|
|
|
583
821
|
// src/pane-orchestrator.ts
|
|
822
|
+
function isLayeredPaneMount(opts) {
|
|
823
|
+
return !!opts.volumeMount;
|
|
824
|
+
}
|
|
825
|
+
function shouldResizeChartPane(focus, pane) {
|
|
826
|
+
if (!focus) return true;
|
|
827
|
+
return focus.has(pane);
|
|
828
|
+
}
|
|
584
829
|
function toUtcSeconds2(tMs) {
|
|
585
830
|
return Math.floor(tMs / 1e3);
|
|
586
831
|
}
|
|
@@ -591,7 +836,18 @@ function barToVolume(b) {
|
|
|
591
836
|
return { time: toUtcSeconds2(b.t), value: b.v ?? 0 };
|
|
592
837
|
}
|
|
593
838
|
var PaneOrchestrator = class {
|
|
594
|
-
|
|
839
|
+
busRegistry = new TimeScaleBusRegistry();
|
|
840
|
+
/** Active sync group bus (last-focused pane); used by ChartController viewport APIs. */
|
|
841
|
+
get bus() {
|
|
842
|
+
return this.busRegistry.activeBus;
|
|
843
|
+
}
|
|
844
|
+
layeredPanes;
|
|
845
|
+
mainEl;
|
|
846
|
+
volWrap;
|
|
847
|
+
detachMainVolResizer = () => {
|
|
848
|
+
};
|
|
849
|
+
resizeFocusPanes = null;
|
|
850
|
+
listenPaneResizeEvents;
|
|
595
851
|
mainChart;
|
|
596
852
|
volumeChart;
|
|
597
853
|
mainSeries;
|
|
@@ -614,6 +870,9 @@ var PaneOrchestrator = class {
|
|
|
614
870
|
barTimesOrdered = [];
|
|
615
871
|
didInitialFit = false;
|
|
616
872
|
skipNextInitialFit = false;
|
|
873
|
+
/** setBars ran before the pane had layout size; refit on first real resize. */
|
|
874
|
+
pendingViewportFit = false;
|
|
875
|
+
pendingViewportBars = null;
|
|
617
876
|
indicatorConfig = null;
|
|
618
877
|
onIndicatorConfigChange;
|
|
619
878
|
currentInterval = "1h";
|
|
@@ -624,18 +883,57 @@ var PaneOrchestrator = class {
|
|
|
624
883
|
smoothPriceDurationMs = 150;
|
|
625
884
|
constructor(opts) {
|
|
626
885
|
this.maxRenderPoints = opts.maxRenderPoints ?? 4e3;
|
|
886
|
+
this.listenPaneResizeEvents = opts.listenPaneResizeEvents !== false;
|
|
627
887
|
this.dark = opts.theme !== "light";
|
|
628
888
|
this.showGrid = opts.showGrid ?? false;
|
|
629
889
|
const layout = this.layoutForTheme(this.dark);
|
|
630
890
|
const grid = gridOptions(this.showGrid, this.dark);
|
|
631
|
-
|
|
632
|
-
mainEl.style.cssText = "flex:7;min-height:120px;width:100%;position:relative;";
|
|
891
|
+
this.layeredPanes = !!opts.volumeMount;
|
|
633
892
|
const volEl = document.createElement("div");
|
|
634
|
-
volEl.style.cssText = "
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
893
|
+
volEl.style.cssText = "width:100%;height:100%;min-height:0;position:relative;";
|
|
894
|
+
if (this.layeredPanes) {
|
|
895
|
+
this.mainEl = opts.container;
|
|
896
|
+
this.mainEl.style.cssText = "width:100%;height:100%;min-height:80px;position:relative;overflow:hidden;";
|
|
897
|
+
this.volWrap = opts.volumeMount;
|
|
898
|
+
this.volWrap.dataset.paneId = "volume";
|
|
899
|
+
this.volWrap.className = "tv-volume-pane tv-volume-pane--layered";
|
|
900
|
+
this.volWrap.replaceChildren();
|
|
901
|
+
this.volWrap.style.cssText = "width:100%;height:100%;min-height:48px;position:relative;overflow:hidden;box-sizing:border-box;";
|
|
902
|
+
const volTag = document.createElement("span");
|
|
903
|
+
volTag.textContent = "Volume";
|
|
904
|
+
volTag.style.cssText = "position:absolute;left:6px;top:4px;z-index:2;font-size:10px;color:#8b949e;pointer-events:none;";
|
|
905
|
+
const volClose = document.createElement("button");
|
|
906
|
+
volClose.type = "button";
|
|
907
|
+
volClose.textContent = "\xD7";
|
|
908
|
+
volClose.title = "\u95DC\u9589\u6210\u4EA4\u91CF";
|
|
909
|
+
volClose.setAttribute("aria-label", "Close volume");
|
|
910
|
+
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;";
|
|
911
|
+
volClose.onclick = () => this.closeVolumePane();
|
|
912
|
+
this.volWrap.append(volTag, volClose, volEl);
|
|
913
|
+
} else {
|
|
914
|
+
this.mainEl = document.createElement("div");
|
|
915
|
+
this.mainEl.style.cssText = "flex:7;min-height:120px;width:100%;position:relative;";
|
|
916
|
+
volEl.style.cssText = "flex:1;min-height:0;width:100%;height:100%;position:relative;";
|
|
917
|
+
this.volWrap = document.createElement("div");
|
|
918
|
+
this.volWrap.dataset.paneId = "volume";
|
|
919
|
+
this.volWrap.className = "tv-volume-pane";
|
|
920
|
+
this.volWrap.style.cssText = "flex:2;min-height:48px;width:100%;position:relative;display:flex;flex-direction:column;border-top:1px solid #30363d;";
|
|
921
|
+
const volTag = document.createElement("span");
|
|
922
|
+
volTag.textContent = "Volume";
|
|
923
|
+
volTag.style.cssText = "position:absolute;left:6px;top:4px;z-index:2;font-size:10px;color:#8b949e;pointer-events:none;";
|
|
924
|
+
const volClose = document.createElement("button");
|
|
925
|
+
volClose.type = "button";
|
|
926
|
+
volClose.textContent = "\xD7";
|
|
927
|
+
volClose.title = "\u95DC\u9589\u6210\u4EA4\u91CF";
|
|
928
|
+
volClose.setAttribute("aria-label", "Close volume");
|
|
929
|
+
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;";
|
|
930
|
+
volClose.onclick = () => this.closeVolumePane();
|
|
931
|
+
this.volWrap.append(volTag, volClose, volEl);
|
|
932
|
+
opts.container.style.cssText = "display:flex;flex-direction:column;height:100%;width:100%;min-height:200px;overflow:hidden;";
|
|
933
|
+
opts.container.append(this.mainEl, this.volWrap);
|
|
934
|
+
this.rebuildMainVolumeResizer();
|
|
935
|
+
}
|
|
936
|
+
this.mainChart = createChart2(this.mainEl, { layout, grid, autoSize: true });
|
|
639
937
|
this.volumeChart = createChart2(volEl, {
|
|
640
938
|
layout,
|
|
641
939
|
grid,
|
|
@@ -691,8 +989,8 @@ var PaneOrchestrator = class {
|
|
|
691
989
|
lineWidth: 1,
|
|
692
990
|
title: "VolMA5"
|
|
693
991
|
});
|
|
694
|
-
this.
|
|
695
|
-
this.
|
|
992
|
+
this.busRegistry.getBusForPane("main").register(this.mainChart);
|
|
993
|
+
this.busRegistry.getBusForPane("volume").register(this.volumeChart);
|
|
696
994
|
this.indicatorRoot = opts.indicatorRoot;
|
|
697
995
|
this.indicatorConfig = opts.indicatorConfig ?? null;
|
|
698
996
|
this.onIndicatorConfigChange = opts.onIndicatorConfigChange;
|
|
@@ -700,9 +998,16 @@ var PaneOrchestrator = class {
|
|
|
700
998
|
this.barSpacingByInterval = opts.barSpacingByInterval;
|
|
701
999
|
this.pinePlots = opts.pinePlots ?? null;
|
|
702
1000
|
this.indicators = this.createIndicatorStack();
|
|
703
|
-
this.initOverlay(mainEl);
|
|
1001
|
+
this.initOverlay(this.mainEl);
|
|
704
1002
|
this.setSmoothPriceUpdate(opts.smoothPriceUpdate ?? false, opts.smoothPriceDurationMs);
|
|
1003
|
+
this.applyVolumeVisibility();
|
|
1004
|
+
if (opts.listenPaneResizeEvents !== false) {
|
|
1005
|
+
window.addEventListener("tradview:pane-resize", this.onPaneResize);
|
|
1006
|
+
}
|
|
705
1007
|
}
|
|
1008
|
+
onPaneResize = () => {
|
|
1009
|
+
this.resize();
|
|
1010
|
+
};
|
|
706
1011
|
setSmoothPriceUpdate(enabled, durationMs = 150) {
|
|
707
1012
|
this.smoothPriceDurationMs = durationMs;
|
|
708
1013
|
if (enabled) {
|
|
@@ -736,7 +1041,9 @@ var PaneOrchestrator = class {
|
|
|
736
1041
|
applyLastBarToSeries(bar) {
|
|
737
1042
|
this.barByTime.set(bar.t, bar);
|
|
738
1043
|
this.mainSeries.update(barToCandle(bar));
|
|
739
|
-
this.
|
|
1044
|
+
if (this.isVolumeVisible()) {
|
|
1045
|
+
this.volumeSeries.update(barToVolume(bar));
|
|
1046
|
+
}
|
|
740
1047
|
this.ensurePriceLine(bar.c);
|
|
741
1048
|
if (this.indicatorConfig) {
|
|
742
1049
|
const bars = [...this.barByTime.values()].sort((a, b) => a.t - b.t);
|
|
@@ -781,10 +1088,12 @@ var PaneOrchestrator = class {
|
|
|
781
1088
|
this.bollMiddle.setData([]);
|
|
782
1089
|
this.bollLower.setData([]);
|
|
783
1090
|
this.volMaSeries.setData([]);
|
|
1091
|
+
this.volumeSeries.setData([]);
|
|
784
1092
|
this.emaSeries.applyOptions({ visible: false });
|
|
785
1093
|
this.bollUpper.applyOptions({ visible: false });
|
|
786
1094
|
this.bollMiddle.applyOptions({ visible: false });
|
|
787
1095
|
this.bollLower.applyOptions({ visible: false });
|
|
1096
|
+
this.applyVolumeVisibility();
|
|
788
1097
|
return;
|
|
789
1098
|
}
|
|
790
1099
|
if (!this.indicators) this.indicators = this.createIndicatorStack();
|
|
@@ -796,6 +1105,81 @@ var PaneOrchestrator = class {
|
|
|
796
1105
|
this.indicators?.setBars(bars);
|
|
797
1106
|
}
|
|
798
1107
|
}
|
|
1108
|
+
this.applyVolumeVisibility();
|
|
1109
|
+
}
|
|
1110
|
+
isVolumeVisible() {
|
|
1111
|
+
return this.indicatorConfig?.showVolume ?? true;
|
|
1112
|
+
}
|
|
1113
|
+
closeVolumePane() {
|
|
1114
|
+
if (!this.indicatorConfig) {
|
|
1115
|
+
this.indicatorConfig = { ...DEFAULT_INDICATOR_CONFIG2, showVolume: false };
|
|
1116
|
+
} else {
|
|
1117
|
+
this.indicatorConfig = { ...this.indicatorConfig, showVolume: false };
|
|
1118
|
+
}
|
|
1119
|
+
this.applyVolumeVisibility();
|
|
1120
|
+
this.onIndicatorConfigChange?.(this.indicatorConfig);
|
|
1121
|
+
}
|
|
1122
|
+
applyVolumeVisibility() {
|
|
1123
|
+
const show = this.isVolumeVisible();
|
|
1124
|
+
this.volWrap.style.display = show ? "" : "none";
|
|
1125
|
+
this.rebuildMainVolumeResizer();
|
|
1126
|
+
if (!show) {
|
|
1127
|
+
this.volumeSeries.setData([]);
|
|
1128
|
+
this.volMaSeries.setData([]);
|
|
1129
|
+
}
|
|
1130
|
+
this.syncChartSize();
|
|
1131
|
+
}
|
|
1132
|
+
/** Assign per-pane sync group ids (`''` / omit = independent). Re-registers LWC charts on group change. */
|
|
1133
|
+
setPaneSyncGroups(patch) {
|
|
1134
|
+
const apply = (pane, groupId) => {
|
|
1135
|
+
const prevKey = this.busRegistry.setPaneSyncGroup(pane, groupId);
|
|
1136
|
+
const nextKey = this.busRegistry.getBusKeyForPane(pane);
|
|
1137
|
+
if (pane === "main") {
|
|
1138
|
+
this.busRegistry.moveChart(this.mainChart, prevKey, nextKey);
|
|
1139
|
+
} else if (pane === "volume") {
|
|
1140
|
+
this.busRegistry.moveChart(this.volumeChart, prevKey, nextKey);
|
|
1141
|
+
} else if (this.indicators) {
|
|
1142
|
+
for (const chart of this.indicators.getCharts()) {
|
|
1143
|
+
this.busRegistry.moveChart(chart, prevKey, nextKey);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
};
|
|
1147
|
+
if (patch.main !== void 0) apply("main", patch.main);
|
|
1148
|
+
if (patch.volume !== void 0) apply("volume", patch.volume);
|
|
1149
|
+
if (patch.indicator !== void 0) apply("indicator", patch.indicator);
|
|
1150
|
+
}
|
|
1151
|
+
setActiveSyncPane(pane) {
|
|
1152
|
+
const key = pane === "volume" ? "volume" : pane === "indicator" ? "indicator" : "main";
|
|
1153
|
+
this.busRegistry.setActivePane(key);
|
|
1154
|
+
}
|
|
1155
|
+
/** P2: when set, only these panes get LWC resize (panes in the same sync group still share TimeScaleBus). */
|
|
1156
|
+
setResizeFocusPanes(panes) {
|
|
1157
|
+
this.resizeFocusPanes = panes?.length ? new Set(panes) : null;
|
|
1158
|
+
this.resize();
|
|
1159
|
+
}
|
|
1160
|
+
/** Current resize focus (null = all panes). @internal — not part of public package API. */
|
|
1161
|
+
getResizeFocusPanes() {
|
|
1162
|
+
return this.resizeFocusPanes ? [...this.resizeFocusPanes] : null;
|
|
1163
|
+
}
|
|
1164
|
+
shouldResizePane(pane) {
|
|
1165
|
+
return shouldResizeChartPane(this.resizeFocusPanes, pane);
|
|
1166
|
+
}
|
|
1167
|
+
rebuildMainVolumeResizer() {
|
|
1168
|
+
if (this.layeredPanes) return;
|
|
1169
|
+
this.detachMainVolResizer();
|
|
1170
|
+
const parent = this.mainEl.parentElement;
|
|
1171
|
+
parent?.querySelectorAll(":scope > [data-pane-resizer]").forEach((el) => el.remove());
|
|
1172
|
+
if (!this.isVolumeVisible()) {
|
|
1173
|
+
this.mainEl.style.flex = "1";
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
this.mainEl.style.flex = "";
|
|
1177
|
+
this.volWrap.style.flex = "";
|
|
1178
|
+
this.detachMainVolResizer = attachPaneResizer(this.mainEl, this.volWrap, {
|
|
1179
|
+
storageKey: "tradview:pane:main-volume",
|
|
1180
|
+
minTopPx: 120,
|
|
1181
|
+
minBottomPx: 48
|
|
1182
|
+
});
|
|
799
1183
|
}
|
|
800
1184
|
setPinePlots(plots) {
|
|
801
1185
|
this.pinePlots = plots;
|
|
@@ -897,7 +1281,11 @@ var PaneOrchestrator = class {
|
|
|
897
1281
|
vols.push(barToVolume(b));
|
|
898
1282
|
}
|
|
899
1283
|
this.mainSeries.setData(candles);
|
|
900
|
-
this.
|
|
1284
|
+
if (this.isVolumeVisible()) {
|
|
1285
|
+
this.volumeSeries.setData(vols);
|
|
1286
|
+
} else {
|
|
1287
|
+
this.volumeSeries.setData([]);
|
|
1288
|
+
}
|
|
901
1289
|
if (this.indicatorConfig) {
|
|
902
1290
|
this.applyMainOverlays(renderBars);
|
|
903
1291
|
if (hasVisibleIndicatorPanes2(this.indicatorConfig)) {
|
|
@@ -914,13 +1302,35 @@ var PaneOrchestrator = class {
|
|
|
914
1302
|
}
|
|
915
1303
|
if (renderBars.length > 0) {
|
|
916
1304
|
this.syncChartSize();
|
|
917
|
-
|
|
918
|
-
this.applyViewAfterDataReload(renderBars);
|
|
919
|
-
this.didInitialFit = true;
|
|
920
|
-
}
|
|
1305
|
+
this.tryInitialViewportFit(renderBars);
|
|
921
1306
|
this.skipNextInitialFit = false;
|
|
922
1307
|
}
|
|
923
1308
|
}
|
|
1309
|
+
mainPaneHasSize() {
|
|
1310
|
+
const el = this.mainChart.chartElement().parentElement;
|
|
1311
|
+
return (el?.clientWidth ?? 0) > 0 && (el?.clientHeight ?? 0) > 0;
|
|
1312
|
+
}
|
|
1313
|
+
tryInitialViewportFit(renderBars) {
|
|
1314
|
+
if (this.didInitialFit || this.skipNextInitialFit) return;
|
|
1315
|
+
if (!this.mainPaneHasSize()) {
|
|
1316
|
+
this.pendingViewportFit = true;
|
|
1317
|
+
this.pendingViewportBars = renderBars;
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
this.applyViewAfterDataReload(renderBars);
|
|
1321
|
+
this.didInitialFit = true;
|
|
1322
|
+
this.pendingViewportFit = false;
|
|
1323
|
+
this.pendingViewportBars = null;
|
|
1324
|
+
}
|
|
1325
|
+
flushPendingViewportFit() {
|
|
1326
|
+
if (!this.pendingViewportFit || this.skipNextInitialFit || this.didInitialFit) return;
|
|
1327
|
+
const bars = this.pendingViewportBars ?? [...this.barByTime.values()].sort((a, b) => a.t - b.t);
|
|
1328
|
+
if (bars.length === 0 || !this.mainPaneHasSize()) return;
|
|
1329
|
+
this.applyViewAfterDataReload(bars);
|
|
1330
|
+
this.didInitialFit = true;
|
|
1331
|
+
this.pendingViewportFit = false;
|
|
1332
|
+
this.pendingViewportBars = null;
|
|
1333
|
+
}
|
|
924
1334
|
subscribeCrosshair(listener) {
|
|
925
1335
|
const handler = (param) => {
|
|
926
1336
|
if (param.time == null || !param.point) {
|
|
@@ -957,7 +1367,7 @@ var PaneOrchestrator = class {
|
|
|
957
1367
|
}
|
|
958
1368
|
createIndicatorStack() {
|
|
959
1369
|
if (!this.indicatorRoot || !this.indicatorConfig) return null;
|
|
960
|
-
return new IndicatorPaneStack(this.indicatorRoot, this.
|
|
1370
|
+
return new IndicatorPaneStack(this.indicatorRoot, this.busRegistry.getBusForPane("indicator"), {
|
|
961
1371
|
theme: this.dark ? "dark" : "light",
|
|
962
1372
|
showGrid: this.showGrid,
|
|
963
1373
|
config: this.indicatorConfig,
|
|
@@ -972,7 +1382,7 @@ var PaneOrchestrator = class {
|
|
|
972
1382
|
if (!this.autoBarSpacingOnInterval) return;
|
|
973
1383
|
const iv = interval ?? this.currentInterval;
|
|
974
1384
|
const spacing = resolveBarSpacingForInterval(iv, this.barSpacingByInterval);
|
|
975
|
-
this.bus.setBarSpacing(spacing);
|
|
1385
|
+
this.busRegistry.forEachBus((_, bus) => bus.setBarSpacing(spacing));
|
|
976
1386
|
}
|
|
977
1387
|
setBarSpacingPolicy(opts) {
|
|
978
1388
|
if (opts.autoBarSpacingOnInterval !== void 0) {
|
|
@@ -988,7 +1398,7 @@ var PaneOrchestrator = class {
|
|
|
988
1398
|
this.applyIntervalBarSpacing();
|
|
989
1399
|
} else {
|
|
990
1400
|
this.mainChart.timeScale().fitContent();
|
|
991
|
-
this.volumeChart.timeScale().fitContent();
|
|
1401
|
+
if (this.isVolumeVisible()) this.volumeChart.timeScale().fitContent();
|
|
992
1402
|
this.indicators?.fitContent();
|
|
993
1403
|
}
|
|
994
1404
|
if (renderBars.length > 0) {
|
|
@@ -1001,14 +1411,31 @@ var PaneOrchestrator = class {
|
|
|
1001
1411
|
resetViewState() {
|
|
1002
1412
|
this.didInitialFit = false;
|
|
1003
1413
|
this.skipNextInitialFit = false;
|
|
1004
|
-
this.
|
|
1005
|
-
this.
|
|
1414
|
+
this.pendingViewportFit = false;
|
|
1415
|
+
this.pendingViewportBars = null;
|
|
1416
|
+
this.busRegistry.forEachBus((_, bus) => {
|
|
1417
|
+
bus.visibleFromMs = 0;
|
|
1418
|
+
bus.visibleToMs = 0;
|
|
1419
|
+
});
|
|
1006
1420
|
}
|
|
1007
1421
|
/** Skip the next automatic fitContent after setBars (used by reloadHistory). */
|
|
1008
1422
|
preserveViewportOnNextSetBars() {
|
|
1009
1423
|
this.skipNextInitialFit = true;
|
|
1010
1424
|
this.didInitialFit = true;
|
|
1011
1425
|
}
|
|
1426
|
+
/**
|
|
1427
|
+
* DESIGN §10.4.1: after `mergeBars(..., prepend)` shift logical ranges on every sync bus
|
|
1428
|
+
* so canonical ms viewport and crosshair `t` stay stable.
|
|
1429
|
+
*/
|
|
1430
|
+
compensatePrependForBuses(sortedTimesBefore, sortedTimesAfter, interval) {
|
|
1431
|
+
compensatePrependOnRegistry({
|
|
1432
|
+
registry: this.busRegistry,
|
|
1433
|
+
sortedTimesBefore,
|
|
1434
|
+
sortedTimesAfter,
|
|
1435
|
+
intervalMs: intervalMs2(interval),
|
|
1436
|
+
referenceChart: this.mainChart
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1012
1439
|
getVisibleRange() {
|
|
1013
1440
|
return this.bus.getVisibleRange();
|
|
1014
1441
|
}
|
|
@@ -1030,10 +1457,10 @@ var PaneOrchestrator = class {
|
|
|
1030
1457
|
if (nearest == null) return;
|
|
1031
1458
|
const i = this.barTimesOrdered.indexOf(nearest.t);
|
|
1032
1459
|
if (i < 0) return;
|
|
1033
|
-
this.
|
|
1460
|
+
this.busRegistry.getBusForPane("main").scrollToLogicalPosition(i, (animationMs ?? 0) > 0);
|
|
1034
1461
|
return;
|
|
1035
1462
|
}
|
|
1036
|
-
this.
|
|
1463
|
+
this.busRegistry.getBusForPane("main").scrollToLogicalPosition(idx, (animationMs ?? 0) > 0);
|
|
1037
1464
|
}
|
|
1038
1465
|
/** Clear series while symbol/interval data reloads (avoids overlapping candles). */
|
|
1039
1466
|
clearBars() {
|
|
@@ -1048,13 +1475,13 @@ var PaneOrchestrator = class {
|
|
|
1048
1475
|
}
|
|
1049
1476
|
fitContent() {
|
|
1050
1477
|
this.mainChart.timeScale().fitContent();
|
|
1051
|
-
this.volumeChart.timeScale().fitContent();
|
|
1478
|
+
if (this.isVolumeVisible()) this.volumeChart.timeScale().fitContent();
|
|
1052
1479
|
this.indicators?.fitContent();
|
|
1053
1480
|
this.didInitialFit = true;
|
|
1054
1481
|
}
|
|
1055
1482
|
scrollToRealtime() {
|
|
1056
1483
|
this.mainChart.timeScale().scrollToRealTime();
|
|
1057
|
-
this.volumeChart.timeScale().scrollToRealTime();
|
|
1484
|
+
if (this.isVolumeVisible()) this.volumeChart.timeScale().scrollToRealTime();
|
|
1058
1485
|
this.indicators?.scrollToRealtime();
|
|
1059
1486
|
}
|
|
1060
1487
|
setLogScale(enabled) {
|
|
@@ -1063,7 +1490,18 @@ var PaneOrchestrator = class {
|
|
|
1063
1490
|
resize() {
|
|
1064
1491
|
this.syncChartSize();
|
|
1065
1492
|
this.syncOverlaySize();
|
|
1493
|
+
if (this.shouldResizePane("indicator")) this.indicators?.resize();
|
|
1494
|
+
this.flushPendingViewportFit();
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Resize every LWC pane once for viewport fit; does **not** change {@link resizeFocusPanes}.
|
|
1498
|
+
* @internal Used by ChartController data refresh paths.
|
|
1499
|
+
*/
|
|
1500
|
+
resizeAllPanes() {
|
|
1501
|
+
this.syncChartSize({ allPanes: true });
|
|
1502
|
+
this.syncOverlaySize();
|
|
1066
1503
|
this.indicators?.resize();
|
|
1504
|
+
this.flushPendingViewportFit();
|
|
1067
1505
|
}
|
|
1068
1506
|
getOverlayCanvas() {
|
|
1069
1507
|
return this.overlayCanvas;
|
|
@@ -1088,6 +1526,10 @@ var PaneOrchestrator = class {
|
|
|
1088
1526
|
return p ?? null;
|
|
1089
1527
|
}
|
|
1090
1528
|
destroy() {
|
|
1529
|
+
if (this.listenPaneResizeEvents) {
|
|
1530
|
+
window.removeEventListener("tradview:pane-resize", this.onPaneResize);
|
|
1531
|
+
}
|
|
1532
|
+
this.detachMainVolResizer();
|
|
1091
1533
|
this.barAnimator?.cancel();
|
|
1092
1534
|
if (this.priceLine) {
|
|
1093
1535
|
this.mainSeries.removePriceLine(this.priceLine);
|
|
@@ -1105,8 +1547,10 @@ var PaneOrchestrator = class {
|
|
|
1105
1547
|
parent.appendChild(canvas);
|
|
1106
1548
|
this.overlayCanvas = canvas;
|
|
1107
1549
|
this.syncOverlaySize();
|
|
1108
|
-
this.
|
|
1109
|
-
|
|
1550
|
+
this.busRegistry.forEachBus((_, bus) => {
|
|
1551
|
+
bus.subscribeTransform(() => {
|
|
1552
|
+
this.syncOverlaySize();
|
|
1553
|
+
});
|
|
1110
1554
|
});
|
|
1111
1555
|
}
|
|
1112
1556
|
/** Let drawing overlay receive clicks; cursor mode keeps pan/zoom on LWC. */
|
|
@@ -1123,15 +1567,16 @@ var PaneOrchestrator = class {
|
|
|
1123
1567
|
this.overlayCanvas.width = rect.width * devicePixelRatio;
|
|
1124
1568
|
this.overlayCanvas.height = rect.height * devicePixelRatio;
|
|
1125
1569
|
}
|
|
1126
|
-
syncChartSize() {
|
|
1570
|
+
syncChartSize(opts) {
|
|
1571
|
+
const all = opts?.allPanes === true;
|
|
1127
1572
|
const mainEl = this.mainChart.chartElement().parentElement;
|
|
1128
1573
|
const volEl = this.volumeChart.chartElement().parentElement;
|
|
1129
|
-
if (mainEl) {
|
|
1574
|
+
if (mainEl && (all || this.shouldResizePane("main"))) {
|
|
1130
1575
|
const w = mainEl.clientWidth;
|
|
1131
1576
|
const h = mainEl.clientHeight;
|
|
1132
1577
|
if (w > 0 && h > 0) this.mainChart.resize(w, h);
|
|
1133
1578
|
}
|
|
1134
|
-
if (volEl) {
|
|
1579
|
+
if (this.isVolumeVisible() && volEl && (all || this.shouldResizePane("volume"))) {
|
|
1135
1580
|
const w = volEl.clientWidth;
|
|
1136
1581
|
const h = volEl.clientHeight;
|
|
1137
1582
|
if (w > 0 && h > 0) this.volumeChart.resize(w, h);
|
|
@@ -1148,12 +1593,27 @@ export {
|
|
|
1148
1593
|
IndicatorPaneStack,
|
|
1149
1594
|
PaneOrchestrator,
|
|
1150
1595
|
TimeScaleBus,
|
|
1596
|
+
TimeScaleBusRegistry,
|
|
1597
|
+
attachPaneResizer,
|
|
1151
1598
|
bollOverlayLines,
|
|
1599
|
+
buildSliceTimes,
|
|
1600
|
+
compensatePrependOnBus,
|
|
1601
|
+
compensatePrependOnRegistry,
|
|
1602
|
+
computePrependSliceDeltaForViewport,
|
|
1603
|
+
countPrependSliceDelta,
|
|
1152
1604
|
defaultBarSpacingForInterval,
|
|
1605
|
+
deriveRenderRange,
|
|
1153
1606
|
detectIndicatorBarMutation,
|
|
1154
1607
|
emaOverlayLine,
|
|
1608
|
+
independentBusKey,
|
|
1609
|
+
isLayeredPaneMount,
|
|
1610
|
+
logicalIndexToBarTimeMs,
|
|
1611
|
+
logicalRangeForVisibleWindow,
|
|
1155
1612
|
maOverlayLine,
|
|
1613
|
+
normalizeSyncGroupId,
|
|
1156
1614
|
resolveBarSpacingForInterval,
|
|
1615
|
+
resolveBusMapKey,
|
|
1616
|
+
shouldResizeChartPane,
|
|
1157
1617
|
volMaOverlayLine
|
|
1158
1618
|
};
|
|
1159
1619
|
//# sourceMappingURL=index.js.map
|