@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.d.ts +91 -3
- package/dist/index.js +368 -58
- package/dist/index.js.map +1 -1
- package/package.json +16 -15
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 {
|
|
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.
|
|
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
|
|
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)
|
|
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
|
-
|
|
342
|
-
{
|
|
343
|
-
{
|
|
344
|
-
{
|
|
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
|
-
|
|
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
|
-
|
|
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 = "
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
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.
|
|
695
|
-
this.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
1005
|
-
this.
|
|
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.
|
|
1318
|
+
this.busRegistry.getBusForPane("main").scrollToLogicalPosition(i, (animationMs ?? 0) > 0);
|
|
1034
1319
|
return;
|
|
1035
1320
|
}
|
|
1036
|
-
this.
|
|
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.
|
|
1109
|
-
|
|
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
|