@ssaprt/tooltip 1.0.3 → 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
@@ -951,6 +951,63 @@ var INTERACTIVE_RECHECK_INTERVAL = 50;
951
951
  var TOUCH_MOVE_THRESHOLD = 10;
952
952
  var TOUCH_MAX_TAP_DURATION = 600;
953
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
+ };
954
1011
  var oppositePlacement = {
955
1012
  top: "bottom",
956
1013
  bottom: "top",
@@ -1076,6 +1133,7 @@ var useTooltip = ({
1076
1133
  }) => {
1077
1134
  const tooltipRef = useRef(null);
1078
1135
  const bodyRef = useRef(null);
1136
+ const ownerRef = useRef(/* @__PURE__ */ Symbol("tooltip"));
1079
1137
  const hideTimerRef = useRef(null);
1080
1138
  const firstFrameRef = useRef(null);
1081
1139
  const secondFrameRef = useRef(null);
@@ -1088,6 +1146,7 @@ var useTooltip = ({
1088
1146
  y: Number.NaN,
1089
1147
  pointerType: ""
1090
1148
  });
1149
+ const interactiveAreaAtPointRef = useRef(() => "outside");
1091
1150
  const touchGestureRef = useRef(null);
1092
1151
  const suppressFocusUntilRef = useRef(0);
1093
1152
  const [mounted, setMounted] = useState(false);
@@ -1226,7 +1285,7 @@ var useTooltip = ({
1226
1285
  });
1227
1286
  }, [anchor, getAvailableSpace, preferredPlacement]);
1228
1287
  const isFocusInsideTooltipRegion = useCallback(() => {
1229
- if (!anchor || typeof document === "undefined") {
1288
+ if (!anchor || typeof document === "undefined" || inputModality !== "keyboard") {
1230
1289
  return false;
1231
1290
  }
1232
1291
  const activeElement = document.activeElement;
@@ -1234,50 +1293,60 @@ var useTooltip = ({
1234
1293
  if (!(activeElement instanceof Node)) {
1235
1294
  return false;
1236
1295
  }
1237
- if (interactive && tooltip?.contains(activeElement)) {
1238
- return true;
1239
- }
1240
- return anchor.contains(activeElement) && activeElement instanceof Element && activeElement.matches(":focus-visible");
1296
+ return anchor.contains(activeElement) || Boolean(interactive && tooltip?.contains(activeElement));
1241
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]);
1242
1332
  const getInteractivePointerArea = useCallback(() => {
1243
- if (!interactive || !anchor) {
1244
- return "outside";
1245
- }
1246
1333
  const pointer = pointerRef.current;
1247
- const tooltip = tooltipRef.current;
1248
- if (!tooltip || pointer.pointerType !== "mouse" && pointer.pointerType !== "pen" || !Number.isFinite(pointer.x) || !Number.isFinite(pointer.y)) {
1334
+ if (pointer.pointerType !== "mouse" && pointer.pointerType !== "pen" || !Number.isFinite(pointer.x) || !Number.isFinite(pointer.y)) {
1249
1335
  return "outside";
1250
1336
  }
1251
- const point = {
1337
+ return getInteractivePointerAreaAtPoint({
1252
1338
  x: pointer.x,
1253
1339
  y: pointer.y
1254
- };
1255
- const anchorRect = anchor.getBoundingClientRect();
1256
- const tooltipRect = tooltip.getBoundingClientRect();
1257
- if (isPointInsideRect(
1258
- point,
1259
- anchorRect,
1260
- INTERACTIVE_DIRECT_PADDING
1261
- ) || isPointInsideRect(
1262
- point,
1263
- tooltipRect,
1264
- INTERACTIVE_DIRECT_PADDING
1265
- )) {
1266
- return "direct";
1267
- }
1268
- const bridge = createInteractiveBridge(
1269
- anchorRect,
1270
- tooltipRect,
1271
- placement
1272
- );
1273
- return isPointInsidePolygon(point, bridge) ? "bridge" : "outside";
1274
- }, [anchor, interactive, placement]);
1340
+ });
1341
+ }, [getInteractivePointerAreaAtPoint]);
1275
1342
  const removeTooltip = useCallback(() => {
1276
1343
  clearHideTimer();
1277
1344
  clearEnterFrames();
1278
1345
  clearInteractiveFrame();
1279
1346
  pendingHideRef.current = false;
1280
1347
  interactiveBridgeDeadlineRef.current = 0;
1348
+ clearTooltipActivation(ownerRef.current);
1349
+ setMounted(false);
1281
1350
  updatePhase("hidden");
1282
1351
  }, [clearEnterFrames, clearHideTimer, clearInteractiveFrame, updatePhase]);
1283
1352
  const runHide = useCallback(() => {
@@ -1353,21 +1422,51 @@ var useTooltip = ({
1353
1422
  runHide
1354
1423
  ]
1355
1424
  );
1356
- const showTooltip = useCallback(() => {
1357
- if (!anchor || disabled) {
1358
- return;
1359
- }
1360
- clearHideTimer();
1361
- clearEnterFrames();
1362
- interactiveBridgeDeadlineRef.current = 0;
1363
- pendingHideRef.current = false;
1364
- setMounted(true);
1365
- if (phaseRef.current === "entering" || phaseRef.current === "visible") {
1366
- return;
1367
- }
1368
- updatePhase("preparing");
1369
- setShowVersion((value) => value + 1);
1370
- }, [anchor, clearEnterFrames, clearHideTimer, disabled, updatePhase]);
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
+ ]
1469
+ );
1371
1470
  const keepTooltipOpen = useCallback(() => {
1372
1471
  if (!interactive || disabled) {
1373
1472
  return;
@@ -1457,6 +1556,13 @@ var useTooltip = ({
1457
1556
  showVersion,
1458
1557
  updatePhase
1459
1558
  ]);
1559
+ useEffect(() => {
1560
+ subscribeInputModality();
1561
+ return () => {
1562
+ unsubscribeInputModality();
1563
+ clearTooltipActivation(ownerRef.current);
1564
+ };
1565
+ }, []);
1460
1566
  useEffect(() => {
1461
1567
  if (!anchor) {
1462
1568
  removeTooltip();
@@ -1474,7 +1580,10 @@ var useTooltip = ({
1474
1580
  if (event.pointerType === "touch") {
1475
1581
  return;
1476
1582
  }
1477
- showTooltip();
1583
+ showTooltip({
1584
+ x: event.clientX,
1585
+ y: event.clientY
1586
+ });
1478
1587
  };
1479
1588
  const onPointerLeave = (event) => {
1480
1589
  updatePointer(event);
@@ -1546,7 +1655,10 @@ var useTooltip = ({
1546
1655
  return;
1547
1656
  }
1548
1657
  if (phaseRef.current === "hidden" || phaseRef.current === "leaving") {
1549
- showTooltip();
1658
+ showTooltip({
1659
+ x: event.clientX,
1660
+ y: event.clientY
1661
+ });
1550
1662
  } else {
1551
1663
  hideTooltip(true);
1552
1664
  }
@@ -1643,7 +1755,9 @@ var useTooltip = ({
1643
1755
  scheduleInteractivePointerProcessing();
1644
1756
  };
1645
1757
  const onFocusIn = () => {
1646
- keepTooltipOpen();
1758
+ if (inputModality === "keyboard") {
1759
+ keepTooltipOpen();
1760
+ }
1647
1761
  };
1648
1762
  const onFocusOut = (event) => {
1649
1763
  const relatedTarget = event.relatedTarget;