|
215 | 215 | } |
216 | 216 | } |
217 | 217 |
|
| 218 | + /** |
| 219 | + * Detects if an element is a non-interactive overlay (e.g. a gradient div |
| 220 | + * with absolute positioning covering its parent). When such an element is |
| 221 | + * the click target it blocks selection of the meaningful content underneath. |
| 222 | + * Returns the parent dyad-tagged element if the current one is an overlay, |
| 223 | + * or the element itself otherwise. |
| 224 | + */ |
| 225 | + function skipOverlayElement(el) { |
| 226 | + if (!el || !el.parentElement) return el; |
| 227 | + |
| 228 | + // Never skip content-bearing elements |
| 229 | + const tag = el.tagName.toLowerCase(); |
| 230 | + if ( |
| 231 | + tag === "img" || |
| 232 | + tag === "video" || |
| 233 | + tag === "canvas" || |
| 234 | + tag === "svg" || |
| 235 | + tag === "iframe" |
| 236 | + ) { |
| 237 | + return el; |
| 238 | + } |
| 239 | + |
| 240 | + const style = getComputedStyle(el); |
| 241 | + |
| 242 | + // Only consider absolutely/fixed positioned elements |
| 243 | + if (style.position !== "absolute" && style.position !== "fixed") return el; |
| 244 | + |
| 245 | + // Don't skip scrollable containers (e.g. message lists with overflow-y-auto) |
| 246 | + if (style.overflowY === "auto" || style.overflowY === "scroll") return el; |
| 247 | + |
| 248 | + // Must cover a large portion of its parent (inset-0 pattern) |
| 249 | + const parentRect = el.parentElement.getBoundingClientRect(); |
| 250 | + const elRect = el.getBoundingClientRect(); |
| 251 | + |
| 252 | + if (parentRect.width === 0 || parentRect.height === 0) return el; |
| 253 | + |
| 254 | + const widthRatio = elRect.width / parentRect.width; |
| 255 | + const heightRatio = elRect.height / parentRect.height; |
| 256 | + |
| 257 | + // 98% accounts for sub-pixel rounding from borders/box-sizing while |
| 258 | + // being tight enough to only match true inset-0 overlays. |
| 259 | + if (widthRatio < 0.98 || heightRatio < 0.98) return el; |
| 260 | + |
| 261 | + // This looks like an overlay — walk up to the parent with a dyad-id |
| 262 | + let parent = el.parentElement; |
| 263 | + while (parent && !parent.dataset.dyadId) parent = parent.parentElement; |
| 264 | + |
| 265 | + return parent || el; |
| 266 | + } |
| 267 | + |
218 | 268 | // Helper function to check if mouse is over the toolbar |
219 | 269 | function isMouseOverToolbar(mouseX, mouseY) { |
220 | 270 | if (!componentCoordinates) return false; |
|
328 | 378 |
|
329 | 379 | let el = e.target; |
330 | 380 | while (el && !el.dataset.dyadId) el = el.parentElement; |
| 381 | + if (el) el = skipOverlayElement(el); |
331 | 382 |
|
332 | 383 | const hoveredItem = overlays.find((item) => item.el === el); |
333 | 384 |
|
|
0 commit comments