@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.d.ts +132 -8
- package/dist/index.js +632 -95
- package/dist/index.js.map +1 -1
- package/package.json +21 -20
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.
|
|
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")
|
|
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")
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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 (
|
|
838
|
+
if (this.destroyed) return;
|
|
839
|
+
this.orchestrator.setResizeFocusPanes(null);
|
|
406
840
|
});
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
this.
|
|
874
|
-
|
|
875
|
-
|
|
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
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
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
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
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
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
void
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|