viainti-chart 1.0.3 → 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/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: '1h',
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
- return (React.createElement("div", { style: { position: 'relative', height: '100%' } },
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
- } }, buttonConfig.map((btn) => (React.createElement("div", { key: btn.key, style: { position: 'relative' } },
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: { ...popoverStyle, gridTemplateColumns: 'repeat(2, 1fr)', width: '200px' } }, cursorOptions.map(option => (React.createElement("button", { key: option.type, onClick: () => handleCursorSelect(option.type), 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: { ...popoverStyle, width: '280px' } },
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: { ...popoverStyle, width: '200px' } },
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: { ...popoverStyle, width: '220px' } },
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: { ...popoverStyle, width: '200px' } },
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: { ...popoverStyle, gridTemplateColumns: 'repeat(2, 1fr)', width: '160px' } }, measurementOptions.map(option => (React.createElement("button", { key: option.tool, onClick: () => handleSelectTool(option.tool), 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
- position: 'absolute',
1224
- left: '64px',
1225
- top: '50%',
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
- width: '40px',
1245
- height: '40px',
1246
- background: '#111827',
1271
+ height: '48px',
1272
+ borderRadius: '12px',
1247
1273
  border: '1px solid #1f2937',
1248
- borderRadius: '10px',
1249
- cursor: 'pointer',
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: '18px',
1254
- color: '#f8fafc'
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: '#050910',
1994
- heroFrom: '#111827',
1995
- heroTo: '#0b1120',
1996
- panelBg: 'rgba(15,23,42,0.9)',
1997
- panelBorder: '#1f2937',
1998
- cardBg: 'rgba(15,23,42,0.85)',
1999
- cardBorder: '#111827',
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: '#94a3b8',
2026
+ textSecondary: '#9ca3af',
2002
2027
  accent: '#2563eb',
2003
- accentSoft: 'rgba(37,99,235,0.2)',
2004
- railBg: 'rgba(2,6,23,0.85)',
2005
- railBorder: '#111827',
2006
- plotBg: '#020617',
2007
- plotBorder: '#111827',
2008
- scaleBg: 'rgba(15,23,42,0.9)',
2009
- overlayBg: 'rgba(5,7,15,0.85)'
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, showStats = true, showHeaderStats = true }) => {
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, showSt
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, showSt
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');
@@ -2447,6 +2513,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2447
2513
  const [showNoteModal, setShowNoteModal] = useState(false);
2448
2514
  const [pendingNotePoint, setPendingNotePoint] = useState(null);
2449
2515
  const [noteDraft, setNoteDraft] = useState('');
2516
+ const [priceFormat, setPriceFormat] = useState({ min: 2, max: 4 });
2450
2517
  const [isMobile, setIsMobile] = useState(false);
2451
2518
  useEffect(() => {
2452
2519
  if (!plotAreaRef.current)
@@ -2469,7 +2536,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2469
2536
  return () => window.clearTimeout(id);
2470
2537
  }, [seriesType, visibleData.length]);
2471
2538
  const locale = language === 'es' ? 'es-ES' : 'en-US';
2472
- const numberFormatter = useMemo(() => new Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }), [locale]);
2539
+ const numberFormatter = useMemo(() => new Intl.NumberFormat(locale, { minimumFractionDigits: priceFormat.min, maximumFractionDigits: priceFormat.max }), [locale, priceFormat]);
2473
2540
  const shortNumberFormatter = useMemo(() => new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }), [locale]);
2474
2541
  const timeFormatter = useMemo(() => new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit' }), [locale]);
2475
2542
  const latencyMs = useMemo(() => Math.round(20 + Math.random() * 15), []);
@@ -2531,9 +2598,14 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2531
2598
  const priceRange = Math.max(1e-6, maxPrice - minPrice);
2532
2599
  const maxVolume = Math.max(bounds.maxVolume, 1);
2533
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));
2534
2604
  const chartWidth = Math.max(1, cssWidth - priceScaleWidth);
2535
2605
  const chartHeight = Math.max(1, cssHeight - timeScaleHeight - volumeHeight);
2536
- const candleWidth = chartWidth / Math.max(candles.length, 1);
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);
2537
2609
  // Generate price labels
2538
2610
  const priceLabelsArray = [];
2539
2611
  for (let i = 0; i <= 10; i++) {
@@ -2568,9 +2640,11 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2568
2640
  gridCtx.lineTo(chartWidth, y);
2569
2641
  gridCtx.stroke();
2570
2642
  }
2571
- const timeStep = Math.max(1, Math.floor(candles.length / 10));
2643
+ const timeStep = Math.max(1, Math.floor(Math.max(candles.length, 1) / GRID_DIVISIONS));
2572
2644
  for (let i = 0; i <= candles.length; i += timeStep) {
2573
- const x = alignStroke(i * candleWidth);
2645
+ const x = alignStroke((i + LEFT_OFFSET_BARS) * candleStep);
2646
+ if (x < 0 || x > chartWidth)
2647
+ continue;
2574
2648
  gridCtx.beginPath();
2575
2649
  gridCtx.moveTo(x, 0);
2576
2650
  gridCtx.lineTo(x, chartHeight);
@@ -2596,7 +2670,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2596
2670
  : minPrice;
2597
2671
  chartCtx.beginPath();
2598
2672
  candles.forEach((candle, index) => {
2599
- const x = index * candleWidth + candleWidth / 2;
2673
+ const x = centerX(index);
2600
2674
  const yClose = ((maxPrice - candle.close) / priceRange) * chartHeight;
2601
2675
  if (index === 0) {
2602
2676
  chartCtx.moveTo(x, yClose);
@@ -2604,6 +2678,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2604
2678
  else if (seriesType === 'step') {
2605
2679
  const prev = candles[index - 1];
2606
2680
  if (prev) {
2681
+ centerX(index - 1);
2607
2682
  const prevY = ((maxPrice - prev.close) / priceRange) * chartHeight;
2608
2683
  chartCtx.lineTo(x, prevY);
2609
2684
  chartCtx.lineTo(x, yClose);
@@ -2619,7 +2694,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2619
2694
  chartCtx.stroke();
2620
2695
  if (seriesType === 'line-markers') {
2621
2696
  candles.forEach((candle, index) => {
2622
- const x = index * candleWidth + candleWidth / 2;
2697
+ const x = centerX(index);
2623
2698
  const yClose = ((maxPrice - candle.close) / priceRange) * chartHeight;
2624
2699
  chartCtx.beginPath();
2625
2700
  chartCtx.arc(x, yClose, 2.5, 0, 2 * Math.PI);
@@ -2628,8 +2703,8 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2628
2703
  });
2629
2704
  }
2630
2705
  if (seriesType === 'area' || seriesType === 'hlc-area' || seriesType === 'baseline') {
2631
- chartCtx.lineTo((candles.length - 1) * candleWidth + candleWidth / 2, ((maxPrice - baselinePrice) / priceRange) * chartHeight);
2632
- chartCtx.lineTo(0 * candleWidth + candleWidth / 2, ((maxPrice - baselinePrice) / priceRange) * chartHeight);
2706
+ chartCtx.lineTo(centerX(candles.length - 1), ((maxPrice - baselinePrice) / priceRange) * chartHeight);
2707
+ chartCtx.lineTo(centerX(0), ((maxPrice - baselinePrice) / priceRange) * chartHeight);
2633
2708
  chartCtx.closePath();
2634
2709
  chartCtx.fillStyle =
2635
2710
  seriesType === 'baseline' ? 'rgba(41,98,255,0.25)' : 'rgba(41,98,255,0.15)';
@@ -2639,7 +2714,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2639
2714
  else {
2640
2715
  // ----- CANDLE / BAR-BASED SERIES -----
2641
2716
  candles.forEach((candle, index) => {
2642
- const xCenter = index * candleWidth + candleWidth / 2;
2717
+ const xCenter = centerX(index);
2643
2718
  const strokeX = alignStroke(xCenter);
2644
2719
  const yHigh = ((maxPrice - candle.high) / priceRange) * chartHeight;
2645
2720
  const yLow = ((maxPrice - candle.low) / priceRange) * chartHeight;
@@ -2724,12 +2799,12 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2724
2799
  if (volumeCtx) {
2725
2800
  volumeCtx.clearRect(0, 0, chartWidth, volumeHeight);
2726
2801
  candles.forEach((candle, index) => {
2727
- const x = index * candleWidth;
2802
+ const xLeft = centerX(index) - candleWidth / 2;
2728
2803
  const barHeight = ((candle.volume || 0) / maxVolume) * volumeHeight;
2729
2804
  const y = volumeHeight - barHeight;
2730
2805
  const isBullish = candle.close > candle.open;
2731
2806
  volumeCtx.fillStyle = isBullish ? bullishColor : bearishColor;
2732
- volumeCtx.fillRect(x + 1, y, candleWidth - 2, barHeight);
2807
+ volumeCtx.fillRect(xLeft + 1, y, Math.max(1, candleWidth - 2), barHeight);
2733
2808
  });
2734
2809
  }
2735
2810
  // Draw indicators
@@ -2743,7 +2818,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2743
2818
  indicatorCtx.beginPath();
2744
2819
  indicator.data.forEach((value, index) => {
2745
2820
  if (value !== null && value !== undefined) {
2746
- const x = index * candleWidth + candleWidth / 2;
2821
+ const x = centerX(index);
2747
2822
  const y = ((maxPrice - value) / priceRange) * chartHeight;
2748
2823
  if (index === 0) {
2749
2824
  indicatorCtx.moveTo(x, y);
@@ -2758,11 +2833,11 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2758
2833
  else if (indicator.type === 'histogram') {
2759
2834
  indicator.data.forEach((value, index) => {
2760
2835
  if (value !== null && value !== undefined) {
2761
- const x = index * candleWidth;
2836
+ const xLeft = centerX(index) - candleWidth / 2;
2762
2837
  const barHeight = Math.abs(value) * chartHeight * 0.1; // Scale histogram
2763
2838
  const y = value >= 0 ? chartHeight / 2 - barHeight : chartHeight / 2;
2764
2839
  indicatorCtx.fillStyle = value >= 0 ? '#089981' : '#f23645';
2765
- indicatorCtx.fillRect(x + 2, y, candleWidth - 4, barHeight);
2840
+ indicatorCtx.fillRect(xLeft + 2, y, Math.max(1, candleWidth - 4), barHeight);
2766
2841
  }
2767
2842
  });
2768
2843
  }
@@ -2792,32 +2867,46 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2792
2867
  }
2793
2868
  }, [chartWidth, chartHeight, overlayHeight]);
2794
2869
  const handlePointerMove = useCallback((e) => {
2795
- if (interactionsLocked || !visibleData.length)
2870
+ if (interactionsLocked || !visibleData.length) {
2871
+ setCrosshairMeta(null);
2796
2872
  return;
2873
+ }
2797
2874
  const rect = e.currentTarget.getBoundingClientRect();
2798
2875
  const x = e.clientX - rect.left;
2799
2876
  const y = e.clientY - rect.top;
2800
2877
  setClickedPrice(null);
2801
2878
  if (cursorType !== 'cross') {
2879
+ setCrosshairMeta(null);
2802
2880
  return;
2803
2881
  }
2804
2882
  if (!isDraggingRef.current) {
2805
2883
  const { min, max } = priceWindowRef.current;
2806
- const snappedX = coordsRef.current.snapToCandle(x, chartWidth, visibleData.length);
2807
- const snappedY = coordsRef.current.snapToPrice(y, chartHeight, min, max);
2808
- mousePosRef.current = {
2809
- x: coordsRef.current.timeToPixel(snappedX, chartWidth, visibleData.length),
2810
- y: coordsRef.current.priceToPixel(snappedY, chartHeight, min, max)
2811
- };
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
+ });
2812
2900
  showCrosshairRef.current = true;
2813
2901
  requestAnimationFrame(drawCrosshair);
2814
2902
  }
2815
- }, [visibleData, cursorType, chartWidth, chartHeight, drawCrosshair, interactionsLocked]);
2903
+ }, [visibleData, cursorType, chartWidth, chartHeight, drawCrosshair, interactionsLocked, magnetEnabled]);
2816
2904
  const handleMouseLeave = useCallback(() => {
2817
2905
  showCrosshairRef.current = false;
2818
2906
  isDraggingRef.current = false;
2819
2907
  dragSampleRef.current.velocity = 0;
2820
2908
  dragSampleRef.current.lastTs = 0;
2909
+ setCrosshairMeta(null);
2821
2910
  requestAnimationFrame(() => {
2822
2911
  const overlayCtx = overlayRef.current?.getContext('2d');
2823
2912
  if (overlayCtx) {
@@ -2861,15 +2950,21 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2861
2950
  ? { minPrice: priceWindowRef.current.min, maxPrice: priceWindowRef.current.max }
2862
2951
  : getDataBounds(targetData);
2863
2952
  const dataLength = Math.max(targetData.length, 1);
2864
- if (magnetEnabled && visibleData.length) {
2865
- const snappedIndex = coordsRef.current.snapToCandle(x, chartWidth, dataLength);
2866
- const snappedPrice = coordsRef.current.snapToPrice(y, chartHeight, bounds.minPrice, bounds.maxPrice, 0.5);
2867
- x = coordsRef.current.timeToPixel(snappedIndex, chartWidth, dataLength);
2868
- y = coordsRef.current.priceToPixel(snappedPrice, chartHeight, bounds.minPrice, bounds.maxPrice);
2869
- }
2870
- const price = coordsRef.current.pixelToPrice(y, chartHeight, bounds.minPrice, bounds.maxPrice);
2871
- const time = coordsRef.current.pixelToTime(x, chartWidth, dataLength);
2872
- return { x, y, price, time };
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 };
2873
2968
  }, [visibleData, storeData, magnetEnabled, chartWidth, chartHeight, interactionsLocked]);
2874
2969
  const tryEraseDrawing = useCallback((x, y) => {
2875
2970
  for (let i = drawings.length - 1; i >= 0; i--) {
@@ -3080,7 +3175,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
3080
3175
  }
3081
3176
  if (visibleData.length) {
3082
3177
  const { min, max } = priceWindowRef.current;
3083
- const snappedIndex = coordsRef.current.snapToCandle(rawX, chartWidth, visibleData.length);
3178
+ const snappedIndex = clampCandleIndex(pixelToCandleIndex(rawX, chartWidth, visibleData.length), visibleData.length);
3084
3179
  setSelectedCandleIndex(snappedIndex);
3085
3180
  const price = coordsRef.current.pixelToPrice(rawY, chartHeight, min, max);
3086
3181
  setClickedPrice({ x: rawX, y: rawY, price });
@@ -3606,7 +3701,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
3606
3701
  minHeight: 0,
3607
3702
  alignItems: 'stretch'
3608
3703
  } },
3609
- 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%' } },
3610
3705
  React.createElement("div", { style: {
3611
3706
  height: '100%',
3612
3707
  borderRadius: '24px',
@@ -3617,7 +3712,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
3617
3712
  alignItems: 'center',
3618
3713
  padding: isMobile ? '12px 0' : '16px 0'
3619
3714
  } },
3620
- React.createElement(DrawingToolbar, null))),
3715
+ React.createElement(DrawingToolbar, null)))),
3621
3716
  React.createElement("div", { ref: plotAreaRef, style: {
3622
3717
  flex: 1,
3623
3718
  position: 'relative',
@@ -3658,6 +3753,65 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
3658
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' } },
3659
3754
  React.createElement("span", { style: { textTransform: 'uppercase', fontSize: '10px', letterSpacing: '0.08em' } }, strings.axis.time),
3660
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)))))),
3661
3815
  clickedPrice && (React.createElement("div", { style: {
3662
3816
  position: 'absolute',
3663
3817
  background: '#1e222d',