@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.js CHANGED
@@ -372,7 +372,7 @@ var presets = {
372
372
  animation: animations.bounce
373
373
  }),
374
374
  hologram: createTheme({
375
- 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)",
375
+ 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)",
376
376
  color: "#ecfeff",
377
377
  fontFamily: "Segoe UI, Arial, sans-serif",
378
378
  fontSize: "12px",
@@ -382,25 +382,23 @@ var presets = {
382
382
  filter: "drop-shadow(0 0 7px rgba(34, 211, 238, 0.75)) drop-shadow(0 0 16px rgba(168, 85, 247, 0.4))",
383
383
  padding: "9px 15px",
384
384
  letterSpacing: "0.06em",
385
- backdropFilter: "blur(8px)",
386
385
  arrowSize: "8px",
387
386
  arrowWidth: "18px",
388
387
  animation: animations.blur
389
388
  }),
390
389
  glass: createTheme({
391
- background: "linear-gradient(135deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.07)), rgba(15, 23, 42, 0.68)",
390
+ background: "linear-gradient(135deg, rgba(255, 255, 255, 0.22), rgba(255, 255, 255, 0.07)), linear-gradient(135deg, #1e293b, #0f172a)",
392
391
  color: "#f8fafc",
393
392
  border: "1px solid rgba(255, 255, 255, 0.34)",
394
393
  borderRadius: "16px",
395
394
  filter: "drop-shadow(0 12px 20px rgba(15, 23, 42, 0.38))",
396
395
  padding: "9px 15px",
397
- backdropFilter: "blur(16px)",
398
396
  arrowSize: "8px",
399
397
  arrowWidth: "18px",
400
398
  animation: animations.blur
401
399
  }),
402
400
  frost: createTheme({
403
- 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))",
401
+ background: "radial-gradient(circle at 15% 20%, rgba(255, 255, 255, 0.85), transparent 35%), linear-gradient(135deg, #e0f2fe, #bae6fd)",
404
402
  color: "#075985",
405
403
  fontWeight: 600,
406
404
  border: "1px solid rgba(125, 211, 252, 0.9)",
@@ -408,7 +406,6 @@ var presets = {
408
406
  filter: "drop-shadow(0 8px 15px rgba(14, 116, 144, 0.24))",
409
407
  padding: "9px 15px",
410
408
  textShadow: "0 1px 0 rgba(255, 255, 255, 0.8)",
411
- backdropFilter: "blur(14px)",
412
409
  arrowSize: "8px",
413
410
  arrowWidth: "19px",
414
411
  animation: animations.blur
@@ -773,7 +770,6 @@ var presets = {
773
770
  borderRadius: "20px 8px 20px 8px",
774
771
  filter: "drop-shadow(0 10px 17px rgba(49, 46, 129, 0.48))",
775
772
  padding: "10px 16px",
776
- backdropFilter: "blur(8px)",
777
773
  arrowSize: "9px",
778
774
  arrowWidth: "21px",
779
775
  animation: animations.blur
@@ -960,6 +956,70 @@ var VIEWPORT_PADDING = 8;
960
956
  var DEFAULT_ARROW_SIZE = 6;
961
957
  var DEFAULT_TOOLTIP_GAP = 10;
962
958
  var ARROW_EDGE_OFFSET = 10;
959
+ var INTERACTIVE_DIRECT_PADDING = 4;
960
+ var INTERACTIVE_BRIDGE_PADDING = 8;
961
+ var INTERACTIVE_BRIDGE_TIMEOUT = 700;
962
+ var INTERACTIVE_RECHECK_INTERVAL = 50;
963
+ var TOUCH_MOVE_THRESHOLD = 10;
964
+ var TOUCH_MAX_TAP_DURATION = 600;
965
+ var TOUCH_FOCUS_SUPPRESSION = 700;
966
+ var activeTooltipSession = null;
967
+ var inputModality = "pointer";
968
+ var inputModalitySubscribers = 0;
969
+ var handleGlobalPointerDown = () => {
970
+ inputModality = "pointer";
971
+ };
972
+ var keyboardFocusKeys = /* @__PURE__ */ new Set([
973
+ "Tab",
974
+ "Enter",
975
+ " ",
976
+ "ArrowUp",
977
+ "ArrowDown",
978
+ "ArrowLeft",
979
+ "ArrowRight",
980
+ "Home",
981
+ "End",
982
+ "PageUp",
983
+ "PageDown"
984
+ ]);
985
+ var handleGlobalKeyDown = (event) => {
986
+ if (event.metaKey || event.ctrlKey || event.altKey || !keyboardFocusKeys.has(event.key)) {
987
+ return;
988
+ }
989
+ inputModality = "keyboard";
990
+ };
991
+ var subscribeInputModality = () => {
992
+ inputModalitySubscribers += 1;
993
+ if (inputModalitySubscribers !== 1) {
994
+ return;
995
+ }
996
+ document.addEventListener("pointerdown", handleGlobalPointerDown, true);
997
+ document.addEventListener("keydown", handleGlobalKeyDown, true);
998
+ };
999
+ var unsubscribeInputModality = () => {
1000
+ inputModalitySubscribers = Math.max(0, inputModalitySubscribers - 1);
1001
+ if (inputModalitySubscribers !== 0) {
1002
+ return;
1003
+ }
1004
+ document.removeEventListener("pointerdown", handleGlobalPointerDown, true);
1005
+ document.removeEventListener("keydown", handleGlobalKeyDown, true);
1006
+ };
1007
+ var requestTooltipActivation = (session, point) => {
1008
+ const currentSession = activeTooltipSession;
1009
+ if (currentSession && currentSession.owner !== session.owner) {
1010
+ if (point && currentSession.interactive && currentSession.protectsPoint(point)) {
1011
+ return false;
1012
+ }
1013
+ currentSession.close();
1014
+ }
1015
+ activeTooltipSession = session;
1016
+ return true;
1017
+ };
1018
+ var clearTooltipActivation = (owner) => {
1019
+ if (activeTooltipSession?.owner === owner) {
1020
+ activeTooltipSession = null;
1021
+ }
1022
+ };
963
1023
  var oppositePlacement = {
964
1024
  top: "bottom",
965
1025
  bottom: "top",
@@ -970,6 +1030,112 @@ var clamp = (value, min, max) => {
970
1030
  const resolvedMax = Math.max(min, max);
971
1031
  return Math.min(Math.max(value, min), resolvedMax);
972
1032
  };
1033
+ var isPointInsideRect = (point, rect, padding = 0) => {
1034
+ return point.x >= rect.left - padding && point.x <= rect.right + padding && point.y >= rect.top - padding && point.y <= rect.bottom + padding;
1035
+ };
1036
+ var isPointOnSegment = (point, start, end) => {
1037
+ const cross = (point.y - start.y) * (end.x - start.x) - (point.x - start.x) * (end.y - start.y);
1038
+ if (Math.abs(cross) > 1e-3) {
1039
+ return false;
1040
+ }
1041
+ 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);
1042
+ };
1043
+ var isPointInsidePolygon = (point, polygon) => {
1044
+ let inside = false;
1045
+ for (let currentIndex = 0, previousIndex = polygon.length - 1; currentIndex < polygon.length; previousIndex = currentIndex, currentIndex += 1) {
1046
+ const current = polygon[currentIndex];
1047
+ const previous = polygon[previousIndex];
1048
+ if (isPointOnSegment(point, previous, current)) {
1049
+ return true;
1050
+ }
1051
+ 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;
1052
+ if (crosses) {
1053
+ inside = !inside;
1054
+ }
1055
+ }
1056
+ return inside;
1057
+ };
1058
+ var createInteractiveBridge = (anchorRect, tooltipRect, placement) => {
1059
+ const padding = INTERACTIVE_BRIDGE_PADDING;
1060
+ if (placement === "top") {
1061
+ return [
1062
+ {
1063
+ x: tooltipRect.left - padding,
1064
+ y: tooltipRect.bottom - padding
1065
+ },
1066
+ {
1067
+ x: tooltipRect.right + padding,
1068
+ y: tooltipRect.bottom - padding
1069
+ },
1070
+ {
1071
+ x: anchorRect.right + padding,
1072
+ y: anchorRect.top + padding
1073
+ },
1074
+ {
1075
+ x: anchorRect.left - padding,
1076
+ y: anchorRect.top + padding
1077
+ }
1078
+ ];
1079
+ }
1080
+ if (placement === "bottom") {
1081
+ return [
1082
+ {
1083
+ x: anchorRect.left - padding,
1084
+ y: anchorRect.bottom - padding
1085
+ },
1086
+ {
1087
+ x: anchorRect.right + padding,
1088
+ y: anchorRect.bottom - padding
1089
+ },
1090
+ {
1091
+ x: tooltipRect.right + padding,
1092
+ y: tooltipRect.top + padding
1093
+ },
1094
+ {
1095
+ x: tooltipRect.left - padding,
1096
+ y: tooltipRect.top + padding
1097
+ }
1098
+ ];
1099
+ }
1100
+ if (placement === "left") {
1101
+ return [
1102
+ {
1103
+ x: tooltipRect.right - padding,
1104
+ y: tooltipRect.top - padding
1105
+ },
1106
+ {
1107
+ x: anchorRect.left + padding,
1108
+ y: anchorRect.top - padding
1109
+ },
1110
+ {
1111
+ x: anchorRect.left + padding,
1112
+ y: anchorRect.bottom + padding
1113
+ },
1114
+ {
1115
+ x: tooltipRect.right - padding,
1116
+ y: tooltipRect.bottom + padding
1117
+ }
1118
+ ];
1119
+ }
1120
+ return [
1121
+ {
1122
+ x: anchorRect.right - padding,
1123
+ y: anchorRect.top - padding
1124
+ },
1125
+ {
1126
+ x: tooltipRect.left + padding,
1127
+ y: tooltipRect.top - padding
1128
+ },
1129
+ {
1130
+ x: tooltipRect.left + padding,
1131
+ y: tooltipRect.bottom + padding
1132
+ },
1133
+ {
1134
+ x: anchorRect.right - padding,
1135
+ y: anchorRect.bottom + padding
1136
+ }
1137
+ ];
1138
+ };
973
1139
  var useTooltip = ({
974
1140
  anchor,
975
1141
  preferredPlacement,
@@ -979,11 +1145,22 @@ var useTooltip = ({
979
1145
  }) => {
980
1146
  const tooltipRef = (0, import_react.useRef)(null);
981
1147
  const bodyRef = (0, import_react.useRef)(null);
1148
+ const ownerRef = (0, import_react.useRef)(/* @__PURE__ */ Symbol("tooltip"));
982
1149
  const hideTimerRef = (0, import_react.useRef)(null);
983
1150
  const firstFrameRef = (0, import_react.useRef)(null);
984
1151
  const secondFrameRef = (0, import_react.useRef)(null);
1152
+ const interactiveFrameRef = (0, import_react.useRef)(null);
985
1153
  const phaseRef = (0, import_react.useRef)("hidden");
986
1154
  const pendingHideRef = (0, import_react.useRef)(false);
1155
+ const interactiveBridgeDeadlineRef = (0, import_react.useRef)(0);
1156
+ const pointerRef = (0, import_react.useRef)({
1157
+ x: Number.NaN,
1158
+ y: Number.NaN,
1159
+ pointerType: ""
1160
+ });
1161
+ const interactiveAreaAtPointRef = (0, import_react.useRef)(() => "outside");
1162
+ const touchGestureRef = (0, import_react.useRef)(null);
1163
+ const suppressFocusUntilRef = (0, import_react.useRef)(0);
987
1164
  const [mounted, setMounted] = (0, import_react.useState)(false);
988
1165
  const [phase, setPhase] = (0, import_react.useState)("hidden");
989
1166
  const [placement, setPlacement] = (0, import_react.useState)(preferredPlacement);
@@ -1015,6 +1192,13 @@ var useTooltip = ({
1015
1192
  secondFrameRef.current = null;
1016
1193
  }
1017
1194
  }, []);
1195
+ const clearInteractiveFrame = (0, import_react.useCallback)(() => {
1196
+ if (interactiveFrameRef.current === null) {
1197
+ return;
1198
+ }
1199
+ window.cancelAnimationFrame(interactiveFrameRef.current);
1200
+ interactiveFrameRef.current = null;
1201
+ }, []);
1018
1202
  const getAvailableSpace = (0, import_react.useCallback)(
1019
1203
  (currentPlacement, rect) => {
1020
1204
  switch (currentPlacement) {
@@ -1054,10 +1238,7 @@ var useTooltip = ({
1054
1238
  }
1055
1239
  return bodyWidth + distance;
1056
1240
  };
1057
- const preferredAvailable = getAvailableSpace(
1058
- preferredPlacement,
1059
- rect
1060
- );
1241
+ const preferredAvailable = getAvailableSpace(preferredPlacement, rect);
1061
1242
  const preferredRequired = getRequiredSpace(preferredPlacement);
1062
1243
  let nextPlacement = preferredPlacement;
1063
1244
  if (preferredAvailable < preferredRequired) {
@@ -1115,12 +1296,71 @@ var useTooltip = ({
1115
1296
  "--tooltip-arrow-offset": arrowOffset
1116
1297
  });
1117
1298
  }, [anchor, getAvailableSpace, preferredPlacement]);
1299
+ const isFocusInsideTooltipRegion = (0, import_react.useCallback)(() => {
1300
+ if (!anchor || typeof document === "undefined" || inputModality !== "keyboard") {
1301
+ return false;
1302
+ }
1303
+ const activeElement = document.activeElement;
1304
+ const tooltip = tooltipRef.current;
1305
+ if (!(activeElement instanceof Node)) {
1306
+ return false;
1307
+ }
1308
+ return anchor.contains(activeElement) || Boolean(interactive && tooltip?.contains(activeElement));
1309
+ }, [anchor, interactive]);
1310
+ const getInteractivePointerAreaAtPoint = (0, import_react.useCallback)(
1311
+ (point) => {
1312
+ if (!interactive || !anchor) {
1313
+ return "outside";
1314
+ }
1315
+ const tooltip = tooltipRef.current;
1316
+ if (!tooltip || !Number.isFinite(point.x) || !Number.isFinite(point.y)) {
1317
+ return "outside";
1318
+ }
1319
+ const anchorRect = anchor.getBoundingClientRect();
1320
+ const tooltipRect = tooltip.getBoundingClientRect();
1321
+ if (isPointInsideRect(
1322
+ point,
1323
+ anchorRect,
1324
+ INTERACTIVE_DIRECT_PADDING
1325
+ ) || isPointInsideRect(
1326
+ point,
1327
+ tooltipRect,
1328
+ INTERACTIVE_DIRECT_PADDING
1329
+ )) {
1330
+ return "direct";
1331
+ }
1332
+ const bridge = createInteractiveBridge(
1333
+ anchorRect,
1334
+ tooltipRect,
1335
+ placement
1336
+ );
1337
+ return isPointInsidePolygon(point, bridge) ? "bridge" : "outside";
1338
+ },
1339
+ [anchor, interactive, placement]
1340
+ );
1341
+ (0, import_react.useLayoutEffect)(() => {
1342
+ interactiveAreaAtPointRef.current = getInteractivePointerAreaAtPoint;
1343
+ }, [getInteractivePointerAreaAtPoint]);
1344
+ const getInteractivePointerArea = (0, import_react.useCallback)(() => {
1345
+ const pointer = pointerRef.current;
1346
+ if (pointer.pointerType !== "mouse" && pointer.pointerType !== "pen" || !Number.isFinite(pointer.x) || !Number.isFinite(pointer.y)) {
1347
+ return "outside";
1348
+ }
1349
+ return getInteractivePointerAreaAtPoint({
1350
+ x: pointer.x,
1351
+ y: pointer.y
1352
+ });
1353
+ }, [getInteractivePointerAreaAtPoint]);
1118
1354
  const removeTooltip = (0, import_react.useCallback)(() => {
1119
1355
  clearHideTimer();
1120
1356
  clearEnterFrames();
1357
+ clearInteractiveFrame();
1121
1358
  pendingHideRef.current = false;
1359
+ interactiveBridgeDeadlineRef.current = 0;
1360
+ clearTooltipActivation(ownerRef.current);
1361
+ setMounted(false);
1122
1362
  updatePhase("hidden");
1123
- }, [clearEnterFrames, clearHideTimer, updatePhase]);
1363
+ }, [clearEnterFrames, clearHideTimer, clearInteractiveFrame, updatePhase]);
1124
1364
  const runHide = (0, import_react.useCallback)(() => {
1125
1365
  const currentPhase = phaseRef.current;
1126
1366
  if (currentPhase === "hidden" || currentPhase === "leaving") {
@@ -1138,42 +1378,157 @@ var useTooltip = ({
1138
1378
  }, [removeTooltip, updatePhase]);
1139
1379
  const hideTooltip = (0, import_react.useCallback)(
1140
1380
  (immediate = false) => {
1141
- clearHideTimer();
1142
1381
  if (immediate) {
1382
+ clearHideTimer();
1383
+ interactiveBridgeDeadlineRef.current = 0;
1143
1384
  runHide();
1144
1385
  return;
1145
1386
  }
1146
- hideTimerRef.current = window.setTimeout(() => {
1387
+ if (hideTimerRef.current !== null) {
1388
+ return;
1389
+ }
1390
+ const attemptHide = () => {
1147
1391
  hideTimerRef.current = null;
1392
+ if (isFocusInsideTooltipRegion()) {
1393
+ interactiveBridgeDeadlineRef.current = 0;
1394
+ return;
1395
+ }
1396
+ if (interactive) {
1397
+ const pointerArea = getInteractivePointerArea();
1398
+ if (pointerArea === "direct") {
1399
+ interactiveBridgeDeadlineRef.current = 0;
1400
+ return;
1401
+ }
1402
+ if (pointerArea === "bridge") {
1403
+ const now = window.performance.now();
1404
+ if (interactiveBridgeDeadlineRef.current === 0) {
1405
+ interactiveBridgeDeadlineRef.current = now + INTERACTIVE_BRIDGE_TIMEOUT;
1406
+ }
1407
+ const remaining = interactiveBridgeDeadlineRef.current - now;
1408
+ if (remaining > 0) {
1409
+ hideTimerRef.current = window.setTimeout(
1410
+ attemptHide,
1411
+ Math.min(
1412
+ INTERACTIVE_RECHECK_INTERVAL,
1413
+ remaining
1414
+ )
1415
+ );
1416
+ return;
1417
+ }
1418
+ }
1419
+ }
1420
+ interactiveBridgeDeadlineRef.current = 0;
1148
1421
  runHide();
1149
- }, resolvedHideDelay);
1422
+ };
1423
+ hideTimerRef.current = window.setTimeout(
1424
+ attemptHide,
1425
+ resolvedHideDelay
1426
+ );
1150
1427
  },
1151
- [clearHideTimer, resolvedHideDelay, runHide]
1428
+ [
1429
+ clearHideTimer,
1430
+ getInteractivePointerArea,
1431
+ interactive,
1432
+ isFocusInsideTooltipRegion,
1433
+ resolvedHideDelay,
1434
+ runHide
1435
+ ]
1436
+ );
1437
+ const showTooltip = (0, import_react.useCallback)(
1438
+ (activationPoint) => {
1439
+ if (!anchor || disabled) {
1440
+ return;
1441
+ }
1442
+ const activated = requestTooltipActivation(
1443
+ {
1444
+ owner: ownerRef.current,
1445
+ interactive,
1446
+ protectsPoint: (point) => {
1447
+ if (phaseRef.current === "hidden") {
1448
+ return false;
1449
+ }
1450
+ return interactiveAreaAtPointRef.current(point) !== "outside";
1451
+ },
1452
+ close: () => {
1453
+ hideTooltip(true);
1454
+ }
1455
+ },
1456
+ activationPoint
1457
+ );
1458
+ if (!activated) {
1459
+ return;
1460
+ }
1461
+ clearHideTimer();
1462
+ clearEnterFrames();
1463
+ interactiveBridgeDeadlineRef.current = 0;
1464
+ pendingHideRef.current = false;
1465
+ setMounted(true);
1466
+ if (phaseRef.current === "entering" || phaseRef.current === "visible") {
1467
+ return;
1468
+ }
1469
+ updatePhase("preparing");
1470
+ setShowVersion((value) => value + 1);
1471
+ },
1472
+ [
1473
+ anchor,
1474
+ clearEnterFrames,
1475
+ clearHideTimer,
1476
+ disabled,
1477
+ hideTooltip,
1478
+ interactive,
1479
+ updatePhase
1480
+ ]
1152
1481
  );
1153
- const showTooltip = (0, import_react.useCallback)(() => {
1154
- if (!anchor || disabled) {
1155
- return;
1156
- }
1157
- clearHideTimer();
1158
- clearEnterFrames();
1159
- pendingHideRef.current = false;
1160
- setMounted(true);
1161
- if (phaseRef.current === "entering" || phaseRef.current === "visible") {
1162
- return;
1163
- }
1164
- updatePhase("preparing");
1165
- setShowVersion((value) => value + 1);
1166
- }, [anchor, clearEnterFrames, clearHideTimer, disabled, updatePhase]);
1167
1482
  const keepTooltipOpen = (0, import_react.useCallback)(() => {
1168
1483
  if (!interactive || disabled) {
1169
1484
  return;
1170
1485
  }
1171
1486
  clearHideTimer();
1487
+ interactiveBridgeDeadlineRef.current = 0;
1172
1488
  pendingHideRef.current = false;
1173
1489
  if (phaseRef.current === "leaving") {
1174
1490
  updatePhase("visible");
1175
1491
  }
1176
1492
  }, [clearHideTimer, disabled, interactive, updatePhase]);
1493
+ const processInteractivePointer = (0, import_react.useCallback)(() => {
1494
+ if (!interactive || disabled || phaseRef.current === "hidden") {
1495
+ return;
1496
+ }
1497
+ if (isFocusInsideTooltipRegion()) {
1498
+ keepTooltipOpen();
1499
+ return;
1500
+ }
1501
+ const pointerArea = getInteractivePointerArea();
1502
+ if (pointerArea === "direct") {
1503
+ keepTooltipOpen();
1504
+ return;
1505
+ }
1506
+ if (pointerArea === "bridge") {
1507
+ if (interactiveBridgeDeadlineRef.current === 0) {
1508
+ interactiveBridgeDeadlineRef.current = window.performance.now() + INTERACTIVE_BRIDGE_TIMEOUT;
1509
+ }
1510
+ hideTooltip(false);
1511
+ return;
1512
+ }
1513
+ interactiveBridgeDeadlineRef.current = 0;
1514
+ hideTooltip(false);
1515
+ }, [
1516
+ disabled,
1517
+ getInteractivePointerArea,
1518
+ hideTooltip,
1519
+ interactive,
1520
+ isFocusInsideTooltipRegion,
1521
+ keepTooltipOpen
1522
+ ]);
1523
+ const scheduleInteractivePointerProcessing = (0, import_react.useCallback)(() => {
1524
+ if (interactiveFrameRef.current !== null) {
1525
+ return;
1526
+ }
1527
+ interactiveFrameRef.current = window.requestAnimationFrame(() => {
1528
+ interactiveFrameRef.current = null;
1529
+ processInteractivePointer();
1530
+ });
1531
+ }, [processInteractivePointer]);
1177
1532
  const onAnimationEnd = (0, import_react.useCallback)(() => {
1178
1533
  const currentPhase = phaseRef.current;
1179
1534
  if (currentPhase === "entering") {
@@ -1213,40 +1568,130 @@ var useTooltip = ({
1213
1568
  showVersion,
1214
1569
  updatePhase
1215
1570
  ]);
1571
+ (0, import_react.useEffect)(() => {
1572
+ subscribeInputModality();
1573
+ return () => {
1574
+ unsubscribeInputModality();
1575
+ clearTooltipActivation(ownerRef.current);
1576
+ };
1577
+ }, []);
1216
1578
  (0, import_react.useEffect)(() => {
1217
1579
  if (!anchor) {
1218
1580
  removeTooltip();
1219
1581
  return;
1220
1582
  }
1583
+ const updatePointer = (event) => {
1584
+ pointerRef.current = {
1585
+ x: event.clientX,
1586
+ y: event.clientY,
1587
+ pointerType: event.pointerType
1588
+ };
1589
+ };
1221
1590
  const onPointerEnter = (event) => {
1591
+ updatePointer(event);
1222
1592
  if (event.pointerType === "touch") {
1223
1593
  return;
1224
1594
  }
1225
- showTooltip();
1595
+ showTooltip({
1596
+ x: event.clientX,
1597
+ y: event.clientY
1598
+ });
1226
1599
  };
1227
1600
  const onPointerLeave = (event) => {
1601
+ updatePointer(event);
1228
1602
  if (event.pointerType === "touch") {
1229
1603
  return;
1230
1604
  }
1231
- const relatedTarget = event.relatedTarget;
1232
- const tooltip = tooltipRef.current;
1233
- if (interactive && relatedTarget instanceof Node && tooltip?.contains(relatedTarget)) {
1234
- keepTooltipOpen();
1605
+ if (interactive) {
1606
+ interactiveBridgeDeadlineRef.current = window.performance.now() + INTERACTIVE_BRIDGE_TIMEOUT;
1607
+ hideTooltip(false);
1608
+ scheduleInteractivePointerProcessing();
1235
1609
  return;
1236
1610
  }
1237
1611
  hideTooltip(false);
1238
1612
  };
1239
1613
  const onPointerDown = (event) => {
1614
+ updatePointer(event);
1615
+ if (event.pointerType !== "touch") {
1616
+ return;
1617
+ }
1618
+ const now = window.performance.now();
1619
+ touchGestureRef.current = {
1620
+ pointerId: event.pointerId,
1621
+ startX: event.clientX,
1622
+ startY: event.clientY,
1623
+ startedAt: now,
1624
+ moved: false,
1625
+ cancelled: false
1626
+ };
1627
+ suppressFocusUntilRef.current = now + TOUCH_FOCUS_SUPPRESSION;
1628
+ };
1629
+ const onPointerMove = (event) => {
1630
+ updatePointer(event);
1240
1631
  if (event.pointerType !== "touch") {
1241
1632
  return;
1242
1633
  }
1634
+ const gesture = touchGestureRef.current;
1635
+ if (!gesture || gesture.pointerId !== event.pointerId) {
1636
+ return;
1637
+ }
1638
+ const distance = Math.hypot(
1639
+ event.clientX - gesture.startX,
1640
+ event.clientY - gesture.startY
1641
+ );
1642
+ if (distance <= TOUCH_MOVE_THRESHOLD) {
1643
+ return;
1644
+ }
1645
+ gesture.moved = true;
1646
+ suppressFocusUntilRef.current = window.performance.now() + TOUCH_FOCUS_SUPPRESSION;
1647
+ hideTooltip(true);
1648
+ };
1649
+ const onPointerUp = (event) => {
1650
+ updatePointer(event);
1651
+ if (event.pointerType !== "touch") {
1652
+ return;
1653
+ }
1654
+ const gesture = touchGestureRef.current;
1655
+ touchGestureRef.current = null;
1656
+ if (!gesture || gesture.pointerId !== event.pointerId) {
1657
+ return;
1658
+ }
1659
+ const now = window.performance.now();
1660
+ const distance = Math.hypot(
1661
+ event.clientX - gesture.startX,
1662
+ event.clientY - gesture.startY
1663
+ );
1664
+ const duration = now - gesture.startedAt;
1665
+ suppressFocusUntilRef.current = now + TOUCH_FOCUS_SUPPRESSION;
1666
+ if (gesture.cancelled || gesture.moved || distance > TOUCH_MOVE_THRESHOLD || duration > TOUCH_MAX_TAP_DURATION) {
1667
+ return;
1668
+ }
1243
1669
  if (phaseRef.current === "hidden" || phaseRef.current === "leaving") {
1244
- showTooltip();
1670
+ showTooltip({
1671
+ x: event.clientX,
1672
+ y: event.clientY
1673
+ });
1245
1674
  } else {
1246
1675
  hideTooltip(true);
1247
1676
  }
1248
1677
  };
1678
+ const onPointerCancel = (event) => {
1679
+ if (event.pointerType !== "touch") {
1680
+ return;
1681
+ }
1682
+ const gesture = touchGestureRef.current;
1683
+ if (!gesture || gesture.pointerId !== event.pointerId) {
1684
+ return;
1685
+ }
1686
+ gesture.cancelled = true;
1687
+ touchGestureRef.current = null;
1688
+ suppressFocusUntilRef.current = window.performance.now() + TOUCH_FOCUS_SUPPRESSION;
1689
+ hideTooltip(true);
1690
+ };
1249
1691
  const onFocusIn = () => {
1692
+ if (window.performance.now() < suppressFocusUntilRef.current) {
1693
+ return;
1694
+ }
1250
1695
  showTooltip();
1251
1696
  };
1252
1697
  const onFocusOut = (event) => {
@@ -1260,54 +1705,71 @@ var useTooltip = ({
1260
1705
  anchor.addEventListener("pointerenter", onPointerEnter);
1261
1706
  anchor.addEventListener("pointerleave", onPointerLeave);
1262
1707
  anchor.addEventListener("pointerdown", onPointerDown);
1708
+ anchor.addEventListener("pointermove", onPointerMove);
1709
+ anchor.addEventListener("pointerup", onPointerUp);
1710
+ anchor.addEventListener("pointercancel", onPointerCancel);
1263
1711
  anchor.addEventListener("focusin", onFocusIn);
1264
1712
  anchor.addEventListener("focusout", onFocusOut);
1265
1713
  return () => {
1266
1714
  anchor.removeEventListener("pointerenter", onPointerEnter);
1267
1715
  anchor.removeEventListener("pointerleave", onPointerLeave);
1268
1716
  anchor.removeEventListener("pointerdown", onPointerDown);
1717
+ anchor.removeEventListener("pointermove", onPointerMove);
1718
+ anchor.removeEventListener("pointerup", onPointerUp);
1719
+ anchor.removeEventListener("pointercancel", onPointerCancel);
1269
1720
  anchor.removeEventListener("focusin", onFocusIn);
1270
1721
  anchor.removeEventListener("focusout", onFocusOut);
1722
+ touchGestureRef.current = null;
1271
1723
  clearHideTimer();
1272
1724
  clearEnterFrames();
1725
+ clearInteractiveFrame();
1273
1726
  };
1274
1727
  }, [
1275
1728
  anchor,
1276
1729
  clearEnterFrames,
1277
1730
  clearHideTimer,
1731
+ clearInteractiveFrame,
1278
1732
  hideTooltip,
1279
1733
  interactive,
1280
- keepTooltipOpen,
1281
1734
  removeTooltip,
1735
+ scheduleInteractivePointerProcessing,
1282
1736
  showTooltip
1283
1737
  ]);
1284
1738
  (0, import_react.useEffect)(() => {
1285
- if (!interactive || !mounted) {
1739
+ if (!interactive || phase === "hidden") {
1286
1740
  return;
1287
1741
  }
1288
1742
  const tooltip = tooltipRef.current;
1289
1743
  if (!tooltip) {
1290
1744
  return;
1291
1745
  }
1746
+ const updatePointer = (event) => {
1747
+ pointerRef.current = {
1748
+ x: event.clientX,
1749
+ y: event.clientY,
1750
+ pointerType: event.pointerType
1751
+ };
1752
+ };
1292
1753
  const onPointerEnter = (event) => {
1754
+ updatePointer(event);
1293
1755
  if (event.pointerType === "touch") {
1294
1756
  return;
1295
1757
  }
1296
1758
  keepTooltipOpen();
1297
1759
  };
1298
1760
  const onPointerLeave = (event) => {
1761
+ updatePointer(event);
1299
1762
  if (event.pointerType === "touch") {
1300
1763
  return;
1301
1764
  }
1302
- const relatedTarget = event.relatedTarget;
1303
- if (relatedTarget instanceof Node && anchor?.contains(relatedTarget)) {
1304
- keepTooltipOpen();
1305
- return;
1306
- }
1765
+ interactiveBridgeDeadlineRef.current = window.performance.now() + INTERACTIVE_BRIDGE_TIMEOUT;
1307
1766
  hideTooltip(false);
1767
+ scheduleInteractivePointerProcessing();
1308
1768
  };
1309
1769
  const onFocusIn = () => {
1310
- keepTooltipOpen();
1770
+ if (inputModality === "keyboard") {
1771
+ keepTooltipOpen();
1772
+ }
1311
1773
  };
1312
1774
  const onFocusOut = (event) => {
1313
1775
  const relatedTarget = event.relatedTarget;
@@ -1331,7 +1793,36 @@ var useTooltip = ({
1331
1793
  hideTooltip,
1332
1794
  interactive,
1333
1795
  keepTooltipOpen,
1334
- mounted
1796
+ phase,
1797
+ scheduleInteractivePointerProcessing
1798
+ ]);
1799
+ (0, import_react.useEffect)(() => {
1800
+ if (!interactive || phase === "hidden") {
1801
+ return;
1802
+ }
1803
+ const onDocumentPointerMove = (event) => {
1804
+ if (event.pointerType === "touch") {
1805
+ return;
1806
+ }
1807
+ pointerRef.current = {
1808
+ x: event.clientX,
1809
+ y: event.clientY,
1810
+ pointerType: event.pointerType
1811
+ };
1812
+ scheduleInteractivePointerProcessing();
1813
+ };
1814
+ document.addEventListener("pointermove", onDocumentPointerMove, {
1815
+ passive: true
1816
+ });
1817
+ return () => {
1818
+ document.removeEventListener("pointermove", onDocumentPointerMove);
1819
+ clearInteractiveFrame();
1820
+ };
1821
+ }, [
1822
+ clearInteractiveFrame,
1823
+ interactive,
1824
+ phase,
1825
+ scheduleInteractivePointerProcessing
1335
1826
  ]);
1336
1827
  (0, import_react.useEffect)(() => {
1337
1828
  if (!anchor || phase === "hidden") {
@@ -1343,6 +1834,7 @@ var useTooltip = ({
1343
1834
  if (target instanceof Node && (anchor.contains(target) || interactive && tooltip?.contains(target))) {
1344
1835
  return;
1345
1836
  }
1837
+ touchGestureRef.current = null;
1346
1838
  hideTooltip(true);
1347
1839
  };
1348
1840
  const onKeyDown = (event) => {
@@ -1350,11 +1842,7 @@ var useTooltip = ({
1350
1842
  hideTooltip(true);
1351
1843
  }
1352
1844
  };
1353
- document.addEventListener(
1354
- "pointerdown",
1355
- onDocumentPointerDown,
1356
- true
1357
- );
1845
+ document.addEventListener("pointerdown", onDocumentPointerDown, true);
1358
1846
  document.addEventListener("keydown", onKeyDown);
1359
1847
  window.addEventListener("resize", calculatePosition);
1360
1848
  window.addEventListener("scroll", calculatePosition, true);
@@ -1368,13 +1856,7 @@ var useTooltip = ({
1368
1856
  window.removeEventListener("resize", calculatePosition);
1369
1857
  window.removeEventListener("scroll", calculatePosition, true);
1370
1858
  };
1371
- }, [
1372
- anchor,
1373
- calculatePosition,
1374
- hideTooltip,
1375
- interactive,
1376
- phase
1377
- ]);
1859
+ }, [anchor, calculatePosition, hideTooltip, interactive, phase]);
1378
1860
  (0, import_react.useLayoutEffect)(() => {
1379
1861
  if (phase === "hidden") {
1380
1862
  return;
@@ -1741,7 +2223,8 @@ var import_react2 = require("react");
1741
2223
  var import_jsx_runtime = require("react/jsx-runtime");
1742
2224
  var DEFAULT_TOOLTIP_VALUES = {
1743
2225
  defaultRenderPosition: "top",
1744
- selectTheme: "primary"
2226
+ selectTheme: "primary",
2227
+ interactive: false
1745
2228
  };
1746
2229
  var TooltipContext = (0, import_react2.createContext)(DEFAULT_TOOLTIP_VALUES);
1747
2230
  var TooltipProvider = ({
@@ -1749,16 +2232,27 @@ var TooltipProvider = ({
1749
2232
  defaultRenderPosition = DEFAULT_TOOLTIP_VALUES.defaultRenderPosition,
1750
2233
  selectTheme = DEFAULT_TOOLTIP_VALUES.selectTheme,
1751
2234
  customTheme,
1752
- animation
2235
+ animation,
2236
+ interactive = DEFAULT_TOOLTIP_VALUES.interactive,
2237
+ hideDelay
1753
2238
  }) => {
1754
2239
  const value = (0, import_react2.useMemo)(() => {
1755
2240
  return {
1756
2241
  defaultRenderPosition,
1757
2242
  selectTheme,
1758
2243
  customTheme,
1759
- animation
2244
+ animation,
2245
+ interactive,
2246
+ hideDelay
1760
2247
  };
1761
- }, [animation, customTheme, defaultRenderPosition, selectTheme]);
2248
+ }, [
2249
+ animation,
2250
+ customTheme,
2251
+ defaultRenderPosition,
2252
+ hideDelay,
2253
+ interactive,
2254
+ selectTheme
2255
+ ]);
1762
2256
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipContext.Provider, { value, children });
1763
2257
  };
1764
2258
  var useTooltipDefaults = () => {
@@ -2219,7 +2713,7 @@ var Tooltip = ({
2219
2713
  customTheme,
2220
2714
  animation,
2221
2715
  disabled = false,
2222
- interactive = false,
2716
+ interactive,
2223
2717
  hideDelay
2224
2718
  }) => {
2225
2719
  const defaults = useTooltipDefaults();
@@ -2263,6 +2757,8 @@ var Tooltip = ({
2263
2757
  const animationSpeed = animation?.speed ?? localThemeAnimation?.speed ?? defaults.animation?.speed ?? inheritedTheme.animation?.speed ?? "120ms";
2264
2758
  const animationEasing = animation?.easing ?? localThemeAnimation?.easing ?? defaults.animation?.easing ?? inheritedTheme.animation?.easing ?? "ease-in-out";
2265
2759
  const preferredPlacement = position ?? defaults.defaultRenderPosition;
2760
+ const resolvedInteractive = interactive ?? defaults.interactive;
2761
+ const resolvedHideDelay = hideDelay ?? defaults.hideDelay;
2266
2762
  const {
2267
2763
  shouldRender,
2268
2764
  phase,
@@ -2275,8 +2771,8 @@ var Tooltip = ({
2275
2771
  anchor,
2276
2772
  preferredPlacement,
2277
2773
  disabled: disabled || content === null || content === void 0,
2278
- interactive,
2279
- hideDelay
2774
+ interactive: resolvedInteractive,
2775
+ hideDelay: resolvedHideDelay
2280
2776
  });
2281
2777
  const arrowSize = theme.arrow?.size ?? "6px";
2282
2778
  const arrowWidth = theme.arrow?.width ?? `calc(${arrowSize} * 2)`;
@@ -2312,7 +2808,7 @@ var Tooltip = ({
2312
2808
  `tooltip-container--phase-${phase}`,
2313
2809
  `tooltip-container--show-${showAnimation}`,
2314
2810
  `tooltip-container--hide-${hideAnimation}`,
2315
- interactive ? "tooltip-container--interactive" : null
2811
+ resolvedInteractive ? "tooltip-container--interactive" : null
2316
2812
  ].filter(Boolean).join(" "),
2317
2813
  style: containerStyle,
2318
2814
  children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(