UNPKG

supapup

Version:

⚡ Lightning-fast MCP browser dev tool. Navigate → Get instant structured data. No screenshots needed! Puppeteer: 📸 → CSS selectors → JS eval. Supapup: semantic IDs ready to use. 10x faster, 90% fewer tokens.

6 lines 8.76 kB
/** * Client-side hover detection script for Supapup * This gets injected into pages to detect hoverable elements */ export declare const HOVER_DETECTION_SCRIPT = "\n(function() {\n // Check if already initialized\n if (window.__SUPAPUP_HOVER_DETECTOR__) return;\n \n window.__SUPAPUP_HOVER_DETECTOR__ = {\n detectHoverableElements: function() {\n const hoverable = [];\n let hoverIndex = 0;\n const processedElements = new Set();\n \n // Helper to generate unique selector\n function getSelector(element) {\n if (element.id) return '#' + element.id;\n if (element.className && typeof element.className === 'string') {\n const classes = element.className.trim().split(/\\s+/);\n if (classes.length && classes[0]) return '.' + classes[0];\n }\n \n // Generate path-based selector\n let path = [];\n let current = element;\n while (current && current.nodeType === Node.ELEMENT_NODE) {\n let selector = current.nodeName.toLowerCase();\n if (current.id) {\n selector = '#' + current.id;\n path.unshift(selector);\n break;\n } else if (current.className && typeof current.className === 'string') {\n selector += '.' + current.className.trim().split(/\\s+/)[0];\n }\n path.unshift(selector);\n current = current.parentNode;\n }\n return path.join(' > ');\n }\n \n // Check if element or its children have hover styles\n function checkHoverability(element) {\n const computed = window.getComputedStyle(element);\n const rect = element.getBoundingClientRect();\n \n // Skip invisible elements\n if (rect.width === 0 || rect.height === 0 || \n computed.display === 'none' || \n computed.visibility === 'hidden' ||\n computed.opacity === '0') {\n return null;\n }\n \n let confidence = 0;\n let hoverType = 'none';\n const indicators = [];\n \n // 1. Check transitions/animations\n if (computed.transition !== 'none' && computed.transition !== 'all 0s ease 0s') {\n confidence += 0.3;\n hoverType = 'css';\n indicators.push('has-transition');\n }\n \n // 2. Check cursor\n if (computed.cursor === 'pointer') {\n confidence += 0.2;\n indicators.push('pointer-cursor');\n }\n \n // 3. Check interactive elements\n const tagName = element.tagName.toLowerCase();\n const interactiveTags = ['a', 'button', 'input', 'select', 'textarea'];\n if (interactiveTags.includes(tagName)) {\n confidence += 0.3;\n hoverType = 'css';\n indicators.push('interactive-tag');\n }\n \n // 4. Check class names\n const className = element.className?.toString() || '';\n const hoverPatterns = ['hover', 'dropdown', 'tooltip', 'menu', 'btn', 'link'];\n const hasHoverClass = hoverPatterns.some(pattern => \n className.toLowerCase().includes(pattern)\n );\n if (hasHoverClass) {\n confidence += 0.2;\n indicators.push('hover-class');\n }\n \n // 5. Check for hover event listeners\n const hasMouseEvents = !!(\n element.onmouseover || \n element.onmouseenter ||\n element.onmouseleave ||\n element.onmouseout\n );\n if (hasMouseEvents) {\n confidence += 0.3;\n hoverType = hoverType === 'css' ? 'both' : 'javascript';\n indicators.push('mouse-events');\n }\n \n // 6. Check for dropdown pattern (hidden children)\n let dropdownContent = null;\n const children = element.querySelectorAll('*');\n for (let child of children) {\n const childStyle = window.getComputedStyle(child);\n if (childStyle.display === 'none' || childStyle.visibility === 'hidden') {\n // Check if this might be dropdown content\n const childClasses = child.className?.toString() || '';\n if (childClasses.includes('dropdown') || \n childClasses.includes('menu') || \n childClasses.includes('content') ||\n childClasses.includes('tooltip')) {\n dropdownContent = child;\n confidence += 0.3;\n hoverType = 'css';\n indicators.push('has-dropdown');\n break;\n }\n }\n }\n \n // 7. Check if element is inside a hoverable parent\n let parentHoverable = null;\n let parent = element.parentElement;\n while (parent && parent !== document.body) {\n const parentClass = parent.className?.toString() || '';\n if (parentClass.includes('dropdown') || parentClass.includes('menu')) {\n parentHoverable = parent;\n break;\n }\n parent = parent.parentElement;\n }\n \n if (confidence > 0.3) {\n return {\n confidence: Math.min(confidence, 1),\n hoverType,\n indicators,\n dropdownContent,\n parentHoverable\n };\n }\n \n return null;\n }\n \n // Process all elements\n const elements = document.querySelectorAll('*');\n \n elements.forEach(element => {\n // Skip if already processed\n if (processedElements.has(element)) return;\n processedElements.add(element);\n \n const hoverData = checkHoverability(element);\n if (!hoverData) return;\n \n const hoverId = 'hover-' + hoverIndex++;\n const selector = getSelector(element);\n \n // Mark element with data attributes\n element.setAttribute('data-mcp-hoverable', 'true');\n element.setAttribute('data-mcp-hover-id', hoverId);\n element.setAttribute('data-mcp-hover-confidence', hoverData.confidence.toString());\n \n const elementData = {\n id: hoverId,\n selector: selector,\n tagName: element.tagName.toLowerCase(),\n text: element.textContent?.trim().substring(0, 50),\n type: element.type || element.getAttribute('type') || '',\n hasTransition: hoverData.indicators.includes('has-transition'),\n transitionDuration: window.getComputedStyle(element).transitionDuration,\n hoverType: hoverData.hoverType,\n confidence: hoverData.confidence,\n indicators: hoverData.indicators\n };\n \n // Handle dropdown patterns\n if (hoverData.dropdownContent) {\n elementData.isDropdownTrigger = true;\n const dropdownId = 'hover-dropdown-' + hoverIndex++;\n hoverData.dropdownContent.setAttribute('data-mcp-hover-dropdown-id', dropdownId);\n elementData.dropdownContentId = dropdownId;\n }\n \n // Handle parent hover patterns\n if (hoverData.parentHoverable && !hoverData.parentHoverable.hasAttribute('data-mcp-hover-id')) {\n const parentId = 'hover-parent-' + hoverIndex++;\n hoverData.parentHoverable.setAttribute('data-mcp-hoverable', 'true');\n hoverData.parentHoverable.setAttribute('data-mcp-hover-id', parentId);\n elementData.parentHoverId = parentId;\n \n // Also add the parent as a hoverable element\n hoverable.push({\n id: parentId,\n selector: getSelector(hoverData.parentHoverable),\n tagName: hoverData.parentHoverable.tagName.toLowerCase(),\n text: 'Container for: ' + (elementData.text || '').substring(0, 30),\n type: 'container',\n hasTransition: false,\n transitionDuration: '0s',\n hoverType: 'css',\n confidence: 0.8,\n indicators: ['dropdown-container']\n });\n }\n \n hoverable.push(elementData);\n });\n \n // Sort by confidence and filter low-confidence items\n return hoverable\n .filter(el => el.confidence > 0.4)\n .sort((a, b) => b.confidence - a.confidence);\n }\n };\n \n // Auto-detect on script injection\n const hoverableElements = window.__SUPAPUP_HOVER_DETECTOR__.detectHoverableElements();\n console.log('[Supapup] Detected ' + hoverableElements.length + ' hoverable elements');\n \n // Store for later use\n window.__SUPAPUP_HOVERABLE_ELEMENTS__ = hoverableElements;\n})();\n"; //# sourceMappingURL=hover-detection-script.d.ts.map