π
React date, time, range, and panel picker primitives with pluggable date-library generate configs.
diff --git a/README.zh-CN.md b/README.zh-CN.md
index a174806ac..b13041b49 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -1,6 +1,6 @@
@rc-component/picker
-
Ant Design ηζηδΈι¨εγ
+
Ant Design ηζηδΈι¨εγ
π
React ζ₯ζδΈζΆι΄ιζ©εΊη‘η»δ»Άγ
diff --git a/docs/examples/cellRender.tsx b/docs/examples/cellRender.tsx
index 423746f97..577592af7 100644
--- a/docs/examples/cellRender.tsx
+++ b/docs/examples/cellRender.tsx
@@ -15,6 +15,10 @@ function formatDate(date: Moment | null) {
return date ? date.format('YYYY-MM-DD HH:mm:ss') : 'null';
}
+function getOriginNode(node: React.ReactElement) {
+ return node as React.ReactElement;
+}
+
export default () => {
const [value, setValue] = React.useState(defaultValue);
const [rangeValue, setRangeValue] = React.useState<[Moment | null, Moment | null] | null>([
@@ -69,9 +73,9 @@ export default () => {
locale={zhCN}
cellRender={(current: Moment, info) =>
React.cloneElement(
- info.originNode,
+ getOriginNode(info.originNode),
{
- ...info.originNode.props,
+ ...getOriginNode(info.originNode).props,
},
{current.get('date')}
,
)
@@ -82,9 +86,9 @@ export default () => {
locale={zhCN}
cellRender={(current: Moment, info) =>
React.cloneElement(
- info.originNode,
+ getOriginNode(info.originNode),
{
- className: `${info.originNode.props.className} testWrapper`,
+ className: `${getOriginNode(info.originNode).props.className} testWrapper`,
},
{current.get('date')}
,
)
@@ -96,9 +100,9 @@ export default () => {
picker="week"
cellRender={(current: Moment, info) =>
React.cloneElement(
- info.originNode,
+ getOriginNode(info.originNode),
{
- ...info.originNode.props,
+ ...getOriginNode(info.originNode).props,
},
{current.get('week')}
,
)
@@ -110,9 +114,9 @@ export default () => {
picker="year"
cellRender={(current: Moment, info) =>
React.cloneElement(
- info.originNode,
+ getOriginNode(info.originNode),
{
- ...info.originNode.props,
+ ...getOriginNode(info.originNode).props,
},
{current.get('year')}
,
)
@@ -124,9 +128,9 @@ export default () => {
picker="month"
cellRender={(current: Moment, info) =>
React.cloneElement(
- info.originNode,
+ getOriginNode(info.originNode),
{
- ...info.originNode.props,
+ ...getOriginNode(info.originNode).props,
},
{current.get('month') + 1}
,
)
@@ -138,9 +142,9 @@ export default () => {
picker="quarter"
cellRender={(current: Moment, info) =>
React.cloneElement(
- info.originNode,
+ getOriginNode(info.originNode),
{
- ...info.originNode.props,
+ ...getOriginNode(info.originNode).props,
},
Q{current.get('quarter')}
,
)
@@ -152,9 +156,9 @@ export default () => {
picker="time"
cellRender={(current: number | string, info) =>
React.cloneElement(
- info.originNode,
+ getOriginNode(info.originNode),
{
- ...info.originNode.props,
+ ...getOriginNode(info.originNode).props,
},
{current}
,
)
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 000000000..76ab8dac2
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,88 @@
+import { FlatCompat } from '@eslint/eslintrc';
+import js from '@eslint/js';
+import tsEslintPlugin from '@typescript-eslint/eslint-plugin';
+import { createRequire } from 'node:module';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+const require = createRequire(import.meta.url);
+
+const compat = new FlatCompat({
+ baseDirectory: __dirname,
+ recommendedConfig: js.configs.recommended,
+ allConfig: js.configs.all,
+});
+
+const recommendedTsRulesConfig = tsEslintPlugin.configs.recommended;
+const recommendedTsRulesObject = Array.isArray(recommendedTsRulesConfig)
+ ? recommendedTsRulesConfig.reduce((rules, config) => ({ ...rules, ...(config.rules || {}) }), {})
+ : recommendedTsRulesConfig?.rules || {};
+const recommendedTsRules = new Set(Object.keys(recommendedTsRulesObject));
+const noopRule = {
+ meta: { type: 'problem', docs: {}, schema: [] },
+ create: () => ({}),
+};
+
+function normalizeConfig(config) {
+ const next = { ...config };
+
+ if (next.plugins?.['@typescript-eslint']) {
+ next.plugins = { ...next.plugins };
+ delete next.plugins['@typescript-eslint'];
+ }
+
+ if (next.rules) {
+ next.rules = Object.fromEntries(
+ Object.entries(next.rules).filter(([ruleName]) => {
+ if (ruleName.startsWith('@babel/')) {
+ return false;
+ }
+ if (!ruleName.startsWith('@typescript-eslint/')) {
+ return true;
+ }
+ return recommendedTsRules.has(ruleName);
+ }),
+ );
+ }
+
+ return next;
+}
+
+export default [
+ {
+ ignores: [
+ 'node_modules/',
+ 'coverage/',
+ 'es/',
+ 'lib/',
+ 'dist/',
+ 'docs-dist/',
+ '.dumi/',
+ '.doc/',
+ '.vercel/',
+ '.eslintrc.js',
+ 'src/index.d.ts',
+ ],
+ },
+ {
+ plugins: {
+ '@typescript-eslint': {
+ ...tsEslintPlugin,
+ rules: {
+ ...tsEslintPlugin.rules,
+ 'consistent-type-exports': noopRule,
+ },
+ },
+ },
+ },
+ ...compat.config(require('./.eslintrc.js')).map(normalizeConfig),
+ {
+ rules: {
+ '@typescript-eslint/no-empty-object-type': 'off',
+ '@typescript-eslint/no-unsafe-function-type': 'off',
+ '@typescript-eslint/no-unused-vars': '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 6f4efd869..8be277037 100644
--- a/package.json
+++ b/package.json
@@ -118,34 +118,47 @@
"clsx": "^2.1.1"
},
"devDependencies": {
+ "@babel/eslint-parser": "^7.29.7",
+ "@babel/eslint-plugin": "^7.29.7",
+ "@eslint/eslintrc": "^3.3.5",
+ "@eslint/js": "^9.39.4",
"@rc-component/father-plugin": "^2.2.0",
"@rc-component/np": "^1.0.4",
- "@testing-library/react": "^15.0.7",
- "@types/jest": "^29.5.14",
- "@types/luxon": "^3.2.0",
+ "@testing-library/dom": "^10.4.1",
+ "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/react": "^16.3.2",
+ "@types/jest": "^30.0.0",
+ "@types/luxon": "^3.7.2",
"@types/node": "^26.0.1",
- "@types/react": "^18.3.31",
- "@types/react-dom": "^18.3.7",
+ "@types/react": "^19.2.17",
+ "@types/react-dom": "^19.2.3",
+ "@typescript-eslint/eslint-plugin": "^8.62.1",
+ "@typescript-eslint/parser": "^8.62.1",
"cross-env": "^10.1.0",
"date-fns": "2.x",
"dayjs": "1.x",
- "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",
+ "eslint-plugin-unicorn": "^65.0.1",
+ "father": "^4.6.24",
"gh-pages": "^6.3.0",
"glob": "^13.0.6",
"husky": "^9.1.7",
"less": "^4.6.7",
- "lint-staged": "^16.4.0",
+ "lint-staged": "^17.0.8",
"luxon": "3.x",
"mockdate": "^3.0.2",
"moment": "^2.24.0",
"moment-timezone": "^0.5.45",
- "prettier": "^3.9.0",
+ "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"
},
"peerDependencies": {
"date-fns": ">= 2.x",
diff --git a/src/PickerInput/Selector/Input.tsx b/src/PickerInput/Selector/Input.tsx
index 926eded61..38a1fbe2a 100644
--- a/src/PickerInput/Selector/Input.tsx
+++ b/src/PickerInput/Selector/Input.tsx
@@ -341,7 +341,7 @@ const Input = React.forwardRef((props, ref) => {
};
// ======================== Format ========================
- const rafRef = React.useRef();
+ const rafRef = React.useRef(undefined);
useLayoutEffect(() => {
if (!focused || !format || mouseDownRef.current) {
diff --git a/src/PickerInput/Selector/RangeSelector.tsx b/src/PickerInput/Selector/RangeSelector.tsx
index 2f0a3095a..6cc28a74b 100644
--- a/src/PickerInput/Selector/RangeSelector.tsx
+++ b/src/PickerInput/Selector/RangeSelector.tsx
@@ -136,9 +136,9 @@ function RangeSelector(
}, [id]);
// ========================= Refs =========================
- const rootRef = React.useRef();
- const inputStartRef = React.useRef();
- const inputEndRef = React.useRef();
+ const rootRef = React.useRef(null);
+ const inputStartRef = React.useRef(null);
+ const inputEndRef = React.useRef(null);
const getInput = (index: number) => [inputStartRef, inputEndRef][index]?.current;
diff --git a/src/PickerInput/Selector/SingleSelector/index.tsx b/src/PickerInput/Selector/SingleSelector/index.tsx
index 7cd1a7ee9..9fc8d3690 100644
--- a/src/PickerInput/Selector/SingleSelector/index.tsx
+++ b/src/PickerInput/Selector/SingleSelector/index.tsx
@@ -114,8 +114,8 @@ function SingleSelector(
const { prefixCls, classNames, styles } = React.useContext(PickerContext);
// ========================= Refs =========================
- const rootRef = React.useRef();
- const inputRef = React.useRef();
+ const rootRef = React.useRef(null);
+ const inputRef = React.useRef(null);
React.useImperativeHandle(ref, () => ({
nativeElement: rootRef.current,
diff --git a/src/PickerInput/hooks/useDelayState.ts b/src/PickerInput/hooks/useDelayState.ts
index 0faca60d1..8e3a739ad 100644
--- a/src/PickerInput/hooks/useDelayState.ts
+++ b/src/PickerInput/hooks/useDelayState.ts
@@ -23,7 +23,7 @@ export default function useDelayState(
const nextValueRef = React.useRef(value);
// ============================= Update =============================
- const rafRef = React.useRef();
+ const rafRef = React.useRef(undefined);
const cancelRaf = () => {
raf.cancel(rafRef.current);
};
diff --git a/src/PickerInput/hooks/usePickerRef.ts b/src/PickerInput/hooks/usePickerRef.ts
index 3d57f8f0a..513cedaaf 100644
--- a/src/PickerInput/hooks/usePickerRef.ts
+++ b/src/PickerInput/hooks/usePickerRef.ts
@@ -6,7 +6,7 @@ type PickerRefType = Omit & {
};
export default function usePickerRef(ref: React.Ref>) {
- const selectorRef = React.useRef>();
+ const selectorRef = React.useRef>(null);
React.useImperativeHandle(ref, () => ({
nativeElement: selectorRef.current?.nativeElement,
diff --git a/src/PickerPanel/TimePanel/TimePanelBody/TimeColumn.tsx b/src/PickerPanel/TimePanel/TimePanelBody/TimeColumn.tsx
index e8130a170..4058f278c 100644
--- a/src/PickerPanel/TimePanel/TimePanelBody/TimeColumn.tsx
+++ b/src/PickerPanel/TimePanel/TimePanelBody/TimeColumn.tsx
@@ -7,7 +7,7 @@ import useScrollTo from './useScrollTo';
const SCROLL_DELAY = 300;
export type Unit = {
- label: React.ReactText;
+ label: string | number;
value: ValueType;
disabled?: boolean;
};
@@ -41,7 +41,7 @@ export default function TimeColumn(props: TimeUnitColum
const ulRef = React.useRef(null);
// ========================= Scroll =========================
- const checkDelayRef = React.useRef();
+ const checkDelayRef = React.useRef(undefined);
const clearDelayCheck = () => {
clearTimeout(checkDelayRef.current);
diff --git a/src/PickerPanel/TimePanel/TimePanelBody/useScrollTo.ts b/src/PickerPanel/TimePanel/TimePanelBody/useScrollTo.ts
index 47c172b04..005a89d85 100644
--- a/src/PickerPanel/TimePanel/TimePanelBody/useScrollTo.ts
+++ b/src/PickerPanel/TimePanel/TimePanelBody/useScrollTo.ts
@@ -19,7 +19,7 @@ export default function useScrollTo(
scrollingRef.current = false;
};
- const scrollRafTimesRef = React.useRef();
+ const scrollRafTimesRef = React.useRef(undefined);
const startScroll = () => {
const ul = ulRef.current;
diff --git a/src/PickerPanel/index.tsx b/src/PickerPanel/index.tsx
index c0eeacebd..51e75516e 100644
--- a/src/PickerPanel/index.tsx
+++ b/src/PickerPanel/index.tsx
@@ -202,7 +202,7 @@ function PickerPanel(
const mergedPrefixCls = contextPrefixCls || prefixCls || 'rc-picker';
// ========================== Refs ==========================
- const rootRef = React.useRef();
+ const rootRef = React.useRef(null);
React.useImperativeHandle(ref, () => ({
nativeElement: rootRef.current,
diff --git a/src/hooks/useTimeInfo.ts b/src/hooks/useTimeInfo.ts
index 61a2846ef..10cd945df 100644
--- a/src/hooks/useTimeInfo.ts
+++ b/src/hooks/useTimeInfo.ts
@@ -6,7 +6,7 @@ import { findValidateTime } from '../PickerPanel/TimePanel/TimePanelBody/util';
import { leftPad } from '../utils/miscUtil';
export type Unit = {
- label: React.ReactText;
+ label: string | number;
value: ValueType;
disabled?: boolean;
};
diff --git a/tests/__snapshots__/panel.spec.tsx.snap b/tests/__snapshots__/panel.spec.tsx.snap
index 34125971b..5e28ffa9a 100644
--- a/tests/__snapshots__/panel.spec.tsx.snap
+++ b/tests/__snapshots__/panel.spec.tsx.snap
@@ -1,4 +1,4 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Picker.Panel append cell with cellRender in date 1`] = `
diff --git a/tests/__snapshots__/picker.spec.tsx.snap b/tests/__snapshots__/picker.spec.tsx.snap
index 10e4195bb..4b1c35f6e 100644
--- a/tests/__snapshots__/picker.spec.tsx.snap
+++ b/tests/__snapshots__/picker.spec.tsx.snap
@@ -1,4 +1,4 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
+// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`Picker.Basic icon 1`] = `
diff --git a/tests/disabledTime.spec.tsx b/tests/disabledTime.spec.tsx
index a6d0ad97d..a3872ec08 100644
--- a/tests/disabledTime.spec.tsx
+++ b/tests/disabledTime.spec.tsx
@@ -10,6 +10,7 @@ import {
isSame,
openPicker,
selectCell,
+ waitFakeTimer,
} from './util/commonUtil';
const fakeTime = getDay('1990-09-03 00:00:00').valueOf();
@@ -69,7 +70,7 @@ describe('Picker.DisabledTime', () => {
).toHaveLength(2);
});
- it('disabledTime', () => {
+ it('disabledTime', async () => {
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */
const disabledTime = jest.fn((_: Dayjs | null, __: 'start' | 'end') => {
return {
@@ -93,16 +94,19 @@ describe('Picker.DisabledTime', () => {
expect(isSame(disabledTime.mock.calls[0][0], '1989-11-28')).toBeTruthy();
expect(disabledTime.mock.calls[0][1]).toEqual('start');
closePicker(container);
+ await waitFakeTimer();
// End
disabledTime.mockClear();
openPicker(container, 1);
+ await waitFakeTimer();
expect(
document.querySelector('.rc-picker-time-panel-column').querySelectorAll('li')[11],
).toHaveClass('rc-picker-time-panel-cell-disabled');
- expect(isSame(disabledTime.mock.calls[0][0], '1990-09-03')).toBeTruthy();
- expect(disabledTime.mock.calls[0][1]).toEqual('end');
+ const endCall = disabledTime.mock.calls.find(([, type]) => type === 'end');
+ expect(isSame(endCall![0], '1990-09-03')).toBeTruthy();
+ expect(endCall![1]).toEqual('end');
closePicker(container, 1);
});
diff --git a/tests/panel.spec.tsx b/tests/panel.spec.tsx
index 7bd3dadf9..813050a4c 100644
--- a/tests/panel.spec.tsx
+++ b/tests/panel.spec.tsx
@@ -591,16 +591,18 @@ describe('Picker.Panel', () => {
const App = () => (
- React.cloneElement(
- info.originNode,
+ cellRender={(current, info) => {
+ const originNode = info.originNode as React.ReactElement;
+
+ return React.cloneElement(
+ originNode,
{
- ...info.originNode.props,
- className: `${info.originNode.props.className} customInner`,
+ ...originNode.props,
+ className: `${originNode.props.className} customInner`,
},
{getCurText(picker, current)}
,
- )
- }
+ );
+ }}
/>
);
diff --git a/tsconfig.json b/tsconfig.json
index 226872a37..c30230435 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,20 +1,21 @@
{
"compilerOptions": {
"target": "esnext",
- "moduleResolution": "node",
- "baseUrl": "./",
+ "moduleResolution": "bundler",
"jsx": "react",
"declaration": true,
"skipLibCheck": true,
"esModuleInterop": true,
"paths": {
- "@/*": ["src/*"],
- "@@/*": [".dumi/tmp/*"],
- "@rc-component/picker": ["src/index.tsx"]
+ "@/*": ["./src/*"],
+ "@@/*": ["./.dumi/tmp/*"],
+ "@rc-component/picker": ["./src/index.tsx"]
},
- "ignoreDeprecations": "5.0"
+ "strict": false,
+ "module": "ESNext"
},
"include": [
+ "global.d.ts",
".dumirc.ts",
".fatherrc.ts",
"src/**/*.ts",