diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..6ade1bb --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,20 @@ +const base = require('@umijs/fabric/dist/eslint'); + +module.exports = { + ...base, + rules: { + ...base.rules, + 'import/no-extraneous-dependencies': 0, + 'import/no-named-as-default': 0, + 'no-template-curly-in-string': 0, + 'prefer-promise-reject-errors': 0, + 'react/no-array-index-key': 0, + 'react/require-default-props': 0, + 'react/sort-comp': 0, + 'react/no-find-dom-node': 1, + '@typescript-eslint/no-explicit-any': 0, + 'jsx-a11y/label-has-associated-control': 0, + 'jsx-a11y/label-has-for': 0, + 'no-param-reassign': 0 + }, +}; \ No newline at end of file diff --git a/.fatherrc.js b/.fatherrc.js new file mode 100644 index 0000000..e062744 --- /dev/null +++ b/.fatherrc.js @@ -0,0 +1,10 @@ + +export default { + cjs: 'babel', + esm: { type: 'babel', importLibToEs: true }, + runtimeHelpers: true, + preCommit: { + eslint: true, + prettier: true, + }, +}; \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..c3d6d5c --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,113 @@ +name: CI + +on: + push: + branches: [master, 2.x] + pull_request: + branches: [master] + +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 diff --git a/.gitignore b/.gitignore index 632123d..9de4c8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ +.storybook *.iml *.log -.idea +.idea/ .ipr .iws *~ @@ -12,17 +13,34 @@ Thumbs.db .project .*proj -.svn +.svn/ *.swp *.swo *.pyc *.pyo +.build node_modules .cache -*.css +dist +assets/**/*.css build lib -coverage -dist es -*.lock \ No newline at end of file +coverage +yarn.lock +package-lock.json +.vscode +.doc +# production +/dist +/docs-dist + +# misc +.DS_Store +# umi +.umi +.umi-production +.umi-test +.env.local +src/.umi +QueueAnim_1.jsx \ No newline at end of file diff --git a/.jshintignore b/.jshintignore deleted file mode 100644 index 3c3629e..0000000 --- a/.jshintignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index 997b3f7..0000000 --- a/.jshintrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "node": true, - - "curly": true, - "latedef": true, - "quotmark": true, - "undef": true, - "unused": true, - "trailing": true -} diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..8db6ebd --- /dev/null +++ b/.npmignore @@ -0,0 +1,29 @@ +build/ +*.cfg +nohup.out +*.iml +.idea/ +.ipr +.iws +*~ +~* +*.diff +*.log +*.patch +*.bak +.DS_Store +Thumbs.db +.project +.*proj +.svn/ +*.swp +out/ +.build +node_modules +.cache +examples +tests +src +/index.js +.* +assets/**/*.less \ No newline at end of file diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..b785a08 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,7 @@ +**/*.svg +**/*.ejs +**/*.html +package.json +.umi +.umi-production +.umi-test \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a2957c1..0000000 --- a/.travis.yml +++ /dev/null @@ -1,34 +0,0 @@ -language: node_js - -sudo: false - -notifications: - email: - - 155259966@qq.com - -node_js: -- 10 - -before_install: -- | - if ! git diff --name-only $TRAVIS_COMMIT_RANGE | grep -qvE '(\.md$)|(^(docs|examples))/' - then - echo "Only docs were updated, stopping build process." - exit - fi -script: -- | - if [ "$TEST_TYPE" = test ]; then - npm test - else - npm run $TEST_TYPE - fi -env: - matrix: - - TEST_TYPE=lint - - TEST_TYPE=test - - TEST_TYPE=coverage - -matrix: - allow_failures: - - env: "TEST_TYPE=saucelabs" diff --git a/.umirc.ts b/.umirc.ts new file mode 100644 index 0000000..4dbd287 --- /dev/null +++ b/.umirc.ts @@ -0,0 +1,16 @@ +// more config: https://d.umijs.org/config +import { defineConfig } from 'dumi'; +const path = require('path'); + +export default defineConfig({ + title: 'rc-queue-anim', + favicon: + 'https://zos.alipayobjects.com/rmsportal/HzvPfCGNCtvGrdk.png', + logo: + 'https://zos.alipayobjects.com/rmsportal/TOXWfHIUGHvZIyb.svg', + outputPath: '.doc', + alias: { + 'rc-queue-anim/es': path.join(__dirname, 'src'), + 'rc-queue-anim/lib': path.join(__dirname, 'src'), + }, +}); \ No newline at end of file diff --git a/README.md b/README.md index 4691058..e892330 100644 --- a/README.md +++ b/README.md @@ -1,120 +1 @@ -# rc-queue-anim ---- - -Animate React Component in queue, thanks to [rc-animate](https://github.com/react-component/animate) and [enter-animation](https://github.com/jljsj33/enter-animation). - -[![NPM version][npm-image]][npm-url] -[![build status][travis-image]][travis-url] -[![Test coverage][coveralls-image]][coveralls-url] -[![Total alerts][lgtm-alerts-image]][lgtm-alerts-url] -[![Language grade: JavaScript][lgtm-grade-image]][lgtm-grade-url] -[![node version][node-image]][node-url] -[![npm download][download-image]][download-url] - -[npm-image]: http://img.shields.io/npm/v/rc-queue-anim.svg?style=flat-square -[npm-url]: http://npmjs.org/package/rc-queue-anim -[travis-image]: https://img.shields.io/travis/react-component/queue-anim.svg?style=flat-square -[travis-url]: https://travis-ci.org/react-component/queue-anim -[coveralls-image]: https://img.shields.io/coveralls/react-component/queue-anim.svg?style=flat-square -[coveralls-url]: https://coveralls.io/r/react-component/queue-anim?branch=master -[lgtm-alerts-image]: https://img.shields.io/lgtm/alerts/g/react-component/queue-anim.svg?logo=lgtm&logoWidth=18&style=flat-square -[lgtm-alerts-url]: https://lgtm.com/projects/g/react-component/queue-anim/alerts/ -[lgtm-grade-image]: https://img.shields.io/lgtm/grade/javascript/g/react-component/queue-anim.svg?logo=lgtm&logoWidth=18&style=flat-square -[lgtm-grade-url]: https://lgtm.com/projects/g/react-component/queue-anim/context:javascript -[node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square -[node-url]: http://nodejs.org/download/ -[download-image]: https://img.shields.io/npm/dm/rc-queue-anim.svg?style=flat-square -[download-url]: https://npmjs.org/package/rc-queue-anim - -## Example - -http://react-component.github.io/queue-anim/examples/ - -![](https://t.alipayobjects.com/images/rmsweb/T12PliXjXgXXXXXXXX.gif) - -## Usage - -```js -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; - -ReactDom.render( - -
enter in queue
-
enter in queue
-
enter in queue
-
-, mountNode); -``` - -## Install - -[![rc-queue-anim](https://nodei.co/npm/rc-queue-anim.png)](https://npmjs.org/package/rc-queue-anim) - -## Browser Support - -|![IE](https://github.com/alrra/browser-logos/blob/master/src/edge/edge_48x48.png?raw=true) | ![Chrome](https://github.com/alrra/browser-logos/blob/master/src/chrome/chrome_48x48.png?raw=true) | ![Firefox](https://github.com/alrra/browser-logos/blob/master/src/firefox/firefox_48x48.png?raw=true) | ![Opera](https://github.com/alrra/browser-logos/blob/master/src/opera/opera_48x48.png?raw=true) | ![Safari](https://github.com/alrra/browser-logos/blob/master/src/safari/safari_48x48.png?raw=true)| -| --- | --- | --- | --- | --- | -| IE 10+ ✔ | Chrome 31.0+ ✔ | Firefox 31.0+ ✔ | Opera 30.0+ ✔ | Safari 7.0+ ✔ | - -### 1.7.x add childRefs and currentRef; - -```jsx - { - this.ref = c; - }} - onEnd={() => { - // this..currentRef = - // this..childRefs.a = 1212 - }} -> - 1212 - -``` - -## API - -> You must provide the key attribute for all children of QueueAnim, children would not peform any animation without key. - -| props | type | default | description | -|------------|----------------|---------|----------------| -| type | string / array | `right` | Animation Styles
`alpha` `left` `right` `top` `bottom` `scale` `scaleBig` `scaleX` `scaleY`| -| animConfig | object / array | null | Custom config, See below for more details [animConfig](#animConfig) | -| delay | number / array | 0 | delay of animation | -| duration | number / array | 450 | duration of animation | -| interval | number / array | 100 | interval of duration | -| leaveReverse | boolean | false | reverse animation order at leave | -| ease | string / array | `easeOutQuart` | animation easing config like `'ease'`, `['easeIn', 'easeOut']`, `[[.42,0,.58,1]`, [.42,0,.58,1]]: [more](http://easings.net/en) | -| appear | boolean | true | whether support appear anim | -| component | string / React.Element | `div` | component tag | -| componentProps | Object | null | component is React.Element, component tag props | -| animatingClassName | array | `['queue-anim-entering', 'queue-anim-leaving']` | className to every element of animating | -| forcedReplay | boolean | false | `leave` animation moment trigger `enter`, forced replay. | -| onEnd | function | null | animation end callback({ key, type }), type: `enter` or `leave` | - -> Above props support array format, like `['left', 'top']`, the secord item is leave config. [Demo](http://react-component.github.io/queue-anim/examples/enter-leave.html) - -### animConfig - -**Data fall into three categories:** - -- Custom set start: `{ opacity:[1, 0] }` ; -
default; -
type: `{ opacity: Array }`; -
leave automatic reverse: `{ opacity: Array }`; - -- Custom: `{ opacity: 0 }`; -
Start position is not set。 - -- Array: `[{ opacity:[1, 0] }, { opacity:[1, 0] }]`; -
type: `[{ opacity: Array }, { opacity: Array}]` - -## Development - -``` -npm install -npm start -``` +docs/index.md \ No newline at end of file diff --git a/assets/index.less b/assets/index.less index baa8c77..e69de29 100644 --- a/assets/index.less +++ b/assets/index.less @@ -1 +0,0 @@ -@zero: 0; \ No newline at end of file diff --git a/docs/demo/animating-class.md b/docs/demo/animating-class.md new file mode 100644 index 0000000..77a029a --- /dev/null +++ b/docs/demo/animating-class.md @@ -0,0 +1,10 @@ +--- +title: animating-class +order: 1 +--- + +## animating-class + +进入时样式控制。 + + diff --git a/docs/demo/appear.md b/docs/demo/appear.md new file mode 100644 index 0000000..11abd64 --- /dev/null +++ b/docs/demo/appear.md @@ -0,0 +1,10 @@ +--- +title: appear +order: 1 +--- + +## appear + +进入不需要动画。 + + diff --git a/docs/demo/component.md b/docs/demo/component.md new file mode 100644 index 0000000..8a0ac36 --- /dev/null +++ b/docs/demo/component.md @@ -0,0 +1,10 @@ +--- +title: component +order: 6 +--- + +## component + +自定义 component + + diff --git a/docs/demo/config.md b/docs/demo/config.md new file mode 100644 index 0000000..7fa83f8 --- /dev/null +++ b/docs/demo/config.md @@ -0,0 +1,10 @@ +--- +title: custom-config +order: 4 +--- + +## custom-config + +自定义 interval、delay、duration。 + + diff --git a/docs/demo/custom-anim.md b/docs/demo/custom-anim.md new file mode 100644 index 0000000..a60f22b --- /dev/null +++ b/docs/demo/custom-anim.md @@ -0,0 +1,10 @@ +--- +title: custom-anim +order: 5 +--- + +## custom-ease + +自定义动画。 + + diff --git a/docs/demo/custom-ease.md b/docs/demo/custom-ease.md new file mode 100644 index 0000000..60a47d2 --- /dev/null +++ b/docs/demo/custom-ease.md @@ -0,0 +1,10 @@ +--- +title: custom-ease +order: 5 +--- + +## custom-ease + +自定义缓动。 + + diff --git a/docs/demo/custom-enter-leave.md b/docs/demo/custom-enter-leave.md new file mode 100644 index 0000000..dedf67a --- /dev/null +++ b/docs/demo/custom-enter-leave.md @@ -0,0 +1,10 @@ +--- +title: custom-enter-leave +order: 5 +--- + +## custom-enter-leave + +自定义进出场。 + + diff --git a/docs/demo/custom-param-func.md b/docs/demo/custom-param-func.md new file mode 100644 index 0000000..d48c940 --- /dev/null +++ b/docs/demo/custom-param-func.md @@ -0,0 +1,10 @@ +--- +title: custom-param-func +order: 5 +--- + +## custom-param-func + +自定义参数用 func。 + + diff --git a/docs/demo/doubleUpdate.md b/docs/demo/doubleUpdate.md new file mode 100644 index 0000000..bd1ec52 --- /dev/null +++ b/docs/demo/doubleUpdate.md @@ -0,0 +1,10 @@ +--- +title: doubleUpdate +order: 7 +--- + +## doubleUpdate + +多次刷新 + + diff --git a/docs/demo/dynamic.md b/docs/demo/dynamic.md new file mode 100644 index 0000000..21a038d --- /dev/null +++ b/docs/demo/dynamic.md @@ -0,0 +1,10 @@ +--- +title: dynamic +order: 7 +--- + +## dynamic + +自定义操作 + + diff --git a/docs/demo/monkey-click.md b/docs/demo/monkey-click.md new file mode 100644 index 0000000..45f4251 --- /dev/null +++ b/docs/demo/monkey-click.md @@ -0,0 +1,8 @@ +--- +title: monkey-click +order: 6 +--- + +## monkey-click + + diff --git a/docs/demo/nested.md b/docs/demo/nested.md new file mode 100644 index 0000000..a4c539d --- /dev/null +++ b/docs/demo/nested.md @@ -0,0 +1,8 @@ +--- +title: nested +order: 1 +--- + +## nested + + diff --git a/docs/demo/router.md b/docs/demo/router.md new file mode 100644 index 0000000..b98c9e9 --- /dev/null +++ b/docs/demo/router.md @@ -0,0 +1,9 @@ +--- +title: router +order: 10 +--- + +## router + + + diff --git a/docs/demo/shortcut.md b/docs/demo/shortcut.md new file mode 100644 index 0000000..00840ee --- /dev/null +++ b/docs/demo/shortcut.md @@ -0,0 +1,8 @@ +--- +title: shortcut +order: 1 +--- + +## shortcut + + diff --git a/docs/demo/simple.md b/docs/demo/simple.md new file mode 100644 index 0000000..983f4cb --- /dev/null +++ b/docs/demo/simple.md @@ -0,0 +1,8 @@ +--- +title: simple +order: 0 +--- + +## basic + + diff --git a/docs/demo/switch-forcedReplay.md b/docs/demo/switch-forcedReplay.md new file mode 100644 index 0000000..8f18e83 --- /dev/null +++ b/docs/demo/switch-forcedReplay.md @@ -0,0 +1,8 @@ +--- +title: switch-forcedReplay +order: 11 +--- + +## switch-forcedReplay + + diff --git a/docs/demo/switch.md b/docs/demo/switch.md new file mode 100644 index 0000000..d58aed7 --- /dev/null +++ b/docs/demo/switch.md @@ -0,0 +1,8 @@ +--- +title: switch +order: 11 +--- + +## switch + + diff --git a/docs/demo/timeline.md b/docs/demo/timeline.md new file mode 100644 index 0000000..d6cbb25 --- /dev/null +++ b/docs/demo/timeline.md @@ -0,0 +1,8 @@ +--- +title: timeline +order: 2 +--- + +## timeline + + diff --git a/docs/examples/animating-class.tsx b/docs/examples/animating-class.tsx new file mode 100644 index 0000000..b2c7340 --- /dev/null +++ b/docs/examples/animating-class.tsx @@ -0,0 +1,50 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React, { useState } from 'react'; +import './assets/animating-class.less'; + +const data = [ + { + children: '依次进入1', + key: 1, + }, + { + children: '依次进入2', + key: 2, + }, + { + children: '依次进入3', + key: 3, + }, + { + children: '依次进入4', + key: 4, + }, + { + children: '依次进入5', + key: 5, + }, + { + children: '依次进入6', + key: 6, + }, +]; +export default () => { + const [items, setItems] = useState(data); + return ( +
+ + {items.map((item) => ( +
{item.children}
+ ))} +
+ +
+ ); +}; diff --git a/docs/examples/appear.tsx b/docs/examples/appear.tsx new file mode 100644 index 0000000..de52875 --- /dev/null +++ b/docs/examples/appear.tsx @@ -0,0 +1,30 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React, { useState } from 'react'; + +const child = [ +
依次进入
, +
依次进入
, +
依次进入
, +
依次进入
, +
依次进入
, +]; +export default () => { + const [items, setItems] = useState(child); + return ( +
+ {items} + +
+ ); +}; diff --git a/docs/examples/assets/animating-class.less b/docs/examples/assets/animating-class.less new file mode 100644 index 0000000..3d46dd8 --- /dev/null +++ b/docs/examples/assets/animating-class.less @@ -0,0 +1,7 @@ +.animating .queue-anim-entering { + background: #2db7f5; +} + +.animating .queue-anim-leaving { + background: #f60; +} diff --git a/examples/assets/router.less b/docs/examples/assets/router.less similarity index 84% rename from examples/assets/router.less rename to docs/examples/assets/router.less index 97bb230..6ec6e6a 100644 --- a/examples/assets/router.less +++ b/docs/examples/assets/router.less @@ -1,4 +1,4 @@ -.queue-anim-leaving { +.router-wrapper .queue-anim-leaving { position: absolute; width: 100%; } diff --git a/examples/assets/switch.less b/docs/examples/assets/switch.less similarity index 100% rename from examples/assets/switch.less rename to docs/examples/assets/switch.less diff --git a/docs/examples/component.tsx b/docs/examples/component.tsx new file mode 100644 index 0000000..2c3b39e --- /dev/null +++ b/docs/examples/component.tsx @@ -0,0 +1,35 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React from 'react'; +import 'antd/lib/style/index.less'; +import 'antd/lib/menu/style/index.less'; + +const Comp = React.forwardRef((props: any, ref: any) => { + return ( +
    + {props.children} +
+ ); +}); + +const Child = React.forwardRef((props: any, ref: any) => { + return
  • {props.children}
  • ; +}); + + +export default () => { + return ( + + 依次进入 + 依次进入 + 依次进入 + 依次进入 + 依次进入 + + ); +}; diff --git a/docs/examples/config.tsx b/docs/examples/config.tsx new file mode 100644 index 0000000..aedef00 --- /dev/null +++ b/docs/examples/config.tsx @@ -0,0 +1,13 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React from 'react'; + +export default () => ( + +
    依次进入
    +
    依次进入
    +
    依次进入
    +
    依次进入
    +
    依次进入
    +
    +); diff --git a/examples/custom.js b/docs/examples/custom-anim.tsx similarity index 77% rename from examples/custom.js rename to docs/examples/custom-anim.tsx index 734173f..cd34862 100644 --- a/examples/custom.js +++ b/docs/examples/custom-anim.tsx @@ -1,9 +1,8 @@ /* eslint-disable no-console,react/no-multi-comp */ import QueueAnim from 'rc-queue-anim'; import React from 'react'; -import ReactDom from 'react-dom'; -ReactDom.render(
    +export default () => (
    依次进入
    依次进入
    @@ -11,4 +10,4 @@ ReactDom.render(
    依次进入
    依次进入
    -
    , document.getElementById('__react-content')); +); diff --git a/examples/custom-ease.js b/docs/examples/custom-ease.tsx similarity index 77% rename from examples/custom-ease.js rename to docs/examples/custom-ease.tsx index 42c153c..d9d77e7 100644 --- a/examples/custom-ease.js +++ b/docs/examples/custom-ease.tsx @@ -1,9 +1,8 @@ /* eslint-disable no-console,react/no-multi-comp */ import QueueAnim from 'rc-queue-anim'; import React from 'react'; -import ReactDom from 'react-dom'; -ReactDom.render( +export default () => (
    div 1
    @@ -13,7 +12,12 @@ ReactDom.render(
    div 1

    - +
    div 2
    div 2
    div 2
    @@ -21,4 +25,4 @@ ReactDom.render(
    div 2
    - , document.getElementById('__react-content')); +); diff --git a/docs/examples/custom-enter-leave.tsx b/docs/examples/custom-enter-leave.tsx new file mode 100644 index 0000000..5d62043 --- /dev/null +++ b/docs/examples/custom-enter-leave.tsx @@ -0,0 +1,57 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React, { useState } from 'react'; + +const data = [ + { + children: '依次进入1', + key: 1, + }, + { + children: '依次进入2', + key: 2, + }, + { + children: '依次进入3', + key: 3, + }, + { + children: '依次进入4', + key: 4, + }, + { + children: '依次进入5', + key: 5, + }, + { + children: '依次进入6', + key: 6, + }, +]; + +export default () => { + const [items, setItems] = useState(data); + return ( +
    + + {items.map((item) => ( +
    {item.children}
    + ))} +
    + +
    + ); +}; diff --git a/docs/examples/custom-param-func.tsx b/docs/examples/custom-param-func.tsx new file mode 100644 index 0000000..c8b5a31 --- /dev/null +++ b/docs/examples/custom-param-func.tsx @@ -0,0 +1,63 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React, { useState } from 'react'; + +const animConfigFunc = (e: any) => { + if (e.key === '3') { + return { opacity: [1, 0], translateX: [0, 30] }; + } + return [ + { opacity: [1, 0], translateX: [0, -30] }, + { opacity: [1, 0], translateX: [0, 30] }, + ]; +}; +const durationFunc = (e: any) => { + if (e.key === '3') { + return [1500, 4000]; + } + return 500; +}; +const easeFunc = (e: any) => { + if (e.key === '3') { + return ['easeOutBack', 'easeInBack']; + } + return 'easeInOutQuart'; +}; +const delayFunc = (e: any) => { + if (e.index >= 3) { + return [1500, 0]; + } + return 0; +}; + +export default () => { + const [show, setShow] = useState(true); + return ( +
    + + + {show + ? [ +
    依次进入
    , +
    依次进入
    , +
    改变type
    , +
    依次进入
    , +
    依次进入
    , + ] + : null} +
    +
    + ); +}; diff --git a/examples/dialog-style.html b/docs/examples/dialog-style.html similarity index 100% rename from examples/dialog-style.html rename to docs/examples/dialog-style.html diff --git a/examples/dialog-style.js b/docs/examples/dialog-style.js similarity index 100% rename from examples/dialog-style.js rename to docs/examples/dialog-style.js diff --git a/docs/examples/doubleUpdate.tsx b/docs/examples/doubleUpdate.tsx new file mode 100755 index 0000000..1d5ade0 --- /dev/null +++ b/docs/examples/doubleUpdate.tsx @@ -0,0 +1,98 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React, { useState } from 'react'; + +const data = [ + { + children: '依次进入1', + key: 1, + }, + { + children: '依次进入2', + key: 2, + }, + { + children: '依次进入3', + key: 3, + }, + { + children: '依次进入4', + key: 4, + }, + { + children: '依次进入5', + key: 5, + }, + { + children: '依次进入6', + key: 6, + }, +]; + +export default () => { + const [items, setItems] = useState(data); + + const onSwitch = () => { + if (items.length) { + setItems([]); + } else { + setItems(data); + } + }; + const onRemove = () => { + console.log('remove: 1'); + setItems([]); + setTimeout(() => { + console.log('remove: 2'); + setItems([]); + setTimeout(() => { + console.log('remove: 3'); + setItems([]); + }); + }); + }; + const onExchange = () => { + setItems([ + { + children: '依次进入1', + key: 1, + }, + { + children: '依次进入2', + key: 2, + }, + ]); + setItems([ + { + children: '依次进入1', + key: 1, + }, + { + children: '依次进入2', + key: 2, + }, + ]); + setItems([ + { + children: '依次进入1', + key: 1, + }, + { + children: '依次进入2', + key: 2, + }, + ]); + }; + return ( +
    + + + + + {items.map((item) => ( +
    {item.children}
    + ))} +
    +
    + ); +}; diff --git a/docs/examples/dynamic.tsx b/docs/examples/dynamic.tsx new file mode 100755 index 0000000..c9b651c --- /dev/null +++ b/docs/examples/dynamic.tsx @@ -0,0 +1,130 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim, { IQueueTypeOrArrayOrFunc } from 'rc-queue-anim'; +import React, { useState } from 'react'; + +let index = 7; + +export default () => { + const [items, setItems] = useState([ + { + children: '依次进入1', + key: 1, + }, + { + children: '依次进入2', + key: 2, + }, + { + children: '依次进入3', + key: 3, + }, + { + children: '依次进入4', + key: 4, + }, + { + children: '依次进入5', + key: 5, + }, + { + children: '依次进入6', + key: 6, + }, + ]); + const [type, setType] = useState('left'); + const add = () => { + const newItems = [...items]; + newItems.push({ + children: '新节点', + key: index, + }); + index++; + setItems(newItems); + }; + const addTwo = () => { + const newItems = [...items]; + newItems.push({ + children: '新节点', + key: index, + }); + index++; + newItems.push({ + children: '新节点', + key: index, + }); + index++; + setItems(newItems); + }; + const remove = (key: number, e: any) => { + e.preventDefault(); + const newItems = [...items]; + const target = newItems.filter((c) => c.key === key); + let i: number | undefined; + if (target && target[0]) { + i = newItems.indexOf(target[0]); + } + if (typeof i === 'number' && i >= 0) { + newItems.splice(i, 1); + } + setItems(newItems); + }; + const removeAll = () => { + setItems([]); + }; + const removeAndAdd = () => { + const newItems = [...items]; + newItems.splice(newItems.length - 1, 1); + newItems.push({ + children: `新节点${Date.now()}--${index}`, + key: index, + }); + index++; + setItems(newItems); + }; + const removeAndAddTow = () => { + const newItems = [...items]; + newItems.splice(newItems.length - 1, 1); + newItems.splice(newItems.length - 2, 1); + newItems.push({ + children: `新节点${Date.now()}`, + key: index, + }); + index++; + newItems.unshift({ + children: `新节点${Date.now()}-top`, + key: index, + }); + index++; + setItems(newItems); + }; + const removeTwo = () => { + const newItems = [...items]; + newItems.splice(1, 1); + setItems(newItems); + }; + return ( +
    + + + + + + + + {items.map((item) => ( + + ))} + +
    + ); +}; diff --git a/examples/empty-children.html b/docs/examples/empty-children.html similarity index 100% rename from examples/empty-children.html rename to docs/examples/empty-children.html diff --git a/examples/empty-children.js b/docs/examples/empty-children.js similarity index 100% rename from examples/empty-children.js rename to docs/examples/empty-children.js diff --git a/docs/examples/monkey-click.tsx b/docs/examples/monkey-click.tsx new file mode 100644 index 0000000..75180f5 --- /dev/null +++ b/docs/examples/monkey-click.tsx @@ -0,0 +1,49 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React, { useState } from 'react'; + +const data = [ + { + children: '依次进入1', + key: 1, + }, + { + children: '依次进入2', + key: 2, + }, + { + children: '依次进入3', + key: 3, + }, + { + children: '依次进入4', + key: 4, + }, + { + children: '依次进入5', + key: 5, + }, + { + children: '依次进入6', + key: 6, + }, +]; + +export default () => { + const [show, setShow] = useState(true); + return ( +
    + + {show ? '显示' : '隐藏'} + + {show ? data.map((item) =>
    {item.children}
    ) : null} +
    +
    + ); +}; diff --git a/docs/examples/nested.tsx b/docs/examples/nested.tsx new file mode 100644 index 0000000..26ec228 --- /dev/null +++ b/docs/examples/nested.tsx @@ -0,0 +1,23 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React from 'react'; + +export default () => ( + +
    依次进入
    +
    依次进入
    +
    依次进入
    +
    依次进入
    +
    依次进入
    + +
    依次进入
    +
    依次进入
    +
    依次进入
    + +
    依次进入
    +
    依次进入
    +
    依次进入
    +
    +
    +
    +); diff --git a/examples/router.js b/docs/examples/router.tsx similarity index 64% rename from examples/router.js rename to docs/examples/router.tsx index 3b42ad3..dc80047 100644 --- a/examples/router.js +++ b/docs/examples/router.tsx @@ -2,7 +2,7 @@ import { HashRouter as Router, Route, Link, Redirect } from 'react-router-dom'; import QueueAnim from 'rc-queue-anim'; import React from 'react'; -import ReactDom from 'react-dom'; + import './assets/router.less'; function Home() { @@ -13,9 +13,9 @@ function Home() { ); } -function Page3() { +const Page3 = React.forwardRef((props, ref) => { return ( - +

    A link to page 2 should be active Page3 @@ -42,35 +42,40 @@ function Page3() {

    ); -} +}); -function Page1() { +const Page1 = React.forwardRef((props, ref) => { return ( - +

    Page 1

    A link to page 2 should be active - 依次进场

    + 依次进场 +

    A link to page 2 should be active - 依次进场

    + 依次进场 +

    A link to page 2 should be active - 依次进场

    + 依次进场 +

    A link to page 2 should be active - 依次进场

    + 依次进场 +

    A link to page 2 should be active - 改变样式

    - 1 + 改变样式 +

    +
    ); -} +}); -function Page2() { +const Page2 = React.forwardRef((props, ref) => { return ( - +

    Page 2

    a link to page 1 @@ -90,49 +95,45 @@ function Page2() {

    ); -} +}); - - -class App extends React.Component { - - getChildren = (props) => { - const { location } = { ...props }; +export default class App extends React.Component { + getChildren = (props: any) => { + const { location } = props; const compArray = [ { to: '/home', component: Home, name: '首页' }, { to: '/page1', component: Page1, name: 'Page1' }, { to: '/page2', component: Page2, name: 'Page2' }, ]; - const component = compArray.map(item => { - if (location.pathname === item.to) { - return item.component; - } - return null; - }).filter(item => item)[0]; - const homeRoute = () => ( - - ); + const component = compArray + .map((item) => { + if (location.pathname === item.to) { + return item.component; + } + return null; + }) + .filter((item) => item)[0]; + const homeRoute = () => ; return ( -
    +
    - {compArray.map(item => ({item.name} ))} - - + {compArray.map((item) => ( + + {item.name}  + + ))} + +
    ); - } + }; render() { - return ( - - ); + return ( + + + + ); } } - -ReactDom.render(, document.getElementById('__react-content')); diff --git a/docs/examples/shortcut.tsx b/docs/examples/shortcut.tsx new file mode 100644 index 0000000..e1ddc61 --- /dev/null +++ b/docs/examples/shortcut.tsx @@ -0,0 +1,53 @@ +import QueueAnim, { IQueueTypeOrArrayOrFunc } from 'rc-queue-anim'; +import React, { useState } from 'react'; + +const d = [ +
    + 依次进入 +
    , +
    + 依次进入 +
    , +
    + 依次进入 +
    , +
    + 依次进入 +
    , +
    + 依次进入 +
    , +]; +export default () => { + const [type, setType] = useState('left'); + const [child, setChild] = useState(d); + + return ( +
    + + + {child} + +
    + ); +}; diff --git a/docs/examples/simple.tsx b/docs/examples/simple.tsx new file mode 100644 index 0000000..b4cf450 --- /dev/null +++ b/docs/examples/simple.tsx @@ -0,0 +1,15 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React from 'react'; + +export default () => { + return ( + +
    依次进入
    +
    依次进入
    +
    依次进入
    +
    依次进入
    +
    依次进入
    +
    + ); +}; diff --git a/docs/examples/switch-forcedReplay.tsx b/docs/examples/switch-forcedReplay.tsx new file mode 100644 index 0000000..120100a --- /dev/null +++ b/docs/examples/switch-forcedReplay.tsx @@ -0,0 +1,34 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React, { useState } from 'react'; +import './assets/switch.less'; + +const childrenKey = [{ key: 1 }, { key: 2 }, { key: 3 }, { key: 4 }, { key: 5 }, { key: 6 }]; +export default () => { + const [children, setChildren] = useState(childrenKey); + const onEnter = () => { + setChildren([]); + }; + const onLeave = () => { + setChildren(childrenKey); + }; + const childrenToRender = (children || []).map((item) => { + return
  • ; + }); + return ( +
    +

    鼠标经过当前区域,再移出区域查看

    +
    + + {childrenToRender} + + + {childrenToRender} + + + {childrenToRender} + +
    +
    + ); +}; diff --git a/docs/examples/switch.tsx b/docs/examples/switch.tsx new file mode 100644 index 0000000..82afe5a --- /dev/null +++ b/docs/examples/switch.tsx @@ -0,0 +1,34 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React, { useState } from 'react'; +import './assets/switch.less'; + +const childrenKey = [{ key: 1 }, { key: 2 }, { key: 3 }, { key: 4 }, { key: 5 }, { key: 6 }]; +export default () => { + const [children, setChildren] = useState(childrenKey); + const onEnter = () => { + setChildren([]); + }; + const onLeave = () => { + setChildren(childrenKey); + }; + const childrenToRender = (children || []).map((item) => { + return
  • ; + }); + return ( +
    +

    鼠标经过当前区域,再移出区域查看

    +
    + + {childrenToRender} + + + {childrenToRender} + + + {childrenToRender} + +
    +
    + ); +}; diff --git a/docs/examples/timeline.tsx b/docs/examples/timeline.tsx new file mode 100644 index 0000000..77a5aa1 --- /dev/null +++ b/docs/examples/timeline.tsx @@ -0,0 +1,34 @@ +/* eslint-disable no-console,react/no-multi-comp */ +import QueueAnim from 'rc-queue-anim'; +import React, { useState } from 'react'; + +const children = [ +
    依次进入
    , +
    依次进入
    , +
    依次进入
    , +
    依次进入
    , +]; + +export default () => { + const [show, setShow] = useState(true); + return ( +
    + + + {show ? children : null} + +
    + ); +}; diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..a994ac5 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,120 @@ +# rc-queue-anim +--- + +Animate React Component in queue, thanks to [rc-animate](https://github.com/react-component/animate) and [enter-animation](https://github.com/jljsj33/enter-animation). + +[![NPM version][npm-image]][npm-url] +[![build status][github-actions-image]][github-actions-url] +[![Codecov][codecov-image]][codecov-url] +[![Total alerts][lgtm-alerts-image]][lgtm-alerts-url] +[![Language grade: JavaScript][lgtm-grade-image]][lgtm-grade-url] +[![node version][node-image]][node-url] +[![npm download][download-image]][download-url] + +[npm-image]: http://img.shields.io/npm/v/rc-queue-anim.svg?style=flat-square +[npm-url]: http://npmjs.org/package/rc-queue-anim +[github-actions-image]: https://github.com/react-component/queue-anim/workflows/CI/badge.svg +[github-actions-url]: https://github.com/react-component/queue-anim/actions +[codecov-image]: https://img.shields.io/codecov/c/github/react-component/queue-anim/master.svg?style=flat-square +[codecov-url]: https://codecov.io/gh/react-component/queue-anim/branch/master +[lgtm-alerts-image]: https://img.shields.io/lgtm/alerts/g/react-component/queue-anim.svg?logo=lgtm&logoWidth=18&style=flat-square +[lgtm-alerts-url]: https://lgtm.com/projects/g/react-component/queue-anim/alerts/ +[lgtm-grade-image]: https://img.shields.io/lgtm/grade/javascript/g/react-component/queue-anim.svg?logo=lgtm&logoWidth=18&style=flat-square +[lgtm-grade-url]: https://lgtm.com/projects/g/react-component/queue-anim/context:javascript +[node-image]: https://img.shields.io/badge/node.js-%3E=_0.10-green.svg?style=flat-square +[node-url]: http://nodejs.org/download/ +[download-image]: https://img.shields.io/npm/dm/rc-queue-anim.svg?style=flat-square +[download-url]: https://npmjs.org/package/rc-queue-anim + +## Example + +http://react-component.github.io/queue-anim/examples/ + +![](https://t.alipayobjects.com/images/rmsweb/T12PliXjXgXXXXXXXX.gif) + +## Usage + +```js +import QueueAnim from 'rc-queue-anim'; +import React from 'react'; +import ReactDom from 'react-dom'; + +ReactDom.render( + +
    enter in queue
    +
    enter in queue
    +
    enter in queue
    +
    +, mountNode); +``` + +## Install + +[![rc-queue-anim](https://nodei.co/npm/rc-queue-anim.png)](https://npmjs.org/package/rc-queue-anim) + +## Browser Support + +|![IE](https://github.com/alrra/browser-logos/blob/master/src/edge/edge_48x48.png?raw=true) | ![Chrome](https://github.com/alrra/browser-logos/blob/master/src/chrome/chrome_48x48.png?raw=true) | ![Firefox](https://github.com/alrra/browser-logos/blob/master/src/firefox/firefox_48x48.png?raw=true) | ![Opera](https://github.com/alrra/browser-logos/blob/master/src/opera/opera_48x48.png?raw=true) | ![Safari](https://github.com/alrra/browser-logos/blob/master/src/safari/safari_48x48.png?raw=true)| +| --- | --- | --- | --- | --- | +| IE 10+ ✔ | Chrome 31.0+ ✔ | Firefox 31.0+ ✔ | Opera 30.0+ ✔ | Safari 7.0+ ✔ | + +### 1.7.x add childRefs and currentRef; + +```js + { + this.ref = c; + }} + onEnd={() => { + // this..currentRef = + // this..childRefs.a = 1212 + }} +> + 1212 + +``` + +## API + +> You must provide the key attribute for all children of QueueAnim, children would not peform any animation without key. + +| props | type | default | description | +|------------|----------------|---------|----------------| +| type | string / array | `right` | Animation Styles
    `alpha` `left` `right` `top` `bottom` `scale` `scaleBig` `scaleX` `scaleY`| +| animConfig | object / array | null | Custom config, See below for more details [animConfig](#animConfig) | +| delay | number / array | 0 | delay of animation | +| duration | number / array | 450 | duration of animation | +| interval | number / array | 100 | interval of duration | +| leaveReverse | boolean | false | reverse animation order at leave | +| ease | string / array | `easeOutQuart` | animation easing config like `'ease'`, `['easeIn', 'easeOut']`, `[[.42,0,.58,1]`, [.42,0,.58,1]]: [more](http://easings.net/en) | +| appear | boolean | true | whether support appear anim | +| component | string / React.Element | `div` | component tag | +| componentProps | Object | null | component is React.Element, component tag props | +| animatingClassName | array | `['queue-anim-entering', 'queue-anim-leaving']` | className to every element of animating | +| forcedReplay | boolean | false | `leave` animation moment trigger `enter`, forced replay. | +| onEnd | function | null | animation end callback({ key, type }), type: `enter` or `leave` | + +> Above props support array format, like `['left', 'top']`, the secord item is leave config. [Demo](http://react-component.github.io/queue-anim/examples/enter-leave.html) + +### animConfig + +**Data fall into three categories:** + +- Custom set start: `{ opacity:[1, 0] }` ; +
    default; +
    type: `{ opacity: Array }`; +
    leave automatic reverse: `{ opacity: Array }`; + +- Custom: `{ opacity: 0 }`; +
    Start position is not set。 + +- Array: `[{ opacity:[1, 0] }, { opacity:[1, 0] }]`; +
    type: `[{ opacity: Array }, { opacity: Array}]` + +## Development + +``` +npm install +npm start +``` diff --git a/examples/animating-class.html b/examples/animating-class.html deleted file mode 100644 index e69de29..0000000 diff --git a/examples/animating-class.js b/examples/animating-class.js deleted file mode 100644 index d4f2a3e..0000000 --- a/examples/animating-class.js +++ /dev/null @@ -1,53 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; -import './assets/animating-class.less'; - -class App extends React.Component { - constructor(props) { - super(props); - this.state = { - items: [{ - children: '依次进入1', - key: 1, - }, { - children: '依次进入2', - key: 2, - }, { - children: '依次进入3', - key: 3, - }, { - children: '依次进入4', - key: 4, - }, { - children: '依次进入5', - key: 5, - }, { - children: '依次进入6', - key: 6, - }], - }; - } - - removeAll = () => { - this.setState({ - items: [], - }); - } - - render() { - return ( -
    - - {this.state.items.map((item) =>
    - {item.children} -
    )} -
    - -
    - ); - } -} - -ReactDom.render(, document.getElementById('__react-content')); diff --git a/examples/appear.html b/examples/appear.html deleted file mode 100644 index b3a4252..0000000 --- a/examples/appear.html +++ /dev/null @@ -1 +0,0 @@ -placeholder \ No newline at end of file diff --git a/examples/appear.js b/examples/appear.js deleted file mode 100644 index 5f746d6..0000000 --- a/examples/appear.js +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; - -ReactDom.render( -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    , document.getElementById('__react-content')); diff --git a/examples/assets/animating-class.less b/examples/assets/animating-class.less deleted file mode 100644 index dee2562..0000000 --- a/examples/assets/animating-class.less +++ /dev/null @@ -1,7 +0,0 @@ -.queue-anim-entering { - background: #2db7f5; -} - -.queue-anim-leaving { - background: #f60; -} diff --git a/examples/component.html b/examples/component.html deleted file mode 100644 index b3a4252..0000000 --- a/examples/component.html +++ /dev/null @@ -1 +0,0 @@ -placeholder \ No newline at end of file diff --git a/examples/component.js b/examples/component.js deleted file mode 100644 index 29a6d8a..0000000 --- a/examples/component.js +++ /dev/null @@ -1,45 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; -import Icon from 'antd/lib/icon'; -import Menu from 'antd/lib/menu'; -import 'antd/lib/style/index.less'; -import 'antd/lib/menu/style/index.less'; - -const SubMenu = Menu.SubMenu; -const MenuItemGroup = Menu.ItemGroup; -function Demo() { - return ( - Navigation One}> - - Option 1 - Option 2 - - - Option 3 - Option 4 - - - Navigation Two}> - Option 5 - Option 6 - - Option 7 - Option 8 - - - Navigation Three}> - Option 9 - Option 10 - Option 11 - Option 12 - - ); -} -ReactDom.render(, document.getElementById('__react-content')); diff --git a/examples/config.html b/examples/config.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/config.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/config.js b/examples/config.js deleted file mode 100644 index 83e3730..0000000 --- a/examples/config.js +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; - -ReactDom.render( -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    , document.getElementById('__react-content')); diff --git a/examples/custom-ease.html b/examples/custom-ease.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/custom-ease.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/custom.html b/examples/custom.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/custom.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/doubleUpdate.html b/examples/doubleUpdate.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/doubleUpdate.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/doubleUpdate.js b/examples/doubleUpdate.js deleted file mode 100755 index f8dbcce..0000000 --- a/examples/doubleUpdate.js +++ /dev/null @@ -1,103 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; - -class App extends React.Component { - constructor(props) { - super(props); - this.index = 100; - this.items = [ - { - children: '依次进入1', - key: 1, - }, { - children: '依次进入2', - key: 2, - }, { - children: '依次进入3', - key: 3, - }, { - children: '依次进入4', - key: 4, - }, { - children: '依次进入5', - key: 5, - }, { - children: '依次进入6', - key: 6, - }, - ]; - this.state = { - items: this.items, - type: 'left', - }; - } - - switch = () => { - this.setState(state => ({ - items: state.items.length ? [] : this.items, - })); - } - remove = () => { - console.log('remove: 1'); - this.setState({ items: [] }, () => { - console.log('remove: 2'); - this.setState({ items: [] }, () => { - console.log('remove: 3'); - this.setState({ items: [] }); - }); - }); - } - exchange = () => { - console.log('exchange: 1'); - this.setState({ - items: [{ - children: '依次进入1', - key: 1, - }, { - children: '依次进入2', - key: 2, - }], - }, () => { - console.log('exchange: 2'); - this.setState({ - items: [{ - children: '依次进入1', - key: 1, - }, { - children: '依次进入2', - key: 2, - }], - }, () => { - console.log('exchange: 3'); - this.setState({ - items: [{ - children: '依次进入1', - key: 1, - }, { - children: '依次进入2', - key: 2, - }], - }); - }); - }); - } - - render() { - return ( -
    - - - - - {this.state.items.map((item) => )} - -
    - ); - } -} - -ReactDom.render(, document.getElementById('__react-content')); diff --git a/examples/dynamic.html b/examples/dynamic.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/dynamic.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/dynamic.js b/examples/dynamic.js deleted file mode 100755 index ce3023d..0000000 --- a/examples/dynamic.js +++ /dev/null @@ -1,120 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; - -class App extends React.Component { - constructor(props) { - super(props); - this.index = 100; - this.state = { - items: [{ - children: '依次进入1', - key: 1, - }, { - children: '依次进入2', - key: 2, - }, { - children: '依次进入3', - key: 3, - }, { - children: '依次进入4', - key: 4, - }, { - children: '依次进入5', - key: 5, - }, { - children: '依次进入6', - key: 6, - }], - type: 'left', - }; - } - - add = () => { - const items = this.state.items; - items.push({ - children: '新节点', - key: this.index++, - }); - this.setState({ items }); - } - addTwo = () => { - const items = this.state.items; - items.push({ - children: '新节点', - key: this.index++, - }); - items.push({ - children: '新节点', - key: this.index++, - }); - this.setState({ items }); - } - remove = (key, e) => { - e.preventDefault(); - const items = this.state.items; - const target = items.filter(item => item.key === key); - let index; - if (target && target[0]) { - index = items.indexOf(target[0]); - } - if (index >= 0) { - items.splice(index, 1); - } - this.setState({ items }); - } - removeAll = () => { - this.setState({ - items: [], - }); - } - removeAndAdd = () => { - const items = this.state.items; - items.splice(items.length - 1, 1); - items.push({ - children: `新节点${Date.now()}`, - key: this.index++, - }); - this.setState({ items }); - } - removeAndAddTow = () => { - const items = this.state.items; - items.splice(items.length - 1, 1); - items.splice(items.length - 2, 1); - items.push({ - children: `新节点${Date.now()}`, - key: this.index++, - }); - items.unshift({ - children: `新节点${Date.now()}-top`, - key: this.index++, - }); - this.setState({ items }); - } - removeTwo = () => { - const items = this.state.items; - items.splice(1, 1); - this.setState({ items }); - } - - render() { - return ( -
    - - - - - - - - {this.state.items.map((item) => )} - -
    - ); - } -} - -ReactDom.render(, document.getElementById('__react-content')); diff --git a/examples/enter-leave.html b/examples/enter-leave.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/enter-leave.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/enter-leave.js b/examples/enter-leave.js deleted file mode 100644 index a907d2e..0000000 --- a/examples/enter-leave.js +++ /dev/null @@ -1,55 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; - -class App extends React.Component { - constructor(props) { - super(props); - this.state = { - items: [{ - children: '依次进入1', - key: 1, - }, { - children: '依次进入2', - key: 2, - }, { - children: '依次进入3', - key: 3, - }, { - children: '依次进入4', - key: 4, - }, { - children: '依次进入5', - key: 5, - }, { - children: '依次进入6', - key: 6, - }], - }; - } - - removeAll = () => { - this.setState({ - items: [], - }); - } - - render() { - return ( -
    - - {this.state.items.map((item) =>
    - {item.children} -
    )} -
    - -
    - ); - } -} - -ReactDom.render(, document.getElementById('__react-content')); diff --git a/examples/monkey-click.html b/examples/monkey-click.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/monkey-click.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/monkey-click.js b/examples/monkey-click.js deleted file mode 100644 index dcff407..0000000 --- a/examples/monkey-click.js +++ /dev/null @@ -1,54 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; - -class App extends React.Component { - constructor(props) { - super(props); - this.state = { - show: true, - items: [{ - children: '依次进入1', - key: 1, - }, { - children: '依次进入2', - key: 2, - }, { - children: '依次进入3', - key: 3, - }, { - children: '依次进入4', - key: 4, - }, { - children: '依次进入5', - key: 5, - }, { - children: '依次进入6', - key: 6, - }], - }; - } - - toggle = () => { - this.setState({ - show: !this.state.show, - }); - } - - render() { - return ( -
    - - {this.state.show ? '显示' : '隐藏'} - - {this.state.show ? this.state.items.map((item) =>
    - {item.children} -
    ) : null} -
    -
    - ); - } -} - -ReactDom.render(, document.getElementById('__react-content')); diff --git a/examples/nested.html b/examples/nested.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/nested.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/nested.js b/examples/nested.js deleted file mode 100644 index ec607f9..0000000 --- a/examples/nested.js +++ /dev/null @@ -1,22 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; - -ReactDom.render( -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    - -
    依次进入
    -
    依次进入
    -
    依次进入
    - -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    -
    -
    , document.getElementById('__react-content')); diff --git a/examples/param-func.html b/examples/param-func.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/param-func.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/param-func.js b/examples/param-func.js deleted file mode 100644 index 29311fa..0000000 --- a/examples/param-func.js +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; - -class Page1 extends React.Component { - constructor(props) { - super(props); - this.state = { - show: true, - }; - } - - onClick = () => { - this.setState({ - show: !this.state.show, - }); - } - animConfigFunc = (e) => { - if (e.key === '3') { - return { opacity: [1, 0], translateX: [0, 30] }; - } - return [{ opacity: [1, 0], translateX: [0, -30] }, { opacity: [1, 0], translateX: [0, 30] }]; - } - durationFunc = (e) => { - if (e.key === '3') { - return [1500, 4000]; - } - return 500; - } - easeFunc = (e) => { - if (e.key === '3') { - return ['easeOutBack', 'easeInBack']; - } - return 'easeInOutQuart'; - } - delayFunc = (e) => { - if (e.index >= 3) { - return [1500, 0]; - } - return 0; - } - render() { - return (
    - - - {this.state.show ? [
    依次进入
    , -
    依次进入
    , -
    改变type
    , -
    依次进入
    , -
    依次进入
    ] : null} -
    -
    - ); - } -} - -ReactDom.render(, document.getElementById('__react-content')); diff --git a/examples/router.html b/examples/router.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/router.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/shortcut.html b/examples/shortcut.html deleted file mode 100644 index 48cdce8..0000000 --- a/examples/shortcut.html +++ /dev/null @@ -1 +0,0 @@ -placeholder diff --git a/examples/shortcut.js b/examples/shortcut.js deleted file mode 100644 index 2a66d93..0000000 --- a/examples/shortcut.js +++ /dev/null @@ -1,79 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; - -ReactDom.render(
    -

    left

    - -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    -

    top

    - -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    -

    right (default)

    - -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    -

    bottom

    - -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    -

    alpha

    - -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    -

    scale

    - -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    -

    scaleBig

    - -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    -

    scaleX

    - -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    -

    scaleY

    - -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    -
    , document.getElementById('__react-content')); diff --git a/examples/simple.html b/examples/simple.html deleted file mode 100644 index b3a4252..0000000 --- a/examples/simple.html +++ /dev/null @@ -1 +0,0 @@ -placeholder \ No newline at end of file diff --git a/examples/simple.js b/examples/simple.js deleted file mode 100644 index 4918f48..0000000 --- a/examples/simple.js +++ /dev/null @@ -1,12 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; - -ReactDom.render( -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    依次进入
    -
    , document.getElementById('__react-content')); diff --git a/examples/switch-forcedReplay.html b/examples/switch-forcedReplay.html deleted file mode 100644 index b3a4252..0000000 --- a/examples/switch-forcedReplay.html +++ /dev/null @@ -1 +0,0 @@ -placeholder \ No newline at end of file diff --git a/examples/switch-forcedReplay.js b/examples/switch-forcedReplay.js deleted file mode 100644 index ea8d815..0000000 --- a/examples/switch-forcedReplay.js +++ /dev/null @@ -1,61 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; -import './assets/switch.less'; - -class Demo extends React.Component { - constructor(props) { - super(props); - this.childrenKey = [ - { key: 1 }, - { key: 2 }, - { key: 3 }, - { key: 4 }, - { key: 5 }, - { key: 6 }, - ]; - this.state = { - childrenKey: this.childrenKey, - }; - } - - onEnter = () => { - this.setState({ - childrenKey: null, - }); - }; - - onLeave = () => { - this.setState({ - childrenKey: this.childrenKey, - }); - }; - - getChildren = () => { - return (this.state.childrenKey || []).map(item => { - return (
  • ); - }); - }; - - render() { - const childrenToRender = this.getChildren(); - return (
    -

    鼠标经过当前区域,再移出区域查看

    -

    清除所有还在动画的参素并设置切换时的初始参数

    -
    - - {childrenToRender} - - - {childrenToRender} - - - {childrenToRender} - -
    -
    ); - } -} - -ReactDom.render(, document.getElementById('__react-content')); diff --git a/examples/switch.html b/examples/switch.html deleted file mode 100644 index b3a4252..0000000 --- a/examples/switch.html +++ /dev/null @@ -1 +0,0 @@ -placeholder \ No newline at end of file diff --git a/examples/switch.js b/examples/switch.js deleted file mode 100644 index 111de08..0000000 --- a/examples/switch.js +++ /dev/null @@ -1,60 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; -import './assets/switch.less'; - -class Demo extends React.Component { - constructor(props) { - super(props); - this.childrenKey = [ - { key: 1 }, - { key: 2 }, - { key: 3 }, - { key: 4 }, - { key: 5 }, - { key: 6 }, - ]; - this.state = { - childrenKey: this.childrenKey, - }; - } - - onEnter = () => { - this.setState({ - childrenKey: null, - }); - }; - - onLeave = () => { - this.setState({ - childrenKey: this.childrenKey, - }); - }; - - getChildren = () => { - return (this.state.childrenKey || []).map(item => { - return (
  • ); - }); - }; - - render() { - const childrenToRender = this.getChildren(); - return (
    -

    鼠标经过当前区域,再移出区域查看

    -
    - - {childrenToRender} - - - {childrenToRender} - - - {childrenToRender} - -
    -
    ); - } -} - -ReactDom.render(, document.getElementById('__react-content')); diff --git a/examples/timeline.html b/examples/timeline.html deleted file mode 100644 index b3a4252..0000000 --- a/examples/timeline.html +++ /dev/null @@ -1 +0,0 @@ -placeholder \ No newline at end of file diff --git a/examples/timeline.js b/examples/timeline.js deleted file mode 100644 index a7706dc..0000000 --- a/examples/timeline.js +++ /dev/null @@ -1,42 +0,0 @@ -/* eslint-disable no-console,react/no-multi-comp */ -import QueueAnim from 'rc-queue-anim'; -import React from 'react'; -import ReactDom from 'react-dom'; - -const children = [
    依次进入
    , -
    依次进入
    , -
    依次进入
    , -
    依次进入
    , -]; -class Demo extends React.Component { - constructor(props) { - super(props); - this.state = { - children, - }; - } - - onClick = () => { - if (this.state.children) { - this.setState({ children: null }); - } else { - this.setState({ children }); - } - } - - render() { - return (
    - - - {this.state.children} - -
    ); - } -} -ReactDom.render(, document.getElementById('__react-content')); diff --git a/now.json b/now.json new file mode 100644 index 0000000..d7e4fdb --- /dev/null +++ b/now.json @@ -0,0 +1,11 @@ +{ + "version": 2, + "name": "rc-queue-anim", + "builds": [ + { + "src": "package.json", + "use": "@now/static-build", + "config": { "distDir": ".doc" } + } + ] +} diff --git a/package.json b/package.json index a9a864d..d34b1b4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "rc-queue-anim", - "version": "1.8.5", + "version": "2.0.0-beta.0", "description": "Queue animation component for react", "keywords": [ "react", @@ -19,7 +19,7 @@ "ant-motion" ], "homepage": "https://github.com/react-component/queue-anim", - "author": "afc163@gmail.com", + "author": "155259966@qq.com", "repository": { "type": "git", "url": "https://github.com/react-component/queue-anim.git" @@ -37,58 +37,55 @@ "licenses": "MIT", "main": "./lib/index", "module": "./es/index", - "config": { - "port": 8001, - "entry": { - "rc-queue-anim": [ - "./assets/index.less", - "./src/index.js" - ] - } - }, "scripts": { - "dist": "rc-tools run dist", - "build": "rc-tools run build", - "gh-pages": "rc-tools run gh-pages", - "start": "rc-tools run server", - "compile": "rc-tools run compile --babel-runtime", - "pub": "rc-tools run pub --babel-runtime", - "lint": "rc-tools run lint -fix", - "karma": "rc-test run karma", - "saucelabs": "rc-test run saucelabs", - "test": "rc-test run test", - "prettier": "rc-tools run prettier", - "chrome-test": "rc-test run chrome-test", - "coverage": "rc-test run coverage", - "validate": "npm ls" + "start": "dumi dev", + "docs:build": "dumi build", + "docs:deploy": "gh-pages -d docs-dist", + "compile": "father-build", + "deploy": "npm run docs:build && npm run docs:deploy", + "prettier": "prettier --write \"**/*.{js,jsx,tsx,ts,less,md,json}\"", + "test": "umi-test test", + "test:coverage": "umi-test --coverage", + "prepublishOnly": "npm run compile && np --tag=beta --no-cleanup --yolo --no-publish --any-branch", + "lint": "eslint src/ --fix --ext .tsx,.ts", + "lint:tsc": "tsc -p tsconfig.json --noEmit", + "now-build": "npm run docs:build" }, "devDependencies": { - "@types/react": "^16.0.0", - "antd": "4.x", - "core-js": "^3.2.1", - "expect.js": "0.3.x", - "precommit-hook": "^3.0.0", - "rc-dialog": "~8.4.0", - "rc-test": "6.x", - "rc-tools": "8.x", - "react": "^16.0.0", - "react-dom": "^16.0.0", - "react-router": "~4.3.1", - "react-router-dom": "^4.1.0", - "react-test-renderer": "^16.0.0", - "tslint-config-prettier": "^1.17.0", - "tslint-react": "^5.0.0", - "typescript": "4.x" + "@ant-design/icons": "^4.3.0", + "@types/enzyme": "^3.10.5", + "@types/jest": "^25.2.1", + "@types/lodash": "^4.14.135", + "@types/react": "^16.8.19", + "@types/react-dom": "^16.8.4", + "@umijs/test": "^3.2.28", + "antd": "^4.8.4", + "dumi": "^1.1.19", + "eslint": "^7.14.0", + "father": "^2.22.6", + "father-build": "^1.18.6", + "gh-pages": "^3.1.0", + "husky": "^4.3.0", + "np": "^6.0.3", + "prettier": "^2.1.2", + "react": "^16.9.0", + "react-dom": "^16.9.0", + "regenerator-runtime": "^0.13.7", + "typescript": "^4.0.2" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" }, "dependencies": { - "babel-runtime": "6.x", - "prop-types": "^15.6.0", - "rc-tween-one": "^2.5.0", - "react-lifecycles-compat": "^3.0.4" + "@babel/runtime": "^7.11.1", + "tween-one": "^1.0.52" }, - "types": "index.d.ts", - "pre-commit": [ - "lint", - "test" - ] + "husky": { + "hooks": { + "pre-commit": [ + "npm run lint" + ] + } + } } diff --git a/src/QueueAnim.jsx b/src/QueueAnim.jsx deleted file mode 100644 index c54fab0..0000000 --- a/src/QueueAnim.jsx +++ /dev/null @@ -1,571 +0,0 @@ -import React, { createElement } from 'react'; -import PropTypes from 'prop-types'; -import TweenOne, { ticker } from 'rc-tween-one'; -import { polyfill } from 'react-lifecycles-compat'; - -import { - toArrayChildren, - findChildInChildrenByKey, - windowIsUndefined, - mergeChildren, - transformArguments, - getChildrenFromProps, -} from './utils'; -import AnimTypes from './animTypes'; - -const noop = () => {}; - -const typeDefault = [ - 'displayName', - 'propTypes', - 'getDefaultProps', - 'defaultProps', - 'childContextTypes', - 'contextTypes', - 'contextType', -]; - -class QueueAnim extends React.Component { - static propTypes = { - children: PropTypes.any, - component: PropTypes.any, - componentProps: PropTypes.object, - interval: PropTypes.any, - duration: PropTypes.any, - delay: PropTypes.any, - type: PropTypes.any, - animConfig: PropTypes.any, - ease: PropTypes.any, - leaveReverse: PropTypes.bool, - forcedReplay: PropTypes.bool, - animatingClassName: PropTypes.array, - onEnd: PropTypes.func, - appear: PropTypes.bool, - }; - - static defaultProps = { - component: 'div', - componentProps: {}, - interval: 100, - duration: 450, - delay: 0, - type: 'right', - animConfig: null, - ease: 'easeOutQuart', - leaveReverse: false, - forcedReplay: false, - animatingClassName: ['queue-anim-entering', 'queue-anim-leaving'], - onEnd: noop, - appear: true, - }; - - static getDerivedStateFromProps( - props, - { prevProps, children, childrenShow: prevChildShow, $self }, - ) { - const nextState = { - prevProps: props, - }; - if (prevProps && props !== prevProps) { - const nextChildren = toArrayChildren(props.children).filter(c => c); - let currentChildren = $self.originalChildren.filter(item => item); - if (children.length) { - /** - * 多次刷新处理 - * 如果 state.children 里还有元素,元素还在动画,当前子级加回在出场的子级; - */ - const leaveChild = children.filter( - item => item && $self.keysToLeave.indexOf(item.key) >= 0, - ); - $self.leaveUnfinishedChild = leaveChild - .map(item => { - if ($self.placeholderTimeoutIds[item.key]) { - return item.key; - } - return null; - }) - .filter(c => c); - /** - * 获取 leaveChild 在 state.children 里的序列,再将 leaveChild 和 currentChildren 的重新排序。 - * 避逸 state.children 在 leaveComplete 里没全部完成不触发, - * leaveComplete 里如果动画完成了是会删除 keyToLeave,但 state.children 是在全部出场后才触发清除, - * 所以这里需要处理出场完成的元素做清除。 - */ - const stateChildren = mergeChildren(currentChildren, children); - const currentChild = []; - const childReOrder = child => { - child.forEach(item => { - const order = stateChildren.findIndex(c => c.key === item.key); - if (currentChild.indexOf(item) !== -1) { - return; - } - // -1 不应该出现的情况,直接插入数组后面. - if (order === -1) { - currentChild.push(item); - } else { - currentChild.splice(order, 0, item); - } - }); - }; - childReOrder(leaveChild); - childReOrder(currentChildren); - currentChildren = currentChild.filter(c => c); - } - const newChildren = mergeChildren(currentChildren, nextChildren); - const childrenShow = !newChildren.length ? {} : prevChildShow; - $self.keysToEnterPaused = {}; - const emptyBool = !nextChildren.length && !currentChildren.length && children.length; - /** - * 在出场没结束时,childrenShow 里的值将不会清除。 - * 再触发进场时, childrenShow 里的值是保留着的, 设置了 forcedReplay 将重新播放进场。 - */ - if (!emptyBool) { - // 空子级状态下刷新不做处理 - const nextKeys = nextChildren.map(c => c.key); - $self.keysToLeave.forEach(key => { - // 将所有在出场里的停止掉。避免间隔性出现 - if (nextKeys.indexOf(key) >= 0) { - $self.keysToEnterPaused[key] = true; - currentChildren = currentChildren.filter(item => item.key !== key); - if (props.forcedReplay) { - // 清掉所有出场的。 - delete childrenShow[key]; - } - } - }); - } - - $self.keysToEnter = []; - $self.keysToLeave = []; - - // need render to avoid update - nextState.childrenShow = childrenShow; - nextState.children = newChildren; - - nextChildren.forEach(c => { - if (!c) { - return; - } - const key = c.key; - const hasPrev = findChildInChildrenByKey(currentChildren, key); - if (!hasPrev && key) { - $self.keysToEnter.push(key); - } - }); - - currentChildren.forEach(c => { - if (!c) { - return; - } - const key = c.key; - const hasNext = findChildInChildrenByKey(nextChildren, key); - if (!hasNext && key) { - $self.keysToLeave.push(key); - ticker.clear($self.placeholderTimeoutIds[key]); - delete $self.placeholderTimeoutIds[key]; - } - }); - } - return nextState; - } - constructor(props) { - super(props); - /** - * @param tweenToEnter; - * 记录强制切换时是否需要添加 animation; - * 如 enter 后, leave -> enter,样式是没有发生变化,就不需要添加 animation 属性。 - */ - this.tweenToEnter = {}; - /** - * @param leaveUnfinishedChild; - * 记录多次切换,出场没完成动画的 key。 - */ - this.leaveUnfinishedChild = []; - /** - * @param saveTweenOneTag; - * 记录 TweenOne 标签,在 leaveUnfinishedChild 里使用,残留的元素不需要考虑 props 的变更。 - */ - this.saveTweenOneTag = {}; - /** - * @param childrenShow; - * 记录 animation 里是否需要 startAnim; - * 当前元素是否处在显示状态 - * enterBegin 到 leaveComplete 之前都处于显示状态 - */ - this.childrenShow = {}; - /** - * @param keysToEnter; - * 记录进场的 key; - */ - this.keysToEnter = []; - /** - * @param keysToLeave; - * 记录出场的 key; - */ - this.keysToLeave = []; - /** - * @param keysToEnterPaused; - * 记录在进入时是否处理暂停状态 - */ - this.keysToEnterPaused = {}; - /** - * @param placeholderTimeoutIds; - * 进场时 deley 的 timeout 记录; - */ - this.placeholderTimeoutIds = {}; - /** - * @param childRefs; - * 储存 children 的 ref; - */ - this.childRefs = {}; - /** - * @param currentRef; - * 记录 component 是组件时的 ref; - */ - this.currentRef = null; - // 第一次进入,默认进场 - const children = toArrayChildren(getChildrenFromProps(props)); - const childrenShow = {}; - children.forEach(child => { - if (!child || !child.key) { - return; - } - if (this.props.appear) { - this.keysToEnter.push(child.key); - } else { - childrenShow[child.key] = true; - this.tweenToEnter[child.key] = true; - } - }); - this.originalChildren = toArrayChildren(getChildrenFromProps(props)); - this.state = { - children, - childrenShow, - $self: this, - }; - } - - componentDidMount() { - if (this.props.appear) { - this.componentDidUpdate(); - } - } - - componentDidUpdate() { - this.originalChildren = toArrayChildren(getChildrenFromProps(this.props)); - const keysToEnter = [...this.keysToEnter]; - const keysToLeave = [...this.keysToLeave]; - keysToEnter.forEach(this.performEnter); - keysToLeave.forEach(this.performLeave); - } - - componentWillUnmount() { - Object.keys(this.placeholderTimeoutIds).forEach(key => { - ticker.clear(this.placeholderTimeoutIds[key]); - }); - this.keysToEnter = []; - this.keysToLeave = []; - this.childrenShow = {}; - } - - getTweenType(type, num) { - const data = AnimTypes[type]; - return this.getTweenAnimConfig(data, num); - } - - getTweenSingleConfig = (data, num, enterOrLeave) => { - const obj = {}; - Object.keys(data).forEach(key => { - if (Array.isArray(data[key])) { - obj[key] = data[key][num]; - } else if ((!enterOrLeave && !num) || (enterOrLeave && num)) { - obj[key] = data[key]; - } - }); - return obj; - }; - - getTweenAnimConfig(data, num, enterOrLeave) { - if (Array.isArray(data)) { - return data.map(item => { - return this.getTweenSingleConfig(item, num, enterOrLeave); - }); - } - return this.getTweenSingleConfig(data, num, enterOrLeave); - } - - getTweenData = (key, i, type) => { - const props = this.props; - const enterOrLeave = type === 'enter' ? 0 : 1; - const start = type === 'enter' ? 1 : 0; - const end = type === 'enter' ? 0 : 1; - const animate = this.getAnimData(props, key, i, enterOrLeave, end); - const startAnim = - type === 'enter' && (props.forcedReplay || !this.childrenShow[key]) - ? this.getAnimData(props, key, i, enterOrLeave, start) - : null; - let ease = transformArguments(props.ease, key, i)[enterOrLeave]; - const duration = transformArguments(props.duration, key, i)[enterOrLeave]; - if (Array.isArray(ease)) { - ease = ease.map(num => num * 100); - ease = TweenOne.easing.path( - `M0,100C${ease[0]},${100 - ease[1]},${ease[2]},${100 - ease[3]},100,0`, - { lengthPixel: duration / 16.6667 }, - ); - } - return { startAnim, animate, ease, duration, isArray: Array.isArray(animate) }; - }; - - getTweenSingleData = (startAnim, animate, ease, duration, delay, onStart, onComplete) => { - const startLength = Object.keys(startAnim || {}).length; - const animation = { - onStart, - onComplete, - duration, - delay, - ease, - ...animate, - }; - const startAnimate = startLength ? { duration: 0, ...startAnim } : null; - return { animation, startAnimate }; - }; - - getTweenEnterOrLeaveData = (key, i, delay, type) => { - let animateData = this.getTweenData(key, i, type); - const startAnim = animateData.startAnim; - const animate = animateData.animate; - const onStart = (type === 'enter' ? this.enterBegin : this.leaveBegin).bind(this, key); - const onComplete = (type === 'enter' ? this.enterComplete : this.leaveComplete).bind(this, key); - if (animateData.isArray) { - const length = animate.length - 1; - const animation = []; - const startArray = []; - animate.forEach((leave, ii) => { - const start = startAnim && startAnim[ii]; - const animObj = this.getTweenSingleData( - start, - leave, - animateData.ease, - animateData.duration / length, - !ii ? delay : 0, - !ii ? onStart : null, - ii === length ? onComplete : null, - ); - animation.push(animObj.animation); - if (animObj.startAnimate) { - startArray.push(animObj.startAnimate); - } - }); - return startArray.concat(animation); - } - animateData = this.getTweenSingleData( - startAnim, - animate, - animateData.ease, - animateData.duration, - delay, - onStart, - onComplete, - ); - return [animateData.startAnimate, animateData.animation].filter(item => item); - }; - - getAnimData = (props, key, i, enterOrLeave, startOrEnd) => { - /** - * transformArguments 第一个为 enter, 第二个为 leave; - * getTweenAnimConfig or getTweenType 第一个为到达的位置, 第二个为开始的位置。 - * 用 tween-one 的数组来实现老的动画逻辑。。。 - */ - return props.animConfig - ? this.getTweenAnimConfig( - transformArguments(props.animConfig, key, i)[enterOrLeave], - startOrEnd, - enterOrLeave, - ) - : this.getTweenType(transformArguments(props.type, key, i)[enterOrLeave], startOrEnd); - }; - - getChildrenToRender = child => { - const { forcedReplay, leaveReverse, delay, interval, children } = this.props; - if (!child || !child.key) { - return child; - } - const key = child.key; - if (!this.state.childrenShow[key]) { - return null; - } - let i = this.keysToLeave.indexOf(key); - let animation; - const isFunc = typeof child.type === 'function'; - const forcedJudg = isFunc ? {} : null; - if (isFunc) { - Object.keys(child.type).forEach(name => { - if (typeDefault.indexOf(name) === -1) { - forcedJudg[name] = child.type[name]; - } - }); - } - let ref = () => { - delete this.childRefs[key]; - }; - // 处理出场 - if (i >= 0) { - if (this.leaveUnfinishedChild.indexOf(key) >= 0) { - return this.saveTweenOneTag[key]; - } - const $interval = transformArguments(interval, key, i)[1]; - let $delay = transformArguments(delay, key, i)[1]; - // 减掉 leaveUnfinishedChild 里的个数,因为 leaveUnfinishedChild 是旧的出场,不应该计录在队列里。 - const order = - (leaveReverse ? this.keysToLeave.length - i - 1 : i) - this.leaveUnfinishedChild.length; - $delay = $interval * order + $delay; - animation = this.getTweenEnterOrLeaveData(key, i, $delay, 'leave'); - } else { - // 处理进场; - i = toArrayChildren(children).findIndex(c => c && c.key === key); - ref = c => { - this.childRefs[key] = c && c.currentRef ? c.currentRef : c; - }; - // appear=false 时,设定 childrenShow 和 tweenToEnter 都为 true, 这里不渲染 animation; - if (this.tweenToEnter[key] && !forcedReplay) { - // 如果是已进入的,将直接返回标签。。 - return createElement(TweenOne, { - key, - component: child.type, - forcedJudg, - componentProps: child.props, - ref, - }); - } - if (!this.tweenToEnter[key] || forcedReplay) { - animation = this.getTweenEnterOrLeaveData(key, i, 0, 'enter'); - } - } - const paused = this.keysToEnterPaused[key] && this.keysToLeave.indexOf(key) === -1; - animation = paused ? null : animation; - const tag = createElement(TweenOne, { - key, - component: child.type, - forcedJudg, - componentProps: child.props, - animation, - ref, - }); - this.saveTweenOneTag[key] = tag; - return tag; - }; - - performEnter = (key, i) => { - const interval = transformArguments(this.props.interval, key, i)[0]; - const delay = transformArguments(this.props.delay, key, i)[0]; - this.placeholderTimeoutIds[key] = ticker.timeout( - this.performEnterBegin.bind(this, key), - interval * i + delay, - ); - if (this.keysToEnter.indexOf(key) >= 0) { - this.keysToEnter.splice(this.keysToEnter.indexOf(key), 1); - } - }; - - performEnterBegin = key => { - const childrenShow = this.state.childrenShow; - childrenShow[key] = true; - delete this.keysToEnterPaused[key]; - ticker.clear(this.placeholderTimeoutIds[key]); - delete this.placeholderTimeoutIds[key]; - this.setState({ childrenShow }); - }; - - performLeave = key => { - ticker.clear(this.placeholderTimeoutIds[key]); - delete this.placeholderTimeoutIds[key]; - }; - - enterBegin = (key, e) => { - const elem = e.target; - const animatingClassName = this.props.animatingClassName; - elem.className = elem.className.replace(animatingClassName[1], ''); - if (elem.className.indexOf(animatingClassName[0]) === -1) { - elem.className = `${elem.className} ${animatingClassName[0]}`.trim(); - } - this.childrenShow[key] = true; - }; - - enterComplete = (key, e) => { - if (this.keysToEnterPaused[key] || this.keysToLeave.indexOf(key) >= 0) { - return; - } - const elem = e.target; - elem.className = elem.className.replace(this.props.animatingClassName[0], '').trim(); - this.tweenToEnter[key] = true; - this.props.onEnd({ key, type: 'enter', target: elem }); - }; - - leaveBegin = (key, e) => { - const elem = e.target; - const animatingClassName = this.props.animatingClassName; - elem.className = elem.className.replace(animatingClassName[0], ''); - if (elem.className.indexOf(animatingClassName[1]) === -1) { - elem.className = `${elem.className} ${animatingClassName[1]}`.trim(); - } - delete this.tweenToEnter[key]; - }; - - leaveComplete = (key, e) => { - // 切换时同时触发 onComplete。 手动跳出。。。 - if (toArrayChildren(this.props.children).findIndex(c => c && c.key === key) >= 0) { - return; - } - const childrenShow = this.state.childrenShow; - delete childrenShow[key]; - delete this.saveTweenOneTag[key]; - delete this.childrenShow[key]; - if (this.keysToLeave.indexOf(key) >= 0) { - this.keysToLeave.splice(this.keysToLeave.indexOf(key), 1); - } - const needLeave = this.keysToLeave.some(c => childrenShow[c]); - if (!needLeave) { - const currentChildren = toArrayChildren(getChildrenFromProps(this.props)); - this.setState({ - children: currentChildren, - childrenShow, - }); - } - const elem = e.target; - elem.className = elem.className.replace(this.props.animatingClassName[1], '').trim(); - this.props.onEnd({ key, type: 'leave', target: elem }); - }; - - render() { - const { - component, - componentProps, - interval, - duration, - delay, - type, - animConfig, - ease, - leaveReverse, - animatingClassName, - forcedReplay, - onEnd, - appear, - ...tagProps - } = this.props; - if (windowIsUndefined) { - return createElement(component, { ...tagProps, ...componentProps }, this.props.children); - } - const childrenToRender = toArrayChildren(this.state.children).map(this.getChildrenToRender); - const props = { - ...tagProps, - ...this.props.componentProps, - ref: c => { - this.currentRef = c; - }, - }; - return createElement(this.props.component, props, childrenToRender); - } -} -QueueAnim.isQueueAnim = true; -export default polyfill(QueueAnim); diff --git a/src/QueueAnim.tsx b/src/QueueAnim.tsx new file mode 100644 index 0000000..2ce0b43 --- /dev/null +++ b/src/QueueAnim.tsx @@ -0,0 +1,447 @@ +import { + useRef, + useMemo, + useLayoutEffect, + useEffect, + useState, + createElement, + cloneElement, + forwardRef, +} from 'react'; +import { findDOMNode } from 'react-dom'; +import TweenOne, { Ticker } from 'tween-one'; + +import { + toArrayChildren, + findChildInChildrenByKey, + windowIsUndefined, + mergeChildren, + transformArguments, +} from './utils'; +import AnimTypes from './animTypes'; + +import type { IObject, IProps, IKeys, IQueueType } from './type'; + +const noop = () => {}; + +export default forwardRef((props: IProps, ref: any) => { + const { + component = 'div', + componentProps = {}, + interval = 100, + duration = 450, + delay = 0, + type = 'right', + animConfig = null, + ease = 'easeOutQuart', + leaveReverse = false, + forcedReplay = false, + animatingClassName = ['queue-anim-entering', 'queue-anim-leaving'], + onEnd = noop, + appear = true, + ...tagProps + } = props; + + /** + * @param childrenShow; + * 记录 animation 里是否需要 startAnim; + * 当前元素是否处在显示状态 + * enterBegin 到 leaveComplete 之前都处于显示状态 + */ + const childrenShow = useRef({}); + + /** + * @param keysToEnter; + * 记录进场的 key; + */ + const keysToEnter = useRef([]); + const recordKeysToEnter = useRef([]); + /** + * @param keysToLeave; + * 记录出场的 key; + */ + const keysToLeave = useRef([]); + const recordKeysToLeave = useRef([]); + + /** + * @param placeholderTimeoutIds; + * 进场时 deley 的 timeout 记录; + */ + const placeholderTimeoutIds = useRef({}); + /** + * @param childRefs; + * 储存 children 的 ref; + */ + const childRefs = useRef({}); + /** + * @param recordAnimKeys; + * 记录启动动画 key + */ + const recordAnimKeys = useRef({}); + + /** + * @param recordAnimKeys; + * 记录启动动画 key + */ + const recordTweenKeys = useRef({}); + + /** + * @param oneEnterBool + * 记录第一次进入 + */ + const oneEnterBool = useRef(false); + + const originalChildren = useRef([]); + + const [child, setChild] = useState(); + const [childShow, setChildShow] = useState({}); + + const getTweenSingleConfig = (data: any, num: number, enterOrLeave?: 0 | 1) => { + const obj: IObject = {}; + Object.keys(data).forEach((key) => { + if (Array.isArray(data[key])) { + obj[key] = data[key][num]; + } else if ((!enterOrLeave && !num) || (enterOrLeave && num)) { + obj[key] = data[key]; + } + }); + return obj; + }; + + const getTweenAnimConfig = (data: any, num: number, enterOrLeave?: 0 | 1) => { + if (Array.isArray(data)) { + return data.map((item) => { + return getTweenSingleConfig(item, num, enterOrLeave); + }); + } + return getTweenSingleConfig(data, num, enterOrLeave); + }; + + const getTweenType = ($type: IQueueType, num: number) => { + const data = AnimTypes[$type]; + return getTweenAnimConfig(data, num); + }; + + const getAnimData = (key: string | number, i: number, enterOrLeave: 0 | 1, startOrEnd: 0 | 1) => + /** + * transformArguments 第一个为 enter, 第二个为 leave; + * getTweenAnimConfig or getTweenType 第一个为到达的位置, 第二个为开始的位置。 + * 用 tween-one 的数组来实现老的动画逻辑。。。 + */ + animConfig + ? getTweenAnimConfig( + transformArguments(animConfig, key, i)[enterOrLeave], + startOrEnd, + enterOrLeave, + ) + : getTweenType(transformArguments(type, key, i)[enterOrLeave], startOrEnd); + + const getTweenData = (key: string | number, i: number, $type: string) => { + const enterOrLeave = $type === 'enter' ? 0 : 1; + const start = $type === 'enter' ? 1 : 0; + const end = $type === 'enter' ? 0 : 1; + const animate = getAnimData(key, i, enterOrLeave, end); + const startAnim = + $type === 'enter' && (forcedReplay || !childrenShow.current[key]) + ? getAnimData(key, i, enterOrLeave, start) + : null; + let $ease = transformArguments(ease, key, i)[enterOrLeave]; + const $duration = transformArguments(duration, key, i)[enterOrLeave]; + if (Array.isArray(ease) && (ease.length > 2 || Array.isArray(ease[0]))) { + $ease = $ease.map((num: number) => num * 100); + $ease = `M0,100C${$ease[0]},${100 - $ease[1]},${$ease[2]},${100 - $ease[3]},100,0`; + } + return { + startAnim, + animate, + ease: $ease, + duration: $duration, + }; + }; + + const enterBegin = (key: string | number, e: any) => { + const elem = e.targets; + elem.className = elem.className.replace(animatingClassName[1], ''); + if (elem.className.indexOf(animatingClassName[0]) === -1) { + elem.className = `${elem.className} ${animatingClassName[0]}`.trim(); + } + if (keysToEnter.current.indexOf(key) >= 0) { + keysToEnter.current.splice(keysToEnter.current.indexOf(key), 1); + } + childrenShow.current[key] = true; + }; + const enterComplete = (key: string | number, e: any) => { + if (keysToLeave.current.indexOf(key) >= 0) { + return; + } + const elem = e.targets; + elem.className = elem.className.replace(animatingClassName[0], '').trim(); + delete recordTweenKeys.current[key]; + onEnd({ key, type: 'enter', target: elem }); + }; + + const leaveBegin = (key: string | number, e: any) => { + const elem = e.targets; + elem.className = elem.className.replace(animatingClassName[0], ''); + if (elem.className.indexOf(animatingClassName[1]) === -1) { + elem.className = `${elem.className} ${animatingClassName[1]}`.trim(); + } + }; + + const leaveComplete = (key: string | number, e: any) => { + // 切换时同时触发 onComplete。 手动跳出。。。 + toArrayChildren(props.children).findIndex((c) => c && c.key === key); + if (toArrayChildren(props.children).findIndex((c) => c && c.key === key) >= 0) { + return; + } + delete childrenShow.current[key]; + delete recordTweenKeys.current[key]; + originalChildren.current = originalChildren.current.filter((c) => c.key !== key); + // 这里不用启动动画,,直接删; + if (keysToLeave.current.indexOf(key) >= 0) { + keysToLeave.current.splice(keysToLeave.current.indexOf(key), 1); + } + const needLeave = keysToLeave.current.some((c) => childShow[c]); + + if (!needLeave) { + const currentChildren = toArrayChildren(props.children); + setChild(currentChildren); + setChildShow({ ...childrenShow.current }); + recordKeysToLeave.current.forEach((k) => { + delete recordAnimKeys.current[k]; + }); + } + const elem = e.targets; + elem.className = elem.className.replace(animatingClassName[1], '').trim(); + onEnd({ key, type: 'leave', target: elem }); + }; + + const performEnterBegin = (key: string | number) => { + childShow[key] = true; + Ticker.clear(placeholderTimeoutIds.current[key]); + delete placeholderTimeoutIds.current[key]; + setChildShow({ ...childShow }); + }; + + const performEnter = (key: string | number, i: number) => { + const $interval = transformArguments(interval, key, i)[0]; + const $delay = transformArguments(delay, key, i)[0]; + placeholderTimeoutIds.current[key] = Ticker.timeout(() => { + performEnterBegin(key); + }, $interval * i + $delay); + }; + + const performLeave = (key: string | number) => { + Ticker.clear(placeholderTimeoutIds.current[key]); + delete placeholderTimeoutIds.current[key]; + }; + + const getTweenOneEnterOrLeave = ( + key: string | number, + i: number, + $delay: number, + $type: string, + ) => { + const animateData = getTweenData(key, i, $type); + const onStart = (e: any) => { + ($type === 'enter' ? enterBegin : leaveBegin)(key, e); + }; + const onComplete = (e: any) => { + ($type === 'enter' ? enterComplete : leaveComplete)(key, e); + }; + if (Array.isArray(animateData.animate)) { + const length = animateData.animate.length - 1; + const animation = animateData.animate.map((item, ii) => { + return { + ...item, + startAt: animateData.startAnim ? animateData.startAnim[ii] : undefined, + duration: animateData.duration / length, + delay: !ii && $type === 'leave' ? $delay : 0, + onStart: !ii ? onStart : undefined, + onComplete: ii === length ? onComplete : undefined, + }; + }); + return animation; + } + return { + ...animateData.animate, + startAt: animateData.startAnim || undefined, + ease: animateData.ease, + duration: animateData.duration, + onStart, + onComplete, + delay: $delay, + }; + }; + useEffect( + () => () => { + Object.keys(recordTweenKeys.current).forEach((key) => { + const tween = recordTweenKeys.current[key]; + if (!tween) { + return; + } + tween.kill(); + }); + }, + [], + ); + useEffect(() => { + const nextChildren = toArrayChildren(props.children).filter((c) => c); + const currentChildren = originalChildren.current.filter((item) => item); + const newChildren = mergeChildren(currentChildren, nextChildren); + const $keysToEnter: IKeys = []; + const $keysToLeave: IKeys = []; + if (!appear && !oneEnterBool.current) { + const $childShow: IObject = {}; + newChildren.forEach((c: any) => { + if (!c || !c.key) { + return; + } + $childShow[c.key] = true; + }); + originalChildren.current = newChildren; + childrenShow.current = { ...$childShow }; + setChildShow($childShow); + } else { + // console.log(nextChildren, recordAnimKeys.current, keysToEnter.current, keysToLeave.current); + currentChildren.forEach((c) => { + if (!c) { + return; + } + const { key } = c; + const hasNext = findChildInChildrenByKey(nextChildren, key); + if (!hasNext && key) { + $keysToLeave.push(key); + Ticker.clear(placeholderTimeoutIds.current[key]); + delete placeholderTimeoutIds.current[key]; + } + }); + + nextChildren.forEach((c: any) => { + if (!c) { + return; + } + + const { key } = c; + const hasPrev = findChildInChildrenByKey(currentChildren, key); + // 如果 nextChildren 和当前的一致,且动画里是出场,改回进场; + if ( + (!hasPrev && key) || + ((!recordAnimKeys.current[key] || + recordAnimKeys.current[key] === 'leave' || + keysToEnter.current.indexOf(key) >= 0) && + $keysToLeave.indexOf(key) === -1) + ) { + $keysToEnter.push(key); + } + }); + } + // console.log('child update', $keysToEnter, $keysToLeave, newChildren); + + keysToEnter.current = $keysToEnter; + // keysToEnter 在启动时就会删除; + recordKeysToEnter.current = [...$keysToEnter]; + keysToLeave.current = $keysToLeave; + recordKeysToLeave.current = [...$keysToLeave]; + + // console.log($keysToEnter, $keysToLeave); + setChild(newChildren); + }, [props.children]); + useLayoutEffect(() => { + originalChildren.current = child || []; + if (appear || oneEnterBool.current) { + const $keysToEnter = [...keysToEnter.current]; + const $keysToLeave = [...keysToLeave.current]; + $keysToEnter.forEach(performEnter); + $keysToLeave.forEach(performLeave); + } + if (child) { + oneEnterBool.current = true; + } + }, [child]); + useLayoutEffect(() => { + if (child) { + child.forEach((item) => { + const { key } = item; + const dom = childRefs.current[key]; + if (!dom) { + return; + } + let animation; + let index = keysToLeave.current.indexOf(key); // children.findIndex(c => c.key === key); + const $interval = transformArguments(interval, key, index); + const $delay = transformArguments(delay, key, index); + + // 处理出场 + if (index >= 0) { + if (recordAnimKeys.current[key] === 'leave') { + return; + } + + const order = leaveReverse ? keysToLeave.current.length - index - 1 : index; + const d = $interval[1] * order + $delay[1]; + animation = getTweenOneEnterOrLeave(key, index, d, 'leave'); + recordAnimKeys.current[key] = 'leave'; + } else { + if (recordAnimKeys.current[key] === 'enter' || keysToEnter.current.indexOf(key) === -1) { + return; + } + index = recordKeysToEnter.current.indexOf(key); + const d = $interval[0] * index + $delay[0]; + // console.log(recordAnimKeys.current[key], dom); + animation = getTweenOneEnterOrLeave( + key, + index, + recordAnimKeys.current[key] === 'leave' ? d : 0, + 'enter', + ); + recordAnimKeys.current[key] = 'enter'; + } + if (recordTweenKeys.current[key]) { + recordTweenKeys.current[key].kill(); + } + + if (forcedReplay) { + const anim = { + ...(Array.isArray(animation) ? animation[0].startAt : animation.startAt), + type: 'set', + }; + TweenOne(dom, { animation: anim }); + } + recordTweenKeys.current[key] = TweenOne(dom, { + animation, + }); + }); + } + }, [childShow, child]); + + return useMemo(() => { + // console.log('--------render--------', childShow); + if (windowIsUndefined) { + return createElement(component, { ...tagProps, ...componentProps, ref }); + } + const childrenToRender = toArrayChildren(child).map((item) => { + if (!item || !item.key) { + return item; + } + return ( + childShow[item.key] && + cloneElement(item, { + ref: (c: any) => { + childRefs.current[item.key] = c instanceof Element ? c : findDOMNode(c); + if (!c) { + delete childRefs.current[item.key]; + } + }, + key: item.key, + }) + ); + }); + const p = { + ...tagProps, + ...componentProps, + ref, + }; + return createElement(component, p, childrenToRender); + }, [childShow, child]); +}); diff --git a/src/animTypes.js b/src/animTypes.ts similarity index 100% rename from src/animTypes.js rename to src/animTypes.ts diff --git a/src/index.js b/src/index.tsx similarity index 78% rename from src/index.js rename to src/index.tsx index 8d9810a..df6b330 100644 --- a/src/index.js +++ b/src/index.tsx @@ -2,3 +2,5 @@ import QueueAnim from './QueueAnim'; export default QueueAnim; + +export * from './type'; diff --git a/src/type.ts b/src/type.ts new file mode 100644 index 0000000..3361153 --- /dev/null +++ b/src/type.ts @@ -0,0 +1,59 @@ +import type { IObject as IObj } from 'tween-one/lib/typings'; +import type { IEaseType as IEase } from 'tween-one/lib/typings/IAnimObject'; +import type React from 'react'; + +export type IObject = IObj; + +export type IKeys = (string | number)[]; + +export type IQueueType = + | 'alpha' + | 'left' + | 'right' + | 'top' + | 'bottom' + | 'scale' + | 'scaleBig' + | 'scaleX' + | 'scaleY'; +export type INumberOrArrayOrFunc = + | number + | [number, number] + | ((e: { key: string; index: number }) => number | number[]); +export type IEaseType = IEase | [number, number, number, number]; + +export type IQueueTypeOrArrayOrFunc = + | IQueueType + | [IQueueType, IQueueType] + | ((e: { key: string; index: number }) => IQueueType | [IQueueType, IQueueType]); +export type IEaseTypeOrArrayOrFunc = + | IEaseType + | IEaseType[] + | ((e: { key: string; index: number }) => IEaseType | IEaseType[]); +export type IAnimConfigOrArrayOrFunc = + | {} + | [{}] + | ((e: { key: string; index: number }) => {} | {}[]); + +interface AllHTMLAttributes + extends Omit, 'crossOrigin'>, + React.AllHTMLAttributes {} +export interface IProps extends Omit { + type?: IQueueTypeOrArrayOrFunc; + animConfig?: IAnimConfigOrArrayOrFunc; + delay?: INumberOrArrayOrFunc; + duration?: INumberOrArrayOrFunc; + interval?: INumberOrArrayOrFunc; + leaveReverse?: boolean; + ease?: IEaseTypeOrArrayOrFunc; + appear?: boolean; + component?: + | string + | React.ClassType> + | React.ForwardRefExoticComponent }> + | undefined; + componentProps?: IObject; + animatingClassName?: string[]; + forcedReplay?: boolean; + onEnd?: (e: { key: string | number; type: string; target: HTMLElement }) => void; +} diff --git a/src/utils.js b/src/utils.ts similarity index 70% rename from src/utils.js rename to src/utils.ts index 1643704..696798e 100644 --- a/src/utils.js +++ b/src/utils.ts @@ -1,4 +1,5 @@ /* eslint no-prototype-builtins: 0 */ +import type { IObject } from './type'; import React from 'react'; export const windowIsUndefined = !( @@ -7,18 +8,18 @@ export const windowIsUndefined = !( window.document.createElement ); -export function toArrayChildren(children) { - const ret = []; - React.Children.forEach(children, c => { +export function toArrayChildren(children: any) { + const ret: any[] = []; + React.Children.forEach(children, (c) => { ret.push(c); }); return ret; } -export function findChildInChildrenByKey(children, key) { - let ret = null; +export function findChildInChildrenByKey(children: any[], key: string) { + let ret: any = null; if (children) { - children.forEach(c => { + children.forEach((c) => { if (ret || !c) { return; } @@ -30,14 +31,14 @@ export function findChildInChildrenByKey(children, key) { return ret; } -export function mergeChildren(prev, next) { - let ret = []; +export function mergeChildren(prev: any, next: any) { + let ret: any = []; // For each key of `next`, the list of keys to insert before that key in // the combined list - const nextChildrenPending = {}; - let pendingChildren = []; - let followChildrenKey; - prev.forEach(c => { + const nextChildrenPending: IObject = {}; + let pendingChildren: any = []; + let followChildrenKey: any; + prev.forEach((c: any) => { if (!c) { return; } @@ -54,7 +55,7 @@ export function mergeChildren(prev, next) { if (!followChildrenKey) { ret = ret.concat(pendingChildren); } - next.forEach(c => { + next.forEach((c: any) => { if (!c) { return; } @@ -70,7 +71,7 @@ export function mergeChildren(prev, next) { return ret; } -export function transformArguments(arg, key, i) { +export function transformArguments(arg: any, key: string | number, i: number) { let result; if (typeof arg === 'function') { result = arg({ @@ -88,7 +89,3 @@ export function transformArguments(arg, key, i) { } return [result, result]; } - -export function getChildrenFromProps(props) { - return props && props.children; -} diff --git a/tests/index.test.jsx b/tests/index.test.jsx new file mode 100644 index 0000000..610f076 --- /dev/null +++ b/tests/index.test.jsx @@ -0,0 +1,324 @@ +import React, { useState } from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { act } from 'react-dom/test-utils'; + +import QueueAnim from '../src'; + +import TweenOne, { Ticker } from 'tween-one'; + +function getOpacity(node) { + if (!node) { + return NaN; + } + // console.log('node.style.opacity)', node.style) + return parseFloat(node.style.opacity); +} + +function getLeft(node) { + if (!node) { + return NaN; + } + return parseFloat(node.style.left); +} + +function getTop(node) { + if (!node) { + return NaN; + } + return parseFloat(node.style.top); +} + +let container = null; +beforeEach(() => { + // 创建一个 DOM 元素作为渲染目标 + container = document.createElement('div'); + document.body.appendChild(container); + + TweenOne({}, { x: 100, duration: 2000000 }); + //jest.useFakeTimers(); +}); + +afterEach(() => { + // 退出时进行清理 + unmountComponentAtNode(container); + container.remove(); + container = null; + //jest.useRealTimers(); +}); + +const items = [ + { + key: 1, + content: 'div', + }, + { + key: 2, + content: 'div', + }, + { + key: 3, + content: 'div', + }, +]; + +const child = items.map((item) => ( + + {item.content} + +)); + +const QueueAnimComp = (props) => { + const [children, setChildren] = useState(child); + const [unmount, setUnmount] = useState(false); + return ( +
    + {!unmount && ( + + {children} + + )} + + +
    + ); +}; + +const interval = 100; + +function shouldAnimatingThisOne(children, index) { + children.forEach((node, i) => { + console.log(index, i, getOpacity(node)); + if (i <= index) { + expect(getOpacity(node)).toBeGreaterThan(0); + } else { + // placeholder + // expect(node.innerHTML).to.be(''); + } + }); +} + +it('should render children', (done) => { + act(() => { + render({child}, container); + // jest.advanceTimersByTime(50); + }); /* Ticker.timeout 0 会直接返回 + let { children } = container.querySelector('[id=queue]'); + expect(children.length).toBe(0); */ + + const { children } = container.querySelector('[id=queue]'); + + Ticker.timeout(() => { + // shouldAnimatingThisOne(0); + expect(children.length).toBe(1); + done(); + }); +}); + +it('should render all children', () => { + act(() => { + render( + + {child} + , + container, + ); + }); + const { children } = container.querySelector('[id=queue]'); + console.log(children.length); + expect(children.length).toBe(3); +}); + +it('should have queue animation', (done) => { + act(() => { + render({child}, container); + }); + const { children } = container.querySelector('[id=queue]'); + Ticker.timeout(() => { + shouldAnimatingThisOne(children, 0); + Ticker.timeout(() => { + shouldAnimatingThisOne(children, 1); + Ticker.timeout(() => { + shouldAnimatingThisOne(children, 2); + done(); + }, interval); + }, interval); + }, 50); + /* act(() => { + jest.advanceTimersByTime(100); + }); + shouldAnimatingThisOne(1); +*/ +}); + +it('custom api queue animation', (done) => { + act(() => { + render( + + {child} + , + container, + ); + }); + const node = container.querySelector('[id=queue]'); + const { children } = node; + console.log('component style', getTop(node)); + expect(getTop(node)).toBe(10); + console.log('component tagName', node.tagName); + expect(node.tagName).toBe('P'); + Ticker.timeout(() => { + expect(children.length).toBe(0); + Ticker.timeout(() => { + expect(children.length).toBe(1); + done(); + }, 100); + }, 950); +}); + +it('custom animConfig queue animation', (done) => { + act(() => { + render( + + {child} + , + container, + ); + }); + const { children } = container.querySelector('[id=queue]'); + Ticker.timeout(() => { + expect(getLeft(children[0])).toBe(100); + done(); + }, 500); +}); + +it('custom forcedReplay queue animation', (done) => { + act(() => { + render( + , + container, + ); + }); + const { children } = container.querySelector('[id=queue]'); + const button = document.querySelector('[data-testId=toggle]'); + Ticker.timeout(() => { + // 出场 + act(() => { + button.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + + Ticker.timeout(() => { + // 重新进入 + act(() => { + button.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + + expect(getLeft(children[0])).toBe(0); + done(); + }, 500); + }, 500); +}); + +it('should support custom animation config array', (done) => { + act(() => { + render(, container); + }); + const { children } = container.querySelector('[id=queue]'); + const button = document.querySelector('[data-testId=toggle]'); + Ticker.timeout(() => { + expect(getLeft(children[0])).toBe(100); + act(() => { + button.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + Ticker.timeout(() => { + console.log('top', getTop(children[0])); + expect(getTop(children[0])).toBe(0); + done(); + }, 500); + }, 500); +}); +it('should has animating config is func enter', (done) => { + act(() => { + render( + { + if (e.index === 1) { + return [[{ top: [100, 0] }, { left: [100, 0]}]]; + } + return { left: [100, 0] }; + }} + />, + container, + ); + }); + const { children } = container.querySelector('[id=queue]'); + const button = document.querySelector('[data-testId=toggle]'); + Ticker.timeout(() => { + expect(getLeft(children[0])).toBe(100); + expect(getTop(children[1])).toBe(100); + done(); + act(() => { + button.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + Ticker.timeout(() => { + // expect(getLeft(children[0])).toBe(0); + // expect(getTop(children[1])).toBe(0); + done(); + }, 600); + }, 600); +}); +it('should have leave animation', (done) => { + act(() => { + render(, container); + }); + const { children } = container.querySelector('[id=queue]'); + const button = document.querySelector('[data-testId=toggle]'); + Ticker.timeout(() => { + expect(children.length).toBe(3); + act(() => { + button.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + Ticker.timeout(() => { + expect(children.length).toBe(0); + done(); + }, 1000); + }, 1000); +}); + +it('should have unmount animation', (done) => { + act(() => { + render(, container); + + const button = document.querySelector('[data-testId=unmount]'); + Ticker.timeout(() => { + act(() => { + button.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + const node = container.querySelector('[id=queue]'); + expect(node).toBe(null); + done(); + }, 1000); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index f5622bd..bb3518e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,31 +1,30 @@ { "compilerOptions": { - "outDir": "build/dist", + "target": "esnext", "module": "esnext", - "target": "es2016", - "lib": ["es6", "dom"], - "sourceMap": true, - "jsx": "react", - "allowSyntheticDefaultImports": true, "moduleResolution": "node", - "rootDirs": ["/src", "./typings"], - "forceConsistentCasingInFileNames": true, - "noImplicitReturns": true, - "suppressImplicitAnyIndexErrors": true, - "noUnusedLocals": true, - "allowJs": true, - "experimentalDecorators": true + "importHelpers": true, + "jsx": "react", + "typeRoots": ["./typings"], + "declaration": true, + "esModuleInterop": true, + "sourceMap": true, + "baseUrl": "./", + "strict": true, + "paths": { + "@/*": ["src/*"], + "@@/*": ["src/.umi/*"], + "rc-queue-anim": ["src/index.tsx"] + }, + "allowSyntheticDefaultImports": true }, - "include": ["./src"], "exclude": [ "node_modules", - "build", - "scripts", - "acceptance-tests", - "webpack", - "jest", - "src/setupTests.ts", - "tslint:latest", - "tslint-config-prettier" + "lib", + "es", + "dist", + "typings", + "**/__test__", + "test" ] } diff --git a/tslint.json b/tslint.json deleted file mode 100644 index 125e217..0000000 --- a/tslint.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": ["tslint:latest", "tslint-react", "tslint-config-prettier"], - "rules": { - "no-var-requires": false, - "no-submodule-imports": false, - "object-literal-sort-keys": false, - "jsx-no-lambda": false, - "no-implicit-dependencies": false, - "no-console": false - } -} diff --git a/typings/global/import.d.ts b/typings/global/import.d.ts new file mode 100644 index 0000000..1b73c18 --- /dev/null +++ b/typings/global/import.d.ts @@ -0,0 +1,6 @@ +import * as CSS from 'csstype'; +declare module 'csstype' { + interface Properties { + [key: string]: any; + } +} \ No newline at end of file diff --git a/typings/global/index.d.ts b/typings/global/index.d.ts new file mode 100644 index 0000000..6c0343c --- /dev/null +++ b/typings/global/index.d.ts @@ -0,0 +1,7 @@ +/// +declare module '*.css'; +declare module '*.less'; +declare module 'style-utils'; +declare module 'tween-functions'; +declare module 'raf'; +declare module 'flubber'; \ No newline at end of file