diff --git a/.dumirc.ts b/.dumirc.ts index b50763f..498e3b6 100644 --- a/.dumirc.ts +++ b/.dumirc.ts @@ -1,5 +1,8 @@ import { defineConfig } from 'dumi'; +const basePath = process.env.GH_PAGES ? '/input/' : '/'; +const publicPath = process.env.GH_PAGES ? '/input/' : '/'; + export default defineConfig({ mfsu: false, favicons: ['https://avatars0.githubusercontent.com/u/9441414?s=200&v=4'], @@ -7,4 +10,7 @@ export default defineConfig({ name: 'Input', logo: 'https://avatars0.githubusercontent.com/u/9441414?s=200&v=4', }, + outputPath: '.doc', + base: basePath, + publicPath, }); diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 432a3fb..5735e2d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,114 +1,6 @@ -name: CI - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - +name: βœ… test +on: [push, pull_request] jobs: - setup: - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@master - - - uses: actions/setup-node@v1 - with: - node-version: '12' - - - name: cache package-lock.json - uses: actions/cache@v2 - with: - path: package-temp-dir - key: lock-${{ github.sha }} - - - name: create package-lock.json - run: npm i --package-lock-only - - - name: hack for singe file - run: | - if [ ! -d "package-temp-dir" ]; then - mkdir package-temp-dir - fi - cp package-lock.json package-temp-dir - - - name: cache node_modules - id: node_modules_cache_id - uses: actions/cache@v2 - with: - path: node_modules - key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }} - - - name: install - if: steps.node_modules_cache_id.outputs.cache-hit != 'true' - run: npm ci - - lint: - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@master - - - name: restore cache from package-lock.json - uses: actions/cache@v2 - with: - path: package-temp-dir - key: lock-${{ github.sha }} - - - name: restore cache from node_modules - uses: actions/cache@v2 - with: - path: node_modules - key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }} - - - name: lint - run: npm run lint - - needs: setup - - compile: - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@master - - - name: restore cache from package-lock.json - uses: actions/cache@v2 - with: - path: package-temp-dir - key: lock-${{ github.sha }} - - - name: restore cache from node_modules - uses: actions/cache@v2 - with: - path: node_modules - key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }} - - - name: compile - run: npm run compile - - needs: setup - - coverage: - runs-on: ubuntu-latest - steps: - - name: checkout - uses: actions/checkout@master - - - name: restore cache from package-lock.json - uses: actions/cache@v2 - with: - path: package-temp-dir - key: lock-${{ github.sha }} - - - name: restore cache from node_modules - uses: actions/cache@v2 - with: - path: node_modules - key: node_modules-${{ hashFiles('**/package-temp-dir/package-lock.json') }} - - - name: coverage - run: npm test -- --coverage && bash <(curl -s https://codecov.io/bash) - - needs: setup + test: + uses: react-component/rc-test/.github/workflows/test.yml@main + secrets: inherit \ No newline at end of file diff --git a/.gitignore b/.gitignore index f1b640b..d099b0d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ dist/ # dumi .dumi/tmp .dumi/tmp-production +.node +bun.lockb diff --git a/README.md b/README.md index 88daefe..e1bb3f6 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,30 @@ # rc-input ⌨️ -[![NPM version][npm-image]][npm-url] [![npm download][download-image]][download-url] [![dumi](https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square)](https://github.com/umijs/dumi) [![build status][github-actions-image]][github-actions-url] [![Codecov][codecov-image]][codecov-url] [![Dependencies][david-image]](david-url) [![DevDependencies][david-dev-image]][david-dev-url] [![bundle size][bundlephobia-image]][bundlephobia-url] +[![NPM version][npm-image]][npm-url] +[![npm download][download-image]][download-url] +[![build status][github-actions-image]][github-actions-url] +[![Codecov][codecov-image]][codecov-url] +[![bundle size][bundlephobia-image]][bundlephobia-url] +[![dumi][dumi-image]][dumi-url] [npm-image]: http://img.shields.io/npm/v/rc-input.svg?style=flat-square -[npm-url]: http://npmjs.org/package/rc-input +[npm-url]: http://npmjs.org/package/rc-select +[travis-image]: https://img.shields.io/travis/react-component/input/master?style=flat-square +[travis-url]: https://travis-ci.com/react-component/input [github-actions-image]: https://github.com/react-component/input/workflows/CI/badge.svg [github-actions-url]: https://github.com/react-component/input/actions [codecov-image]: https://img.shields.io/codecov/c/github/react-component/input/master.svg?style=flat-square -[codecov-url]: https://codecov.io/gh/react-component/input/branch/master +[codecov-url]: https://app.codecov.io/gh/react-component/input [david-url]: https://david-dm.org/react-component/input [david-image]: https://david-dm.org/react-component/input/status.svg?style=flat-square [david-dev-url]: https://david-dm.org/react-component/input?type=dev [david-dev-image]: https://david-dm.org/react-component/input/dev-status.svg?style=flat-square -[download-image]: https://img.shields.io/npm/dm/rc-input.svg?style=flat-square -[download-url]: https://npmjs.org/package/rc-input -[bundlephobia-url]: https://bundlephobia.com/result?p=rc-input -[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/rc-input +[download-image]: https://img.shields.io/npm/dm/rc-select.svg?style=flat-square +[download-url]: https://npmjs.org/package/rc-select +[bundlephobia-url]: https://bundlephobia.com/package/rc-select +[bundlephobia-image]: https://badgen.net/bundlephobia/minzip/rc-select +[dumi-url]: https://github.com/umijs/dumi +[dumi-image]: https://img.shields.io/badge/docs%20by-dumi-blue?style=flat-square ## Install diff --git a/assets/index.less b/assets/index.less index cd89f8f..afac539 100644 --- a/assets/index.less +++ b/assets/index.less @@ -1,4 +1,8 @@ .rc-input { + &-out-of-range { + color: red; + } + &-affix-wrapper { padding: 2px 8px; overflow: hidden; diff --git a/bunfig.toml b/bunfig.toml new file mode 100644 index 0000000..80d57b6 --- /dev/null +++ b/bunfig.toml @@ -0,0 +1,2 @@ +[install] +peer = false \ No newline at end of file diff --git a/docs/examples/addon.tsx b/docs/examples/addon.tsx index f461ce5..7f9b884 100644 --- a/docs/examples/addon.tsx +++ b/docs/examples/addon.tsx @@ -1,7 +1,7 @@ +import Input from 'rc-input'; import type { FC } from 'react'; import React from 'react'; import '../../assets/index.less'; -import Input from 'rc-input'; const Demo: FC = () => { return ( diff --git a/docs/examples/show-count.tsx b/docs/examples/show-count.tsx index 42d590e..8d1be49 100644 --- a/docs/examples/show-count.tsx +++ b/docs/examples/show-count.tsx @@ -1,10 +1,68 @@ +import Input from 'rc-input'; import type { FC } from 'react'; import React from 'react'; import '../../assets/index.less'; -import Input from 'rc-input'; + +const sharedHeadStyle: React.CSSProperties = { + margin: 0, + padding: 0, +}; const Demo: FC = () => { - return ; + return ( +
+

Native

+ + +

Count

+

Only Max

+ +

Customize strategy

+ [...new Intl.Segmenter().segment(val)].length, + }} + /> +

Customize exceedFormatter

+ { + const segments = [...new Intl.Segmenter().segment(val)]; + + return segments + .filter((seg) => seg.index + seg.segment.length <= max) + .map((seg) => seg.segment) + .join(''); + }, + }} + /> +
+ ); }; export default Demo; diff --git a/package.json b/package.json index 47d7ea6..d37acc0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rc-input", - "version": "1.1.0", + "version": "1.6.4", "description": "React input component", "keywords": [ "react", @@ -32,15 +32,15 @@ "docs:build": "dumi build", "docs:deploy": "gh-pages -d .doc", "compile": "father build && lessc assets/index.less assets/index.css", - "gh-pages": "npm run docs:build && npm run docs:deploy", + "gh-pages": "GH_PAGES=1 npm run docs:build && npm run docs:deploy", "prepublishOnly": "npm run compile && np --yolo --no-publish", "postpublish": "npm run gh-pages", "lint": "eslint src/ --ext .ts,.tsx,.jsx,.js,.md", "prettier": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"", "pretty-quick": "pretty-quick", "lint-staged": "lint-staged", - "test": "umi-test test", - "coverage": "father test --coverage", + "test": "rc-test", + "coverage": "rc-test --coverage", "prepare": "husky install" }, "dependencies": { @@ -61,7 +61,7 @@ "cross-env": "^7.0.2", "dumi": "^2.1.14", "eslint": "^7.0.0", - "father": "^4", + "father": "^4.3.7", "gh-pages": "^3.1.0", "husky": "^8.0.1", "less": "^3.10.3", @@ -70,10 +70,10 @@ "prettier": "^2.0.5", "pretty-quick": "^3.0.0", "rc-dialog": "^9.1.0", + "rc-test": "^7.0.15", "react": "^18.0.0", "react-dom": "^18.0.0", - "typescript": "^4.0.5", - "umi-test": "^1.9.7" + "typescript": "^4.0.5" }, "peerDependencies": { "react": ">=16.0.0", diff --git a/src/BaseInput.tsx b/src/BaseInput.tsx index f183df7..5d18647 100644 --- a/src/BaseInput.tsx +++ b/src/BaseInput.tsx @@ -1,12 +1,18 @@ import clsx from 'classnames'; -import type { FC, ReactElement } from 'react'; +import type { ReactElement, ReactNode } from 'react'; import React, { cloneElement, useRef } from 'react'; import type { BaseInputProps } from './interface'; import { hasAddon, hasPrefixSuffix } from './utils/commonUtils'; -const BaseInput: FC = (props) => { +export interface HolderRef { + /** Provider holder ref. Will return `null` if not wrap anything */ + nativeElement: HTMLElement | null; +} + +const BaseInput = React.forwardRef((props, ref) => { const { - inputElement, + inputElement: inputEl, + children, prefixCls, prefix, suffix, @@ -27,8 +33,11 @@ const BaseInput: FC = (props) => { dataAttrs, styles, components, + onClear, } = props; + const inputElement = children ?? inputEl; + const AffixWrapperComponent = components?.affixWrapper || 'span'; const GroupWrapperComponent = components?.groupWrapper || 'span'; const WrapperComponent = components?.wrapper || 'span'; @@ -42,65 +51,69 @@ const BaseInput: FC = (props) => { } }; - // ================== Clear Icon ================== // - const getClearIcon = () => { - if (!allowClear) { - return null; - } - const needClear = !disabled && !readOnly && value; - const clearIconCls = `${prefixCls}-clear-icon`; - const iconNode = - typeof allowClear === 'object' && allowClear?.clearIcon - ? allowClear.clearIcon - : 'βœ–'; - - return ( - e.preventDefault()} - className={clsx(clearIconCls, { - [`${clearIconCls}-hidden`]: !needClear, - [`${clearIconCls}-has-suffix`]: !!suffix, - })} - role="button" - tabIndex={-1} - > - {iconNode} - - ); - }; + const hasAffix = hasPrefixSuffix(props); let element: ReactElement = cloneElement(inputElement, { value, - hidden, className: - clsx( - inputElement.props?.className, - !hasPrefixSuffix(props) && !hasAddon(props) && className, - ) || null, - style: { - ...inputElement.props?.style, - ...(!hasPrefixSuffix(props) && !hasAddon(props) ? style : {}), - }, + clsx(inputElement.props.className, !hasAffix && classNames?.variant) || + null, }); + // ======================== Ref ======================== // + const groupRef = useRef(null); + + React.useImperativeHandle(ref, () => ({ + nativeElement: groupRef.current || containerRef.current, + })); + // ================== Prefix & Suffix ================== // - if (hasPrefixSuffix(props)) { + if (hasAffix) { + // ================== Clear Icon ================== // + let clearIcon: ReactNode = null; + if (allowClear) { + const needClear = !disabled && !readOnly && value; + const clearIconCls = `${prefixCls}-clear-icon`; + const iconNode = + typeof allowClear === 'object' && allowClear?.clearIcon + ? allowClear.clearIcon + : 'βœ–'; + + clearIcon = ( + { + handleReset?.(event); + onClear?.(); + }} + // Do not trigger onBlur when clear input + // https://github.com/ant-design/ant-design/issues/31200 + onMouseDown={(e) => e.preventDefault()} + className={clsx(clearIconCls, { + [`${clearIconCls}-hidden`]: !needClear, + [`${clearIconCls}-has-suffix`]: !!suffix, + })} + role="button" + tabIndex={-1} + > + {iconNode} + + ); + } + const affixWrapperPrefixCls = `${prefixCls}-affix-wrapper`; const affixWrapperCls = clsx( affixWrapperPrefixCls, { - [`${affixWrapperPrefixCls}-disabled`]: disabled, - [`${affixWrapperPrefixCls}-focused`]: focused, + [`${prefixCls}-disabled`]: disabled, + [`${affixWrapperPrefixCls}-disabled`]: disabled, // Not used, but keep it + [`${affixWrapperPrefixCls}-focused`]: focused, // Not used, but keep it [`${affixWrapperPrefixCls}-readonly`]: readOnly, [`${affixWrapperPrefixCls}-input-with-clear-btn`]: suffix && allowClear && value, }, - !hasAddon(props) && className, classes?.affixWrapper, classNames?.affixWrapper, + classNames?.variant, ); const suffixNode = (suffix || allowClear) && ( @@ -108,7 +121,7 @@ const BaseInput: FC = (props) => { className={clsx(`${prefixCls}-suffix`, classNames?.suffix)} style={styles?.suffix} > - {getClearIcon()} + {clearIcon} {suffix} ); @@ -116,8 +129,7 @@ const BaseInput: FC = (props) => { element = ( ); @@ -143,36 +152,35 @@ const BaseInput: FC = (props) => { if (hasAddon(props)) { const wrapperCls = `${prefixCls}-group`; const addonCls = `${wrapperCls}-addon`; + const groupWrapperCls = `${wrapperCls}-wrapper`; const mergedWrapperClassName = clsx( `${prefixCls}-wrapper`, wrapperCls, classes?.wrapper, + classNames?.wrapper, ); const mergedGroupClassName = clsx( - `${prefixCls}-group-wrapper`, - className, + groupWrapperCls, + { + [`${groupWrapperCls}-disabled`]: disabled, + }, classes?.group, + classNames?.groupWrapper, ); // Need another wrapper for changing display:table to display:inline-block // and put style prop in wrapper - return ( -