๐ฏ Composable Select component for React, with search, async-friendly option data, custom rendering, and virtual scrolling.
diff --git a/README.zh-CN.md b/README.zh-CN.md
index b84b8763f..63759d622 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -1,6 +1,6 @@
@rc-component/select
-
Ant Design ็ๆ็ไธ้จๅใ
+
Ant Design ็ๆ็ไธ้จๅใ
๐ฏ React ้ๆฉๅจ็ปไปถ๏ผๆฏๆๅ้ใๅค้ใๆ็ดขใๆ ็ญพๅ่ชๅฎไนๆธฒๆใ
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 000000000..40f08ec94
--- /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 000000000..e0bd355c6
--- /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 890f41d82..4b5957be2 100644
--- a/package.json
+++ b/package.json
@@ -55,30 +55,37 @@
"clsx": "^2.1.1"
},
"devDependencies": {
+ "@eslint/js": "^9.39.4",
"@rc-component/dialog": "^1.10.0",
"@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",
- "babel-jest": "^29.7.0",
- "dumi": "^2.4.35",
- "eslint": "^8.57.1",
- "father": "^4.6.23",
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "babel-jest": "^30.4.1",
+ "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",
+ "globals": "^17.7.0",
"husky": "^9.1.7",
"jsonp": "^0.2.1",
"less": "^4.6.7",
- "lint-staged": "^16.4.0",
- "prettier": "^3.9.0",
+ "lint-staged": "^17.0.8",
+ "prettier": "^3.9.4",
"querystring": "^0.2.1",
"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"
},
"publishConfig": {
"access": "public"
diff --git a/src/BaseSelect/index.tsx b/src/BaseSelect/index.tsx
index 44f27812c..a4569e5e2 100644
--- a/src/BaseSelect/index.tsx
+++ b/src/BaseSelect/index.tsx
@@ -171,9 +171,9 @@ export interface BaseSelectProps
// >>> Customize Input
/** @private Internal usage. Do not use in your production. */
- getInputElement?: () => JSX.Element;
+ getInputElement?: () => React.ReactElement;
/** @private Internal usage. Do not use in your production. */
- getRawInputElement?: () => JSX.Element;
+ getRawInputElement?: () => React.ReactElement;
// >>> Selector
maxTagTextLength?: number;
diff --git a/src/Select.tsx b/src/Select.tsx
index 41bd66ccc..2489e2a38 100644
--- a/src/Select.tsx
+++ b/src/Select.tsx
@@ -539,7 +539,7 @@ const Select = React.forwardRef>();
+ const activeEventRef = React.useRef | undefined>(undefined);
const onActiveValue: OnActiveValue = React.useCallback(
(active, index, { source = 'keyboard' } = {}) => {
diff --git a/src/hooks/useRefFunc.ts b/src/hooks/useRefFunc.ts
index 720f972cd..993e9021f 100644
--- a/src/hooks/useRefFunc.ts
+++ b/src/hooks/useRefFunc.ts
@@ -5,7 +5,7 @@ import * as React from 'react';
* but redirect to real function.
*/
export default function useRefFunc any>(callback: T): T {
- const funcRef = React.useRef();
+ const funcRef = React.useRef(callback);
funcRef.current = callback;
const cacheFn = React.useCallback((...args: any[]) => {
diff --git a/src/utils/legacyUtil.ts b/src/utils/legacyUtil.ts
index 3e9a449ad..644806d8a 100644
--- a/src/utils/legacyUtil.ts
+++ b/src/utils/legacyUtil.ts
@@ -3,12 +3,12 @@ import { toArray } from '@rc-component/util';
import type { BaseOptionType, DefaultOptionType } from '../Select';
function convertNodeToOption(
- node: React.ReactElement,
+ node: React.ReactElement,
): OptionType {
const {
key,
props: { children, value, ...restProps },
- } = node as React.ReactElement;
+ } = node;
return { key, value: value !== undefined ? value : key, children, ...restProps };
}
@@ -18,7 +18,7 @@ export function convertChildrenToData {
+ .map((node: React.ReactElement, index: number): OptionType | null => {
if (!React.isValidElement(node) || !node.type) {
return null;
}
@@ -27,7 +27,7 @@ export function convertChildrenToData & { type: { isSelectOptGroup?: boolean } };
if (optionOnly || !isSelectOptGroup) {
return convertNodeToOption(node);
diff --git a/src/utils/warningPropsUtil.ts b/src/utils/warningPropsUtil.ts
index c55608947..e6f96ae3d 100644
--- a/src/utils/warningPropsUtil.ts
+++ b/src/utils/warningPropsUtil.ts
@@ -116,19 +116,19 @@ function warningProps(props: SelectProps) {
return false;
}
if (type.isSelectOptGroup) {
- const allChildrenValid = toNodeArray(node.props.children).every(
- (subNode: React.ReactElement) => {
- if (
- !React.isValidElement(subNode) ||
- !node.type ||
- (subNode.type as { isSelectOption?: boolean }).isSelectOption
- ) {
- return true;
- }
- invalidateChildType = subNode.type;
- return false;
- },
- );
+ const allChildrenValid = toNodeArray(
+ (node as React.ReactElement).props.children,
+ ).every((subNode: React.ReactElement) => {
+ if (
+ !React.isValidElement(subNode) ||
+ !node.type ||
+ (subNode.type as { isSelectOption?: boolean }).isSelectOption
+ ) {
+ return true;
+ }
+ invalidateChildType = subNode.type;
+ return false;
+ });
if (allChildrenValid) {
return false;
diff --git a/tests/__snapshots__/Combobox.test.tsx.snap b/tests/__snapshots__/Combobox.test.tsx.snap
index 4e4b36018..6eac14f0d 100644
--- a/tests/__snapshots__/Combobox.test.tsx.snap
+++ b/tests/__snapshots__/Combobox.test.tsx.snap
@@ -1,4 +1,4 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Select.Combobox renders controlled correctly 1`] = `
diff --git a/tests/__snapshots__/Select.test.tsx.snap b/tests/__snapshots__/Select.test.tsx.snap
index 5ec0a6302..b94d30ad7 100644
--- a/tests/__snapshots__/Select.test.tsx.snap
+++ b/tests/__snapshots__/Select.test.tsx.snap
@@ -1,4 +1,4 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Select.Basic does not filter when filterOption value is false 1`] = `
diff --git a/tests/__snapshots__/ssr.test.tsx.snap b/tests/__snapshots__/ssr.test.tsx.snap
index de7ade80e..1bdd9301a 100644
--- a/tests/__snapshots__/ssr.test.tsx.snap
+++ b/tests/__snapshots__/ssr.test.tsx.snap
@@ -1,3 +1,3 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
-exports[`Select.SSR should work 1`] = `"
"`;
+exports[`Select.SSR should work 1`] = `"
"`;
diff --git a/tsconfig.json b/tsconfig.json
index eec0a112f..cd160b40d 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,18 +1,18 @@
{
"compilerOptions": {
"target": "esnext",
- "moduleResolution": "node",
- "baseUrl": "./",
+ "moduleResolution": "bundler",
"jsx": "preserve",
"declaration": true,
"skipLibCheck": true,
"esModuleInterop": true,
"paths": {
- "@/*": ["src/*"],
- "@@/*": [".dumi/tmp/*"],
- "@rc-component/select": ["src/index.ts"]
+ "@/*": ["./src/*"],
+ "@@/*": ["./.dumi/tmp/*"],
+ "@rc-component/select": ["./src/index.ts"]
},
- "ignoreDeprecations": "5.0"
+ "strict": false,
+ "module": "ESNext"
},
- "include": [".dumirc.ts", ".fatherrc.ts", "src", "tests", "docs"]
+ "include": ["global.d.ts", ".dumirc.ts", ".fatherrc.ts", "src", "tests", "docs"]
}