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.mjs
CHANGED
|
@@ -138,8 +138,20 @@ function BsArrowRepeat (props) {
|
|
|
138
138
|
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);
|
|
139
139
|
}function BsDiagram3 (props) {
|
|
140
140
|
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);
|
|
141
|
+
}function BsEmojiAngry (props) {
|
|
142
|
+
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);
|
|
143
|
+
}function BsEmojiDizzy (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":"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);
|
|
145
|
+
}function BsEmojiHeartEyes (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":"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);
|
|
147
|
+
}function BsEmojiLaughing (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":"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);
|
|
149
|
+
}function BsEmojiNeutral (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":"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);
|
|
141
151
|
}function BsEmojiSmile (props) {
|
|
142
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.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);
|
|
153
|
+
}function BsEmojiSunglasses (props) {
|
|
154
|
+
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);
|
|
143
155
|
}function BsEyeSlash (props) {
|
|
144
156
|
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);
|
|
145
157
|
}function BsGear (props) {
|
|
@@ -841,7 +853,7 @@ function createStore(creator) {
|
|
|
841
853
|
const useChartStore = createStore((set) => ({
|
|
842
854
|
data: [],
|
|
843
855
|
visibleData: [],
|
|
844
|
-
timeframe: '
|
|
856
|
+
timeframe: '12h',
|
|
845
857
|
zoomLevel: 1,
|
|
846
858
|
panOffset: 0,
|
|
847
859
|
indicators: [],
|
|
@@ -920,7 +932,6 @@ const useChartStore = createStore((set) => ({
|
|
|
920
932
|
toggleDrawingsHidden: () => set((state) => ({ drawingsHidden: !state.drawingsHidden }))
|
|
921
933
|
}));
|
|
922
934
|
|
|
923
|
-
const EMOJIS = ['⭐', '🔥', '🚀', '✅', '❌', '💎', '🐂', '🐻', '📈', '📉'];
|
|
924
935
|
const cursorOptions = [
|
|
925
936
|
{ type: 'cross', icon: React.createElement(BsCursor, null), label: 'Cross' },
|
|
926
937
|
{ type: 'dot', icon: React.createElement("div", { style: { width: '6px', height: '6px', borderRadius: '50%', background: 'currentColor' } }), label: 'Dot' },
|
|
@@ -960,6 +971,15 @@ const measurementOptions = [
|
|
|
960
971
|
{ tool: 'fibonacci', icon: React.createElement(BsGraphUp, null), label: 'Fibonacci' },
|
|
961
972
|
{ tool: 'ruler', icon: React.createElement(BsGraphDown, null), label: 'Regla' }
|
|
962
973
|
];
|
|
974
|
+
const emojiOptions = [
|
|
975
|
+
{ label: 'Smile', icon: React.createElement(BsEmojiSmile, null), value: '🙂' },
|
|
976
|
+
{ label: 'Laugh', icon: React.createElement(BsEmojiLaughing, null), value: '😂' },
|
|
977
|
+
{ label: 'Heart Eyes', icon: React.createElement(BsEmojiHeartEyes, null), value: '😍' },
|
|
978
|
+
{ label: 'Sunglasses', icon: React.createElement(BsEmojiSunglasses, null), value: '😎' },
|
|
979
|
+
{ label: 'Dizzy', icon: React.createElement(BsEmojiDizzy, null), value: '😵' },
|
|
980
|
+
{ label: 'Neutral', icon: React.createElement(BsEmojiNeutral, null), value: '😐' },
|
|
981
|
+
{ label: 'Angry', icon: React.createElement(BsEmojiAngry, null), value: '😠' }
|
|
982
|
+
];
|
|
963
983
|
const popoverStyle = {
|
|
964
984
|
position: 'absolute',
|
|
965
985
|
left: '64px',
|
|
@@ -978,6 +998,15 @@ const DrawingToolbar = () => {
|
|
|
978
998
|
const { activeTool, cursorType, strokeColor, strokeWidth, setActiveTool, setCursorType, setStrokeColor, setStrokeWidth, clearDrawings, setSelectedEmoji, setIsDrawing } = useChartStore();
|
|
979
999
|
const [openMenu, setOpenMenu] = useState(null);
|
|
980
1000
|
const [showEmojiPicker, setShowEmojiPicker] = useState(false);
|
|
1001
|
+
const [isCompact, setIsCompact] = useState(false);
|
|
1002
|
+
useEffect(() => {
|
|
1003
|
+
const handleResize = () => {
|
|
1004
|
+
setIsCompact(window.innerWidth < 768);
|
|
1005
|
+
};
|
|
1006
|
+
handleResize();
|
|
1007
|
+
window.addEventListener('resize', handleResize);
|
|
1008
|
+
return () => window.removeEventListener('resize', handleResize);
|
|
1009
|
+
}, []);
|
|
981
1010
|
const LINE_COLORS = ['#8ab4ff', '#f472b6', '#34d399', '#facc15', '#f87171', '#e2e8f0'];
|
|
982
1011
|
const WIDTH_OPTIONS = [1, 1.5, 2.5, 3.5];
|
|
983
1012
|
const handleSelectTool = (tool) => {
|
|
@@ -1073,14 +1102,27 @@ const DrawingToolbar = () => {
|
|
|
1073
1102
|
title: 'Limpiar todo'
|
|
1074
1103
|
}
|
|
1075
1104
|
];
|
|
1076
|
-
|
|
1105
|
+
const filteredButtons = isCompact
|
|
1106
|
+
? buttonConfig.filter(btn => !['pitchforks', 'measurements'].includes(btn.key))
|
|
1107
|
+
: buttonConfig;
|
|
1108
|
+
const buildPopoverStyle = (custom) => ({
|
|
1109
|
+
...popoverStyle,
|
|
1110
|
+
...(isCompact
|
|
1111
|
+
? { left: '0px', top: 'calc(100% + 12px)', transform: 'none', width: 'min(360px, 92vw)' }
|
|
1112
|
+
: {}),
|
|
1113
|
+
...custom
|
|
1114
|
+
});
|
|
1115
|
+
return (React.createElement("div", { style: { position: 'relative', height: '100%', width: '100%' } },
|
|
1077
1116
|
React.createElement("div", { style: {
|
|
1078
|
-
height: '100%',
|
|
1117
|
+
height: isCompact ? 'auto' : '100%',
|
|
1118
|
+
width: '100%',
|
|
1079
1119
|
display: 'flex',
|
|
1080
|
-
flexDirection: 'column',
|
|
1120
|
+
flexDirection: isCompact ? 'row' : 'column',
|
|
1081
1121
|
alignItems: 'center',
|
|
1082
|
-
gap: '12px'
|
|
1083
|
-
|
|
1122
|
+
gap: '12px',
|
|
1123
|
+
overflowX: isCompact ? 'auto' : 'visible',
|
|
1124
|
+
padding: isCompact ? '12px 6px' : 0
|
|
1125
|
+
} }, filteredButtons.map((btn) => (React.createElement("div", { key: btn.key, style: { position: 'relative' } },
|
|
1084
1126
|
React.createElement("button", { onClick: btn.onClick, title: btn.title, "aria-label": btn.title, style: {
|
|
1085
1127
|
width: '46px',
|
|
1086
1128
|
height: '46px',
|
|
@@ -1095,7 +1137,7 @@ const DrawingToolbar = () => {
|
|
|
1095
1137
|
fontSize: '18px',
|
|
1096
1138
|
transition: 'all 0.2s'
|
|
1097
1139
|
} }, btn.icon),
|
|
1098
|
-
btn.key === 'cursor' && openMenu === 'cursor' && (React.createElement("div", { style: {
|
|
1140
|
+
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: {
|
|
1099
1141
|
width: '84px',
|
|
1100
1142
|
height: '60px',
|
|
1101
1143
|
borderRadius: '12px',
|
|
@@ -1111,7 +1153,7 @@ const DrawingToolbar = () => {
|
|
|
1111
1153
|
}, title: option.label },
|
|
1112
1154
|
React.createElement("span", { style: { fontSize: '18px', display: 'flex', alignItems: 'center', justifyContent: 'center' } }, option.icon),
|
|
1113
1155
|
React.createElement("span", { style: { fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase' } }, option.label)))))),
|
|
1114
|
-
btn.key === 'lines' && openMenu === 'lines' && (React.createElement("div", { style: {
|
|
1156
|
+
btn.key === 'lines' && openMenu === 'lines' && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(360px, 92vw)' : '280px' }) },
|
|
1115
1157
|
React.createElement("p", { style: { margin: 0, fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Lines"),
|
|
1116
1158
|
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: {
|
|
1117
1159
|
borderRadius: '12px',
|
|
@@ -1151,7 +1193,7 @@ const DrawingToolbar = () => {
|
|
|
1151
1193
|
} },
|
|
1152
1194
|
width,
|
|
1153
1195
|
"px"))))))),
|
|
1154
|
-
btn.key === 'shapes' && openMenu === 'shapes' && (React.createElement("div", { style: {
|
|
1196
|
+
btn.key === 'shapes' && openMenu === 'shapes' && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(320px, 92vw)' : '200px' }) },
|
|
1155
1197
|
React.createElement("p", { style: { margin: 0, fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Shapes"),
|
|
1156
1198
|
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: {
|
|
1157
1199
|
borderRadius: '12px',
|
|
@@ -1168,7 +1210,7 @@ const DrawingToolbar = () => {
|
|
|
1168
1210
|
}, title: option.label },
|
|
1169
1211
|
React.createElement("span", { style: { fontSize: '16px' } }, option.icon),
|
|
1170
1212
|
React.createElement("span", { style: { fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.08em' } }, option.label))))))),
|
|
1171
|
-
btn.key === 'channels' && openMenu === 'channels' && (React.createElement("div", { style: {
|
|
1213
|
+
btn.key === 'channels' && openMenu === 'channels' && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(340px, 92vw)' : '220px' }) },
|
|
1172
1214
|
React.createElement("p", { style: { margin: 0, fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Channels"),
|
|
1173
1215
|
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: {
|
|
1174
1216
|
borderRadius: '12px',
|
|
@@ -1185,7 +1227,7 @@ const DrawingToolbar = () => {
|
|
|
1185
1227
|
}, title: option.label },
|
|
1186
1228
|
React.createElement("span", { style: { fontSize: '16px' } }, option.icon),
|
|
1187
1229
|
React.createElement("span", { style: { fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.08em' } }, option.label))))))),
|
|
1188
|
-
btn.key === 'pitchforks' && openMenu === 'pitchforks' && (React.createElement("div", { style: {
|
|
1230
|
+
btn.key === 'pitchforks' && openMenu === 'pitchforks' && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(320px, 92vw)' : '200px' }) },
|
|
1189
1231
|
React.createElement("p", { style: { margin: 0, fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Pitchforks"),
|
|
1190
1232
|
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: {
|
|
1191
1233
|
borderRadius: '12px',
|
|
@@ -1202,7 +1244,7 @@ const DrawingToolbar = () => {
|
|
|
1202
1244
|
}, title: option.label },
|
|
1203
1245
|
React.createElement("span", { style: { fontSize: '16px' } }, option.icon),
|
|
1204
1246
|
React.createElement("span", { style: { fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.08em', textAlign: 'center' } }, option.label))))))),
|
|
1205
|
-
btn.key === 'measurements' && openMenu === 'measurements' && (React.createElement("div", { style: {
|
|
1247
|
+
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: {
|
|
1206
1248
|
width: '64px',
|
|
1207
1249
|
height: '54px',
|
|
1208
1250
|
borderRadius: '12px',
|
|
@@ -1219,41 +1261,24 @@ const DrawingToolbar = () => {
|
|
|
1219
1261
|
}, title: option.label },
|
|
1220
1262
|
React.createElement("span", { style: { fontSize: '16px' } }, option.icon),
|
|
1221
1263
|
React.createElement("span", null, option.label)))))),
|
|
1222
|
-
btn.key === 'emoji' && showEmojiPicker && (React.createElement("div", { style: {
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
transform: 'translateY(-50%)',
|
|
1227
|
-
zIndex: 1100,
|
|
1228
|
-
background: '#020617',
|
|
1229
|
-
borderRadius: '14px',
|
|
1230
|
-
overflow: 'hidden',
|
|
1231
|
-
boxShadow: '0 18px 40px rgba(0,0,0,0.65)',
|
|
1232
|
-
padding: '10px',
|
|
1233
|
-
border: '1px solid #1f2937'
|
|
1234
|
-
} },
|
|
1235
|
-
React.createElement("div", { style: {
|
|
1236
|
-
display: 'grid',
|
|
1237
|
-
gridTemplateColumns: 'repeat(3, 1fr)',
|
|
1238
|
-
gap: '6px'
|
|
1239
|
-
} }, EMOJIS.map((emoji) => (React.createElement("button", { key: emoji, onClick: () => {
|
|
1240
|
-
setSelectedEmoji(emoji);
|
|
1264
|
+
btn.key === 'emoji' && showEmojiPicker && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(320px, 92vw)' : '220px' }) },
|
|
1265
|
+
React.createElement("p", { style: { margin: '0 0 6px', fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Emojis"),
|
|
1266
|
+
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: () => {
|
|
1267
|
+
setSelectedEmoji(option.value);
|
|
1241
1268
|
setActiveTool('icon');
|
|
1242
1269
|
setShowEmojiPicker(false);
|
|
1243
1270
|
}, style: {
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
background: '#111827',
|
|
1271
|
+
height: '48px',
|
|
1272
|
+
borderRadius: '12px',
|
|
1247
1273
|
border: '1px solid #1f2937',
|
|
1248
|
-
|
|
1249
|
-
|
|
1274
|
+
background: 'rgba(2,6,23,0.65)',
|
|
1275
|
+
color: '#f8fafc',
|
|
1250
1276
|
display: 'flex',
|
|
1251
1277
|
alignItems: 'center',
|
|
1252
1278
|
justifyContent: 'center',
|
|
1253
|
-
fontSize: '
|
|
1254
|
-
|
|
1255
|
-
} },
|
|
1256
|
-
React.createElement("span", null, emoji)))))))))))));
|
|
1279
|
+
fontSize: '22px',
|
|
1280
|
+
cursor: 'pointer'
|
|
1281
|
+
}, title: option.label, "aria-label": option.label }, option.icon))))))))))));
|
|
1257
1282
|
};
|
|
1258
1283
|
|
|
1259
1284
|
const drawRoundedRect = (ctx, x, y, width, height, radius = 8) => {
|
|
@@ -1990,23 +2015,23 @@ const LINE_TOOL_TYPES = [
|
|
|
1990
2015
|
const LINE_TOOL_SET = new Set(LINE_TOOL_TYPES);
|
|
1991
2016
|
const THEME_PRESETS = {
|
|
1992
2017
|
dark: {
|
|
1993
|
-
pageBg: '#
|
|
1994
|
-
heroFrom: '#
|
|
1995
|
-
heroTo: '#
|
|
1996
|
-
panelBg: 'rgba(
|
|
1997
|
-
panelBorder: '#
|
|
1998
|
-
cardBg: 'rgba(
|
|
1999
|
-
cardBorder: '#
|
|
2018
|
+
pageBg: '#000000',
|
|
2019
|
+
heroFrom: '#050505',
|
|
2020
|
+
heroTo: '#000000',
|
|
2021
|
+
panelBg: 'rgba(0,0,0,0.92)',
|
|
2022
|
+
panelBorder: '#0f0f0f',
|
|
2023
|
+
cardBg: 'rgba(0,0,0,0.88)',
|
|
2024
|
+
cardBorder: '#050505',
|
|
2000
2025
|
textPrimary: '#f8fafc',
|
|
2001
|
-
textSecondary: '#
|
|
2026
|
+
textSecondary: '#9ca3af',
|
|
2002
2027
|
accent: '#2563eb',
|
|
2003
|
-
accentSoft: 'rgba(37,99,235,0.
|
|
2004
|
-
railBg: 'rgba(
|
|
2005
|
-
railBorder: '#
|
|
2006
|
-
plotBg: '#
|
|
2007
|
-
plotBorder: '#
|
|
2008
|
-
scaleBg: 'rgba(
|
|
2009
|
-
overlayBg: 'rgba(
|
|
2028
|
+
accentSoft: 'rgba(37,99,235,0.25)',
|
|
2029
|
+
railBg: 'rgba(0,0,0,0.9)',
|
|
2030
|
+
railBorder: '#080808',
|
|
2031
|
+
plotBg: '#010101',
|
|
2032
|
+
plotBorder: '#0d0d0d',
|
|
2033
|
+
scaleBg: 'rgba(0,0,0,0.92)',
|
|
2034
|
+
overlayBg: 'rgba(0,0,0,0.85)'
|
|
2010
2035
|
},
|
|
2011
2036
|
blue: {
|
|
2012
2037
|
pageBg: '#060e1f',
|
|
@@ -2058,6 +2083,10 @@ const CUSTOM_THEME_FIELDS = [
|
|
|
2058
2083
|
const MIN_CANDLE_PX = 4;
|
|
2059
2084
|
const MAX_CANDLE_PX = 28;
|
|
2060
2085
|
const BUFFER_FRACTION = 0.18;
|
|
2086
|
+
const LEFT_OFFSET_BARS = 2;
|
|
2087
|
+
const RIGHT_OFFSET_BARS = 18;
|
|
2088
|
+
const BAR_BODY_RATIO = 0.78;
|
|
2089
|
+
const GRID_DIVISIONS = 10;
|
|
2061
2090
|
const INERTIA_DURATION_MS = 900;
|
|
2062
2091
|
const ZOOM_MIN = 0.2;
|
|
2063
2092
|
const ZOOM_MAX = 6;
|
|
@@ -2065,6 +2094,42 @@ const easeOutQuad = (t) => 1 - (1 - t) * (1 - t);
|
|
|
2065
2094
|
const easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
2066
2095
|
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
2067
2096
|
const alignStroke = (value) => Math.round(value) + 0.5;
|
|
2097
|
+
const determinePriceFormat = (reference) => {
|
|
2098
|
+
const value = Math.abs(reference);
|
|
2099
|
+
if (!Number.isFinite(value) || value === 0)
|
|
2100
|
+
return { min: 2, max: 6 };
|
|
2101
|
+
if (value < 0.0001)
|
|
2102
|
+
return { min: 6, max: 8 };
|
|
2103
|
+
if (value < 0.001)
|
|
2104
|
+
return { min: 5, max: 7 };
|
|
2105
|
+
if (value < 0.01)
|
|
2106
|
+
return { min: 4, max: 6 };
|
|
2107
|
+
if (value < 0.1)
|
|
2108
|
+
return { min: 4, max: 5 };
|
|
2109
|
+
if (value < 1)
|
|
2110
|
+
return { min: 3, max: 4 };
|
|
2111
|
+
if (value < 100)
|
|
2112
|
+
return { min: 2, max: 3 };
|
|
2113
|
+
return { min: 2, max: 2 };
|
|
2114
|
+
};
|
|
2115
|
+
const getEffectiveBarCount = (count) => Math.max(count + LEFT_OFFSET_BARS + RIGHT_OFFSET_BARS, 1);
|
|
2116
|
+
const getCandleStep = (width, count) => (width <= 0 ? 0 : width / getEffectiveBarCount(count));
|
|
2117
|
+
const getCandleCenter = (index, width, count) => {
|
|
2118
|
+
const step = getCandleStep(width, count);
|
|
2119
|
+
return (index + LEFT_OFFSET_BARS + 0.5) * step;
|
|
2120
|
+
};
|
|
2121
|
+
const clampPixelToChart = (pixel, width) => Math.max(0, Math.min(width, pixel));
|
|
2122
|
+
const pixelToCandleIndex = (pixel, width, count) => {
|
|
2123
|
+
const step = getCandleStep(width, count);
|
|
2124
|
+
if (step === 0)
|
|
2125
|
+
return 0;
|
|
2126
|
+
return pixel / step - LEFT_OFFSET_BARS - 0.5;
|
|
2127
|
+
};
|
|
2128
|
+
const clampCandleIndex = (value, length) => {
|
|
2129
|
+
if (length <= 0)
|
|
2130
|
+
return 0;
|
|
2131
|
+
return clamp(Math.round(value), 0, Math.max(length - 1, 0));
|
|
2132
|
+
};
|
|
2068
2133
|
const createParallelPoints = (start, end, offset) => {
|
|
2069
2134
|
const dx = end.x - start.x;
|
|
2070
2135
|
const dy = end.y - start.y;
|
|
@@ -2154,7 +2219,7 @@ const computeVisibleWindow = (dataset, chartWidth, timeframe, zoomLevel, panOffs
|
|
|
2154
2219
|
}
|
|
2155
2220
|
};
|
|
2156
2221
|
};
|
|
2157
|
-
const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
2222
|
+
const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showStats = true, showHeaderStats = true, showDrawingToolbar = true }) => {
|
|
2158
2223
|
const chartRef = useRef(null);
|
|
2159
2224
|
const volumeRef = useRef(null);
|
|
2160
2225
|
const gridRef = useRef(null);
|
|
@@ -2293,7 +2358,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2293
2358
|
if (!chartWidth)
|
|
2294
2359
|
return;
|
|
2295
2360
|
const candles = getVisibleCandles();
|
|
2296
|
-
const pxPerCandle = chartWidth / Math.max(candles.length || 1, 1);
|
|
2361
|
+
const pxPerCandle = chartWidth / Math.max(getEffectiveBarCount(candles.length || 1), 1);
|
|
2297
2362
|
if (!isFinite(pxPerCandle) || pxPerCandle === 0)
|
|
2298
2363
|
return;
|
|
2299
2364
|
const deltaOffset = deltaPx / Math.max(pxPerCandle, 1e-3);
|
|
@@ -2417,6 +2482,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2417
2482
|
const [priceLabels, setPriceLabels] = useState([]);
|
|
2418
2483
|
const [timeLabels, setTimeLabels] = useState([]);
|
|
2419
2484
|
const [clickedPrice, setClickedPrice] = useState(null);
|
|
2485
|
+
const [crosshairMeta, setCrosshairMeta] = useState(null);
|
|
2420
2486
|
const [showConfigPanel, setShowConfigPanel] = useState(false);
|
|
2421
2487
|
const [language, setLanguage] = useState('en');
|
|
2422
2488
|
const [colorScheme, setColorScheme] = useState('green');
|
|
@@ -2436,16 +2502,18 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2436
2502
|
const iconBaseColor = '#f8fafc';
|
|
2437
2503
|
const leftRailBaseBg = themePreset === 'light' ? '#0f172a' : activeTheme.panelBg;
|
|
2438
2504
|
const strings = useMemo(() => UI_TEXT[language] ?? UI_TEXT.en, [language]);
|
|
2505
|
+
const compactHeader = !showHeaderStats;
|
|
2439
2506
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
|
2440
2507
|
const [selectedCandleIndex, setSelectedCandleIndex] = useState(null);
|
|
2441
2508
|
const [seriesType, setSeriesType] = useState('candles');
|
|
2442
2509
|
const [showSeriesMenu, setShowSeriesMenu] = useState(false);
|
|
2443
2510
|
const [isSeriesLoading, setIsSeriesLoading] = useState(false);
|
|
2444
|
-
const [showQuickTips, setShowQuickTips] = useState(
|
|
2511
|
+
const [showQuickTips, setShowQuickTips] = useState(showHeaderStats);
|
|
2445
2512
|
const [toastMessage, setToastMessage] = useState(null);
|
|
2446
2513
|
const [showNoteModal, setShowNoteModal] = useState(false);
|
|
2447
2514
|
const [pendingNotePoint, setPendingNotePoint] = useState(null);
|
|
2448
2515
|
const [noteDraft, setNoteDraft] = useState('');
|
|
2516
|
+
const [priceFormat, setPriceFormat] = useState({ min: 2, max: 4 });
|
|
2449
2517
|
const [isMobile, setIsMobile] = useState(false);
|
|
2450
2518
|
useEffect(() => {
|
|
2451
2519
|
if (!plotAreaRef.current)
|
|
@@ -2468,7 +2536,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2468
2536
|
return () => window.clearTimeout(id);
|
|
2469
2537
|
}, [seriesType, visibleData.length]);
|
|
2470
2538
|
const locale = language === 'es' ? 'es-ES' : 'en-US';
|
|
2471
|
-
const numberFormatter = useMemo(() => new Intl.NumberFormat(locale, { minimumFractionDigits:
|
|
2539
|
+
const numberFormatter = useMemo(() => new Intl.NumberFormat(locale, { minimumFractionDigits: priceFormat.min, maximumFractionDigits: priceFormat.max }), [locale, priceFormat]);
|
|
2472
2540
|
const shortNumberFormatter = useMemo(() => new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }), [locale]);
|
|
2473
2541
|
const timeFormatter = useMemo(() => new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit' }), [locale]);
|
|
2474
2542
|
const latencyMs = useMemo(() => Math.round(20 + Math.random() * 15), []);
|
|
@@ -2530,9 +2598,14 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2530
2598
|
const priceRange = Math.max(1e-6, maxPrice - minPrice);
|
|
2531
2599
|
const maxVolume = Math.max(bounds.maxVolume, 1);
|
|
2532
2600
|
priceWindowRef.current = { min: minPrice, max: maxPrice };
|
|
2601
|
+
const referenceMagnitude = Math.min(Math.abs(minPrice), Math.abs(maxPrice)) || Math.max(Math.abs(minPrice), Math.abs(maxPrice));
|
|
2602
|
+
const suggestedFormat = determinePriceFormat(referenceMagnitude);
|
|
2603
|
+
setPriceFormat((prev) => (prev.min === suggestedFormat.min && prev.max === suggestedFormat.max ? prev : suggestedFormat));
|
|
2533
2604
|
const chartWidth = Math.max(1, cssWidth - priceScaleWidth);
|
|
2534
2605
|
const chartHeight = Math.max(1, cssHeight - timeScaleHeight - volumeHeight);
|
|
2535
|
-
const
|
|
2606
|
+
const candleStep = getCandleStep(chartWidth, candles.length);
|
|
2607
|
+
const candleWidth = Math.max(1, candleStep * BAR_BODY_RATIO);
|
|
2608
|
+
const centerX = (index) => getCandleCenter(index, chartWidth, candles.length);
|
|
2536
2609
|
// Generate price labels
|
|
2537
2610
|
const priceLabelsArray = [];
|
|
2538
2611
|
for (let i = 0; i <= 10; i++) {
|
|
@@ -2567,9 +2640,11 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2567
2640
|
gridCtx.lineTo(chartWidth, y);
|
|
2568
2641
|
gridCtx.stroke();
|
|
2569
2642
|
}
|
|
2570
|
-
const timeStep = Math.max(1, Math.floor(candles.length /
|
|
2643
|
+
const timeStep = Math.max(1, Math.floor(Math.max(candles.length, 1) / GRID_DIVISIONS));
|
|
2571
2644
|
for (let i = 0; i <= candles.length; i += timeStep) {
|
|
2572
|
-
const x = alignStroke(i *
|
|
2645
|
+
const x = alignStroke((i + LEFT_OFFSET_BARS) * candleStep);
|
|
2646
|
+
if (x < 0 || x > chartWidth)
|
|
2647
|
+
continue;
|
|
2573
2648
|
gridCtx.beginPath();
|
|
2574
2649
|
gridCtx.moveTo(x, 0);
|
|
2575
2650
|
gridCtx.lineTo(x, chartHeight);
|
|
@@ -2595,7 +2670,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2595
2670
|
: minPrice;
|
|
2596
2671
|
chartCtx.beginPath();
|
|
2597
2672
|
candles.forEach((candle, index) => {
|
|
2598
|
-
const x = index
|
|
2673
|
+
const x = centerX(index);
|
|
2599
2674
|
const yClose = ((maxPrice - candle.close) / priceRange) * chartHeight;
|
|
2600
2675
|
if (index === 0) {
|
|
2601
2676
|
chartCtx.moveTo(x, yClose);
|
|
@@ -2603,6 +2678,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2603
2678
|
else if (seriesType === 'step') {
|
|
2604
2679
|
const prev = candles[index - 1];
|
|
2605
2680
|
if (prev) {
|
|
2681
|
+
centerX(index - 1);
|
|
2606
2682
|
const prevY = ((maxPrice - prev.close) / priceRange) * chartHeight;
|
|
2607
2683
|
chartCtx.lineTo(x, prevY);
|
|
2608
2684
|
chartCtx.lineTo(x, yClose);
|
|
@@ -2618,7 +2694,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2618
2694
|
chartCtx.stroke();
|
|
2619
2695
|
if (seriesType === 'line-markers') {
|
|
2620
2696
|
candles.forEach((candle, index) => {
|
|
2621
|
-
const x = index
|
|
2697
|
+
const x = centerX(index);
|
|
2622
2698
|
const yClose = ((maxPrice - candle.close) / priceRange) * chartHeight;
|
|
2623
2699
|
chartCtx.beginPath();
|
|
2624
2700
|
chartCtx.arc(x, yClose, 2.5, 0, 2 * Math.PI);
|
|
@@ -2627,8 +2703,8 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2627
2703
|
});
|
|
2628
2704
|
}
|
|
2629
2705
|
if (seriesType === 'area' || seriesType === 'hlc-area' || seriesType === 'baseline') {
|
|
2630
|
-
chartCtx.lineTo((candles.length - 1)
|
|
2631
|
-
chartCtx.lineTo(0
|
|
2706
|
+
chartCtx.lineTo(centerX(candles.length - 1), ((maxPrice - baselinePrice) / priceRange) * chartHeight);
|
|
2707
|
+
chartCtx.lineTo(centerX(0), ((maxPrice - baselinePrice) / priceRange) * chartHeight);
|
|
2632
2708
|
chartCtx.closePath();
|
|
2633
2709
|
chartCtx.fillStyle =
|
|
2634
2710
|
seriesType === 'baseline' ? 'rgba(41,98,255,0.25)' : 'rgba(41,98,255,0.15)';
|
|
@@ -2638,7 +2714,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2638
2714
|
else {
|
|
2639
2715
|
// ----- CANDLE / BAR-BASED SERIES -----
|
|
2640
2716
|
candles.forEach((candle, index) => {
|
|
2641
|
-
const xCenter = index
|
|
2717
|
+
const xCenter = centerX(index);
|
|
2642
2718
|
const strokeX = alignStroke(xCenter);
|
|
2643
2719
|
const yHigh = ((maxPrice - candle.high) / priceRange) * chartHeight;
|
|
2644
2720
|
const yLow = ((maxPrice - candle.low) / priceRange) * chartHeight;
|
|
@@ -2723,12 +2799,12 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2723
2799
|
if (volumeCtx) {
|
|
2724
2800
|
volumeCtx.clearRect(0, 0, chartWidth, volumeHeight);
|
|
2725
2801
|
candles.forEach((candle, index) => {
|
|
2726
|
-
const
|
|
2802
|
+
const xLeft = centerX(index) - candleWidth / 2;
|
|
2727
2803
|
const barHeight = ((candle.volume || 0) / maxVolume) * volumeHeight;
|
|
2728
2804
|
const y = volumeHeight - barHeight;
|
|
2729
2805
|
const isBullish = candle.close > candle.open;
|
|
2730
2806
|
volumeCtx.fillStyle = isBullish ? bullishColor : bearishColor;
|
|
2731
|
-
volumeCtx.fillRect(
|
|
2807
|
+
volumeCtx.fillRect(xLeft + 1, y, Math.max(1, candleWidth - 2), barHeight);
|
|
2732
2808
|
});
|
|
2733
2809
|
}
|
|
2734
2810
|
// Draw indicators
|
|
@@ -2742,7 +2818,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2742
2818
|
indicatorCtx.beginPath();
|
|
2743
2819
|
indicator.data.forEach((value, index) => {
|
|
2744
2820
|
if (value !== null && value !== undefined) {
|
|
2745
|
-
const x = index
|
|
2821
|
+
const x = centerX(index);
|
|
2746
2822
|
const y = ((maxPrice - value) / priceRange) * chartHeight;
|
|
2747
2823
|
if (index === 0) {
|
|
2748
2824
|
indicatorCtx.moveTo(x, y);
|
|
@@ -2757,11 +2833,11 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2757
2833
|
else if (indicator.type === 'histogram') {
|
|
2758
2834
|
indicator.data.forEach((value, index) => {
|
|
2759
2835
|
if (value !== null && value !== undefined) {
|
|
2760
|
-
const
|
|
2836
|
+
const xLeft = centerX(index) - candleWidth / 2;
|
|
2761
2837
|
const barHeight = Math.abs(value) * chartHeight * 0.1; // Scale histogram
|
|
2762
2838
|
const y = value >= 0 ? chartHeight / 2 - barHeight : chartHeight / 2;
|
|
2763
2839
|
indicatorCtx.fillStyle = value >= 0 ? '#089981' : '#f23645';
|
|
2764
|
-
indicatorCtx.fillRect(
|
|
2840
|
+
indicatorCtx.fillRect(xLeft + 2, y, Math.max(1, candleWidth - 4), barHeight);
|
|
2765
2841
|
}
|
|
2766
2842
|
});
|
|
2767
2843
|
}
|
|
@@ -2791,32 +2867,46 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2791
2867
|
}
|
|
2792
2868
|
}, [chartWidth, chartHeight, overlayHeight]);
|
|
2793
2869
|
const handlePointerMove = useCallback((e) => {
|
|
2794
|
-
if (interactionsLocked || !visibleData.length)
|
|
2870
|
+
if (interactionsLocked || !visibleData.length) {
|
|
2871
|
+
setCrosshairMeta(null);
|
|
2795
2872
|
return;
|
|
2873
|
+
}
|
|
2796
2874
|
const rect = e.currentTarget.getBoundingClientRect();
|
|
2797
2875
|
const x = e.clientX - rect.left;
|
|
2798
2876
|
const y = e.clientY - rect.top;
|
|
2799
2877
|
setClickedPrice(null);
|
|
2800
2878
|
if (cursorType !== 'cross') {
|
|
2879
|
+
setCrosshairMeta(null);
|
|
2801
2880
|
return;
|
|
2802
2881
|
}
|
|
2803
2882
|
if (!isDraggingRef.current) {
|
|
2804
2883
|
const { min, max } = priceWindowRef.current;
|
|
2805
|
-
const
|
|
2806
|
-
const
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2884
|
+
const rawIndex = pixelToCandleIndex(x, chartWidth, visibleData.length);
|
|
2885
|
+
const hoveredIndex = clampCandleIndex(rawIndex, visibleData.length);
|
|
2886
|
+
const hoveredCandle = visibleData[hoveredIndex];
|
|
2887
|
+
const snappedPrice = coordsRef.current.snapToPrice(y, chartHeight, min, max);
|
|
2888
|
+
const yPixel = coordsRef.current.priceToPixel(snappedPrice, chartHeight, min, max);
|
|
2889
|
+
const xPixel = magnetEnabled
|
|
2890
|
+
? getCandleCenter(hoveredIndex, chartWidth, visibleData.length)
|
|
2891
|
+
: clampPixelToChart(x, chartWidth);
|
|
2892
|
+
mousePosRef.current = { x: xPixel, y: yPixel };
|
|
2893
|
+
setCrosshairMeta({
|
|
2894
|
+
price: snappedPrice,
|
|
2895
|
+
timestamp: hoveredCandle?.timestamp ?? null,
|
|
2896
|
+
x: xPixel,
|
|
2897
|
+
y: yPixel,
|
|
2898
|
+
candle: hoveredCandle ?? null
|
|
2899
|
+
});
|
|
2811
2900
|
showCrosshairRef.current = true;
|
|
2812
2901
|
requestAnimationFrame(drawCrosshair);
|
|
2813
2902
|
}
|
|
2814
|
-
}, [visibleData, cursorType, chartWidth, chartHeight, drawCrosshair, interactionsLocked]);
|
|
2903
|
+
}, [visibleData, cursorType, chartWidth, chartHeight, drawCrosshair, interactionsLocked, magnetEnabled]);
|
|
2815
2904
|
const handleMouseLeave = useCallback(() => {
|
|
2816
2905
|
showCrosshairRef.current = false;
|
|
2817
2906
|
isDraggingRef.current = false;
|
|
2818
2907
|
dragSampleRef.current.velocity = 0;
|
|
2819
2908
|
dragSampleRef.current.lastTs = 0;
|
|
2909
|
+
setCrosshairMeta(null);
|
|
2820
2910
|
requestAnimationFrame(() => {
|
|
2821
2911
|
const overlayCtx = overlayRef.current?.getContext('2d');
|
|
2822
2912
|
if (overlayCtx) {
|
|
@@ -2860,15 +2950,21 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
2860
2950
|
? { minPrice: priceWindowRef.current.min, maxPrice: priceWindowRef.current.max }
|
|
2861
2951
|
: getDataBounds(targetData);
|
|
2862
2952
|
const dataLength = Math.max(targetData.length, 1);
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2953
|
+
let snappedPrice = coordsRef.current.snapToPrice(y, chartHeight, bounds.minPrice, bounds.maxPrice, 0.5);
|
|
2954
|
+
let snappedY = y;
|
|
2955
|
+
if (magnetEnabled && targetData.length) {
|
|
2956
|
+
const snappedIndex = clampCandleIndex(pixelToCandleIndex(x, chartWidth, dataLength), dataLength);
|
|
2957
|
+
x = getCandleCenter(snappedIndex, chartWidth, dataLength);
|
|
2958
|
+
snappedY = coordsRef.current.priceToPixel(snappedPrice, chartHeight, bounds.minPrice, bounds.maxPrice);
|
|
2959
|
+
y = snappedY;
|
|
2960
|
+
}
|
|
2961
|
+
else {
|
|
2962
|
+
x = clampPixelToChart(x, chartWidth);
|
|
2963
|
+
}
|
|
2964
|
+
const price = coordsRef.current.pixelToPrice(snappedY, chartHeight, bounds.minPrice, bounds.maxPrice);
|
|
2965
|
+
const hoveredIndex = clampCandleIndex(pixelToCandleIndex(x, chartWidth, dataLength), dataLength);
|
|
2966
|
+
const time = targetData[hoveredIndex]?.timestamp ?? hoveredIndex;
|
|
2967
|
+
return { x, y: snappedY, price, time };
|
|
2872
2968
|
}, [visibleData, storeData, magnetEnabled, chartWidth, chartHeight, interactionsLocked]);
|
|
2873
2969
|
const tryEraseDrawing = useCallback((x, y) => {
|
|
2874
2970
|
for (let i = drawings.length - 1; i >= 0; i--) {
|
|
@@ -3079,7 +3175,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3079
3175
|
}
|
|
3080
3176
|
if (visibleData.length) {
|
|
3081
3177
|
const { min, max } = priceWindowRef.current;
|
|
3082
|
-
const snappedIndex =
|
|
3178
|
+
const snappedIndex = clampCandleIndex(pixelToCandleIndex(rawX, chartWidth, visibleData.length), visibleData.length);
|
|
3083
3179
|
setSelectedCandleIndex(snappedIndex);
|
|
3084
3180
|
const price = coordsRef.current.pixelToPrice(rawY, chartHeight, min, max);
|
|
3085
3181
|
setClickedPrice({ x: rawX, y: rawY, price });
|
|
@@ -3222,6 +3318,87 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3222
3318
|
{ value: 'columns', label: 'Columnas', icon: seriesIconMap.columns },
|
|
3223
3319
|
{ value: 'high-low', label: 'High/Low', icon: seriesIconMap['high-low'] }
|
|
3224
3320
|
];
|
|
3321
|
+
const headerButtons = (React.createElement(React.Fragment, null,
|
|
3322
|
+
React.createElement("button", { onClick: () => setShowSeriesMenu(!showSeriesMenu), style: {
|
|
3323
|
+
width: '38px',
|
|
3324
|
+
height: '38px',
|
|
3325
|
+
borderRadius: '999px',
|
|
3326
|
+
border: `1px solid ${showSeriesMenu ? activeTheme.accent : iconBaseBg}`,
|
|
3327
|
+
background: showSeriesMenu ? activeTheme.accent : iconBaseBg,
|
|
3328
|
+
color: iconBaseColor,
|
|
3329
|
+
display: 'flex',
|
|
3330
|
+
alignItems: 'center',
|
|
3331
|
+
justifyContent: 'center',
|
|
3332
|
+
cursor: 'pointer',
|
|
3333
|
+
transition: 'all 0.2s'
|
|
3334
|
+
}, title: strings.buttons.series, "aria-label": strings.buttons.series }, seriesIconMap[seriesType] ?? React.createElement(BsGraphUp, null)),
|
|
3335
|
+
React.createElement("button", { onClick: toggleIndicatorsPanel, style: {
|
|
3336
|
+
background: showIndicatorsPanel ? '#2563eb' : 'rgba(15,23,42,0.6)',
|
|
3337
|
+
color: '#e2e8f0',
|
|
3338
|
+
border: '1px solid #1f2937',
|
|
3339
|
+
borderRadius: '999px',
|
|
3340
|
+
padding: '8px 14px',
|
|
3341
|
+
fontSize: '12px',
|
|
3342
|
+
fontWeight: 600,
|
|
3343
|
+
cursor: 'pointer',
|
|
3344
|
+
transition: 'all 0.2s'
|
|
3345
|
+
} }, strings.buttons.indicators),
|
|
3346
|
+
React.createElement("button", { onClick: () => setShowConfigPanel(!showConfigPanel), style: {
|
|
3347
|
+
width: '38px',
|
|
3348
|
+
height: '38px',
|
|
3349
|
+
borderRadius: '999px',
|
|
3350
|
+
border: `1px solid ${showConfigPanel ? activeTheme.accent : iconBaseBg}`,
|
|
3351
|
+
background: showConfigPanel ? activeTheme.accent : iconBaseBg,
|
|
3352
|
+
color: iconBaseColor,
|
|
3353
|
+
display: 'flex',
|
|
3354
|
+
alignItems: 'center',
|
|
3355
|
+
justifyContent: 'center',
|
|
3356
|
+
cursor: 'pointer',
|
|
3357
|
+
transition: 'all 0.2s'
|
|
3358
|
+
}, title: strings.buttons.config, "aria-label": strings.buttons.config },
|
|
3359
|
+
React.createElement(BsGear, null)),
|
|
3360
|
+
React.createElement("button", { onClick: () => setIsFullscreen(prev => !prev), style: {
|
|
3361
|
+
width: '38px',
|
|
3362
|
+
height: '38px',
|
|
3363
|
+
borderRadius: '999px',
|
|
3364
|
+
border: `1px solid ${isFullscreen ? activeTheme.accent : iconBaseBg}`,
|
|
3365
|
+
background: isFullscreen ? activeTheme.accent : iconBaseBg,
|
|
3366
|
+
color: iconBaseColor,
|
|
3367
|
+
display: 'flex',
|
|
3368
|
+
alignItems: 'center',
|
|
3369
|
+
justifyContent: 'center',
|
|
3370
|
+
cursor: 'pointer',
|
|
3371
|
+
transition: 'all 0.2s'
|
|
3372
|
+
}, title: isFullscreen ? strings.buttons.fullscreenExit : strings.buttons.fullscreenEnter, "aria-label": isFullscreen ? strings.buttons.fullscreenExit : strings.buttons.fullscreenEnter },
|
|
3373
|
+
React.createElement(BsArrowsFullscreen, null)),
|
|
3374
|
+
React.createElement("button", { onClick: takeScreenshot, style: {
|
|
3375
|
+
width: '38px',
|
|
3376
|
+
height: '38px',
|
|
3377
|
+
borderRadius: '999px',
|
|
3378
|
+
border: `1px solid ${iconBaseBg}`,
|
|
3379
|
+
background: iconBaseBg,
|
|
3380
|
+
color: iconBaseColor,
|
|
3381
|
+
display: 'flex',
|
|
3382
|
+
alignItems: 'center',
|
|
3383
|
+
justifyContent: 'center',
|
|
3384
|
+
cursor: 'pointer',
|
|
3385
|
+
transition: 'all 0.2s'
|
|
3386
|
+
}, title: strings.buttons.screenshot, "aria-label": strings.buttons.screenshot },
|
|
3387
|
+
React.createElement(BsCamera, null)),
|
|
3388
|
+
showHeaderStats && (React.createElement("button", { onClick: () => setShowQuickTips(prev => !prev), style: {
|
|
3389
|
+
width: '38px',
|
|
3390
|
+
height: '38px',
|
|
3391
|
+
borderRadius: '999px',
|
|
3392
|
+
border: `1px solid ${showQuickTips ? activeTheme.accent : iconBaseBg}`,
|
|
3393
|
+
background: showQuickTips ? activeTheme.accent : iconBaseBg,
|
|
3394
|
+
color: iconBaseColor,
|
|
3395
|
+
display: 'flex',
|
|
3396
|
+
alignItems: 'center',
|
|
3397
|
+
justifyContent: 'center',
|
|
3398
|
+
cursor: 'pointer',
|
|
3399
|
+
transition: 'all 0.2s'
|
|
3400
|
+
}, title: strings.buttons.help, "aria-label": strings.buttons.help },
|
|
3401
|
+
React.createElement(BsQuestionCircle, null)))));
|
|
3225
3402
|
const cursorCss = isDraggingRef.current
|
|
3226
3403
|
? 'grabbing'
|
|
3227
3404
|
: cursorType === 'cross'
|
|
@@ -3250,13 +3427,12 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3250
3427
|
background: `linear-gradient(120deg, ${activeTheme.heroFrom} 0%, ${activeTheme.heroTo} 100%)`,
|
|
3251
3428
|
borderBottom: `1px solid ${activeTheme.panelBorder}`,
|
|
3252
3429
|
display: 'flex',
|
|
3253
|
-
|
|
3430
|
+
flexDirection: 'column',
|
|
3254
3431
|
gap: isMobile ? '12px' : '16px',
|
|
3255
|
-
alignItems: 'center',
|
|
3256
3432
|
padding: isMobile ? '12px 16px' : '16px 20px',
|
|
3257
3433
|
boxShadow: elevatedShadow
|
|
3258
3434
|
}, initial: { opacity: 0, y: -15 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.3 } },
|
|
3259
|
-
React.createElement("div", { style: { display: 'flex', flexWrap: 'wrap', gap: '12px', alignItems: 'center', minWidth: 0 } },
|
|
3435
|
+
showHeaderStats && (React.createElement("div", { style: { display: 'flex', flexWrap: 'wrap', gap: '12px', alignItems: 'center', minWidth: 0 } },
|
|
3260
3436
|
React.createElement("div", { style: { background: activeTheme.cardBg, border: `1px solid ${activeTheme.cardBorder}`, borderRadius: '14px', padding: '10px 16px', minWidth: '160px', color: activeTheme.textPrimary } },
|
|
3261
3437
|
React.createElement("div", { style: { fontSize: '11px', textTransform: 'uppercase', letterSpacing: '0.08em', color: activeTheme.textSecondary, marginBottom: '4px' } }, strings.symbolLabel),
|
|
3262
3438
|
React.createElement("div", { style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', color: activeTheme.textPrimary, fontWeight: 600, gap: '10px' } },
|
|
@@ -3288,150 +3464,118 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3288
3464
|
{ label: 'V', value: referenceCandle.volume ?? 0 }
|
|
3289
3465
|
].map(item => (React.createElement("div", { key: item.label, style: { display: 'flex', flexDirection: 'column', minWidth: '50px' } },
|
|
3290
3466
|
React.createElement("span", { style: { fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase' } }, item.label),
|
|
3291
|
-
React.createElement("span", { style: { color: '#e2e8f0', fontWeight: 600 } }, item.value.toFixed(2)))))))),
|
|
3292
|
-
React.createElement("div", { style: {
|
|
3467
|
+
React.createElement("span", { style: { color: '#e2e8f0', fontWeight: 600 } }, item.value.toFixed(2))))))))),
|
|
3468
|
+
compactHeader ? (React.createElement("div", { style: {
|
|
3469
|
+
width: '100%',
|
|
3293
3470
|
display: 'flex',
|
|
3471
|
+
flexWrap: isMobile ? 'wrap' : 'nowrap',
|
|
3294
3472
|
alignItems: 'center',
|
|
3295
|
-
gap: '12px'
|
|
3296
|
-
padding: '10px 14px',
|
|
3297
|
-
background: activeTheme.panelBg,
|
|
3298
|
-
borderRadius: '16px',
|
|
3299
|
-
border: `1px solid ${activeTheme.panelBorder}`,
|
|
3300
|
-
overflowX: 'auto'
|
|
3473
|
+
gap: isMobile ? '12px' : '16px'
|
|
3301
3474
|
} },
|
|
3302
|
-
React.createElement("div", { style: {
|
|
3303
|
-
React.createElement("span", { style: { fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, strings.timeframeTitle),
|
|
3304
|
-
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)),
|
|
3305
|
-
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: {
|
|
3306
|
-
background: timeframe === tf ? activeTheme.accent : activeTheme.panelBg,
|
|
3307
|
-
color: timeframe === tf ? '#f8fafc' : activeTheme.textSecondary,
|
|
3308
|
-
border: `1px solid ${timeframe === tf ? activeTheme.accent : activeTheme.panelBorder}`,
|
|
3309
|
-
borderRadius: '10px',
|
|
3310
|
-
padding: '6px 12px',
|
|
3311
|
-
fontSize: '12px',
|
|
3312
|
-
fontWeight: 600,
|
|
3313
|
-
cursor: 'pointer',
|
|
3314
|
-
transition: 'all 0.2s',
|
|
3315
|
-
flex: '0 0 auto',
|
|
3316
|
-
whiteSpace: 'nowrap'
|
|
3317
|
-
} }, tf)))),
|
|
3318
|
-
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '6px', fontSize: '12px', color: '#cbd5f5', whiteSpace: 'nowrap', flexShrink: 0 } },
|
|
3319
|
-
React.createElement(BsClockHistory, null),
|
|
3320
|
-
React.createElement("span", null,
|
|
3321
|
-
strings.axis.time,
|
|
3322
|
-
": ",
|
|
3323
|
-
currentTimeLabel)),
|
|
3324
|
-
React.createElement("select", { value: timeframe, onChange: (e) => handleTimeframeChange(e.target.value), style: {
|
|
3325
|
-
background: 'rgba(15,23,42,0.8)',
|
|
3326
|
-
color: '#e2e8f0',
|
|
3327
|
-
border: '1px solid #1f2937',
|
|
3328
|
-
padding: '10px 36px 10px 12px',
|
|
3329
|
-
borderRadius: '12px',
|
|
3330
|
-
cursor: 'pointer',
|
|
3331
|
-
fontSize: '12px',
|
|
3332
|
-
fontWeight: 600,
|
|
3333
|
-
appearance: 'none',
|
|
3334
|
-
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")`,
|
|
3335
|
-
backgroundPosition: 'right 10px center',
|
|
3336
|
-
backgroundRepeat: 'no-repeat',
|
|
3337
|
-
backgroundSize: '14px',
|
|
3338
|
-
minWidth: '120px',
|
|
3339
|
-
flexShrink: 0
|
|
3340
|
-
} },
|
|
3341
|
-
React.createElement("option", { value: "1m" }, "1m"),
|
|
3342
|
-
React.createElement("option", { value: "3m" }, "3m"),
|
|
3343
|
-
React.createElement("option", { value: "5m" }, "5m"),
|
|
3344
|
-
React.createElement("option", { value: "15m" }, "15m"),
|
|
3345
|
-
React.createElement("option", { value: "30m" }, "30m"),
|
|
3346
|
-
React.createElement("option", { value: "1h" }, "1h"),
|
|
3347
|
-
React.createElement("option", { value: "4h" }, "4h"),
|
|
3348
|
-
React.createElement("option", { value: "12h" }, "12h"),
|
|
3349
|
-
React.createElement("option", { value: "1D" }, "1D"),
|
|
3350
|
-
React.createElement("option", { value: "3D" }, "3D"),
|
|
3351
|
-
React.createElement("option", { value: "1W" }, "1W"),
|
|
3352
|
-
React.createElement("option", { value: "1M" }, "1M"))),
|
|
3353
|
-
React.createElement("div", { style: { marginLeft: 'auto', display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center' } },
|
|
3354
|
-
React.createElement("button", { onClick: () => setShowSeriesMenu(!showSeriesMenu), style: {
|
|
3355
|
-
width: '38px',
|
|
3356
|
-
height: '38px',
|
|
3357
|
-
borderRadius: '999px',
|
|
3358
|
-
border: `1px solid ${showSeriesMenu ? activeTheme.accent : iconBaseBg}`,
|
|
3359
|
-
background: showSeriesMenu ? activeTheme.accent : iconBaseBg,
|
|
3360
|
-
color: iconBaseColor,
|
|
3475
|
+
React.createElement("div", { style: {
|
|
3361
3476
|
display: 'flex',
|
|
3362
3477
|
alignItems: 'center',
|
|
3363
|
-
|
|
3364
|
-
cursor: 'pointer',
|
|
3365
|
-
transition: 'all 0.2s'
|
|
3366
|
-
}, title: strings.buttons.series, "aria-label": strings.buttons.series }, seriesIconMap[seriesType] ?? React.createElement(BsGraphUp, null)),
|
|
3367
|
-
React.createElement("button", { onClick: toggleIndicatorsPanel, style: {
|
|
3368
|
-
background: showIndicatorsPanel ? '#2563eb' : 'rgba(15,23,42,0.6)',
|
|
3369
|
-
color: '#e2e8f0',
|
|
3370
|
-
border: '1px solid #1f2937',
|
|
3371
|
-
borderRadius: '999px',
|
|
3478
|
+
gap: '10px',
|
|
3372
3479
|
padding: '8px 14px',
|
|
3373
|
-
|
|
3374
|
-
|
|
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
|
-
border: `1px solid ${iconBaseBg}`,
|
|
3411
|
-
background: iconBaseBg,
|
|
3412
|
-
color: iconBaseColor,
|
|
3413
|
-
display: 'flex',
|
|
3414
|
-
alignItems: 'center',
|
|
3415
|
-
justifyContent: 'center',
|
|
3416
|
-
cursor: 'pointer',
|
|
3417
|
-
transition: 'all 0.2s'
|
|
3418
|
-
}, title: strings.buttons.screenshot, "aria-label": strings.buttons.screenshot },
|
|
3419
|
-
React.createElement(BsCamera, null)),
|
|
3420
|
-
React.createElement("button", { onClick: () => setShowQuickTips(prev => !prev), style: {
|
|
3421
|
-
width: '38px',
|
|
3422
|
-
height: '38px',
|
|
3423
|
-
borderRadius: '999px',
|
|
3424
|
-
border: `1px solid ${showQuickTips ? activeTheme.accent : iconBaseBg}`,
|
|
3425
|
-
background: showQuickTips ? activeTheme.accent : iconBaseBg,
|
|
3426
|
-
color: iconBaseColor,
|
|
3480
|
+
background: activeTheme.panelBg,
|
|
3481
|
+
borderRadius: '16px',
|
|
3482
|
+
border: `1px solid ${activeTheme.panelBorder}`,
|
|
3483
|
+
flex: 1,
|
|
3484
|
+
minWidth: isMobile ? '100%' : '320px'
|
|
3485
|
+
} },
|
|
3486
|
+
React.createElement("span", { style: { fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, strings.timeframeTitle),
|
|
3487
|
+
React.createElement("select", { value: timeframe, onChange: (e) => handleTimeframeChange(e.target.value), style: {
|
|
3488
|
+
background: 'rgba(15,23,42,0.8)',
|
|
3489
|
+
color: '#e2e8f0',
|
|
3490
|
+
border: '1px solid #1f2937',
|
|
3491
|
+
padding: '8px 32px 8px 12px',
|
|
3492
|
+
borderRadius: '12px',
|
|
3493
|
+
cursor: 'pointer',
|
|
3494
|
+
fontSize: '12px',
|
|
3495
|
+
fontWeight: 600,
|
|
3496
|
+
appearance: 'none',
|
|
3497
|
+
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")`,
|
|
3498
|
+
backgroundPosition: 'right 10px center',
|
|
3499
|
+
backgroundRepeat: 'no-repeat',
|
|
3500
|
+
backgroundSize: '14px',
|
|
3501
|
+
minWidth: '140px'
|
|
3502
|
+
} },
|
|
3503
|
+
React.createElement("option", { value: "1m" }, "1m"),
|
|
3504
|
+
React.createElement("option", { value: "3m" }, "3m"),
|
|
3505
|
+
React.createElement("option", { value: "5m" }, "5m"),
|
|
3506
|
+
React.createElement("option", { value: "15m" }, "15m"),
|
|
3507
|
+
React.createElement("option", { value: "30m" }, "30m"),
|
|
3508
|
+
React.createElement("option", { value: "1h" }, "1h"),
|
|
3509
|
+
React.createElement("option", { value: "4h" }, "4h"),
|
|
3510
|
+
React.createElement("option", { value: "12h" }, "12h"),
|
|
3511
|
+
React.createElement("option", { value: "1D" }, "1D"),
|
|
3512
|
+
React.createElement("option", { value: "3D" }, "3D"),
|
|
3513
|
+
React.createElement("option", { value: "1W" }, "1W"),
|
|
3514
|
+
React.createElement("option", { value: "1M" }, "1M"))),
|
|
3515
|
+
React.createElement("div", { style: { marginLeft: isMobile ? 0 : 'auto', display: 'flex', flexWrap: isMobile ? 'wrap' : 'nowrap', gap: '8px', alignItems: 'center' } }, headerButtons))) : (React.createElement(React.Fragment, null,
|
|
3516
|
+
React.createElement("div", { style: {
|
|
3427
3517
|
display: 'flex',
|
|
3428
3518
|
alignItems: 'center',
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3519
|
+
gap: '12px',
|
|
3520
|
+
padding: '10px 14px',
|
|
3521
|
+
background: activeTheme.panelBg,
|
|
3522
|
+
borderRadius: '16px',
|
|
3523
|
+
border: `1px solid ${activeTheme.panelBorder}`,
|
|
3524
|
+
overflowX: 'auto'
|
|
3525
|
+
} },
|
|
3526
|
+
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '6px', whiteSpace: 'nowrap' } },
|
|
3527
|
+
React.createElement("span", { style: { fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, strings.timeframeTitle),
|
|
3528
|
+
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)),
|
|
3529
|
+
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: {
|
|
3530
|
+
background: timeframe === tf ? activeTheme.accent : activeTheme.panelBg,
|
|
3531
|
+
color: timeframe === tf ? '#f8fafc' : activeTheme.textSecondary,
|
|
3532
|
+
border: `1px solid ${timeframe === tf ? activeTheme.accent : activeTheme.panelBorder}`,
|
|
3533
|
+
borderRadius: '10px',
|
|
3534
|
+
padding: '6px 12px',
|
|
3535
|
+
fontSize: '12px',
|
|
3536
|
+
fontWeight: 600,
|
|
3537
|
+
cursor: 'pointer',
|
|
3538
|
+
transition: 'all 0.2s',
|
|
3539
|
+
flex: '0 0 auto',
|
|
3540
|
+
whiteSpace: 'nowrap'
|
|
3541
|
+
} }, tf)))),
|
|
3542
|
+
React.createElement("div", { style: { display: 'flex', alignItems: 'center', gap: '6px', fontSize: '12px', color: '#cbd5f5', whiteSpace: 'nowrap', flexShrink: 0 } },
|
|
3543
|
+
React.createElement(BsClockHistory, null),
|
|
3544
|
+
React.createElement("span", null,
|
|
3545
|
+
strings.axis.time,
|
|
3546
|
+
": ",
|
|
3547
|
+
currentTimeLabel)),
|
|
3548
|
+
React.createElement("select", { value: timeframe, onChange: (e) => handleTimeframeChange(e.target.value), style: {
|
|
3549
|
+
background: 'rgba(15,23,42,0.8)',
|
|
3550
|
+
color: '#e2e8f0',
|
|
3551
|
+
border: '1px solid #1f2937',
|
|
3552
|
+
padding: '10px 36px 10px 12px',
|
|
3553
|
+
borderRadius: '12px',
|
|
3554
|
+
cursor: 'pointer',
|
|
3555
|
+
fontSize: '12px',
|
|
3556
|
+
fontWeight: 600,
|
|
3557
|
+
appearance: 'none',
|
|
3558
|
+
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")`,
|
|
3559
|
+
backgroundPosition: 'right 10px center',
|
|
3560
|
+
backgroundRepeat: 'no-repeat',
|
|
3561
|
+
backgroundSize: '14px',
|
|
3562
|
+
minWidth: '120px',
|
|
3563
|
+
flexShrink: 0
|
|
3564
|
+
} },
|
|
3565
|
+
React.createElement("option", { value: "1m" }, "1m"),
|
|
3566
|
+
React.createElement("option", { value: "3m" }, "3m"),
|
|
3567
|
+
React.createElement("option", { value: "5m" }, "5m"),
|
|
3568
|
+
React.createElement("option", { value: "15m" }, "15m"),
|
|
3569
|
+
React.createElement("option", { value: "30m" }, "30m"),
|
|
3570
|
+
React.createElement("option", { value: "1h" }, "1h"),
|
|
3571
|
+
React.createElement("option", { value: "4h" }, "4h"),
|
|
3572
|
+
React.createElement("option", { value: "12h" }, "12h"),
|
|
3573
|
+
React.createElement("option", { value: "1D" }, "1D"),
|
|
3574
|
+
React.createElement("option", { value: "3D" }, "3D"),
|
|
3575
|
+
React.createElement("option", { value: "1W" }, "1W"),
|
|
3576
|
+
React.createElement("option", { value: "1M" }, "1M"))),
|
|
3577
|
+
React.createElement("div", { style: { marginLeft: 'auto', display: 'flex', flexWrap: 'wrap', gap: '8px', alignItems: 'center' } }, headerButtons)))),
|
|
3578
|
+
showStats && derivedStats && (React.createElement("div", { style: { padding: '16px 20px 0', background: activeTheme.panelBg, borderBottom: `1px solid ${activeTheme.panelBorder}` } },
|
|
3435
3579
|
React.createElement("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: '12px' } },
|
|
3436
3580
|
React.createElement("div", { style: { ...metricCardStyle, background: activeTheme.cardBg, border: `1px solid ${activeTheme.cardBorder}` } },
|
|
3437
3581
|
React.createElement("span", { style: { ...metricLabelStyle, color: activeTheme.textSecondary } }, "Trading range"),
|
|
@@ -3459,7 +3603,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3459
3603
|
"Latency ~",
|
|
3460
3604
|
latencyMs,
|
|
3461
3605
|
"ms"))))),
|
|
3462
|
-
React.createElement(AnimatePresence, null, showQuickTips && (React.createElement(motion.div, { initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -10 }, transition: { duration: 0.2 }, style: {
|
|
3606
|
+
React.createElement(AnimatePresence, null, showHeaderStats && showQuickTips && (React.createElement(motion.div, { initial: { opacity: 0, y: -10 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -10 }, transition: { duration: 0.2 }, style: {
|
|
3463
3607
|
position: (isFullscreen ? 'fixed' : 'absolute'),
|
|
3464
3608
|
top: isFullscreen ? 80 : 140,
|
|
3465
3609
|
left: isFullscreen ? 80 : 140,
|
|
@@ -3557,7 +3701,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3557
3701
|
minHeight: 0,
|
|
3558
3702
|
alignItems: 'stretch'
|
|
3559
3703
|
} },
|
|
3560
|
-
React.createElement("div", { style: { width: isMobile ? '60px' : '88px', flexShrink: 0, height: '100%' } },
|
|
3704
|
+
showDrawingToolbar && (React.createElement("div", { style: { width: isMobile ? '60px' : '88px', flexShrink: 0, height: '100%' } },
|
|
3561
3705
|
React.createElement("div", { style: {
|
|
3562
3706
|
height: '100%',
|
|
3563
3707
|
borderRadius: '24px',
|
|
@@ -3568,7 +3712,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3568
3712
|
alignItems: 'center',
|
|
3569
3713
|
padding: isMobile ? '12px 0' : '16px 0'
|
|
3570
3714
|
} },
|
|
3571
|
-
React.createElement(DrawingToolbar, null))),
|
|
3715
|
+
React.createElement(DrawingToolbar, null)))),
|
|
3572
3716
|
React.createElement("div", { ref: plotAreaRef, style: {
|
|
3573
3717
|
flex: 1,
|
|
3574
3718
|
position: 'relative',
|
|
@@ -3609,6 +3753,65 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3609
3753
|
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' } },
|
|
3610
3754
|
React.createElement("span", { style: { textTransform: 'uppercase', fontSize: '10px', letterSpacing: '0.08em' } }, strings.axis.time),
|
|
3611
3755
|
React.createElement("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', color: activeTheme.textPrimary } }, timeLabels.map((label, i) => (React.createElement("div", { key: i }, label))))),
|
|
3756
|
+
crosshairMeta?.candle && (React.createElement("div", { style: {
|
|
3757
|
+
position: 'absolute',
|
|
3758
|
+
top: 16,
|
|
3759
|
+
left: 16,
|
|
3760
|
+
padding: '10px 14px',
|
|
3761
|
+
borderRadius: '14px',
|
|
3762
|
+
background: activeTheme.overlayBg,
|
|
3763
|
+
border: `1px solid ${activeTheme.panelBorder}`,
|
|
3764
|
+
boxShadow: '0 12px 30px rgba(0,0,0,0.45)',
|
|
3765
|
+
color: activeTheme.textPrimary,
|
|
3766
|
+
fontSize: '11px',
|
|
3767
|
+
letterSpacing: '0.04em',
|
|
3768
|
+
display: 'flex',
|
|
3769
|
+
gap: '14px',
|
|
3770
|
+
pointerEvents: 'none',
|
|
3771
|
+
zIndex: 60
|
|
3772
|
+
} },
|
|
3773
|
+
([
|
|
3774
|
+
{ label: 'O', value: crosshairMeta.candle.open },
|
|
3775
|
+
{ label: 'H', value: crosshairMeta.candle.high },
|
|
3776
|
+
{ label: 'L', value: crosshairMeta.candle.low },
|
|
3777
|
+
{ label: 'C', value: crosshairMeta.candle.close }
|
|
3778
|
+
]).map((item) => (React.createElement("div", { key: item.label, style: { display: 'flex', flexDirection: 'column', gap: '2px' } },
|
|
3779
|
+
React.createElement("span", { style: { color: activeTheme.textSecondary } }, item.label),
|
|
3780
|
+
React.createElement("span", { style: { fontWeight: 600 } }, numberFormatter.format(item.value))))),
|
|
3781
|
+
React.createElement("div", { style: { display: 'flex', flexDirection: 'column', gap: '2px' } },
|
|
3782
|
+
React.createElement("span", { style: { color: activeTheme.textSecondary } }, "V"),
|
|
3783
|
+
React.createElement("span", { style: { fontWeight: 600 } }, shortNumberFormatter.format(crosshairMeta.candle.volume ?? 0))))),
|
|
3784
|
+
crosshairMeta && (React.createElement(React.Fragment, null,
|
|
3785
|
+
React.createElement("div", { style: {
|
|
3786
|
+
position: 'absolute',
|
|
3787
|
+
right: `${priceScaleWidth}px`,
|
|
3788
|
+
top: `${Math.min(Math.max(crosshairMeta.y, 12), overlayHeight - 12)}px`,
|
|
3789
|
+
transform: 'translate(0, -50%)',
|
|
3790
|
+
background: activeTheme.scaleBg,
|
|
3791
|
+
border: `1px solid ${activeTheme.panelBorder}`,
|
|
3792
|
+
borderRadius: '6px 0 0 6px',
|
|
3793
|
+
padding: '2px 8px',
|
|
3794
|
+
fontSize: '11px',
|
|
3795
|
+
fontWeight: 600,
|
|
3796
|
+
color: activeTheme.textPrimary,
|
|
3797
|
+
pointerEvents: 'none',
|
|
3798
|
+
boxShadow: '0 8px 20px rgba(0,0,0,0.4)'
|
|
3799
|
+
} }, numberFormatter.format(crosshairMeta.price)),
|
|
3800
|
+
crosshairMeta.timestamp && (React.createElement("div", { style: {
|
|
3801
|
+
position: 'absolute',
|
|
3802
|
+
left: `${Math.min(Math.max(crosshairMeta.x, 40), chartWidth - 40)}px`,
|
|
3803
|
+
bottom: `${timeScaleHeight}px`,
|
|
3804
|
+
transform: 'translate(-50%, 50%)',
|
|
3805
|
+
background: activeTheme.scaleBg,
|
|
3806
|
+
border: `1px solid ${activeTheme.panelBorder}`,
|
|
3807
|
+
borderRadius: '6px',
|
|
3808
|
+
padding: '2px 8px',
|
|
3809
|
+
fontSize: '10px',
|
|
3810
|
+
letterSpacing: '0.05em',
|
|
3811
|
+
color: activeTheme.textPrimary,
|
|
3812
|
+
pointerEvents: 'none',
|
|
3813
|
+
boxShadow: '0 8px 20px rgba(0,0,0,0.35)'
|
|
3814
|
+
} }, timeFormatter.format(new Date(crosshairMeta.timestamp)))))),
|
|
3612
3815
|
clickedPrice && (React.createElement("div", { style: {
|
|
3613
3816
|
position: 'absolute',
|
|
3614
3817
|
background: '#1e222d',
|
|
@@ -3791,7 +3994,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3791
3994
|
React.createElement("span", null, field.label),
|
|
3792
3995
|
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' } })))))),
|
|
3793
3996
|
React.createElement("div", { style: { fontSize: '12px', color: activeTheme.textSecondary } }, strings.config.soon))),
|
|
3794
|
-
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 } },
|
|
3997
|
+
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 } },
|
|
3795
3998
|
React.createElement("span", null,
|
|
3796
3999
|
"Latency ",
|
|
3797
4000
|
latencyMs,
|
|
@@ -3800,7 +4003,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange }) => {
|
|
|
3800
4003
|
"Session ",
|
|
3801
4004
|
derivedStats?.session ?? 'Global'),
|
|
3802
4005
|
React.createElement("span", null, "Feed Binance Composite"),
|
|
3803
|
-
React.createElement("span", null, "Security AES-256"))));
|
|
4006
|
+
React.createElement("span", null, "Security AES-256")))));
|
|
3804
4007
|
};
|
|
3805
4008
|
var TradingViewChart_default = memo(TradingViewChart);
|
|
3806
4009
|
|