@marcoschwartz/lite-ui 0.27.8 → 0.27.9

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.js CHANGED
@@ -7246,7 +7246,7 @@ var LineChart = ({
7246
7246
  tooltipContent,
7247
7247
  onPointClick,
7248
7248
  onPointHover,
7249
- showCrosshair = false,
7249
+ showCrosshair = true,
7250
7250
  showValueLabels = false,
7251
7251
  className = "",
7252
7252
  children,
@@ -7255,8 +7255,7 @@ var LineChart = ({
7255
7255
  }) => {
7256
7256
  const containerRef = (0, import_react32.useRef)(null);
7257
7257
  const svgRef = (0, import_react32.useRef)(null);
7258
- const [hoveredPoint, setHoveredPoint] = (0, import_react32.useState)(null);
7259
- const [crosshairX, setCrosshairX] = (0, import_react32.useState)(null);
7258
+ const [activeIndex, setActiveIndex] = (0, import_react32.useState)(null);
7260
7259
  const { tooltipData, showTooltip: showTooltipFn, hideTooltip } = useTooltip();
7261
7260
  const {
7262
7261
  width,
@@ -7388,43 +7387,59 @@ var LineChart = ({
7388
7387
  return { points, path };
7389
7388
  });
7390
7389
  }, [data, xScale, yScale]);
7391
- const handlePointEnter = (0, import_react32.useCallback)((e, seriesIndex, pointIndex) => {
7392
- const series = data[seriesIndex];
7393
- const point = series.data[pointIndex];
7394
- const scaledPoint = seriesPaths[seriesIndex].points[pointIndex];
7395
- setHoveredPoint({ seriesIndex, pointIndex });
7396
- if (showCrosshair) {
7397
- setCrosshairX(scaledPoint.x);
7390
+ const handleChartMouseMove = (0, import_react32.useCallback)((e) => {
7391
+ if (!svgRef.current || !containerRef.current || data.length === 0 || data[0].data.length === 0) return;
7392
+ const svgElement = svgRef.current;
7393
+ const point = svgElement.createSVGPoint();
7394
+ point.x = e.clientX;
7395
+ point.y = e.clientY;
7396
+ const ctm = svgElement.getScreenCTM();
7397
+ if (!ctm) return;
7398
+ const svgPoint = point.matrixTransform(ctm.inverse());
7399
+ const chartX = svgPoint.x - padding.left;
7400
+ const firstSeriesPoints = seriesPaths[0].points;
7401
+ let nearestIndex = 0;
7402
+ let minDist = Infinity;
7403
+ for (let i = 0; i < firstSeriesPoints.length; i++) {
7404
+ const dist = Math.abs(firstSeriesPoints[i].x - chartX);
7405
+ if (dist < minDist) {
7406
+ minDist = dist;
7407
+ nearestIndex = i;
7408
+ }
7398
7409
  }
7399
- if (showTooltip && containerRef.current) {
7410
+ setActiveIndex(nearestIndex);
7411
+ if (showTooltip) {
7400
7412
  const rect = containerRef.current.getBoundingClientRect();
7401
7413
  const mouseX = e.clientX - rect.left;
7402
7414
  const mouseY = e.clientY - rect.top;
7403
- const payload = [{
7415
+ const tooltipPayload = data.filter((series) => nearestIndex < series.data.length).map((series, i) => ({
7404
7416
  name: series.name,
7405
- value: point.y,
7406
- color: series.color || colors[seriesIndex % colors.length],
7407
- payload: point
7408
- }];
7417
+ value: series.data[nearestIndex].y,
7418
+ color: series.color || colors[i % colors.length],
7419
+ payload: series.data[nearestIndex]
7420
+ }));
7409
7421
  showTooltipFn({
7410
7422
  x: mouseX,
7411
7423
  y: mouseY,
7412
- label: xFormatter(point.x),
7413
- payload
7424
+ label: xFormatter(data[0].data[nearestIndex].x),
7425
+ payload: tooltipPayload
7414
7426
  });
7415
7427
  }
7416
- onPointHover?.(point, seriesIndex, pointIndex);
7417
- }, [data, seriesPaths, showCrosshair, showTooltip, colors, xFormatter, showTooltipFn, onPointHover]);
7418
- const handlePointLeave = (0, import_react32.useCallback)(() => {
7419
- setHoveredPoint(null);
7420
- setCrosshairX(null);
7428
+ onPointHover?.(data[0].data[nearestIndex], 0, nearestIndex);
7429
+ }, [data, seriesPaths, padding, showTooltip, colors, xFormatter, showTooltipFn, onPointHover]);
7430
+ const handleChartMouseLeave = (0, import_react32.useCallback)(() => {
7431
+ setActiveIndex(null);
7421
7432
  hideTooltip();
7422
7433
  onPointHover?.(null, -1, -1);
7423
7434
  }, [hideTooltip, onPointHover]);
7424
- const handlePointClick = (0, import_react32.useCallback)((seriesIndex, pointIndex) => {
7425
- const point = data[seriesIndex].data[pointIndex];
7426
- onPointClick?.(point, seriesIndex, pointIndex);
7427
- }, [data, onPointClick]);
7435
+ const handleChartClick = (0, import_react32.useCallback)(() => {
7436
+ if (activeIndex === null || !onPointClick) return;
7437
+ data.forEach((series, seriesIndex) => {
7438
+ if (series.data[activeIndex]) {
7439
+ onPointClick(series.data[activeIndex], seriesIndex, activeIndex);
7440
+ }
7441
+ });
7442
+ }, [activeIndex, data, onPointClick]);
7428
7443
  const legendItems = (0, import_react32.useMemo)(
7429
7444
  () => data.map((series, i) => ({
7430
7445
  name: series.name,
@@ -7501,12 +7516,12 @@ var LineChart = ({
7501
7516
  }
7502
7517
  ),
7503
7518
  referenceElements,
7504
- showCrosshair && crosshairX !== null && /* @__PURE__ */ (0, import_jsx_runtime118.jsx)(
7519
+ showCrosshair && activeIndex !== null && seriesPaths[0]?.points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime118.jsx)(
7505
7520
  "line",
7506
7521
  {
7507
- x1: crosshairX,
7522
+ x1: seriesPaths[0].points[activeIndex].x,
7508
7523
  y1: 0,
7509
- x2: crosshairX,
7524
+ x2: seriesPaths[0].points[activeIndex].x,
7510
7525
  y2: chartHeight,
7511
7526
  stroke: "currentColor",
7512
7527
  strokeWidth: 1,
@@ -7542,30 +7557,60 @@ var LineChart = ({
7542
7557
  const showSeriesDots = series.dot !== false;
7543
7558
  if (!showSeriesDots) return null;
7544
7559
  return points.map((point, pointIndex) => {
7545
- const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
7560
+ const isActive = activeIndex === pointIndex;
7546
7561
  const animateDots = animate && !isResponsive;
7547
7562
  return /* @__PURE__ */ (0, import_jsx_runtime118.jsx)(
7548
7563
  "circle",
7549
7564
  {
7550
7565
  cx: point.x,
7551
7566
  cy: point.y,
7552
- r: isHovered ? CHART_DEFAULTS.line.activeDotRadius : CHART_DEFAULTS.line.dotRadius,
7567
+ r: isActive ? CHART_DEFAULTS.line.activeDotRadius : CHART_DEFAULTS.line.dotRadius,
7553
7568
  fill: color,
7554
7569
  stroke: "white",
7555
7570
  strokeWidth: 2,
7556
- className: "cursor-pointer transition-all duration-150",
7571
+ className: "transition-all duration-150",
7557
7572
  style: {
7558
7573
  opacity: animateDots ? 0 : 1,
7559
7574
  animation: animateDots ? `fadeIn 200ms ease-out ${animationDuration + pointIndex * 20}ms forwards` : void 0
7560
7575
  },
7561
- onMouseEnter: (e) => handlePointEnter(e, seriesIndex, pointIndex),
7562
- onMouseLeave: handlePointLeave,
7563
- onClick: () => handlePointClick(seriesIndex, pointIndex)
7576
+ pointerEvents: "none"
7564
7577
  },
7565
7578
  `dot-${seriesIndex}-${pointIndex}`
7566
7579
  );
7567
7580
  });
7568
7581
  }),
7582
+ !showDots && activeIndex !== null && data.map((series, seriesIndex) => {
7583
+ const point = seriesPaths[seriesIndex]?.points[activeIndex];
7584
+ if (!point || activeIndex >= series.data.length) return null;
7585
+ const color = series.color || colors[seriesIndex % colors.length];
7586
+ return /* @__PURE__ */ (0, import_jsx_runtime118.jsx)(
7587
+ "circle",
7588
+ {
7589
+ cx: point.x,
7590
+ cy: point.y,
7591
+ r: CHART_DEFAULTS.line.activeDotRadius,
7592
+ fill: color,
7593
+ stroke: "white",
7594
+ strokeWidth: 2,
7595
+ pointerEvents: "none"
7596
+ },
7597
+ `active-dot-${seriesIndex}`
7598
+ );
7599
+ }),
7600
+ /* @__PURE__ */ (0, import_jsx_runtime118.jsx)(
7601
+ "rect",
7602
+ {
7603
+ x: 0,
7604
+ y: 0,
7605
+ width: chartWidth,
7606
+ height: chartHeight,
7607
+ fill: "transparent",
7608
+ onMouseMove: handleChartMouseMove,
7609
+ onMouseLeave: handleChartMouseLeave,
7610
+ onClick: handleChartClick,
7611
+ style: { cursor: "default" }
7612
+ }
7613
+ ),
7569
7614
  showValueLabels && data.map((series, seriesIndex) => {
7570
7615
  const lastPoint = series.data[series.data.length - 1];
7571
7616
  const { points } = seriesPaths[seriesIndex];
@@ -8174,8 +8219,9 @@ var AreaChart = ({
8174
8219
  ariaLabel
8175
8220
  }) => {
8176
8221
  const containerRef = (0, import_react34.useRef)(null);
8222
+ const svgRef = (0, import_react34.useRef)(null);
8177
8223
  const chartId = (0, import_react34.useId)();
8178
- const [hoveredPoint, setHoveredPoint] = (0, import_react34.useState)(null);
8224
+ const [activeIndex, setActiveIndex] = (0, import_react34.useState)(null);
8179
8225
  const { tooltipData, showTooltip: showTooltipFn, hideTooltip } = useTooltip();
8180
8226
  const {
8181
8227
  width,
@@ -8323,38 +8369,59 @@ var AreaChart = ({
8323
8369
  });
8324
8370
  return paths;
8325
8371
  }, [data, xScale, yScale, stacked, curved]);
8326
- const handlePointEnter = (0, import_react34.useCallback)((e, seriesIndex, pointIndex) => {
8327
- const series = data[seriesIndex];
8328
- const point = series.data[pointIndex];
8329
- setHoveredPoint({ seriesIndex, pointIndex });
8330
- if (showTooltip && containerRef.current) {
8372
+ const handleChartMouseMove = (0, import_react34.useCallback)((e) => {
8373
+ if (!svgRef.current || !containerRef.current || data.length === 0 || data[0].data.length === 0) return;
8374
+ const svgElement = svgRef.current;
8375
+ const point = svgElement.createSVGPoint();
8376
+ point.x = e.clientX;
8377
+ point.y = e.clientY;
8378
+ const ctm = svgElement.getScreenCTM();
8379
+ if (!ctm) return;
8380
+ const svgPoint = point.matrixTransform(ctm.inverse());
8381
+ const chartX = svgPoint.x - padding.left;
8382
+ const firstSeriesPoints = areaPaths[0].points;
8383
+ let nearestIndex = 0;
8384
+ let minDist = Infinity;
8385
+ for (let i = 0; i < firstSeriesPoints.length; i++) {
8386
+ const dist = Math.abs(firstSeriesPoints[i].x - chartX);
8387
+ if (dist < minDist) {
8388
+ minDist = dist;
8389
+ nearestIndex = i;
8390
+ }
8391
+ }
8392
+ setActiveIndex(nearestIndex);
8393
+ if (showTooltip) {
8331
8394
  const rect = containerRef.current.getBoundingClientRect();
8332
8395
  const mouseX = e.clientX - rect.left;
8333
8396
  const mouseY = e.clientY - rect.top;
8334
- const payload = [{
8397
+ const tooltipPayload = data.filter((series) => nearestIndex < series.data.length).map((series, i) => ({
8335
8398
  name: series.name,
8336
- value: point.y,
8337
- color: series.color || colors[seriesIndex % colors.length],
8338
- payload: point
8339
- }];
8399
+ value: series.data[nearestIndex].y,
8400
+ color: series.color || colors[i % colors.length],
8401
+ payload: series.data[nearestIndex]
8402
+ }));
8340
8403
  showTooltipFn({
8341
8404
  x: mouseX,
8342
8405
  y: mouseY,
8343
- label: xFormatter(point.x),
8344
- payload
8406
+ label: xFormatter(data[0].data[nearestIndex].x),
8407
+ payload: tooltipPayload
8345
8408
  });
8346
8409
  }
8347
- onPointHover?.(point, seriesIndex, pointIndex);
8348
- }, [data, showTooltip, colors, xFormatter, showTooltipFn, onPointHover]);
8349
- const handlePointLeave = (0, import_react34.useCallback)(() => {
8350
- setHoveredPoint(null);
8410
+ onPointHover?.(data[0].data[nearestIndex], 0, nearestIndex);
8411
+ }, [data, areaPaths, padding, showTooltip, colors, xFormatter, showTooltipFn, onPointHover]);
8412
+ const handleChartMouseLeave = (0, import_react34.useCallback)(() => {
8413
+ setActiveIndex(null);
8351
8414
  hideTooltip();
8352
8415
  onPointHover?.(null, -1, -1);
8353
8416
  }, [hideTooltip, onPointHover]);
8354
- const handlePointClick = (0, import_react34.useCallback)((seriesIndex, pointIndex) => {
8355
- const point = data[seriesIndex].data[pointIndex];
8356
- onPointClick?.(point, seriesIndex, pointIndex);
8357
- }, [data, onPointClick]);
8417
+ const handleChartClick = (0, import_react34.useCallback)(() => {
8418
+ if (activeIndex === null || !onPointClick) return;
8419
+ data.forEach((series, seriesIndex) => {
8420
+ if (series.data[activeIndex]) {
8421
+ onPointClick(series.data[activeIndex], seriesIndex, activeIndex);
8422
+ }
8423
+ });
8424
+ }, [activeIndex, data, onPointClick]);
8358
8425
  const legendItems = (0, import_react34.useMemo)(
8359
8426
  () => data.map((series, i) => ({
8360
8427
  name: series.name,
@@ -8402,6 +8469,7 @@ var AreaChart = ({
8402
8469
  /* @__PURE__ */ (0, import_jsx_runtime120.jsxs)(
8403
8470
  "svg",
8404
8471
  {
8472
+ ref: svgRef,
8405
8473
  width: svgWidth,
8406
8474
  height: svgHeight,
8407
8475
  viewBox: `0 0 ${width} ${height}`,
@@ -8484,35 +8552,79 @@ var AreaChart = ({
8484
8552
  )
8485
8553
  ] }, `area-${seriesIndex}`);
8486
8554
  }),
8555
+ activeIndex !== null && areaPaths[0]?.points[activeIndex] && /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(
8556
+ "line",
8557
+ {
8558
+ x1: areaPaths[0].points[activeIndex].x,
8559
+ y1: 0,
8560
+ x2: areaPaths[0].points[activeIndex].x,
8561
+ y2: chartHeight,
8562
+ stroke: "currentColor",
8563
+ strokeWidth: 1,
8564
+ strokeDasharray: "4 4",
8565
+ className: "text-[hsl(var(--muted-foreground))]",
8566
+ pointerEvents: "none"
8567
+ }
8568
+ ),
8487
8569
  showDots && data.map((series, seriesIndex) => {
8488
8570
  const { points } = areaPaths[seriesIndex];
8489
8571
  const color = series.color || colors[seriesIndex % colors.length];
8490
8572
  const showSeriesDots = series.dot !== false;
8491
8573
  if (!showSeriesDots) return null;
8492
8574
  return points.map((point, pointIndex) => {
8493
- const isHovered = hoveredPoint?.seriesIndex === seriesIndex && hoveredPoint?.pointIndex === pointIndex;
8575
+ const isActive = activeIndex === pointIndex;
8494
8576
  return /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(
8495
8577
  "circle",
8496
8578
  {
8497
8579
  cx: point.x,
8498
8580
  cy: point.y,
8499
- r: isHovered ? CHART_DEFAULTS.line.activeDotRadius : CHART_DEFAULTS.line.dotRadius,
8581
+ r: isActive ? CHART_DEFAULTS.line.activeDotRadius : CHART_DEFAULTS.line.dotRadius,
8500
8582
  fill: color,
8501
8583
  stroke: "white",
8502
8584
  strokeWidth: 2,
8503
- className: "cursor-pointer transition-all duration-150",
8585
+ className: "transition-all duration-150",
8504
8586
  style: animate ? {
8505
8587
  opacity: 0,
8506
8588
  animation: `fadeIn 200ms ease-out ${animationDuration + pointIndex * 20}ms forwards`
8507
8589
  } : void 0,
8508
- onMouseEnter: (e) => handlePointEnter(e, seriesIndex, pointIndex),
8509
- onMouseLeave: handlePointLeave,
8510
- onClick: () => handlePointClick(seriesIndex, pointIndex)
8590
+ pointerEvents: "none"
8511
8591
  },
8512
8592
  `dot-${seriesIndex}-${pointIndex}`
8513
8593
  );
8514
8594
  });
8515
8595
  }),
8596
+ !showDots && activeIndex !== null && data.map((series, seriesIndex) => {
8597
+ const point = areaPaths[seriesIndex]?.points[activeIndex];
8598
+ if (!point || activeIndex >= series.data.length) return null;
8599
+ const color = series.color || colors[seriesIndex % colors.length];
8600
+ return /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(
8601
+ "circle",
8602
+ {
8603
+ cx: point.x,
8604
+ cy: point.y,
8605
+ r: CHART_DEFAULTS.line.activeDotRadius,
8606
+ fill: color,
8607
+ stroke: "white",
8608
+ strokeWidth: 2,
8609
+ pointerEvents: "none"
8610
+ },
8611
+ `active-dot-${seriesIndex}`
8612
+ );
8613
+ }),
8614
+ /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(
8615
+ "rect",
8616
+ {
8617
+ x: 0,
8618
+ y: 0,
8619
+ width: chartWidth,
8620
+ height: chartHeight,
8621
+ fill: "transparent",
8622
+ onMouseMove: handleChartMouseMove,
8623
+ onMouseLeave: handleChartMouseLeave,
8624
+ onClick: handleChartClick,
8625
+ style: { cursor: "default" }
8626
+ }
8627
+ ),
8516
8628
  showXAxis && /* @__PURE__ */ (0, import_jsx_runtime120.jsxs)("g", { transform: `translate(0, ${chartHeight})`, children: [
8517
8629
  /* @__PURE__ */ (0, import_jsx_runtime120.jsx)(
8518
8630
  "line",