react-klinecharts-ui 1.0.0 → 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/README.md +179 -0
- package/dist/chart.cjs +70 -0
- package/dist/chart.cjs.map +1 -0
- package/dist/chart.d.cts +45 -0
- package/dist/chart.d.ts +45 -0
- package/dist/chart.js +68 -0
- package/dist/chart.js.map +1 -0
- package/dist/chunk-F24D6PWY.cjs +98 -0
- package/dist/chunk-F24D6PWY.cjs.map +1 -0
- package/dist/chunk-O7E57I66.js +92 -0
- package/dist/chunk-O7E57I66.js.map +1 -0
- package/dist/chunk-PZ5AY32C.js +9 -0
- package/dist/chunk-PZ5AY32C.js.map +1 -0
- package/dist/chunk-Q7SFCCGT.cjs +11 -0
- package/dist/chunk-Q7SFCCGT.cjs.map +1 -0
- package/dist/{chunk-JD4NBJ5F.js → chunk-RIT2LJQT.js} +4 -9
- package/dist/chunk-RIT2LJQT.js.map +1 -0
- package/dist/{chunk-LGYYJ2GP.cjs → chunk-SZDU2D3D.cjs} +5 -10
- package/dist/chunk-SZDU2D3D.cjs.map +1 -0
- package/dist/extensions.cjs +10 -9
- package/dist/extensions.js +2 -1
- package/dist/index.cjs +459 -211
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +233 -7
- package/dist/index.d.ts +233 -7
- package/dist/index.js +361 -132
- package/dist/index.js.map +1 -1
- package/package.json +19 -2
- package/dist/chunk-JD4NBJ5F.js.map +0 -1
- package/dist/chunk-LGYYJ2GP.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,31 +1,12 @@
|
|
|
1
|
-
import { registerExtensions, TA_default, ensureAlertLineRegistered } from './chunk-
|
|
2
|
-
export { TA_default as TA, abcd_default as abcd, alertLine_default as alertLine, anyWaves_default as anyWaves, arrow_default as arrow, bollTv_default as bollTv, brush_default as brush, cci_default as cci, circle_default as circle, depthOverlay_default as depthOverlay, eightWaves_default as eightWaves, elliottWave_default as elliottWave, fibRetracement_default as fibRetracement, fibonacciCircle_default as fibonacciCircle, fibonacciExtension_default as fibonacciExtension, fibonacciSegment_default as fibonacciSegment, fibonacciSpeedResistanceFan_default as fibonacciSpeedResistanceFan, fibonacciSpiral_default as fibonacciSpiral, fiveWaves_default as fiveWaves, gannBox_default as gannBox, gannFan_default as gannFan, hma_default as hma, ichimoku_default as ichimoku, indicators, longPosition_default as longPosition, maRibbon_default as maRibbon, macdTv_default as macdTv, measure_default as measure, orderLine_default as orderLine, overlays, parallelChannel_default as parallelChannel, parallelogram_default as parallelogram, pivotPoints_default as pivotPoints, ray_default as ray, rect_default as rect, registerExtensions, rsiTv_default as rsiTv, shortPosition_default as shortPosition, stochastic_default as stochastic, superTrend_default as superTrend, threeWaves_default as threeWaves, triangle_default as triangle, vwap_default as vwap, xabcd_default as xabcd } from './chunk-
|
|
3
|
-
import {
|
|
1
|
+
import { registerExtensions, TA_default, ensureAlertLineRegistered } from './chunk-RIT2LJQT.js';
|
|
2
|
+
export { TA_default as TA, abcd_default as abcd, alertLine_default as alertLine, anyWaves_default as anyWaves, arrow_default as arrow, bollTv_default as bollTv, brush_default as brush, cci_default as cci, circle_default as circle, depthOverlay_default as depthOverlay, eightWaves_default as eightWaves, elliottWave_default as elliottWave, fibRetracement_default as fibRetracement, fibonacciCircle_default as fibonacciCircle, fibonacciExtension_default as fibonacciExtension, fibonacciSegment_default as fibonacciSegment, fibonacciSpeedResistanceFan_default as fibonacciSpeedResistanceFan, fibonacciSpiral_default as fibonacciSpiral, fiveWaves_default as fiveWaves, gannBox_default as gannBox, gannFan_default as gannFan, hma_default as hma, ichimoku_default as ichimoku, indicators, longPosition_default as longPosition, maRibbon_default as maRibbon, macdTv_default as macdTv, measure_default as measure, orderLine_default as orderLine, overlays, parallelChannel_default as parallelChannel, parallelogram_default as parallelogram, pivotPoints_default as pivotPoints, ray_default as ray, rect_default as rect, registerExtensions, rsiTv_default as rsiTv, shortPosition_default as shortPosition, stochastic_default as stochastic, superTrend_default as superTrend, threeWaves_default as threeWaves, triangle_default as triangle, vwap_default as vwap, xabcd_default as xabcd } from './chunk-RIT2LJQT.js';
|
|
3
|
+
import { KlinechartsUIStateContext, KlinechartsUIDispatchContext, useKlinechartsUI, useKlinechartsUIDispatch } from './chunk-O7E57I66.js';
|
|
4
|
+
export { KlinechartsUIDispatchContext, KlinechartsUIStateContext, createDataLoader, useKlinechartsUI } from './chunk-O7E57I66.js';
|
|
5
|
+
import './chunk-PZ5AY32C.js';
|
|
6
|
+
import { createContext, useMemo, useReducer, useRef, useEffect, useCallback, useState, useId, useContext } from 'react';
|
|
4
7
|
import { registerOverlay, registerHotkey, getHotkey, getSupportedHotkeys, registerIndicator } from 'klinecharts';
|
|
5
8
|
import { jsx } from 'react/jsx-runtime';
|
|
6
9
|
|
|
7
|
-
var KlinechartsUIStateContext = createContext(null);
|
|
8
|
-
var KlinechartsUIDispatchContext = createContext(null);
|
|
9
|
-
function useKlinechartsUIDispatch() {
|
|
10
|
-
const ctx = useContext(KlinechartsUIDispatchContext);
|
|
11
|
-
if (!ctx) {
|
|
12
|
-
throw new Error(
|
|
13
|
-
"useKlinechartsUI must be used within a <KlinechartsUIProvider>"
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
return ctx;
|
|
17
|
-
}
|
|
18
|
-
function useKlinechartsUI() {
|
|
19
|
-
const state = useContext(KlinechartsUIStateContext);
|
|
20
|
-
const dispatch = useContext(KlinechartsUIDispatchContext);
|
|
21
|
-
if (!state || !dispatch) {
|
|
22
|
-
throw new Error(
|
|
23
|
-
"useKlinechartsUI must be used within a <KlinechartsUIProvider>"
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
return { state, ...dispatch };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
10
|
// src/data/periods.ts
|
|
30
11
|
var DEFAULT_PERIODS = [
|
|
31
12
|
{ span: 1, type: "minute", label: "1m" },
|
|
@@ -39,6 +20,50 @@ var DEFAULT_PERIODS = [
|
|
|
39
20
|
{ span: 1, type: "month", label: "M" },
|
|
40
21
|
{ span: 1, type: "year", label: "Y" }
|
|
41
22
|
];
|
|
23
|
+
|
|
24
|
+
// src/storage/types.ts
|
|
25
|
+
var DEFAULT_STORAGE_NAMESPACES = [
|
|
26
|
+
"alerts",
|
|
27
|
+
"settings",
|
|
28
|
+
"indicators"
|
|
29
|
+
];
|
|
30
|
+
var DEFAULT_STORAGE_KEY_PREFIX = "rkui:";
|
|
31
|
+
|
|
32
|
+
// src/storage/createDefaultStorage.ts
|
|
33
|
+
var NOOP_ADAPTER = {
|
|
34
|
+
getItem: () => null,
|
|
35
|
+
setItem: () => {
|
|
36
|
+
},
|
|
37
|
+
removeItem: () => {
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
function createDefaultStorage() {
|
|
41
|
+
if (typeof localStorage === "undefined") return NOOP_ADAPTER;
|
|
42
|
+
try {
|
|
43
|
+
const probe = localStorage.getItem("__rkui_probe__");
|
|
44
|
+
return probe === null ? localStorage : localStorage;
|
|
45
|
+
} catch {
|
|
46
|
+
return NOOP_ADAPTER;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function resolveStorage(options) {
|
|
50
|
+
const adapter = options?.adapter ?? createDefaultStorage();
|
|
51
|
+
const keyPrefix = options?.keyPrefix ?? DEFAULT_STORAGE_KEY_PREFIX;
|
|
52
|
+
const namespaces = new Set(
|
|
53
|
+
options?.namespaces ?? DEFAULT_STORAGE_NAMESPACES
|
|
54
|
+
);
|
|
55
|
+
return {
|
|
56
|
+
adapter,
|
|
57
|
+
namespaces,
|
|
58
|
+
keyPrefix,
|
|
59
|
+
key(namespace) {
|
|
60
|
+
return `${keyPrefix}${namespace}`;
|
|
61
|
+
},
|
|
62
|
+
persists(namespace) {
|
|
63
|
+
return namespaces.has(namespace);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
42
67
|
function reducer(state, action) {
|
|
43
68
|
switch (action.type) {
|
|
44
69
|
case "SET_CHART":
|
|
@@ -102,13 +127,16 @@ function KlinechartsUIProvider({
|
|
|
102
127
|
defaultPeriod,
|
|
103
128
|
defaultTheme = "light",
|
|
104
129
|
defaultTimezone = "Asia/Shanghai",
|
|
105
|
-
|
|
106
|
-
|
|
130
|
+
// NOTE: no destructuring default here — the init function must distinguish
|
|
131
|
+
// "consumer passed default*" (wins) from "use stored value" (hydrate).
|
|
132
|
+
defaultMainIndicators,
|
|
133
|
+
defaultSubIndicators,
|
|
107
134
|
defaultLocale = "en-US",
|
|
108
135
|
periods,
|
|
109
136
|
styles,
|
|
110
137
|
registerExtensions: shouldRegister = true,
|
|
111
138
|
overlays: extraOverlays,
|
|
139
|
+
storage: storageOptions,
|
|
112
140
|
children,
|
|
113
141
|
onStateChange,
|
|
114
142
|
onSymbolChange,
|
|
@@ -119,6 +147,11 @@ function KlinechartsUIProvider({
|
|
|
119
147
|
onSubIndicatorsChange,
|
|
120
148
|
onSettingsChange
|
|
121
149
|
}) {
|
|
150
|
+
const resolvedStorage = useMemo(
|
|
151
|
+
() => storageOptions ? resolveStorage(storageOptions) : null,
|
|
152
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
153
|
+
[]
|
|
154
|
+
);
|
|
122
155
|
const [state, dispatch] = useReducer(
|
|
123
156
|
reducer,
|
|
124
157
|
{
|
|
@@ -131,41 +164,61 @@ function KlinechartsUIProvider({
|
|
|
131
164
|
defaultSubIndicators,
|
|
132
165
|
defaultLocale,
|
|
133
166
|
periods,
|
|
134
|
-
styles
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
styles,
|
|
168
|
+
storage: resolvedStorage
|
|
169
|
+
},
|
|
170
|
+
(opts) => {
|
|
171
|
+
const s = opts.storage;
|
|
172
|
+
const read = (ns, fallback) => {
|
|
173
|
+
if (!s || !s.persists(ns)) return fallback;
|
|
174
|
+
try {
|
|
175
|
+
const raw = s.adapter.getItem(s.key(ns));
|
|
176
|
+
return raw ? JSON.parse(raw) : fallback;
|
|
177
|
+
} catch {
|
|
178
|
+
return fallback;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const storedIndicators = read("indicators", {});
|
|
182
|
+
return {
|
|
183
|
+
chart: null,
|
|
184
|
+
datafeed: opts.datafeed,
|
|
185
|
+
symbol: opts.defaultSymbol ?? null,
|
|
186
|
+
period: opts.defaultPeriod ?? (opts.periods ?? DEFAULT_PERIODS)[0],
|
|
187
|
+
theme: opts.defaultTheme ?? "light",
|
|
188
|
+
timezone: opts.defaultTimezone ?? "Asia/Shanghai",
|
|
189
|
+
isLoading: false,
|
|
190
|
+
locale: opts.defaultLocale ?? "en-US",
|
|
191
|
+
periods: opts.periods ?? DEFAULT_PERIODS,
|
|
192
|
+
// Persistence semantics: STORED values win over `default*` props —
|
|
193
|
+
// the user's saved configuration should survive reload even when the
|
|
194
|
+
// app passes defaults. Only when storage is empty/unconfigured do the
|
|
195
|
+
// `default*` props apply, falling back to the built-ins last.
|
|
196
|
+
// (Settings live in useKlinechartsUISettings useState and hydrate
|
|
197
|
+
// separately via the dispatch-context `storage`.)
|
|
198
|
+
mainIndicators: storedIndicators.main ?? opts.defaultMainIndicators ?? ["MA"],
|
|
199
|
+
subIndicators: storedIndicators.sub ?? (opts.defaultSubIndicators ? opts.defaultSubIndicators.reduce(
|
|
200
|
+
(acc, name) => ({ ...acc, [name]: "" }),
|
|
201
|
+
{}
|
|
202
|
+
) : { VOL: "" }),
|
|
203
|
+
indicatorAxes: storedIndicators.axes ?? {},
|
|
204
|
+
indicatorVisibility: storedIndicators.visibility ?? {},
|
|
205
|
+
alerts: read("alerts", []),
|
|
206
|
+
measure: { isActive: false, fromPoint: null, result: null },
|
|
207
|
+
replay: {
|
|
208
|
+
isReplaying: false,
|
|
209
|
+
isPaused: false,
|
|
210
|
+
speed: 1,
|
|
211
|
+
barIndex: 0,
|
|
212
|
+
totalBars: 0
|
|
213
|
+
},
|
|
214
|
+
styles: opts.styles,
|
|
215
|
+
screenshotUrl: null
|
|
216
|
+
};
|
|
217
|
+
}
|
|
165
218
|
);
|
|
166
219
|
const fullscreenContainerRef = useRef(null);
|
|
167
220
|
const undoRedoListenerRef = useRef(null);
|
|
168
|
-
const
|
|
221
|
+
const alertTriggeredListenersRef = useRef(/* @__PURE__ */ new Set());
|
|
169
222
|
const replayIntervalRef = useRef(null);
|
|
170
223
|
const replaySavedDataRef = useRef([]);
|
|
171
224
|
const replayIndexRef = useRef(0);
|
|
@@ -243,23 +296,53 @@ function KlinechartsUIProvider({
|
|
|
243
296
|
const chart = state.chart;
|
|
244
297
|
if (!chart || !hasAlerts) return;
|
|
245
298
|
alertPrevCloseRef.current = null;
|
|
299
|
+
const prevValueByAlert = /* @__PURE__ */ new Map();
|
|
246
300
|
const interval = setInterval(() => {
|
|
247
301
|
const dataList = chart.getDataList();
|
|
248
302
|
if (!dataList || dataList.length === 0) return;
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
303
|
+
const lastIdx = dataList.length - 1;
|
|
304
|
+
const indicatorCache = /* @__PURE__ */ new Map();
|
|
305
|
+
const readIndicatorValue = (indicatorId, figureKey) => {
|
|
306
|
+
const cacheKey = `${indicatorId}:${figureKey}`;
|
|
307
|
+
if (indicatorCache.has(cacheKey)) return indicatorCache.get(cacheKey);
|
|
308
|
+
let value = null;
|
|
309
|
+
try {
|
|
310
|
+
const indicators2 = chart.getIndicators({ id: indicatorId });
|
|
311
|
+
const ind = indicators2?.[0];
|
|
312
|
+
const result = ind?.result;
|
|
313
|
+
const last = Array.isArray(result) ? result[lastIdx] : null;
|
|
314
|
+
if (last && typeof last === "object") {
|
|
315
|
+
const v = last[figureKey];
|
|
316
|
+
value = typeof v === "number" ? v : null;
|
|
317
|
+
}
|
|
318
|
+
} catch {
|
|
319
|
+
value = null;
|
|
320
|
+
}
|
|
321
|
+
indicatorCache.set(cacheKey, value);
|
|
322
|
+
return value;
|
|
323
|
+
};
|
|
253
324
|
const alerts = stateRef.current.alerts;
|
|
254
325
|
const triggeredIds = [];
|
|
255
326
|
for (const alert of alerts) {
|
|
256
327
|
if (alert.triggered) continue;
|
|
257
|
-
const
|
|
258
|
-
|
|
328
|
+
const target = alert.target ?? { type: "price" };
|
|
329
|
+
let currentValue;
|
|
330
|
+
if (target.type === "indicator") {
|
|
331
|
+
currentValue = readIndicatorValue(target.indicatorId, target.figureKey);
|
|
332
|
+
} else {
|
|
333
|
+
currentValue = dataList[lastIdx].close;
|
|
334
|
+
}
|
|
335
|
+
if (currentValue === null || !Number.isFinite(currentValue)) continue;
|
|
336
|
+
const prevValue = prevValueByAlert.get(alert.id) ?? null;
|
|
337
|
+
prevValueByAlert.set(alert.id, currentValue);
|
|
338
|
+
if (prevValue === null) continue;
|
|
339
|
+
const crossedUp = prevValue < alert.price && currentValue >= alert.price;
|
|
340
|
+
const crossedDown = prevValue > alert.price && currentValue <= alert.price;
|
|
259
341
|
const shouldTrigger = alert.condition === "crossing_up" ? crossedUp : alert.condition === "crossing_down" ? crossedDown : crossedUp || crossedDown;
|
|
260
342
|
if (shouldTrigger) {
|
|
261
343
|
triggeredIds.push(alert.id);
|
|
262
|
-
|
|
344
|
+
const fired = { ...alert, triggered: true };
|
|
345
|
+
alertTriggeredListenersRef.current.forEach((cb) => cb(fired));
|
|
263
346
|
}
|
|
264
347
|
}
|
|
265
348
|
if (triggeredIds.length > 0) {
|
|
@@ -274,6 +357,36 @@ function KlinechartsUIProvider({
|
|
|
274
357
|
useEffect(() => {
|
|
275
358
|
alertPrevCloseRef.current = null;
|
|
276
359
|
}, [state.symbol, state.period]);
|
|
360
|
+
const writeNs = useCallback(
|
|
361
|
+
(ns, value) => {
|
|
362
|
+
if (!resolvedStorage || !resolvedStorage.persists(ns)) return;
|
|
363
|
+
try {
|
|
364
|
+
resolvedStorage.adapter.setItem(
|
|
365
|
+
resolvedStorage.key(ns),
|
|
366
|
+
JSON.stringify(value)
|
|
367
|
+
);
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
370
|
+
},
|
|
371
|
+
[resolvedStorage]
|
|
372
|
+
);
|
|
373
|
+
useEffect(() => {
|
|
374
|
+
writeNs("alerts", state.alerts);
|
|
375
|
+
}, [state.alerts, writeNs]);
|
|
376
|
+
useEffect(() => {
|
|
377
|
+
writeNs("indicators", {
|
|
378
|
+
main: state.mainIndicators,
|
|
379
|
+
sub: state.subIndicators,
|
|
380
|
+
axes: state.indicatorAxes,
|
|
381
|
+
visibility: state.indicatorVisibility
|
|
382
|
+
});
|
|
383
|
+
}, [
|
|
384
|
+
state.mainIndicators,
|
|
385
|
+
state.subIndicators,
|
|
386
|
+
state.indicatorAxes,
|
|
387
|
+
state.indicatorVisibility,
|
|
388
|
+
writeNs
|
|
389
|
+
]);
|
|
277
390
|
useEffect(() => {
|
|
278
391
|
return () => {
|
|
279
392
|
if (replayIntervalRef.current !== null) {
|
|
@@ -289,13 +402,14 @@ function KlinechartsUIProvider({
|
|
|
289
402
|
onSettingsChange: onSettingsChangeRef.current,
|
|
290
403
|
fullscreenContainerRef,
|
|
291
404
|
undoRedoListenerRef,
|
|
292
|
-
|
|
405
|
+
alertTriggeredListenersRef,
|
|
293
406
|
replayIntervalRef,
|
|
294
407
|
replaySavedDataRef,
|
|
295
|
-
replayIndexRef
|
|
408
|
+
replayIndexRef,
|
|
409
|
+
storage: resolvedStorage
|
|
296
410
|
}),
|
|
297
411
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
298
|
-
[enhancedDispatch]
|
|
412
|
+
[enhancedDispatch, resolvedStorage]
|
|
299
413
|
);
|
|
300
414
|
return /* @__PURE__ */ jsx(KlinechartsUIStateContext.Provider, { value: state, children: /* @__PURE__ */ jsx(KlinechartsUIDispatchContext.Provider, { value: dispatchValue, children }) });
|
|
301
415
|
}
|
|
@@ -1437,7 +1551,17 @@ var defaultSettings = {
|
|
|
1437
1551
|
};
|
|
1438
1552
|
function useKlinechartsUISettings() {
|
|
1439
1553
|
const { state, onSettingsChange } = useKlinechartsUI();
|
|
1440
|
-
const
|
|
1554
|
+
const { storage } = useKlinechartsUIDispatch();
|
|
1555
|
+
const [settings, setSettings] = useState(() => {
|
|
1556
|
+
if (storage && storage.persists("settings")) {
|
|
1557
|
+
try {
|
|
1558
|
+
const raw = storage.adapter.getItem(storage.key("settings"));
|
|
1559
|
+
if (raw) return { ...defaultSettings, ...JSON.parse(raw) };
|
|
1560
|
+
} catch {
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
return defaultSettings;
|
|
1564
|
+
});
|
|
1441
1565
|
const isInitialMount = useRef(true);
|
|
1442
1566
|
useEffect(() => {
|
|
1443
1567
|
if (isInitialMount.current) {
|
|
@@ -1446,6 +1570,14 @@ function useKlinechartsUISettings() {
|
|
|
1446
1570
|
}
|
|
1447
1571
|
onSettingsChange?.({ ...settings });
|
|
1448
1572
|
}, [settings, onSettingsChange]);
|
|
1573
|
+
useEffect(() => {
|
|
1574
|
+
if (!storage || !storage.persists("settings")) return;
|
|
1575
|
+
if (isInitialMount.current) return;
|
|
1576
|
+
try {
|
|
1577
|
+
storage.adapter.setItem(storage.key("settings"), JSON.stringify(settings));
|
|
1578
|
+
} catch {
|
|
1579
|
+
}
|
|
1580
|
+
}, [settings, storage]);
|
|
1449
1581
|
const candleTypes = useMemo(
|
|
1450
1582
|
() => CANDLE_TYPES.map((ct) => ({
|
|
1451
1583
|
key: ct.key,
|
|
@@ -2788,10 +2920,10 @@ function useCrosshair() {
|
|
|
2788
2920
|
var alertCounter = 0;
|
|
2789
2921
|
function useAlerts() {
|
|
2790
2922
|
const { state, dispatch } = useKlinechartsUI();
|
|
2791
|
-
const {
|
|
2923
|
+
const { alertTriggeredListenersRef } = useKlinechartsUIDispatch();
|
|
2792
2924
|
const alerts = state.alerts;
|
|
2793
2925
|
const addAlert = useCallback(
|
|
2794
|
-
(price, condition, message, extendData) => {
|
|
2926
|
+
(price, condition, message, extendData, target) => {
|
|
2795
2927
|
const id = `alert_${++alertCounter}`;
|
|
2796
2928
|
const precision = state.chart?.getSymbol()?.pricePrecision ?? 2;
|
|
2797
2929
|
const resolvedExtendData = {
|
|
@@ -2804,7 +2936,10 @@ function useAlerts() {
|
|
|
2804
2936
|
condition,
|
|
2805
2937
|
message,
|
|
2806
2938
|
triggered: false,
|
|
2807
|
-
extendData: resolvedExtendData
|
|
2939
|
+
extendData: resolvedExtendData,
|
|
2940
|
+
// Persist the target only when it's an indicator alert; omit for the
|
|
2941
|
+
// default price target so existing serialized alerts stay compatible.
|
|
2942
|
+
...target && target.type === "indicator" ? { target } : {}
|
|
2808
2943
|
};
|
|
2809
2944
|
dispatch({ type: "ADD_ALERT", alert });
|
|
2810
2945
|
if (state.chart) {
|
|
@@ -2835,9 +2970,13 @@ function useAlerts() {
|
|
|
2835
2970
|
}, [state.chart, dispatch]);
|
|
2836
2971
|
const onAlertTriggered = useCallback(
|
|
2837
2972
|
(callback) => {
|
|
2838
|
-
|
|
2973
|
+
const set = alertTriggeredListenersRef.current;
|
|
2974
|
+
set.add(callback);
|
|
2975
|
+
return () => {
|
|
2976
|
+
set.delete(callback);
|
|
2977
|
+
};
|
|
2839
2978
|
},
|
|
2840
|
-
[
|
|
2979
|
+
[alertTriggeredListenersRef]
|
|
2841
2980
|
);
|
|
2842
2981
|
return {
|
|
2843
2982
|
alerts,
|
|
@@ -3441,71 +3580,161 @@ function useAnnotations() {
|
|
|
3441
3580
|
clearAnnotations
|
|
3442
3581
|
};
|
|
3443
3582
|
}
|
|
3583
|
+
var WorkspaceContext = createContext(null);
|
|
3584
|
+
function useWorkspace() {
|
|
3585
|
+
const ctx = useContext(WorkspaceContext);
|
|
3586
|
+
if (!ctx) {
|
|
3587
|
+
throw new Error("useWorkspace must be used within a <WorkspaceProvider>");
|
|
3588
|
+
}
|
|
3589
|
+
return ctx;
|
|
3590
|
+
}
|
|
3444
3591
|
|
|
3445
|
-
// src/
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3592
|
+
// src/workspace/types.ts
|
|
3593
|
+
var DEFAULT_SYNC_CONFIG = {
|
|
3594
|
+
crosshair: true,
|
|
3595
|
+
scroll: true,
|
|
3596
|
+
zoom: true,
|
|
3597
|
+
symbol: true,
|
|
3598
|
+
period: true
|
|
3599
|
+
};
|
|
3600
|
+
function reducer2(state, action) {
|
|
3601
|
+
switch (action.type) {
|
|
3602
|
+
case "SET_CELLS":
|
|
3603
|
+
return { ...state, cells: action.cells };
|
|
3604
|
+
case "ADD_CELL":
|
|
3605
|
+
return { ...state, cells: [...state.cells, action.cell] };
|
|
3606
|
+
case "REMOVE_CELL": {
|
|
3607
|
+
const cells = state.cells.filter((c) => c.id !== action.id);
|
|
3608
|
+
return {
|
|
3609
|
+
cells,
|
|
3610
|
+
activeCellId: state.activeCellId === action.id ? cells[0]?.id ?? null : state.activeCellId
|
|
3611
|
+
};
|
|
3612
|
+
}
|
|
3613
|
+
case "SET_CELL_SYMBOL":
|
|
3614
|
+
return {
|
|
3615
|
+
...state,
|
|
3616
|
+
cells: state.cells.map(
|
|
3617
|
+
(c) => c.id === action.id ? { ...c, symbol: action.symbol } : c
|
|
3618
|
+
)
|
|
3619
|
+
};
|
|
3620
|
+
case "SET_CELL_PERIOD":
|
|
3621
|
+
return {
|
|
3622
|
+
...state,
|
|
3623
|
+
cells: state.cells.map(
|
|
3624
|
+
(c) => c.id === action.id ? { ...c, period: action.period } : c
|
|
3625
|
+
)
|
|
3626
|
+
};
|
|
3627
|
+
case "SET_ACTIVE_CELL":
|
|
3628
|
+
return { ...state, activeCellId: action.id };
|
|
3629
|
+
default:
|
|
3630
|
+
return state;
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
function WorkspaceProvider({
|
|
3634
|
+
defaultCells,
|
|
3635
|
+
sync,
|
|
3636
|
+
children
|
|
3637
|
+
}) {
|
|
3638
|
+
const [state, dispatch] = useReducer(reducer2, defaultCells, (cells) => ({
|
|
3639
|
+
cells,
|
|
3640
|
+
activeCellId: cells[0]?.id ?? null
|
|
3641
|
+
}));
|
|
3642
|
+
const chartsRef = useRef(/* @__PURE__ */ new Map());
|
|
3643
|
+
const broadcastingRef = useRef(false);
|
|
3644
|
+
const resolvedSync = useMemo(
|
|
3645
|
+
() => ({ ...DEFAULT_SYNC_CONFIG, ...sync }),
|
|
3646
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3647
|
+
[]
|
|
3648
|
+
);
|
|
3649
|
+
const value = useMemo(
|
|
3650
|
+
() => ({
|
|
3651
|
+
state,
|
|
3652
|
+
dispatch,
|
|
3653
|
+
chartsRef,
|
|
3654
|
+
broadcastingRef,
|
|
3655
|
+
sync: resolvedSync
|
|
3656
|
+
}),
|
|
3657
|
+
[state, resolvedSync]
|
|
3658
|
+
);
|
|
3659
|
+
return /* @__PURE__ */ jsx(WorkspaceContext.Provider, { value, children });
|
|
3660
|
+
}
|
|
3661
|
+
function useChartSync({ cellId }) {
|
|
3662
|
+
const { chartsRef, broadcastingRef, sync, dispatch: workspaceDispatch } = useWorkspace();
|
|
3663
|
+
const { state } = useKlinechartsUI();
|
|
3664
|
+
const chart = state.chart;
|
|
3665
|
+
useEffect(() => {
|
|
3666
|
+
if (!chart) return;
|
|
3667
|
+
chartsRef.current.set(cellId, chart);
|
|
3668
|
+
return () => {
|
|
3669
|
+
chartsRef.current.delete(cellId);
|
|
3670
|
+
};
|
|
3671
|
+
}, [chart, cellId, chartsRef]);
|
|
3672
|
+
useEffect(() => {
|
|
3673
|
+
if (!chart) return;
|
|
3674
|
+
const broadcast = (fn) => {
|
|
3675
|
+
if (broadcastingRef.current) return;
|
|
3676
|
+
broadcastingRef.current = true;
|
|
3451
3677
|
try {
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
|
|
3458
|
-
{ ...params.period, label: "" },
|
|
3459
|
-
0,
|
|
3460
|
-
Date.now()
|
|
3461
|
-
);
|
|
3462
|
-
if (gen !== currentGen) return;
|
|
3463
|
-
if (data.length > 0) {
|
|
3464
|
-
oldestTimestamp = data[0].timestamp;
|
|
3465
|
-
}
|
|
3466
|
-
params.callback(data, {
|
|
3467
|
-
forward: data.length > 0,
|
|
3468
|
-
backward: false
|
|
3469
|
-
});
|
|
3470
|
-
} else if (params.type === "forward" && oldestTimestamp !== null) {
|
|
3471
|
-
const gen = currentGen;
|
|
3472
|
-
const data = await datafeed.getHistoryKLineData(
|
|
3473
|
-
params.symbol,
|
|
3474
|
-
{ ...params.period, label: "" },
|
|
3475
|
-
0,
|
|
3476
|
-
oldestTimestamp - 1
|
|
3477
|
-
);
|
|
3478
|
-
if (gen !== currentGen) return;
|
|
3479
|
-
if (data.length > 0) {
|
|
3480
|
-
oldestTimestamp = data[0].timestamp;
|
|
3678
|
+
chartsRef.current.forEach((target, id) => {
|
|
3679
|
+
if (id !== cellId) {
|
|
3680
|
+
try {
|
|
3681
|
+
fn(target);
|
|
3682
|
+
} catch {
|
|
3683
|
+
}
|
|
3481
3684
|
}
|
|
3482
|
-
|
|
3483
|
-
forward: data.length > 0,
|
|
3484
|
-
backward: false
|
|
3485
|
-
});
|
|
3486
|
-
} else if (params.type === "backward") {
|
|
3487
|
-
params.callback([], { forward: false, backward: false });
|
|
3488
|
-
}
|
|
3489
|
-
} catch (error) {
|
|
3490
|
-
console.error("Failed to load chart data:", error);
|
|
3491
|
-
params.callback([], { forward: false, backward: false });
|
|
3685
|
+
});
|
|
3492
3686
|
} finally {
|
|
3493
|
-
|
|
3687
|
+
broadcastingRef.current = false;
|
|
3494
3688
|
}
|
|
3495
|
-
}
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3689
|
+
};
|
|
3690
|
+
const onCrosshairChange = (data) => {
|
|
3691
|
+
if (!sync.crosshair) return;
|
|
3692
|
+
const crosshair = data;
|
|
3693
|
+
if (!crosshair) return;
|
|
3694
|
+
broadcast((target) => target.executeAction("onCrosshairChange", crosshair));
|
|
3695
|
+
};
|
|
3696
|
+
const onScroll = () => {
|
|
3697
|
+
if (!sync.scroll) return;
|
|
3698
|
+
let range = null;
|
|
3699
|
+
try {
|
|
3700
|
+
range = chart.getVisibleRange();
|
|
3701
|
+
} catch {
|
|
3702
|
+
return;
|
|
3703
|
+
}
|
|
3704
|
+
if (!range || range.realTo == null) return;
|
|
3705
|
+
broadcast(
|
|
3706
|
+
(target) => target.scrollToTimestamp(range.realTo, 0)
|
|
3501
3707
|
);
|
|
3502
|
-
}
|
|
3503
|
-
|
|
3504
|
-
|
|
3708
|
+
};
|
|
3709
|
+
const onZoom = () => {
|
|
3710
|
+
if (!sync.zoom) return;
|
|
3711
|
+
let bar = 0;
|
|
3712
|
+
try {
|
|
3713
|
+
bar = chart.getBarSpace().bar;
|
|
3714
|
+
} catch {
|
|
3715
|
+
return;
|
|
3716
|
+
}
|
|
3717
|
+
broadcast((target) => target.setBarSpace(bar));
|
|
3718
|
+
};
|
|
3719
|
+
chart.subscribeAction("onCrosshairChange", onCrosshairChange);
|
|
3720
|
+
chart.subscribeAction("onScroll", onScroll);
|
|
3721
|
+
chart.subscribeAction("onZoom", onZoom);
|
|
3722
|
+
return () => {
|
|
3723
|
+
chart.unsubscribeAction("onCrosshairChange", onCrosshairChange);
|
|
3724
|
+
chart.unsubscribeAction("onScroll", onScroll);
|
|
3725
|
+
chart.unsubscribeAction("onZoom", onZoom);
|
|
3726
|
+
};
|
|
3727
|
+
}, [chart, cellId, chartsRef, broadcastingRef, sync.crosshair, sync.scroll, sync.zoom]);
|
|
3728
|
+
useEffect(() => {
|
|
3729
|
+
if (state.symbol) {
|
|
3730
|
+
workspaceDispatch({ type: "SET_CELL_SYMBOL", id: cellId, symbol: state.symbol });
|
|
3505
3731
|
}
|
|
3506
|
-
};
|
|
3732
|
+
}, [state.symbol]);
|
|
3733
|
+
useEffect(() => {
|
|
3734
|
+
workspaceDispatch({ type: "SET_CELL_PERIOD", id: cellId, period: state.period });
|
|
3735
|
+
}, [state.period]);
|
|
3507
3736
|
}
|
|
3508
3737
|
|
|
3509
|
-
export { CANDLE_TYPES, COMPARE_RULES, DEFAULT_PERIODS,
|
|
3738
|
+
export { CANDLE_TYPES, COMPARE_RULES, DEFAULT_PERIODS, DEFAULT_STORAGE_KEY_PREFIX, DEFAULT_STORAGE_NAMESPACES, DEFAULT_SYNC_CONFIG, DRAWING_CATEGORIES, INDICATOR_PARAMS, KlinechartsUIProvider, MAIN_INDICATORS, PRICE_AXIS_TYPES, SUB_INDICATORS, TIMEZONES, TOOLTIP_SHOW_RULES, WorkspaceProvider, YAXIS_POSITIONS, createDefaultStorage, resolveStorage, useAlerts, useAnnotations, useChartAxes, useChartSync, useCompare, useCrosshair, useDataExport, useDrawingTools, useFullscreen, useHotkeys, useIndicators, useKlinechartsUILoading, useKlinechartsUISettings, useKlinechartsUITheme, useLayoutManager, useMeasure, useOrderLines, usePeriods, useReplay, useScreenshot, useScriptEditor, useSymbolSearch, useTimezone, useUndoRedo, useWatchlist, useWorkspace };
|
|
3510
3739
|
//# sourceMappingURL=index.js.map
|
|
3511
3740
|
//# sourceMappingURL=index.js.map
|