react-klinecharts-ui 0.6.0 → 1.0.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 +7 -6
- package/dist/{chunk-MVD3ILJT.js → chunk-JD4NBJ5F.js} +34 -16
- package/dist/chunk-JD4NBJ5F.js.map +1 -0
- package/dist/{chunk-FSHI2ZDB.cjs → chunk-LGYYJ2GP.cjs} +43 -25
- package/dist/chunk-LGYYJ2GP.cjs.map +1 -0
- package/dist/extensions.cjs +9 -9
- package/dist/extensions.d.cts +10 -7
- package/dist/extensions.d.ts +10 -7
- package/dist/extensions.js +1 -1
- package/dist/index.cjs +249 -163
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +199 -113
- package/dist/index.js.map +1 -1
- package/package.json +15 -6
- package/dist/chunk-FSHI2ZDB.cjs.map +0 -1
- package/dist/chunk-MVD3ILJT.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 { createContext, useContext, useReducer, useRef,
|
|
4
|
-
import { registerOverlay, registerHotkey, getHotkey, getSupportedHotkeys, registerIndicator } from '
|
|
1
|
+
import { registerExtensions, TA_default, ensureAlertLineRegistered } from './chunk-JD4NBJ5F.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-JD4NBJ5F.js';
|
|
3
|
+
import { createContext, useContext, useReducer, useRef, useEffect, useCallback, useMemo, useState, useId } from 'react';
|
|
4
|
+
import { registerOverlay, registerHotkey, getHotkey, getSupportedHotkeys, registerIndicator } from 'klinecharts';
|
|
5
5
|
import { jsx } from 'react/jsx-runtime';
|
|
6
6
|
|
|
7
7
|
var KlinechartsUIStateContext = createContext(null);
|
|
@@ -63,6 +63,25 @@ function reducer(state, action) {
|
|
|
63
63
|
return { ...state, indicatorVisibility: action.visibility };
|
|
64
64
|
case "SET_ALERTS":
|
|
65
65
|
return { ...state, alerts: action.alerts };
|
|
66
|
+
case "ADD_ALERT":
|
|
67
|
+
return { ...state, alerts: [...state.alerts, action.alert] };
|
|
68
|
+
case "REMOVE_ALERT":
|
|
69
|
+
return {
|
|
70
|
+
...state,
|
|
71
|
+
alerts: state.alerts.filter((a) => a.id !== action.id)
|
|
72
|
+
};
|
|
73
|
+
case "CLEAR_ALERTS":
|
|
74
|
+
return { ...state, alerts: [] };
|
|
75
|
+
case "MARK_ALERT_TRIGGERED": {
|
|
76
|
+
if (action.ids.length === 0) return state;
|
|
77
|
+
const triggered = new Set(action.ids);
|
|
78
|
+
return {
|
|
79
|
+
...state,
|
|
80
|
+
alerts: state.alerts.map(
|
|
81
|
+
(a) => triggered.has(a.id) ? { ...a, triggered: true } : a
|
|
82
|
+
)
|
|
83
|
+
};
|
|
84
|
+
}
|
|
66
85
|
case "SET_MEASURE":
|
|
67
86
|
return { ...state, measure: { ...state.measure, ...action.measure } };
|
|
68
87
|
case "SET_REPLAY":
|
|
@@ -152,7 +171,6 @@ function KlinechartsUIProvider({
|
|
|
152
171
|
const replayIndexRef = useRef(0);
|
|
153
172
|
const alertPrevCloseRef = useRef(null);
|
|
154
173
|
const stateRef = useRef(state);
|
|
155
|
-
stateRef.current = state;
|
|
156
174
|
const callbacksRef = useRef({
|
|
157
175
|
onStateChange,
|
|
158
176
|
onSymbolChange,
|
|
@@ -162,15 +180,18 @@ function KlinechartsUIProvider({
|
|
|
162
180
|
onMainIndicatorsChange,
|
|
163
181
|
onSubIndicatorsChange
|
|
164
182
|
});
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
stateRef.current = state;
|
|
185
|
+
callbacksRef.current = {
|
|
186
|
+
onStateChange,
|
|
187
|
+
onSymbolChange,
|
|
188
|
+
onPeriodChange,
|
|
189
|
+
onThemeChange,
|
|
190
|
+
onTimezoneChange,
|
|
191
|
+
onMainIndicatorsChange,
|
|
192
|
+
onSubIndicatorsChange
|
|
193
|
+
};
|
|
194
|
+
});
|
|
174
195
|
const enhancedDispatch = useCallback((action) => {
|
|
175
196
|
const prevState = stateRef.current;
|
|
176
197
|
const newState = reducer(prevState, action);
|
|
@@ -200,6 +221,12 @@ function KlinechartsUIProvider({
|
|
|
200
221
|
}
|
|
201
222
|
}, []);
|
|
202
223
|
const extraOverlaysRef = useRef(extraOverlays);
|
|
224
|
+
const datafeedRef = useRef(datafeed);
|
|
225
|
+
const onSettingsChangeRef = useRef(onSettingsChange);
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
datafeedRef.current = datafeed;
|
|
228
|
+
onSettingsChangeRef.current = onSettingsChange;
|
|
229
|
+
});
|
|
203
230
|
useEffect(() => {
|
|
204
231
|
if (shouldRegister) {
|
|
205
232
|
registerExtensions();
|
|
@@ -224,26 +251,29 @@ function KlinechartsUIProvider({
|
|
|
224
251
|
alertPrevCloseRef.current = currentClose;
|
|
225
252
|
if (prevClose === null) return;
|
|
226
253
|
const alerts = stateRef.current.alerts;
|
|
227
|
-
|
|
228
|
-
const
|
|
229
|
-
if (alert.triggered)
|
|
254
|
+
const triggeredIds = [];
|
|
255
|
+
for (const alert of alerts) {
|
|
256
|
+
if (alert.triggered) continue;
|
|
230
257
|
const crossedUp = prevClose < alert.price && currentClose >= alert.price;
|
|
231
258
|
const crossedDown = prevClose > alert.price && currentClose <= alert.price;
|
|
232
259
|
const shouldTrigger = alert.condition === "crossing_up" ? crossedUp : alert.condition === "crossing_down" ? crossedDown : crossedUp || crossedDown;
|
|
233
260
|
if (shouldTrigger) {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
alertTriggeredListenerRef.current?.(triggered);
|
|
237
|
-
return triggered;
|
|
261
|
+
triggeredIds.push(alert.id);
|
|
262
|
+
alertTriggeredListenerRef.current?.({ ...alert, triggered: true });
|
|
238
263
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
264
|
+
}
|
|
265
|
+
if (triggeredIds.length > 0) {
|
|
266
|
+
enhancedDispatch({
|
|
267
|
+
type: "MARK_ALERT_TRIGGERED",
|
|
268
|
+
ids: triggeredIds
|
|
269
|
+
});
|
|
243
270
|
}
|
|
244
271
|
}, 1e3);
|
|
245
272
|
return () => clearInterval(interval);
|
|
246
273
|
}, [state.chart, hasAlerts, enhancedDispatch]);
|
|
274
|
+
useEffect(() => {
|
|
275
|
+
alertPrevCloseRef.current = null;
|
|
276
|
+
}, [state.symbol, state.period]);
|
|
247
277
|
useEffect(() => {
|
|
248
278
|
return () => {
|
|
249
279
|
if (replayIntervalRef.current !== null) {
|
|
@@ -255,8 +285,8 @@ function KlinechartsUIProvider({
|
|
|
255
285
|
const dispatchValue = useMemo(
|
|
256
286
|
() => ({
|
|
257
287
|
dispatch: enhancedDispatch,
|
|
258
|
-
datafeed,
|
|
259
|
-
onSettingsChange,
|
|
288
|
+
datafeed: datafeedRef.current,
|
|
289
|
+
onSettingsChange: onSettingsChangeRef.current,
|
|
260
290
|
fullscreenContainerRef,
|
|
261
291
|
undoRedoListenerRef,
|
|
262
292
|
alertTriggeredListenerRef,
|
|
@@ -264,7 +294,8 @@ function KlinechartsUIProvider({
|
|
|
264
294
|
replaySavedDataRef,
|
|
265
295
|
replayIndexRef
|
|
266
296
|
}),
|
|
267
|
-
|
|
297
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
298
|
+
[enhancedDispatch]
|
|
268
299
|
);
|
|
269
300
|
return /* @__PURE__ */ jsx(KlinechartsUIStateContext.Provider, { value: state, children: /* @__PURE__ */ jsx(KlinechartsUIDispatchContext.Provider, { value: dispatchValue, children }) });
|
|
270
301
|
}
|
|
@@ -1222,15 +1253,17 @@ function useDrawingTools() {
|
|
|
1222
1253
|
const [isVisible, setIsVisible] = useState(true);
|
|
1223
1254
|
const [autoRetrigger, setAutoRetrigger] = useState(true);
|
|
1224
1255
|
const activeToolRef = useRef(activeTool);
|
|
1225
|
-
activeToolRef.current = activeTool;
|
|
1226
1256
|
const autoRetriggerRef = useRef(autoRetrigger);
|
|
1227
|
-
autoRetriggerRef.current = autoRetrigger;
|
|
1228
1257
|
const isLockedRef = useRef(isLocked);
|
|
1229
|
-
isLockedRef.current = isLocked;
|
|
1230
1258
|
const isVisibleRef = useRef(isVisible);
|
|
1231
|
-
isVisibleRef.current = isVisible;
|
|
1232
1259
|
const magnetModeRef = useRef(magnetMode);
|
|
1233
|
-
|
|
1260
|
+
useEffect(() => {
|
|
1261
|
+
activeToolRef.current = activeTool;
|
|
1262
|
+
autoRetriggerRef.current = autoRetrigger;
|
|
1263
|
+
isLockedRef.current = isLocked;
|
|
1264
|
+
isVisibleRef.current = isVisible;
|
|
1265
|
+
magnetModeRef.current = magnetMode;
|
|
1266
|
+
});
|
|
1234
1267
|
const categories = useMemo(
|
|
1235
1268
|
() => DRAWING_CATEGORIES.map((cat) => ({
|
|
1236
1269
|
key: cat.key,
|
|
@@ -1241,6 +1274,8 @@ function useDrawingTools() {
|
|
|
1241
1274
|
})),
|
|
1242
1275
|
[]
|
|
1243
1276
|
);
|
|
1277
|
+
const createOverlayForToolRef = useRef(() => {
|
|
1278
|
+
});
|
|
1244
1279
|
const createOverlayForTool = useCallback(
|
|
1245
1280
|
(name) => {
|
|
1246
1281
|
const mode = magnetModeRef.current === "strong" ? "strong_magnet" : magnetModeRef.current === "weak" ? "weak_magnet" : "normal";
|
|
@@ -1266,7 +1301,7 @@ function useDrawingTools() {
|
|
|
1266
1301
|
});
|
|
1267
1302
|
if (autoRetriggerRef.current && activeToolRef.current === name) {
|
|
1268
1303
|
requestAnimationFrame(() => {
|
|
1269
|
-
|
|
1304
|
+
createOverlayForToolRef.current(name);
|
|
1270
1305
|
});
|
|
1271
1306
|
}
|
|
1272
1307
|
}
|
|
@@ -1274,6 +1309,9 @@ function useDrawingTools() {
|
|
|
1274
1309
|
},
|
|
1275
1310
|
[state.chart, undoRedoListenerRef]
|
|
1276
1311
|
);
|
|
1312
|
+
useEffect(() => {
|
|
1313
|
+
createOverlayForToolRef.current = createOverlayForTool;
|
|
1314
|
+
});
|
|
1277
1315
|
const selectTool = useCallback(
|
|
1278
1316
|
(name) => {
|
|
1279
1317
|
setActiveTool(name);
|
|
@@ -1730,22 +1768,28 @@ function useFullscreen() {
|
|
|
1730
1768
|
const enter = useCallback(() => {
|
|
1731
1769
|
const el = fullscreenContainerRef.current;
|
|
1732
1770
|
if (!el) return;
|
|
1771
|
+
let result;
|
|
1733
1772
|
if (el.requestFullscreen) {
|
|
1734
|
-
el.requestFullscreen();
|
|
1773
|
+
result = el.requestFullscreen();
|
|
1735
1774
|
} else if ("webkitRequestFullscreen" in el) {
|
|
1736
1775
|
el.webkitRequestFullscreen();
|
|
1737
1776
|
} else if ("msRequestFullscreen" in el) {
|
|
1738
1777
|
el.msRequestFullscreen();
|
|
1739
1778
|
}
|
|
1779
|
+
result?.catch(() => {
|
|
1780
|
+
});
|
|
1740
1781
|
}, [fullscreenContainerRef]);
|
|
1741
1782
|
const exit = useCallback(() => {
|
|
1783
|
+
let result;
|
|
1742
1784
|
if (document.exitFullscreen) {
|
|
1743
|
-
document.exitFullscreen();
|
|
1785
|
+
result = document.exitFullscreen();
|
|
1744
1786
|
} else if ("webkitExitFullscreen" in document) {
|
|
1745
1787
|
document.webkitExitFullscreen();
|
|
1746
1788
|
} else if ("msExitFullscreen" in document) {
|
|
1747
1789
|
document.msExitFullscreen();
|
|
1748
1790
|
}
|
|
1791
|
+
result?.catch(() => {
|
|
1792
|
+
});
|
|
1749
1793
|
}, []);
|
|
1750
1794
|
const toggle = useCallback(() => {
|
|
1751
1795
|
if (isFullscreen) {
|
|
@@ -1776,6 +1820,7 @@ function useFullscreen() {
|
|
|
1776
1820
|
function useOrderLines() {
|
|
1777
1821
|
const { state } = useKlinechartsUI();
|
|
1778
1822
|
const callbacksRef = useRef(/* @__PURE__ */ new Map());
|
|
1823
|
+
const ownedIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
1779
1824
|
const createOrderLine = useCallback(
|
|
1780
1825
|
(options) => {
|
|
1781
1826
|
if (!state.chart) return null;
|
|
@@ -1806,6 +1851,7 @@ function useOrderLines() {
|
|
|
1806
1851
|
}
|
|
1807
1852
|
}
|
|
1808
1853
|
});
|
|
1854
|
+
ownedIdsRef.current.add(id);
|
|
1809
1855
|
return id;
|
|
1810
1856
|
},
|
|
1811
1857
|
[state.chart]
|
|
@@ -1837,12 +1883,28 @@ function useOrderLines() {
|
|
|
1837
1883
|
(id) => {
|
|
1838
1884
|
state.chart?.removeOverlay({ id });
|
|
1839
1885
|
callbacksRef.current.delete(id);
|
|
1886
|
+
ownedIdsRef.current.delete(id);
|
|
1840
1887
|
},
|
|
1841
1888
|
[state.chart]
|
|
1842
1889
|
);
|
|
1843
1890
|
const removeAllOrderLines = useCallback(() => {
|
|
1844
1891
|
state.chart?.removeOverlay({ name: "orderLine" });
|
|
1845
1892
|
callbacksRef.current.clear();
|
|
1893
|
+
ownedIdsRef.current.clear();
|
|
1894
|
+
}, [state.chart]);
|
|
1895
|
+
useEffect(() => {
|
|
1896
|
+
const chart = state.chart;
|
|
1897
|
+
const owned = ownedIdsRef.current;
|
|
1898
|
+
return () => {
|
|
1899
|
+
owned.forEach((id) => {
|
|
1900
|
+
try {
|
|
1901
|
+
chart?.removeOverlay({ id });
|
|
1902
|
+
} catch {
|
|
1903
|
+
}
|
|
1904
|
+
});
|
|
1905
|
+
owned.clear();
|
|
1906
|
+
callbacksRef.current.clear();
|
|
1907
|
+
};
|
|
1846
1908
|
}, [state.chart]);
|
|
1847
1909
|
return {
|
|
1848
1910
|
createOrderLine,
|
|
@@ -1860,6 +1922,14 @@ function useUndoRedo() {
|
|
|
1860
1922
|
const isProcessingRef = useRef(false);
|
|
1861
1923
|
const canUndo = undoStack.length > 0;
|
|
1862
1924
|
const canRedo = redoStack.length > 0;
|
|
1925
|
+
const undoStackRef = useRef([]);
|
|
1926
|
+
const redoStackRef = useRef([]);
|
|
1927
|
+
useEffect(() => {
|
|
1928
|
+
undoStackRef.current = undoStack;
|
|
1929
|
+
}, [undoStack]);
|
|
1930
|
+
useEffect(() => {
|
|
1931
|
+
redoStackRef.current = redoStack;
|
|
1932
|
+
}, [redoStack]);
|
|
1863
1933
|
const pushAction = useCallback((action) => {
|
|
1864
1934
|
if (isProcessingRef.current) return;
|
|
1865
1935
|
setUndoStack((prev) => [...prev, action]);
|
|
@@ -1876,10 +1946,11 @@ function useUndoRedo() {
|
|
|
1876
1946
|
setRedoStack([]);
|
|
1877
1947
|
}, []);
|
|
1878
1948
|
const undo = useCallback(() => {
|
|
1879
|
-
|
|
1949
|
+
const stack = undoStackRef.current;
|
|
1950
|
+
if (stack.length === 0 || !state.chart || isProcessingRef.current)
|
|
1880
1951
|
return;
|
|
1881
1952
|
isProcessingRef.current = true;
|
|
1882
|
-
const action =
|
|
1953
|
+
const action = stack[stack.length - 1];
|
|
1883
1954
|
setUndoStack((prev) => prev.slice(0, -1));
|
|
1884
1955
|
try {
|
|
1885
1956
|
switch (action.type) {
|
|
@@ -2000,12 +2071,13 @@ function useUndoRedo() {
|
|
|
2000
2071
|
} finally {
|
|
2001
2072
|
isProcessingRef.current = false;
|
|
2002
2073
|
}
|
|
2003
|
-
}, [
|
|
2074
|
+
}, [state.chart, state.mainIndicators, state.subIndicators, state.indicatorAxes, dispatch]);
|
|
2004
2075
|
const redo = useCallback(() => {
|
|
2005
|
-
|
|
2076
|
+
const stack = redoStackRef.current;
|
|
2077
|
+
if (stack.length === 0 || !state.chart || isProcessingRef.current)
|
|
2006
2078
|
return;
|
|
2007
2079
|
isProcessingRef.current = true;
|
|
2008
|
-
const action =
|
|
2080
|
+
const action = stack[stack.length - 1];
|
|
2009
2081
|
setRedoStack((prev) => prev.slice(0, -1));
|
|
2010
2082
|
try {
|
|
2011
2083
|
switch (action.type) {
|
|
@@ -2130,7 +2202,7 @@ function useUndoRedo() {
|
|
|
2130
2202
|
} finally {
|
|
2131
2203
|
isProcessingRef.current = false;
|
|
2132
2204
|
}
|
|
2133
|
-
}, [
|
|
2205
|
+
}, [state.chart, state.mainIndicators, state.subIndicators, state.indicatorAxes, dispatch]);
|
|
2134
2206
|
useEffect(() => {
|
|
2135
2207
|
const handleKeyDown = (e) => {
|
|
2136
2208
|
const isCtrlOrMeta = e.ctrlKey || e.metaKey;
|
|
@@ -2168,6 +2240,7 @@ function generateId() {
|
|
|
2168
2240
|
);
|
|
2169
2241
|
}
|
|
2170
2242
|
function getLayoutIds() {
|
|
2243
|
+
if (typeof localStorage === "undefined") return [];
|
|
2171
2244
|
try {
|
|
2172
2245
|
const raw = localStorage.getItem(INDEX_KEY);
|
|
2173
2246
|
return raw ? JSON.parse(raw) : [];
|
|
@@ -2176,6 +2249,7 @@ function getLayoutIds() {
|
|
|
2176
2249
|
}
|
|
2177
2250
|
}
|
|
2178
2251
|
function getEntry(id) {
|
|
2252
|
+
if (typeof localStorage === "undefined") return null;
|
|
2179
2253
|
try {
|
|
2180
2254
|
const raw = localStorage.getItem(STORAGE_KEY_PREFIX + id);
|
|
2181
2255
|
return raw ? JSON.parse(raw) : null;
|
|
@@ -2185,35 +2259,31 @@ function getEntry(id) {
|
|
|
2185
2259
|
}
|
|
2186
2260
|
function useLayoutManager() {
|
|
2187
2261
|
const { state, dispatch } = useKlinechartsUI();
|
|
2188
|
-
const [layouts, setLayouts] = useState(
|
|
2262
|
+
const [layouts, setLayouts] = useState(
|
|
2263
|
+
() => getLayoutIds().map((id) => getEntry(id)).filter((e) => e !== null)
|
|
2264
|
+
);
|
|
2189
2265
|
const [autoSaveEnabled, setAutoSaveEnabled] = useState(false);
|
|
2190
2266
|
const autoSaveTimerRef = useRef(null);
|
|
2191
2267
|
const autoSaveIdRef = useRef(null);
|
|
2192
2268
|
const refreshLayouts = useCallback(() => {
|
|
2193
|
-
|
|
2194
|
-
|
|
2269
|
+
setLayouts(
|
|
2270
|
+
getLayoutIds().map((id) => getEntry(id)).filter((e) => e !== null)
|
|
2271
|
+
);
|
|
2195
2272
|
}, []);
|
|
2196
|
-
useEffect(() => {
|
|
2197
|
-
refreshLayouts();
|
|
2198
|
-
}, [refreshLayouts]);
|
|
2199
2273
|
const serializeState = useCallback(() => {
|
|
2200
2274
|
const chart = state.chart;
|
|
2201
2275
|
if (!chart) return null;
|
|
2202
2276
|
const indicators2 = [];
|
|
2203
|
-
const
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
...indicator.styles ? { styles: indicator.styles } : {},
|
|
2214
|
-
...yAxisId ? { yAxisId } : {}
|
|
2215
|
-
});
|
|
2216
|
-
});
|
|
2277
|
+
const allIndicators = chart.getIndicators();
|
|
2278
|
+
for (const indicator of allIndicators) {
|
|
2279
|
+
const yAxisId = state.indicatorAxes[indicator.id];
|
|
2280
|
+
indicators2.push({
|
|
2281
|
+
paneId: indicator.paneId,
|
|
2282
|
+
name: indicator.name,
|
|
2283
|
+
calcParams: indicator.calcParams,
|
|
2284
|
+
visible: indicator.visible,
|
|
2285
|
+
...indicator.styles ? { styles: indicator.styles } : {},
|
|
2286
|
+
...yAxisId ? { yAxisId } : {}
|
|
2217
2287
|
});
|
|
2218
2288
|
}
|
|
2219
2289
|
const drawings = [];
|
|
@@ -2277,13 +2347,8 @@ function useLayoutManager() {
|
|
|
2277
2347
|
if (chartState.version !== STATE_VERSION) return false;
|
|
2278
2348
|
const chart = state.chart;
|
|
2279
2349
|
chart.removeOverlay();
|
|
2280
|
-
const
|
|
2281
|
-
|
|
2282
|
-
indicatorMap.forEach((_paneIndicators, paneId) => {
|
|
2283
|
-
_paneIndicators.forEach((_, indicatorName) => {
|
|
2284
|
-
chart.removeIndicator({ paneId, name: indicatorName });
|
|
2285
|
-
});
|
|
2286
|
-
});
|
|
2350
|
+
for (const indicator of chart.getIndicators()) {
|
|
2351
|
+
chart.removeIndicator({ id: indicator.id });
|
|
2287
2352
|
}
|
|
2288
2353
|
const newMainIndicators = [];
|
|
2289
2354
|
const newSubIndicators = {};
|
|
@@ -2301,7 +2366,12 @@ function useLayoutManager() {
|
|
|
2301
2366
|
visible: ind.visible
|
|
2302
2367
|
},
|
|
2303
2368
|
{
|
|
2304
|
-
|
|
2369
|
+
// Main indicators stack OVER the candle series on the candle
|
|
2370
|
+
// pane (isStack: true), exactly as useIndicators does on the
|
|
2371
|
+
// add path. The previous `ind.paneId !== "candle_pane"` inverted
|
|
2372
|
+
// this, so loading a layout with main indicators replaced the
|
|
2373
|
+
// candles instead of overlaying them.
|
|
2374
|
+
isStack: isMain,
|
|
2305
2375
|
pane: { id: ind.paneId },
|
|
2306
2376
|
...ind.yAxisId ? { yAxis: { id: ind.yAxisId } } : {}
|
|
2307
2377
|
}
|
|
@@ -2501,8 +2571,10 @@ function useScriptEditor() {
|
|
|
2501
2571
|
const [status, setStatus] = useState("");
|
|
2502
2572
|
const [isRunning, setIsRunning] = useState(false);
|
|
2503
2573
|
const activeIdRef = useRef(null);
|
|
2574
|
+
const [activeName, setActiveName] = useState(null);
|
|
2504
2575
|
const activeNameRef = useRef(null);
|
|
2505
|
-
|
|
2576
|
+
activeNameRef.current = activeName;
|
|
2577
|
+
const hasActiveScript = activeName !== null;
|
|
2506
2578
|
const runScript = useCallback(() => {
|
|
2507
2579
|
const chart = state.chart;
|
|
2508
2580
|
if (!chart) {
|
|
@@ -2523,19 +2595,26 @@ function useScriptEditor() {
|
|
|
2523
2595
|
`"use strict";
|
|
2524
2596
|
${code}`
|
|
2525
2597
|
];
|
|
2526
|
-
const
|
|
2598
|
+
const compiledFn = new Function(...allArgs);
|
|
2527
2599
|
const shadowValues = SHADOW_KEYS.map(() => void 0);
|
|
2528
|
-
const
|
|
2529
|
-
|
|
2600
|
+
const sampleResult = compiledFn(
|
|
2601
|
+
...shadowValues,
|
|
2602
|
+
TA_default,
|
|
2603
|
+
dataList,
|
|
2604
|
+
parsedParams
|
|
2605
|
+
);
|
|
2606
|
+
if (!Array.isArray(sampleResult)) {
|
|
2530
2607
|
throw new Error("Script must return an Array.");
|
|
2531
2608
|
}
|
|
2532
|
-
const sample =
|
|
2609
|
+
const sample = sampleResult.find(
|
|
2610
|
+
(v) => v !== null && v !== void 0
|
|
2611
|
+
);
|
|
2533
2612
|
const seriesKeys = sample ? Object.keys(sample) : ["value"];
|
|
2534
2613
|
if (seriesKeys.length === 0) {
|
|
2535
2614
|
throw new Error("Returned objects have no keys.");
|
|
2536
2615
|
}
|
|
2537
2616
|
scriptCounter++;
|
|
2538
|
-
const indicatorName = `
|
|
2617
|
+
const indicatorName = `_custom_script_active`;
|
|
2539
2618
|
const figures = seriesKeys.map((key, i) => ({
|
|
2540
2619
|
key,
|
|
2541
2620
|
title: `${key}: `,
|
|
@@ -2547,7 +2626,7 @@ ${code}`
|
|
|
2547
2626
|
shortName: (scriptName.trim() || `Script #${scriptCounter}`) + (placement === "main" ? " (Main)" : " (Sub)"),
|
|
2548
2627
|
calcParams: parsedParams,
|
|
2549
2628
|
figures,
|
|
2550
|
-
calc: () =>
|
|
2629
|
+
calc: (liveDataList) => compiledFn(...shadowValues, TA_default, liveDataList, parsedParams),
|
|
2551
2630
|
extendData: {
|
|
2552
2631
|
isCustomScript: true,
|
|
2553
2632
|
code,
|
|
@@ -2573,7 +2652,7 @@ ${code}`
|
|
|
2573
2652
|
paneId = chart.createIndicator({ name: indicatorName });
|
|
2574
2653
|
}
|
|
2575
2654
|
activeIdRef.current = typeof paneId === "string" ? paneId : null;
|
|
2576
|
-
|
|
2655
|
+
setActiveName(indicatorName);
|
|
2577
2656
|
const title = scriptName.trim() || `Script #${scriptCounter}`;
|
|
2578
2657
|
setStatus(
|
|
2579
2658
|
`${title} applied to ${placement === "main" ? "Main Chart" : "Sub Pane"} \u2014 ${seriesKeys.length} series: ${seriesKeys.join(", ")}`
|
|
@@ -2595,7 +2674,7 @@ ${code}`
|
|
|
2595
2674
|
} catch {
|
|
2596
2675
|
}
|
|
2597
2676
|
activeIdRef.current = null;
|
|
2598
|
-
|
|
2677
|
+
setActiveName(null);
|
|
2599
2678
|
setStatus("Indicator removed.");
|
|
2600
2679
|
}
|
|
2601
2680
|
}, [state.chart]);
|
|
@@ -2727,7 +2806,7 @@ function useAlerts() {
|
|
|
2727
2806
|
triggered: false,
|
|
2728
2807
|
extendData: resolvedExtendData
|
|
2729
2808
|
};
|
|
2730
|
-
dispatch({ type: "
|
|
2809
|
+
dispatch({ type: "ADD_ALERT", alert });
|
|
2731
2810
|
if (state.chart) {
|
|
2732
2811
|
ensureAlertLineRegistered();
|
|
2733
2812
|
state.chart.createOverlay({
|
|
@@ -2741,24 +2820,19 @@ function useAlerts() {
|
|
|
2741
2820
|
}
|
|
2742
2821
|
return id;
|
|
2743
2822
|
},
|
|
2744
|
-
[state.chart,
|
|
2823
|
+
[state.chart, dispatch]
|
|
2745
2824
|
);
|
|
2746
2825
|
const removeAlert = useCallback(
|
|
2747
2826
|
(id) => {
|
|
2748
|
-
dispatch({
|
|
2749
|
-
type: "SET_ALERTS",
|
|
2750
|
-
alerts: state.alerts.filter((a) => a.id !== id)
|
|
2751
|
-
});
|
|
2827
|
+
dispatch({ type: "REMOVE_ALERT", id });
|
|
2752
2828
|
state.chart?.removeOverlay({ id });
|
|
2753
2829
|
},
|
|
2754
|
-
[state.chart,
|
|
2830
|
+
[state.chart, dispatch]
|
|
2755
2831
|
);
|
|
2756
2832
|
const clearAlerts = useCallback(() => {
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
dispatch({ type: "SET_ALERTS", alerts: [] });
|
|
2761
|
-
}, [state.chart, state.alerts, dispatch]);
|
|
2833
|
+
state.chart?.removeOverlay({ groupId: "price_alerts" });
|
|
2834
|
+
dispatch({ type: "CLEAR_ALERTS" });
|
|
2835
|
+
}, [state.chart, dispatch]);
|
|
2762
2836
|
const onAlertTriggered = useCallback(
|
|
2763
2837
|
(callback) => {
|
|
2764
2838
|
alertTriggeredListenerRef.current = callback;
|
|
@@ -2942,6 +3016,7 @@ function useReplay() {
|
|
|
2942
3016
|
);
|
|
2943
3017
|
const startReplay = useCallback(() => {
|
|
2944
3018
|
if (!state.chart) return;
|
|
3019
|
+
if (isReplaying) return;
|
|
2945
3020
|
const dataList = state.chart.getDataList();
|
|
2946
3021
|
if (!dataList || dataList.length === 0) return;
|
|
2947
3022
|
replaySavedDataRef.current = [...dataList];
|
|
@@ -2957,7 +3032,12 @@ function useReplay() {
|
|
|
2957
3032
|
});
|
|
2958
3033
|
state.chart.clearData?.();
|
|
2959
3034
|
startInterval(speed);
|
|
2960
|
-
}, [state.chart, speed, startInterval, replaySavedDataRef, replayIndexRef, dispatch]);
|
|
3035
|
+
}, [state.chart, state.symbol, state.period, speed, startInterval, isReplaying, replaySavedDataRef, replayIndexRef, dispatch]);
|
|
3036
|
+
useEffect(() => {
|
|
3037
|
+
if (isReplaying) {
|
|
3038
|
+
stopReplay();
|
|
3039
|
+
}
|
|
3040
|
+
}, [state.symbol, state.period]);
|
|
2961
3041
|
const stopReplay = useCallback(() => {
|
|
2962
3042
|
if (!state.chart) return;
|
|
2963
3043
|
clearInterval_();
|
|
@@ -3053,24 +3133,29 @@ var DEFAULT_COLORS = [
|
|
|
3053
3133
|
"#ff5722",
|
|
3054
3134
|
"#8bc34a"
|
|
3055
3135
|
];
|
|
3056
|
-
var colorIndex = 0;
|
|
3057
|
-
var compareCounter = 0;
|
|
3058
3136
|
function useCompare() {
|
|
3059
3137
|
const { state, datafeed } = useKlinechartsUI();
|
|
3060
3138
|
const [symbols, setSymbols] = useState([]);
|
|
3061
3139
|
const indicatorsRef = useRef(/* @__PURE__ */ new Map());
|
|
3140
|
+
const instanceSalt = useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
3062
3141
|
const addSymbol = useCallback(
|
|
3063
3142
|
async (ticker, color) => {
|
|
3064
3143
|
if (!state.chart || !datafeed) return;
|
|
3065
3144
|
if (indicatorsRef.current.has(ticker)) return;
|
|
3066
|
-
const assignedColor = color ?? DEFAULT_COLORS[
|
|
3145
|
+
const assignedColor = color ?? DEFAULT_COLORS[symbols.length % DEFAULT_COLORS.length];
|
|
3067
3146
|
const mainDataList = state.chart.getDataList();
|
|
3068
3147
|
if (!mainDataList || mainDataList.length === 0) return;
|
|
3069
3148
|
const from = mainDataList[0].timestamp;
|
|
3070
3149
|
const to = mainDataList[mainDataList.length - 1].timestamp;
|
|
3150
|
+
const mainSymbol = state.symbol;
|
|
3151
|
+
const symbol = {
|
|
3152
|
+
ticker,
|
|
3153
|
+
pricePrecision: mainSymbol?.pricePrecision ?? 2,
|
|
3154
|
+
volumePrecision: mainSymbol?.volumePrecision ?? 8
|
|
3155
|
+
};
|
|
3071
3156
|
const compareData = await datafeed.getHistoryKLineData(
|
|
3072
|
-
|
|
3073
|
-
|
|
3157
|
+
symbol,
|
|
3158
|
+
state.period,
|
|
3074
3159
|
from,
|
|
3075
3160
|
to
|
|
3076
3161
|
);
|
|
@@ -3089,13 +3174,7 @@ function useCompare() {
|
|
|
3089
3174
|
}
|
|
3090
3175
|
if (basePrice === null || basePrice === 0) return;
|
|
3091
3176
|
const bp = basePrice;
|
|
3092
|
-
const
|
|
3093
|
-
const close = compareMap.get(mainBar.timestamp);
|
|
3094
|
-
return {
|
|
3095
|
-
pct: close != null ? (close - bp) / bp * 100 : NaN
|
|
3096
|
-
};
|
|
3097
|
-
});
|
|
3098
|
-
const indicatorName = `__cmp_${++compareCounter}_${ticker}`;
|
|
3177
|
+
const indicatorName = `__cmp_${instanceSalt}_${ticker}`;
|
|
3099
3178
|
registerIndicator({
|
|
3100
3179
|
name: indicatorName,
|
|
3101
3180
|
shortName: ticker,
|
|
@@ -3107,7 +3186,16 @@ function useCompare() {
|
|
|
3107
3186
|
styles: () => ({ color: assignedColor })
|
|
3108
3187
|
}
|
|
3109
3188
|
],
|
|
3110
|
-
calc: () =>
|
|
3189
|
+
calc: (dataList) => {
|
|
3190
|
+
let lastPct = null;
|
|
3191
|
+
return dataList.map((bar) => {
|
|
3192
|
+
const close = compareMap.get(bar.timestamp);
|
|
3193
|
+
if (close != null) {
|
|
3194
|
+
lastPct = (close - bp) / bp * 100;
|
|
3195
|
+
}
|
|
3196
|
+
return { pct: lastPct };
|
|
3197
|
+
});
|
|
3198
|
+
}
|
|
3111
3199
|
});
|
|
3112
3200
|
const paneId = state.chart.createIndicator(
|
|
3113
3201
|
{ name: indicatorName },
|
|
@@ -3125,7 +3213,7 @@ function useCompare() {
|
|
|
3125
3213
|
];
|
|
3126
3214
|
});
|
|
3127
3215
|
},
|
|
3128
|
-
[state.chart, state.period, datafeed]
|
|
3216
|
+
[state.chart, state.symbol, state.period, datafeed, symbols.length, instanceSalt]
|
|
3129
3217
|
);
|
|
3130
3218
|
const removeSymbol = useCallback(
|
|
3131
3219
|
(ticker) => {
|
|
@@ -3335,13 +3423,11 @@ function useAnnotations() {
|
|
|
3335
3423
|
[state.chart]
|
|
3336
3424
|
);
|
|
3337
3425
|
const clearAnnotations = useCallback(() => {
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
});
|
|
3344
|
-
}, [state.chart]);
|
|
3426
|
+
for (const annotation of annotations) {
|
|
3427
|
+
state.chart?.removeOverlay({ id: annotation.id });
|
|
3428
|
+
}
|
|
3429
|
+
setAnnotations([]);
|
|
3430
|
+
}, [state.chart, annotations]);
|
|
3345
3431
|
useEffect(() => {
|
|
3346
3432
|
return () => {
|
|
3347
3433
|
state.chart?.removeOverlay({ groupId: "annotations" });
|