-
-
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