diff --git a/package.json b/package.json index 4194b6c..c5b3aa3 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "dependencies": { "@babel/runtime": "^7.11.2", "@rc-component/trigger": "^2.0.0", - "classnames": "^2.3.1" + "classnames": "^2.3.1", + "rc-util": "^5.44.3" }, "devDependencies": { "@rc-component/father-plugin": "^1.0.0", @@ -69,4 +70,4 @@ "react": ">=16.9.0", "react-dom": ">=16.9.0" } -} +} \ No newline at end of file diff --git a/src/Tooltip.tsx b/src/Tooltip.tsx index 3f9a2f4..30c15dd 100644 --- a/src/Tooltip.tsx +++ b/src/Tooltip.tsx @@ -1,11 +1,12 @@ import type { ArrowType, TriggerProps, TriggerRef } from '@rc-component/trigger'; import Trigger from '@rc-component/trigger'; import type { ActionType, AlignType, AnimationType } from '@rc-component/trigger/lib/interface'; +import classNames from 'classnames'; import * as React from 'react'; import { forwardRef, useImperativeHandle, useRef } from 'react'; import { placements } from './placements'; import Popup from './Popup'; -import classNames from 'classnames'; +import useId from 'rc-util/lib/hooks/useId'; export interface TooltipProps extends Pick< @@ -60,7 +61,7 @@ export interface TooltipClassNames { body?: string; } -export interface TooltipRef extends TriggerRef {} +export interface TooltipRef extends TriggerRef { } const Tooltip = (props: TooltipProps, ref: React.Ref) => { const { @@ -91,7 +92,9 @@ const Tooltip = (props: TooltipProps, ref: React.Ref) => { ...restProps } = props; + const mergedId = useId(id); const triggerRef = useRef(null); + useImperativeHandle(ref, () => triggerRef.current); const extraProps: Partial = { ...restProps }; @@ -103,7 +106,7 @@ const Tooltip = (props: TooltipProps, ref: React.Ref) => { @@ -111,6 +114,18 @@ const Tooltip = (props: TooltipProps, ref: React.Ref) => { ); + const getChildren = () => { + const child = React.Children.only(children); + const originalProps = child?.props || {}; + + const childProps = { + ...originalProps, + 'aria-describedby': overlay ? mergedId : null, + }; + + return React.cloneElement(children, childProps); + }; + return ( ) => { arrow={showArrow} {...extraProps} > - {children} + {getChildren()} ); }; diff --git a/tests/index.test.tsx b/tests/index.test.tsx index 6842205..7436ab8 100644 --- a/tests/index.test.tsx +++ b/tests/index.test.tsx @@ -279,4 +279,62 @@ describe('rc-tooltip', () => { expect(tooltipElement.style.backgroundColor).toBe('blue'); expect(tooltipBodyElement.style.color).toBe('red'); }); + + describe('children handling', () => { + it('should pass aria-describedby to child element when overlay exists', () => { + const { container } = render( + + + , + ); + + expect(container.querySelector('button')).toHaveAttribute('aria-describedby', 'test-id'); + }); + + it('should not pass aria-describedby when overlay is empty', () => { + const { container } = render( + + + , + ); + + expect(container.querySelector('button')).not.toHaveAttribute('aria-describedby'); + }); + + it('should preserve original props of children', () => { + const onMouseEnter = jest.fn(); + + const { container } = render( + + + , + ); + + const btn = container.querySelector('button'); + expect(btn).toHaveClass('custom-btn'); + + // 触发原始事件处理器 + fireEvent.mouseEnter(btn); + expect(onMouseEnter).toHaveBeenCalled(); + }); + + it('should throw error when multiple children provided', () => { + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => { }); + + expect(() => { + render( + // @ts-expect-error + + + + , + ); + }).toThrow(); + + errorSpy.mockRestore(); + }); + }); }); +