Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# rc-tooltip
# @rc-component/tooltip

React Tooltip

Expand All @@ -9,18 +9,18 @@ React Tooltip
[![bundle size][bundlephobia-image]][bundlephobia-url]
[![dumi][dumi-image]][dumi-url]

[npm-image]: http://img.shields.io/npm/v/rc-tooltip.svg?style=flat-square
[npm-url]: http://npmjs.org/package/rc-tooltip
[npm-image]: http://img.shields.io/npm/v/@rc-component/tooltip.svg?style=flat-square
[npm-url]: http://npmjs.org/package/@rc-component/tooltip
[travis-image]: https://img.shields.io/travis/react-component/tooltip/master?style=flat-square
[travis-url]: https://travis-ci.com/react-component/tooltip
[github-actions-image]: https://github.com/react-component/tooltip/actions/workflows/react-component-ci.yml/badge.svg
[github-actions-url]: https://github.com/react-component/tooltip/actions/workflows/react-component-ci.yml
[codecov-image]: https://img.shields.io/codecov/c/github/react-component/tooltip/master.svg?style=flat-square
[codecov-url]: https://app.codecov.io/gh/react-component/tooltip
[download-image]: https://img.shields.io/npm/dm/rc-tooltip.svg?style=flat-square
[download-url]: https://npmjs.org/package/rc-tooltip
[bundlephobia-url]: https://bundlephobia.com/package/rc-tooltip
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/rc-tooltip
[download-image]: https://img.shields.io/npm/dm/@rc-component/tooltip.svg?style=flat-square
[download-url]: https://npmjs.org/package/@rc-component/tooltip
[bundlephobia-url]: https://bundlephobia.com/package/@rc-component/tooltip
[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/@rc-component/tooltip
[dumi-url]: https://github.com/umijs/dumi
[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square

Expand All @@ -36,18 +36,18 @@ React Tooltip

## Install

[![rc-tooltip](https://nodei.co/npm/rc-tooltip.png)](https://npmjs.org/package/rc-tooltip)
[![@rc-component/tooltip](https://nodei.co/npm/@rc-component/tooltip.png)](https://npmjs.org/package/@rc-component/tooltip)

## Usage

```js
var Tooltip = require('rc-tooltip');
var Tooltip = require('@rc-component/tooltip');
var React = require('react');
var ReactDOM = require('react-dom');

// By default, the tooltip has no style.
// Consider importing the stylesheet it comes with:
// 'rc-tooltip/assets/bootstrap_white.css'
// '@rc-component/tooltip/assets/bootstrap_white.css'

ReactDOM.render(
<Tooltip placement="left" trigger={['click']} overlay={<span>tooltip</span>}>
Expand Down Expand Up @@ -135,4 +135,4 @@ npm run coverage

## License

`rc-tooltip` is released under the MIT license.
`@rc-component/tooltip` is released under the MIT license.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"devDependencies": {
"@rc-component/father-plugin": "^2.0.1",
"@rc-component/np": "^1.0.3",
"@testing-library/jest-dom": "^6.9.1",
"@testing-library/react": "^16.3.0",
"@types/jest": "^29.4.0",
"@types/node": "^22.15.18",
Expand Down
53 changes: 50 additions & 3 deletions src/Tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { ActionType, AlignType } from '@rc-component/trigger/lib/interface'
import useId from '@rc-component/util/lib/hooks/useId';
import { clsx } from 'clsx';
import * as React from 'react';
import { useImperativeHandle, useRef } from 'react';
import { useImperativeHandle, useRef, useEffect, useCallback } from 'react';
import { placements } from './placements';
import Popup from './Popup';

Expand Down Expand Up @@ -79,6 +79,7 @@ const Tooltip = React.forwardRef<TooltipRef, TooltipProps>((props, ref) => {
showArrow = true,
classNames,
styles,
forceRender,
...restProps
} = props;

Expand All @@ -93,6 +94,51 @@ const Tooltip = React.forwardRef<TooltipRef, TooltipProps>((props, ref) => {
extraProps.popupVisible = props.visible;
}

const isControlled = 'visible' in props;
const mergedVisible = props.visible;
const [popupMounted, setPopupMounted] = React.useState(() => {
if (forceRender) {
return true;
}
if (isControlled) {
return mergedVisible;
}
return defaultVisible;
});

const updatePopupMounted = useCallback(
(nextVisible: boolean) => {
setPopupMounted((prev) => {
if (nextVisible) {
return true;
}

if (destroyOnHidden) {
return false;
}

return prev;
});
},
[destroyOnHidden],
);

const handleVisibleChange = (nextVisible: boolean) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

清理一下

updatePopupMounted(nextVisible);
onVisibleChange?.(nextVisible);
};

useEffect(() => {
if (forceRender) {
setPopupMounted(true);
return;
}

if (isControlled) {
updatePopupMounted(mergedVisible);
}
}, [forceRender, isControlled, mergedVisible, updatePopupMounted]);

// ========================= Arrow ==========================
// Process arrow configuration
const mergedArrow = React.useMemo(() => {
Expand All @@ -118,7 +164,7 @@ const Tooltip = React.forwardRef<TooltipRef, TooltipProps>((props, ref) => {
const originalProps = child?.props || {};
const childProps = {
...originalProps,
'aria-describedby': overlay ? mergedId : null,
'aria-describedby': overlay && popupMounted ? mergedId : null,
};
return React.cloneElement<any>(children, childProps) as any;
};
Expand All @@ -145,10 +191,11 @@ const Tooltip = React.forwardRef<TooltipRef, TooltipProps>((props, ref) => {
ref={triggerRef}
popupAlign={align}
getPopupContainer={getTooltipContainer}
onOpenChange={onVisibleChange}
onOpenChange={handleVisibleChange}
afterOpenChange={afterVisibleChange}
popupMotion={motion}
defaultPopupVisible={defaultVisible}
forceRender={forceRender}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个你已经不消费了,还原给解构填入吧

autoDestroy={destroyOnHidden}
mouseLeaveDelay={mouseLeaveDelay}
popupStyle={styles?.root}
Expand Down
74 changes: 71 additions & 3 deletions tests/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -502,14 +502,24 @@ describe('rc-tooltip', () => {
});

describe('children handling', () => {
it('should pass aria-describedby to child element when overlay exists', () => {
it('should only set aria-describedby once popup is mounted', async () => {
const { container } = render(
<Tooltip id="test-id" overlay="tooltip content">
<Tooltip trigger={['click']} overlay="tooltip content">
<button>Click me</button>
</Tooltip>,
);

expect(container.querySelector('button')).toHaveAttribute('aria-describedby', 'test-id');
const btn = container.querySelector('button');
expect(btn).not.toHaveAttribute('aria-describedby');

fireEvent.click(btn);
await waitFakeTimers();
const describedby = btn.getAttribute('aria-describedby');
expect(describedby).toBeTruthy();

fireEvent.click(btn);
await waitFakeTimers();
expect(btn).toHaveAttribute('aria-describedby', describedby);
});

it('should not pass aria-describedby when overlay is empty', () => {
Expand All @@ -522,6 +532,64 @@ describe('rc-tooltip', () => {
expect(container.querySelector('button')).not.toHaveAttribute('aria-describedby');
});

it('should set aria-describedby immediately when defaultVisible is true', () => {
const { container } = render(
<Tooltip defaultVisible overlay="tooltip content">
<button>Click me</button>
</Tooltip>,
);

expect(container.querySelector('button')).toHaveAttribute('aria-describedby');
});

it('should set aria-describedby immediately when forceRender is true', () => {
const { container } = render(
<Tooltip forceRender overlay="tooltip content">
<button>Click me</button>
</Tooltip>,
);

expect(container.querySelector('button')).toHaveAttribute('aria-describedby');
});

it('should keep aria-describedby when controlled hidden without destroy', () => {
const overlay = 'tooltip content';
const { container, rerender } = render(
<Tooltip overlay={overlay} visible>
<button>Click me</button>
</Tooltip>,
);

expect(container.querySelector('button')).toHaveAttribute('aria-describedby');

rerender(
<Tooltip overlay={overlay} visible={false}>
<button>Click me</button>
</Tooltip>,
);

expect(container.querySelector('button')).toHaveAttribute('aria-describedby');
});

it('should remove aria-describedby when popup is destroyed on hide', async () => {
const { container } = render(
<Tooltip destroyOnHidden trigger={['click']} overlay="tooltip content">
<button>Click me</button>
</Tooltip>,
);

const btn = container.querySelector('button');
expect(btn).not.toHaveAttribute('aria-describedby');

fireEvent.click(btn);
await waitFakeTimers();
expect(btn).toHaveAttribute('aria-describedby');

fireEvent.click(btn);
await waitFakeTimers();
expect(btn).not.toHaveAttribute('aria-describedby');
});

it('should preserve original props of children', () => {
const onMouseEnter = jest.fn();

Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"declaration": true,
"skipLibCheck": true,
"esModuleInterop": true,
"types": ["@testing-library/jest-dom"],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

By adding the types property, you override TypeScript's default type inclusion. This means that other ambient type definitions, like for Jest (jest) and Node.js (node), will no longer be automatically included, which will likely break your build. You need to explicitly list all required ambient types.1

Suggested change
"types": ["@testing-library/jest-dom"],
"types": ["jest", "node", "@testing-library/jest-dom"],

Rules References

Footnotes

  1. When using the compilerOptions.types property in tsconfig.json, TypeScript will only include the type definitions for the packages explicitly listed in the array. This overrides the default behavior of automatically including all packages found in node_modules/@types. Therefore, you must list all necessary type definitions, such as jest and node, to avoid compilation errors.

"paths": {
"@/*": [
"src/*"
Expand Down