viainti-chart 1.0.2 → 1.0.5
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 +31 -2
- package/dist/index.cjs +438 -235
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +438 -235
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -140,8 +140,20 @@ function BsArrowRepeat (props) {
|
|
|
140
140
|
return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8"},"child":[]}]})(props);
|
|
141
141
|
}function BsDiagram3 (props) {
|
|
142
142
|
return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"fillRule":"evenodd","d":"M6 3.5A1.5 1.5 0 0 1 7.5 2h1A1.5 1.5 0 0 1 10 3.5v1A1.5 1.5 0 0 1 8.5 6v1H14a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 2 7h5.5V6A1.5 1.5 0 0 1 6 4.5zM8.5 5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5zM0 11.5A1.5 1.5 0 0 1 1.5 10h1A1.5 1.5 0 0 1 4 11.5v1A1.5 1.5 0 0 1 2.5 14h-1A1.5 1.5 0 0 1 0 12.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm4.5.5A1.5 1.5 0 0 1 7.5 10h1a1.5 1.5 0 0 1 1.5 1.5v1A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm4.5.5a1.5 1.5 0 0 1 1.5-1.5h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5z"},"child":[]}]})(props);
|
|
143
|
+
}function BsEmojiAngry (props) {
|
|
144
|
+
return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"},"child":[]},{"tag":"path","attr":{"d":"M4.285 12.433a.5.5 0 0 0 .683-.183A3.5 3.5 0 0 1 8 10.5c1.295 0 2.426.703 3.032 1.75a.5.5 0 0 0 .866-.5A4.5 4.5 0 0 0 8 9.5a4.5 4.5 0 0 0-3.898 2.25.5.5 0 0 0 .183.683m6.991-8.38a.5.5 0 1 1 .448.894l-1.009.504c.176.27.285.64.285 1.049 0 .828-.448 1.5-1 1.5s-1-.672-1-1.5c0-.247.04-.48.11-.686a.502.502 0 0 1 .166-.761zm-6.552 0a.5.5 0 0 0-.448.894l1.009.504A1.94 1.94 0 0 0 5 6.5C5 7.328 5.448 8 6 8s1-.672 1-1.5c0-.247-.04-.48-.11-.686a.502.502 0 0 0-.166-.761z"},"child":[]}]})(props);
|
|
145
|
+
}function BsEmojiDizzy (props) {
|
|
146
|
+
return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"},"child":[]},{"tag":"path","attr":{"d":"M9.146 5.146a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 0 1 .708.708l-.647.646.647.646a.5.5 0 0 1-.708.708l-.646-.647-.646.647a.5.5 0 1 1-.708-.708l.647-.646-.647-.646a.5.5 0 0 1 0-.708m-5 0a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 1 1 .708.708l-.647.646.647.646a.5.5 0 1 1-.708.708L5.5 7.207l-.646.647a.5.5 0 1 1-.708-.708l.647-.646-.647-.646a.5.5 0 0 1 0-.708M10 11a2 2 0 1 1-4 0 2 2 0 0 1 4 0"},"child":[]}]})(props);
|
|
147
|
+
}function BsEmojiHeartEyes (props) {
|
|
148
|
+
return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"},"child":[]},{"tag":"path","attr":{"d":"M11.315 10.014a.5.5 0 0 1 .548.736A4.5 4.5 0 0 1 7.965 13a4.5 4.5 0 0 1-3.898-2.25.5.5 0 0 1 .548-.736h.005l.017.005.067.015.252.055c.215.046.515.108.857.169.693.124 1.522.242 2.152.242s1.46-.118 2.152-.242a27 27 0 0 0 1.109-.224l.067-.015.017-.004.005-.002zM4.756 4.566c.763-1.424 4.02-.12.952 3.434-4.496-1.596-2.35-4.298-.952-3.434m6.488 0c1.398-.864 3.544 1.838-.952 3.434-3.067-3.554.19-4.858.952-3.434"},"child":[]}]})(props);
|
|
149
|
+
}function BsEmojiLaughing (props) {
|
|
150
|
+
return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"},"child":[]},{"tag":"path","attr":{"d":"M12.331 9.5a1 1 0 0 1 0 1A5 5 0 0 1 8 13a5 5 0 0 1-4.33-2.5A1 1 0 0 1 4.535 9h6.93a1 1 0 0 1 .866.5M7 6.5c0 .828-.448 0-1 0s-1 .828-1 0S5.448 5 6 5s1 .672 1 1.5m4 0c0 .828-.448 0-1 0s-1 .828-1 0S9.448 5 10 5s1 .672 1 1.5"},"child":[]}]})(props);
|
|
151
|
+
}function BsEmojiNeutral (props) {
|
|
152
|
+
return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"},"child":[]},{"tag":"path","attr":{"d":"M4 10.5a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 0-1h-7a.5.5 0 0 0-.5.5m3-4C7 5.672 6.552 5 6 5s-1 .672-1 1.5S5.448 8 6 8s1-.672 1-1.5m4 0c0-.828-.448-1.5-1-1.5s-1 .672-1 1.5S9.448 8 10 8s1-.672 1-1.5"},"child":[]}]})(props);
|
|
143
153
|
}function BsEmojiSmile (props) {
|
|
144
154
|
return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"},"child":[]},{"tag":"path","attr":{"d":"M4.285 9.567a.5.5 0 0 1 .683.183A3.5 3.5 0 0 0 8 11.5a3.5 3.5 0 0 0 3.032-1.75.5.5 0 1 1 .866.5A4.5 4.5 0 0 1 8 12.5a4.5 4.5 0 0 1-3.898-2.25.5.5 0 0 1 .183-.683M7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5m4 0c0 .828-.448 1.5-1 1.5s-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5"},"child":[]}]})(props);
|
|
155
|
+
}function BsEmojiSunglasses (props) {
|
|
156
|
+
return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M4.968 9.75a.5.5 0 1 0-.866.5A4.5 4.5 0 0 0 8 12.5a4.5 4.5 0 0 0 3.898-2.25.5.5 0 1 0-.866-.5A3.5 3.5 0 0 1 8 11.5a3.5 3.5 0 0 1-3.032-1.75M7 5.116V5a1 1 0 0 0-1-1H3.28a1 1 0 0 0-.97 1.243l.311 1.242A2 2 0 0 0 4.561 8H5a2 2 0 0 0 1.994-1.839A3 3 0 0 1 8 6c.393 0 .74.064 1.006.161A2 2 0 0 0 11 8h.438a2 2 0 0 0 1.94-1.515l.311-1.242A1 1 0 0 0 12.72 4H10a1 1 0 0 0-1 1v.116A4.2 4.2 0 0 0 8 5c-.35 0-.69.04-1 .116"},"child":[]},{"tag":"path","attr":{"d":"M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-1 0A7 7 0 1 0 1 8a7 7 0 0 0 14 0"},"child":[]}]})(props);
|
|
145
157
|
}function BsEyeSlash (props) {
|
|
146
158
|
return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M13.359 11.238C15.06 9.72 16 8 16 8s-3-5.5-8-5.5a7 7 0 0 0-2.79.588l.77.771A6 6 0 0 1 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755q-.247.248-.517.486z"},"child":[]},{"tag":"path","attr":{"d":"M11.297 9.176a3.5 3.5 0 0 0-4.474-4.474l.823.823a2.5 2.5 0 0 1 2.829 2.829zm-2.943 1.299.822.822a3.5 3.5 0 0 1-4.474-4.474l.823.823a2.5 2.5 0 0 0 2.829 2.829"},"child":[]},{"tag":"path","attr":{"d":"M3.35 5.47q-.27.24-.518.487A13 13 0 0 0 1.172 8l.195.288c.335.48.83 1.12 1.465 1.755C4.121 11.332 5.881 12.5 8 12.5c.716 0 1.39-.133 2.02-.36l.77.772A7 7 0 0 1 8 13.5C3 13.5 0 8 0 8s.939-1.721 2.641-3.238l.708.709zm10.296 8.884-12-12 .708-.708 12 12z"},"child":[]}]})(props);
|
|
147
159
|
}function BsGear (props) {
|
|
@@ -843,7 +855,7 @@ function createStore(creator) {
|
|
|
843
855
|
const useChartStore = createStore((set) => ({
|
|
844
856
|
data: [],
|
|
845
857
|
visibleData: [],
|
|
846
|
-
timeframe: '
|
|
858
|
+
timeframe: '12h',
|
|
847
859
|
zoomLevel: 1,
|
|
848
860
|
panOffset: 0,
|
|
849
861
|
indicators: [],
|
|
@@ -922,7 +934,6 @@ const useChartStore = createStore((set) => ({
|
|
|
922
934
|
toggleDrawingsHidden: () => set((state) => ({ drawingsHidden: !state.drawingsHidden }))
|
|
923
935
|
}));
|
|
924
936
|
|
|
925
|
-
const EMOJIS = ['⭐', '🔥', '🚀', '✅', '❌', '💎', '🐂', '🐻', '📈', '📉'];
|
|
926
937
|
const cursorOptions = [
|
|
927
938
|
{ type: 'cross', icon: React.createElement(BsCursor, null), label: 'Cross' },
|
|
928
939
|
{ type: 'dot', icon: React.createElement("div", { style: { width: '6px', height: '6px', borderRadius: '50%', background: 'currentColor' } }), label: 'Dot' },
|
|
@@ -962,6 +973,15 @@ const measurementOptions = [
|
|
|
962
973
|
{ tool: 'fibonacci', icon: React.createElement(BsGraphUp, null), label: 'Fibonacci' },
|
|
963
974
|
{ tool: 'ruler', icon: React.createElement(BsGraphDown, null), label: 'Regla' }
|
|
964
975
|
];
|
|
976
|
+
const emojiOptions = [
|
|
977
|
+
{ label: 'Smile', icon: React.createElement(BsEmojiSmile, null), value: '🙂' },
|
|
978
|
+
{ label: 'Laugh', icon: React.createElement(BsEmojiLaughing, null), value: '😂' },
|
|
979
|
+
{ label: 'Heart Eyes', icon: React.createElement(BsEmojiHeartEyes, null), value: '😍' },
|
|
980
|
+
{ label: 'Sunglasses', icon: React.createElement(BsEmojiSunglasses, null), value: '😎' },
|
|
981
|
+
{ label: 'Dizzy', icon: React.createElement(BsEmojiDizzy, null), value: '😵' },
|
|
982
|
+
{ label: 'Neutral', icon: React.createElement(BsEmojiNeutral, null), value: '😐' },
|
|
983
|
+
{ label: 'Angry', icon: React.createElement(BsEmojiAngry, null), value: '😠' }
|
|
984
|
+
];
|
|
965
985
|
const popoverStyle = {
|
|
966
986
|
position: 'absolute',
|
|
967
987
|
left: '64px',
|
|
@@ -980,6 +1000,15 @@ const DrawingToolbar = () => {
|
|
|
980
1000
|
const { activeTool, cursorType, strokeColor, strokeWidth, setActiveTool, setCursorType, setStrokeColor, setStrokeWidth, clearDrawings, setSelectedEmoji, setIsDrawing } = useChartStore();
|
|
981
1001
|
const [openMenu, setOpenMenu] = React.useState(null);
|
|
982
1002
|
const [showEmojiPicker, setShowEmojiPicker] = React.useState(false);
|
|
1003
|
+
const [isCompact, setIsCompact] = React.useState(false);
|
|
1004
|
+
React.useEffect(() => {
|
|
1005
|
+
const handleResize = () => {
|
|
1006
|
+
setIsCompact(window.innerWidth < 768);
|
|
1007
|
+
};
|
|
1008
|
+
handleResize();
|
|
1009
|
+
window.addEventListener('resize', handleResize);
|
|
1010
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
1011
|
+
}, []);
|
|
983
1012
|
const LINE_COLORS = ['#8ab4ff', '#f472b6', '#34d399', '#facc15', '#f87171', '#e2e8f0'];
|
|
984
1013
|
const WIDTH_OPTIONS = [1, 1.5, 2.5, 3.5];
|
|
985
1014
|
const handleSelectTool = (tool) => {
|
|
@@ -1075,14 +1104,27 @@ const DrawingToolbar = () => {
|
|
|
1075
1104
|
title: 'Limpiar todo'
|
|
1076
1105
|
}
|
|
1077
1106
|
];
|
|
1078
|
-
|
|
1107
|
+
const filteredButtons = isCompact
|
|
1108
|
+
? buttonConfig.filter(btn => !['pitchforks', 'measurements'].includes(btn.key))
|
|
1109
|
+
: buttonConfig;
|
|
1110
|
+
const buildPopoverStyle = (custom) => ({
|
|
1111
|
+
...popoverStyle,
|
|
1112
|
+
...(isCompact
|
|
1113
|
+
? { left: '0px', top: 'calc(100% + 12px)', transform: 'none', width: 'min(360px, 92vw)' }
|
|
1114
|
+
: {}),
|
|
1115
|
+
...custom
|
|
1116
|
+
});
|
|
1117
|
+
return (React.createElement("div", { style: { position: 'relative', height: '100%', width: '100%' } },
|
|
1079
1118
|
React.createElement("div", { style: {
|
|
1080
|
-
height: '100%',
|
|
1119
|
+
height: isCompact ? 'auto' : '100%',
|
|
1120
|
+
width: '100%',
|
|
1081
1121
|
display: 'flex',
|
|
1082
|
-
flexDirection: 'column',
|
|
1122
|
+
flexDirection: isCompact ? 'row' : 'column',
|
|
1083
1123
|
alignItems: 'center',
|
|
1084
|
-
gap: '12px'
|
|
1085
|
-
|
|
1124
|
+
gap: '12px',
|
|
1125
|
+
overflowX: isCompact ? 'auto' : 'visible',
|
|
1126
|
+
padding: isCompact ? '12px 6px' : 0
|
|
1127
|
+
} }, filteredButtons.map((btn) => (React.createElement("div", { key: btn.key, style: { position: 'relative' } },
|
|
1086
1128
|
React.createElement("button", { onClick: btn.onClick, title: btn.title, "aria-label": btn.title, style: {
|
|
1087
1129
|
width: '46px',
|
|
1088
1130
|
height: '46px',
|
|
@@ -1097,7 +1139,7 @@ const DrawingToolbar = () => {
|
|
|
1097
1139
|
fontSize: '18px',
|
|
1098
1140
|
transition: 'all 0.2s'
|
|
1099
1141
|
} }, btn.icon),
|
|
1100
|
-
btn.key === 'cursor' && openMenu === 'cursor' && (React.createElement("div", { style: {
|
|
1142
|
+
btn.key === 'cursor' && openMenu === 'cursor' && (React.createElement("div", { style: buildPopoverStyle({ gridTemplateColumns: 'repeat(2, 1fr)', width: isCompact ? 'min(320px, 92vw)' : '200px' }) }, cursorOptions.map(option => (React.createElement("button", { key: option.type, onClick: () => handleCursorSelect(option.type), style: {
|
|
1101
1143
|
width: '84px',
|
|
1102
1144
|
height: '60px',
|
|
1103
1145
|
borderRadius: '12px',
|
|
@@ -1113,7 +1155,7 @@ const DrawingToolbar = () => {
|
|
|
1113
1155
|
}, title: option.label },
|
|
1114
1156
|
React.createElement("span", { style: { fontSize: '18px', display: 'flex', alignItems: 'center', justifyContent: 'center' } }, option.icon),
|
|
1115
1157
|
React.createElement("span", { style: { fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase' } }, option.label)))))),
|
|
1116
|
-
btn.key === 'lines' && openMenu === 'lines' && (React.createElement("div", { style: {
|
|
1158
|
+
btn.key === 'lines' && openMenu === 'lines' && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(360px, 92vw)' : '280px' }) },
|
|
1117
1159
|
React.createElement("p", { style: { margin: 0, fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Lines"),
|
|
1118
1160
|
React.createElement("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '8px' } }, lineOptions.map(option => (React.createElement("button", { key: option.tool, onClick: () => handleSelectTool(option.tool), style: {
|
|
1119
1161
|
borderRadius: '12px',
|
|
@@ -1153,7 +1195,7 @@ const DrawingToolbar = () => {
|
|
|
1153
1195
|
} },
|
|
1154
1196
|
width,
|
|
1155
1197
|
"px"))))))),
|
|
1156
|
-
btn.key === 'shapes' && openMenu === 'shapes' && (React.createElement("div", { style: {
|
|
1198
|
+
btn.key === 'shapes' && openMenu === 'shapes' && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(320px, 92vw)' : '200px' }) },
|
|
1157
1199
|
React.createElement("p", { style: { margin: 0, fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Shapes"),
|
|
1158
1200
|
React.createElement("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '8px' } }, shapeOptions.map(option => (React.createElement("button", { key: option.tool, onClick: () => handleSelectTool(option.tool), style: {
|
|
1159
1201
|
borderRadius: '12px',
|
|
@@ -1170,7 +1212,7 @@ const DrawingToolbar = () => {
|
|
|
1170
1212
|
}, title: option.label },
|
|
1171
1213
|
React.createElement("span", { style: { fontSize: '16px' } }, option.icon),
|
|
1172
1214
|
React.createElement("span", { style: { fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.08em' } }, option.label))))))),
|
|
1173
|
-
btn.key === 'channels' && openMenu === 'channels' && (React.createElement("div", { style: {
|
|
1215
|
+
btn.key === 'channels' && openMenu === 'channels' && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(340px, 92vw)' : '220px' }) },
|
|
1174
1216
|
React.createElement("p", { style: { margin: 0, fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Channels"),
|
|
1175
1217
|
React.createElement("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '8px' } }, channelOptions.map(option => (React.createElement("button", { key: option.tool, onClick: () => handleSelectTool(option.tool), style: {
|
|
1176
1218
|
borderRadius: '12px',
|
|
@@ -1187,7 +1229,7 @@ const DrawingToolbar = () => {
|
|
|
1187
1229
|
}, title: option.label },
|
|
1188
1230
|
React.createElement("span", { style: { fontSize: '16px' } }, option.icon),
|
|
1189
1231
|
React.createElement("span", { style: { fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.08em' } }, option.label))))))),
|
|
1190
|
-
btn.key === 'pitchforks' && openMenu === 'pitchforks' && (React.createElement("div", { style: {
|
|
1232
|
+
btn.key === 'pitchforks' && openMenu === 'pitchforks' && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(320px, 92vw)' : '200px' }) },
|
|
1191
1233
|
React.createElement("p", { style: { margin: 0, fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Pitchforks"),
|
|
1192
1234
|
React.createElement("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '8px' } }, pitchforkOptions.map(option => (React.createElement("button", { key: option.tool, onClick: () => handleSelectTool(option.tool), style: {
|
|
1193
1235
|
borderRadius: '12px',
|
|
@@ -1204,7 +1246,7 @@ const DrawingToolbar = () => {
|
|
|
1204
1246
|
}, title: option.label },
|
|
1205
1247
|
React.createElement("span", { style: { fontSize: '16px' } }, option.icon),
|
|
1206
1248
|
React.createElement("span", { style: { fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.08em', textAlign: 'center' } }, option.label))))))),
|
|
1207
|
-
btn.key === 'measurements' && openMenu === 'measurements' && (React.createElement("div", { style: {
|
|
1249
|
+
btn.key === 'measurements' && openMenu === 'measurements' && (React.createElement("div", { style: buildPopoverStyle({ gridTemplateColumns: 'repeat(2, 1fr)', width: isCompact ? 'min(280px, 80vw)' : '160px' }) }, measurementOptions.map(option => (React.createElement("button", { key: option.tool, onClick: () => handleSelectTool(option.tool), style: {
|
|
1208
1250
|
width: '64px',
|
|
1209
1251
|
height: '54px',
|
|
1210
1252
|
borderRadius: '12px',
|
|
@@ -1221,41 +1263,24 @@ const DrawingToolbar = () => {
|
|
|
1221
1263
|
}, title: option.label },
|
|
1222
1264
|
React.createElement("span", { style: { fontSize: '16px' } }, option.icon),
|
|
1223
1265
|
React.createElement("span", null, option.label)))))),
|
|
1224
|
-
btn.key === 'emoji' && showEmojiPicker && (React.createElement("div", { style: {
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
transform: 'translateY(-50%)',
|
|
1229
|
-
zIndex: 1100,
|
|
1230
|
-
background: '#020617',
|
|
1231
|
-
borderRadius: '14px',
|
|
1232
|
-
overflow: 'hidden',
|
|
1233
|
-
boxShadow: '0 18px 40px rgba(0,0,0,0.65)',
|
|
1234
|
-
padding: '10px',
|
|
1235
|
-
border: '1px solid #1f2937'
|
|
1236
|
-
} },
|
|
1237
|
-
React.createElement("div", { style: {
|
|
1238
|
-
display: 'grid',
|
|
1239
|
-
gridTemplateColumns: 'repeat(3, 1fr)',
|
|
1240
|
-
gap: '6px'
|
|
1241
|
-
} }, EMOJIS.map((emoji) => (React.createElement("button", { key: emoji, onClick: () => {
|
|
1242
|
-
setSelectedEmoji(emoji);
|
|
1266
|
+
btn.key === 'emoji' && showEmojiPicker && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(320px, 92vw)' : '220px' }) },
|
|
1267
|
+
React.createElement("p", { style: { margin: '0 0 6px', fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Emojis"),
|
|
1268
|
+
React.createElement("div", { style: { display: 'grid', gridTemplateColumns: isCompact ? 'repeat(4, minmax(0, 1fr))' : 'repeat(3, minmax(0, 1fr))', gap: '8px' } }, emojiOptions.map(option => (React.createElement("button", { key: option.label, onClick: () => {
|
|
1269
|
+
setSelectedEmoji(option.value);
|
|
1243
1270
|
setActiveTool('icon');
|
|
1244
1271
|
setShowEmojiPicker(false);
|
|
1245
1272
|
}, style: {
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
background: '#111827',
|
|
1273
|
+
height: '48px',
|
|
1274
|
+
borderRadius: '12px',
|
|
1249
1275
|
border: '1px solid #1f2937',
|
|
1250
|
-
|
|
1251
|
-
|
|
1276
|
+
background: 'rgba(2,6,23,0.65)',
|
|
1277
|
+
color: '#f8fafc',
|
|
1252
1278
|
display: 'flex',
|
|
1253
1279
|
alignItems: 'center',
|
|
1254
1280
|
justifyContent: 'center',
|
|
1255
|
-
fontSize: '
|
|
1256
|
-
|
|
1257
|
-
} },
|
|
1258
|
-
React.createElement("span", null, emoji)))))))))))));
|
|
1281
|
+
fontSize: '22px',
|
|
1282
|
+
cursor: 'pointer'
|
|
1283
|
+
}, title: option.label, "aria-label": option.label }, option.icon))))))))))));
|
|
1259
1284
|
};
|
|
1260
1285
|
|
|
1261
1286
|
const drawRoundedRect = (ctx, x, y, width, height, radius = 8) => {
|
|
@@ -1992,23 +2017,23 @@ const LINE_TOOL_TYPES = [
|
|
|
1992
2017
|
const LINE_TOOL_SET = new Set(LINE_TOOL_TYPES);
|
|
1993
2018
|
const THEME_PRESETS = {
|
|
1994
2019
|
dark: {
|
|
1995
|
-
pageBg: '#
|
|
1996
|
-
heroFrom: '#
|
|
1997
|
-
heroTo: '#
|
|
1998
|
-
panelBg: 'rgba(
|
|
1999
|
-
panelBorder: '#
|
|
2000
|
-
cardBg: 'rgba(
|
|
2001
|
-
cardBorder: '#
|
|
2020
|
+
pageBg: '#000000',
|
|
2021
|
+
heroFrom: '#050505',
|
|
2022
|
+
heroTo: '#000000',
|
|
2023
|
+
panelBg: 'rgba(0,0,0,0.92)',
|
|
2024
|
+
panelBorder: '#0f0f0f',
|
|
2025
|
+
cardBg: 'rgba(0,0,0,0.88)',
|
|
2026
|
+
cardBorder: '#050505',
|
|
2002
2027
|
textPrimary: '#f8fafc',
|
|
2003
|
-
textSecondary: '#
|
|
2028
|
+
textSecondary: '#9ca3af',
|
|
2004
2029
|
accent: '#2563eb',
|
|
2005
|
-
accentSoft: 'rgba(37,99,235,0.
|
|
2006
|
-
railBg: 'rgba(
|
|
2007
|
-
railBorder: '#
|
|
2008
|
-
plotBg: '#
|
|
2009
|
-
plotBorder: '#
|
|
2010
|
-
scaleBg: 'rgba(
|
|
2011
|
-
overlayBg: 'rgba(
|
|
2030
|
+
accentSoft: 'rgba(37,99,235,0.25)',
|
|
2031
|
+
railBg: 'rgba(0,0,0,0.9)',
|
|
2032
|
+
railBorder: '#080808',
|
|
2033
|
+
plotBg: '#010101',
|
|
2034
|
+
plotBorder: '#0d0d0d',
|
|
2035
|
+
scaleBg: 'rgba(0,0,0,0.92)',
|
|
2036
|
+
overlayBg: 'rgba(0,0,0,0.85)'
|
|
2012
2037
|
},
|
|
2013
2038
|
blue: {
|
|
2014
2039
|
pageBg: '#060e1f',
|
|
@@ -2060,6 +2085,10 @@ const CUSTOM_THEME_FIELDS = [
|
|
|
2060
2085
|
const MIN_CANDLE_PX = 4;
|
|
2061
2086
|
const MAX_CANDLE_PX = 28;
|
|
2062
2087
|
const BUFFER_FRACTION = 0.18;
|
|
2088
|
+
const LEFT_OFFSET_BARS = 2;
|
|
2089
|
+
const RIGHT_OFFSET_BARS = 18;
|
|
2090
|
+
const BAR_BODY_RATIO = 0.78;
|
|
2091
|
+
const GRID_DIVISIONS = 10;
|
|
2063
2092
|
const INERTIA_DURATION_MS = 900;
|
|
2064
2093
|
const ZOOM_MIN = 0.2;
|
|
2065
2094
|
const ZOOM_MAX = 6;
|
|
@@ -2067,6 +2096,42 @@ const easeOutQuad = (t) => 1 - (1 - t) * (1 - t);
|
|
|
2067
2096
|
const easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
2068
2097
|
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
2069
2098
|
const alignStroke = (value) => Math.round(value) + 0.5;
|
|
2099
|
+
const determinePriceFormat = (reference) => {
|
|
2100
|
+
const value = Math.abs(reference);
|
|
2101
|
+
if (!Number.isFinite(value) || value === 0)
|
|
2102
|
+
return { min: 2, max: 6 };
|
|
2103
|
+
if (value < 0.0001)
|
|
2104
|
+
return { min: 6, max: 8 };
|
|
2105
|
+
if (value < 0.001)
|
|
2106
|
+
return { min: 5, max: 7 };
|
|
2107
|
+
if (value < 0.01)
|
|
2108
|
+
return { min: 4, max: 6 };
|
|
2109
|
+
if (value < 0.1)
|
|
2110
|
+
return { min: 4, max: 5 };
|
|
2111
|
+
if (value < 1)
|
|
2112
|
+
return { min: 3, max: 4 };
|
|
2113
|
+
if (value < 100)
|
|
2114
|
+
return { min: 2, max: 3 };
|
|
2115
|
+
return { min: 2, max: 2 };
|
|
2116
|
+
};
|
|
2117
|
+
const getEffectiveBarCount = (count) => Math.max(count + LEFT_OFFSET_BARS + RIGHT_OFFSET_BARS, 1);
|
|
2118
|
+
const getCandleStep = (width, count) => (width <= 0 ? 0 : width / getEffectiveBarCount(count));
|
|
2119
|
+
const getCandleCenter = (index, width, count) => {
|
|
2120
|
+
const step = getCandleStep(width, count);
|
|
2121
|
+
return (index + LEFT_OFFSET_BARS + 0.5) * step;
|
|
2122
|
+
};
|
|
2123
|
+
const clampPixelToChart = (pixel, width) => Math.max(0, Math.min(width, pixel));
|
|
2124
|
+
const pixelToCandleIndex = (pixel, width, count) => {
|
|
2125
|
+
const step = getCandleStep(width, count);
|
|
2126
|
+
if (step === 0)
|
|
2127
|
+
return 0;
|
|
2128
|
+
return pixel / step - LEFT_OFFSET_BARS - 0.5;
|
|
2129
|
+
};
|
|
2130
|
+
const clampCandleIndex = (value, length) => {
|
|
2131
|
+
if (length <= 0)
|
|
2132
|
+
return 0;
|
|
2133
|
+
return clamp(Math.round(value), 0, Math.max(length - 1, 0));
|
|
2134
|
+
};
|
|
2070
2135
|
const createParallelPoints = (start, end, offset) => {
|
|
2071
2136
|
const dx = end.x - start.x;
|
|
2072
2137
|
const dy = end.y - start.y;
|
|
@@ -2156,7 +2221,7 @@ const computeVisibleWindow = (dataset, chartWidth, timeframe, zoomLevel, panOffs
|
|
|
2156
2221
|
}
|
|
2157
2222
|
};
|
|
2158
2223
|
};
|
|
2159
|
-
const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
2224
|
+
const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showStats = true, showHeaderStats = true, showDrawingToolbar = true }) => {
|
|
2160
2225
|
const chartRef = React.useRef(null);
|
|
2161
2226
|
const volumeRef = React.useRef(null);
|
|
2162
2227
|
const gridRef = React.useRef(null);
|
|
@@ -2295,7 +2360,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2295
2360
|
if (!chartWidth)
|
|
2296
2361
|
return;
|
|
2297
2362
|
const candles = getVisibleCandles();
|
|
2298
|
-
const pxPerCandle = chartWidth / Math.max(candles.length || 1, 1);
|
|
2363
|
+
const pxPerCandle = chartWidth / Math.max(getEffectiveBarCount(candles.length || 1), 1);
|
|
2299
2364
|
if (!isFinite(pxPerCandle) || pxPerCandle === 0)
|
|
2300
2365
|
return;
|
|
2301
2366
|
const deltaOffset = deltaPx / Math.max(pxPerCandle, 1e-3);
|
|
@@ -2419,6 +2484,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2419
2484
|
const [priceLabels, setPriceLabels] = React.useState([]);
|
|
2420
2485
|
const [timeLabels, setTimeLabels] = React.useState([]);
|
|
2421
2486
|
const [clickedPrice, setClickedPrice] = React.useState(null);
|
|
2487
|
+
const [crosshairMeta, setCrosshairMeta] = React.useState(null);
|
|
2422
2488
|
const [showConfigPanel, setShowConfigPanel] = React.useState(false);
|
|
2423
2489
|
const [language, setLanguage] = React.useState('en');
|
|
2424
2490
|
const [colorScheme, setColorScheme] = React.useState('green');
|
|
@@ -2438,16 +2504,18 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2438
2504
|
const iconBaseColor = '#f8fafc';
|
|
2439
2505
|
const leftRailBaseBg = themePreset === 'light' ? '#0f172a' : activeTheme.panelBg;
|
|
2440
2506
|
const strings = React.useMemo(() => UI_TEXT[language] ?? UI_TEXT.en, [language]);
|
|
2507
|
+
const compactHeader = !showHeaderStats;
|
|
2441
2508
|
const [isFullscreen, setIsFullscreen] = React.useState(false);
|
|
2442
2509
|
const [selectedCandleIndex, setSelectedCandleIndex] = React.useState(null);
|
|
2443
2510
|
const [seriesType, setSeriesType] = React.useState('candles');
|
|
2444
2511
|
const [showSeriesMenu, setShowSeriesMenu] = React.useState(false);
|
|
2445
2512
|
const [isSeriesLoading, setIsSeriesLoading] = React.useState(false);
|
|
2446
|
-
const [showQuickTips, setShowQuickTips] = React.useState(
|
|
2513
|
+
const [showQuickTips, setShowQuickTips] = React.useState(showHeaderStats);
|
|
2447
2514
|
const [toastMessage, setToastMessage] = React.useState(null);
|
|
2448
2515
|
const [showNoteModal, setShowNoteModal] = React.useState(false);
|
|
2449
2516
|
const [pendingNotePoint, setPendingNotePoint] = React.useState(null);
|
|
2450
2517
|
const [noteDraft, setNoteDraft] = React.useState('');
|
|
2518
|
+
const [priceFormat, setPriceFormat] = React.useState({ min: 2, max: 4 });
|
|
2451
2519
|
const [isMobile, setIsMobile] = React.useState(false);
|
|
2452
2520
|
React.useEffect(() => {
|
|
2453
2521
|
if (!plotAreaRef.current)
|
|
@@ -2470,7 +2538,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2470
2538
|
return () => window.clearTimeout(id);
|
|
2471
2539
|
}, [seriesType, visibleData.length]);
|
|
2472
2540
|
const locale = language === 'es' ? 'es-ES' : 'en-US';
|
|
2473
|
-
const numberFormatter = React.useMemo(() => new Intl.NumberFormat(locale, { minimumFractionDigits:
|
|
2541
|
+
const numberFormatter = React.useMemo(() => new Intl.NumberFormat(locale, { minimumFractionDigits: priceFormat.min, maximumFractionDigits: priceFormat.max }), [locale, priceFormat]);
|
|
2474
2542
|
const shortNumberFormatter = React.useMemo(() => new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }), [locale]);
|
|
2475
2543
|
const timeFormatter = React.useMemo(() => new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit' }), [locale]);
|
|
2476
2544
|
const latencyMs = React.useMemo(() => Math.round(20 + Math.random() * 15), []);
|
|
@@ -2532,9 +2600,14 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2532
2600
|
const priceRange = Math.max(1e-6, maxPrice - minPrice);
|
|
2533
2601
|
const maxVolume = Math.max(bounds.maxVolume, 1);
|
|
2534
2602
|
priceWindowRef.current = { min: minPrice, max: maxPrice };
|
|
2603
|
+
const referenceMagnitude = Math.min(Math.abs(minPrice), Math.abs(maxPrice)) || Math.max(Math.abs(minPrice), Math.abs(maxPrice));
|
|
2604
|
+
const suggestedFormat = determinePriceFormat(referenceMagnitude);
|
|
2605
|
+
setPriceFormat((prev) => (prev.min === suggestedFormat.min && prev.max === suggestedFormat.max ? prev : suggestedFormat));
|
|
2535
2606
|
const chartWidth = Math.max(1, cssWidth - priceScaleWidth);
|
|
2536
2607
|
const chartHeight = Math.max(1, cssHeight - timeScaleHeight - volumeHeight);
|
|
2537
|
-
const
|
|
2608
|
+
const candleStep = getCandleStep(chartWidth, candles.length);
|
|
2609
|
+
const candleWidth = Math.max(1, candleStep * BAR_BODY_RATIO);
|
|
2610
|
+
const centerX = (index) => getCandleCenter(index, chartWidth, candles.length);
|
|
2538
2611
|
// Generate price labels
|
|
2539
2612
|
const priceLabelsArray = [];
|
|
2540
2613
|
for (let i = 0; i <= 10; i++) {
|
|
@@ -2569,9 +2642,11 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2569
2642
|
gridCtx.lineTo(chartWidth, y);
|
|
2570
2643
|
gridCtx.stroke();
|
|
2571
2644
|
}
|
|
2572
|
-
const timeStep = Math.max(1, Math.floor(candles.length /
|
|
2645
|
+
const timeStep = Math.max(1, Math.floor(Math.max(candles.length, 1) / GRID_DIVISIONS));
|
|
2573
2646
|
for (let i = 0; i <= candles.length; i += timeStep) {
|
|
2574
|
-
const x = alignStroke(i *
|
|
2647
|
+
const x = alignStroke((i + LEFT_OFFSET_BARS) * candleStep);
|
|
2648
|
+
if (x < 0 || x > chartWidth)
|
|
2649
|
+
continue;
|
|
2575
2650
|
gridCtx.beginPath();
|
|
2576
2651
|
gridCtx.moveTo(x, 0);
|
|
2577
2652
|
gridCtx.lineTo(x, chartHeight);
|
|
@@ -2597,7 +2672,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2597
2672
|
: minPrice;
|
|
2598
2673
|
chartCtx.beginPath();
|
|
2599
2674
|
candles.forEach((candle, index) => {
|
|
2600
|
-
const x = index
|
|
2675
|
+
const x = centerX(index);
|
|
2601
2676
|
const yClose = ((maxPrice - candle.close) / priceRange) * chartHeight;
|
|
2602
2677
|
if (index === 0) {
|
|
2603
2678
|
chartCtx.moveTo(x, yClose);
|
|
@@ -2605,6 +2680,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2605
2680
|
else if (seriesType === 'step') {
|
|
2606
2681
|
const prev = candles[index - 1];
|
|
2607
2682
|
if (prev) {
|
|
2683
|
+
centerX(index - 1);
|
|
2608
2684
|
const prevY = ((maxPrice - prev.close) / priceRange) * chartHeight;
|
|
2609
2685
|
chartCtx.lineTo(x, prevY);
|
|
2610
2686
|
chartCtx.lineTo(x, yClose);
|
|
@@ -2620,7 +2696,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2620
2696
|
chartCtx.stroke();
|
|
2621
2697
|
if (seriesType === 'line-markers') {
|
|
2622
2698
|
candles.forEach((candle, index) => {
|
|
2623
|
-
const x = index
|
|
2699
|
+
const x = centerX(index);
|
|
2624
2700
|
const yClose = ((maxPrice - candle.close) / priceRange) * chartHeight;
|
|
2625
2701
|
chartCtx.beginPath();
|
|
2626
2702
|
chartCtx.arc(x, yClose, 2.5, 0, 2 * Math.PI);
|
|
@@ -2629,8 +2705,8 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2629
2705
|
});
|
|
2630
2706
|
}
|
|
2631
2707
|
if (seriesType === 'area' || seriesType === 'hlc-area' || seriesType === 'baseline') {
|
|
2632
|
-
chartCtx.lineTo((candles.length - 1)
|
|
2633
|
-
chartCtx.lineTo(0
|
|
2708
|
+
chartCtx.lineTo(centerX(candles.length - 1), ((maxPrice - baselinePrice) / priceRange) * chartHeight);
|
|
2709
|
+
chartCtx.lineTo(centerX(0), ((maxPrice - baselinePrice) / priceRange) * chartHeight);
|
|
2634
2710
|
chartCtx.closePath();
|
|
2635
2711
|
chartCtx.fillStyle =
|
|
2636
2712
|
seriesType === 'baseline' ? 'rgba(41,98,255,0.25)' : 'rgba(41,98,255,0.15)';
|
|
@@ -2640,7 +2716,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2640
2716
|
else {
|
|
2641
2717
|
// ----- CANDLE / BAR-BASED SERIES -----
|
|
2642
2718
|
candles.forEach((candle, index) => {
|
|
2643
|
-
const xCenter = index
|
|
2719
|
+
const xCenter = centerX(index);
|
|
2644
2720
|
const strokeX = alignStroke(xCenter);
|
|
2645
2721
|
const yHigh = ((maxPrice - candle.high) / priceRange) * chartHeight;
|
|
2646
2722
|
const yLow = ((maxPrice - candle.low) / priceRange) * chartHeight;
|
|
@@ -2725,12 +2801,12 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2725
2801
|
if (volumeCtx) {
|
|
2726
2802
|
volumeCtx.clearRect(0, 0, chartWidth, volumeHeight);
|
|
2727
2803
|
candles.forEach((candle, index) => {
|
|
2728
|
-
const
|
|
2804
|
+
const xLeft = centerX(index) - candleWidth / 2;
|
|
2729
2805
|
const barHeight = ((candle.volume || 0) / maxVolume) * volumeHeight;
|
|
2730
2806
|
const y = volumeHeight - barHeight;
|
|
2731
2807
|
const isBullish = candle.close > candle.open;
|
|
2732
2808
|
volumeCtx.fillStyle = isBullish ? bullishColor : bearishColor;
|
|
2733
|
-
volumeCtx.fillRect(
|
|
2809
|
+
volumeCtx.fillRect(xLeft + 1, y, Math.max(1, candleWidth - 2), barHeight);
|
|
2734
2810
|
});
|
|
2735
2811
|
}
|
|
2736
2812
|
// Draw indicators
|
|
@@ -2744,7 +2820,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2744
2820
|
indicatorCtx.beginPath();
|
|
2745
2821
|
indicator.data.forEach((value, index) => {
|
|
2746
2822
|
if (value !== null && value !== undefined) {
|
|
2747
|
-
const x = index
|
|
2823
|
+
const x = centerX(index);
|
|
2748
2824
|
const y = ((maxPrice - value) / priceRange) * chartHeight;
|
|
2749
2825
|
if (index === 0) {
|
|
2750
2826
|
indicatorCtx.moveTo(x, y);
|
|
@@ -2759,11 +2835,11 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2759
2835
|
else if (indicator.type === 'histogram') {
|
|
2760
2836
|
indicator.data.forEach((value, index) => {
|
|
2761
2837
|
if (value !== null && value !== undefined) {
|
|
2762
|
-
const
|
|
2838
|
+
const xLeft = centerX(index) - candleWidth / 2;
|
|
2763
2839
|
const barHeight = Math.abs(value) * chartHeight * 0.1; // Scale histogram
|
|
2764
2840
|
const y = value >= 0 ? chartHeight / 2 - barHeight : chartHeight / 2;
|
|
2765
2841
|
indicatorCtx.fillStyle = value >= 0 ? '#089981' : '#f23645';
|
|
2766
|
-
indicatorCtx.fillRect(
|
|
2842
|
+
indicatorCtx.fillRect(xLeft + 2, y, Math.max(1, candleWidth - 4), barHeight);
|
|
2767
2843
|
}
|
|
2768
2844
|
});
|
|
2769
2845
|
}
|
|
@@ -2793,32 +2869,46 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2793
2869
|
}
|
|
2794
2870
|
}, [chartWidth, chartHeight, overlayHeight]);
|
|
2795
2871
|
const handlePointerMove = React.useCallback((e) => {
|
|
2796
|
-
if (interactionsLocked || !visibleData.length)
|
|
2872
|
+
if (interactionsLocked || !visibleData.length) {
|
|
2873
|
+
setCrosshairMeta(null);
|
|
2797
2874
|
return;
|
|
2875
|
+
}
|
|
2798
2876
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
2799
2877
|
const x = e.clientX - rect.left;
|
|
2800
2878
|
const y = e.clientY - rect.top;
|
|
2801
2879
|
setClickedPrice(null);
|
|
2802
2880
|
if (cursorType !== 'cross') {
|
|
2881
|
+
setCrosshairMeta(null);
|
|
2803
2882
|
return;
|
|
2804
2883
|
}
|
|
2805
2884
|
if (!isDraggingRef.current) {
|
|
2806
2885
|
const { min, max } = priceWindowRef.current;
|
|
2807
|
-
const
|
|
2808
|
-
const
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
|
|
2886
|
+
const rawIndex = pixelToCandleIndex(x, chartWidth, visibleData.length);
|
|
2887
|
+
const hoveredIndex = clampCandleIndex(rawIndex, visibleData.length);
|
|
2888
|
+
const hoveredCandle = visibleData[hoveredIndex];
|
|
2889
|
+
const snappedPrice = coordsRef.current.snapToPrice(y, chartHeight, min, max);
|
|
2890
|
+
const yPixel = coordsRef.current.priceToPixel(snappedPrice, chartHeight, min, max);
|
|
2891
|
+
const xPixel = magnetEnabled
|
|
2892
|
+
? getCandleCenter(hoveredIndex, chartWidth, visibleData.length)
|
|
2893
|
+
: clampPixelToChart(x, chartWidth);
|
|
2894
|
+
mousePosRef.current = { x: xPixel, y: yPixel };
|
|
2895
|
+
setCrosshairMeta({
|
|
2896
|
+
price: snappedPrice,
|
|
2897
|
+
timestamp: hoveredCandle?.timestamp ?? null,
|
|
2898
|
+
x: xPixel,
|
|
2899
|
+
y: yPixel,
|
|
2900
|
+
candle: hoveredCandle ?? null
|
|
2901
|
+
});
|
|
2813
2902
|
showCrosshairRef.current = true;
|
|
2814
2903
|
requestAnimationFrame(drawCrosshair);
|
|
2815
2904
|
}
|
|
2816
|
-
}, [visibleData, cursorType, chartWidth, chartHeight, drawCrosshair, interactionsLocked]);
|
|
2905
|
+
}, [visibleData, cursorType, chartWidth, chartHeight, drawCrosshair, interactionsLocked, magnetEnabled]);
|
|
2817
2906
|
const handleMouseLeave = React.useCallback(() => {
|
|
2818
2907
|
showCrosshairRef.current = false;
|
|
2819
2908
|
isDraggingRef.current = false;
|
|
2820
2909
|
dragSampleRef.current.velocity = 0;
|
|
2821
2910
|
dragSampleRef.current.lastTs = 0;
|
|
2911
|
+
setCrosshairMeta(null);
|
|
2822
2912
|
requestAnimationFrame(() => {
|
|
2823
2913
|
const overlayCtx = overlayRef.current?.getContext('2d');
|
|
2824
2914
|
if (overlayCtx) {
|
|
@@ -2862,15 +2952,21 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2862
2952
|
? { minPrice: priceWindowRef.current.min, maxPrice: priceWindowRef.current.max }
|
|
2863
2953
|
: getDataBounds(targetData);
|
|
2864
2954
|
const dataLength = Math.max(targetData.length, 1);
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2955
|
+
let snappedPrice = coordsRef.current.snapToPrice(y, chartHeight, bounds.minPrice, bounds.maxPrice, 0.5);
|
|
2956
|
+
let snappedY = y;
|
|
2957
|
+
if (magnetEnabled && targetData.length) {
|
|
2958
|
+
const snappedIndex = clampCandleIndex(pixelToCandleIndex(x, chartWidth, dataLength), dataLength);
|
|
2959
|
+
x = getCandleCenter(snappedIndex, chartWidth, dataLength);
|
|
2960
|
+
snappedY = coordsRef.current.priceToPixel(snappedPrice, chartHeight, bounds.minPrice, bounds.maxPrice);
|
|
2961
|
+
y = snappedY;
|
|
2962
|
+
}
|
|
2963
|
+
else {
|
|
2964
|
+
x = clampPixelToChart(x, chartWidth);
|
|
2965
|
+
}
|
|
2966
|
+
const price = coordsRef.current.pixelToPrice(snappedY, chartHeight, bounds.minPrice, bounds.maxPrice);
|
|
2967
|
+
const hoveredIndex = clampCandleIndex(pixelToCandleIndex(x, chartWidth, dataLength), dataLength);
|
|
2968
|
+
const time = targetData[hoveredIndex]?.timestamp ?? hoveredIndex;
|
|
2969
|
+
return { x, y: snappedY, price, time };
|
|
2874
2970
|
}, [visibleData, storeData, magnetEnabled, chartWidth, chartHeight, interactionsLocked]);
|
|
2875
2971
|
const tryEraseDrawing = React.useCallback((x, y) => {
|
|
2876
2972
|
for (let i = drawings.length - 1; i >= 0; i--) {
|
|
@@ -3081,7 +3177,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3081
3177
|
}
|
|
3082
3178
|
if (visibleData.length) {
|
|
3083
3179
|
const { min, max } = priceWindowRef.current;
|
|
3084
|
-
const snappedIndex =
|
|
3180
|
+
const snappedIndex = clampCandleIndex(pixelToCandleIndex(rawX, chartWidth, visibleData.length), visibleData.length);
|
|
3085
3181
|
setSelectedCandleIndex(snappedIndex);
|
|
3086
3182
|
const price = coordsRef.current.pixelToPrice(rawY, chartHeight, min, max);
|
|
3087
3183
|
setClickedPrice({ x: rawX, y: rawY, price });
|
|
@@ -3224,6 +3320,87 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3224
3320
|
{ value: 'columns', label: 'Columnas', icon: seriesIconMap.columns },
|
|
3225
3321
|
{ value: 'high-low', label: 'High/Low', icon: seriesIconMap['high-low'] }
|
|
3226
3322
|
];
|
|
3323
|
+
const headerButtons = (React.createElement(React.Fragment, null,
|
|
3324
|
+
React.createElement("button", { onClick: () => setShowSeriesMenu(!showSeriesMenu), style: {
|
|
3325
|
+
width: '38px',
|
|
3326
|
+
height: '38px',
|
|
3327
|
+
borderRadius: '999px',
|
|
3328
|
+
border: `1px solid ${showSeriesMenu ? activeTheme.accent : iconBaseBg}`,
|
|
3329
|
+
background: showSeriesMenu ? activeTheme.accent : iconBaseBg,
|
|
3330
|
+
color: iconBaseColor,
|
|
3331
|
+
display: 'flex',
|
|
3332
|
+
alignItems: 'center',
|
|
3333
|
+
justifyContent: 'center',
|
|
3334
|
+
cursor: 'pointer',
|
|
3335
|
+
transition: 'all 0.2s'
|
|
3336
|
+
}, title: strings.buttons.series, "aria-label": strings.buttons.series }, seriesIconMap[seriesType] ?? React.createElement(BsGraphUp, null)),
|
|
3337
|
+
React.createElement("button", { onClick: toggleIndicatorsPanel, style: {
|
|
3338
|
+
background: showIndicatorsPanel ? '#2563eb' : 'rgba(15,23,42,0.6)',
|
|
3339
|
+
color: '#e2e8f0',
|
|
3340
|
+
border: '1px solid #1f2937',
|
|
3341
|
+
borderRadius: '999px',
|
|
3342
|
+
padding: '8px 14px',
|
|
3343
|
+
fontSize: '12px',
|
|
3344
|
+
fontWeight: 600,
|
|
3345
|
+
cursor: 'pointer',
|
|
3346
|
+
transition: 'all 0.2s'
|
|
3347
|
+
} }, strings.buttons.indicators),
|
|
3348
|
+
React.createElement("button", { onClick: () => setShowConfigPanel(!showConfigPanel), style: {
|
|
3349
|
+
width: '38px',
|
|
3350
|
+
height: '38px',
|
|
3351
|
+
borderRadius: '999px',
|
|
3352
|
+
border: `1px solid ${showConfigPanel ? activeTheme.accent : iconBaseBg}`,
|
|
3353
|
+
background: showConfigPanel ? activeTheme.accent : iconBaseBg,
|
|
3354
|
+
color: iconBaseColor,
|
|
3355
|
+
display: 'flex',
|
|
3356
|
+
alignItems: 'center',
|
|
3357
|
+
justifyContent: 'center',
|
|
3358
|
+
cursor: 'pointer',
|
|
3359
|
+
transition: 'all 0.2s'
|
|
3360
|
+
}, title: strings.buttons.config, "aria-label": strings.buttons.config },
|
|
3361
|
+
React.createElement(BsGear, null)),
|
|
3362
|
+
React.createElement("button", { onClick: () => setIsFullscreen(prev => !prev), style: {
|
|
3363
|
+
width: '38px',
|
|
3364
|
+
height: '38px',
|
|
3365
|
+
borderRadius: '999px',
|
|
3366
|
+
border: `1px solid ${isFullscreen ? activeTheme.accent : iconBaseBg}`,
|
|
3367
|
+
background: isFullscreen ? activeTheme.accent : iconBaseBg,
|
|
3368
|
+
color: iconBaseColor,
|
|
3369
|
+
display: 'flex',
|
|
3370
|
+
alignItems: 'center',
|
|
3371
|
+
justifyContent: 'center',
|
|
3372
|
+
cursor: 'pointer',
|
|
3373
|
+
transition: 'all 0.2s'
|
|
3374
|
+
}, title: isFullscreen ? strings.buttons.fullscreenExit : strings.buttons.fullscreenEnter, "aria-label": isFullscreen ? strings.buttons.fullscreenExit : strings.buttons.fullscreenEnter },
|
|
3375
|
+
React.createElement(BsArrowsFullscreen, null)),
|
|
3376
|
+
React.createElement("button", { onClick: takeScreenshot, style: {
|
|
3377
|
+
width: '38px',
|
|
3378
|
+
height: '38px',
|
|
3379
|
+
borderRadius: '999px',
|
|
3380
|
+
border: `1px solid ${iconBaseBg}`,
|
|
3381
|
+
background: iconBaseBg,
|
|
3382
|
+
color: iconBaseColor,
|
|
3383
|
+
display: 'flex',
|
|
3384
|
+
alignItems: 'center',
|
|
3385
|
+
justifyContent: 'center',
|
|
3386
|
+
cursor: 'pointer',
|
|
3387
|
+
transition: 'all 0.2s'
|
|
3388
|
+
}, title: strings.buttons.screenshot, "aria-label": strings.buttons.screenshot },
|
|
3389
|
+
React.createElement(BsCamera, null)),
|
|
3390
|
+
showHeaderStats && (React.createElement("button", { onClick: () => setShowQuickTips(prev => !prev), style: {
|
|
3391
|
+
width: '38px',
|
|
3392
|
+
height: '38px',
|
|
3393
|
+
borderRadius: '999px',
|
|
3394
|
+
border: `1px solid ${showQuickTips ? activeTheme.accent : iconBaseBg}`,
|
|
3395
|
+
background: showQuickTips ? activeTheme.accent : iconBaseBg,
|
|
3396
|
+
color: iconBaseColor,
|
|
3397
|
+
display: 'flex',
|
|
3398
|
+
alignItems: 'center',
|
|
3399
|
+
justifyContent: 'center',
|
|
3400
|
+
cursor: 'pointer',
|
|
3401
|
+
transition: 'all 0.2s'
|
|
3402
|
+
}, title: strings.buttons.help, "aria-label": strings.buttons.help },
|
|
3403
|
+
React.createElement(BsQuestionCircle, null)))));
|
|
3227
3404
|
const cursorCss = isDraggingRef.current
|
|
3228
3405
|
? 'grabbing'
|
|
3229
3406
|
: cursorType === 'cross'
|
|
@@ -3252,13 +3429,12 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3252
3429
|
background: `linear-gradient(120deg, ${activeTheme.heroFrom} 0%, ${activeTheme.heroTo} 100%)`,
|
|
3253
3430
|
borderBottom: `1px solid ${activeTheme.panelBorder}`,
|
|
3254
3431
|
display: 'flex',
|
|
3255
|
-
|
|
3432
|
+
flexDirection: 'column',
|
|
3256
3433
|
gap: isMobile ? '12px' : '16px',
|
|
3257
|
-
alignItems: 'center',
|
|
3258
3434
|
padding: isMobile ? '12px 16px' : '16px 20px',
|
|
3259
3435
|
boxShadow: elevatedShadow
|
|
3260
3436
|
}, initial: { opacity: 0, y: -15 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.3 } },
|
|
3261
|
-
React.createElement("div", { style: { display: 'flex', flexWrap: 'wrap', gap: '12px', alignItems: 'center', minWidth: 0 } },
|
|
3437
|
+
showHeaderStats && (React.createElement("div", { style: { display: 'flex', flexWrap: 'wrap', gap: '12px', alignItems: 'center', minWidth: 0 } },
|
|
3262
3438
|
React.createElement("div", { style: { background: activeTheme.cardBg, border: `1px solid ${activeTheme.cardBorder}`, borderRadius: '14px', padding: '10px 16px', minWidth: '160px', color: activeTheme.textPrimary } },
|
|
3263
3439
|
React.createElement("div", { style: { fontSize: '11px', textTransform: 'uppercase', letterSpacing: '0.08em', color: activeTheme.textSecondary, marginBottom: '4px' } }, strings.symbolLabel),
|
|
3264
3440
|
React.createElement("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', color: activeTheme.textPrimary, fontWeight: 600, gap: '10px' } },
|
|
@@ -3290,150 +3466,118 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3290
3466
|
{ label: 'V', value: referenceCandle.volume ?? 0 }
|
|
3291
3467
|
].map(item => (React.createElement("div", { key: item.label, style: { display: 'flex', flexDirection: 'column', minWidth: '50px' } },
|
|
3292
3468
|
React.createElement("span", { style: { fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase' } }, item.label),
|
|
3293
|
-
React.createElement("span", { style: { color: '#e2e8f0', fontWeight: 600 } }, item.value.toFixed(2)))))))),
|
|
3294
|
-
React.createElement("div", { style: {
|
|
3469
|
+
React.createElement("span", { style: { color: '#e2e8f0', fontWeight: 600 } }, item.value.toFixed(2))))))))),
|
|
3470
|
+
compactHeader ? (React.createElement("div", { style: {
|
|
3471
|
+
width: '100%',
|
|
3295
3472
|
display: 'flex',
|
|
3473
|
+
flexWrap: isMobile ? 'wrap' : 'nowrap',
|
|
3296
3474
|
alignItems: 'center',
|
|
3297
|
-
gap: '12px'
|
|
3298
|
-
padding: '10px 14px',
|
|
3299
|
-
background: activeTheme.panelBg,
|
|
3300
|
-
borderRadius: '16px',
|
|
3301
|
-
border: `1px solid ${activeTheme.panelBorder}`,
|
|
3302
|
-
overflowX: 'auto'
|
|
3475
|
+
gap: isMobile ? '12px' : '16px'
|
|
3303
3476
|
} },
|
|
3304
|
-
React.createElement("div", { style: {
|
|
3305
|
-
React.createElement("span", { style: { fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, strings.timeframeTitle),
|
|
3306
|
-
React.createElement("span", { style: { fontSize: '11px', color: '#f8fafc', fontWeight: 600, padding: '2px 8px', borderRadius: '999px', background: 'rgba(37,99,235,0.25)', border: '1px solid rgba(37,99,235,0.6)' } }, timeframe)),
|
|
3307
|
-
React.createElement("div", { style: { display: 'flex', gap: '6px', flex: 1, minWidth: 0, overflowX: 'auto', paddingBottom: '4px' } }, ['1m', '3m', '5m', '15m', '30m', '1h', '4h'].map(tf => (React.createElement("button", { key: tf, onClick: () => handleTimeframeChange(tf), style: {
|
|
3308
|
-
background: timeframe === tf ? activeTheme.accent : activeTheme.panelBg,
|
|
3309
|
-
color: timeframe === tf ? '#f8fafc' : activeTheme.textSecondary,
|
|
3310
|
-
border: `1px solid ${timeframe === tf ? activeTheme.accent : activeTheme.panelBorder}`,
|
|
3311
|
-
borderRadius: '10px',
|
|
3312
|
-
padding: '6px 12px',
|
|
3313
|
-
fontSize: '12px',
|
|
3314
|
-
fontWeight: 600,
|
|
3315
|
-
cursor: 'pointer',
|
|
3316
|
-
transition: 'all 0.2s',
|
|
3317
|
-
flex: '0 0 auto',
|
|
3318
|
-
whiteSpace: 'nowrap'
|
|
3319
|
-
} }, tf)))),
|
|
3320
|
-
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '6px', fontSize: '12px', color: '#cbd5f5', whiteSpace: 'nowrap', flexShrink: 0 } },
|
|
3321
|
-
React.createElement(BsClockHistory, null),
|
|
3322
|
-
React.createElement("span", null,
|
|
3323
|
-
strings.axis.time,
|
|
3324
|
-
": ",
|
|
3325
|
-
currentTimeLabel)),
|
|
3326
|
-
React.createElement("select", { value: timeframe, onChange: (e) => handleTimeframeChange(e.target.value), style: {
|
|
3327
|
-
background: 'rgba(15,23,42,0.8)',
|
|
3328
|
-
color: '#e2e8f0',
|
|
3329
|
-
border: '1px solid #1f2937',
|
|
3330
|
-
padding: '10px 36px 10px 12px',
|
|
3331
|
-
borderRadius: '12px',
|
|
3332
|
-
cursor: 'pointer',
|
|
3333
|
-
fontSize: '12px',
|
|
3334
|
-
fontWeight: 600,
|
|
3335
|
-
appearance: 'none',
|
|
3336
|
-
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%2394a3b8' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e")`,
|
|
3337
|
-
backgroundPosition: 'right 10px center',
|
|
3338
|
-
backgroundRepeat: 'no-repeat',
|
|
3339
|
-
backgroundSize: '14px',
|
|
3340
|
-
minWidth: '120px',
|
|
3341
|
-
flexShrink: 0
|
|
3342
|
-
} },
|
|
3343
|
-
React.createElement("option", { value: "1m" }, "1m"),
|
|
3344
|
-
React.createElement("option", { value: "3m" }, "3m"),
|
|
3345
|
-
React.createElement("option", { value: "5m" }, "5m"),
|
|
3346
|
-
React.createElement("option", { value: "15m" }, "15m"),
|
|
3347
|
-
React.createElement("option", { value: "30m" }, "30m"),
|
|
3348
|
-
React.createElement("option", { value: "1h" }, "1h"),
|
|
3349
|
-
React.createElement("option", { value: "4h" }, "4h"),
|
|
3350
|
-
React.createElement("option", { value: "12h" }, "12h"),
|
|
3351
|
-
React.createElement("option", { value: "1D" }, "1D"),
|
|
3352
|
-
React.createElement("option", { value: "3D" }, "3D"),
|
|
3353
|
-
React.createElement("option", { value: "1W" }, "1W"),
|
|
3354
|
-
React.createElement("option", { value: "1M" }, "1M"))),
|
|
3355
|
-
React.createElement("div", { style: { marginLeft: 'auto', display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center' } },
|
|
3356
|
-
React.createElement("button", { onClick: () => setShowSeriesMenu(!showSeriesMenu), style: {
|
|
3357
|
-
width: '38px',
|
|
3358
|
-
height: '38px',
|
|
3359
|
-
borderRadius: '999px',
|
|
3360
|
-
border: `1px solid ${showSeriesMenu ? activeTheme.accent : iconBaseBg}`,
|
|
3361
|
-
background: showSeriesMenu ? activeTheme.accent : iconBaseBg,
|
|
3362
|
-
color: iconBaseColor,
|
|
3477
|
+
React.createElement("div", { style: {
|
|
3363
3478
|
display: 'flex',
|
|
3364
3479
|
alignItems: 'center',
|
|
3365
|
-
|
|
3366
|
-
cursor: 'pointer',
|
|
3367
|
-
transition: 'all 0.2s'
|
|
3368
|
-
}, title: strings.buttons.series, "aria-label": strings.buttons.series }, seriesIconMap[seriesType] ?? React.createElement(BsGraphUp, null)),
|
|
3369
|
-
React.createElement("button", { onClick: toggleIndicatorsPanel, style: {
|
|
3370
|
-
background: showIndicatorsPanel ? '#2563eb' : 'rgba(15,23,42,0.6)',
|
|
3371
|
-
color: '#e2e8f0',
|
|
3372
|
-
border: '1px solid #1f2937',
|
|
3373
|
-
borderRadius: '999px',
|
|
3480
|
+
gap: '10px',
|
|
3374
3481
|
padding: '8px 14px',
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
border: `1px solid ${iconBaseBg}`,
|
|
3413
|
-
background: iconBaseBg,
|
|
3414
|
-
color: iconBaseColor,
|
|
3415
|
-
display: 'flex',
|
|
3416
|
-
alignItems: 'center',
|
|
3417
|
-
justifyContent: 'center',
|
|
3418
|
-
cursor: 'pointer',
|
|
3419
|
-
transition: 'all 0.2s'
|
|
3420
|
-
}, title: strings.buttons.screenshot, "aria-label": strings.buttons.screenshot },
|
|
3421
|
-
React.createElement(BsCamera, null)),
|
|
3422
|
-
React.createElement("button", { onClick: () => setShowQuickTips(prev => !prev), style: {
|
|
3423
|
-
width: '38px',
|
|
3424
|
-
height: '38px',
|
|
3425
|
-
borderRadius: '999px',
|
|
3426
|
-
border: `1px solid ${showQuickTips ? activeTheme.accent : iconBaseBg}`,
|
|
3427
|
-
background: showQuickTips ? activeTheme.accent : iconBaseBg,
|
|
3428
|
-
color: iconBaseColor,
|
|
3482
|
+
background: activeTheme.panelBg,
|
|
3483
|
+
borderRadius: '16px',
|
|
3484
|
+
border: `1px solid ${activeTheme.panelBorder}`,
|
|
3485
|
+
flex: 1,
|
|
3486
|
+
minWidth: isMobile ? '100%' : '320px'
|
|
3487
|
+
} },
|
|
3488
|
+
React.createElement("span", { style: { fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, strings.timeframeTitle),
|
|
3489
|
+
React.createElement("select", { value: timeframe, onChange: (e) => handleTimeframeChange(e.target.value), style: {
|
|
3490
|
+
background: 'rgba(15,23,42,0.8)',
|
|
3491
|
+
color: '#e2e8f0',
|
|
3492
|
+
border: '1px solid #1f2937',
|
|
3493
|
+
padding: '8px 32px 8px 12px',
|
|
3494
|
+
borderRadius: '12px',
|
|
3495
|
+
cursor: 'pointer',
|
|
3496
|
+
fontSize: '12px',
|
|
3497
|
+
fontWeight: 600,
|
|
3498
|
+
appearance: 'none',
|
|
3499
|
+
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%2394a3b8' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e")`,
|
|
3500
|
+
backgroundPosition: 'right 10px center',
|
|
3501
|
+
backgroundRepeat: 'no-repeat',
|
|
3502
|
+
backgroundSize: '14px',
|
|
3503
|
+
minWidth: '140px'
|
|
3504
|
+
} },
|
|
3505
|
+
React.createElement("option", { value: "1m" }, "1m"),
|
|
3506
|
+
React.createElement("option", { value: "3m" }, "3m"),
|
|
3507
|
+
React.createElement("option", { value: "5m" }, "5m"),
|
|
3508
|
+
React.createElement("option", { value: "15m" }, "15m"),
|
|
3509
|
+
React.createElement("option", { value: "30m" }, "30m"),
|
|
3510
|
+
React.createElement("option", { value: "1h" }, "1h"),
|
|
3511
|
+
React.createElement("option", { value: "4h" }, "4h"),
|
|
3512
|
+
React.createElement("option", { value: "12h" }, "12h"),
|
|
3513
|
+
React.createElement("option", { value: "1D" }, "1D"),
|
|
3514
|
+
React.createElement("option", { value: "3D" }, "3D"),
|
|
3515
|
+
React.createElement("option", { value: "1W" }, "1W"),
|
|
3516
|
+
React.createElement("option", { value: "1M" }, "1M"))),
|
|
3517
|
+
React.createElement("div", { style: { marginLeft: isMobile ? 0 : 'auto', display: 'flex', flexWrap: isMobile ? 'wrap' : 'nowrap', gap: '8px', alignItems: 'center' } }, headerButtons))) : (React.createElement(React.Fragment, null,
|
|
3518
|
+
React.createElement("div", { style: {
|
|
3429
3519
|
display: 'flex',
|
|
3430
3520
|
alignItems: 'center',
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3521
|
+
gap: '12px',
|
|
3522
|
+
padding: '10px 14px',
|
|
3523
|
+
background: activeTheme.panelBg,
|
|
3524
|
+
borderRadius: '16px',
|
|
3525
|
+
border: `1px solid ${activeTheme.panelBorder}`,
|
|
3526
|
+
overflowX: 'auto'
|
|
3527
|
+
} },
|
|
3528
|
+
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap' } },
|
|
3529
|
+
React.createElement("span", { style: { fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, strings.timeframeTitle),
|
|
3530
|
+
React.createElement("span", { style: { fontSize: '11px', color: '#f8fafc', fontWeight: 600, padding: '2px 8px', borderRadius: '999px', background: 'rgba(37,99,235,0.25)', border: '1px solid rgba(37,99,235,0.6)' } }, timeframe)),
|
|
3531
|
+
React.createElement("div", { style: { display: 'flex', gap: '6px', flex: 1, minWidth: 0, overflowX: 'auto', paddingBottom: '4px' } }, ['1m', '3m', '5m', '15m', '30m', '1h', '4h'].map(tf => (React.createElement("button", { key: tf, onClick: () => handleTimeframeChange(tf), style: {
|
|
3532
|
+
background: timeframe === tf ? activeTheme.accent : activeTheme.panelBg,
|
|
3533
|
+
color: timeframe === tf ? '#f8fafc' : activeTheme.textSecondary,
|
|
3534
|
+
border: `1px solid ${timeframe === tf ? activeTheme.accent : activeTheme.panelBorder}`,
|
|
3535
|
+
borderRadius: '10px',
|
|
3536
|
+
padding: '6px 12px',
|
|
3537
|
+
fontSize: '12px',
|
|
3538
|
+
fontWeight: 600,
|
|
3539
|
+
cursor: 'pointer',
|
|
3540
|
+
transition: 'all 0.2s',
|
|
3541
|
+
flex: '0 0 auto',
|
|
3542
|
+
whiteSpace: 'nowrap'
|
|
3543
|
+
} }, tf)))),
|
|
3544
|
+
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '6px', fontSize: '12px', color: '#cbd5f5', whiteSpace: 'nowrap', flexShrink: 0 } },
|
|
3545
|
+
React.createElement(BsClockHistory, null),
|
|
3546
|
+
React.createElement("span", null,
|
|
3547
|
+
strings.axis.time,
|
|
3548
|
+
": ",
|
|
3549
|
+
currentTimeLabel)),
|
|
3550
|
+
React.createElement("select", { value: timeframe, onChange: (e) => handleTimeframeChange(e.target.value), style: {
|
|
3551
|
+
background: 'rgba(15,23,42,0.8)',
|
|
3552
|
+
color: '#e2e8f0',
|
|
3553
|
+
border: '1px solid #1f2937',
|
|
3554
|
+
padding: '10px 36px 10px 12px',
|
|
3555
|
+
borderRadius: '12px',
|
|
3556
|
+
cursor: 'pointer',
|
|
3557
|
+
fontSize: '12px',
|
|
3558
|
+
fontWeight: 600,
|
|
3559
|
+
appearance: 'none',
|
|
3560
|
+
backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%2394a3b8' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e")`,
|
|
3561
|
+
backgroundPosition: 'right 10px center',
|
|
3562
|
+
backgroundRepeat: 'no-repeat',
|
|
3563
|
+
backgroundSize: '14px',
|
|
3564
|
+
minWidth: '120px',
|
|
3565
|
+
flexShrink: 0
|
|
3566
|
+
} },
|
|
3567
|
+
React.createElement("option", { value: "1m" }, "1m"),
|
|
3568
|
+
React.createElement("option", { value: "3m" }, "3m"),
|
|
3569
|
+
React.createElement("option", { value: "5m" }, "5m"),
|
|
3570
|
+
React.createElement("option", { value: "15m" }, "15m"),
|
|
3571
|
+
React.createElement("option", { value: "30m" }, "30m"),
|
|
3572
|
+
React.createElement("option", { value: "1h" }, "1h"),
|
|
3573
|
+
React.createElement("option", { value: "4h" }, "4h"),
|
|
3574
|
+
React.createElement("option", { value: "12h" }, "12h"),
|
|
3575
|
+
React.createElement("option", { value: "1D" }, "1D"),
|
|
3576
|
+
React.createElement("option", { value: "3D" }, "3D"),
|
|
3577
|
+
React.createElement("option", { value: "1W" }, "1W"),
|
|
3578
|
+
React.createElement("option", { value: "1M" }, "1M"))),
|
|
3579
|
+
React.createElement("div", { style: { marginLeft: 'auto', display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center' } }, headerButtons)))),
|
|
3580
|
+
showStats && derivedStats && (React.createElement("div", { style: { padding: '16px 20px 0', background: activeTheme.panelBg, borderBottom: `1px solid ${activeTheme.panelBorder}` } },
|
|
3437
3581
|
React.createElement("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: '12px' } },
|
|
3438
3582
|
React.createElement("div", { style: { ...metricCardStyle, background: activeTheme.cardBg, border: `1px solid ${activeTheme.cardBorder}` } },
|
|
3439
3583
|
React.createElement("span", { style: { ...metricLabelStyle, color: activeTheme.textSecondary } }, "Trading range"),
|
|
@@ -3461,7 +3605,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3461
3605
|
"Latency ~",
|
|
3462
3606
|
latencyMs,
|
|
3463
3607
|
"ms"))))),
|
|
3464
|
-
React.createElement(framerMotion.AnimatePresence, null, showQuickTips && (React.createElement(framerMotion.motion.div, { initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -10 }, transition: { duration: 0.2 }, style: {
|
|
3608
|
+
React.createElement(framerMotion.AnimatePresence, null, showHeaderStats && showQuickTips && (React.createElement(framerMotion.motion.div, { initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -10 }, transition: { duration: 0.2 }, style: {
|
|
3465
3609
|
position: (isFullscreen ? 'fixed' : 'absolute'),
|
|
3466
3610
|
top: isFullscreen ? 80 : 140,
|
|
3467
3611
|
left: isFullscreen ? 80 : 140,
|
|
@@ -3559,7 +3703,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3559
3703
|
minHeight: 0,
|
|
3560
3704
|
alignItems: 'stretch'
|
|
3561
3705
|
} },
|
|
3562
|
-
React.createElement("div", { style: { width: isMobile ? '60px' : '88px', flexShrink: 0, height: '100%' } },
|
|
3706
|
+
showDrawingToolbar && (React.createElement("div", { style: { width: isMobile ? '60px' : '88px', flexShrink: 0, height: '100%' } },
|
|
3563
3707
|
React.createElement("div", { style: {
|
|
3564
3708
|
height: '100%',
|
|
3565
3709
|
borderRadius: '24px',
|
|
@@ -3570,7 +3714,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3570
3714
|
alignItems: 'center',
|
|
3571
3715
|
padding: isMobile ? '12px 0' : '16px 0'
|
|
3572
3716
|
} },
|
|
3573
|
-
React.createElement(DrawingToolbar, null))),
|
|
3717
|
+
React.createElement(DrawingToolbar, null)))),
|
|
3574
3718
|
React.createElement("div", { ref: plotAreaRef, style: {
|
|
3575
3719
|
flex: 1,
|
|
3576
3720
|
position: 'relative',
|
|
@@ -3611,6 +3755,65 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3611
3755
|
React.createElement("div", { style: { position: 'absolute', bottom: 0, left: 0, right: priceScaleWidth, height: `${timeScaleHeight}px`, background: activeTheme.scaleBg, borderTop: `1px solid ${activeTheme.plotBorder}`, fontSize: '11px', color: activeTheme.textSecondary, display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: '4px', padding: '4px 10px' } },
|
|
3612
3756
|
React.createElement("span", { style: { textTransform: 'uppercase', fontSize: '10px', letterSpacing: '0.08em' } }, strings.axis.time),
|
|
3613
3757
|
React.createElement("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', color: activeTheme.textPrimary } }, timeLabels.map((label, i) => (React.createElement("div", { key: i }, label))))),
|
|
3758
|
+
crosshairMeta?.candle && (React.createElement("div", { style: {
|
|
3759
|
+
position: 'absolute',
|
|
3760
|
+
top: 16,
|
|
3761
|
+
left: 16,
|
|
3762
|
+
padding: '10px 14px',
|
|
3763
|
+
borderRadius: '14px',
|
|
3764
|
+
background: activeTheme.overlayBg,
|
|
3765
|
+
border: `1px solid ${activeTheme.panelBorder}`,
|
|
3766
|
+
boxShadow: '0 12px 30px rgba(0,0,0,0.45)',
|
|
3767
|
+
color: activeTheme.textPrimary,
|
|
3768
|
+
fontSize: '11px',
|
|
3769
|
+
letterSpacing: '0.04em',
|
|
3770
|
+
display: 'flex',
|
|
3771
|
+
gap: '14px',
|
|
3772
|
+
pointerEvents: 'none',
|
|
3773
|
+
zIndex: 60
|
|
3774
|
+
} },
|
|
3775
|
+
([
|
|
3776
|
+
{ label: 'O', value: crosshairMeta.candle.open },
|
|
3777
|
+
{ label: 'H', value: crosshairMeta.candle.high },
|
|
3778
|
+
{ label: 'L', value: crosshairMeta.candle.low },
|
|
3779
|
+
{ label: 'C', value: crosshairMeta.candle.close }
|
|
3780
|
+
]).map((item) => (React.createElement("div", { key: item.label, style: { display: 'flex', flexDirection: 'column', gap: '2px' } },
|
|
3781
|
+
React.createElement("span", { style: { color: activeTheme.textSecondary } }, item.label),
|
|
3782
|
+
React.createElement("span", { style: { fontWeight: 600 } }, numberFormatter.format(item.value))))),
|
|
3783
|
+
React.createElement("div", { style: { display: 'flex', flexDirection: 'column', gap: '2px' } },
|
|
3784
|
+
React.createElement("span", { style: { color: activeTheme.textSecondary } }, "V"),
|
|
3785
|
+
React.createElement("span", { style: { fontWeight: 600 } }, shortNumberFormatter.format(crosshairMeta.candle.volume ?? 0))))),
|
|
3786
|
+
crosshairMeta && (React.createElement(React.Fragment, null,
|
|
3787
|
+
React.createElement("div", { style: {
|
|
3788
|
+
position: 'absolute',
|
|
3789
|
+
right: `${priceScaleWidth}px`,
|
|
3790
|
+
top: `${Math.min(Math.max(crosshairMeta.y, 12), overlayHeight - 12)}px`,
|
|
3791
|
+
transform: 'translate(0, -50%)',
|
|
3792
|
+
background: activeTheme.scaleBg,
|
|
3793
|
+
border: `1px solid ${activeTheme.panelBorder}`,
|
|
3794
|
+
borderRadius: '6px 0 0 6px',
|
|
3795
|
+
padding: '2px 8px',
|
|
3796
|
+
fontSize: '11px',
|
|
3797
|
+
fontWeight: 600,
|
|
3798
|
+
color: activeTheme.textPrimary,
|
|
3799
|
+
pointerEvents: 'none',
|
|
3800
|
+
boxShadow: '0 8px 20px rgba(0,0,0,0.4)'
|
|
3801
|
+
} }, numberFormatter.format(crosshairMeta.price)),
|
|
3802
|
+
crosshairMeta.timestamp && (React.createElement("div", { style: {
|
|
3803
|
+
position: 'absolute',
|
|
3804
|
+
left: `${Math.min(Math.max(crosshairMeta.x, 40), chartWidth - 40)}px`,
|
|
3805
|
+
bottom: `${timeScaleHeight}px`,
|
|
3806
|
+
transform: 'translate(-50%, 50%)',
|
|
3807
|
+
background: activeTheme.scaleBg,
|
|
3808
|
+
border: `1px solid ${activeTheme.panelBorder}`,
|
|
3809
|
+
borderRadius: '6px',
|
|
3810
|
+
padding: '2px 8px',
|
|
3811
|
+
fontSize: '10px',
|
|
3812
|
+
letterSpacing: '0.05em',
|
|
3813
|
+
color: activeTheme.textPrimary,
|
|
3814
|
+
pointerEvents: 'none',
|
|
3815
|
+
boxShadow: '0 8px 20px rgba(0,0,0,0.35)'
|
|
3816
|
+
} }, timeFormatter.format(new Date(crosshairMeta.timestamp)))))),
|
|
3614
3817
|
clickedPrice && (React.createElement("div", { style: {
|
|
3615
3818
|
position: 'absolute',
|
|
3616
3819
|
background: '#1e222d',
|
|
@@ -3793,7 +3996,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3793
3996
|
React.createElement("span", null, field.label),
|
|
3794
3997
|
React.createElement("input", { type: "color", value: customTheme[field.key], onChange: (event) => handleCustomThemeChange(field.key, event.target.value), style: { width: '100%', height: '34px', border: 'none', cursor: 'pointer', borderRadius: '8px', background: 'transparent' } })))))),
|
|
3795
3998
|
React.createElement("div", { style: { fontSize: '12px', color: activeTheme.textSecondary } }, strings.config.soon))),
|
|
3796
|
-
React.createElement("div", { style: { padding: isMobile ? '8px 16px' : '10px 20px', background: activeTheme.panelBg, borderTop: `1px solid ${activeTheme.panelBorder}`, display: 'flex', flexWrap: 'wrap', gap: isMobile ? '12px' : '18px', fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase', color: activeTheme.textSecondary } },
|
|
3999
|
+
showHeaderStats && (React.createElement("div", { style: { padding: isMobile ? '8px 16px' : '10px 20px', background: activeTheme.panelBg, borderTop: `1px solid ${activeTheme.panelBorder}`, display: 'flex', flexWrap: 'wrap', gap: isMobile ? '12px' : '18px', fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase', color: activeTheme.textSecondary } },
|
|
3797
4000
|
React.createElement("span", null,
|
|
3798
4001
|
"Latency ",
|
|
3799
4002
|
latencyMs,
|
|
@@ -3802,7 +4005,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3802
4005
|
"Session ",
|
|
3803
4006
|
derivedStats?.session ?? 'Global'),
|
|
3804
4007
|
React.createElement("span", null, "Feed Binance Composite"),
|
|
3805
|
-
React.createElement("span", null, "Security AES-256"))));
|
|
4008
|
+
React.createElement("span", null, "Security AES-256")))));
|
|
3806
4009
|
};
|
|
3807
4010
|
var TradingViewChart_default = React.memo(TradingViewChart);
|
|
3808
4011
|
|