diff --git a/src/KeyCode.js b/src/KeyCode.ts similarity index 77% rename from src/KeyCode.js rename to src/KeyCode.ts index 69b0c880..a85d600c 100644 --- a/src/KeyCode.js +++ b/src/KeyCode.ts @@ -1,4 +1,4 @@ -export default { +const KeyCode = { ZERO: 48, NINE: 57, @@ -12,3 +12,5 @@ export default { ARROW_UP: 38, ARROW_DOWN: 40, }; + +export default KeyCode; \ No newline at end of file diff --git a/src/Options.jsx b/src/Options.tsx similarity index 76% rename from src/Options.jsx rename to src/Options.tsx index c3249895..f89b17dc 100644 --- a/src/Options.jsx +++ b/src/Options.tsx @@ -2,7 +2,28 @@ import React from 'react'; import KEYCODE from './KeyCode'; -class Options extends React.Component { +interface Props { + disabled: boolean; + locale: any; + rootPrefixCls: string; + selectPrefixCls: string; + current: number; + pageSize: number; + pageSizeOptions: (string | number)[]; + goButton: boolean | string; + changeSize: (size: number) => void; + quickGo: (value: number) => void; + buildOptionText?: (value: string | number) => string; + selectComponentClass: React.ComponentType & { + Option?: React.ComponentType; + }; +} + +interface State { + goInputText: string; +} + +class Options extends React.Component { static defaultProps = { pageSizeOptions: ['10', '20', '50', '100'], }; @@ -11,33 +32,32 @@ class Options extends React.Component { goInputText: '', }; - getValidValue() { + getValidValue = () => { const { goInputText } = this.state; // eslint-disable-next-line no-restricted-globals - return !goInputText || isNaN(goInputText) ? undefined : Number(goInputText); - } + return !goInputText || Number.isNaN(goInputText) + ? undefined + : Number(goInputText); + }; - buildOptionText = (value) => `${value} ${this.props.locale.items_per_page}`; + buildOptionText = (value: string) => + `${value} ${this.props.locale.items_per_page}`; - changeSize = (value) => { + changeSize = (value: number) => { this.props.changeSize(Number(value)); }; - handleChange = (e) => { - this.setState({ - goInputText: e.target.value, - }); + handleChange = (e: React.ChangeEvent) => { + this.setState({ goInputText: e.target.value }); }; - handleBlur = (e) => { + handleBlur = (e: React.FocusEvent) => { const { goButton, quickGo, rootPrefixCls } = this.props; const { goInputText } = this.state; if (goButton || goInputText === '') { return; } - this.setState({ - goInputText: '', - }); + this.setState({ goInputText: '' }); if ( e.relatedTarget && (e.relatedTarget.className.indexOf(`${rootPrefixCls}-item-link`) >= 0 || @@ -48,15 +68,13 @@ class Options extends React.Component { quickGo(this.getValidValue()); }; - go = (e) => { + go = (e: any) => { const { goInputText } = this.state; if (goInputText === '') { return; } if (e.keyCode === KEYCODE.ENTER || e.type === 'click') { - this.setState({ - goInputText: '', - }); + this.setState({ goInputText: '' }); this.props.quickGo(this.getValidValue()); } }; @@ -72,9 +90,9 @@ class Options extends React.Component { } return pageSizeOptions.concat([pageSize.toString()]).sort((a, b) => { // eslint-disable-next-line no-restricted-globals - const numberA = isNaN(Number(a)) ? 0 : Number(a); + const numberA = Number.isNaN(Number(a)) ? 0 : Number(a); // eslint-disable-next-line no-restricted-globals - const numberB = isNaN(Number(b)) ? 0 : Number(b); + const numberB = Number.isNaN(Number(b)) ? 0 : Number(b); return numberA - numberB; }); } diff --git a/src/Pager.jsx b/src/Pager.jsx deleted file mode 100644 index 1a7a15ec..00000000 --- a/src/Pager.jsx +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint react/prop-types: 0 */ -import React from 'react'; -import classNames from 'classnames'; - -const Pager = (props) => { - const prefixCls = `${props.rootPrefixCls}-item`; - const cls = classNames(prefixCls, `${prefixCls}-${props.page}`, { - [`${prefixCls}-active`]: props.active, - [`${prefixCls}-disabled`]: !props.page, - [props.className]: !!props.className, - }); - - const handleClick = () => { - props.onClick(props.page); - }; - - const handleKeyPress = (e) => { - props.onKeyPress(e, props.onClick, props.page); - }; - - return ( -
  • - {props.itemRender(props.page, 'page', {props.page})} -
  • - ); -}; - -export default Pager; diff --git a/src/Pager.tsx b/src/Pager.tsx new file mode 100644 index 00000000..ed9475cf --- /dev/null +++ b/src/Pager.tsx @@ -0,0 +1,65 @@ +/* eslint react/prop-types: 0 */ +import classNames from 'classnames'; +import React from 'react'; + +interface Props { + last?: boolean; + locale?: any; + rootPrefixCls: string; + page: number; + active?: boolean; + className?: string; + showTitle: boolean; + onClick?: (page: number) => void; + onKeyPress?: ( + e: React.KeyboardEvent, + onClick: Props['onClick'], + page: Props['page'], + ) => void; + itemRender?: ( + page: number, + type: 'page' | 'prev' | 'next' | 'jump-prev' | 'jump-next', + element: React.ReactNode, + ) => React.ReactNode; +} + +const Pager: React.FC = (props) => { + const { + rootPrefixCls, + page, + active, + className, + showTitle, + onClick, + onKeyPress, + itemRender, + } = props; + const prefixCls = `${rootPrefixCls}-item`; + const cls = classNames(prefixCls, `${prefixCls}-${page}`, { + [`${prefixCls}-active`]: active, + [`${prefixCls}-disabled`]: !page, + [props.className]: className, + }); + + const handleClick = () => { + onClick(page); + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + onKeyPress(e, onClick, page); + }; + + return ( +
  • + {itemRender(page, 'page', {page})} +
  • + ); +}; + +export default Pager; diff --git a/src/Pagination.jsx b/src/Pagination.tsx similarity index 79% rename from src/Pagination.jsx rename to src/Pagination.tsx index dc0e6b94..45f992b2 100644 --- a/src/Pagination.jsx +++ b/src/Pagination.tsx @@ -1,34 +1,108 @@ /* eslint react/prop-types: 0 */ -import React, { cloneElement, isValidElement } from 'react'; import classNames from 'classnames'; -import Pager from './Pager'; -import Options from './Options'; +import React, { cloneElement, isValidElement } from 'react'; import KEYCODE from './KeyCode'; import LOCALE from './locale/zh_CN'; +import Options from './Options'; +import Pager from './Pager'; + +export interface PaginationLocale { + // Options.jsx + items_per_page?: string; + jump_to?: string; + jump_to_confirm?: string; + page?: string; + + // Pagination.jsx + prev_page?: string; + next_page?: string; + prev_5?: string; + next_5?: string; + prev_3?: string; + next_3?: string; +} + +export interface PaginationData { + className: string; + selectPrefixCls: string; + prefixCls: string; + pageSizeOptions: string[] | number[]; + + current: number; + defaultCurrent: number; + total: number; + totalBoundaryShowSizeChanger?: number; + pageSize: number; + defaultPageSize: number; + + hideOnSinglePage: boolean; + showSizeChanger: boolean; + showLessItems: boolean; + showPrevNextJumpers: boolean; + showQuickJumper: boolean | object; + showTitle: boolean; + simple: boolean; + disabled: boolean; + + locale: PaginationLocale; + + style: React.CSSProperties; + + selectComponentClass: React.ComponentType; + prevIcon: React.ComponentType | React.ReactNode; + nextIcon: React.ComponentType | React.ReactNode; + jumpPrevIcon: React.ComponentType | React.ReactNode; + jumpNextIcon: React.ComponentType | React.ReactNode; +} + +export interface PaginationProps extends Partial { + onChange?: (page: number, pageSize: number) => void; + onShowSizeChange?: (current: number, size: number) => void; + itemRender?: ( + page: number, + type: 'page' | 'prev' | 'next' | 'jump-prev' | 'jump-next', + element: React.ReactNode, + ) => React.ReactNode; + showTotal?: (total: number, range: [number, number]) => React.ReactNode; +} + +interface PaginationState { + current: number; + currentInputValue: number; + pageSize: number; +} function noop() {} -function isInteger(v) { +function isInteger(v: number) { const value = Number(v); return ( // eslint-disable-next-line no-restricted-globals typeof value === 'number' && - !isNaN(value) && + !Number.isNaN(value) && isFinite(value) && Math.floor(value) === value ); } -function defaultItemRender(page, type, element) { +const defaultItemRender: PaginationProps['itemRender'] = ( + page, + type, + element, +) => { return element; -} +}; -function calculatePage(p, state, props) { +function calculatePage( + p: number | undefined, + state: PaginationState, + props: PaginationProps, +) { const pageSize = typeof p === 'undefined' ? state.pageSize : p; return Math.floor((props.total - 1) / pageSize) + 1; } -class Pagination extends React.Component { +class Pagination extends React.Component { static defaultProps = { defaultCurrent: 1, total: 0, @@ -49,8 +123,8 @@ class Pagination extends React.Component { itemRender: defaultItemRender, totalBoundaryShowSizeChanger: 50, }; - - constructor(props) { + paginationNode = React.createRef(); + constructor(props: PaginationProps) { super(props); const hasOnChange = props.onChange !== noop; @@ -83,22 +157,29 @@ class Pagination extends React.Component { }; } - componentDidUpdate(prevProps, prevState) { + componentDidUpdate(_: PaginationProps, prevState: PaginationState) { // When current page change, fix focused style of prev item // A hacky solution of https://github.com/ant-design/ant-design/issues/8948 const { prefixCls } = this.props; - if (prevState.current !== this.state.current && this.paginationNode) { - const lastCurrentNode = this.paginationNode.querySelector( - `.${prefixCls}-item-${prevState.current}`, - ); + if ( + prevState.current !== this.state.current && + this.paginationNode.current + ) { + const lastCurrentNode = + this.paginationNode.current.querySelector( + `.${prefixCls}-item-${prevState.current}`, + ); if (lastCurrentNode && document.activeElement === lastCurrentNode) { - lastCurrentNode.blur(); + lastCurrentNode?.blur?.(); } } } - static getDerivedStateFromProps(props, prevState) { - const newState = {}; + static getDerivedStateFromProps( + props: PaginationProps, + prevState: PaginationState, + ) { + const newState: Partial = {}; if ('current' in props) { newState.current = props.current; @@ -132,12 +213,10 @@ class Pagination extends React.Component { this.state.current + (this.props.showLessItems ? 3 : 5), ); - /** - * computed icon node that need to be rendered. - * @param {React.ReactNode | React.ComponentType} icon received icon. - * @returns {React.ReactNode} - */ - getItemIcon = (icon, label) => { + getItemIcon = ( + icon: React.ReactNode | React.ComponentType, + label: string, + ) => { const { prefixCls } = this.props; let iconNode = icon || (