@coderyo/core 1.0.2 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,14 +1,414 @@
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.2";
399
+ var TRADVIEW_VERSION = "1.1.0";
9
400
 
10
401
  // src/bridge-wire.ts
11
402
  var TRADVIEW_API_VERSION = 1;
403
+ var DRAWING_TOOLS = /* @__PURE__ */ new Set([
404
+ "cursor",
405
+ "trendline",
406
+ "hline",
407
+ "vline",
408
+ "rectangle",
409
+ "fibonacci",
410
+ "text"
411
+ ]);
12
412
  var CHART_EVENT_TO_BRIDGE = {
13
413
  connectionChange: "chart.connectionChange",
14
414
  visibleRangeChange: "chart.visibleRange",
@@ -32,7 +432,8 @@ function wireChartBridge(opts) {
32
432
  chartId,
33
433
  bridgeSchemaVersion: BRIDGE_SCHEMA_VERSION,
34
434
  apiVersion: TRADVIEW_API_VERSION,
35
- version: TRADVIEW_VERSION
435
+ version: TRADVIEW_VERSION,
436
+ layerApi: LAYER_API_READY
36
437
  });
37
438
  const postResize = () => {
38
439
  const el = controller.getContainer();
@@ -115,15 +516,37 @@ function wireChartBridge(opts) {
115
516
  chart.on(ev, fn);
116
517
  }
117
518
  }
519
+ const unregisterLayer = opts.layerBridge ? registerChartLayerBridge({ ...opts.layerBridge, chartId }) : void 0;
118
520
  const offHost = bridge.onMessage((msg) => {
119
521
  if (!isBridgeInbound(msg)) return;
120
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
+ }
121
538
  switch (msg.type) {
122
539
  case "host.setSymbol":
123
- if (typeof p.symbol === "string") chart.setSymbol(p.symbol);
540
+ if (typeof p.symbol === "string") {
541
+ chart.setSymbol(p.symbol);
542
+ clearLayerBridgeVisitedPages(chartId);
543
+ }
124
544
  break;
125
545
  case "host.setInterval":
126
- if (typeof p.interval === "string") chart.setInterval(p.interval);
546
+ if (typeof p.interval === "string") {
547
+ chart.setInterval(p.interval);
548
+ clearLayerBridgeVisitedPages(chartId);
549
+ }
127
550
  break;
128
551
  case "host.setTheme":
129
552
  if (p.theme === "dark" || p.theme === "light") chart.setTheme(p.theme);
@@ -174,6 +597,33 @@ function wireChartBridge(opts) {
174
597
  chart.setFeatures(p.features);
175
598
  }
176
599
  break;
600
+ case "host.setIndicatorConfig":
601
+ if (p.config && typeof p.config === "object") {
602
+ chart.setIndicatorConfig(p.config);
603
+ }
604
+ break;
605
+ case "host.clearAllIndicators":
606
+ chart.clearAllIndicators();
607
+ break;
608
+ case "host.clearAllDrawings":
609
+ chart.clearAllDrawings();
610
+ break;
611
+ case "host.setDrawingTool":
612
+ if (typeof p.tool === "string" && DRAWING_TOOLS.has(p.tool)) {
613
+ chart.setDrawingTool(p.tool);
614
+ }
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;
177
627
  case "host.destroy":
178
628
  chart.destroy();
179
629
  break;
@@ -184,11 +634,17 @@ function wireChartBridge(opts) {
184
634
  return () => {
185
635
  if (crosshairTimer) clearTimeout(crosshairTimer);
186
636
  offHost();
637
+ unregisterLayer?.();
187
638
  for (const [ev, fn] of handlers) chart.off(ev, fn);
188
639
  };
189
640
  }
190
641
 
191
642
  // src/chart-controller.ts
643
+ import {
644
+ clearedIndicatorConfig,
645
+ disableIndicatorLayer as applyDisableIndicatorLayer,
646
+ listActiveIndicatorLayers
647
+ } from "@coderyo/indicators";
192
648
  import { floorBarOpenTime, intervalMs as intervalMs2 } from "@coderyo/data";
193
649
  import { parseInterval } from "@coderyo/data";
194
650
  import { BarStore, computeGapStartTimes, TickAggregator } from "@coderyo/series";
@@ -234,7 +690,9 @@ import {
234
690
  runPineLiteAsync,
235
691
  terminatePineWorker
236
692
  } from "@coderyo/pine-lite";
237
- import { PaneOrchestrator } from "@coderyo/renderer-lite";
693
+ import {
694
+ PaneOrchestrator
695
+ } from "@coderyo/renderer-lite";
238
696
 
239
697
  // src/chart-features.ts
240
698
  var DEFAULT_CHART_FEATURES = {
@@ -251,7 +709,9 @@ var DEFAULT_CHART_FEATURES = {
251
709
  protobuf: false,
252
710
  telemetry: false,
253
711
  tickStream: false,
254
- pineWorker: true
712
+ pineWorker: true,
713
+ autoBarSpacingOnInterval: true,
714
+ barSpacingByInterval: void 0
255
715
  };
256
716
  function resolveChartFeatures(partial) {
257
717
  const d = DEFAULT_CHART_FEATURES;
@@ -275,7 +735,9 @@ function resolveChartFeatures(partial) {
275
735
  protobuf: partial?.protobuf ?? d.protobuf,
276
736
  telemetry: partial?.telemetry ?? d.telemetry,
277
737
  tickStream: partial?.tickStream ?? d.tickStream,
278
- pineWorker: partial?.pineWorker ?? d.pineWorker
738
+ pineWorker: partial?.pineWorker ?? d.pineWorker,
739
+ autoBarSpacingOnInterval: partial?.autoBarSpacingOnInterval ?? d.autoBarSpacingOnInterval,
740
+ barSpacingByInterval: partial?.barSpacingByInterval ?? d.barSpacingByInterval
279
741
  };
280
742
  }
281
743
  function mergeChartFeatures(current, patch) {
@@ -293,16 +755,58 @@ function mergeChartFeatures(current, patch) {
293
755
  protobuf: patch.protobuf ?? current.protobuf,
294
756
  telemetry: patch.telemetry ?? current.telemetry,
295
757
  tickStream: patch.tickStream ?? current.tickStream,
296
- pineWorker: patch.pineWorker ?? current.pineWorker
758
+ pineWorker: patch.pineWorker ?? current.pineWorker,
759
+ autoBarSpacingOnInterval: patch.autoBarSpacingOnInterval ?? current.autoBarSpacingOnInterval,
760
+ barSpacingByInterval: patch.barSpacingByInterval ?? current.barSpacingByInterval
297
761
  });
298
762
  }
299
763
  var PENDING_SYMBOL = "";
300
764
 
765
+ // src/indicator-storage.ts
766
+ import {
767
+ DEFAULT_INDICATOR_CONFIG,
768
+ indicatorConfigStorageKey
769
+ } from "@coderyo/indicators";
770
+ function createLocalChartStorage() {
771
+ return {
772
+ getItem: (key) => {
773
+ try {
774
+ return localStorage.getItem(key);
775
+ } catch {
776
+ return null;
777
+ }
778
+ },
779
+ setItem: (key, value) => {
780
+ try {
781
+ localStorage.setItem(key, value);
782
+ } catch {
783
+ }
784
+ }
785
+ };
786
+ }
787
+ var defaultChartStorage = createLocalChartStorage();
788
+ function loadIndicatorConfig(storage, symbol, interval) {
789
+ try {
790
+ const raw = storage.getItem(indicatorConfigStorageKey(symbol, interval));
791
+ if (!raw) return { ...DEFAULT_INDICATOR_CONFIG };
792
+ return { ...DEFAULT_INDICATOR_CONFIG, ...JSON.parse(raw) };
793
+ } catch {
794
+ return { ...DEFAULT_INDICATOR_CONFIG };
795
+ }
796
+ }
797
+ function saveIndicatorConfig(storage, symbol, interval, config) {
798
+ try {
799
+ storage.setItem(indicatorConfigStorageKey(symbol, interval), JSON.stringify(config));
800
+ } catch {
801
+ }
802
+ }
803
+
301
804
  // src/chart-controller.ts
302
805
  var ChartController = class {
303
806
  constructor(container, options) {
304
807
  this.container = container;
305
808
  this.options = options;
809
+ this.chartStorage = options.chartStorage ?? defaultChartStorage;
306
810
  this.features = resolveChartFeatures({
307
811
  ...options.features,
308
812
  fetchPolicy: options.features?.fetchPolicy ?? options.fetchPolicy,
@@ -310,36 +814,48 @@ var ChartController = class {
310
814
  });
311
815
  const symbol = options.symbol?.trim() || PENDING_SYMBOL;
312
816
  const interval = parseInterval(options.interval ?? "1h");
313
- 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;
314
818
  this.store = new BarStore(symbol || PENDING_SYMBOL, interval);
315
- this.virtualWindow = new VirtualWindow(this.store, { fetchPolicy });
316
819
  this.orchestrator = new PaneOrchestrator({
317
820
  container,
821
+ volumeMount: options.volumeMount,
822
+ listenPaneResizeEvents: false,
318
823
  indicatorRoot: options.indicatorHost,
319
824
  theme: options.theme ?? "dark",
320
825
  scaleMode: options.scaleMode ?? "linear",
321
826
  showGrid: options.showGrid ?? false,
322
827
  indicatorConfig: this.features.indicators,
323
828
  smoothPriceUpdate: this.features.smoothPriceUpdate,
324
- smoothPriceDurationMs: this.features.smoothPriceDurationMs
829
+ smoothPriceDurationMs: this.features.smoothPriceDurationMs,
830
+ onIndicatorConfigChange: (config) => this.setIndicatorConfig(config),
831
+ autoBarSpacingOnInterval: this.features.autoBarSpacingOnInterval,
832
+ barSpacingByInterval: this.features.barSpacingByInterval
325
833
  });
834
+ this.orchestrator.setIntervalContext(interval);
326
835
  if (options.width) container.style.width = `${options.width}px`;
327
836
  if (options.height) container.style.height = `${options.height}px`;
328
837
  this.resizeObserver = new ResizeObserver(() => {
329
- if (!this.destroyed) this.resize();
838
+ if (this.destroyed) return;
839
+ this.orchestrator.setResizeFocusPanes(null);
330
840
  });
331
- this.resizeObserver.observe(container);
332
- this.orchestrator.bus.subscribeTransform(() => {
333
- const bus = this.orchestrator.bus;
334
- if (bus.visibleToMs > bus.visibleFromMs) {
335
- this.virtualWindow.setVisibleRange({
336
- fromMs: bus.visibleFromMs,
337
- toMs: bus.visibleToMs
338
- });
339
- this.visibleRangeInitialized = true;
340
- }
341
- void this.maybeLoadMore();
342
- 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
+ });
343
859
  });
344
860
  const overlay = this.orchestrator.getOverlayCanvas();
345
861
  if (overlay) {
@@ -366,6 +882,7 @@ var ChartController = class {
366
882
  this.emit("crosshairChange", payload);
367
883
  });
368
884
  this.bindPageResumeCatchUp();
885
+ this.applyPersistedIndicatorsOnInit(options);
369
886
  if (this.hasActiveSymbol()) {
370
887
  void this.bootstrap(this.loadGeneration);
371
888
  } else {
@@ -375,7 +892,9 @@ var ChartController = class {
375
892
  container;
376
893
  options;
377
894
  store;
378
- virtualWindow;
895
+ fetchPolicy;
896
+ /** Per sync-group viewport for loadMore / render slicing (active bus drives IChart APIs). */
897
+ virtualWindows = /* @__PURE__ */ new Map();
379
898
  orchestrator;
380
899
  handlers = /* @__PURE__ */ new Map();
381
900
  subscriptionId = null;
@@ -393,10 +912,20 @@ var ChartController = class {
393
912
  catchUpInFlight = false;
394
913
  lastCatchUpAt = 0;
395
914
  offPageResume = null;
915
+ /** After clearAllIndicators(); blocks Pine replot until script/features change. */
916
+ pinePlotsSuppressed = false;
917
+ chartStorage;
918
+ onPaneResize = () => {
919
+ if (this.destroyed) return;
920
+ this.orchestrator.setResizeFocusPanes(null);
921
+ };
396
922
  getFeatures() {
397
923
  return { ...this.features };
398
924
  }
399
925
  setFeatures(patch) {
926
+ if (patch.pineEnabled === true || patch.pineScript !== void 0) {
927
+ this.pinePlotsSuppressed = false;
928
+ }
400
929
  this.features = mergeChartFeatures(this.features, patch);
401
930
  this.applyFeatures();
402
931
  this.emit("featuresChange", this.getFeatures());
@@ -406,10 +935,26 @@ var ChartController = class {
406
935
  const s = this.store.symbol;
407
936
  return s.length > 0 && s !== PENDING_SYMBOL;
408
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
+ }
409
949
  applyFeatures() {
410
950
  const fetchPolicy = this.features.gaps.fillVisibleHoles ? "fill-visible-holes" : this.features.fetchPolicy;
411
- this.virtualWindow.setFetchPolicy(fetchPolicy);
951
+ this.fetchPolicy = fetchPolicy;
952
+ for (const vw of this.virtualWindows.values()) vw.setFetchPolicy(fetchPolicy);
412
953
  this.orchestrator.setIndicatorConfig(this.features.indicators);
954
+ this.orchestrator.setBarSpacingPolicy({
955
+ autoBarSpacingOnInterval: this.features.autoBarSpacingOnInterval,
956
+ barSpacingByInterval: this.features.barSpacingByInterval
957
+ });
413
958
  this.orchestrator.setSmoothPriceUpdate(
414
959
  this.features.smoothPriceUpdate,
415
960
  this.features.smoothPriceDurationMs
@@ -433,12 +978,12 @@ var ChartController = class {
433
978
  }
434
979
  this.pineIr = compiled.ir;
435
980
  if (this.hasActiveSymbol()) {
436
- const bars = this.virtualWindow.getBarsForRender();
981
+ const bars = this.activeVirtualWindow().getBarsForRender();
437
982
  if (bars.length > 0) this.applyPinePlots(bars);
438
983
  }
439
984
  }
440
985
  applyPinePlots(bars) {
441
- if (!this.features.pineEnabled || !this.pineIr) {
986
+ if (this.pinePlotsSuppressed || !this.features.pineEnabled || !this.pineIr) {
442
987
  this.orchestrator.setPinePlots(null);
443
988
  return;
444
989
  }
@@ -503,7 +1048,7 @@ var ChartController = class {
503
1048
  async applyRealtimeBar(bar, partial, loadGen = this.loadGeneration) {
504
1049
  await this.store.mergeRealtime({ bar, partial });
505
1050
  if (!this.isLoadGenerationCurrent(loadGen)) return;
506
- const bars = this.virtualWindow.getBarsForRender();
1051
+ const bars = this.activeVirtualWindow().getBarsForRender();
507
1052
  const last = bars[bars.length - 1];
508
1053
  if (last && this.features.smoothPriceUpdate) {
509
1054
  this.orchestrator.updateLastBar(last, {
@@ -570,6 +1115,21 @@ var ChartController = class {
570
1115
  this.orchestrator.resize();
571
1116
  return this;
572
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
+ }
573
1133
  setDrawingTool(tool) {
574
1134
  this.drawingManager?.setTool(tool);
575
1135
  this.syncOverlayPointerEvents();
@@ -593,27 +1153,80 @@ var ChartController = class {
593
1153
  setIndicatorConfig(config) {
594
1154
  this.features = mergeChartFeatures(this.features, { indicators: config });
595
1155
  this.orchestrator.setIndicatorConfig(config);
1156
+ if (config && this.features.indicatorPersist && this.hasActiveSymbol()) {
1157
+ saveIndicatorConfig(
1158
+ this.chartStorage,
1159
+ this.store.symbol,
1160
+ this.store.interval,
1161
+ config
1162
+ );
1163
+ }
1164
+ this.emit("featuresChange", this.getFeatures());
1165
+ }
1166
+ applyPersistedIndicatorsOnInit(options) {
1167
+ if (!this.features.indicatorPersist || !this.hasActiveSymbol()) return;
1168
+ const explicit = options.features?.indicators !== void 0 || options.indicatorConfig !== void 0;
1169
+ if (explicit) return;
1170
+ this.applyPersistedIndicatorsForContext();
1171
+ }
1172
+ applyPersistedIndicatorsForContext() {
1173
+ if (!this.features.indicatorPersist || !this.hasActiveSymbol()) return;
1174
+ const loaded = loadIndicatorConfig(
1175
+ this.chartStorage,
1176
+ this.store.symbol,
1177
+ this.store.interval
1178
+ );
1179
+ this.features = mergeChartFeatures(this.features, { indicators: loaded });
1180
+ this.orchestrator.setIndicatorConfig(loaded);
596
1181
  this.emit("featuresChange", this.getFeatures());
597
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
+ }
1197
+ clearAllIndicators() {
1198
+ const config = clearedIndicatorConfig(this.features.indicators ?? void 0);
1199
+ this.setIndicatorConfig(config);
1200
+ this.orchestrator.setPinePlots(null);
1201
+ this.pinePlotsSuppressed = true;
1202
+ return config;
1203
+ }
1204
+ clearAllDrawings() {
1205
+ return this.drawingManager?.clearAll() ?? 0;
1206
+ }
598
1207
  setReturnToCursorAfterDraw(v) {
599
1208
  this.drawingManager?.setReturnToCursorAfterDraw(v);
600
1209
  }
601
1210
  async setSymbol(symbol) {
602
1211
  const trimmed = symbol.trim();
603
1212
  if (!trimmed) return;
1213
+ this.orchestrator.setIntervalContext(this.store.interval);
604
1214
  const gen = this.beginDataContextChange();
605
1215
  await this.teardownSubscription();
606
1216
  await this.store.setSymbolInterval(trimmed, this.store.interval);
607
1217
  this.drawingManager?.setContext(symbol, this.store.interval);
1218
+ this.applyPersistedIndicatorsForContext();
608
1219
  const info = await this.resolveSymbol(symbol);
609
1220
  await this.bootstrap(gen);
610
1221
  this.emit("symbolChange", info ?? { symbol });
611
1222
  }
612
1223
  async setInterval(interval) {
1224
+ this.orchestrator.setIntervalContext(interval);
613
1225
  const gen = this.beginDataContextChange();
614
1226
  await this.teardownSubscription();
615
1227
  await this.store.setSymbolInterval(this.store.symbol, interval);
616
1228
  this.drawingManager?.setContext(this.store.symbol, interval);
1229
+ this.applyPersistedIndicatorsForContext();
617
1230
  if (this.hasActiveSymbol()) {
618
1231
  await this.bootstrap(gen);
619
1232
  }
@@ -648,7 +1261,7 @@ var ChartController = class {
648
1261
  this.orchestrator.setVisibleRange(range);
649
1262
  const { fromMs, toMs } = range;
650
1263
  if (toMs > fromMs) {
651
- this.virtualWindow.setVisibleRange({ fromMs, toMs });
1264
+ this.activeVirtualWindow().setVisibleRange({ fromMs, toMs });
652
1265
  this.visibleRangeInitialized = true;
653
1266
  }
654
1267
  return this;
@@ -720,6 +1333,7 @@ var ChartController = class {
720
1333
  this.offCrosshair?.();
721
1334
  this.offCrosshair = null;
722
1335
  this.resizeObserver.disconnect();
1336
+ window.removeEventListener("tradview:pane-resize", this.onPaneResize);
723
1337
  this.drawingManager?.destroy();
724
1338
  void this.teardownSubscription();
725
1339
  this.orchestrator.destroy();
@@ -730,7 +1344,7 @@ var ChartController = class {
730
1344
  beginDataContextChange() {
731
1345
  this.loadGeneration += 1;
732
1346
  this.visibleRangeInitialized = false;
733
- this.virtualWindow.setVisibleRange({ fromMs: 0, toMs: 0 });
1347
+ this.virtualWindows.clear();
734
1348
  this.orchestrator.resetViewState();
735
1349
  this.orchestrator.clearBars();
736
1350
  return this.loadGeneration;
@@ -742,74 +1356,86 @@ var ChartController = class {
742
1356
  if (!this.isLoadGenerationCurrent(loadGen)) return;
743
1357
  const symbol = this.store.symbol;
744
1358
  const interval = this.store.interval;
745
- const endTime = Date.now();
746
- if (this.features.protobuf) {
747
- this.emit("error", {
748
- code: "PROTOBUF_UNAVAILABLE",
749
- 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
750
1373
  });
751
- }
752
- const history = await fetchChartHistory(this.options.dataProvider, {
753
- mode: "loadMore",
754
- symbol,
755
- interval,
756
- endTime,
757
- limit: 500
758
- });
759
- if (!this.isLoadGenerationCurrent(loadGen)) return;
760
- if (this.store.symbol !== symbol || this.store.interval !== interval) return;
761
- await this.store.mergeBars(history.bars.map((bar) => ({ bar })));
762
- if (!this.isLoadGenerationCurrent(loadGen)) return;
763
- this.refreshRender(loadGen);
764
- void this.resolveSymbol(symbol).then((info) => {
765
- if (!this.isLoadGenerationCurrent(loadGen)) return;
766
- this.emit("symbolChange", info ?? { symbol });
767
- });
768
- this.emit("intervalChange", interval);
769
- const streamMode = this.features.tickStream ? "bar+tick" : this.features.streamMode;
770
- const tickOnly = streamMode === "tick";
771
- const params = {
772
- symbol,
773
- interval,
774
- channels: tickOnly ? ["tick"] : this.features.tickStream ? ["bar", "tick"] : ["bar"],
775
- streamMode
776
- };
777
- this.tickAggregator = tickOnly ? new TickAggregator(interval, (bar, partial) => {
778
1374
  if (!this.isLoadGenerationCurrent(loadGen)) return;
779
1375
  if (this.store.symbol !== symbol || this.store.interval !== interval) return;
780
- void this.applyRealtimeBar(bar, partial, loadGen);
781
- }) : null;
782
- await this.options.dataProvider.connect?.();
783
- if (!this.isLoadGenerationCurrent(loadGen)) return;
784
- const sub = await this.options.dataProvider.subscribe(params, {
785
- 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) => {
786
1387
  if (!this.isLoadGenerationCurrent(loadGen)) return;
787
- if (this.store.symbol !== symbol || this.store.interval !== interval) return;
788
- void this.applyRealtimeBar(bar, meta.partial, loadGen);
789
- },
790
- 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) => {
791
1400
  if (!this.isLoadGenerationCurrent(loadGen)) return;
792
1401
  if (this.store.symbol !== symbol || this.store.interval !== interval) return;
793
- if (this.tickAggregator) {
794
- this.tickAggregator.ingest(tick);
795
- return;
796
- }
797
- const bar = this.buildBarFromPrice(tick.price, tick.t);
798
- void this.applyRealtimeBar(bar, true, loadGen);
799
- },
800
- onConnectionChange: (state) => {
801
- this.emit("connectionChange", state);
802
- if (state === "connected" && this.subscriptionId != null) {
803
- void this.catchUpMissedBars();
804
- }
805
- },
806
- onError: (err) => this.emit("error", err)
807
- });
808
- if (!this.isLoadGenerationCurrent(loadGen)) {
809
- await this.options.dataProvider.unsubscribe(sub.id);
810
- 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");
811
1438
  }
812
- this.subscriptionId = sub.id;
813
1439
  }
814
1440
  async teardownSubscription() {
815
1441
  this.tickAggregator?.flush();
@@ -882,7 +1508,7 @@ var ChartController = class {
882
1508
  async maybeLoadMore() {
883
1509
  if (this.destroyed || this.loadingMore) return;
884
1510
  const loadGen = this.loadGeneration;
885
- const reqs = this.virtualWindow.planFetches();
1511
+ const reqs = this.activeVirtualWindow().planFetches();
886
1512
  if (reqs.length === 0) return;
887
1513
  this.loadingMore = true;
888
1514
  try {
@@ -912,19 +1538,20 @@ var ChartController = class {
912
1538
  const times = this.store.sortedTimes;
913
1539
  if (times.length === 0) return;
914
1540
  if (!this.visibleRangeInitialized) {
915
- this.virtualWindow.setVisibleRange({
1541
+ this.activeVirtualWindow().setVisibleRange({
916
1542
  fromMs: times[0],
917
1543
  toMs: times[times.length - 1]
918
1544
  });
919
1545
  this.visibleRangeInitialized = true;
920
1546
  }
921
- const bars = this.virtualWindow.getBarsForRender();
1547
+ const bars = this.activeVirtualWindow().getBarsForRender();
922
1548
  if (bars.length === 0) return;
923
1549
  const gaps = this.features.gaps.whitespace ? computeGapStartTimes(
924
1550
  bars.map((b) => b.t),
925
1551
  this.store.interval
926
1552
  ) : void 0;
927
1553
  this.orchestrator.setBars(bars, gaps);
1554
+ this.orchestrator.resizeAllPanes();
928
1555
  this.applyPinePlots(bars);
929
1556
  this.drawingManager?.redraw();
930
1557
  this.emit("visibleRangeChange", {
@@ -956,7 +1583,7 @@ function toHistoryQuery(req) {
956
1583
  }
957
1584
 
958
1585
  // src/demo-presets.ts
959
- import { DEFAULT_INDICATOR_CONFIG } from "@coderyo/indicators";
1586
+ import { DEFAULT_INDICATOR_CONFIG as DEFAULT_INDICATOR_CONFIG2 } from "@coderyo/indicators";
960
1587
  import { PINE_SAMPLE_SCRIPT } from "@coderyo/pine-lite";
961
1588
  function createDemoChartFeatures(opts) {
962
1589
  return {
@@ -964,7 +1591,7 @@ function createDemoChartFeatures(opts) {
964
1591
  streamMode: "bar+tick",
965
1592
  gaps: { whitespace: false, fillVisibleHoles: false },
966
1593
  drawings: { layer: true, persist: true },
967
- indicators: opts.indicatorConfig ?? DEFAULT_INDICATOR_CONFIG,
1594
+ indicators: opts.indicatorConfig ?? DEFAULT_INDICATOR_CONFIG2,
968
1595
  indicatorPersist: true,
969
1596
  smoothPriceUpdate: true,
970
1597
  smoothPriceDurationMs: 150,
@@ -974,7 +1601,7 @@ function createDemoChartFeatures(opts) {
974
1601
  };
975
1602
  }
976
1603
  function createDemoChartOptions(base) {
977
- const indicatorConfig = base.indicatorConfig ?? DEFAULT_INDICATOR_CONFIG;
1604
+ const indicatorConfig = base.indicatorConfig ?? DEFAULT_INDICATOR_CONFIG2;
978
1605
  return {
979
1606
  ...base,
980
1607
  features: createDemoChartFeatures({
@@ -1039,6 +1666,14 @@ function wrap(controller, beforeDestroy) {
1039
1666
  controller.resize(s);
1040
1667
  return wrap(controller, beforeDestroy);
1041
1668
  },
1669
+ setChartPaneResizeFocus: (pane) => {
1670
+ controller.setChartPaneResizeFocus(pane);
1671
+ return wrap(controller, beforeDestroy);
1672
+ },
1673
+ applyTimeScaleSyncFromLayers: (layers, pageId) => {
1674
+ controller.applyTimeScaleSyncFromLayers(layers, pageId);
1675
+ return wrap(controller, beforeDestroy);
1676
+ },
1042
1677
  setFullscreen: (e) => {
1043
1678
  controller.setFullscreen(e);
1044
1679
  return wrap(controller, beforeDestroy);
@@ -1072,6 +1707,10 @@ function wrap(controller, beforeDestroy) {
1072
1707
  controller.setIndicatorConfig(c);
1073
1708
  return wrap(controller, beforeDestroy);
1074
1709
  },
1710
+ listIndicatorLayers: () => controller.listIndicatorLayers(),
1711
+ disableIndicatorLayer: (id) => controller.disableIndicatorLayer(id),
1712
+ clearAllIndicators: () => controller.clearAllIndicators(),
1713
+ clearAllDrawings: () => controller.clearAllDrawings(),
1075
1714
  setReturnToCursorAfterDraw: (v) => {
1076
1715
  controller.setReturnToCursorAfterDraw(v);
1077
1716
  return wrap(controller, beforeDestroy);
@@ -1110,13 +1749,24 @@ function createChart(target, options) {
1110
1749
  bridge: options.bridge,
1111
1750
  chartId: options.chartId,
1112
1751
  outboundEvents: options.bridgeOutboundEvents,
1113
- crosshairThrottleMs: options.bridgeCrosshairThrottleMs
1752
+ crosshairThrottleMs: options.bridgeCrosshairThrottleMs,
1753
+ layerBridge: options.layerBridge
1114
1754
  });
1115
1755
  }
1116
1756
  return chart;
1117
1757
  }
1118
1758
 
1119
1759
  // src/index.ts
1760
+ import {
1761
+ DEFAULT_INDICATOR_CONFIG as DEFAULT_INDICATOR_CONFIG3,
1762
+ clearedIndicatorConfig as clearedIndicatorConfig2,
1763
+ hasVisibleIndicatorPanes,
1764
+ hasMainChartOverlays,
1765
+ hasAnyActiveIndicators,
1766
+ indicatorConfigStorageKey as indicatorConfigStorageKey2,
1767
+ listActiveIndicatorLayers as listActiveIndicatorLayers2,
1768
+ disableIndicatorLayer
1769
+ } from "@coderyo/indicators";
1120
1770
  import {
1121
1771
  compilePineLite as compilePineLite2,
1122
1772
  runPineLite,
@@ -1126,17 +1776,39 @@ import {
1126
1776
  export {
1127
1777
  ChartController,
1128
1778
  DEFAULT_CHART_FEATURES,
1779
+ DEFAULT_INDICATOR_CONFIG3 as DEFAULT_INDICATOR_CONFIG,
1129
1780
  PENDING_SYMBOL,
1130
1781
  PINE_EDITOR_DEFAULT,
1131
1782
  PINE_SAMPLE_SCRIPT2 as PINE_SAMPLE_SCRIPT,
1132
1783
  TRADVIEW_API_VERSION,
1133
1784
  TRADVIEW_VERSION,
1785
+ clearLayerBridgeVisitedPages,
1786
+ clearedIndicatorConfig2 as clearedIndicatorConfig,
1134
1787
  compilePineLite2 as compilePineLite,
1135
1788
  createChart,
1136
1789
  createDemoChartFeatures,
1137
1790
  createDemoChartOptions,
1791
+ createLocalChartStorage,
1792
+ defaultChartStorage,
1793
+ disableIndicatorLayer,
1794
+ handleLayerBridgeMessage,
1795
+ hasAnyActiveIndicators,
1796
+ hasLayerBridgeRegistration,
1797
+ hasMainChartOverlays,
1798
+ hasVisibleIndicatorPanes,
1799
+ indicatorConfigStorageKey2 as indicatorConfigStorageKey,
1800
+ isValidLayerBridgePane,
1801
+ listActiveIndicatorLayers2 as listActiveIndicatorLayers,
1802
+ loadIndicatorConfig,
1803
+ mergeLayerBridgePreset,
1804
+ registerChartLayerBridge,
1138
1805
  resolveChartFeatures,
1806
+ resolvePaneLayerIds,
1807
+ resolvePaneSyncGroupsForBridge,
1808
+ resolvePaneSyncGroupsFromLayers,
1139
1809
  runPineLite,
1810
+ saveIndicatorConfig,
1811
+ unregisterChartLayerBridge,
1140
1812
  wireChartBridge
1141
1813
  };
1142
1814
  //# sourceMappingURL=index.js.map