Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Shadow DOM issues research #1068

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion public/index-rollup.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Dev - React Tooltip</title>
<link rel="stylesheet" href="index.css" />
<link rel="stylesheet" id="style-rt" href="index.css" />
</head>
<body>
<noscript>You need to enable JavaScript to run this app. Please enable JavaScript 😭</noscript>
27 changes: 16 additions & 11 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -33,25 +33,30 @@ function App() {
>
My button
</button>
<Tooltip place="bottom" anchorId={anchorId} isOpen={isDarkOpen} setIsOpen={setIsDarkOpen} />
<Tooltip
place="bottom"
anchorSelect={anchorId}
isOpen={isDarkOpen}
setIsOpen={setIsDarkOpen}
/>
<Tooltip
place="top"
variant="success"
anchorId="button2"
anchorSelect="button2"
isOpen={isDarkOpen}
setIsOpen={setIsDarkOpen}
/>
<Tooltip
place="top"
variant="info"
anchorId="button3"
anchorSelect="button3"
isOpen={isDarkOpen}
setIsOpen={setIsDarkOpen}
/>
<Tooltip
place="right"
variant="info"
anchorId="button3"
anchorSelect="button3"
content="My big tooltip content"
isOpen={isDarkOpen}
setIsOpen={setIsDarkOpen}
@@ -109,7 +114,7 @@ function App() {
Hover me!
</div>
<Tooltip
anchorId="floatAnchor"
anchorSelect="floatAnchor"
content={
toggle
? 'This is a float tooltip with a very very large content string'
@@ -133,7 +138,7 @@ function App() {
Click me!
</div>
<Tooltip
anchorId="onClickAnchor"
anchorSelect="onClickAnchor"
content={`This is an on click tooltip (x:${position.x},y:${position.y})`}
events={['click']}
position={position}
@@ -146,7 +151,7 @@ function App() {
<button id="buttonCallbacks">Check the dev console</button>
<Tooltip
place="bottom"
anchorId="buttonCallbacks"
anchorSelect="buttonCallbacks"
// eslint-disable-next-line no-console
afterShow={() => console.log('After show')}
// eslint-disable-next-line no-console
@@ -158,7 +163,7 @@ function App() {
<Tooltip
events={['click']}
place="bottom"
anchorId="buttonCallbacksClick"
anchorSelect="buttonCallbacksClick"
// eslint-disable-next-line no-console
afterShow={() => console.log('After show with click')}
// eslint-disable-next-line no-console
@@ -170,7 +175,7 @@ function App() {
<Tooltip
delayShow={1000}
place="bottom"
anchorId="buttonCallbacksDelay"
anchorSelect="buttonCallbacksDelay"
// eslint-disable-next-line no-console
afterShow={() => console.log('After show with delay')}
// eslint-disable-next-line no-console
@@ -202,7 +207,7 @@ function App() {

<Tooltip
place="top"
anchorId="withoutCustomMiddleware"
anchorSelect="withoutCustomMiddleware"
content="Showing tooltip with default middlewares"
positionStrategy="fixed"
/>
@@ -231,7 +236,7 @@ function App() {

<Tooltip
place="top"
anchorId="withCustomMiddleware"
anchorSelect="withCustomMiddleware"
content="Showing tooltip with custom inline middleware"
positionStrategy="fixed"
middlewares={[inline(), offset(10)]}
51 changes: 43 additions & 8 deletions src/components/Tooltip/Tooltip.tsx
Original file line number Diff line number Diff line change
@@ -5,9 +5,9 @@ import { useTooltip } from 'components/TooltipProvider'
import useIsomorphicLayoutEffect from 'utils/use-isomorphic-layout-effect'
import { getScrollParent } from 'utils/get-scroll-parent'
import { computeTooltipPosition } from 'utils/compute-positions'
import coreStyles from './core-styles.module.css'
import styles from './styles.module.css'
import type { IPosition, ITooltip, PlacesType } from './TooltipTypes'
import coreStyles from './core-styles.module.css'

const Tooltip = ({
// props
@@ -17,6 +17,7 @@ const Tooltip = ({
variant = 'dark',
anchorId,
anchorSelect,
root,
place = 'top',
offset = 10,
events = ['hover'],
@@ -56,8 +57,13 @@ const Tooltip = ({
const [inlineArrowStyles, setInlineArrowStyles] = useState({})
const [show, setShow] = useState(false)
const [rendered, setRendered] = useState(false)

const wasShowing = useRef(false)
const lastFloatPosition = useRef<IPosition | null>(null)

/** Set IsShadowRoot const */
const isShadowDom = root instanceof ShadowRoot

/**
* @todo Remove this in a future version (provider/wrapper method is deprecated)
*/
@@ -68,6 +74,29 @@ const Tooltip = ({

const shouldOpenOnClick = openOnClick || events.includes('click')

// If isShadowDom update to true, set the inline CSS to the shadow DOM
useEffect(() => {
if (typeof window !== 'undefined') {
if (isShadowDom && document.getElementById('style-rt')) {
const externalStylesheet = document.getElementById('style-rt')
const hrefCss = externalStylesheet!.getAttribute('href')

// Fetch the CSS file, should be easier tbh but this works
fetch(hrefCss as string)
.then((response) => response.text())
.then((cssContent) => {
const styleElement = document.createElement('style')
styleElement.textContent = cssContent

// Add the new <style> tag to the document's head or any other appropriate location
root.appendChild(styleElement)
})
.catch((error) => {
console.error('Error fetching the CSS file:', error)
})
}
}
}, [isShadowDom, root])
/**
* useLayoutEffect runs before useEffect,
* but should be used carefully because of caveats
@@ -272,7 +301,8 @@ const Tooltip = ({
}

const handleClickOutsideAnchors = (event: MouseEvent) => {
const anchorById = document.querySelector<HTMLElement>(`[id='${anchorId}']`)
// Root querySelector on either Shadow DOM or document, seems to work
const anchorById = root.querySelector(`[id='${anchorId}']`)
const anchors = [anchorById, ...anchorsBySelect]
if (anchors.some((anchor) => anchor?.contains(event.target as HTMLElement))) {
return
@@ -298,7 +328,8 @@ const Tooltip = ({
elementRefs.add({ current: anchor })
})

const anchorById = document.querySelector<HTMLElement>(`[id='${anchorId}']`)
// Root querySelector on either Shadow DOM or document, seems to work
const anchorById = root.querySelector<HTMLElement>(`[id='${anchorId}']`)
if (anchorById) {
elementRefs.add({ current: anchorById })
}
@@ -371,6 +402,8 @@ const Tooltip = ({

return () => {
if (closeOnScroll) {
// window to remove with Shadow DOM somewhat
// Click event doesn't work yet
window.removeEventListener('scroll', handleScrollResize)
anchorScrollParent?.removeEventListener('scroll', handleScrollResize)
tooltipScrollParent?.removeEventListener('scroll', handleScrollResize)
@@ -474,7 +507,7 @@ const Tooltip = ({
return () => {
documentObserver.disconnect()
}
}, [id, anchorSelect, activeAnchor])
}, [id, anchorSelect, activeAnchor, isShadowDom, root])

const updateTooltipPosition = () => {
if (position) {
@@ -540,7 +573,8 @@ const Tooltip = ({
}, [content, contentWrapperRef?.current])

useEffect(() => {
const anchorById = document.querySelector<HTMLElement>(`[id='${anchorId}']`)
// Root querySelector on either Shadow DOM or document, seems to work
const anchorById = root.querySelector(`[id='${anchorId}']`)
const anchors = [...anchorsBySelect, anchorById]
if (!activeAnchor || !anchors.includes(activeAnchor)) {
/**
@@ -550,7 +584,7 @@ const Tooltip = ({
*/
setActiveAnchor(anchorsBySelect[0] ?? anchorById)
}
}, [anchorId, anchorsBySelect, activeAnchor])
}, [anchorId, anchorsBySelect, activeAnchor, isShadowDom, root])

useEffect(() => {
return () => {
@@ -572,13 +606,14 @@ const Tooltip = ({
return
}
try {
const anchors = Array.from(document.querySelectorAll<HTMLElement>(selector))
// Root querySelector on either Shadow DOM or document, seems to work
const anchors: HTMLElement[] = Array.from(root.querySelectorAll(selector))
setAnchorsBySelect(anchors)
} catch {
// warning was already issued in the controller
setAnchorsBySelect([])
}
}, [id, anchorSelect])
}, [id, anchorSelect, isShadowDom, root])

const canShow = !hidden && content && show && Object.keys(inlineStyles).length > 0

1 change: 1 addition & 0 deletions src/components/Tooltip/TooltipTypes.d.ts
Original file line number Diff line number Diff line change
@@ -54,6 +54,7 @@ export interface ITooltip {
classNameArrow?: string
content?: ChildrenType
contentWrapperRef?: RefObject<HTMLDivElement>
root: Document | ShadowRoot
place?: PlacesType
offset?: number
id?: string
Loading