react-klinecharts-ui 0.5.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 +201 -54
- package/dist/{chunk-DTBNTO4M.js → chunk-JD4NBJ5F.js} +149 -17
- package/dist/chunk-JD4NBJ5F.js.map +1 -0
- package/dist/{chunk-UJNJH3BS.cjs → chunk-LGYYJ2GP.cjs} +158 -23
- package/dist/chunk-LGYYJ2GP.cjs.map +1 -0
- package/dist/extensions.cjs +18 -6
- package/dist/extensions.d.cts +46 -10
- package/dist/extensions.d.ts +46 -10
- package/dist/extensions.js +1 -1
- package/dist/index.cjs +308 -168
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -5
- package/dist/index.d.ts +76 -5
- package/dist/index.js +258 -124
- package/dist/index.js.map +1 -1
- package/package.json +15 -6
- package/dist/chunk-DTBNTO4M.js.map +0 -1
- package/dist/chunk-UJNJH3BS.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { registerExtensions, TA_default } from './chunk-
|
|
2
|
-
export { TA_default as TA, abcd_default as abcd, 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, 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);
|
|
@@ -1677,28 +1715,81 @@ function useScreenshot() {
|
|
|
1677
1715
|
clear
|
|
1678
1716
|
};
|
|
1679
1717
|
}
|
|
1718
|
+
function useHotkeys() {
|
|
1719
|
+
const { state } = useKlinechartsUI();
|
|
1720
|
+
const registerHotkey$1 = useCallback((template) => {
|
|
1721
|
+
registerHotkey(template);
|
|
1722
|
+
}, []);
|
|
1723
|
+
const getHotkey$1 = useCallback(
|
|
1724
|
+
(name) => getHotkey(name) ?? null,
|
|
1725
|
+
[]
|
|
1726
|
+
);
|
|
1727
|
+
const setHotkeysEnabled = useCallback(
|
|
1728
|
+
(enabled, exclude) => {
|
|
1729
|
+
state.chart?.setHotkey(
|
|
1730
|
+
exclude ? { enabled, exclude } : { enabled }
|
|
1731
|
+
);
|
|
1732
|
+
},
|
|
1733
|
+
[state.chart]
|
|
1734
|
+
);
|
|
1735
|
+
const getHotkeysConfig = useCallback(
|
|
1736
|
+
() => state.chart?.getHotkey() ?? null,
|
|
1737
|
+
[state.chart]
|
|
1738
|
+
);
|
|
1739
|
+
return {
|
|
1740
|
+
registerHotkey: registerHotkey$1,
|
|
1741
|
+
getHotkey: getHotkey$1,
|
|
1742
|
+
supportedHotkeys: getSupportedHotkeys(),
|
|
1743
|
+
setHotkeysEnabled,
|
|
1744
|
+
getHotkeysConfig
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
function useChartAxes() {
|
|
1748
|
+
const { state } = useKlinechartsUI();
|
|
1749
|
+
const overrideXAxis = useCallback(
|
|
1750
|
+
(override) => {
|
|
1751
|
+
if (!state.chart) return;
|
|
1752
|
+
state.chart.overrideXAxis(override);
|
|
1753
|
+
},
|
|
1754
|
+
[state.chart]
|
|
1755
|
+
);
|
|
1756
|
+
const overrideYAxis = useCallback(
|
|
1757
|
+
(override) => {
|
|
1758
|
+
if (!state.chart) return;
|
|
1759
|
+
state.chart.overrideYAxis(override);
|
|
1760
|
+
},
|
|
1761
|
+
[state.chart]
|
|
1762
|
+
);
|
|
1763
|
+
return { overrideXAxis, overrideYAxis };
|
|
1764
|
+
}
|
|
1680
1765
|
function useFullscreen() {
|
|
1681
1766
|
const { fullscreenContainerRef } = useKlinechartsUIDispatch();
|
|
1682
1767
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
1683
1768
|
const enter = useCallback(() => {
|
|
1684
1769
|
const el = fullscreenContainerRef.current;
|
|
1685
1770
|
if (!el) return;
|
|
1771
|
+
let result;
|
|
1686
1772
|
if (el.requestFullscreen) {
|
|
1687
|
-
el.requestFullscreen();
|
|
1773
|
+
result = el.requestFullscreen();
|
|
1688
1774
|
} else if ("webkitRequestFullscreen" in el) {
|
|
1689
1775
|
el.webkitRequestFullscreen();
|
|
1690
1776
|
} else if ("msRequestFullscreen" in el) {
|
|
1691
1777
|
el.msRequestFullscreen();
|
|
1692
1778
|
}
|
|
1779
|
+
result?.catch(() => {
|
|
1780
|
+
});
|
|
1693
1781
|
}, [fullscreenContainerRef]);
|
|
1694
1782
|
const exit = useCallback(() => {
|
|
1783
|
+
let result;
|
|
1695
1784
|
if (document.exitFullscreen) {
|
|
1696
|
-
document.exitFullscreen();
|
|
1785
|
+
result = document.exitFullscreen();
|
|
1697
1786
|
} else if ("webkitExitFullscreen" in document) {
|
|
1698
1787
|
document.webkitExitFullscreen();
|
|
1699
1788
|
} else if ("msExitFullscreen" in document) {
|
|
1700
1789
|
document.msExitFullscreen();
|
|
1701
1790
|
}
|
|
1791
|
+
result?.catch(() => {
|
|
1792
|
+
});
|
|
1702
1793
|
}, []);
|
|
1703
1794
|
const toggle = useCallback(() => {
|
|
1704
1795
|
if (isFullscreen) {
|
|
@@ -1729,6 +1820,7 @@ function useFullscreen() {
|
|
|
1729
1820
|
function useOrderLines() {
|
|
1730
1821
|
const { state } = useKlinechartsUI();
|
|
1731
1822
|
const callbacksRef = useRef(/* @__PURE__ */ new Map());
|
|
1823
|
+
const ownedIdsRef = useRef(/* @__PURE__ */ new Set());
|
|
1732
1824
|
const createOrderLine = useCallback(
|
|
1733
1825
|
(options) => {
|
|
1734
1826
|
if (!state.chart) return null;
|
|
@@ -1759,6 +1851,7 @@ function useOrderLines() {
|
|
|
1759
1851
|
}
|
|
1760
1852
|
}
|
|
1761
1853
|
});
|
|
1854
|
+
ownedIdsRef.current.add(id);
|
|
1762
1855
|
return id;
|
|
1763
1856
|
},
|
|
1764
1857
|
[state.chart]
|
|
@@ -1790,12 +1883,28 @@ function useOrderLines() {
|
|
|
1790
1883
|
(id) => {
|
|
1791
1884
|
state.chart?.removeOverlay({ id });
|
|
1792
1885
|
callbacksRef.current.delete(id);
|
|
1886
|
+
ownedIdsRef.current.delete(id);
|
|
1793
1887
|
},
|
|
1794
1888
|
[state.chart]
|
|
1795
1889
|
);
|
|
1796
1890
|
const removeAllOrderLines = useCallback(() => {
|
|
1797
1891
|
state.chart?.removeOverlay({ name: "orderLine" });
|
|
1798
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
|
+
};
|
|
1799
1908
|
}, [state.chart]);
|
|
1800
1909
|
return {
|
|
1801
1910
|
createOrderLine,
|
|
@@ -1813,6 +1922,14 @@ function useUndoRedo() {
|
|
|
1813
1922
|
const isProcessingRef = useRef(false);
|
|
1814
1923
|
const canUndo = undoStack.length > 0;
|
|
1815
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]);
|
|
1816
1933
|
const pushAction = useCallback((action) => {
|
|
1817
1934
|
if (isProcessingRef.current) return;
|
|
1818
1935
|
setUndoStack((prev) => [...prev, action]);
|
|
@@ -1829,10 +1946,11 @@ function useUndoRedo() {
|
|
|
1829
1946
|
setRedoStack([]);
|
|
1830
1947
|
}, []);
|
|
1831
1948
|
const undo = useCallback(() => {
|
|
1832
|
-
|
|
1949
|
+
const stack = undoStackRef.current;
|
|
1950
|
+
if (stack.length === 0 || !state.chart || isProcessingRef.current)
|
|
1833
1951
|
return;
|
|
1834
1952
|
isProcessingRef.current = true;
|
|
1835
|
-
const action =
|
|
1953
|
+
const action = stack[stack.length - 1];
|
|
1836
1954
|
setUndoStack((prev) => prev.slice(0, -1));
|
|
1837
1955
|
try {
|
|
1838
1956
|
switch (action.type) {
|
|
@@ -1953,12 +2071,13 @@ function useUndoRedo() {
|
|
|
1953
2071
|
} finally {
|
|
1954
2072
|
isProcessingRef.current = false;
|
|
1955
2073
|
}
|
|
1956
|
-
}, [
|
|
2074
|
+
}, [state.chart, state.mainIndicators, state.subIndicators, state.indicatorAxes, dispatch]);
|
|
1957
2075
|
const redo = useCallback(() => {
|
|
1958
|
-
|
|
2076
|
+
const stack = redoStackRef.current;
|
|
2077
|
+
if (stack.length === 0 || !state.chart || isProcessingRef.current)
|
|
1959
2078
|
return;
|
|
1960
2079
|
isProcessingRef.current = true;
|
|
1961
|
-
const action =
|
|
2080
|
+
const action = stack[stack.length - 1];
|
|
1962
2081
|
setRedoStack((prev) => prev.slice(0, -1));
|
|
1963
2082
|
try {
|
|
1964
2083
|
switch (action.type) {
|
|
@@ -2083,7 +2202,7 @@ function useUndoRedo() {
|
|
|
2083
2202
|
} finally {
|
|
2084
2203
|
isProcessingRef.current = false;
|
|
2085
2204
|
}
|
|
2086
|
-
}, [
|
|
2205
|
+
}, [state.chart, state.mainIndicators, state.subIndicators, state.indicatorAxes, dispatch]);
|
|
2087
2206
|
useEffect(() => {
|
|
2088
2207
|
const handleKeyDown = (e) => {
|
|
2089
2208
|
const isCtrlOrMeta = e.ctrlKey || e.metaKey;
|
|
@@ -2121,6 +2240,7 @@ function generateId() {
|
|
|
2121
2240
|
);
|
|
2122
2241
|
}
|
|
2123
2242
|
function getLayoutIds() {
|
|
2243
|
+
if (typeof localStorage === "undefined") return [];
|
|
2124
2244
|
try {
|
|
2125
2245
|
const raw = localStorage.getItem(INDEX_KEY);
|
|
2126
2246
|
return raw ? JSON.parse(raw) : [];
|
|
@@ -2129,6 +2249,7 @@ function getLayoutIds() {
|
|
|
2129
2249
|
}
|
|
2130
2250
|
}
|
|
2131
2251
|
function getEntry(id) {
|
|
2252
|
+
if (typeof localStorage === "undefined") return null;
|
|
2132
2253
|
try {
|
|
2133
2254
|
const raw = localStorage.getItem(STORAGE_KEY_PREFIX + id);
|
|
2134
2255
|
return raw ? JSON.parse(raw) : null;
|
|
@@ -2138,35 +2259,31 @@ function getEntry(id) {
|
|
|
2138
2259
|
}
|
|
2139
2260
|
function useLayoutManager() {
|
|
2140
2261
|
const { state, dispatch } = useKlinechartsUI();
|
|
2141
|
-
const [layouts, setLayouts] = useState(
|
|
2262
|
+
const [layouts, setLayouts] = useState(
|
|
2263
|
+
() => getLayoutIds().map((id) => getEntry(id)).filter((e) => e !== null)
|
|
2264
|
+
);
|
|
2142
2265
|
const [autoSaveEnabled, setAutoSaveEnabled] = useState(false);
|
|
2143
2266
|
const autoSaveTimerRef = useRef(null);
|
|
2144
2267
|
const autoSaveIdRef = useRef(null);
|
|
2145
2268
|
const refreshLayouts = useCallback(() => {
|
|
2146
|
-
|
|
2147
|
-
|
|
2269
|
+
setLayouts(
|
|
2270
|
+
getLayoutIds().map((id) => getEntry(id)).filter((e) => e !== null)
|
|
2271
|
+
);
|
|
2148
2272
|
}, []);
|
|
2149
|
-
useEffect(() => {
|
|
2150
|
-
refreshLayouts();
|
|
2151
|
-
}, [refreshLayouts]);
|
|
2152
2273
|
const serializeState = useCallback(() => {
|
|
2153
2274
|
const chart = state.chart;
|
|
2154
2275
|
if (!chart) return null;
|
|
2155
2276
|
const indicators2 = [];
|
|
2156
|
-
const
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
...indicator.styles ? { styles: indicator.styles } : {},
|
|
2167
|
-
...yAxisId ? { yAxisId } : {}
|
|
2168
|
-
});
|
|
2169
|
-
});
|
|
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 } : {}
|
|
2170
2287
|
});
|
|
2171
2288
|
}
|
|
2172
2289
|
const drawings = [];
|
|
@@ -2230,13 +2347,8 @@ function useLayoutManager() {
|
|
|
2230
2347
|
if (chartState.version !== STATE_VERSION) return false;
|
|
2231
2348
|
const chart = state.chart;
|
|
2232
2349
|
chart.removeOverlay();
|
|
2233
|
-
const
|
|
2234
|
-
|
|
2235
|
-
indicatorMap.forEach((_paneIndicators, paneId) => {
|
|
2236
|
-
_paneIndicators.forEach((_, indicatorName) => {
|
|
2237
|
-
chart.removeIndicator({ paneId, name: indicatorName });
|
|
2238
|
-
});
|
|
2239
|
-
});
|
|
2350
|
+
for (const indicator of chart.getIndicators()) {
|
|
2351
|
+
chart.removeIndicator({ id: indicator.id });
|
|
2240
2352
|
}
|
|
2241
2353
|
const newMainIndicators = [];
|
|
2242
2354
|
const newSubIndicators = {};
|
|
@@ -2254,7 +2366,12 @@ function useLayoutManager() {
|
|
|
2254
2366
|
visible: ind.visible
|
|
2255
2367
|
},
|
|
2256
2368
|
{
|
|
2257
|
-
|
|
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,
|
|
2258
2375
|
pane: { id: ind.paneId },
|
|
2259
2376
|
...ind.yAxisId ? { yAxis: { id: ind.yAxisId } } : {}
|
|
2260
2377
|
}
|
|
@@ -2454,8 +2571,10 @@ function useScriptEditor() {
|
|
|
2454
2571
|
const [status, setStatus] = useState("");
|
|
2455
2572
|
const [isRunning, setIsRunning] = useState(false);
|
|
2456
2573
|
const activeIdRef = useRef(null);
|
|
2574
|
+
const [activeName, setActiveName] = useState(null);
|
|
2457
2575
|
const activeNameRef = useRef(null);
|
|
2458
|
-
|
|
2576
|
+
activeNameRef.current = activeName;
|
|
2577
|
+
const hasActiveScript = activeName !== null;
|
|
2459
2578
|
const runScript = useCallback(() => {
|
|
2460
2579
|
const chart = state.chart;
|
|
2461
2580
|
if (!chart) {
|
|
@@ -2476,19 +2595,26 @@ function useScriptEditor() {
|
|
|
2476
2595
|
`"use strict";
|
|
2477
2596
|
${code}`
|
|
2478
2597
|
];
|
|
2479
|
-
const
|
|
2598
|
+
const compiledFn = new Function(...allArgs);
|
|
2480
2599
|
const shadowValues = SHADOW_KEYS.map(() => void 0);
|
|
2481
|
-
const
|
|
2482
|
-
|
|
2600
|
+
const sampleResult = compiledFn(
|
|
2601
|
+
...shadowValues,
|
|
2602
|
+
TA_default,
|
|
2603
|
+
dataList,
|
|
2604
|
+
parsedParams
|
|
2605
|
+
);
|
|
2606
|
+
if (!Array.isArray(sampleResult)) {
|
|
2483
2607
|
throw new Error("Script must return an Array.");
|
|
2484
2608
|
}
|
|
2485
|
-
const sample =
|
|
2609
|
+
const sample = sampleResult.find(
|
|
2610
|
+
(v) => v !== null && v !== void 0
|
|
2611
|
+
);
|
|
2486
2612
|
const seriesKeys = sample ? Object.keys(sample) : ["value"];
|
|
2487
2613
|
if (seriesKeys.length === 0) {
|
|
2488
2614
|
throw new Error("Returned objects have no keys.");
|
|
2489
2615
|
}
|
|
2490
2616
|
scriptCounter++;
|
|
2491
|
-
const indicatorName = `
|
|
2617
|
+
const indicatorName = `_custom_script_active`;
|
|
2492
2618
|
const figures = seriesKeys.map((key, i) => ({
|
|
2493
2619
|
key,
|
|
2494
2620
|
title: `${key}: `,
|
|
@@ -2500,7 +2626,7 @@ ${code}`
|
|
|
2500
2626
|
shortName: (scriptName.trim() || `Script #${scriptCounter}`) + (placement === "main" ? " (Main)" : " (Sub)"),
|
|
2501
2627
|
calcParams: parsedParams,
|
|
2502
2628
|
figures,
|
|
2503
|
-
calc: () =>
|
|
2629
|
+
calc: (liveDataList) => compiledFn(...shadowValues, TA_default, liveDataList, parsedParams),
|
|
2504
2630
|
extendData: {
|
|
2505
2631
|
isCustomScript: true,
|
|
2506
2632
|
code,
|
|
@@ -2526,7 +2652,7 @@ ${code}`
|
|
|
2526
2652
|
paneId = chart.createIndicator({ name: indicatorName });
|
|
2527
2653
|
}
|
|
2528
2654
|
activeIdRef.current = typeof paneId === "string" ? paneId : null;
|
|
2529
|
-
|
|
2655
|
+
setActiveName(indicatorName);
|
|
2530
2656
|
const title = scriptName.trim() || `Script #${scriptCounter}`;
|
|
2531
2657
|
setStatus(
|
|
2532
2658
|
`${title} applied to ${placement === "main" ? "Main Chart" : "Sub Pane"} \u2014 ${seriesKeys.length} series: ${seriesKeys.join(", ")}`
|
|
@@ -2548,7 +2674,7 @@ ${code}`
|
|
|
2548
2674
|
} catch {
|
|
2549
2675
|
}
|
|
2550
2676
|
activeIdRef.current = null;
|
|
2551
|
-
|
|
2677
|
+
setActiveName(null);
|
|
2552
2678
|
setStatus("Indicator removed.");
|
|
2553
2679
|
}
|
|
2554
2680
|
}, [state.chart]);
|
|
@@ -2665,52 +2791,48 @@ function useAlerts() {
|
|
|
2665
2791
|
const { alertTriggeredListenerRef } = useKlinechartsUIDispatch();
|
|
2666
2792
|
const alerts = state.alerts;
|
|
2667
2793
|
const addAlert = useCallback(
|
|
2668
|
-
(price, condition, message) => {
|
|
2794
|
+
(price, condition, message, extendData) => {
|
|
2669
2795
|
const id = `alert_${++alertCounter}`;
|
|
2796
|
+
const precision = state.chart?.getSymbol()?.pricePrecision ?? 2;
|
|
2797
|
+
const resolvedExtendData = {
|
|
2798
|
+
...extendData,
|
|
2799
|
+
text: extendData?.text ?? message ?? price.toFixed(precision)
|
|
2800
|
+
};
|
|
2670
2801
|
const alert = {
|
|
2671
2802
|
id,
|
|
2672
2803
|
price,
|
|
2673
2804
|
condition,
|
|
2674
2805
|
message,
|
|
2675
|
-
triggered: false
|
|
2806
|
+
triggered: false,
|
|
2807
|
+
extendData: resolvedExtendData
|
|
2676
2808
|
};
|
|
2677
|
-
dispatch({ type: "
|
|
2809
|
+
dispatch({ type: "ADD_ALERT", alert });
|
|
2678
2810
|
if (state.chart) {
|
|
2811
|
+
ensureAlertLineRegistered();
|
|
2679
2812
|
state.chart.createOverlay({
|
|
2680
|
-
name: "
|
|
2813
|
+
name: "alertLine",
|
|
2681
2814
|
id,
|
|
2682
2815
|
groupId: "price_alerts",
|
|
2683
2816
|
points: [{ value: price }],
|
|
2684
|
-
|
|
2685
|
-
line: {
|
|
2686
|
-
style: "dashed",
|
|
2687
|
-
color: "#ff9800",
|
|
2688
|
-
size: 1
|
|
2689
|
-
}
|
|
2690
|
-
},
|
|
2817
|
+
extendData: resolvedExtendData,
|
|
2691
2818
|
lock: true
|
|
2692
2819
|
});
|
|
2693
2820
|
}
|
|
2694
2821
|
return id;
|
|
2695
2822
|
},
|
|
2696
|
-
[state.chart,
|
|
2823
|
+
[state.chart, dispatch]
|
|
2697
2824
|
);
|
|
2698
2825
|
const removeAlert = useCallback(
|
|
2699
2826
|
(id) => {
|
|
2700
|
-
dispatch({
|
|
2701
|
-
type: "SET_ALERTS",
|
|
2702
|
-
alerts: state.alerts.filter((a) => a.id !== id)
|
|
2703
|
-
});
|
|
2827
|
+
dispatch({ type: "REMOVE_ALERT", id });
|
|
2704
2828
|
state.chart?.removeOverlay({ id });
|
|
2705
2829
|
},
|
|
2706
|
-
[state.chart,
|
|
2830
|
+
[state.chart, dispatch]
|
|
2707
2831
|
);
|
|
2708
2832
|
const clearAlerts = useCallback(() => {
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
dispatch({ type: "SET_ALERTS", alerts: [] });
|
|
2713
|
-
}, [state.chart, state.alerts, dispatch]);
|
|
2833
|
+
state.chart?.removeOverlay({ groupId: "price_alerts" });
|
|
2834
|
+
dispatch({ type: "CLEAR_ALERTS" });
|
|
2835
|
+
}, [state.chart, dispatch]);
|
|
2714
2836
|
const onAlertTriggered = useCallback(
|
|
2715
2837
|
(callback) => {
|
|
2716
2838
|
alertTriggeredListenerRef.current = callback;
|
|
@@ -2894,6 +3016,7 @@ function useReplay() {
|
|
|
2894
3016
|
);
|
|
2895
3017
|
const startReplay = useCallback(() => {
|
|
2896
3018
|
if (!state.chart) return;
|
|
3019
|
+
if (isReplaying) return;
|
|
2897
3020
|
const dataList = state.chart.getDataList();
|
|
2898
3021
|
if (!dataList || dataList.length === 0) return;
|
|
2899
3022
|
replaySavedDataRef.current = [...dataList];
|
|
@@ -2909,7 +3032,12 @@ function useReplay() {
|
|
|
2909
3032
|
});
|
|
2910
3033
|
state.chart.clearData?.();
|
|
2911
3034
|
startInterval(speed);
|
|
2912
|
-
}, [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]);
|
|
2913
3041
|
const stopReplay = useCallback(() => {
|
|
2914
3042
|
if (!state.chart) return;
|
|
2915
3043
|
clearInterval_();
|
|
@@ -3005,24 +3133,29 @@ var DEFAULT_COLORS = [
|
|
|
3005
3133
|
"#ff5722",
|
|
3006
3134
|
"#8bc34a"
|
|
3007
3135
|
];
|
|
3008
|
-
var colorIndex = 0;
|
|
3009
|
-
var compareCounter = 0;
|
|
3010
3136
|
function useCompare() {
|
|
3011
3137
|
const { state, datafeed } = useKlinechartsUI();
|
|
3012
3138
|
const [symbols, setSymbols] = useState([]);
|
|
3013
3139
|
const indicatorsRef = useRef(/* @__PURE__ */ new Map());
|
|
3140
|
+
const instanceSalt = useId().replace(/[^a-zA-Z0-9]/g, "");
|
|
3014
3141
|
const addSymbol = useCallback(
|
|
3015
3142
|
async (ticker, color) => {
|
|
3016
3143
|
if (!state.chart || !datafeed) return;
|
|
3017
3144
|
if (indicatorsRef.current.has(ticker)) return;
|
|
3018
|
-
const assignedColor = color ?? DEFAULT_COLORS[
|
|
3145
|
+
const assignedColor = color ?? DEFAULT_COLORS[symbols.length % DEFAULT_COLORS.length];
|
|
3019
3146
|
const mainDataList = state.chart.getDataList();
|
|
3020
3147
|
if (!mainDataList || mainDataList.length === 0) return;
|
|
3021
3148
|
const from = mainDataList[0].timestamp;
|
|
3022
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
|
+
};
|
|
3023
3156
|
const compareData = await datafeed.getHistoryKLineData(
|
|
3024
|
-
|
|
3025
|
-
|
|
3157
|
+
symbol,
|
|
3158
|
+
state.period,
|
|
3026
3159
|
from,
|
|
3027
3160
|
to
|
|
3028
3161
|
);
|
|
@@ -3041,13 +3174,7 @@ function useCompare() {
|
|
|
3041
3174
|
}
|
|
3042
3175
|
if (basePrice === null || basePrice === 0) return;
|
|
3043
3176
|
const bp = basePrice;
|
|
3044
|
-
const
|
|
3045
|
-
const close = compareMap.get(mainBar.timestamp);
|
|
3046
|
-
return {
|
|
3047
|
-
pct: close != null ? (close - bp) / bp * 100 : NaN
|
|
3048
|
-
};
|
|
3049
|
-
});
|
|
3050
|
-
const indicatorName = `__cmp_${++compareCounter}_${ticker}`;
|
|
3177
|
+
const indicatorName = `__cmp_${instanceSalt}_${ticker}`;
|
|
3051
3178
|
registerIndicator({
|
|
3052
3179
|
name: indicatorName,
|
|
3053
3180
|
shortName: ticker,
|
|
@@ -3059,7 +3186,16 @@ function useCompare() {
|
|
|
3059
3186
|
styles: () => ({ color: assignedColor })
|
|
3060
3187
|
}
|
|
3061
3188
|
],
|
|
3062
|
-
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
|
+
}
|
|
3063
3199
|
});
|
|
3064
3200
|
const paneId = state.chart.createIndicator(
|
|
3065
3201
|
{ name: indicatorName },
|
|
@@ -3077,7 +3213,7 @@ function useCompare() {
|
|
|
3077
3213
|
];
|
|
3078
3214
|
});
|
|
3079
3215
|
},
|
|
3080
|
-
[state.chart, state.period, datafeed]
|
|
3216
|
+
[state.chart, state.symbol, state.period, datafeed, symbols.length, instanceSalt]
|
|
3081
3217
|
);
|
|
3082
3218
|
const removeSymbol = useCallback(
|
|
3083
3219
|
(ticker) => {
|
|
@@ -3287,13 +3423,11 @@ function useAnnotations() {
|
|
|
3287
3423
|
[state.chart]
|
|
3288
3424
|
);
|
|
3289
3425
|
const clearAnnotations = useCallback(() => {
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
});
|
|
3296
|
-
}, [state.chart]);
|
|
3426
|
+
for (const annotation of annotations) {
|
|
3427
|
+
state.chart?.removeOverlay({ id: annotation.id });
|
|
3428
|
+
}
|
|
3429
|
+
setAnnotations([]);
|
|
3430
|
+
}, [state.chart, annotations]);
|
|
3297
3431
|
useEffect(() => {
|
|
3298
3432
|
return () => {
|
|
3299
3433
|
state.chart?.removeOverlay({ groupId: "annotations" });
|
|
@@ -3372,6 +3506,6 @@ function createDataLoader(datafeed, dispatch) {
|
|
|
3372
3506
|
};
|
|
3373
3507
|
}
|
|
3374
3508
|
|
|
3375
|
-
export { CANDLE_TYPES, COMPARE_RULES, DEFAULT_PERIODS, DRAWING_CATEGORIES, INDICATOR_PARAMS, KlinechartsUIDispatchContext, KlinechartsUIProvider, KlinechartsUIStateContext, MAIN_INDICATORS, PRICE_AXIS_TYPES, SUB_INDICATORS, TIMEZONES, TOOLTIP_SHOW_RULES, YAXIS_POSITIONS, createDataLoader, useAlerts, useAnnotations, useCompare, useCrosshair, useDataExport, useDrawingTools, useFullscreen, useIndicators, useKlinechartsUI, useKlinechartsUILoading, useKlinechartsUISettings, useKlinechartsUITheme, useLayoutManager, useMeasure, useOrderLines, usePeriods, useReplay, useScreenshot, useScriptEditor, useSymbolSearch, useTimezone, useUndoRedo, useWatchlist };
|
|
3509
|
+
export { CANDLE_TYPES, COMPARE_RULES, DEFAULT_PERIODS, DRAWING_CATEGORIES, INDICATOR_PARAMS, KlinechartsUIDispatchContext, KlinechartsUIProvider, KlinechartsUIStateContext, MAIN_INDICATORS, PRICE_AXIS_TYPES, SUB_INDICATORS, TIMEZONES, TOOLTIP_SHOW_RULES, YAXIS_POSITIONS, createDataLoader, useAlerts, useAnnotations, useChartAxes, useCompare, useCrosshair, useDataExport, useDrawingTools, useFullscreen, useHotkeys, useIndicators, useKlinechartsUI, useKlinechartsUILoading, useKlinechartsUISettings, useKlinechartsUITheme, useLayoutManager, useMeasure, useOrderLines, usePeriods, useReplay, useScreenshot, useScriptEditor, useSymbolSearch, useTimezone, useUndoRedo, useWatchlist };
|
|
3376
3510
|
//# sourceMappingURL=index.js.map
|
|
3377
3511
|
//# sourceMappingURL=index.js.map
|