SafePolygon handleTriggerLeave bypassed when an element sits between tooltip trigger and content
bug
## Describe the bug
When a DOM element with pointer events (such as the `Sidebar.Rail` button) sits in the gap between a tooltip trigger and its portaled content, the SafePolygon's `handleTriggerLeave` immediately closes the tooltip without ever running the polygon geometry check.
The cursor physically passes through the intermediate element on its way to the content, making it the `relatedTarget` — which triggers the early-exit code path in `safe-polygon.svelte.ts`:
```typescript
if (
isElement(target) &&
!triggerNode.contains(target) &&
!contentNode.contains(target) &&
!target.contains(contentNode)
) {
this.#clearTracking();
this.#opts.onPointerExit(); // immediate close — polygon never runs
return;
}
```
When `relatedTarget` is the rail button: all conditions pass → immediate close.
When `relatedTarget` is `<body>`: `body.contains(contentNode)` is `true` → check fails → polygon runs → tooltip stays open.
This is a regression introduced in **2.15.8** (PR #1959) and partially addressed in **2.16.0** (PR #1962). The 2.16.0 fix added `!target.contains(contentNode)` to allow `<body>` through (since `<body>` contains the portaled content), but it does not handle concrete DOM elements that are spatially between the trigger and content but DOM-unrelated to the tooltip.
**Expected:** The tooltip remains open while the cursor moves from the trigger toward the content, regardless of what DOM elements the cursor crosses in between. The safe polygon geometry should be checked before deciding to close.
**Actual:** The tooltip closes immediately when the cursor enters any DOM element that is not the trigger, the content, or an ancestor of the content — even if the cursor is moving directly toward the content through the safe polygon area.
## Reproduction
Reproducible with the **official shadcn-svelte sidebar-07 block** ("A sidebar that collapses to icons"):
```bash
bunx shadcn-svelte@latest add sidebar-07
```
1. Collapse the sidebar to icon mode (click the rail or use the keyboard shortcut)
2. Hover any menu item to see its tooltip appear
3. Try to move the cursor from the trigger toward the tooltip content
4. The tooltip closes immediately — you cannot hover over it
The `Sidebar.Rail` is a 16px-wide invisible `<button>` (`w-4`) positioned at the sidebar's right edge with `z-20`. It sits directly in the gap between the tooltip trigger and the portaled tooltip content (`z-50`). When the cursor leaves the trigger heading toward the content, `pointerleave` fires with `relatedTarget` = the rail button.
## System Info
```
System:
OS: macOS 15.7.3
CPU: (10) arm64 Apple M4
Memory: 98.88 MB / 24.00 GB
Shell: 5.9 - /bin/zsh
Binaries:
Node: 25.6.1 - /opt/homebrew/bin/node
npm: 11.9.0 - /opt/homebrew/bin/npm
pnpm: 10.30.1 - /opt/homebrew/bin/pnpm
bun: 1.3.10 - /opt/homebrew/bin/bun
Browsers:
Safari: 26.2
npmPackages:
@sveltejs/kit: ^2.53.4 => 2.53.4
bits-ui: ^2.16.2 => 2.16.2
svelte: ^5.53.6 => 5.53.6
```
## Severity
This breaks all tooltips in the official shadcn-svelte collapsed sidebar example (sidebar-07). `disableHoverableContent={false}` (the default) has no effect — the safe polygon is never reached.
1 条评论