@ssaprt/tooltip 1.0.0 → 1.0.4

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
@@ -354,7 +354,7 @@ var presets = {
354
354
  animation: animations.bounce
355
355
  }),
356
356
  hologram: createTheme({
357
- background: "linear-gradient(115deg, rgba(34, 211, 238, 0.42), rgba(168, 85, 247, 0.35), rgba(236, 72, 153, 0.35), rgba(34, 211, 238, 0.42)), rgba(8, 47, 73, 0.72)",
357
+ background: "linear-gradient(115deg, rgba(34, 211, 238, 0.42), rgba(168, 85, 247, 0.35), rgba(236, 72, 153, 0.35), rgba(34, 211, 238, 0.42)), linear-gradient(135deg, #082f49, #312e81)",
358
358
  color: "#ecfeff",
359
359
  fontFamily: "Segoe UI, Arial, sans-serif",
360
360
  fontSize: "12px",
@@ -364,25 +364,23 @@ var presets = {
364
364
  filter: "drop-shadow(0 0 7px rgba(34, 211, 238, 0.75)) drop-shadow(0 0 16px rgba(168, 85, 247, 0.4))",
365
365
  padding: "9px 15px",
366
366
  letterSpacing: "0.06em",
367
- backdropFilter: "blur(8px)",
368
367
  arrowSize: "8px",
369
368
  arrowWidth: "18px",
370
369
  animation: animations.blur
371
370
  }),
372
371
  glass: createTheme({
373
- background: "linear-gradient(135deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.07)), rgba(15, 23, 42, 0.68)",
372
+ background: "linear-gradient(135deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.07)), linear-gradient(135deg, #1e293b, #0f172a)",
374
373
  color: "#f8fafc",
375
374
  border: "1px solid rgba(255, 255, 255, 0.34)",
376
375
  borderRadius: "16px",
377
376
  filter: "drop-shadow(0 12px 20px rgba(15, 23, 42, 0.38))",
378
377
  padding: "9px 15px",
379
- backdropFilter: "blur(16px)",
380
378
  arrowSize: "8px",
381
379
  arrowWidth: "18px",
382
380
  animation: animations.blur
383
381
  }),
384
382
  frost: createTheme({
385
- background: "radial-gradient(circle at 15% 20%, rgba(255, 255, 255, 0.85), transparent 35%), linear-gradient(135deg, rgba(224, 242, 254, 0.92), rgba(186, 230, 253, 0.72))",
383
+ background: "radial-gradient(circle at 15% 20%, rgba(255, 255, 255, 0.85), transparent 35%), linear-gradient(135deg, #e0f2fe, #bae6fd)",
386
384
  color: "#075985",
387
385
  fontWeight: 600,
388
386
  border: "1px solid rgba(125, 211, 252, 0.9)",
@@ -390,7 +388,6 @@ var presets = {
390
388
  filter: "drop-shadow(0 8px 15px rgba(14, 116, 144, 0.24))",
391
389
  padding: "9px 15px",
392
390
  textShadow: "0 1px 0 rgba(255, 255, 255, 0.8)",
393
- backdropFilter: "blur(14px)",
394
391
  arrowSize: "8px",
395
392
  arrowWidth: "19px",
396
393
  animation: animations.blur
@@ -755,7 +752,6 @@ var presets = {
755
752
  borderRadius: "20px 8px 20px 8px",
756
753
  filter: "drop-shadow(0 10px 17px rgba(49, 46, 129, 0.48))",
757
754
  padding: "10px 16px",
758
- backdropFilter: "blur(8px)",
759
755
  arrowSize: "9px",
760
756
  arrowWidth: "21px",
761
757
  animation: animations.blur
@@ -948,6 +944,70 @@ var VIEWPORT_PADDING = 8;
948
944
  var DEFAULT_ARROW_SIZE = 6;
949
945
  var DEFAULT_TOOLTIP_GAP = 10;
950
946
  var ARROW_EDGE_OFFSET = 10;
947
+ var INTERACTIVE_DIRECT_PADDING = 4;
948
+ var INTERACTIVE_BRIDGE_PADDING = 8;
949
+ var INTERACTIVE_BRIDGE_TIMEOUT = 700;
950
+ var INTERACTIVE_RECHECK_INTERVAL = 50;
951
+ var TOUCH_MOVE_THRESHOLD = 10;
952
+ var TOUCH_MAX_TAP_DURATION = 600;
953
+ var TOUCH_FOCUS_SUPPRESSION = 700;
954
+ var activeTooltipSession = null;
955
+ var inputModality = "pointer";
956
+ var inputModalitySubscribers = 0;
957
+ var handleGlobalPointerDown = () => {
958
+ inputModality = "pointer";
959
+ };
960
+ var keyboardFocusKeys = /* @__PURE__ */ new Set([
961
+ "Tab",
962
+ "Enter",
963
+ " ",
964
+ "ArrowUp",
965
+ "ArrowDown",
966
+ "ArrowLeft",
967
+ "ArrowRight",
968
+ "Home",
969
+ "End",
970
+ "PageUp",
971
+ "PageDown"
972
+ ]);
973
+ var handleGlobalKeyDown = (event) => {
974
+ if (event.metaKey || event.ctrlKey || event.altKey || !keyboardFocusKeys.has(event.key)) {
975
+ return;
976
+ }
977
+ inputModality = "keyboard";
978
+ };
979
+ var subscribeInputModality = () => {
980
+ inputModalitySubscribers += 1;
981
+ if (inputModalitySubscribers !== 1) {
982
+ return;
983
+ }
984
+ document.addEventListener("pointerdown", handleGlobalPointerDown, true);
985
+ document.addEventListener("keydown", handleGlobalKeyDown, true);
986
+ };
987
+ var unsubscribeInputModality = () => {
988
+ inputModalitySubscribers = Math.max(0, inputModalitySubscribers - 1);
989
+ if (inputModalitySubscribers !== 0) {
990
+ return;
991
+ }
992
+ document.removeEventListener("pointerdown", handleGlobalPointerDown, true);
993
+ document.removeEventListener("keydown", handleGlobalKeyDown, true);
994
+ };
995
+ var requestTooltipActivation = (session, point) => {
996
+ const currentSession = activeTooltipSession;
997
+ if (currentSession && currentSession.owner !== session.owner) {
998
+ if (point && currentSession.interactive && currentSession.protectsPoint(point)) {
999
+ return false;
1000
+ }
1001
+ currentSession.close();
1002
+ }
1003
+ activeTooltipSession = session;
1004
+ return true;
1005
+ };
1006
+ var clearTooltipActivation = (owner) => {
1007
+ if (activeTooltipSession?.owner === owner) {
1008
+ activeTooltipSession = null;
1009
+ }
1010
+ };
951
1011
  var oppositePlacement = {
952
1012
  top: "bottom",
953
1013
  bottom: "top",
@@ -958,6 +1018,112 @@ var clamp = (value, min, max) => {
958
1018
  const resolvedMax = Math.max(min, max);
959
1019
  return Math.min(Math.max(value, min), resolvedMax);
960
1020
  };
1021
+ var isPointInsideRect = (point, rect, padding = 0) => {
1022
+ return point.x >= rect.left - padding && point.x <= rect.right + padding && point.y >= rect.top - padding && point.y <= rect.bottom + padding;
1023
+ };
1024
+ var isPointOnSegment = (point, start, end) => {
1025
+ const cross = (point.y - start.y) * (end.x - start.x) - (point.x - start.x) * (end.y - start.y);
1026
+ if (Math.abs(cross) > 1e-3) {
1027
+ return false;
1028
+ }
1029
+ return point.x >= Math.min(start.x, end.x) && point.x <= Math.max(start.x, end.x) && point.y >= Math.min(start.y, end.y) && point.y <= Math.max(start.y, end.y);
1030
+ };
1031
+ var isPointInsidePolygon = (point, polygon) => {
1032
+ let inside = false;
1033
+ for (let currentIndex = 0, previousIndex = polygon.length - 1; currentIndex < polygon.length; previousIndex = currentIndex, currentIndex += 1) {
1034
+ const current = polygon[currentIndex];
1035
+ const previous = polygon[previousIndex];
1036
+ if (isPointOnSegment(point, previous, current)) {
1037
+ return true;
1038
+ }
1039
+ const crosses = current.y > point.y !== previous.y > point.y && point.x < (previous.x - current.x) * (point.y - current.y) / (previous.y - current.y) + current.x;
1040
+ if (crosses) {
1041
+ inside = !inside;
1042
+ }
1043
+ }
1044
+ return inside;
1045
+ };
1046
+ var createInteractiveBridge = (anchorRect, tooltipRect, placement) => {
1047
+ const padding = INTERACTIVE_BRIDGE_PADDING;
1048
+ if (placement === "top") {
1049
+ return [
1050
+ {
1051
+ x: tooltipRect.left - padding,
1052
+ y: tooltipRect.bottom - padding
1053
+ },
1054
+ {
1055
+ x: tooltipRect.right + padding,
1056
+ y: tooltipRect.bottom - padding
1057
+ },
1058
+ {
1059
+ x: anchorRect.right + padding,
1060
+ y: anchorRect.top + padding
1061
+ },
1062
+ {
1063
+ x: anchorRect.left - padding,
1064
+ y: anchorRect.top + padding
1065
+ }
1066
+ ];
1067
+ }
1068
+ if (placement === "bottom") {
1069
+ return [
1070
+ {
1071
+ x: anchorRect.left - padding,
1072
+ y: anchorRect.bottom - padding
1073
+ },
1074
+ {
1075
+ x: anchorRect.right + padding,
1076
+ y: anchorRect.bottom - padding
1077
+ },
1078
+ {
1079
+ x: tooltipRect.right + padding,
1080
+ y: tooltipRect.top + padding
1081
+ },
1082
+ {
1083
+ x: tooltipRect.left - padding,
1084
+ y: tooltipRect.top + padding
1085
+ }
1086
+ ];
1087
+ }
1088
+ if (placement === "left") {
1089
+ return [
1090
+ {
1091
+ x: tooltipRect.right - padding,
1092
+ y: tooltipRect.top - padding
1093
+ },
1094
+ {
1095
+ x: anchorRect.left + padding,
1096
+ y: anchorRect.top - padding
1097
+ },
1098
+ {
1099
+ x: anchorRect.left + padding,
1100
+ y: anchorRect.bottom + padding
1101
+ },
1102
+ {
1103
+ x: tooltipRect.right - padding,
1104
+ y: tooltipRect.bottom + padding
1105
+ }
1106
+ ];
1107
+ }
1108
+ return [
1109
+ {
1110
+ x: anchorRect.right - padding,
1111
+ y: anchorRect.top - padding
1112
+ },
1113
+ {
1114
+ x: tooltipRect.left + padding,
1115
+ y: tooltipRect.top - padding
1116
+ },
1117
+ {
1118
+ x: tooltipRect.left + padding,
1119
+ y: tooltipRect.bottom + padding
1120
+ },
1121
+ {
1122
+ x: anchorRect.right - padding,
1123
+ y: anchorRect.bottom + padding
1124
+ }
1125
+ ];
1126
+ };
961
1127
  var useTooltip = ({
962
1128
  anchor,
963
1129
  preferredPlacement,
@@ -967,11 +1133,22 @@ var useTooltip = ({
967
1133
  }) => {
968
1134
  const tooltipRef = useRef(null);
969
1135
  const bodyRef = useRef(null);
1136
+ const ownerRef = useRef(/* @__PURE__ */ Symbol("tooltip"));
970
1137
  const hideTimerRef = useRef(null);
971
1138
  const firstFrameRef = useRef(null);
972
1139
  const secondFrameRef = useRef(null);
1140
+ const interactiveFrameRef = useRef(null);
973
1141
  const phaseRef = useRef("hidden");
974
1142
  const pendingHideRef = useRef(false);
1143
+ const interactiveBridgeDeadlineRef = useRef(0);
1144
+ const pointerRef = useRef({
1145
+ x: Number.NaN,
1146
+ y: Number.NaN,
1147
+ pointerType: ""
1148
+ });
1149
+ const interactiveAreaAtPointRef = useRef(() => "outside");
1150
+ const touchGestureRef = useRef(null);
1151
+ const suppressFocusUntilRef = useRef(0);
975
1152
  const [mounted, setMounted] = useState(false);
976
1153
  const [phase, setPhase] = useState("hidden");
977
1154
  const [placement, setPlacement] = useState(preferredPlacement);
@@ -1003,6 +1180,13 @@ var useTooltip = ({
1003
1180
  secondFrameRef.current = null;
1004
1181
  }
1005
1182
  }, []);
1183
+ const clearInteractiveFrame = useCallback(() => {
1184
+ if (interactiveFrameRef.current === null) {
1185
+ return;
1186
+ }
1187
+ window.cancelAnimationFrame(interactiveFrameRef.current);
1188
+ interactiveFrameRef.current = null;
1189
+ }, []);
1006
1190
  const getAvailableSpace = useCallback(
1007
1191
  (currentPlacement, rect) => {
1008
1192
  switch (currentPlacement) {
@@ -1042,10 +1226,7 @@ var useTooltip = ({
1042
1226
  }
1043
1227
  return bodyWidth + distance;
1044
1228
  };
1045
- const preferredAvailable = getAvailableSpace(
1046
- preferredPlacement,
1047
- rect
1048
- );
1229
+ const preferredAvailable = getAvailableSpace(preferredPlacement, rect);
1049
1230
  const preferredRequired = getRequiredSpace(preferredPlacement);
1050
1231
  let nextPlacement = preferredPlacement;
1051
1232
  if (preferredAvailable < preferredRequired) {
@@ -1103,12 +1284,71 @@ var useTooltip = ({
1103
1284
  "--tooltip-arrow-offset": arrowOffset
1104
1285
  });
1105
1286
  }, [anchor, getAvailableSpace, preferredPlacement]);
1287
+ const isFocusInsideTooltipRegion = useCallback(() => {
1288
+ if (!anchor || typeof document === "undefined" || inputModality !== "keyboard") {
1289
+ return false;
1290
+ }
1291
+ const activeElement = document.activeElement;
1292
+ const tooltip = tooltipRef.current;
1293
+ if (!(activeElement instanceof Node)) {
1294
+ return false;
1295
+ }
1296
+ return anchor.contains(activeElement) || Boolean(interactive && tooltip?.contains(activeElement));
1297
+ }, [anchor, interactive]);
1298
+ const getInteractivePointerAreaAtPoint = useCallback(
1299
+ (point) => {
1300
+ if (!interactive || !anchor) {
1301
+ return "outside";
1302
+ }
1303
+ const tooltip = tooltipRef.current;
1304
+ if (!tooltip || !Number.isFinite(point.x) || !Number.isFinite(point.y)) {
1305
+ return "outside";
1306
+ }
1307
+ const anchorRect = anchor.getBoundingClientRect();
1308
+ const tooltipRect = tooltip.getBoundingClientRect();
1309
+ if (isPointInsideRect(
1310
+ point,
1311
+ anchorRect,
1312
+ INTERACTIVE_DIRECT_PADDING
1313
+ ) || isPointInsideRect(
1314
+ point,
1315
+ tooltipRect,
1316
+ INTERACTIVE_DIRECT_PADDING
1317
+ )) {
1318
+ return "direct";
1319
+ }
1320
+ const bridge = createInteractiveBridge(
1321
+ anchorRect,
1322
+ tooltipRect,
1323
+ placement
1324
+ );
1325
+ return isPointInsidePolygon(point, bridge) ? "bridge" : "outside";
1326
+ },
1327
+ [anchor, interactive, placement]
1328
+ );
1329
+ useLayoutEffect(() => {
1330
+ interactiveAreaAtPointRef.current = getInteractivePointerAreaAtPoint;
1331
+ }, [getInteractivePointerAreaAtPoint]);
1332
+ const getInteractivePointerArea = useCallback(() => {
1333
+ const pointer = pointerRef.current;
1334
+ if (pointer.pointerType !== "mouse" && pointer.pointerType !== "pen" || !Number.isFinite(pointer.x) || !Number.isFinite(pointer.y)) {
1335
+ return "outside";
1336
+ }
1337
+ return getInteractivePointerAreaAtPoint({
1338
+ x: pointer.x,
1339
+ y: pointer.y
1340
+ });
1341
+ }, [getInteractivePointerAreaAtPoint]);
1106
1342
  const removeTooltip = useCallback(() => {
1107
1343
  clearHideTimer();
1108
1344
  clearEnterFrames();
1345
+ clearInteractiveFrame();
1109
1346
  pendingHideRef.current = false;
1347
+ interactiveBridgeDeadlineRef.current = 0;
1348
+ clearTooltipActivation(ownerRef.current);
1349
+ setMounted(false);
1110
1350
  updatePhase("hidden");
1111
- }, [clearEnterFrames, clearHideTimer, updatePhase]);
1351
+ }, [clearEnterFrames, clearHideTimer, clearInteractiveFrame, updatePhase]);
1112
1352
  const runHide = useCallback(() => {
1113
1353
  const currentPhase = phaseRef.current;
1114
1354
  if (currentPhase === "hidden" || currentPhase === "leaving") {
@@ -1126,42 +1366,157 @@ var useTooltip = ({
1126
1366
  }, [removeTooltip, updatePhase]);
1127
1367
  const hideTooltip = useCallback(
1128
1368
  (immediate = false) => {
1129
- clearHideTimer();
1130
1369
  if (immediate) {
1370
+ clearHideTimer();
1371
+ interactiveBridgeDeadlineRef.current = 0;
1131
1372
  runHide();
1132
1373
  return;
1133
1374
  }
1134
- hideTimerRef.current = window.setTimeout(() => {
1375
+ if (hideTimerRef.current !== null) {
1376
+ return;
1377
+ }
1378
+ const attemptHide = () => {
1135
1379
  hideTimerRef.current = null;
1380
+ if (isFocusInsideTooltipRegion()) {
1381
+ interactiveBridgeDeadlineRef.current = 0;
1382
+ return;
1383
+ }
1384
+ if (interactive) {
1385
+ const pointerArea = getInteractivePointerArea();
1386
+ if (pointerArea === "direct") {
1387
+ interactiveBridgeDeadlineRef.current = 0;
1388
+ return;
1389
+ }
1390
+ if (pointerArea === "bridge") {
1391
+ const now = window.performance.now();
1392
+ if (interactiveBridgeDeadlineRef.current === 0) {
1393
+ interactiveBridgeDeadlineRef.current = now + INTERACTIVE_BRIDGE_TIMEOUT;
1394
+ }
1395
+ const remaining = interactiveBridgeDeadlineRef.current - now;
1396
+ if (remaining > 0) {
1397
+ hideTimerRef.current = window.setTimeout(
1398
+ attemptHide,
1399
+ Math.min(
1400
+ INTERACTIVE_RECHECK_INTERVAL,
1401
+ remaining
1402
+ )
1403
+ );
1404
+ return;
1405
+ }
1406
+ }
1407
+ }
1408
+ interactiveBridgeDeadlineRef.current = 0;
1136
1409
  runHide();
1137
- }, resolvedHideDelay);
1410
+ };
1411
+ hideTimerRef.current = window.setTimeout(
1412
+ attemptHide,
1413
+ resolvedHideDelay
1414
+ );
1138
1415
  },
1139
- [clearHideTimer, resolvedHideDelay, runHide]
1416
+ [
1417
+ clearHideTimer,
1418
+ getInteractivePointerArea,
1419
+ interactive,
1420
+ isFocusInsideTooltipRegion,
1421
+ resolvedHideDelay,
1422
+ runHide
1423
+ ]
1424
+ );
1425
+ const showTooltip = useCallback(
1426
+ (activationPoint) => {
1427
+ if (!anchor || disabled) {
1428
+ return;
1429
+ }
1430
+ const activated = requestTooltipActivation(
1431
+ {
1432
+ owner: ownerRef.current,
1433
+ interactive,
1434
+ protectsPoint: (point) => {
1435
+ if (phaseRef.current === "hidden") {
1436
+ return false;
1437
+ }
1438
+ return interactiveAreaAtPointRef.current(point) !== "outside";
1439
+ },
1440
+ close: () => {
1441
+ hideTooltip(true);
1442
+ }
1443
+ },
1444
+ activationPoint
1445
+ );
1446
+ if (!activated) {
1447
+ return;
1448
+ }
1449
+ clearHideTimer();
1450
+ clearEnterFrames();
1451
+ interactiveBridgeDeadlineRef.current = 0;
1452
+ pendingHideRef.current = false;
1453
+ setMounted(true);
1454
+ if (phaseRef.current === "entering" || phaseRef.current === "visible") {
1455
+ return;
1456
+ }
1457
+ updatePhase("preparing");
1458
+ setShowVersion((value) => value + 1);
1459
+ },
1460
+ [
1461
+ anchor,
1462
+ clearEnterFrames,
1463
+ clearHideTimer,
1464
+ disabled,
1465
+ hideTooltip,
1466
+ interactive,
1467
+ updatePhase
1468
+ ]
1140
1469
  );
1141
- const showTooltip = useCallback(() => {
1142
- if (!anchor || disabled) {
1143
- return;
1144
- }
1145
- clearHideTimer();
1146
- clearEnterFrames();
1147
- pendingHideRef.current = false;
1148
- setMounted(true);
1149
- if (phaseRef.current === "entering" || phaseRef.current === "visible") {
1150
- return;
1151
- }
1152
- updatePhase("preparing");
1153
- setShowVersion((value) => value + 1);
1154
- }, [anchor, clearEnterFrames, clearHideTimer, disabled, updatePhase]);
1155
1470
  const keepTooltipOpen = useCallback(() => {
1156
1471
  if (!interactive || disabled) {
1157
1472
  return;
1158
1473
  }
1159
1474
  clearHideTimer();
1475
+ interactiveBridgeDeadlineRef.current = 0;
1160
1476
  pendingHideRef.current = false;
1161
1477
  if (phaseRef.current === "leaving") {
1162
1478
  updatePhase("visible");
1163
1479
  }
1164
1480
  }, [clearHideTimer, disabled, interactive, updatePhase]);
1481
+ const processInteractivePointer = useCallback(() => {
1482
+ if (!interactive || disabled || phaseRef.current === "hidden") {
1483
+ return;
1484
+ }
1485
+ if (isFocusInsideTooltipRegion()) {
1486
+ keepTooltipOpen();
1487
+ return;
1488
+ }
1489
+ const pointerArea = getInteractivePointerArea();
1490
+ if (pointerArea === "direct") {
1491
+ keepTooltipOpen();
1492
+ return;
1493
+ }
1494
+ if (pointerArea === "bridge") {
1495
+ if (interactiveBridgeDeadlineRef.current === 0) {
1496
+ interactiveBridgeDeadlineRef.current = window.performance.now() + INTERACTIVE_BRIDGE_TIMEOUT;
1497
+ }
1498
+ hideTooltip(false);
1499
+ return;
1500
+ }
1501
+ interactiveBridgeDeadlineRef.current = 0;
1502
+ hideTooltip(false);
1503
+ }, [
1504
+ disabled,
1505
+ getInteractivePointerArea,
1506
+ hideTooltip,
1507
+ interactive,
1508
+ isFocusInsideTooltipRegion,
1509
+ keepTooltipOpen
1510
+ ]);
1511
+ const scheduleInteractivePointerProcessing = useCallback(() => {
1512
+ if (interactiveFrameRef.current !== null) {
1513
+ return;
1514
+ }
1515
+ interactiveFrameRef.current = window.requestAnimationFrame(() => {
1516
+ interactiveFrameRef.current = null;
1517
+ processInteractivePointer();
1518
+ });
1519
+ }, [processInteractivePointer]);
1165
1520
  const onAnimationEnd = useCallback(() => {
1166
1521
  const currentPhase = phaseRef.current;
1167
1522
  if (currentPhase === "entering") {
@@ -1201,40 +1556,130 @@ var useTooltip = ({
1201
1556
  showVersion,
1202
1557
  updatePhase
1203
1558
  ]);
1559
+ useEffect(() => {
1560
+ subscribeInputModality();
1561
+ return () => {
1562
+ unsubscribeInputModality();
1563
+ clearTooltipActivation(ownerRef.current);
1564
+ };
1565
+ }, []);
1204
1566
  useEffect(() => {
1205
1567
  if (!anchor) {
1206
1568
  removeTooltip();
1207
1569
  return;
1208
1570
  }
1571
+ const updatePointer = (event) => {
1572
+ pointerRef.current = {
1573
+ x: event.clientX,
1574
+ y: event.clientY,
1575
+ pointerType: event.pointerType
1576
+ };
1577
+ };
1209
1578
  const onPointerEnter = (event) => {
1579
+ updatePointer(event);
1210
1580
  if (event.pointerType === "touch") {
1211
1581
  return;
1212
1582
  }
1213
- showTooltip();
1583
+ showTooltip({
1584
+ x: event.clientX,
1585
+ y: event.clientY
1586
+ });
1214
1587
  };
1215
1588
  const onPointerLeave = (event) => {
1589
+ updatePointer(event);
1216
1590
  if (event.pointerType === "touch") {
1217
1591
  return;
1218
1592
  }
1219
- const relatedTarget = event.relatedTarget;
1220
- const tooltip = tooltipRef.current;
1221
- if (interactive && relatedTarget instanceof Node && tooltip?.contains(relatedTarget)) {
1222
- keepTooltipOpen();
1593
+ if (interactive) {
1594
+ interactiveBridgeDeadlineRef.current = window.performance.now() + INTERACTIVE_BRIDGE_TIMEOUT;
1595
+ hideTooltip(false);
1596
+ scheduleInteractivePointerProcessing();
1223
1597
  return;
1224
1598
  }
1225
1599
  hideTooltip(false);
1226
1600
  };
1227
1601
  const onPointerDown = (event) => {
1602
+ updatePointer(event);
1603
+ if (event.pointerType !== "touch") {
1604
+ return;
1605
+ }
1606
+ const now = window.performance.now();
1607
+ touchGestureRef.current = {
1608
+ pointerId: event.pointerId,
1609
+ startX: event.clientX,
1610
+ startY: event.clientY,
1611
+ startedAt: now,
1612
+ moved: false,
1613
+ cancelled: false
1614
+ };
1615
+ suppressFocusUntilRef.current = now + TOUCH_FOCUS_SUPPRESSION;
1616
+ };
1617
+ const onPointerMove = (event) => {
1618
+ updatePointer(event);
1228
1619
  if (event.pointerType !== "touch") {
1229
1620
  return;
1230
1621
  }
1622
+ const gesture = touchGestureRef.current;
1623
+ if (!gesture || gesture.pointerId !== event.pointerId) {
1624
+ return;
1625
+ }
1626
+ const distance = Math.hypot(
1627
+ event.clientX - gesture.startX,
1628
+ event.clientY - gesture.startY
1629
+ );
1630
+ if (distance <= TOUCH_MOVE_THRESHOLD) {
1631
+ return;
1632
+ }
1633
+ gesture.moved = true;
1634
+ suppressFocusUntilRef.current = window.performance.now() + TOUCH_FOCUS_SUPPRESSION;
1635
+ hideTooltip(true);
1636
+ };
1637
+ const onPointerUp = (event) => {
1638
+ updatePointer(event);
1639
+ if (event.pointerType !== "touch") {
1640
+ return;
1641
+ }
1642
+ const gesture = touchGestureRef.current;
1643
+ touchGestureRef.current = null;
1644
+ if (!gesture || gesture.pointerId !== event.pointerId) {
1645
+ return;
1646
+ }
1647
+ const now = window.performance.now();
1648
+ const distance = Math.hypot(
1649
+ event.clientX - gesture.startX,
1650
+ event.clientY - gesture.startY
1651
+ );
1652
+ const duration = now - gesture.startedAt;
1653
+ suppressFocusUntilRef.current = now + TOUCH_FOCUS_SUPPRESSION;
1654
+ if (gesture.cancelled || gesture.moved || distance > TOUCH_MOVE_THRESHOLD || duration > TOUCH_MAX_TAP_DURATION) {
1655
+ return;
1656
+ }
1231
1657
  if (phaseRef.current === "hidden" || phaseRef.current === "leaving") {
1232
- showTooltip();
1658
+ showTooltip({
1659
+ x: event.clientX,
1660
+ y: event.clientY
1661
+ });
1233
1662
  } else {
1234
1663
  hideTooltip(true);
1235
1664
  }
1236
1665
  };
1666
+ const onPointerCancel = (event) => {
1667
+ if (event.pointerType !== "touch") {
1668
+ return;
1669
+ }
1670
+ const gesture = touchGestureRef.current;
1671
+ if (!gesture || gesture.pointerId !== event.pointerId) {
1672
+ return;
1673
+ }
1674
+ gesture.cancelled = true;
1675
+ touchGestureRef.current = null;
1676
+ suppressFocusUntilRef.current = window.performance.now() + TOUCH_FOCUS_SUPPRESSION;
1677
+ hideTooltip(true);
1678
+ };
1237
1679
  const onFocusIn = () => {
1680
+ if (window.performance.now() < suppressFocusUntilRef.current) {
1681
+ return;
1682
+ }
1238
1683
  showTooltip();
1239
1684
  };
1240
1685
  const onFocusOut = (event) => {
@@ -1248,54 +1693,71 @@ var useTooltip = ({
1248
1693
  anchor.addEventListener("pointerenter", onPointerEnter);
1249
1694
  anchor.addEventListener("pointerleave", onPointerLeave);
1250
1695
  anchor.addEventListener("pointerdown", onPointerDown);
1696
+ anchor.addEventListener("pointermove", onPointerMove);
1697
+ anchor.addEventListener("pointerup", onPointerUp);
1698
+ anchor.addEventListener("pointercancel", onPointerCancel);
1251
1699
  anchor.addEventListener("focusin", onFocusIn);
1252
1700
  anchor.addEventListener("focusout", onFocusOut);
1253
1701
  return () => {
1254
1702
  anchor.removeEventListener("pointerenter", onPointerEnter);
1255
1703
  anchor.removeEventListener("pointerleave", onPointerLeave);
1256
1704
  anchor.removeEventListener("pointerdown", onPointerDown);
1705
+ anchor.removeEventListener("pointermove", onPointerMove);
1706
+ anchor.removeEventListener("pointerup", onPointerUp);
1707
+ anchor.removeEventListener("pointercancel", onPointerCancel);
1257
1708
  anchor.removeEventListener("focusin", onFocusIn);
1258
1709
  anchor.removeEventListener("focusout", onFocusOut);
1710
+ touchGestureRef.current = null;
1259
1711
  clearHideTimer();
1260
1712
  clearEnterFrames();
1713
+ clearInteractiveFrame();
1261
1714
  };
1262
1715
  }, [
1263
1716
  anchor,
1264
1717
  clearEnterFrames,
1265
1718
  clearHideTimer,
1719
+ clearInteractiveFrame,
1266
1720
  hideTooltip,
1267
1721
  interactive,
1268
- keepTooltipOpen,
1269
1722
  removeTooltip,
1723
+ scheduleInteractivePointerProcessing,
1270
1724
  showTooltip
1271
1725
  ]);
1272
1726
  useEffect(() => {
1273
- if (!interactive || !mounted) {
1727
+ if (!interactive || phase === "hidden") {
1274
1728
  return;
1275
1729
  }
1276
1730
  const tooltip = tooltipRef.current;
1277
1731
  if (!tooltip) {
1278
1732
  return;
1279
1733
  }
1734
+ const updatePointer = (event) => {
1735
+ pointerRef.current = {
1736
+ x: event.clientX,
1737
+ y: event.clientY,
1738
+ pointerType: event.pointerType
1739
+ };
1740
+ };
1280
1741
  const onPointerEnter = (event) => {
1742
+ updatePointer(event);
1281
1743
  if (event.pointerType === "touch") {
1282
1744
  return;
1283
1745
  }
1284
1746
  keepTooltipOpen();
1285
1747
  };
1286
1748
  const onPointerLeave = (event) => {
1749
+ updatePointer(event);
1287
1750
  if (event.pointerType === "touch") {
1288
1751
  return;
1289
1752
  }
1290
- const relatedTarget = event.relatedTarget;
1291
- if (relatedTarget instanceof Node && anchor?.contains(relatedTarget)) {
1292
- keepTooltipOpen();
1293
- return;
1294
- }
1753
+ interactiveBridgeDeadlineRef.current = window.performance.now() + INTERACTIVE_BRIDGE_TIMEOUT;
1295
1754
  hideTooltip(false);
1755
+ scheduleInteractivePointerProcessing();
1296
1756
  };
1297
1757
  const onFocusIn = () => {
1298
- keepTooltipOpen();
1758
+ if (inputModality === "keyboard") {
1759
+ keepTooltipOpen();
1760
+ }
1299
1761
  };
1300
1762
  const onFocusOut = (event) => {
1301
1763
  const relatedTarget = event.relatedTarget;
@@ -1319,7 +1781,36 @@ var useTooltip = ({
1319
1781
  hideTooltip,
1320
1782
  interactive,
1321
1783
  keepTooltipOpen,
1322
- mounted
1784
+ phase,
1785
+ scheduleInteractivePointerProcessing
1786
+ ]);
1787
+ useEffect(() => {
1788
+ if (!interactive || phase === "hidden") {
1789
+ return;
1790
+ }
1791
+ const onDocumentPointerMove = (event) => {
1792
+ if (event.pointerType === "touch") {
1793
+ return;
1794
+ }
1795
+ pointerRef.current = {
1796
+ x: event.clientX,
1797
+ y: event.clientY,
1798
+ pointerType: event.pointerType
1799
+ };
1800
+ scheduleInteractivePointerProcessing();
1801
+ };
1802
+ document.addEventListener("pointermove", onDocumentPointerMove, {
1803
+ passive: true
1804
+ });
1805
+ return () => {
1806
+ document.removeEventListener("pointermove", onDocumentPointerMove);
1807
+ clearInteractiveFrame();
1808
+ };
1809
+ }, [
1810
+ clearInteractiveFrame,
1811
+ interactive,
1812
+ phase,
1813
+ scheduleInteractivePointerProcessing
1323
1814
  ]);
1324
1815
  useEffect(() => {
1325
1816
  if (!anchor || phase === "hidden") {
@@ -1331,6 +1822,7 @@ var useTooltip = ({
1331
1822
  if (target instanceof Node && (anchor.contains(target) || interactive && tooltip?.contains(target))) {
1332
1823
  return;
1333
1824
  }
1825
+ touchGestureRef.current = null;
1334
1826
  hideTooltip(true);
1335
1827
  };
1336
1828
  const onKeyDown = (event) => {
@@ -1338,11 +1830,7 @@ var useTooltip = ({
1338
1830
  hideTooltip(true);
1339
1831
  }
1340
1832
  };
1341
- document.addEventListener(
1342
- "pointerdown",
1343
- onDocumentPointerDown,
1344
- true
1345
- );
1833
+ document.addEventListener("pointerdown", onDocumentPointerDown, true);
1346
1834
  document.addEventListener("keydown", onKeyDown);
1347
1835
  window.addEventListener("resize", calculatePosition);
1348
1836
  window.addEventListener("scroll", calculatePosition, true);
@@ -1356,13 +1844,7 @@ var useTooltip = ({
1356
1844
  window.removeEventListener("resize", calculatePosition);
1357
1845
  window.removeEventListener("scroll", calculatePosition, true);
1358
1846
  };
1359
- }, [
1360
- anchor,
1361
- calculatePosition,
1362
- hideTooltip,
1363
- interactive,
1364
- phase
1365
- ]);
1847
+ }, [anchor, calculatePosition, hideTooltip, interactive, phase]);
1366
1848
  useLayoutEffect(() => {
1367
1849
  if (phase === "hidden") {
1368
1850
  return;
@@ -1729,7 +2211,8 @@ import { createContext, useContext, useMemo } from "react";
1729
2211
  import { jsx } from "react/jsx-runtime";
1730
2212
  var DEFAULT_TOOLTIP_VALUES = {
1731
2213
  defaultRenderPosition: "top",
1732
- selectTheme: "primary"
2214
+ selectTheme: "primary",
2215
+ interactive: false
1733
2216
  };
1734
2217
  var TooltipContext = createContext(DEFAULT_TOOLTIP_VALUES);
1735
2218
  var TooltipProvider = ({
@@ -1737,16 +2220,27 @@ var TooltipProvider = ({
1737
2220
  defaultRenderPosition = DEFAULT_TOOLTIP_VALUES.defaultRenderPosition,
1738
2221
  selectTheme = DEFAULT_TOOLTIP_VALUES.selectTheme,
1739
2222
  customTheme,
1740
- animation
2223
+ animation,
2224
+ interactive = DEFAULT_TOOLTIP_VALUES.interactive,
2225
+ hideDelay
1741
2226
  }) => {
1742
2227
  const value = useMemo(() => {
1743
2228
  return {
1744
2229
  defaultRenderPosition,
1745
2230
  selectTheme,
1746
2231
  customTheme,
1747
- animation
2232
+ animation,
2233
+ interactive,
2234
+ hideDelay
1748
2235
  };
1749
- }, [animation, customTheme, defaultRenderPosition, selectTheme]);
2236
+ }, [
2237
+ animation,
2238
+ customTheme,
2239
+ defaultRenderPosition,
2240
+ hideDelay,
2241
+ interactive,
2242
+ selectTheme
2243
+ ]);
1750
2244
  return /* @__PURE__ */ jsx(TooltipContext.Provider, { value, children });
1751
2245
  };
1752
2246
  var useTooltipDefaults = () => {
@@ -2211,7 +2705,7 @@ var Tooltip = ({
2211
2705
  customTheme,
2212
2706
  animation,
2213
2707
  disabled = false,
2214
- interactive = false,
2708
+ interactive,
2215
2709
  hideDelay
2216
2710
  }) => {
2217
2711
  const defaults = useTooltipDefaults();
@@ -2255,6 +2749,8 @@ var Tooltip = ({
2255
2749
  const animationSpeed = animation?.speed ?? localThemeAnimation?.speed ?? defaults.animation?.speed ?? inheritedTheme.animation?.speed ?? "120ms";
2256
2750
  const animationEasing = animation?.easing ?? localThemeAnimation?.easing ?? defaults.animation?.easing ?? inheritedTheme.animation?.easing ?? "ease-in-out";
2257
2751
  const preferredPlacement = position ?? defaults.defaultRenderPosition;
2752
+ const resolvedInteractive = interactive ?? defaults.interactive;
2753
+ const resolvedHideDelay = hideDelay ?? defaults.hideDelay;
2258
2754
  const {
2259
2755
  shouldRender,
2260
2756
  phase,
@@ -2267,8 +2763,8 @@ var Tooltip = ({
2267
2763
  anchor,
2268
2764
  preferredPlacement,
2269
2765
  disabled: disabled || content === null || content === void 0,
2270
- interactive,
2271
- hideDelay
2766
+ interactive: resolvedInteractive,
2767
+ hideDelay: resolvedHideDelay
2272
2768
  });
2273
2769
  const arrowSize = theme.arrow?.size ?? "6px";
2274
2770
  const arrowWidth = theme.arrow?.width ?? `calc(${arrowSize} * 2)`;
@@ -2304,7 +2800,7 @@ var Tooltip = ({
2304
2800
  `tooltip-container--phase-${phase}`,
2305
2801
  `tooltip-container--show-${showAnimation}`,
2306
2802
  `tooltip-container--hide-${hideAnimation}`,
2307
- interactive ? "tooltip-container--interactive" : null
2803
+ resolvedInteractive ? "tooltip-container--interactive" : null
2308
2804
  ].filter(Boolean).join(" "),
2309
2805
  style: containerStyle,
2310
2806
  children: /* @__PURE__ */ jsxs2(