@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.js CHANGED
@@ -963,6 +963,63 @@ var INTERACTIVE_RECHECK_INTERVAL = 50;
963
963
  var TOUCH_MOVE_THRESHOLD = 10;
964
964
  var TOUCH_MAX_TAP_DURATION = 600;
965
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
+ };
966
1023
  var oppositePlacement = {
967
1024
  top: "bottom",
968
1025
  bottom: "top",
@@ -1088,6 +1145,7 @@ var useTooltip = ({
1088
1145
  }) => {
1089
1146
  const tooltipRef = (0, import_react.useRef)(null);
1090
1147
  const bodyRef = (0, import_react.useRef)(null);
1148
+ const ownerRef = (0, import_react.useRef)(/* @__PURE__ */ Symbol("tooltip"));
1091
1149
  const hideTimerRef = (0, import_react.useRef)(null);
1092
1150
  const firstFrameRef = (0, import_react.useRef)(null);
1093
1151
  const secondFrameRef = (0, import_react.useRef)(null);
@@ -1100,6 +1158,7 @@ var useTooltip = ({
1100
1158
  y: Number.NaN,
1101
1159
  pointerType: ""
1102
1160
  });
1161
+ const interactiveAreaAtPointRef = (0, import_react.useRef)(() => "outside");
1103
1162
  const touchGestureRef = (0, import_react.useRef)(null);
1104
1163
  const suppressFocusUntilRef = (0, import_react.useRef)(0);
1105
1164
  const [mounted, setMounted] = (0, import_react.useState)(false);
@@ -1238,7 +1297,7 @@ var useTooltip = ({
1238
1297
  });
1239
1298
  }, [anchor, getAvailableSpace, preferredPlacement]);
1240
1299
  const isFocusInsideTooltipRegion = (0, import_react.useCallback)(() => {
1241
- if (!anchor || typeof document === "undefined") {
1300
+ if (!anchor || typeof document === "undefined" || inputModality !== "keyboard") {
1242
1301
  return false;
1243
1302
  }
1244
1303
  const activeElement = document.activeElement;
@@ -1246,50 +1305,60 @@ var useTooltip = ({
1246
1305
  if (!(activeElement instanceof Node)) {
1247
1306
  return false;
1248
1307
  }
1249
- if (interactive && tooltip?.contains(activeElement)) {
1250
- return true;
1251
- }
1252
- return anchor.contains(activeElement) && activeElement instanceof Element && activeElement.matches(":focus-visible");
1308
+ return anchor.contains(activeElement) || Boolean(interactive && tooltip?.contains(activeElement));
1253
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]);
1254
1344
  const getInteractivePointerArea = (0, import_react.useCallback)(() => {
1255
- if (!interactive || !anchor) {
1256
- return "outside";
1257
- }
1258
1345
  const pointer = pointerRef.current;
1259
- const tooltip = tooltipRef.current;
1260
- if (!tooltip || pointer.pointerType !== "mouse" && pointer.pointerType !== "pen" || !Number.isFinite(pointer.x) || !Number.isFinite(pointer.y)) {
1346
+ if (pointer.pointerType !== "mouse" && pointer.pointerType !== "pen" || !Number.isFinite(pointer.x) || !Number.isFinite(pointer.y)) {
1261
1347
  return "outside";
1262
1348
  }
1263
- const point = {
1349
+ return getInteractivePointerAreaAtPoint({
1264
1350
  x: pointer.x,
1265
1351
  y: pointer.y
1266
- };
1267
- const anchorRect = anchor.getBoundingClientRect();
1268
- const tooltipRect = tooltip.getBoundingClientRect();
1269
- if (isPointInsideRect(
1270
- point,
1271
- anchorRect,
1272
- INTERACTIVE_DIRECT_PADDING
1273
- ) || isPointInsideRect(
1274
- point,
1275
- tooltipRect,
1276
- INTERACTIVE_DIRECT_PADDING
1277
- )) {
1278
- return "direct";
1279
- }
1280
- const bridge = createInteractiveBridge(
1281
- anchorRect,
1282
- tooltipRect,
1283
- placement
1284
- );
1285
- return isPointInsidePolygon(point, bridge) ? "bridge" : "outside";
1286
- }, [anchor, interactive, placement]);
1352
+ });
1353
+ }, [getInteractivePointerAreaAtPoint]);
1287
1354
  const removeTooltip = (0, import_react.useCallback)(() => {
1288
1355
  clearHideTimer();
1289
1356
  clearEnterFrames();
1290
1357
  clearInteractiveFrame();
1291
1358
  pendingHideRef.current = false;
1292
1359
  interactiveBridgeDeadlineRef.current = 0;
1360
+ clearTooltipActivation(ownerRef.current);
1361
+ setMounted(false);
1293
1362
  updatePhase("hidden");
1294
1363
  }, [clearEnterFrames, clearHideTimer, clearInteractiveFrame, updatePhase]);
1295
1364
  const runHide = (0, import_react.useCallback)(() => {
@@ -1365,21 +1434,51 @@ var useTooltip = ({
1365
1434
  runHide
1366
1435
  ]
1367
1436
  );
1368
- const showTooltip = (0, import_react.useCallback)(() => {
1369
- if (!anchor || disabled) {
1370
- return;
1371
- }
1372
- clearHideTimer();
1373
- clearEnterFrames();
1374
- interactiveBridgeDeadlineRef.current = 0;
1375
- pendingHideRef.current = false;
1376
- setMounted(true);
1377
- if (phaseRef.current === "entering" || phaseRef.current === "visible") {
1378
- return;
1379
- }
1380
- updatePhase("preparing");
1381
- setShowVersion((value) => value + 1);
1382
- }, [anchor, clearEnterFrames, clearHideTimer, disabled, updatePhase]);
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
+ ]
1481
+ );
1383
1482
  const keepTooltipOpen = (0, import_react.useCallback)(() => {
1384
1483
  if (!interactive || disabled) {
1385
1484
  return;
@@ -1469,6 +1568,13 @@ var useTooltip = ({
1469
1568
  showVersion,
1470
1569
  updatePhase
1471
1570
  ]);
1571
+ (0, import_react.useEffect)(() => {
1572
+ subscribeInputModality();
1573
+ return () => {
1574
+ unsubscribeInputModality();
1575
+ clearTooltipActivation(ownerRef.current);
1576
+ };
1577
+ }, []);
1472
1578
  (0, import_react.useEffect)(() => {
1473
1579
  if (!anchor) {
1474
1580
  removeTooltip();
@@ -1486,7 +1592,10 @@ var useTooltip = ({
1486
1592
  if (event.pointerType === "touch") {
1487
1593
  return;
1488
1594
  }
1489
- showTooltip();
1595
+ showTooltip({
1596
+ x: event.clientX,
1597
+ y: event.clientY
1598
+ });
1490
1599
  };
1491
1600
  const onPointerLeave = (event) => {
1492
1601
  updatePointer(event);
@@ -1558,7 +1667,10 @@ var useTooltip = ({
1558
1667
  return;
1559
1668
  }
1560
1669
  if (phaseRef.current === "hidden" || phaseRef.current === "leaving") {
1561
- showTooltip();
1670
+ showTooltip({
1671
+ x: event.clientX,
1672
+ y: event.clientY
1673
+ });
1562
1674
  } else {
1563
1675
  hideTooltip(true);
1564
1676
  }
@@ -1655,7 +1767,9 @@ var useTooltip = ({
1655
1767
  scheduleInteractivePointerProcessing();
1656
1768
  };
1657
1769
  const onFocusIn = () => {
1658
- keepTooltipOpen();
1770
+ if (inputModality === "keyboard") {
1771
+ keepTooltipOpen();
1772
+ }
1659
1773
  };
1660
1774
  const onFocusOut = (event) => {
1661
1775
  const relatedTarget = event.relatedTarget;