ποΈ Lifecycle-driven motion primitives for React enter, leave, and list animations.
diff --git a/README.zh-CN.md b/README.zh-CN.md
index ee7045a..7cc6446 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -1,6 +1,6 @@
@rc-component/motion
-
Ant Design ηζηδΈι¨εγ
+
Ant Design ηζηδΈι¨εγ
ποΈ React ε¨ζεΊη‘η»δ»ΆοΌε°θ£
CSS ε¨η»γθΏζΈ‘εηε½ε¨ζηΆζγ
diff --git a/docs/examples/ssr.tsx b/docs/examples/ssr.tsx
index 95b06d1..1c97c7b 100644
--- a/docs/examples/ssr.tsx
+++ b/docs/examples/ssr.tsx
@@ -1,7 +1,7 @@
import { clsx } from 'clsx';
import CSSMotion, { genCSSMotion } from 'rc-motion';
import React from 'react';
-import { hydrate } from 'react-dom';
+import { hydrateRoot } from 'react-dom/client';
import ReactDOMServer from 'react-dom/server';
import './basic.less';
@@ -44,9 +44,10 @@ const App = () => {
document.body.appendChild(div);
div.innerHTML = ssr;
- hydrate(, div);
+ const root = hydrateRoot(div, );
return () => {
+ root.unmount();
document.body.removeChild(div);
};
}, []);
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..40f08ec
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,115 @@
+import js from '@eslint/js';
+import { defineConfig } from 'eslint/config';
+import { dirname } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import prettier from 'eslint-config-prettier';
+import jest from 'eslint-plugin-jest';
+import react from 'eslint-plugin-react';
+import reactHooks from 'eslint-plugin-react-hooks';
+import globals from 'globals';
+import tseslint from 'typescript-eslint';
+
+const tsconfigRootDir = dirname(fileURLToPath(import.meta.url));
+
+export default defineConfig([
+ {
+ plugins: {
+ '@typescript-eslint': tseslint.plugin,
+ },
+ },
+ {
+ linterOptions: {
+ reportUnusedDisableDirectives: 'warn',
+ },
+ },
+ {
+ ignores: [
+ 'node_modules/',
+ 'coverage/',
+ 'es/',
+ 'lib/',
+ 'dist/',
+ 'docs-dist/',
+ '.docs-dist/',
+ '.dumi/',
+ '.doc/',
+ '.vercel/',
+ ],
+ },
+ {
+ files: ['**/*.{js,jsx,ts,tsx}'],
+ extends: [
+ js.configs.recommended,
+ react.configs.flat.recommended,
+ react.configs.flat['jsx-runtime'],
+ prettier,
+ ],
+ plugins: {
+ 'react-hooks': reactHooks,
+ },
+ languageOptions: {
+ globals: {
+ ...globals.browser,
+ ...globals.node,
+ },
+ },
+ settings: {
+ react: {
+ version: 'detect',
+ },
+ },
+ rules: {
+ 'no-async-promise-executor': 'off',
+ 'no-empty-pattern': 'off',
+ 'no-irregular-whitespace': 'off',
+ 'no-prototype-builtins': 'off',
+ 'no-useless-escape': 'off',
+ 'no-extra-boolean-cast': 'off',
+ 'no-undef': 'off',
+ 'no-unused-vars': 'off',
+ 'react/no-find-dom-node': 'off',
+ 'react/display-name': 'off',
+ 'react/no-unknown-property': 'off',
+ 'react/prop-types': 'off',
+ 'react-hooks/exhaustive-deps': 'warn',
+ 'react-hooks/rules-of-hooks': 'error',
+ },
+ },
+ {
+ files: ['**/*.{ts,tsx}'],
+ extends: [...tseslint.configs.recommended],
+ rules: {
+ '@typescript-eslint/ban-ts-comment': 'off',
+ '@typescript-eslint/no-empty-object-type': 'off',
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-unsafe-function-type': 'off',
+ '@typescript-eslint/no-unnecessary-type-constraint': 'off',
+ '@typescript-eslint/no-unused-vars': 'off',
+ },
+ },
+ {
+ files: ['src/**/*.{ts,tsx}'],
+ languageOptions: {
+ parserOptions: {
+ projectService: true,
+ tsconfigRootDir,
+ },
+ },
+ },
+ {
+ files: ['tests/**/*.{js,jsx,ts,tsx}', '**/*.{test,spec}.{js,jsx,ts,tsx}'],
+ extends: [jest.configs['flat/recommended']],
+ rules: {
+ 'jest/no-disabled-tests': 'off',
+ 'jest/no-done-callback': 'off',
+ 'jest/no-identical-title': 'off',
+ 'jest/expect-expect': 'off',
+ 'jest/no-alias-methods': 'off',
+ 'jest/no-conditional-expect': 'off',
+ 'jest/no-export': 'off',
+ 'jest/no-standalone-expect': 'off',
+ 'jest/valid-expect': 'off',
+ 'jest/valid-title': 'off',
+ },
+ },
+]);
diff --git a/global.d.ts b/global.d.ts
new file mode 100644
index 0000000..e0bd355
--- /dev/null
+++ b/global.d.ts
@@ -0,0 +1,11 @@
+///
+///
+///
+///
+///
+
+declare module '*.css';
+declare module '*.less';
+declare module 'jsonp';
+
+declare module 'moment/locale/zh-cn';
diff --git a/package.json b/package.json
index a43afdd..44b5c41 100644
--- a/package.json
+++ b/package.json
@@ -52,27 +52,34 @@
"clsx": "^2.1.1"
},
"devDependencies": {
+ "@eslint/js": "^9.39.4",
"@rc-component/father-plugin": "^2.2.0",
"@rc-component/np": "^1.0.4",
+ "@testing-library/dom": "^10.4.1",
"@testing-library/jest-dom": "^6.9.1",
- "@testing-library/react": "^15.0.7",
- "@types/jest": "^29.5.14",
+ "@testing-library/react": "^16.3.2",
+ "@types/jest": "^30.0.0",
"@types/node": "^26.0.1",
- "@types/react": "^18.3.31",
- "@types/react-dom": "^18.3.7",
- "@umijs/fabric": "^4.0.1",
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
"cross-env": "^10.1.0",
- "dumi": "^2.4.35",
- "eslint": "^8.57.1",
- "father": "^4.6.23",
+ "dumi": "^2.4.38",
+ "eslint": "^9.39.4",
+ "eslint-config-prettier": "^10.1.8",
+ "eslint-plugin-jest": "^29.15.4",
+ "eslint-plugin-react": "^7.37.5",
+ "eslint-plugin-react-hooks": "^7.1.1",
+ "father": "^4.6.24",
"gh-pages": "^6.3.0",
+ "globals": "^17.7.0",
"husky": "^9.1.7",
- "lint-staged": "^16.4.0",
- "prettier": "^3.9.0",
+ "lint-staged": "^17.0.8",
+ "prettier": "^3.9.4",
"rc-test": "^7.1.3",
- "react": "^18.3.1",
- "react-dom": "^18.3.1",
- "typescript": "^5.9.3"
+ "react": "^19.2.7",
+ "react-dom": "^19.2.7",
+ "typescript": "^6.0.3",
+ "typescript-eslint": "^8.62.1"
},
"peerDependencies": {
"react": ">=16.9.0",
diff --git a/src/CSSMotion.tsx b/src/CSSMotion.tsx
index f23148b..20d9d32 100644
--- a/src/CSSMotion.tsx
+++ b/src/CSSMotion.tsx
@@ -148,7 +148,7 @@ export function genCSSMotion(config: CSSMotionConfig) {
const supportMotion = isSupportTransition(props, contextMotion);
// Ref to the react node, it may be a HTMLElement
- const nodeRef = useRef();
+ const nodeRef = useRef(null);
function getDomElement() {
return getDOM(nodeRef.current) as HTMLElement;
diff --git a/src/hooks/useDomMotionEvents.ts b/src/hooks/useDomMotionEvents.ts
index 16abd2c..9cc94eb 100644
--- a/src/hooks/useDomMotionEvents.ts
+++ b/src/hooks/useDomMotionEvents.ts
@@ -6,11 +6,14 @@ import { animationEndName, transitionEndName } from '../util/motion';
export default (
onInternalMotionEnd: (event: MotionEvent) => void,
-): [(element: HTMLElement) => void, (element: HTMLElement) => void] => {
- const cacheElementRef = useRef();
+): [
+ (element: HTMLElement | null) => void,
+ (element: HTMLElement | null) => void,
+] => {
+ const cacheElementRef = useRef(null);
// Remove events
- function removeMotionEvents(element: HTMLElement) {
+ function removeMotionEvents(element: HTMLElement | null) {
if (element) {
element.removeEventListener(transitionEndName, onInternalMotionEnd);
element.removeEventListener(animationEndName, onInternalMotionEnd);
@@ -18,7 +21,7 @@ export default (
}
// Patch events
- function patchMotionEvents(element: HTMLElement) {
+ function patchMotionEvents(element: HTMLElement | null) {
if (cacheElementRef.current && cacheElementRef.current !== element) {
removeMotionEvents(cacheElementRef.current);
}
diff --git a/tests/CSSMotion.spec.tsx b/tests/CSSMotion.spec.tsx
index 848bfe9..8f4a946 100644
--- a/tests/CSSMotion.spec.tsx
+++ b/tests/CSSMotion.spec.tsx
@@ -855,12 +855,35 @@ describe('CSSMotion', () => {
});
describe('strict mode', () => {
+ let hasFindDOMNode = false;
+ let originalFindDOMNode: unknown;
+
beforeEach(() => {
+ hasFindDOMNode = 'findDOMNode' in ReactDOM;
+ originalFindDOMNode = (ReactDOM as any).findDOMNode;
+
+ if (!('findDOMNode' in ReactDOM)) {
+ Object.defineProperty(ReactDOM, 'findDOMNode', {
+ value: jest.fn(),
+ writable: true,
+ configurable: true,
+ });
+ }
jest.spyOn(ReactDOM, 'findDOMNode');
});
afterEach(() => {
- jest.resetAllMocks();
+ jest.restoreAllMocks();
+
+ if (hasFindDOMNode) {
+ Object.defineProperty(ReactDOM, 'findDOMNode', {
+ value: originalFindDOMNode,
+ writable: true,
+ configurable: true,
+ });
+ } else {
+ delete (ReactDOM as any).findDOMNode;
+ }
});
it('not crash when no refs are passed', () => {
diff --git a/tests/__snapshots__/CSSMotion.spec.tsx.snap b/tests/__snapshots__/CSSMotion.spec.tsx.snap
index c198b8b..f534cfe 100644
--- a/tests/__snapshots__/CSSMotion.spec.tsx.snap
+++ b/tests/__snapshots__/CSSMotion.spec.tsx.snap
@@ -1,3 +1,3 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`CSSMotion transition not crash when no children 1`] = `null`;
diff --git a/tsconfig.json b/tsconfig.json
index fbc3458..3f024eb 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,39 +1,20 @@
{
"compilerOptions": {
"target": "esnext",
- "moduleResolution": "node",
- "baseUrl": "./",
+ "moduleResolution": "bundler",
"jsx": "react",
"declaration": true,
"skipLibCheck": true,
"esModuleInterop": true,
"paths": {
- "@/*": [
- "src/*"
- ],
- "@@/*": [
- ".dumi/tmp/*"
- ],
- "@rc-component/motion": [
- "src/index.tsx"
- ],
- "rc-motion": [
- "src/index.tsx"
- ]
+ "@/*": ["./src/*"],
+ "@@/*": ["./.dumi/tmp/*"],
+ "@rc-component/motion": ["./src/index.tsx"],
+ "rc-motion": ["./src/index.tsx"]
},
- "ignoreDeprecations": "5.0"
+ "strict": false,
+ "module": "ESNext"
},
- "include": [
- "src",
- "docs",
- ".dumirc.ts",
- ".fatherrc.ts"
- ],
- "exclude": [
- "node_modules",
- "lib",
- "es",
- "dist",
- "docs-dist"
- ]
+ "include": ["global.d.ts", "src", "docs", ".dumirc.ts", ".fatherrc.ts"],
+ "exclude": ["node_modules", "lib", "es", "dist", "docs-dist"]
}