@coderyo/core 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.js CHANGED
@@ -1,11 +1,402 @@
1
1
  // src/bridge-wire.ts
2
2
  import {
3
3
  BRIDGE_SCHEMA_VERSION,
4
- isBridgeInbound
4
+ isBridgeInbound,
5
+ isBridgeLayerInboundType as isBridgeLayerInboundType2,
6
+ LAYER_API_READY
5
7
  } from "@coderyo/bridge";
6
8
 
9
+ // src/bridge-layer-wire.ts
10
+ import {
11
+ bridgeLayerPayloadHasDeprecatedLayerId,
12
+ isBridgeLayerInboundType
13
+ } from "@coderyo/bridge";
14
+
15
+ // src/merge-layer-bridge-preset.ts
16
+ function upsertById(current, incoming) {
17
+ if (!incoming?.length) return current.map((x) => ({ ...x }));
18
+ const map = new Map(current.map((x) => [x.id, { ...x }]));
19
+ for (const item of incoming) {
20
+ if (!item?.id) continue;
21
+ map.set(item.id, { ...item });
22
+ }
23
+ return [...map.values()];
24
+ }
25
+ function mergeLayers(current, incoming) {
26
+ if (!incoming?.length) return current.map((l) => ({ ...l }));
27
+ const map = new Map(current.map((l) => [l.id, { ...l }]));
28
+ for (const raw of incoming) {
29
+ if (!raw?.id) continue;
30
+ const prev = map.get(raw.id);
31
+ map.set(raw.id, { ...prev ?? raw, ...raw });
32
+ }
33
+ return [...map.values()];
34
+ }
35
+ function mergeGroups(current, incoming) {
36
+ if (!incoming?.length) {
37
+ return current.map((g) => ({ ...g, layerIds: [...g.layerIds ?? []] }));
38
+ }
39
+ const map = new Map(
40
+ current.map((g) => [g.id, { ...g, layerIds: [...g.layerIds ?? []] }])
41
+ );
42
+ for (const raw of incoming) {
43
+ if (!raw?.id) continue;
44
+ map.set(raw.id, {
45
+ id: raw.id,
46
+ name: raw.name ?? map.get(raw.id)?.name,
47
+ layerIds: [...raw.layerIds ?? map.get(raw.id)?.layerIds ?? []]
48
+ });
49
+ }
50
+ return [...map.values()];
51
+ }
52
+ function mergeLayerBridgePreset(current, partial) {
53
+ return {
54
+ ...current,
55
+ ...partial,
56
+ pages: upsertById(current.pages, partial.pages),
57
+ layers: mergeLayers(current.layers, partial.layers),
58
+ groups: mergeGroups(current.groups, partial.groups)
59
+ };
60
+ }
61
+
62
+ // src/resolve-pane-sync-groups.ts
63
+ function resolvePaneSyncId(layer) {
64
+ if (layer == null) return void 0;
65
+ const raw = layer.syncTimeScaleGroupId;
66
+ if (raw == null) return null;
67
+ const trimmed = String(raw).trim();
68
+ return trimmed.length > 0 ? trimmed : null;
69
+ }
70
+ function resolvePaneSyncGroupsFromLayers(layers, pageId) {
71
+ const scoped = pageId != null ? layers.filter((l) => l.pageId === pageId) : layers;
72
+ const pick = (type) => scoped.find((l) => l.type === type);
73
+ return {
74
+ main: resolvePaneSyncId(pick("chart.main")),
75
+ volume: resolvePaneSyncId(pick("chart.volume")),
76
+ indicator: resolvePaneSyncId(pick("chart.indicator"))
77
+ };
78
+ }
79
+
80
+ // src/bridge-layer-wire.ts
81
+ var PANE_LAYER_TYPES = {
82
+ main: "chart.main",
83
+ volume: "chart.volume",
84
+ indicator: "chart.indicator"
85
+ };
86
+ var registry = /* @__PURE__ */ new Map();
87
+ function registerChartLayerBridge(reg) {
88
+ const state = {
89
+ ...reg,
90
+ mergePreset: reg.mergePreset ?? mergeLayerBridgePreset,
91
+ visitedPageIds: /* @__PURE__ */ new Set(),
92
+ pendingAllPagesApply: false
93
+ };
94
+ registry.set(reg.chartId, state);
95
+ return () => {
96
+ registry.delete(reg.chartId);
97
+ };
98
+ }
99
+ function unregisterChartLayerBridge(chartId) {
100
+ registry.delete(chartId);
101
+ }
102
+ function clearLayerBridgeVisitedPages(chartId) {
103
+ const state = registry.get(chartId);
104
+ if (!state) return;
105
+ state.visitedPageIds.clear();
106
+ state.pendingAllPagesApply = false;
107
+ }
108
+ function hasLayerBridgeRegistration(chartId) {
109
+ if (chartId != null) return registry.has(chartId);
110
+ return registry.size > 0;
111
+ }
112
+ function isValidLayerBridgePane(pane) {
113
+ return pane === "main" || pane === "volume" || pane === "indicator";
114
+ }
115
+ function resolvePaneLayerIds(preset, pane, opts) {
116
+ const layerType = PANE_LAYER_TYPES[pane];
117
+ const allPages = opts?.allPages === true;
118
+ const activePageId = opts?.activePageId;
119
+ const pageIds = allPages ? preset.pages.map((p) => p.id) : activePageId ? [activePageId] : [];
120
+ if (pageIds.length === 0) return [];
121
+ return preset.layers.filter((l) => pageIds.includes(l.pageId) && l.type === layerType).map((l) => l.id);
122
+ }
123
+ function postLayerError(chartId, code, message, post) {
124
+ post("chart.error", { chartId, code, message });
125
+ }
126
+ function applyTimeScaleForPage(state, pageId) {
127
+ state.chart.applyTimeScaleSyncFromLayers(state.layerController.getPreset().layers, pageId);
128
+ state.visitedPageIds.add(pageId);
129
+ }
130
+ function lazyApplyActivePage(state) {
131
+ applyTimeScaleForPage(state, state.layerController.activePageId);
132
+ }
133
+ function invalidateNonActivePageVisits(state) {
134
+ const activeId = state.layerController.activePageId;
135
+ for (const p of state.layerController.getPreset().pages) {
136
+ if (p.id !== activeId) state.visitedPageIds.delete(p.id);
137
+ }
138
+ }
139
+ function afterPresetMutation(state) {
140
+ state.compositorApply?.();
141
+ state.syncCompositorShellVisibility?.();
142
+ lazyApplyActivePage(state);
143
+ }
144
+ function requirePayloadChartId(payload) {
145
+ const id = typeof payload.chartId === "string" ? payload.chartId.trim() : "";
146
+ return id.length > 0 ? id : null;
147
+ }
148
+ function handleSetSyncGroup(state, payload, post, postError) {
149
+ if (bridgeLayerPayloadHasDeprecatedLayerId(payload)) {
150
+ postError("INVALID_PAYLOAD", "host.layer.setSyncGroup does not accept layerId (use pane)");
151
+ return;
152
+ }
153
+ if (!("pane" in payload)) {
154
+ postError("INVALID_PAYLOAD", "pane is required");
155
+ return;
156
+ }
157
+ if (!isValidLayerBridgePane(payload.pane)) {
158
+ postError("INVALID_PANE", `Invalid pane: ${String(payload.pane ?? "")}`);
159
+ return;
160
+ }
161
+ const pane = payload.pane;
162
+ const allPages = payload.allPages === true;
163
+ const groupId = payload.groupId == null ? "" : String(payload.groupId);
164
+ const preset = state.layerController.getPreset();
165
+ const layerIds = resolvePaneLayerIds(preset, pane, {
166
+ allPages,
167
+ activePageId: state.layerController.activePageId
168
+ });
169
+ if (layerIds.length === 0) {
170
+ postError("PANE_NOT_FOUND", `No ${pane} pane layers in scope`);
171
+ return;
172
+ }
173
+ for (const layerId of layerIds) {
174
+ state.layerController.setLayerSyncGroup(layerId, groupId);
175
+ }
176
+ lazyApplyActivePage(state);
177
+ post("chart.layerSyncGroupChanged", {
178
+ chartId: state.chartId,
179
+ pane,
180
+ groupId,
181
+ allPages,
182
+ activePageId: state.layerController.activePageId
183
+ });
184
+ }
185
+ function handleSetVisible(state, payload, post, postError) {
186
+ if (!("pane" in payload)) {
187
+ postError("INVALID_PAYLOAD", "pane is required");
188
+ return;
189
+ }
190
+ if (!isValidLayerBridgePane(payload.pane)) {
191
+ postError("INVALID_PANE", `Invalid pane: ${String(payload.pane ?? "")}`);
192
+ return;
193
+ }
194
+ if (typeof payload.visible !== "boolean") {
195
+ postError("INVALID_PAYLOAD", "visible must be boolean");
196
+ return;
197
+ }
198
+ const pane = payload.pane;
199
+ const allPages = payload.allPages === true;
200
+ const visible = payload.visible;
201
+ const preset = state.layerController.getPreset();
202
+ const layerIds = resolvePaneLayerIds(preset, pane, {
203
+ allPages,
204
+ activePageId: state.layerController.activePageId
205
+ });
206
+ if (layerIds.length === 0) {
207
+ postError("PANE_NOT_FOUND", `No ${pane} pane layers in scope`);
208
+ return;
209
+ }
210
+ for (const layerId of layerIds) {
211
+ state.layerController.setLayerVisible(layerId, visible);
212
+ }
213
+ state.compositorApply?.();
214
+ state.syncCompositorShellVisibility?.();
215
+ post("chart.layerVisibleChanged", {
216
+ chartId: state.chartId,
217
+ pane,
218
+ visible,
219
+ allPages
220
+ });
221
+ }
222
+ function maybeClearPendingAllPages(state) {
223
+ if (!state.pendingAllPagesApply) return;
224
+ const allVisited = state.layerController.getPreset().pages.every((p) => state.visitedPageIds.has(p.id));
225
+ if (allVisited) state.pendingAllPagesApply = false;
226
+ }
227
+ function handleSetActivePage(state, payload, post, postError) {
228
+ const pageId = typeof payload.pageId === "string" ? payload.pageId : "";
229
+ if (!pageId) {
230
+ postError("INVALID_PAYLOAD", "pageId is required");
231
+ return;
232
+ }
233
+ const preset = state.layerController.getPreset();
234
+ if (!preset.pages.some((p) => p.id === pageId)) {
235
+ postError("INVALID_PAYLOAD", `Unknown pageId: ${pageId}`);
236
+ return;
237
+ }
238
+ const previousPageId = state.layerController.activePageId;
239
+ if (previousPageId === pageId) {
240
+ return;
241
+ }
242
+ if (!state.layerController.setActivePage(pageId)) {
243
+ return;
244
+ }
245
+ state.compositorApply?.();
246
+ state.syncCompositorShellVisibility?.();
247
+ if (!state.visitedPageIds.has(pageId) || state.pendingAllPagesApply) {
248
+ applyTimeScaleForPage(state, pageId);
249
+ }
250
+ maybeClearPendingAllPages(state);
251
+ post("chart.layerPageChanged", {
252
+ chartId: state.chartId,
253
+ pageId,
254
+ previousPageId
255
+ });
256
+ }
257
+ function handleSetPreset(state, payload, postError, emitLayerDeltas) {
258
+ const rawPreset = payload.preset;
259
+ if (!rawPreset || typeof rawPreset !== "object") {
260
+ postError("INVALID_PRESET", "preset object is required");
261
+ return;
262
+ }
263
+ const incoming = rawPreset;
264
+ const revisionRaw = Number(incoming.revision);
265
+ if (!Number.isFinite(revisionRaw) || revisionRaw < 1) {
266
+ postError("INVALID_PRESET", "preset.revision must be an integer \u2265 1");
267
+ return;
268
+ }
269
+ const revision = Math.floor(revisionRaw);
270
+ if (revision < state.layerController.presetRevision) {
271
+ postError(
272
+ "STALE_PRESET_REVISION",
273
+ `Host revision ${revision} < current ${state.layerController.presetRevision}`
274
+ );
275
+ return;
276
+ }
277
+ const replace = payload.replace === true;
278
+ const normalize = state.normalizePreset ?? ((p) => p);
279
+ const mergeFn = state.mergePreset ?? mergeLayerBridgePreset;
280
+ const current = state.layerController.getPreset();
281
+ let merged;
282
+ try {
283
+ merged = replace ? normalize({ ...incoming, revision }) : normalize(mergeFn(current, { ...incoming, revision }));
284
+ } catch (err) {
285
+ postError("INVALID_PRESET", err instanceof Error ? err.message : String(err));
286
+ return;
287
+ }
288
+ const prev = state.layerController.getPreset();
289
+ if (!state.layerController.setPreset(merged)) {
290
+ postError("INVALID_PRESET", "setPreset rejected (interaction in progress)");
291
+ return;
292
+ }
293
+ state.layerController.presetRevision = revision;
294
+ afterPresetMutation(state);
295
+ emitLayerDeltas(prev, state.layerController.getPreset());
296
+ }
297
+ function handleApplyTimeScaleSync(state, payload, postError) {
298
+ const allPages = payload.allPages === true;
299
+ const activeId = state.layerController.activePageId;
300
+ const pageId = typeof payload.pageId === "string" ? payload.pageId : activeId;
301
+ if (!state.layerController.getPreset().pages.some((p) => p.id === pageId)) {
302
+ postError("INVALID_PAYLOAD", `Unknown pageId: ${pageId}`);
303
+ return;
304
+ }
305
+ if (allPages) {
306
+ state.pendingAllPagesApply = true;
307
+ invalidateNonActivePageVisits(state);
308
+ applyTimeScaleForPage(state, activeId);
309
+ return;
310
+ }
311
+ if (pageId === activeId) {
312
+ applyTimeScaleForPage(state, pageId);
313
+ } else {
314
+ state.visitedPageIds.delete(pageId);
315
+ }
316
+ }
317
+ function emitPresetLayerDeltas(state, prev, next, postSync, postVisible, _postPage) {
318
+ const active = state.layerController.activePageId;
319
+ for (const pane of ["main", "volume", "indicator"]) {
320
+ const prevIds = resolvePaneLayerIds(prev, pane, { activePageId: active });
321
+ const nextIds = resolvePaneLayerIds(next, pane, { activePageId: active });
322
+ if (prevIds.length === 0 && nextIds.length === 0) continue;
323
+ const prevLayer = prev.layers.find((l) => l.id === prevIds[0]);
324
+ const nextLayer = next.layers.find((l) => l.id === nextIds[0]);
325
+ const prevGroup = prevLayer?.syncTimeScaleGroupId ?? "";
326
+ const nextGroup = nextLayer?.syncTimeScaleGroupId ?? "";
327
+ if (prevGroup !== nextGroup && nextIds.length > 0) {
328
+ postSync("chart.layerSyncGroupChanged", {
329
+ chartId: state.chartId,
330
+ pane,
331
+ groupId: nextGroup,
332
+ allPages: false,
333
+ activePageId: active
334
+ });
335
+ }
336
+ if (prevLayer && nextLayer && prevLayer.visible !== nextLayer.visible) {
337
+ postVisible("chart.layerVisibleChanged", {
338
+ chartId: state.chartId,
339
+ pane,
340
+ visible: nextLayer.visible !== false,
341
+ allPages: false
342
+ });
343
+ }
344
+ }
345
+ }
346
+ function handleLayerBridgeMessage(type, payload, opts) {
347
+ if (!isBridgeLayerInboundType(type)) return false;
348
+ const chartId = requirePayloadChartId(payload);
349
+ if (!chartId) {
350
+ opts.post("chart.error", {
351
+ chartId: "",
352
+ code: "MISSING_CHART_ID",
353
+ message: "chartId is required in payload for host.layer.*"
354
+ });
355
+ return true;
356
+ }
357
+ const state = registry.get(chartId);
358
+ if (!state) {
359
+ const code = registry.size === 0 ? "LAYER_BRIDGE_NOT_REGISTERED" : "CHART_NOT_FOUND";
360
+ const message = code === "LAYER_BRIDGE_NOT_REGISTERED" ? "Layer bridge is not registered (pass layerBridge to wireChartBridge)" : `No layer bridge registered for chartId: ${chartId}`;
361
+ opts.post("chart.error", { chartId, code, message });
362
+ return true;
363
+ }
364
+ const postError = (code, message) => postLayerError(chartId, code, message, opts.post);
365
+ const postSync = (_type, payload2) => opts.post("chart.layerSyncGroupChanged", payload2);
366
+ const postVisible = (_type, payload2) => opts.post("chart.layerVisibleChanged", payload2);
367
+ const postPage = (_type, payload2) => opts.post("chart.layerPageChanged", payload2);
368
+ switch (type) {
369
+ case "host.layer.setSyncGroup":
370
+ handleSetSyncGroup(state, payload, postSync, postError);
371
+ break;
372
+ case "host.layer.setVisible":
373
+ handleSetVisible(state, payload, postVisible, postError);
374
+ break;
375
+ case "host.layer.setActivePage":
376
+ handleSetActivePage(state, payload, postPage, postError);
377
+ break;
378
+ case "host.layer.setPreset":
379
+ handleSetPreset(
380
+ state,
381
+ payload,
382
+ postError,
383
+ (prev, next) => emitPresetLayerDeltas(state, prev, next, postSync, postVisible, postPage)
384
+ );
385
+ break;
386
+ case "host.layer.applyTimeScaleSync":
387
+ handleApplyTimeScaleSync(state, payload, postError);
388
+ break;
389
+ default:
390
+ postError("SCHEMA_MISMATCH", `Unknown host.layer.* event: ${type}`);
391
+ }
392
+ return true;
393
+ }
394
+ function resolvePaneSyncGroupsForBridge(layers, pageId) {
395
+ return resolvePaneSyncGroupsFromLayers(layers, pageId);
396
+ }
397
+
7
398
  // src/version.ts
8
- var TRADVIEW_VERSION = "1.0.3";
399
+ var TRADVIEW_VERSION = "1.1.1";
9
400
 
10
401
  // src/bridge-wire.ts
11
402
  var TRADVIEW_API_VERSION = 1;
@@ -41,7 +432,8 @@ function wireChartBridge(opts) {
41
432
  chartId,
42
433
  bridgeSchemaVersion: BRIDGE_SCHEMA_VERSION,
43
434
  apiVersion: TRADVIEW_API_VERSION,
44
- version: TRADVIEW_VERSION
435
+ version: TRADVIEW_VERSION,
436
+ layerApi: LAYER_API_READY
45
437
  });
46
438
  const postResize = () => {
47
439
  const el = controller.getContainer();
@@ -124,15 +516,37 @@ function wireChartBridge(opts) {
124
516
  chart.on(ev, fn);
125
517
  }
126
518
  }
519
+ const unregisterLayer = opts.layerBridge ? registerChartLayerBridge({ ...opts.layerBridge, chartId }) : void 0;
127
520
  const offHost = bridge.onMessage((msg) => {
128
521
  if (!isBridgeInbound(msg)) return;
129
522
  const p = msg.payload ?? {};
523
+ if (msg.type.startsWith("host.layer.")) {
524
+ if (!isBridgeLayerInboundType2(msg.type)) {
525
+ post("chart.error", {
526
+ chartId: typeof p.chartId === "string" ? p.chartId : "",
527
+ code: "SCHEMA_MISMATCH",
528
+ message: `Unknown host.layer.* event: ${msg.type}`
529
+ });
530
+ return;
531
+ }
532
+ handleLayerBridgeMessage(msg.type, p, {
533
+ bridge,
534
+ post: (type, payload) => post(type, payload)
535
+ });
536
+ return;
537
+ }
130
538
  switch (msg.type) {
131
539
  case "host.setSymbol":
132
- if (typeof p.symbol === "string") chart.setSymbol(p.symbol);
540
+ if (typeof p.symbol === "string") {
541
+ chart.setSymbol(p.symbol);
542
+ clearLayerBridgeVisitedPages(chartId);
543
+ }
133
544
  break;
134
545
  case "host.setInterval":
135
- if (typeof p.interval === "string") chart.setInterval(p.interval);
546
+ if (typeof p.interval === "string") {
547
+ chart.setInterval(p.interval);
548
+ clearLayerBridgeVisitedPages(chartId);
549
+ }
136
550
  break;
137
551
  case "host.setTheme":
138
552
  if (p.theme === "dark" || p.theme === "light") chart.setTheme(p.theme);
@@ -199,6 +613,17 @@ function wireChartBridge(opts) {
199
613
  chart.setDrawingTool(p.tool);
200
614
  }
201
615
  break;
616
+ case "host.setChartPaneResizeFocus":
617
+ if (p.pane === "main" || p.pane === "volume" || p.pane === "indicator" || p.pane === "all") {
618
+ chart.setChartPaneResizeFocus(p.pane);
619
+ } else {
620
+ post("chart.error", {
621
+ chartId,
622
+ code: "INVALID_PANE",
623
+ message: `Invalid pane: ${String(p.pane ?? "")}`
624
+ });
625
+ }
626
+ break;
202
627
  case "host.destroy":
203
628
  chart.destroy();
204
629
  break;
@@ -209,12 +634,17 @@ function wireChartBridge(opts) {
209
634
  return () => {
210
635
  if (crosshairTimer) clearTimeout(crosshairTimer);
211
636
  offHost();
637
+ unregisterLayer?.();
212
638
  for (const [ev, fn] of handlers) chart.off(ev, fn);
213
639
  };
214
640
  }
215
641
 
216
642
  // src/chart-controller.ts
217
- import { clearedIndicatorConfig } from "@coderyo/indicators";
643
+ import {
644
+ clearedIndicatorConfig,
645
+ disableIndicatorLayer as applyDisableIndicatorLayer,
646
+ listActiveIndicatorLayers
647
+ } from "@coderyo/indicators";
218
648
  import { floorBarOpenTime, intervalMs as intervalMs2 } from "@coderyo/data";
219
649
  import { parseInterval } from "@coderyo/data";
220
650
  import { BarStore, computeGapStartTimes, TickAggregator } from "@coderyo/series";
@@ -260,7 +690,9 @@ import {
260
690
  runPineLiteAsync,
261
691
  terminatePineWorker
262
692
  } from "@coderyo/pine-lite";
263
- import { PaneOrchestrator } from "@coderyo/renderer-lite";
693
+ import {
694
+ PaneOrchestrator
695
+ } from "@coderyo/renderer-lite";
264
696
 
265
697
  // src/chart-features.ts
266
698
  var DEFAULT_CHART_FEATURES = {
@@ -382,11 +814,12 @@ var ChartController = class {
382
814
  });
383
815
  const symbol = options.symbol?.trim() || PENDING_SYMBOL;
384
816
  const interval = parseInterval(options.interval ?? "1h");
385
- const fetchPolicy = this.features.gaps.fillVisibleHoles ? "fill-visible-holes" : this.features.fetchPolicy;
817
+ this.fetchPolicy = this.features.gaps.fillVisibleHoles ? "fill-visible-holes" : this.features.fetchPolicy;
386
818
  this.store = new BarStore(symbol || PENDING_SYMBOL, interval);
387
- this.virtualWindow = new VirtualWindow(this.store, { fetchPolicy });
388
819
  this.orchestrator = new PaneOrchestrator({
389
820
  container,
821
+ volumeMount: options.volumeMount,
822
+ listenPaneResizeEvents: false,
390
823
  indicatorRoot: options.indicatorHost,
391
824
  theme: options.theme ?? "dark",
392
825
  scaleMode: options.scaleMode ?? "linear",
@@ -402,20 +835,27 @@ var ChartController = class {
402
835
  if (options.width) container.style.width = `${options.width}px`;
403
836
  if (options.height) container.style.height = `${options.height}px`;
404
837
  this.resizeObserver = new ResizeObserver(() => {
405
- if (!this.destroyed) this.resize();
838
+ if (this.destroyed) return;
839
+ this.orchestrator.setResizeFocusPanes(null);
406
840
  });
407
- this.resizeObserver.observe(container);
408
- this.orchestrator.bus.subscribeTransform(() => {
409
- const bus = this.orchestrator.bus;
410
- if (bus.visibleToMs > bus.visibleFromMs) {
411
- this.virtualWindow.setVisibleRange({
412
- fromMs: bus.visibleFromMs,
413
- toMs: bus.visibleToMs
414
- });
415
- this.visibleRangeInitialized = true;
416
- }
417
- void this.maybeLoadMore();
418
- this.drawingManager?.redraw();
841
+ const resizeTargets = [container, options.volumeMount, options.indicatorHost].filter(
842
+ Boolean
843
+ );
844
+ for (const el of resizeTargets) this.resizeObserver.observe(el);
845
+ window.addEventListener("tradview:pane-resize", this.onPaneResize);
846
+ this.orchestrator.busRegistry.forEachBus((key, bus) => {
847
+ bus.subscribeTransform(() => {
848
+ if (key !== this.orchestrator.busRegistry.getActiveBusKey()) return;
849
+ if (bus.visibleToMs > bus.visibleFromMs) {
850
+ this.activeVirtualWindow().setVisibleRange({
851
+ fromMs: bus.visibleFromMs,
852
+ toMs: bus.visibleToMs
853
+ });
854
+ this.visibleRangeInitialized = true;
855
+ }
856
+ void this.maybeLoadMore();
857
+ this.drawingManager?.redraw();
858
+ });
419
859
  });
420
860
  const overlay = this.orchestrator.getOverlayCanvas();
421
861
  if (overlay) {
@@ -452,7 +892,9 @@ var ChartController = class {
452
892
  container;
453
893
  options;
454
894
  store;
455
- virtualWindow;
895
+ fetchPolicy;
896
+ /** Per sync-group viewport for loadMore / render slicing (active bus drives IChart APIs). */
897
+ virtualWindows = /* @__PURE__ */ new Map();
456
898
  orchestrator;
457
899
  handlers = /* @__PURE__ */ new Map();
458
900
  subscriptionId = null;
@@ -473,6 +915,10 @@ var ChartController = class {
473
915
  /** After clearAllIndicators(); blocks Pine replot until script/features change. */
474
916
  pinePlotsSuppressed = false;
475
917
  chartStorage;
918
+ onPaneResize = () => {
919
+ if (this.destroyed) return;
920
+ this.orchestrator.setResizeFocusPanes(null);
921
+ };
476
922
  getFeatures() {
477
923
  return { ...this.features };
478
924
  }
@@ -489,9 +935,21 @@ var ChartController = class {
489
935
  const s = this.store.symbol;
490
936
  return s.length > 0 && s !== PENDING_SYMBOL;
491
937
  }
938
+ activeVirtualWindow() {
939
+ const key = this.orchestrator.busRegistry.getActiveBusKey();
940
+ let vw = this.virtualWindows.get(key);
941
+ if (!vw) {
942
+ vw = new VirtualWindow(this.store, { fetchPolicy: this.fetchPolicy });
943
+ const range = this.orchestrator.busRegistry.getOrCreateBus(key).getVisibleRange();
944
+ if (range) vw.setVisibleRange(range);
945
+ this.virtualWindows.set(key, vw);
946
+ }
947
+ return vw;
948
+ }
492
949
  applyFeatures() {
493
950
  const fetchPolicy = this.features.gaps.fillVisibleHoles ? "fill-visible-holes" : this.features.fetchPolicy;
494
- this.virtualWindow.setFetchPolicy(fetchPolicy);
951
+ this.fetchPolicy = fetchPolicy;
952
+ for (const vw of this.virtualWindows.values()) vw.setFetchPolicy(fetchPolicy);
495
953
  this.orchestrator.setIndicatorConfig(this.features.indicators);
496
954
  this.orchestrator.setBarSpacingPolicy({
497
955
  autoBarSpacingOnInterval: this.features.autoBarSpacingOnInterval,
@@ -520,7 +978,7 @@ var ChartController = class {
520
978
  }
521
979
  this.pineIr = compiled.ir;
522
980
  if (this.hasActiveSymbol()) {
523
- const bars = this.virtualWindow.getBarsForRender();
981
+ const bars = this.activeVirtualWindow().getBarsForRender();
524
982
  if (bars.length > 0) this.applyPinePlots(bars);
525
983
  }
526
984
  }
@@ -590,7 +1048,7 @@ var ChartController = class {
590
1048
  async applyRealtimeBar(bar, partial, loadGen = this.loadGeneration) {
591
1049
  await this.store.mergeRealtime({ bar, partial });
592
1050
  if (!this.isLoadGenerationCurrent(loadGen)) return;
593
- const bars = this.virtualWindow.getBarsForRender();
1051
+ const bars = this.activeVirtualWindow().getBarsForRender();
594
1052
  const last = bars[bars.length - 1];
595
1053
  if (last && this.features.smoothPriceUpdate) {
596
1054
  this.orchestrator.updateLastBar(last, {
@@ -657,6 +1115,21 @@ var ChartController = class {
657
1115
  this.orchestrator.resize();
658
1116
  return this;
659
1117
  }
1118
+ /** P2: limit LWC resize to focused panes; also selects that pane's time-scale sync group for IChart APIs. */
1119
+ setChartPaneResizeFocus(pane) {
1120
+ if (pane !== "all") {
1121
+ this.orchestrator.setActiveSyncPane(pane);
1122
+ this.orchestrator.setResizeFocusPanes([pane]);
1123
+ } else {
1124
+ this.orchestrator.setResizeFocusPanes(null);
1125
+ }
1126
+ return this;
1127
+ }
1128
+ /** Apply `syncTimeScaleGroupId` from layout layers to pane buses (empty = independent). */
1129
+ applyTimeScaleSyncFromLayers(layers, pageId) {
1130
+ this.orchestrator.setPaneSyncGroups(resolvePaneSyncGroupsFromLayers(layers, pageId));
1131
+ return this;
1132
+ }
660
1133
  setDrawingTool(tool) {
661
1134
  this.drawingManager?.setTool(tool);
662
1135
  this.syncOverlayPointerEvents();
@@ -707,6 +1180,20 @@ var ChartController = class {
707
1180
  this.orchestrator.setIndicatorConfig(loaded);
708
1181
  this.emit("featuresChange", this.getFeatures());
709
1182
  }
1183
+ /** @public List built-in indicator layers currently enabled on the chart. */
1184
+ listIndicatorLayers() {
1185
+ if (this.features.indicators === null) return [];
1186
+ return listActiveIndicatorLayers(this.features.indicators);
1187
+ }
1188
+ /** @public Disable a single built-in indicator layer by id. */
1189
+ disableIndicatorLayer(id) {
1190
+ if (this.features.indicators === null) {
1191
+ return clearedIndicatorConfig();
1192
+ }
1193
+ const next = applyDisableIndicatorLayer(this.features.indicators, id);
1194
+ this.setIndicatorConfig(next);
1195
+ return next;
1196
+ }
710
1197
  clearAllIndicators() {
711
1198
  const config = clearedIndicatorConfig(this.features.indicators ?? void 0);
712
1199
  this.setIndicatorConfig(config);
@@ -774,7 +1261,7 @@ var ChartController = class {
774
1261
  this.orchestrator.setVisibleRange(range);
775
1262
  const { fromMs, toMs } = range;
776
1263
  if (toMs > fromMs) {
777
- this.virtualWindow.setVisibleRange({ fromMs, toMs });
1264
+ this.activeVirtualWindow().setVisibleRange({ fromMs, toMs });
778
1265
  this.visibleRangeInitialized = true;
779
1266
  }
780
1267
  return this;
@@ -846,6 +1333,7 @@ var ChartController = class {
846
1333
  this.offCrosshair?.();
847
1334
  this.offCrosshair = null;
848
1335
  this.resizeObserver.disconnect();
1336
+ window.removeEventListener("tradview:pane-resize", this.onPaneResize);
849
1337
  this.drawingManager?.destroy();
850
1338
  void this.teardownSubscription();
851
1339
  this.orchestrator.destroy();
@@ -856,7 +1344,7 @@ var ChartController = class {
856
1344
  beginDataContextChange() {
857
1345
  this.loadGeneration += 1;
858
1346
  this.visibleRangeInitialized = false;
859
- this.virtualWindow.setVisibleRange({ fromMs: 0, toMs: 0 });
1347
+ this.virtualWindows.clear();
860
1348
  this.orchestrator.resetViewState();
861
1349
  this.orchestrator.clearBars();
862
1350
  return this.loadGeneration;
@@ -868,74 +1356,86 @@ var ChartController = class {
868
1356
  if (!this.isLoadGenerationCurrent(loadGen)) return;
869
1357
  const symbol = this.store.symbol;
870
1358
  const interval = this.store.interval;
871
- const endTime = Date.now();
872
- if (this.features.protobuf) {
873
- this.emit("error", {
874
- code: "PROTOBUF_UNAVAILABLE",
875
- message: "Protobuf encoding requires protocol v1.1 (not in 1.0.0)"
1359
+ try {
1360
+ const endTime = Date.now();
1361
+ if (this.features.protobuf) {
1362
+ this.emit("error", {
1363
+ code: "PROTOBUF_UNAVAILABLE",
1364
+ message: "Protobuf encoding requires protocol v1.1 (not in 1.0.0)"
1365
+ });
1366
+ }
1367
+ const history = await fetchChartHistory(this.options.dataProvider, {
1368
+ mode: "loadMore",
1369
+ symbol,
1370
+ interval,
1371
+ endTime,
1372
+ limit: 500
876
1373
  });
877
- }
878
- const history = await fetchChartHistory(this.options.dataProvider, {
879
- mode: "loadMore",
880
- symbol,
881
- interval,
882
- endTime,
883
- limit: 500
884
- });
885
- if (!this.isLoadGenerationCurrent(loadGen)) return;
886
- if (this.store.symbol !== symbol || this.store.interval !== interval) return;
887
- await this.store.mergeBars(history.bars.map((bar) => ({ bar })));
888
- if (!this.isLoadGenerationCurrent(loadGen)) return;
889
- this.refreshRender(loadGen);
890
- void this.resolveSymbol(symbol).then((info) => {
891
- if (!this.isLoadGenerationCurrent(loadGen)) return;
892
- this.emit("symbolChange", info ?? { symbol });
893
- });
894
- this.emit("intervalChange", interval);
895
- const streamMode = this.features.tickStream ? "bar+tick" : this.features.streamMode;
896
- const tickOnly = streamMode === "tick";
897
- const params = {
898
- symbol,
899
- interval,
900
- channels: tickOnly ? ["tick"] : this.features.tickStream ? ["bar", "tick"] : ["bar"],
901
- streamMode
902
- };
903
- this.tickAggregator = tickOnly ? new TickAggregator(interval, (bar, partial) => {
904
1374
  if (!this.isLoadGenerationCurrent(loadGen)) return;
905
1375
  if (this.store.symbol !== symbol || this.store.interval !== interval) return;
906
- void this.applyRealtimeBar(bar, partial, loadGen);
907
- }) : null;
908
- await this.options.dataProvider.connect?.();
909
- if (!this.isLoadGenerationCurrent(loadGen)) return;
910
- const sub = await this.options.dataProvider.subscribe(params, {
911
- onBar: tickOnly ? void 0 : (bar, meta) => {
1376
+ if (history.bars.length === 0) {
1377
+ this.emit("error", {
1378
+ kind: "history",
1379
+ message: `No bars returned for ${symbol} (${interval})`
1380
+ });
1381
+ return;
1382
+ }
1383
+ await this.store.mergeBars(history.bars.map((bar) => ({ bar })));
1384
+ if (!this.isLoadGenerationCurrent(loadGen)) return;
1385
+ this.refreshRender(loadGen);
1386
+ void this.resolveSymbol(symbol).then((info) => {
912
1387
  if (!this.isLoadGenerationCurrent(loadGen)) return;
913
- if (this.store.symbol !== symbol || this.store.interval !== interval) return;
914
- void this.applyRealtimeBar(bar, meta.partial, loadGen);
915
- },
916
- onTick: (tick) => {
1388
+ this.emit("symbolChange", info ?? { symbol });
1389
+ });
1390
+ this.emit("intervalChange", interval);
1391
+ const streamMode = this.features.tickStream ? "bar+tick" : this.features.streamMode;
1392
+ const tickOnly = streamMode === "tick";
1393
+ const params = {
1394
+ symbol,
1395
+ interval,
1396
+ channels: tickOnly ? ["tick"] : this.features.tickStream ? ["bar", "tick"] : ["bar"],
1397
+ streamMode
1398
+ };
1399
+ this.tickAggregator = tickOnly ? new TickAggregator(interval, (bar, partial) => {
917
1400
  if (!this.isLoadGenerationCurrent(loadGen)) return;
918
1401
  if (this.store.symbol !== symbol || this.store.interval !== interval) return;
919
- if (this.tickAggregator) {
920
- this.tickAggregator.ingest(tick);
921
- return;
922
- }
923
- const bar = this.buildBarFromPrice(tick.price, tick.t);
924
- void this.applyRealtimeBar(bar, true, loadGen);
925
- },
926
- onConnectionChange: (state) => {
927
- this.emit("connectionChange", state);
928
- if (state === "connected" && this.subscriptionId != null) {
929
- void this.catchUpMissedBars();
930
- }
931
- },
932
- onError: (err) => this.emit("error", err)
933
- });
934
- if (!this.isLoadGenerationCurrent(loadGen)) {
935
- await this.options.dataProvider.unsubscribe(sub.id);
936
- return;
1402
+ void this.applyRealtimeBar(bar, partial, loadGen);
1403
+ }) : null;
1404
+ await this.options.dataProvider.connect?.();
1405
+ if (!this.isLoadGenerationCurrent(loadGen)) return;
1406
+ const sub = await this.options.dataProvider.subscribe(params, {
1407
+ onBar: tickOnly ? void 0 : (bar, meta) => {
1408
+ if (!this.isLoadGenerationCurrent(loadGen)) return;
1409
+ if (this.store.symbol !== symbol || this.store.interval !== interval) return;
1410
+ void this.applyRealtimeBar(bar, meta.partial, loadGen);
1411
+ },
1412
+ onTick: (tick) => {
1413
+ if (!this.isLoadGenerationCurrent(loadGen)) return;
1414
+ if (this.store.symbol !== symbol || this.store.interval !== interval) return;
1415
+ if (this.tickAggregator) {
1416
+ this.tickAggregator.ingest(tick);
1417
+ return;
1418
+ }
1419
+ const bar = this.buildBarFromPrice(tick.price, tick.t);
1420
+ void this.applyRealtimeBar(bar, true, loadGen);
1421
+ },
1422
+ onConnectionChange: (state) => {
1423
+ this.emit("connectionChange", state);
1424
+ if (state === "connected" && this.subscriptionId != null) {
1425
+ void this.catchUpMissedBars();
1426
+ }
1427
+ },
1428
+ onError: (err) => this.emit("error", err)
1429
+ });
1430
+ if (!this.isLoadGenerationCurrent(loadGen)) {
1431
+ await this.options.dataProvider.unsubscribe(sub.id);
1432
+ return;
1433
+ }
1434
+ this.subscriptionId = sub.id;
1435
+ } catch (err) {
1436
+ this.emit("error", err);
1437
+ this.emit("connectionChange", "disconnected");
937
1438
  }
938
- this.subscriptionId = sub.id;
939
1439
  }
940
1440
  async teardownSubscription() {
941
1441
  this.tickAggregator?.flush();
@@ -1008,10 +1508,12 @@ var ChartController = class {
1008
1508
  async maybeLoadMore() {
1009
1509
  if (this.destroyed || this.loadingMore) return;
1010
1510
  const loadGen = this.loadGeneration;
1011
- const reqs = this.virtualWindow.planFetches();
1511
+ const reqs = this.activeVirtualWindow().planFetches();
1012
1512
  if (reqs.length === 0) return;
1013
1513
  this.loadingMore = true;
1014
1514
  try {
1515
+ const sortedTimesBefore = [...this.store.sortedTimes];
1516
+ let didPrepend = false;
1015
1517
  for (const req of reqs) {
1016
1518
  if (!this.isLoadGenerationCurrent(loadGen)) return;
1017
1519
  const history = await fetchChartHistory(
@@ -1020,12 +1522,21 @@ var ChartController = class {
1020
1522
  );
1021
1523
  if (!this.isLoadGenerationCurrent(loadGen)) return;
1022
1524
  if (history.bars.length === 0) continue;
1525
+ const prepend = req.mode === "loadMore";
1526
+ if (prepend) didPrepend = true;
1023
1527
  await this.store.mergeBars(
1024
1528
  history.bars.map((bar) => ({ bar, source: "rest" })),
1025
- req.mode === "loadMore"
1529
+ prepend
1026
1530
  );
1027
1531
  }
1028
1532
  if (!this.isLoadGenerationCurrent(loadGen)) return;
1533
+ if (didPrepend && this.visibleRangeInitialized) {
1534
+ this.orchestrator.compensatePrependForBuses(
1535
+ sortedTimesBefore,
1536
+ this.store.sortedTimes,
1537
+ this.store.interval
1538
+ );
1539
+ }
1029
1540
  this.refreshRender(loadGen);
1030
1541
  } catch (err) {
1031
1542
  this.emit("error", err);
@@ -1038,19 +1549,20 @@ var ChartController = class {
1038
1549
  const times = this.store.sortedTimes;
1039
1550
  if (times.length === 0) return;
1040
1551
  if (!this.visibleRangeInitialized) {
1041
- this.virtualWindow.setVisibleRange({
1552
+ this.activeVirtualWindow().setVisibleRange({
1042
1553
  fromMs: times[0],
1043
1554
  toMs: times[times.length - 1]
1044
1555
  });
1045
1556
  this.visibleRangeInitialized = true;
1046
1557
  }
1047
- const bars = this.virtualWindow.getBarsForRender();
1558
+ const bars = this.activeVirtualWindow().getBarsForRender();
1048
1559
  if (bars.length === 0) return;
1049
1560
  const gaps = this.features.gaps.whitespace ? computeGapStartTimes(
1050
1561
  bars.map((b) => b.t),
1051
1562
  this.store.interval
1052
1563
  ) : void 0;
1053
1564
  this.orchestrator.setBars(bars, gaps);
1565
+ this.orchestrator.resizeAllPanes();
1054
1566
  this.applyPinePlots(bars);
1055
1567
  this.drawingManager?.redraw();
1056
1568
  this.emit("visibleRangeChange", {
@@ -1165,6 +1677,14 @@ function wrap(controller, beforeDestroy) {
1165
1677
  controller.resize(s);
1166
1678
  return wrap(controller, beforeDestroy);
1167
1679
  },
1680
+ setChartPaneResizeFocus: (pane) => {
1681
+ controller.setChartPaneResizeFocus(pane);
1682
+ return wrap(controller, beforeDestroy);
1683
+ },
1684
+ applyTimeScaleSyncFromLayers: (layers, pageId) => {
1685
+ controller.applyTimeScaleSyncFromLayers(layers, pageId);
1686
+ return wrap(controller, beforeDestroy);
1687
+ },
1168
1688
  setFullscreen: (e) => {
1169
1689
  controller.setFullscreen(e);
1170
1690
  return wrap(controller, beforeDestroy);
@@ -1198,6 +1718,8 @@ function wrap(controller, beforeDestroy) {
1198
1718
  controller.setIndicatorConfig(c);
1199
1719
  return wrap(controller, beforeDestroy);
1200
1720
  },
1721
+ listIndicatorLayers: () => controller.listIndicatorLayers(),
1722
+ disableIndicatorLayer: (id) => controller.disableIndicatorLayer(id),
1201
1723
  clearAllIndicators: () => controller.clearAllIndicators(),
1202
1724
  clearAllDrawings: () => controller.clearAllDrawings(),
1203
1725
  setReturnToCursorAfterDraw: (v) => {
@@ -1238,7 +1760,8 @@ function createChart(target, options) {
1238
1760
  bridge: options.bridge,
1239
1761
  chartId: options.chartId,
1240
1762
  outboundEvents: options.bridgeOutboundEvents,
1241
- crosshairThrottleMs: options.bridgeCrosshairThrottleMs
1763
+ crosshairThrottleMs: options.bridgeCrosshairThrottleMs,
1764
+ layerBridge: options.layerBridge
1242
1765
  });
1243
1766
  }
1244
1767
  return chart;
@@ -1251,7 +1774,9 @@ import {
1251
1774
  hasVisibleIndicatorPanes,
1252
1775
  hasMainChartOverlays,
1253
1776
  hasAnyActiveIndicators,
1254
- indicatorConfigStorageKey as indicatorConfigStorageKey2
1777
+ indicatorConfigStorageKey as indicatorConfigStorageKey2,
1778
+ listActiveIndicatorLayers as listActiveIndicatorLayers2,
1779
+ disableIndicatorLayer
1255
1780
  } from "@coderyo/indicators";
1256
1781
  import {
1257
1782
  compilePineLite as compilePineLite2,
@@ -1268,6 +1793,7 @@ export {
1268
1793
  PINE_SAMPLE_SCRIPT2 as PINE_SAMPLE_SCRIPT,
1269
1794
  TRADVIEW_API_VERSION,
1270
1795
  TRADVIEW_VERSION,
1796
+ clearLayerBridgeVisitedPages,
1271
1797
  clearedIndicatorConfig2 as clearedIndicatorConfig,
1272
1798
  compilePineLite2 as compilePineLite,
1273
1799
  createChart,
@@ -1275,14 +1801,25 @@ export {
1275
1801
  createDemoChartOptions,
1276
1802
  createLocalChartStorage,
1277
1803
  defaultChartStorage,
1804
+ disableIndicatorLayer,
1805
+ handleLayerBridgeMessage,
1278
1806
  hasAnyActiveIndicators,
1807
+ hasLayerBridgeRegistration,
1279
1808
  hasMainChartOverlays,
1280
1809
  hasVisibleIndicatorPanes,
1281
1810
  indicatorConfigStorageKey2 as indicatorConfigStorageKey,
1811
+ isValidLayerBridgePane,
1812
+ listActiveIndicatorLayers2 as listActiveIndicatorLayers,
1282
1813
  loadIndicatorConfig,
1814
+ mergeLayerBridgePreset,
1815
+ registerChartLayerBridge,
1283
1816
  resolveChartFeatures,
1817
+ resolvePaneLayerIds,
1818
+ resolvePaneSyncGroupsForBridge,
1819
+ resolvePaneSyncGroupsFromLayers,
1284
1820
  runPineLite,
1285
1821
  saveIndicatorConfig,
1822
+ unregisterChartLayerBridge,
1286
1823
  wireChartBridge
1287
1824
  };
1288
1825
  //# sourceMappingURL=index.js.map