-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Description
🐛 Bug Report: Select Dropdown Positioning Issues in Closed Shadow DOM
📋 Issue Summary
Ant Design Vue Select dropdowns calculate incorrect positions when rendered inside a closed Shadow DOM, resulting in negative coordinates and dropdowns appearing offscreen.
🌍 Environment
- Ant Design Vue: v4.2.6
- Vue: 3.x
- Browser: Chrome/Firefox/Safari (all affected)
- Shadow DOM Mode: Closed
- Use Case: Micro-frontend architecture with CSS isolation
🎯 Expected Behavior
Select dropdowns should position correctly relative to their trigger elements, appearing below or above the select input as appropriate.
🚫 Actual Behavior
Dropdowns render with negative coordinates (e.g., left: -387px; top: -820px
), appearing offscreen and unusable.
🔬 Root Cause Analysis
The issue occurs because:
- Coordinate System Mismatch: Ant Design's positioning logic uses
getBoundingClientRect()
which returns coordinates relative to the viewport - Shadow DOM Boundary: When components are inside a closed Shadow DOM, the coordinate system becomes isolated
- Container Context: The
getPopupContainer
function cannot properly bridge the coordinate gap between shadow DOM and main document
📝 Reproduction Steps
1. Create Shadow DOM Setup
<!DOCTYPE html> <html> <head> <title>Shadow DOM Test</title> </head> <body> <div id="shadow-host"></div> <script type="module"> // Create closed shadow DOM const shadowHost = document.getElementById('shadow-host') const shadowRoot = shadowHost.attachShadow({ mode: 'closed' }) // Mount Vue app inside shadow DOM shadowRoot.innerHTML = '<div id="app"></div>' // Your Vue app mounts here... </script> </body> </html>
2. Vue Component with Select
<template> <ConfigProvider :get-popup-container="getPopupContainer"> <Select v-model:value="selectedValue" placeholder="Select an option" :options="options" /> </ConfigProvider> </template> <script setup> import { Select, ConfigProvider } from 'ant-design-vue' import { ref } from 'vue' const selectedValue = ref(undefined) const options = [ { label: 'Option 1', value: '1' }, { label: 'Option 2', value: '2' }, { label: 'Option 3', value: '3' }, ] // This doesn't work properly in closed Shadow DOM const getPopupContainer = (triggerNode) => { return document.body // Points to main document, not shadow DOM } </script>
3. Observe the Issue
- Click the Select dropdown
- Open browser DevTools
- Inspect the dropdown element
- Notice negative
left
andtop
values in computed styles
🔧 Current Workarounds Attempted
We've tried several approaches, all with limitations:
1. Custom Popup Container Inside Shadow DOM
function createShadowDOMContainer() { return (triggerNode) => { const shadowRoot = getShadowRoot() return shadowRoot // Still has coordinate issues } }
2. Coordinate Transformation Patch
// Patch getBoundingClientRect to transform coordinates const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect Element.prototype.getBoundingClientRect = function () { const rect = originalGetBoundingClientRect.call(this) // Transform coordinates based on shadow host offset // Complex and fragile approach }
3. Viewport-Fixed Container
function createViewportContainer() { return (triggerNode) => { const container = document.createElement('div') container.style.cssText = ` position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; pointer-events: none; z-index: 1050; ` shadowRoot.appendChild(container) return container } }
💡 Proposed Solutions
Option 1: Shadow DOM Detection and Coordinate Adjustment
Add built-in detection for Shadow DOM context and automatically adjust positioning calculations:
// In Ant Design's positioning logic function getAdjustedPosition(triggerElement, popupElement) { const shadowRoot = triggerElement.getRootNode() if (shadowRoot instanceof ShadowRoot) { const shadowHost = shadowRoot.host const hostRect = shadowHost.getBoundingClientRect() const triggerRect = triggerElement.getBoundingClientRect() // Adjust coordinates relative to shadow host return { left: triggerRect.left - hostRect.left, top: triggerRect.top - hostRect.top, } } // Normal positioning for non-shadow DOM return normalPositioning(triggerElement, popupElement) }
Option 2: Enhanced getPopupContainer Support
Provide better utilities for Shadow DOM popup containers:
// New utility from Ant Design Vue import { createShadowDOMPopupContainer } from 'ant-design-vue/es/utils' const getPopupContainer = createShadowDOMPopupContainer({ mode: 'closed', coordinateTransform: true, })
Option 3: Configuration Option
Add a configuration option to handle Shadow DOM positioning:
<ConfigProvider :get-popup-container="getPopupContainer" :shadow-dom-support="true"> <!-- Components --> </ConfigProvider>
🎯 Impact
This issue affects:
- ✅ Micro-frontend architectures using Shadow DOM for CSS isolation
- ✅ Web Components integration with Vue apps
- ✅ Third-party widget development requiring style isolation
- ✅ Enterprise applications with strict CSS encapsulation requirements
📚 Additional Context
- Similar issues exist in other UI libraries (React Ant Design, Material UI)
- Shadow DOM usage is increasing for micro-frontend architectures
- CSS isolation is crucial for enterprise applications
- This affects all popup-based components (Select, DatePicker, Dropdown, etc.)
🙏 Community Support Needed
This is a significant architectural challenge that would benefit from:
- Core team guidance on preferred solution approach
- Community input on Shadow DOM best practices
- Potential collaboration on implementation
Would the maintainers be open to discussing this issue and potential solutions? We're happy to contribute to the implementation once a direction is established.
🔗 Related Issues
Environment Details:
- OS: macOS/Windows/Linux (all affected)
- Node.js: 18.x+
- Build Tool: Vite 6.x
- Architecture: Micro-frontend with Shadow DOM isolation