@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.d.ts +164 -7
- package/dist/index.js +772 -100
- package/dist/index.js.map +1 -1
- package/package.json +21 -20
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
|
|
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")
|
|
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")
|
|
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 {
|
|
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
|
-
|
|
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 (
|
|
838
|
+
if (this.destroyed) return;
|
|
839
|
+
this.orchestrator.setResizeFocusPanes(null);
|
|
330
840
|
});
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
746
|
-
|
|
747
|
-
this.
|
|
748
|
-
|
|
749
|
-
|
|
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
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
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
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
void
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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 ??
|
|
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 ??
|
|
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
|