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.cjs CHANGED
@@ -140,8 +140,20 @@ function BsArrowRepeat (props) {
140
140
  return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M4 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 4 8"},"child":[]}]})(props);
141
141
  }function BsDiagram3 (props) {
142
142
  return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"fillRule":"evenodd","d":"M6 3.5A1.5 1.5 0 0 1 7.5 2h1A1.5 1.5 0 0 1 10 3.5v1A1.5 1.5 0 0 1 8.5 6v1H14a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0V8h-5v.5a.5.5 0 0 1-1 0v-1A.5.5 0 0 1 2 7h5.5V6A1.5 1.5 0 0 1 6 4.5zM8.5 5a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5zM0 11.5A1.5 1.5 0 0 1 1.5 10h1A1.5 1.5 0 0 1 4 11.5v1A1.5 1.5 0 0 1 2.5 14h-1A1.5 1.5 0 0 1 0 12.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm4.5.5A1.5 1.5 0 0 1 7.5 10h1a1.5 1.5 0 0 1 1.5 1.5v1A1.5 1.5 0 0 1 8.5 14h-1A1.5 1.5 0 0 1 6 12.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5zm4.5.5a1.5 1.5 0 0 1 1.5-1.5h1a1.5 1.5 0 0 1 1.5 1.5v1a1.5 1.5 0 0 1-1.5 1.5h-1a1.5 1.5 0 0 1-1.5-1.5zm1.5-.5a.5.5 0 0 0-.5.5v1a.5.5 0 0 0 .5.5h1a.5.5 0 0 0 .5-.5v-1a.5.5 0 0 0-.5-.5z"},"child":[]}]})(props);
143
+ }function BsEmojiAngry (props) {
144
+ return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"},"child":[]},{"tag":"path","attr":{"d":"M4.285 12.433a.5.5 0 0 0 .683-.183A3.5 3.5 0 0 1 8 10.5c1.295 0 2.426.703 3.032 1.75a.5.5 0 0 0 .866-.5A4.5 4.5 0 0 0 8 9.5a4.5 4.5 0 0 0-3.898 2.25.5.5 0 0 0 .183.683m6.991-8.38a.5.5 0 1 1 .448.894l-1.009.504c.176.27.285.64.285 1.049 0 .828-.448 1.5-1 1.5s-1-.672-1-1.5c0-.247.04-.48.11-.686a.502.502 0 0 1 .166-.761zm-6.552 0a.5.5 0 0 0-.448.894l1.009.504A1.94 1.94 0 0 0 5 6.5C5 7.328 5.448 8 6 8s1-.672 1-1.5c0-.247-.04-.48-.11-.686a.502.502 0 0 0-.166-.761z"},"child":[]}]})(props);
145
+ }function BsEmojiDizzy (props) {
146
+ return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"},"child":[]},{"tag":"path","attr":{"d":"M9.146 5.146a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 0 1 .708.708l-.647.646.647.646a.5.5 0 0 1-.708.708l-.646-.647-.646.647a.5.5 0 1 1-.708-.708l.647-.646-.647-.646a.5.5 0 0 1 0-.708m-5 0a.5.5 0 0 1 .708 0l.646.647.646-.647a.5.5 0 1 1 .708.708l-.647.646.647.646a.5.5 0 1 1-.708.708L5.5 7.207l-.646.647a.5.5 0 1 1-.708-.708l.647-.646-.647-.646a.5.5 0 0 1 0-.708M10 11a2 2 0 1 1-4 0 2 2 0 0 1 4 0"},"child":[]}]})(props);
147
+ }function BsEmojiHeartEyes (props) {
148
+ return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"},"child":[]},{"tag":"path","attr":{"d":"M11.315 10.014a.5.5 0 0 1 .548.736A4.5 4.5 0 0 1 7.965 13a4.5 4.5 0 0 1-3.898-2.25.5.5 0 0 1 .548-.736h.005l.017.005.067.015.252.055c.215.046.515.108.857.169.693.124 1.522.242 2.152.242s1.46-.118 2.152-.242a27 27 0 0 0 1.109-.224l.067-.015.017-.004.005-.002zM4.756 4.566c.763-1.424 4.02-.12.952 3.434-4.496-1.596-2.35-4.298-.952-3.434m6.488 0c1.398-.864 3.544 1.838-.952 3.434-3.067-3.554.19-4.858.952-3.434"},"child":[]}]})(props);
149
+ }function BsEmojiLaughing (props) {
150
+ return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"},"child":[]},{"tag":"path","attr":{"d":"M12.331 9.5a1 1 0 0 1 0 1A5 5 0 0 1 8 13a5 5 0 0 1-4.33-2.5A1 1 0 0 1 4.535 9h6.93a1 1 0 0 1 .866.5M7 6.5c0 .828-.448 0-1 0s-1 .828-1 0S5.448 5 6 5s1 .672 1 1.5m4 0c0 .828-.448 0-1 0s-1 .828-1 0S9.448 5 10 5s1 .672 1 1.5"},"child":[]}]})(props);
151
+ }function BsEmojiNeutral (props) {
152
+ return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"},"child":[]},{"tag":"path","attr":{"d":"M4 10.5a.5.5 0 0 0 .5.5h7a.5.5 0 0 0 0-1h-7a.5.5 0 0 0-.5.5m3-4C7 5.672 6.552 5 6 5s-1 .672-1 1.5S5.448 8 6 8s1-.672 1-1.5m4 0c0-.828-.448-1.5-1-1.5s-1 .672-1 1.5S9.448 8 10 8s1-.672 1-1.5"},"child":[]}]})(props);
143
153
  }function BsEmojiSmile (props) {
144
154
  return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"},"child":[]},{"tag":"path","attr":{"d":"M4.285 9.567a.5.5 0 0 1 .683.183A3.5 3.5 0 0 0 8 11.5a3.5 3.5 0 0 0 3.032-1.75.5.5 0 1 1 .866.5A4.5 4.5 0 0 1 8 12.5a4.5 4.5 0 0 1-3.898-2.25.5.5 0 0 1 .183-.683M7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5m4 0c0 .828-.448 1.5-1 1.5s-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5"},"child":[]}]})(props);
155
+ }function BsEmojiSunglasses (props) {
156
+ return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M4.968 9.75a.5.5 0 1 0-.866.5A4.5 4.5 0 0 0 8 12.5a4.5 4.5 0 0 0 3.898-2.25.5.5 0 1 0-.866-.5A3.5 3.5 0 0 1 8 11.5a3.5 3.5 0 0 1-3.032-1.75M7 5.116V5a1 1 0 0 0-1-1H3.28a1 1 0 0 0-.97 1.243l.311 1.242A2 2 0 0 0 4.561 8H5a2 2 0 0 0 1.994-1.839A3 3 0 0 1 8 6c.393 0 .74.064 1.006.161A2 2 0 0 0 11 8h.438a2 2 0 0 0 1.94-1.515l.311-1.242A1 1 0 0 0 12.72 4H10a1 1 0 0 0-1 1v.116A4.2 4.2 0 0 0 8 5c-.35 0-.69.04-1 .116"},"child":[]},{"tag":"path","attr":{"d":"M16 8A8 8 0 1 1 0 8a8 8 0 0 1 16 0m-1 0A7 7 0 1 0 1 8a7 7 0 0 0 14 0"},"child":[]}]})(props);
145
157
  }function BsEyeSlash (props) {
146
158
  return GenIcon({"attr":{"fill":"currentColor","viewBox":"0 0 16 16"},"child":[{"tag":"path","attr":{"d":"M13.359 11.238C15.06 9.72 16 8 16 8s-3-5.5-8-5.5a7 7 0 0 0-2.79.588l.77.771A6 6 0 0 1 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13 13 0 0 1 14.828 8q-.086.13-.195.288c-.335.48-.83 1.12-1.465 1.755q-.247.248-.517.486z"},"child":[]},{"tag":"path","attr":{"d":"M11.297 9.176a3.5 3.5 0 0 0-4.474-4.474l.823.823a2.5 2.5 0 0 1 2.829 2.829zm-2.943 1.299.822.822a3.5 3.5 0 0 1-4.474-4.474l.823.823a2.5 2.5 0 0 0 2.829 2.829"},"child":[]},{"tag":"path","attr":{"d":"M3.35 5.47q-.27.24-.518.487A13 13 0 0 0 1.172 8l.195.288c.335.48.83 1.12 1.465 1.755C4.121 11.332 5.881 12.5 8 12.5c.716 0 1.39-.133 2.02-.36l.77.772A7 7 0 0 1 8 13.5C3 13.5 0 8 0 8s.939-1.721 2.641-3.238l.708.709zm10.296 8.884-12-12 .708-.708 12 12z"},"child":[]}]})(props);
147
159
  }function BsGear (props) {
@@ -843,7 +855,7 @@ function createStore(creator) {
843
855
  const useChartStore = createStore((set) => ({
844
856
  data: [],
845
857
  visibleData: [],
846
- timeframe: '1h',
858
+ timeframe: '12h',
847
859
  zoomLevel: 1,
848
860
  panOffset: 0,
849
861
  indicators: [],
@@ -922,7 +934,6 @@ const useChartStore = createStore((set) => ({
922
934
  toggleDrawingsHidden: () => set((state) => ({ drawingsHidden: !state.drawingsHidden }))
923
935
  }));
924
936
 
925
- const EMOJIS = ['⭐', '🔥', '🚀', '✅', '❌', '💎', '🐂', '🐻', '📈', '📉'];
926
937
  const cursorOptions = [
927
938
  { type: 'cross', icon: React.createElement(BsCursor, null), label: 'Cross' },
928
939
  { type: 'dot', icon: React.createElement("div", { style: { width: '6px', height: '6px', borderRadius: '50%', background: 'currentColor' } }), label: 'Dot' },
@@ -962,6 +973,15 @@ const measurementOptions = [
962
973
  { tool: 'fibonacci', icon: React.createElement(BsGraphUp, null), label: 'Fibonacci' },
963
974
  { tool: 'ruler', icon: React.createElement(BsGraphDown, null), label: 'Regla' }
964
975
  ];
976
+ const emojiOptions = [
977
+ { label: 'Smile', icon: React.createElement(BsEmojiSmile, null), value: '🙂' },
978
+ { label: 'Laugh', icon: React.createElement(BsEmojiLaughing, null), value: '😂' },
979
+ { label: 'Heart Eyes', icon: React.createElement(BsEmojiHeartEyes, null), value: '😍' },
980
+ { label: 'Sunglasses', icon: React.createElement(BsEmojiSunglasses, null), value: '😎' },
981
+ { label: 'Dizzy', icon: React.createElement(BsEmojiDizzy, null), value: '😵' },
982
+ { label: 'Neutral', icon: React.createElement(BsEmojiNeutral, null), value: '😐' },
983
+ { label: 'Angry', icon: React.createElement(BsEmojiAngry, null), value: '😠' }
984
+ ];
965
985
  const popoverStyle = {
966
986
  position: 'absolute',
967
987
  left: '64px',
@@ -980,6 +1000,15 @@ const DrawingToolbar = () => {
980
1000
  const { activeTool, cursorType, strokeColor, strokeWidth, setActiveTool, setCursorType, setStrokeColor, setStrokeWidth, clearDrawings, setSelectedEmoji, setIsDrawing } = useChartStore();
981
1001
  const [openMenu, setOpenMenu] = React.useState(null);
982
1002
  const [showEmojiPicker, setShowEmojiPicker] = React.useState(false);
1003
+ const [isCompact, setIsCompact] = React.useState(false);
1004
+ React.useEffect(() => {
1005
+ const handleResize = () => {
1006
+ setIsCompact(window.innerWidth < 768);
1007
+ };
1008
+ handleResize();
1009
+ window.addEventListener('resize', handleResize);
1010
+ return () => window.removeEventListener('resize', handleResize);
1011
+ }, []);
983
1012
  const LINE_COLORS = ['#8ab4ff', '#f472b6', '#34d399', '#facc15', '#f87171', '#e2e8f0'];
984
1013
  const WIDTH_OPTIONS = [1, 1.5, 2.5, 3.5];
985
1014
  const handleSelectTool = (tool) => {
@@ -1075,14 +1104,27 @@ const DrawingToolbar = () => {
1075
1104
  title: 'Limpiar todo'
1076
1105
  }
1077
1106
  ];
1078
- return (React.createElement("div", { style: { position: 'relative', height: '100%' } },
1107
+ const filteredButtons = isCompact
1108
+ ? buttonConfig.filter(btn => !['pitchforks', 'measurements'].includes(btn.key))
1109
+ : buttonConfig;
1110
+ const buildPopoverStyle = (custom) => ({
1111
+ ...popoverStyle,
1112
+ ...(isCompact
1113
+ ? { left: '0px', top: 'calc(100% + 12px)', transform: 'none', width: 'min(360px, 92vw)' }
1114
+ : {}),
1115
+ ...custom
1116
+ });
1117
+ return (React.createElement("div", { style: { position: 'relative', height: '100%', width: '100%' } },
1079
1118
  React.createElement("div", { style: {
1080
- height: '100%',
1119
+ height: isCompact ? 'auto' : '100%',
1120
+ width: '100%',
1081
1121
  display: 'flex',
1082
- flexDirection: 'column',
1122
+ flexDirection: isCompact ? 'row' : 'column',
1083
1123
  alignItems: 'center',
1084
- gap: '12px'
1085
- } }, buttonConfig.map((btn) => (React.createElement("div", { key: btn.key, style: { position: 'relative' } },
1124
+ gap: '12px',
1125
+ overflowX: isCompact ? 'auto' : 'visible',
1126
+ padding: isCompact ? '12px 6px' : 0
1127
+ } }, filteredButtons.map((btn) => (React.createElement("div", { key: btn.key, style: { position: 'relative' } },
1086
1128
  React.createElement("button", { onClick: btn.onClick, title: btn.title, "aria-label": btn.title, style: {
1087
1129
  width: '46px',
1088
1130
  height: '46px',
@@ -1097,7 +1139,7 @@ const DrawingToolbar = () => {
1097
1139
  fontSize: '18px',
1098
1140
  transition: 'all 0.2s'
1099
1141
  } }, btn.icon),
1100
- btn.key === 'cursor' && openMenu === 'cursor' && (React.createElement("div", { style: { ...popoverStyle, gridTemplateColumns: 'repeat(2, 1fr)', width: '200px' } }, cursorOptions.map(option => (React.createElement("button", { key: option.type, onClick: () => handleCursorSelect(option.type), style: {
1142
+ btn.key === 'cursor' && openMenu === 'cursor' && (React.createElement("div", { style: buildPopoverStyle({ gridTemplateColumns: 'repeat(2, 1fr)', width: isCompact ? 'min(320px, 92vw)' : '200px' }) }, cursorOptions.map(option => (React.createElement("button", { key: option.type, onClick: () => handleCursorSelect(option.type), style: {
1101
1143
  width: '84px',
1102
1144
  height: '60px',
1103
1145
  borderRadius: '12px',
@@ -1113,7 +1155,7 @@ const DrawingToolbar = () => {
1113
1155
  }, title: option.label },
1114
1156
  React.createElement("span", { style: { fontSize: '18px', display: 'flex', alignItems: 'center', justifyContent: 'center' } }, option.icon),
1115
1157
  React.createElement("span", { style: { fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase' } }, option.label)))))),
1116
- btn.key === 'lines' && openMenu === 'lines' && (React.createElement("div", { style: { ...popoverStyle, width: '280px' } },
1158
+ btn.key === 'lines' && openMenu === 'lines' && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(360px, 92vw)' : '280px' }) },
1117
1159
  React.createElement("p", { style: { margin: 0, fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Lines"),
1118
1160
  React.createElement("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '8px' } }, lineOptions.map(option => (React.createElement("button", { key: option.tool, onClick: () => handleSelectTool(option.tool), style: {
1119
1161
  borderRadius: '12px',
@@ -1153,7 +1195,7 @@ const DrawingToolbar = () => {
1153
1195
  } },
1154
1196
  width,
1155
1197
  "px"))))))),
1156
- btn.key === 'shapes' && openMenu === 'shapes' && (React.createElement("div", { style: { ...popoverStyle, width: '200px' } },
1198
+ btn.key === 'shapes' && openMenu === 'shapes' && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(320px, 92vw)' : '200px' }) },
1157
1199
  React.createElement("p", { style: { margin: 0, fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Shapes"),
1158
1200
  React.createElement("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '8px' } }, shapeOptions.map(option => (React.createElement("button", { key: option.tool, onClick: () => handleSelectTool(option.tool), style: {
1159
1201
  borderRadius: '12px',
@@ -1170,7 +1212,7 @@ const DrawingToolbar = () => {
1170
1212
  }, title: option.label },
1171
1213
  React.createElement("span", { style: { fontSize: '16px' } }, option.icon),
1172
1214
  React.createElement("span", { style: { fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.08em' } }, option.label))))))),
1173
- btn.key === 'channels' && openMenu === 'channels' && (React.createElement("div", { style: { ...popoverStyle, width: '220px' } },
1215
+ btn.key === 'channels' && openMenu === 'channels' && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(340px, 92vw)' : '220px' }) },
1174
1216
  React.createElement("p", { style: { margin: 0, fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Channels"),
1175
1217
  React.createElement("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '8px' } }, channelOptions.map(option => (React.createElement("button", { key: option.tool, onClick: () => handleSelectTool(option.tool), style: {
1176
1218
  borderRadius: '12px',
@@ -1187,7 +1229,7 @@ const DrawingToolbar = () => {
1187
1229
  }, title: option.label },
1188
1230
  React.createElement("span", { style: { fontSize: '16px' } }, option.icon),
1189
1231
  React.createElement("span", { style: { fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.08em' } }, option.label))))))),
1190
- btn.key === 'pitchforks' && openMenu === 'pitchforks' && (React.createElement("div", { style: { ...popoverStyle, width: '200px' } },
1232
+ btn.key === 'pitchforks' && openMenu === 'pitchforks' && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(320px, 92vw)' : '200px' }) },
1191
1233
  React.createElement("p", { style: { margin: 0, fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Pitchforks"),
1192
1234
  React.createElement("div", { style: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: '8px' } }, pitchforkOptions.map(option => (React.createElement("button", { key: option.tool, onClick: () => handleSelectTool(option.tool), style: {
1193
1235
  borderRadius: '12px',
@@ -1204,7 +1246,7 @@ const DrawingToolbar = () => {
1204
1246
  }, title: option.label },
1205
1247
  React.createElement("span", { style: { fontSize: '16px' } }, option.icon),
1206
1248
  React.createElement("span", { style: { fontSize: '10px', textTransform: 'uppercase', letterSpacing: '0.08em', textAlign: 'center' } }, option.label))))))),
1207
- btn.key === 'measurements' && openMenu === 'measurements' && (React.createElement("div", { style: { ...popoverStyle, gridTemplateColumns: 'repeat(2, 1fr)', width: '160px' } }, measurementOptions.map(option => (React.createElement("button", { key: option.tool, onClick: () => handleSelectTool(option.tool), style: {
1249
+ btn.key === 'measurements' && openMenu === 'measurements' && (React.createElement("div", { style: buildPopoverStyle({ gridTemplateColumns: 'repeat(2, 1fr)', width: isCompact ? 'min(280px, 80vw)' : '160px' }) }, measurementOptions.map(option => (React.createElement("button", { key: option.tool, onClick: () => handleSelectTool(option.tool), style: {
1208
1250
  width: '64px',
1209
1251
  height: '54px',
1210
1252
  borderRadius: '12px',
@@ -1221,41 +1263,24 @@ const DrawingToolbar = () => {
1221
1263
  }, title: option.label },
1222
1264
  React.createElement("span", { style: { fontSize: '16px' } }, option.icon),
1223
1265
  React.createElement("span", null, option.label)))))),
1224
- btn.key === 'emoji' && showEmojiPicker && (React.createElement("div", { style: {
1225
- position: 'absolute',
1226
- left: '64px',
1227
- top: '50%',
1228
- transform: 'translateY(-50%)',
1229
- zIndex: 1100,
1230
- background: '#020617',
1231
- borderRadius: '14px',
1232
- overflow: 'hidden',
1233
- boxShadow: '0 18px 40px rgba(0,0,0,0.65)',
1234
- padding: '10px',
1235
- border: '1px solid #1f2937'
1236
- } },
1237
- React.createElement("div", { style: {
1238
- display: 'grid',
1239
- gridTemplateColumns: 'repeat(3, 1fr)',
1240
- gap: '6px'
1241
- } }, EMOJIS.map((emoji) => (React.createElement("button", { key: emoji, onClick: () => {
1242
- setSelectedEmoji(emoji);
1266
+ btn.key === 'emoji' && showEmojiPicker && (React.createElement("div", { style: buildPopoverStyle({ width: isCompact ? 'min(320px, 92vw)' : '220px' }) },
1267
+ React.createElement("p", { style: { margin: '0 0 6px', fontSize: '10px', letterSpacing: '0.08em', textTransform: 'uppercase', color: '#94a3b8' } }, "Emojis"),
1268
+ React.createElement("div", { style: { display: 'grid', gridTemplateColumns: isCompact ? 'repeat(4, minmax(0, 1fr))' : 'repeat(3, minmax(0, 1fr))', gap: '8px' } }, emojiOptions.map(option => (React.createElement("button", { key: option.label, onClick: () => {
1269
+ setSelectedEmoji(option.value);
1243
1270
  setActiveTool('icon');
1244
1271
  setShowEmojiPicker(false);
1245
1272
  }, style: {
1246
- width: '40px',
1247
- height: '40px',
1248
- background: '#111827',
1273
+ height: '48px',
1274
+ borderRadius: '12px',
1249
1275
  border: '1px solid #1f2937',
1250
- borderRadius: '10px',
1251
- cursor: 'pointer',
1276
+ background: 'rgba(2,6,23,0.65)',
1277
+ color: '#f8fafc',
1252
1278
  display: 'flex',
1253
1279
  alignItems: 'center',
1254
1280
  justifyContent: 'center',
1255
- fontSize: '18px',
1256
- color: '#f8fafc'
1257
- } },
1258
- React.createElement("span", null, emoji)))))))))))));
1281
+ fontSize: '22px',
1282
+ cursor: 'pointer'
1283
+ }, title: option.label, "aria-label": option.label }, option.icon))))))))))));
1259
1284
  };
1260
1285
 
1261
1286
  const drawRoundedRect = (ctx, x, y, width, height, radius = 8) => {
@@ -1992,23 +2017,23 @@ const LINE_TOOL_TYPES = [
1992
2017
  const LINE_TOOL_SET = new Set(LINE_TOOL_TYPES);
1993
2018
  const THEME_PRESETS = {
1994
2019
  dark: {
1995
- pageBg: '#050910',
1996
- heroFrom: '#111827',
1997
- heroTo: '#0b1120',
1998
- panelBg: 'rgba(15,23,42,0.9)',
1999
- panelBorder: '#1f2937',
2000
- cardBg: 'rgba(15,23,42,0.85)',
2001
- cardBorder: '#111827',
2020
+ pageBg: '#000000',
2021
+ heroFrom: '#050505',
2022
+ heroTo: '#000000',
2023
+ panelBg: 'rgba(0,0,0,0.92)',
2024
+ panelBorder: '#0f0f0f',
2025
+ cardBg: 'rgba(0,0,0,0.88)',
2026
+ cardBorder: '#050505',
2002
2027
  textPrimary: '#f8fafc',
2003
- textSecondary: '#94a3b8',
2028
+ textSecondary: '#9ca3af',
2004
2029
  accent: '#2563eb',
2005
- accentSoft: 'rgba(37,99,235,0.2)',
2006
- railBg: 'rgba(2,6,23,0.85)',
2007
- railBorder: '#111827',
2008
- plotBg: '#020617',
2009
- plotBorder: '#111827',
2010
- scaleBg: 'rgba(15,23,42,0.9)',
2011
- overlayBg: 'rgba(5,7,15,0.85)'
2030
+ accentSoft: 'rgba(37,99,235,0.25)',
2031
+ railBg: 'rgba(0,0,0,0.9)',
2032
+ railBorder: '#080808',
2033
+ plotBg: '#010101',
2034
+ plotBorder: '#0d0d0d',
2035
+ scaleBg: 'rgba(0,0,0,0.92)',
2036
+ overlayBg: 'rgba(0,0,0,0.85)'
2012
2037
  },
2013
2038
  blue: {
2014
2039
  pageBg: '#060e1f',
@@ -2060,6 +2085,10 @@ const CUSTOM_THEME_FIELDS = [
2060
2085
  const MIN_CANDLE_PX = 4;
2061
2086
  const MAX_CANDLE_PX = 28;
2062
2087
  const BUFFER_FRACTION = 0.18;
2088
+ const LEFT_OFFSET_BARS = 2;
2089
+ const RIGHT_OFFSET_BARS = 18;
2090
+ const BAR_BODY_RATIO = 0.78;
2091
+ const GRID_DIVISIONS = 10;
2063
2092
  const INERTIA_DURATION_MS = 900;
2064
2093
  const ZOOM_MIN = 0.2;
2065
2094
  const ZOOM_MAX = 6;
@@ -2067,6 +2096,42 @@ const easeOutQuad = (t) => 1 - (1 - t) * (1 - t);
2067
2096
  const easeInOutCubic = (t) => t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
2068
2097
  const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
2069
2098
  const alignStroke = (value) => Math.round(value) + 0.5;
2099
+ const determinePriceFormat = (reference) => {
2100
+ const value = Math.abs(reference);
2101
+ if (!Number.isFinite(value) || value === 0)
2102
+ return { min: 2, max: 6 };
2103
+ if (value < 0.0001)
2104
+ return { min: 6, max: 8 };
2105
+ if (value < 0.001)
2106
+ return { min: 5, max: 7 };
2107
+ if (value < 0.01)
2108
+ return { min: 4, max: 6 };
2109
+ if (value < 0.1)
2110
+ return { min: 4, max: 5 };
2111
+ if (value < 1)
2112
+ return { min: 3, max: 4 };
2113
+ if (value < 100)
2114
+ return { min: 2, max: 3 };
2115
+ return { min: 2, max: 2 };
2116
+ };
2117
+ const getEffectiveBarCount = (count) => Math.max(count + LEFT_OFFSET_BARS + RIGHT_OFFSET_BARS, 1);
2118
+ const getCandleStep = (width, count) => (width <= 0 ? 0 : width / getEffectiveBarCount(count));
2119
+ const getCandleCenter = (index, width, count) => {
2120
+ const step = getCandleStep(width, count);
2121
+ return (index + LEFT_OFFSET_BARS + 0.5) * step;
2122
+ };
2123
+ const clampPixelToChart = (pixel, width) => Math.max(0, Math.min(width, pixel));
2124
+ const pixelToCandleIndex = (pixel, width, count) => {
2125
+ const step = getCandleStep(width, count);
2126
+ if (step === 0)
2127
+ return 0;
2128
+ return pixel / step - LEFT_OFFSET_BARS - 0.5;
2129
+ };
2130
+ const clampCandleIndex = (value, length) => {
2131
+ if (length <= 0)
2132
+ return 0;
2133
+ return clamp(Math.round(value), 0, Math.max(length - 1, 0));
2134
+ };
2070
2135
  const createParallelPoints = (start, end, offset) => {
2071
2136
  const dx = end.x - start.x;
2072
2137
  const dy = end.y - start.y;
@@ -2156,7 +2221,7 @@ const computeVisibleWindow = (dataset, chartWidth, timeframe, zoomLevel, panOffs
2156
2221
  }
2157
2222
  };
2158
2223
  };
2159
- const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showStats = true, showHeaderStats = true }) => {
2224
+ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showStats = true, showHeaderStats = true, showDrawingToolbar = true }) => {
2160
2225
  const chartRef = React.useRef(null);
2161
2226
  const volumeRef = React.useRef(null);
2162
2227
  const gridRef = React.useRef(null);
@@ -2295,7 +2360,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2295
2360
  if (!chartWidth)
2296
2361
  return;
2297
2362
  const candles = getVisibleCandles();
2298
- const pxPerCandle = chartWidth / Math.max(candles.length || 1, 1);
2363
+ const pxPerCandle = chartWidth / Math.max(getEffectiveBarCount(candles.length || 1), 1);
2299
2364
  if (!isFinite(pxPerCandle) || pxPerCandle === 0)
2300
2365
  return;
2301
2366
  const deltaOffset = deltaPx / Math.max(pxPerCandle, 1e-3);
@@ -2419,6 +2484,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2419
2484
  const [priceLabels, setPriceLabels] = React.useState([]);
2420
2485
  const [timeLabels, setTimeLabels] = React.useState([]);
2421
2486
  const [clickedPrice, setClickedPrice] = React.useState(null);
2487
+ const [crosshairMeta, setCrosshairMeta] = React.useState(null);
2422
2488
  const [showConfigPanel, setShowConfigPanel] = React.useState(false);
2423
2489
  const [language, setLanguage] = React.useState('en');
2424
2490
  const [colorScheme, setColorScheme] = React.useState('green');
@@ -2449,6 +2515,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2449
2515
  const [showNoteModal, setShowNoteModal] = React.useState(false);
2450
2516
  const [pendingNotePoint, setPendingNotePoint] = React.useState(null);
2451
2517
  const [noteDraft, setNoteDraft] = React.useState('');
2518
+ const [priceFormat, setPriceFormat] = React.useState({ min: 2, max: 4 });
2452
2519
  const [isMobile, setIsMobile] = React.useState(false);
2453
2520
  React.useEffect(() => {
2454
2521
  if (!plotAreaRef.current)
@@ -2471,7 +2538,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2471
2538
  return () => window.clearTimeout(id);
2472
2539
  }, [seriesType, visibleData.length]);
2473
2540
  const locale = language === 'es' ? 'es-ES' : 'en-US';
2474
- const numberFormatter = React.useMemo(() => new Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }), [locale]);
2541
+ const numberFormatter = React.useMemo(() => new Intl.NumberFormat(locale, { minimumFractionDigits: priceFormat.min, maximumFractionDigits: priceFormat.max }), [locale, priceFormat]);
2475
2542
  const shortNumberFormatter = React.useMemo(() => new Intl.NumberFormat(locale, { maximumFractionDigits: 2 }), [locale]);
2476
2543
  const timeFormatter = React.useMemo(() => new Intl.DateTimeFormat(locale, { hour: '2-digit', minute: '2-digit' }), [locale]);
2477
2544
  const latencyMs = React.useMemo(() => Math.round(20 + Math.random() * 15), []);
@@ -2533,9 +2600,14 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2533
2600
  const priceRange = Math.max(1e-6, maxPrice - minPrice);
2534
2601
  const maxVolume = Math.max(bounds.maxVolume, 1);
2535
2602
  priceWindowRef.current = { min: minPrice, max: maxPrice };
2603
+ const referenceMagnitude = Math.min(Math.abs(minPrice), Math.abs(maxPrice)) || Math.max(Math.abs(minPrice), Math.abs(maxPrice));
2604
+ const suggestedFormat = determinePriceFormat(referenceMagnitude);
2605
+ setPriceFormat((prev) => (prev.min === suggestedFormat.min && prev.max === suggestedFormat.max ? prev : suggestedFormat));
2536
2606
  const chartWidth = Math.max(1, cssWidth - priceScaleWidth);
2537
2607
  const chartHeight = Math.max(1, cssHeight - timeScaleHeight - volumeHeight);
2538
- const candleWidth = chartWidth / Math.max(candles.length, 1);
2608
+ const candleStep = getCandleStep(chartWidth, candles.length);
2609
+ const candleWidth = Math.max(1, candleStep * BAR_BODY_RATIO);
2610
+ const centerX = (index) => getCandleCenter(index, chartWidth, candles.length);
2539
2611
  // Generate price labels
2540
2612
  const priceLabelsArray = [];
2541
2613
  for (let i = 0; i <= 10; i++) {
@@ -2570,9 +2642,11 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2570
2642
  gridCtx.lineTo(chartWidth, y);
2571
2643
  gridCtx.stroke();
2572
2644
  }
2573
- const timeStep = Math.max(1, Math.floor(candles.length / 10));
2645
+ const timeStep = Math.max(1, Math.floor(Math.max(candles.length, 1) / GRID_DIVISIONS));
2574
2646
  for (let i = 0; i <= candles.length; i += timeStep) {
2575
- const x = alignStroke(i * candleWidth);
2647
+ const x = alignStroke((i + LEFT_OFFSET_BARS) * candleStep);
2648
+ if (x < 0 || x > chartWidth)
2649
+ continue;
2576
2650
  gridCtx.beginPath();
2577
2651
  gridCtx.moveTo(x, 0);
2578
2652
  gridCtx.lineTo(x, chartHeight);
@@ -2598,7 +2672,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2598
2672
  : minPrice;
2599
2673
  chartCtx.beginPath();
2600
2674
  candles.forEach((candle, index) => {
2601
- const x = index * candleWidth + candleWidth / 2;
2675
+ const x = centerX(index);
2602
2676
  const yClose = ((maxPrice - candle.close) / priceRange) * chartHeight;
2603
2677
  if (index === 0) {
2604
2678
  chartCtx.moveTo(x, yClose);
@@ -2606,6 +2680,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2606
2680
  else if (seriesType === 'step') {
2607
2681
  const prev = candles[index - 1];
2608
2682
  if (prev) {
2683
+ centerX(index - 1);
2609
2684
  const prevY = ((maxPrice - prev.close) / priceRange) * chartHeight;
2610
2685
  chartCtx.lineTo(x, prevY);
2611
2686
  chartCtx.lineTo(x, yClose);
@@ -2621,7 +2696,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2621
2696
  chartCtx.stroke();
2622
2697
  if (seriesType === 'line-markers') {
2623
2698
  candles.forEach((candle, index) => {
2624
- const x = index * candleWidth + candleWidth / 2;
2699
+ const x = centerX(index);
2625
2700
  const yClose = ((maxPrice - candle.close) / priceRange) * chartHeight;
2626
2701
  chartCtx.beginPath();
2627
2702
  chartCtx.arc(x, yClose, 2.5, 0, 2 * Math.PI);
@@ -2630,8 +2705,8 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2630
2705
  });
2631
2706
  }
2632
2707
  if (seriesType === 'area' || seriesType === 'hlc-area' || seriesType === 'baseline') {
2633
- chartCtx.lineTo((candles.length - 1) * candleWidth + candleWidth / 2, ((maxPrice - baselinePrice) / priceRange) * chartHeight);
2634
- chartCtx.lineTo(0 * candleWidth + candleWidth / 2, ((maxPrice - baselinePrice) / priceRange) * chartHeight);
2708
+ chartCtx.lineTo(centerX(candles.length - 1), ((maxPrice - baselinePrice) / priceRange) * chartHeight);
2709
+ chartCtx.lineTo(centerX(0), ((maxPrice - baselinePrice) / priceRange) * chartHeight);
2635
2710
  chartCtx.closePath();
2636
2711
  chartCtx.fillStyle =
2637
2712
  seriesType === 'baseline' ? 'rgba(41,98,255,0.25)' : 'rgba(41,98,255,0.15)';
@@ -2641,7 +2716,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2641
2716
  else {
2642
2717
  // ----- CANDLE / BAR-BASED SERIES -----
2643
2718
  candles.forEach((candle, index) => {
2644
- const xCenter = index * candleWidth + candleWidth / 2;
2719
+ const xCenter = centerX(index);
2645
2720
  const strokeX = alignStroke(xCenter);
2646
2721
  const yHigh = ((maxPrice - candle.high) / priceRange) * chartHeight;
2647
2722
  const yLow = ((maxPrice - candle.low) / priceRange) * chartHeight;
@@ -2726,12 +2801,12 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2726
2801
  if (volumeCtx) {
2727
2802
  volumeCtx.clearRect(0, 0, chartWidth, volumeHeight);
2728
2803
  candles.forEach((candle, index) => {
2729
- const x = index * candleWidth;
2804
+ const xLeft = centerX(index) - candleWidth / 2;
2730
2805
  const barHeight = ((candle.volume || 0) / maxVolume) * volumeHeight;
2731
2806
  const y = volumeHeight - barHeight;
2732
2807
  const isBullish = candle.close > candle.open;
2733
2808
  volumeCtx.fillStyle = isBullish ? bullishColor : bearishColor;
2734
- volumeCtx.fillRect(x + 1, y, candleWidth - 2, barHeight);
2809
+ volumeCtx.fillRect(xLeft + 1, y, Math.max(1, candleWidth - 2), barHeight);
2735
2810
  });
2736
2811
  }
2737
2812
  // Draw indicators
@@ -2745,7 +2820,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2745
2820
  indicatorCtx.beginPath();
2746
2821
  indicator.data.forEach((value, index) => {
2747
2822
  if (value !== null && value !== undefined) {
2748
- const x = index * candleWidth + candleWidth / 2;
2823
+ const x = centerX(index);
2749
2824
  const y = ((maxPrice - value) / priceRange) * chartHeight;
2750
2825
  if (index === 0) {
2751
2826
  indicatorCtx.moveTo(x, y);
@@ -2760,11 +2835,11 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2760
2835
  else if (indicator.type === 'histogram') {
2761
2836
  indicator.data.forEach((value, index) => {
2762
2837
  if (value !== null && value !== undefined) {
2763
- const x = index * candleWidth;
2838
+ const xLeft = centerX(index) - candleWidth / 2;
2764
2839
  const barHeight = Math.abs(value) * chartHeight * 0.1; // Scale histogram
2765
2840
  const y = value >= 0 ? chartHeight / 2 - barHeight : chartHeight / 2;
2766
2841
  indicatorCtx.fillStyle = value >= 0 ? '#089981' : '#f23645';
2767
- indicatorCtx.fillRect(x + 2, y, candleWidth - 4, barHeight);
2842
+ indicatorCtx.fillRect(xLeft + 2, y, Math.max(1, candleWidth - 4), barHeight);
2768
2843
  }
2769
2844
  });
2770
2845
  }
@@ -2794,32 +2869,46 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2794
2869
  }
2795
2870
  }, [chartWidth, chartHeight, overlayHeight]);
2796
2871
  const handlePointerMove = React.useCallback((e) => {
2797
- if (interactionsLocked || !visibleData.length)
2872
+ if (interactionsLocked || !visibleData.length) {
2873
+ setCrosshairMeta(null);
2798
2874
  return;
2875
+ }
2799
2876
  const rect = e.currentTarget.getBoundingClientRect();
2800
2877
  const x = e.clientX - rect.left;
2801
2878
  const y = e.clientY - rect.top;
2802
2879
  setClickedPrice(null);
2803
2880
  if (cursorType !== 'cross') {
2881
+ setCrosshairMeta(null);
2804
2882
  return;
2805
2883
  }
2806
2884
  if (!isDraggingRef.current) {
2807
2885
  const { min, max } = priceWindowRef.current;
2808
- const snappedX = coordsRef.current.snapToCandle(x, chartWidth, visibleData.length);
2809
- const snappedY = coordsRef.current.snapToPrice(y, chartHeight, min, max);
2810
- mousePosRef.current = {
2811
- x: coordsRef.current.timeToPixel(snappedX, chartWidth, visibleData.length),
2812
- y: coordsRef.current.priceToPixel(snappedY, chartHeight, min, max)
2813
- };
2886
+ const rawIndex = pixelToCandleIndex(x, chartWidth, visibleData.length);
2887
+ const hoveredIndex = clampCandleIndex(rawIndex, visibleData.length);
2888
+ const hoveredCandle = visibleData[hoveredIndex];
2889
+ const snappedPrice = coordsRef.current.snapToPrice(y, chartHeight, min, max);
2890
+ const yPixel = coordsRef.current.priceToPixel(snappedPrice, chartHeight, min, max);
2891
+ const xPixel = magnetEnabled
2892
+ ? getCandleCenter(hoveredIndex, chartWidth, visibleData.length)
2893
+ : clampPixelToChart(x, chartWidth);
2894
+ mousePosRef.current = { x: xPixel, y: yPixel };
2895
+ setCrosshairMeta({
2896
+ price: snappedPrice,
2897
+ timestamp: hoveredCandle?.timestamp ?? null,
2898
+ x: xPixel,
2899
+ y: yPixel,
2900
+ candle: hoveredCandle ?? null
2901
+ });
2814
2902
  showCrosshairRef.current = true;
2815
2903
  requestAnimationFrame(drawCrosshair);
2816
2904
  }
2817
- }, [visibleData, cursorType, chartWidth, chartHeight, drawCrosshair, interactionsLocked]);
2905
+ }, [visibleData, cursorType, chartWidth, chartHeight, drawCrosshair, interactionsLocked, magnetEnabled]);
2818
2906
  const handleMouseLeave = React.useCallback(() => {
2819
2907
  showCrosshairRef.current = false;
2820
2908
  isDraggingRef.current = false;
2821
2909
  dragSampleRef.current.velocity = 0;
2822
2910
  dragSampleRef.current.lastTs = 0;
2911
+ setCrosshairMeta(null);
2823
2912
  requestAnimationFrame(() => {
2824
2913
  const overlayCtx = overlayRef.current?.getContext('2d');
2825
2914
  if (overlayCtx) {
@@ -2863,15 +2952,21 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
2863
2952
  ? { minPrice: priceWindowRef.current.min, maxPrice: priceWindowRef.current.max }
2864
2953
  : getDataBounds(targetData);
2865
2954
  const dataLength = Math.max(targetData.length, 1);
2866
- if (magnetEnabled && visibleData.length) {
2867
- const snappedIndex = coordsRef.current.snapToCandle(x, chartWidth, dataLength);
2868
- const snappedPrice = coordsRef.current.snapToPrice(y, chartHeight, bounds.minPrice, bounds.maxPrice, 0.5);
2869
- x = coordsRef.current.timeToPixel(snappedIndex, chartWidth, dataLength);
2870
- y = coordsRef.current.priceToPixel(snappedPrice, chartHeight, bounds.minPrice, bounds.maxPrice);
2871
- }
2872
- const price = coordsRef.current.pixelToPrice(y, chartHeight, bounds.minPrice, bounds.maxPrice);
2873
- const time = coordsRef.current.pixelToTime(x, chartWidth, dataLength);
2874
- return { x, y, price, time };
2955
+ let snappedPrice = coordsRef.current.snapToPrice(y, chartHeight, bounds.minPrice, bounds.maxPrice, 0.5);
2956
+ let snappedY = y;
2957
+ if (magnetEnabled && targetData.length) {
2958
+ const snappedIndex = clampCandleIndex(pixelToCandleIndex(x, chartWidth, dataLength), dataLength);
2959
+ x = getCandleCenter(snappedIndex, chartWidth, dataLength);
2960
+ snappedY = coordsRef.current.priceToPixel(snappedPrice, chartHeight, bounds.minPrice, bounds.maxPrice);
2961
+ y = snappedY;
2962
+ }
2963
+ else {
2964
+ x = clampPixelToChart(x, chartWidth);
2965
+ }
2966
+ const price = coordsRef.current.pixelToPrice(snappedY, chartHeight, bounds.minPrice, bounds.maxPrice);
2967
+ const hoveredIndex = clampCandleIndex(pixelToCandleIndex(x, chartWidth, dataLength), dataLength);
2968
+ const time = targetData[hoveredIndex]?.timestamp ?? hoveredIndex;
2969
+ return { x, y: snappedY, price, time };
2875
2970
  }, [visibleData, storeData, magnetEnabled, chartWidth, chartHeight, interactionsLocked]);
2876
2971
  const tryEraseDrawing = React.useCallback((x, y) => {
2877
2972
  for (let i = drawings.length - 1; i >= 0; i--) {
@@ -3082,7 +3177,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
3082
3177
  }
3083
3178
  if (visibleData.length) {
3084
3179
  const { min, max } = priceWindowRef.current;
3085
- const snappedIndex = coordsRef.current.snapToCandle(rawX, chartWidth, visibleData.length);
3180
+ const snappedIndex = clampCandleIndex(pixelToCandleIndex(rawX, chartWidth, visibleData.length), visibleData.length);
3086
3181
  setSelectedCandleIndex(snappedIndex);
3087
3182
  const price = coordsRef.current.pixelToPrice(rawY, chartHeight, min, max);
3088
3183
  setClickedPrice({ x: rawX, y: rawY, price });
@@ -3608,7 +3703,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
3608
3703
  minHeight: 0,
3609
3704
  alignItems: 'stretch'
3610
3705
  } },
3611
- React.createElement("div", { style: { width: isMobile ? '60px' : '88px', flexShrink: 0, height: '100%' } },
3706
+ showDrawingToolbar && (React.createElement("div", { style: { width: isMobile ? '60px' : '88px', flexShrink: 0, height: '100%' } },
3612
3707
  React.createElement("div", { style: {
3613
3708
  height: '100%',
3614
3709
  borderRadius: '24px',
@@ -3619,7 +3714,7 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
3619
3714
  alignItems: 'center',
3620
3715
  padding: isMobile ? '12px 0' : '16px 0'
3621
3716
  } },
3622
- React.createElement(DrawingToolbar, null))),
3717
+ React.createElement(DrawingToolbar, null)))),
3623
3718
  React.createElement("div", { ref: plotAreaRef, style: {
3624
3719
  flex: 1,
3625
3720
  position: 'relative',
@@ -3660,6 +3755,65 @@ const TradingViewChart = ({ data, symbol = 'BTC/USDT', onTimeframeChange, showSt
3660
3755
  React.createElement("div", { style: { position: 'absolute', bottom: 0, left: 0, right: priceScaleWidth, height: `${timeScaleHeight}px`, background: activeTheme.scaleBg, borderTop: `1px solid ${activeTheme.plotBorder}`, fontSize: '11px', color: activeTheme.textSecondary, display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: '4px', padding: '4px 10px' } },
3661
3756
  React.createElement("span", { style: { textTransform: 'uppercase', fontSize: '10px', letterSpacing: '0.08em' } }, strings.axis.time),
3662
3757
  React.createElement("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center', color: activeTheme.textPrimary } }, timeLabels.map((label, i) => (React.createElement("div", { key: i }, label))))),
3758
+ crosshairMeta?.candle && (React.createElement("div", { style: {
3759
+ position: 'absolute',
3760
+ top: 16,
3761
+ left: 16,
3762
+ padding: '10px 14px',
3763
+ borderRadius: '14px',
3764
+ background: activeTheme.overlayBg,
3765
+ border: `1px solid ${activeTheme.panelBorder}`,
3766
+ boxShadow: '0 12px 30px rgba(0,0,0,0.45)',
3767
+ color: activeTheme.textPrimary,
3768
+ fontSize: '11px',
3769
+ letterSpacing: '0.04em',
3770
+ display: 'flex',
3771
+ gap: '14px',
3772
+ pointerEvents: 'none',
3773
+ zIndex: 60
3774
+ } },
3775
+ ([
3776
+ { label: 'O', value: crosshairMeta.candle.open },
3777
+ { label: 'H', value: crosshairMeta.candle.high },
3778
+ { label: 'L', value: crosshairMeta.candle.low },
3779
+ { label: 'C', value: crosshairMeta.candle.close }
3780
+ ]).map((item) => (React.createElement("div", { key: item.label, style: { display: 'flex', flexDirection: 'column', gap: '2px' } },
3781
+ React.createElement("span", { style: { color: activeTheme.textSecondary } }, item.label),
3782
+ React.createElement("span", { style: { fontWeight: 600 } }, numberFormatter.format(item.value))))),
3783
+ React.createElement("div", { style: { display: 'flex', flexDirection: 'column', gap: '2px' } },
3784
+ React.createElement("span", { style: { color: activeTheme.textSecondary } }, "V"),
3785
+ React.createElement("span", { style: { fontWeight: 600 } }, shortNumberFormatter.format(crosshairMeta.candle.volume ?? 0))))),
3786
+ crosshairMeta && (React.createElement(React.Fragment, null,
3787
+ React.createElement("div", { style: {
3788
+ position: 'absolute',
3789
+ right: `${priceScaleWidth}px`,
3790
+ top: `${Math.min(Math.max(crosshairMeta.y, 12), overlayHeight - 12)}px`,
3791
+ transform: 'translate(0, -50%)',
3792
+ background: activeTheme.scaleBg,
3793
+ border: `1px solid ${activeTheme.panelBorder}`,
3794
+ borderRadius: '6px 0 0 6px',
3795
+ padding: '2px 8px',
3796
+ fontSize: '11px',
3797
+ fontWeight: 600,
3798
+ color: activeTheme.textPrimary,
3799
+ pointerEvents: 'none',
3800
+ boxShadow: '0 8px 20px rgba(0,0,0,0.4)'
3801
+ } }, numberFormatter.format(crosshairMeta.price)),
3802
+ crosshairMeta.timestamp && (React.createElement("div", { style: {
3803
+ position: 'absolute',
3804
+ left: `${Math.min(Math.max(crosshairMeta.x, 40), chartWidth - 40)}px`,
3805
+ bottom: `${timeScaleHeight}px`,
3806
+ transform: 'translate(-50%, 50%)',
3807
+ background: activeTheme.scaleBg,
3808
+ border: `1px solid ${activeTheme.panelBorder}`,
3809
+ borderRadius: '6px',
3810
+ padding: '2px 8px',
3811
+ fontSize: '10px',
3812
+ letterSpacing: '0.05em',
3813
+ color: activeTheme.textPrimary,
3814
+ pointerEvents: 'none',
3815
+ boxShadow: '0 8px 20px rgba(0,0,0,0.35)'
3816
+ } }, timeFormatter.format(new Date(crosshairMeta.timestamp)))))),
3663
3817
  clickedPrice && (React.createElement("div", { style: {
3664
3818
  position: 'absolute',
3665
3819
  background: '#1e222d',