Fix visibility state of native tooltip (#8581)
* Fix visibility state of native tooltip * rm onLayout --------- Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
@@ -3,6 +3,7 @@ import {
|
|||||||
createContext,
|
createContext,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
@@ -30,31 +31,33 @@ const BUBBLE_SHADOW_OFFSET = ARROW_SIZE / 3 // vibes-based, provide more shadow
|
|||||||
|
|
||||||
type TooltipContextType = {
|
type TooltipContextType = {
|
||||||
position: 'top' | 'bottom'
|
position: 'top' | 'bottom'
|
||||||
ready: boolean
|
visible: boolean
|
||||||
onVisibleChange: (visible: boolean) => void
|
onVisibleChange: (visible: boolean) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TargetMeasurements = {
|
||||||
|
x: number
|
||||||
|
y: number
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
|
||||||
type TargetContextType = {
|
type TargetContextType = {
|
||||||
targetMeasurements:
|
targetMeasurements: TargetMeasurements | undefined
|
||||||
| {
|
setTargetMeasurements: (measurements: TargetMeasurements) => void
|
||||||
x: number
|
shouldMeasure: boolean
|
||||||
y: number
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
targetRef: React.RefObject<View>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const TooltipContext = createContext<TooltipContextType>({
|
const TooltipContext = createContext<TooltipContextType>({
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
ready: false,
|
visible: false,
|
||||||
onVisibleChange: () => {},
|
onVisibleChange: () => {},
|
||||||
})
|
})
|
||||||
|
|
||||||
const TargetContext = createContext<TargetContextType>({
|
const TargetContext = createContext<TargetContextType>({
|
||||||
targetMeasurements: undefined,
|
targetMeasurements: undefined,
|
||||||
targetRef: {current: null},
|
setTargetMeasurements: () => {},
|
||||||
|
shouldMeasure: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
export function Outer({
|
export function Outer({
|
||||||
@@ -68,21 +71,12 @@ export function Outer({
|
|||||||
visible: boolean
|
visible: boolean
|
||||||
onVisibleChange: (visible: boolean) => void
|
onVisibleChange: (visible: boolean) => void
|
||||||
}) {
|
}) {
|
||||||
/**
|
|
||||||
* Whether we have measured the target and are ready to show the tooltip.
|
|
||||||
*/
|
|
||||||
const [ready, setReady] = useState(false)
|
|
||||||
/**
|
/**
|
||||||
* Lagging state to track the externally-controlled visibility of the
|
* Lagging state to track the externally-controlled visibility of the
|
||||||
* tooltip.
|
* tooltip, which needs to wait for the target to be measured before
|
||||||
|
* actually being shown.
|
||||||
*/
|
*/
|
||||||
const [prevRequestVisible, setPrevRequestVisible] = useState<
|
const [visible, setVisible] = useState<boolean>(false)
|
||||||
boolean | undefined
|
|
||||||
>()
|
|
||||||
/**
|
|
||||||
* Needs to reference the element this Tooltip is attached to.
|
|
||||||
*/
|
|
||||||
const targetRef = useRef<View>(null)
|
|
||||||
const [targetMeasurements, setTargetMeasurements] = useState<
|
const [targetMeasurements, setTargetMeasurements] = useState<
|
||||||
| {
|
| {
|
||||||
x: number
|
x: number
|
||||||
@@ -93,33 +87,24 @@ export function Outer({
|
|||||||
| undefined
|
| undefined
|
||||||
>(undefined)
|
>(undefined)
|
||||||
|
|
||||||
if (requestVisible && !prevRequestVisible) {
|
if (requestVisible && !visible && targetMeasurements) {
|
||||||
setPrevRequestVisible(true)
|
setVisible(true)
|
||||||
|
} else if (!requestVisible && visible) {
|
||||||
if (targetRef.current) {
|
setVisible(false)
|
||||||
/*
|
|
||||||
* Once opened, measure the dimensions and position of the target
|
|
||||||
*/
|
|
||||||
targetRef.current.measure((_x, _y, width, height, pageX, pageY) => {
|
|
||||||
if (pageX !== undefined && pageY !== undefined && width && height) {
|
|
||||||
setTargetMeasurements({x: pageX, y: pageY, width, height})
|
|
||||||
setReady(true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else if (!requestVisible && prevRequestVisible) {
|
|
||||||
setPrevRequestVisible(false)
|
|
||||||
setTargetMeasurements(undefined)
|
setTargetMeasurements(undefined)
|
||||||
setReady(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ctx = useMemo(
|
const ctx = useMemo(
|
||||||
() => ({position, ready, onVisibleChange}),
|
() => ({position, visible, onVisibleChange}),
|
||||||
[position, ready, onVisibleChange],
|
[position, visible, onVisibleChange],
|
||||||
)
|
)
|
||||||
const targetCtx = useMemo(
|
const targetCtx = useMemo(
|
||||||
() => ({targetMeasurements, targetRef}),
|
() => ({
|
||||||
[targetMeasurements, targetRef],
|
targetMeasurements,
|
||||||
|
setTargetMeasurements,
|
||||||
|
shouldMeasure: requestVisible,
|
||||||
|
}),
|
||||||
|
[requestVisible, targetMeasurements, setTargetMeasurements],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -132,7 +117,20 @@ export function Outer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Target({children}: {children: React.ReactNode}) {
|
export function Target({children}: {children: React.ReactNode}) {
|
||||||
const {targetRef} = useContext(TargetContext)
|
const {shouldMeasure, setTargetMeasurements} = useContext(TargetContext)
|
||||||
|
const targetRef = useRef<View>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!shouldMeasure) return
|
||||||
|
/*
|
||||||
|
* Once opened, measure the dimensions and position of the target
|
||||||
|
*/
|
||||||
|
targetRef.current?.measure((_x, _y, width, height, pageX, pageY) => {
|
||||||
|
if (pageX !== undefined && pageY !== undefined && width && height) {
|
||||||
|
setTargetMeasurements({x: pageX, y: pageY, width, height})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [shouldMeasure, setTargetMeasurements])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View collapsable={false} ref={targetRef}>
|
<View collapsable={false} ref={targetRef}>
|
||||||
@@ -148,13 +146,13 @@ export function Content({
|
|||||||
children: React.ReactNode
|
children: React.ReactNode
|
||||||
label: string
|
label: string
|
||||||
}) {
|
}) {
|
||||||
const {position, ready, onVisibleChange} = useContext(TooltipContext)
|
const {position, visible, onVisibleChange} = useContext(TooltipContext)
|
||||||
const {targetMeasurements} = useContext(TargetContext)
|
const {targetMeasurements} = useContext(TargetContext)
|
||||||
const requestClose = useCallback(() => {
|
const requestClose = useCallback(() => {
|
||||||
onVisibleChange(false)
|
onVisibleChange(false)
|
||||||
}, [onVisibleChange])
|
}, [onVisibleChange])
|
||||||
|
|
||||||
if (!ready || !targetMeasurements) return null
|
if (!visible || !targetMeasurements) return null
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
|
|||||||
Reference in New Issue
Block a user