Skip to content

Commit

Permalink
fix: [tooltip]typescript类型报错 fix #1007
Browse files Browse the repository at this point in the history
  • Loading branch information
hxh2010 authored and albyben committed Dec 13, 2024
1 parent 165dc14 commit 32d7f33
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 67 deletions.
1 change: 1 addition & 0 deletions components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ export { default as Spin } from './spin'
export { default as Timeline } from './timeline'

export { default as Tooltip } from './tooltip'
export type { TooltipProps as ITooltipProps } from './tooltip'

export { default as Tabs } from './tabs'

Expand Down
2 changes: 0 additions & 2 deletions components/popper/__tests__/__snapshots__/index.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ exports[`Popper special case 测试ref返回值不为dom元素 1`] = `
onChange={[Function]}
onFocus={[Function]}
onKeyUp={[Function]}
prefix="KDesign"
type="text"
value=""
/>
Expand Down Expand Up @@ -135,7 +134,6 @@ exports[`Popper special case 测试ref返回值不为dom元素 1`] = `
onChange={[Function]}
onFocus={[Function]}
onKeyUp={[Function]}
prefix="KDesign"
style={null}
type="text"
value=""
Expand Down
120 changes: 73 additions & 47 deletions components/popper/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import React, {
useEffect,
useRef,
useState,
ReactElement,
Children,
forwardRef,
useContext,
isValidElement,
useImperativeHandle,
MutableRefObject,
} from 'react'
import { createPopper, Instance, Modifier, Placement } from '@popperjs/core'
import { createPopper, Instance, Modifier, OptionsGeneric, Placement } from '@popperjs/core'
import { tuple } from '../_utils/type'
import classnames from 'classnames'
import debounce from 'lodash/debounce'
Expand Down Expand Up @@ -44,6 +44,10 @@ export type Reason = TriggerType | 'scroll' | 'clickOutside' | 'clickToClose' |

export type RenderFunction = () => React.ReactNode

export type RenderType = RenderFunction | React.ReactNode | undefined

export type RefType = React.RefObject<HTMLElement | Record<string, HTMLElement>>

export type PopperProps = {
gap?: number
arrow?: boolean
Expand All @@ -65,7 +69,7 @@ export type PopperProps = {
popperOuterClassName?: string
popperOuterStyle?: React.CSSProperties
placement?: PlacementType
tip?: any
tip?: RenderType
trigger?: TriggerType | Array<TriggerType>
strategy?: 'fixed' | 'absolute'
clickToClose?: boolean
Expand All @@ -75,14 +79,14 @@ export type PopperProps = {
getPopupContainer?: (locatorNode: HTMLElement) => HTMLElement
onTransitionEnd?: (e: React.TransitionEvent) => void
onAnimationEnd?: (e: React.AnimationEvent) => void
children?: React.ReactNode
children?: RenderType
customerModifiers?: (modifiers: Partial<Modifier<any, any>>[]) => Partial<Modifier<any, any>>[]
}

const useEnhancedEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect

export interface TriggerContextProps {
registerSubPopup: (id: string, node: any) => void
registerSubPopup: (id: string, node: SubPopup) => void
}

const TriggerContext = React.createContext<TriggerContextProps | null>(null)
Expand Down Expand Up @@ -130,28 +134,42 @@ const getFallbackPlacementList: (arr: string[]) => Placement[] = (arr) => {
})
.filter((d) => d) as Placement[]
}
const hasDisplayName = (type: unknown): type is { displayName: string } => {
return typeof type === 'object' && type !== null && 'displayName' in type
}

const getRealDom = (locatorRef: any, locatorElement: any = undefined) => {
const getRealDom = (
locatorRef: RefType,
locatorElement: React.ReactElement | undefined = undefined,
): HTMLElement | null => {
if (!locatorRef.current) return locatorRef.current
if (locatorRef.current.tagName) return locatorRef.current
if (locatorRef.current instanceof HTMLElement) return locatorRef.current
if (locatorElement) {
const REF_NAME_OBJ: any = {
const REF_NAME_OBJ: Record<string, string> = {
Input: 'input',
InputNumber: 'input',
Select: 'select',
Upload: 'input',
}
const name = REF_NAME_OBJ?.[locatorElement?.type?.displayName]
return locatorRef?.current[name]

const name = hasDisplayName(locatorElement.type) ? REF_NAME_OBJ[locatorElement.type.displayName] : ''

return locatorRef?.current?.[name] || null
}
return locatorElement
return null
}

const getElement = (element: any) => {
const getElement = (element: RenderType) => {
return typeof element === 'function' ? element() : element
}

export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
export type SubPopup = {
dom: HTMLElement | null
triggerOpen: (v: boolean, reason?: Reason) => void
visible: boolean
}

export const Popper = forwardRef<SubPopup | null, PopperProps>((props, ref) => {
const { getPrefixCls, prefixCls: pkgPrefixCls } = React.useContext(ConfigContext)
const {
prefixCls,
Expand Down Expand Up @@ -190,15 +208,15 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {

const popperPrefixCls = getPrefixCls!(pkgPrefixCls, 'popper')
const referencePrefixCls = `${popperPrefixCls}-reference`
const child: any = getElement(children)
const child: React.ReactElement = getElement(children as RenderType)
const childrenInner = isValidElement(child) && !isFragment(child) ? child : <span>{child}</span>
const popperElement = getElement(tip)
const referenceElement: any = Children.only(childrenInner) as ReactElement
const popperElement = getElement(tip as RenderType)
const referenceElement = Children.only(childrenInner) as React.ReactElement & { ref?: RefType }

const arrowOffsetInner = arrowSize + arrowOffset
const getArrowOffset = (popperSize: number, referenceSize: number, arr: string[]) => {
const boundary = arrowOffsetInner * 2
let offset: any
let offset

if (referenceSize < boundary || popperSize < boundary) {
const o = Math.min(referenceSize, popperSize) / 2
Expand All @@ -219,7 +237,7 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
}
const id = useId()
const parentContext = useContext(TriggerContext)
const subPopupRefs = useRef<Record<string, any>>({})
const subPopupRefs = useRef<Record<string, SubPopup>>({})
const context = React.useMemo<TriggerContextProps>(() => {
return {
registerSubPopup: (id, subPopupEle) => {
Expand All @@ -230,14 +248,16 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
}
}, [parentContext])

const popperRefDom = useRef<any>(null)
const popperRefInner = useRef<any>(null)
const popperRef: any = ref || popperRefInner
const popperRefDom = useRef<HTMLDivElement | null>(null)
const popperRefInner = useRef<SubPopup | null>(null)
const popperRef = (ref || popperRefInner) as MutableRefObject<SubPopup | null>

const popperInstance = useRef<Instance | null>(null)
const referenceRefInner = useRef<any>(null)
const referenceRefInner = useRef<HTMLElement | null>(null)
const onVisibleChangeRef = useRef<PopperProps['onVisibleChange']>(onVisibleChange)

const referenceRef = (referenceElement?.ref as RefType | null) || referenceRefInner

const referenceRef = referenceElement?.ref || referenceRefInner
const container = getPopupContainer(getRealDom(referenceRef, referenceElement) || document.body) || document.body

const [visibleInner, setVisibleInner] = useMergedState<boolean>(false, {
Expand All @@ -247,9 +267,9 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
const [exist, setExist] = useState<boolean>(visibleInner)
const [placementInner, setPlacementInner] = useState<Placement>(getRealPlacement(placement))

const delayRef = useRef<any>()
const delayRef = useRef<NodeJS.Timeout | null>(null)
const clearDelay = () => {
if (typeof delayRef.current !== 'undefined') {
if (delayRef.current) {
clearTimeout(delayRef.current)
delayRef.current = null
}
Expand All @@ -262,10 +282,10 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
if (typeof visible === 'undefined') {
setVisibleInner(nextOpen)
}
onVisibleChange?.(nextOpen, triggerType)
onVisibleChangeRef.current?.(nextOpen, triggerType)
}
if (!nextOpen && Object.keys(subPopupRefs.current || {}).length) {
Object.values(subPopupRefs.current).forEach((d: any) => {
Object.values(subPopupRefs.current).forEach((d: SubPopup) => {
if (typeof d?.triggerOpen === 'function' && d?.visible) {
d?.triggerOpen(false, 'parentHidden')
}
Expand All @@ -292,15 +312,6 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
return false
}

// let targetElement: HTMLElement = event.target as HTMLElement
// const POP_ATTR_NAME = 'data-popper-placement'
// while (targetElement && targetElement !== document.documentElement) {
// if (targetElement?.getAttribute(POP_ATTR_NAME) && targetElement?.className.includes(popperPrefixCls)) {
// return true
// }
// targetElement = targetElement?.parentNode as HTMLElement
// }

const target: HTMLElement = event.target as HTMLElement
if (subPopupRefs.current) {
for (const key in subPopupRefs.current) {
Expand Down Expand Up @@ -374,7 +385,10 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
return trigger === triggerValue || (Array.isArray(trigger) && trigger.includes(triggerValue))
}

const triggerEventHandle = (triggerNode: any, type = 'addEventListener') => {
const triggerEventHandle = (
triggerNode: HTMLElement,
type: 'addEventListener' | 'removeEventListener' = 'addEventListener',
) => {
if (isTrigger('click')) {
triggerNode?.[type]('click', onClick)
}
Expand All @@ -391,14 +405,18 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
}
}

useEffect(() => {
onVisibleChangeRef.current = onVisibleChange
}, [onVisibleChange])

useEffect(() => {
setPlacementInner(getRealPlacement(placement))
}, [placement])

useEffect(() => {
const scrollHandle = debounce((e: Event) => {
if (visibleInner) {
const isPopper = e.target === popperRefDom.current || popperRefDom.current?.contains?.(e.target)
const isPopper = e.target === popperRefDom.current || popperRefDom.current?.contains?.(e.target as Node)
if (scrollHidden && !isPopper) {
triggerOpen(false, 'scroll')
}
Expand All @@ -419,11 +437,13 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
(e: MouseEvent) => {
if (visibleInner) {
const isPopper = popperRefDom.current
? popperRefDom.current === e.target || popperRefDom.current.contains?.(e.target)
? popperRefDom.current === e.target || popperRefDom.current.contains?.(e.target as Node)
: false

const domReference = getRealDom(referenceRef, referenceElement)
const isReference = domReference ? domReference === e.target || domReference?.contains?.(e.target) : false
const isReference = domReference
? domReference === e.target || domReference?.contains?.(e.target as Node)
: false
const isTarget = isPopper || isReference
if (!isTarget && !isSubPopper(e)) {
triggerOpen(false, 'clickOutside', 0)
Expand All @@ -449,7 +469,7 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {

useEffect(() => {
const realDom = getRealDom(referenceRef, referenceElement)
const triggerNode = getTriggerElement(realDom)
const triggerNode = getTriggerElement(realDom as HTMLElement)

triggerEventHandle(triggerNode)

Expand Down Expand Up @@ -491,15 +511,15 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
name: 'applyArrowOffset',
enabled: true,
phase: 'write',
fn(data: any) {
fn(data) {
const {
elements: { arrow },
placement,
rects: { popper, reference },
} = data.state
if (arrow) {
const arr = placement.split('-')
let offset: any
let offset
if (arr.length === 2) {
switch (arr[0]) {
case 'bottom':
Expand Down Expand Up @@ -547,7 +567,7 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
const popperModifiers =
typeof customerModifiers === 'function' ? customerModifiers(defaultModifiers) : defaultModifiers

const popperOptionsInner: any = {
const popperOptionsInner: Partial<OptionsGeneric<any>> = {
placement: placementInner,
modifiers: popperModifiers,
strategy,
Expand All @@ -566,7 +586,11 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
popperInstance.current?.forceUpdate()
}
setTimeout(() => {
parentContext?.registerSubPopup(id, popperRef.current)
if (popperRef) {
if (popperRef?.current) {
parentContext?.registerSubPopup(id, popperRef?.current)
}
}
}, 10)
}
}
Expand All @@ -582,11 +606,13 @@ export const Popper = forwardRef<unknown, PopperProps>((props, ref) => {
if (current) {
popperInstance.current = createPopper(
trigger === 'contextMenu' ? virtualElement : current?.closest(`.${referencePrefixCls}`) || current,
popperRefDom.current,
popperRefDom.current as HTMLElement,
popperOptionsInner,
)
setTimeout(() => {
parentContext?.registerSubPopup(id, popperRef.current)
if (popperRef?.current) {
parentContext?.registerSubPopup(id, popperRef?.current)
}
}, 10)
}

Expand Down
9 changes: 6 additions & 3 deletions components/tooltip/demo/basic.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,21 @@ order: 0

简单用法。

```jsx
```tsx
import React from 'react'
import ReactDOM from 'react-dom'
import { Tooltip } from '@kdcloudjs/kdesign'
import type { ITooltipProps } from '@kdcloudjs/kdesign'

const Demo: React.FC = () => {
const hover: ITooltipProps['trigger'] = 'hover'

return (
<Tooltip tip="一行最多显示20个字符,超过的字符可折行显示,建议最多不要超过40个字符">
<Tooltip trigger={hover} tip="一行最多显示20个字符,超过的字符可折行显示,建议最多不要超过40个字符">
<span>Mouse over will show Tooltip</span>
</Tooltip>
)
}

ReactDOM.render(<Demo />, mountNode)
```
```
6 changes: 4 additions & 2 deletions components/tooltip/demo/controll.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ order: 0

受控显示

```jsx
```tsx
import React from 'react'
import ReactDOM from 'react-dom'
import { Tooltip, Switch, Input } from '@kdcloudjs/kdesign'
import type { ITooltipProps } from '@kdcloudjs/kdesign'

const Demo: React.FC = () => {
const [visible, setVisible] = React.useState<boolean>(false)
const [visible, setVisible] = React.useState<ITooltipProps['visible']>(false)

return (
<>
<Tooltip tip="一行最多显示20个字符,超过的字符可折行显示,建议最多不要超过40个字符" visible={visible} trigger="click">
Expand Down
Loading

0 comments on commit 32d7f33

Please sign in to comment.