├── .all-contributorsrc
├── .eslintignore
├── .eslintrc.json
├── .flowconfig
├── .gitignore
├── .prettierignore
├── .prettierrc
├── .size-snapshot.json
├── .travis.yml
├── LICENSE
├── README.md
├── babel.config.js
├── docs
├── Guide.mdx
├── Introduction.mdx
├── _ui
│ ├── MDXComponents.js
│ └── PropsTable.js
├── components
│ ├── Active.mdx
│ ├── Compose.mdx
│ ├── Counter.mdx
│ ├── Field.mdx
│ ├── Focus.mdx
│ ├── Form.mdx
│ ├── Hover.mdx
│ ├── Interval.mdx
│ ├── List.mdx
│ ├── Map.mdx
│ ├── Set.mdx
│ ├── State.mdx
│ ├── Toggle.mdx
│ ├── Touch.mdx
│ └── Value.mdx
└── utils
│ ├── compose.mdx
│ └── composeEvents.mdx
├── doczrc.js
├── logo.png
├── package.json
├── rollup.config.js
├── src
├── components
│ ├── Active.js
│ ├── Compose.js
│ ├── Counter.js
│ ├── Field.js
│ ├── Focus.js
│ ├── FocusManager.js
│ ├── Form.js
│ ├── Hover.js
│ ├── Interval.js
│ ├── List.js
│ ├── Map.js
│ ├── Set.js
│ ├── State.js
│ ├── Toggle.js
│ ├── Touch.js
│ └── Value.js
├── index.js
├── index.js.flow
└── utils
│ ├── compose.js
│ ├── composeEvents.js
│ ├── renderProps.js
│ └── warn.js
├── tests
├── components
│ ├── Active.test.js
│ ├── Compose.test.js
│ ├── Counter.test.js
│ ├── Field.test.js
│ ├── Focus.test.js
│ ├── FocusManager.test.js
│ ├── Form.test.js
│ ├── Hover.test.js
│ ├── Interval.test.js
│ ├── List.test.js
│ ├── Map.test.js
│ ├── Set.test.js
│ ├── State.test.js
│ ├── Toggle.test.js
│ ├── Touch.test.js
│ ├── Value.test.js
│ └── utils.js
├── jestCJSSetup.js
├── jestUMDSetup.js
├── test_flow.js
└── utils
│ ├── compose.test.js
│ ├── composeEvents.test.js
│ └── renderProps.test.js
├── types
├── index.d.ts
├── test.tsx
└── tsconfig.json
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "react-powerplug",
3 | "projectOwner": "renatorib",
4 | "repoType": "github",
5 | "repoHost": "https://github.com",
6 | "files": [
7 | "README.md"
8 | ],
9 | "imageSize": 100,
10 | "commit": true,
11 | "contributors": [
12 | {
13 | "login": "renatorib",
14 | "name": "Renato Ribeiro",
15 | "avatar_url": "https://avatars2.githubusercontent.com/u/3277185?v=4",
16 | "profile": "http://twitter.com/renatorib_",
17 | "contributions": [
18 | "code",
19 | "design",
20 | "doc",
21 | "test"
22 | ]
23 | },
24 | {
25 | "login": "TrySound",
26 | "name": "Bogdan Chadkin",
27 | "avatar_url": "https://avatars0.githubusercontent.com/u/5635476?v=4",
28 | "profile": "https://github.com/TrySound",
29 | "contributions": [
30 | "code",
31 | "doc",
32 | "test",
33 | "infra"
34 | ]
35 | },
36 | {
37 | "login": "souporserious",
38 | "name": "Travis Arnold",
39 | "avatar_url": "https://avatars1.githubusercontent.com/u/2762082?v=4",
40 | "profile": "https://souporserious.com/",
41 | "contributions": [
42 | "code",
43 | "doc",
44 | "bug"
45 | ]
46 | },
47 | {
48 | "login": "MaxGraey",
49 | "name": "Max Graey",
50 | "avatar_url": "https://avatars3.githubusercontent.com/u/1301959?v=4",
51 | "profile": "https://github.com/MaxGraey",
52 | "contributions": [
53 | "code"
54 | ]
55 | },
56 | {
57 | "login": "Andarist",
58 | "name": "Mateusz Burzyński",
59 | "avatar_url": "https://avatars2.githubusercontent.com/u/9800850?v=4",
60 | "profile": "https://github.com/Andarist",
61 | "contributions": [
62 | "bug"
63 | ]
64 | },
65 | {
66 | "login": "jedwards1211",
67 | "name": "Andy Edwards",
68 | "avatar_url": "https://avatars0.githubusercontent.com/u/1448194?v=4",
69 | "profile": "http://helloandy.xyz",
70 | "contributions": [
71 | "code"
72 | ]
73 | },
74 | {
75 | "login": "apuntovanini",
76 | "name": "Andrea Vanini",
77 | "avatar_url": "https://avatars2.githubusercontent.com/u/1159781?v=4",
78 | "profile": "http://uidu.org",
79 | "contributions": [
80 | "bug"
81 | ]
82 | },
83 | {
84 | "login": "istarkov",
85 | "name": "Ivan Starkov",
86 | "avatar_url": "https://avatars3.githubusercontent.com/u/5077042?v=4",
87 | "profile": "https://twitter.com/icelabaratory",
88 | "contributions": [
89 | "bug"
90 | ]
91 | },
92 | {
93 | "login": "SeanRoberts",
94 | "name": "Sean Roberts",
95 | "avatar_url": "https://avatars1.githubusercontent.com/u/25376?v=4",
96 | "profile": "http://factore.ca",
97 | "contributions": [
98 | "doc"
99 | ]
100 | },
101 | {
102 | "login": "redbmk",
103 | "name": "Braden Kelley",
104 | "avatar_url": "https://avatars3.githubusercontent.com/u/83964?v=4",
105 | "profile": "https://github.com/redbmk",
106 | "contributions": [
107 | "bug"
108 | ]
109 | }
110 | ]
111 | }
112 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | *.flow
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "plugins": [
4 | "import",
5 | "react",
6 | "flowtype"
7 | ],
8 | "extends": [
9 | "eslint:recommended",
10 | "plugin:import/recommended",
11 | "plugin:react/recommended"
12 | ],
13 | "env": {
14 | "browser": true,
15 | "es6": true,
16 | "node": true,
17 | "jest": true
18 | },
19 | "rules": {
20 | "no-unused-vars": ["error", { "ignoreRestSiblings": true }],
21 | "valid-jsdoc": "error",
22 | "react/prop-types": "off",
23 | "react/jsx-uses-react": "warn",
24 | "react/jsx-no-undef": "error",
25 | "import/no-unresolved": ["error", { "ignore": ["^react$"] }],
26 | "import/unambiguous": "off",
27 | "react/jsx-key": "off",
28 | "flowtype/define-flow-type": "error",
29 | "flowtype/use-flow-type": "error"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 |
3 | [include]
4 |
5 | [libs]
6 |
7 | [lints]
8 |
9 | [options]
10 | include_warnings=true
11 |
12 | [strict]
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .docz
3 | node_modules
4 | dist
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | package.json
2 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "requirePragma": false,
3 | "printWidth": 80,
4 | "tabWidth": 2,
5 | "useTabs": false,
6 | "semi": false,
7 | "singleQuote": true,
8 | "trailingComma": "es5",
9 | "bracketSpacing": true,
10 | "jsxBracketSameLine": false
11 | }
12 |
--------------------------------------------------------------------------------
/.size-snapshot.json:
--------------------------------------------------------------------------------
1 | {
2 | "dist/react-powerplug.umd.js": {
3 | "bundled": 21989,
4 | "minified": 8724,
5 | "gzipped": 2360
6 | },
7 | "dist/react-powerplug.cjs.js": {
8 | "bundled": 19846,
9 | "minified": 9915,
10 | "gzipped": 2384
11 | },
12 | "dist/react-powerplug.esm.js": {
13 | "bundled": 19241,
14 | "minified": 9402,
15 | "gzipped": 2250,
16 | "treeshaked": {
17 | "rollup": {
18 | "code": 197,
19 | "import_statements": 197
20 | },
21 | "webpack": {
22 | "code": 1495
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - '8'
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Renato Ribeiro
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | > **React PowerPlug is a set of pluggable renderless components and helpers** that provides different types of state and logic utilities that you can use with your dumb components. It creates state and passes down the logic to the children, so you can handle your data. Read about the [Render Props](https://reactjs.org/docs/render-props.html) pattern.
20 |
21 | ## Highlights
22 |
23 | - :ok_hand: Dependency free
24 | - :electric_plug: Plug and play
25 | - :crystal_ball: Tree shaking friendly (ESM, no side effects)
26 | - :package: Super tiny (~3kb)
27 | - :books: Well documented
28 | - :beers: Bunch of awesome utilities
29 |
30 |
31 | See quick examples
32 |
33 | ```jsx
34 | import { State, Toggle } from 'react-powerplug'
35 | import { Pagination, Tabs, Checkbox } from './MyDumbComponents'
36 |
37 |
38 | {({ state, setState }) => (
39 | setState({ offset })} />
40 | )}
41 |
42 |
43 |
44 | {({ on, toggle }) => (
45 |
46 | )}
47 |
48 |
49 | // You can also use a `render` prop instead
50 |
51 | (
54 |
55 | )}
56 | />
57 | ```
58 |
59 |
60 |
61 | ## Guide & Documentation
62 |
63 | http://rena.to/react-powerplug/
64 |
65 | ---
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | ---
74 |
75 | # Install
76 |
77 | ### Node Module
78 |
79 | ```
80 | yarn add react-powerplug
81 | ```
82 |
83 | ```
84 | npm i react-powerplug
85 | ```
86 |
87 | ### UMD
88 |
89 | ```html
90 |
91 | ```
92 |
93 | exposed as `ReactPowerPlug`
94 |
95 | # Contributors
96 |
97 | Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):
98 |
99 |
100 |
101 |
102 | | [Renato Ribeiro ](http://twitter.com/renatorib_) [💻](https://github.com/renatorib/react-powerplug/commits?author=renatorib "Code") [🎨](#design-renatorib "Design") [📖](https://github.com/renatorib/react-powerplug/commits?author=renatorib "Documentation") [⚠️](https://github.com/renatorib/react-powerplug/commits?author=renatorib "Tests") | [Bogdan Chadkin ](https://github.com/TrySound) [💻](https://github.com/renatorib/react-powerplug/commits?author=TrySound "Code") [📖](https://github.com/renatorib/react-powerplug/commits?author=TrySound "Documentation") [⚠️](https://github.com/renatorib/react-powerplug/commits?author=TrySound "Tests") [🚇](#infra-TrySound "Infrastructure (Hosting, Build-Tools, etc)") | [Travis Arnold ](http://travisrayarnold.com) [💻](https://github.com/renatorib/react-powerplug/commits?author=souporserious "Code") [📖](https://github.com/renatorib/react-powerplug/commits?author=souporserious "Documentation") [🐛](https://github.com/renatorib/react-powerplug/issues?q=author%3Asouporserious "Bug reports") | [Max Graey ](https://github.com/MaxGraey) [💻](https://github.com/renatorib/react-powerplug/commits?author=MaxGraey "Code") | [Mateusz Burzyński ](https://github.com/Andarist) [🐛](https://github.com/renatorib/react-powerplug/issues?q=author%3AAndarist "Bug reports") | [Andy Edwards ](http://helloandy.xyz) [💻](https://github.com/renatorib/react-powerplug/commits?author=jedwards1211 "Code") | [Andrea Vanini ](http://uidu.org) [🐛](https://github.com/renatorib/react-powerplug/issues?q=author%3Aapuntovanini "Bug reports") |
103 | | :---: | :---: | :---: | :---: | :---: | :---: | :---: |
104 | | [Ivan Starkov ](https://twitter.com/icelabaratory) [🐛](https://github.com/renatorib/react-powerplug/issues?q=author%3Aistarkov "Bug reports") | [Sean Roberts ](http://factore.ca) [📖](https://github.com/renatorib/react-powerplug/commits?author=SeanRoberts "Documentation") | [Braden Kelley ](https://github.com/redbmk) [🐛](https://github.com/renatorib/react-powerplug/issues?q=author%3Aredbmk "Bug reports") |
105 |
106 |
107 |
108 | This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
109 |
110 | # Contribute
111 |
112 | You can help improving this project sending PRs and helping with issues.
113 | Also you can ping me at [Twitter](http://twitter.com/renatorib_)
114 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [['@babel/env', { loose: true }], '@babel/react'],
3 | plugins: [['@babel/proposal-class-properties', { loose: true }]],
4 | }
5 |
--------------------------------------------------------------------------------
/docs/Guide.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Guide
3 | route: /guide
4 | order: 19
5 | ---
6 |
7 | # Guide
8 |
9 | ## Creating a Dumb Component
10 |
11 | Dumb Component, Presentational Component or (sometimes) Controlled Component is a component
12 | responsible **only for displaying content without any logic behind**. Usually they
13 | receive specific props, and if they are interactive, it exposes events like onClick, onChange, etc.
14 |
15 | A Styled Component a good example.
16 |
17 | ```jsx
18 | import React from "react";
19 | import styled from "styled-components";
20 |
21 | const DumbCheckbox = styled("div")`
22 | cursor: pointer;
23 | &:before {
24 | content: '${props => (props.checked ? "■" : "□")} ';
25 | }
26 | `;
27 |
28 | const App = () => (
29 | Check me
30 | );
31 | ```
32 |
33 | ## Using React PowerPlug
34 |
35 | Now that you have your Dumb Component, you can pass state to it.
36 | Using react-powerplug this step is trivial and pretty simple.
37 |
38 | ```jsx
39 | import { Toggle } from "react-powerplug";
40 |
41 |
42 | {({ on, toggle }) => (
43 |
44 | Check me
45 |
46 | )}
47 |
48 | ```
49 |
50 |
--------------------------------------------------------------------------------
/docs/Introduction.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Introduction
3 | route: /
4 | order: 20
5 | ---
6 |
7 | # Introduction
8 |
9 | **React PowerPlug is a set of pluggable renderless components and utils** that provides
10 | different types of state and logics so you can use with your dumb components. It creates
11 | a state and pass down the logic to the children, so you can handle your data.
12 | Read about [Render Props](https://reactjs.org/docs/render-props.html) pattern.
13 |
14 | - Dependency free
15 | - Super tiny (~3kb)
16 | - Plug and play
17 | - Tree shaking friendly (ESM, no side effects)
18 | - Well documented
19 | - Bunch of awesome utilities
20 |
21 | ## Quick Examples
22 |
23 | ```jsx
24 | import { State, Toggle } from 'react-powerplug'
25 | import { Pagination, Tabs, Checkbox } from './MyDumbComponents'
26 | ```
27 |
28 | ```jsx
29 |
30 | {({ state, setState }) => (
31 | setState({ offset })} />
32 | )}
33 |
34 | ```
35 |
36 | ```jsx
37 |
38 | {({ on, toggle }) => (
39 |
40 | )}
41 |
42 | ```
43 |
44 | You can also use a `render` prop instead
45 |
46 | ```jsx
47 | (
50 |
51 | )}
52 | />
53 | ```
--------------------------------------------------------------------------------
/docs/_ui/MDXComponents.js:
--------------------------------------------------------------------------------
1 | import { withMDXComponents } from '@mdx-js/tag/dist/mdx-provider'
2 |
3 | const MDXComponents = withMDXComponents(({ children, components }) =>
4 | children(components)
5 | )
6 |
7 | export default MDXComponents
8 |
--------------------------------------------------------------------------------
/docs/_ui/PropsTable.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import MDXComponents from './MDXComponents'
3 |
4 | const Strong = props =>
5 |
6 | export const Props = ({ children }) => (
7 |
8 | {({ table: Table }) => (
9 |
10 |
11 |
12 | Prop
13 | Type
14 | Default
15 | Description
16 |
17 |
18 | {children}
19 |
20 | )}
21 |
22 | )
23 |
24 | export const Prop = ({
25 | name,
26 | type,
27 | default: _default,
28 | children,
29 | required = false,
30 | }) => (
31 |
32 |
33 |
34 | {name}
35 | {required ? ' *' : ''}
36 |
37 |
38 | {type}
39 |
40 | {typeof _default !== 'undefined' ? (
41 | {JSON.stringify(_default)}
42 | ) : (
43 | ''
44 | )}
45 |
46 | {children}
47 |
48 | )
49 |
50 | export const ChildrenProps = ({ children }) => (
51 |
52 | {({ table: Table }) => (
53 |
54 |
55 |
56 | Prop
57 | Type
58 | Description
59 |
60 |
61 | {children}
62 |
63 | )}
64 |
65 | )
66 |
67 | export const ChildrenProp = ({ name, type, children }) => (
68 |
69 |
70 | {name}
71 |
72 | {type}
73 | {children}
74 |
75 | )
76 |
--------------------------------------------------------------------------------
/docs/components/Active.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Active
3 | menu: 2. Feedback Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { Active } from '../../dist/react-powerplug.esm'
8 |
9 | # Active
10 |
11 | The Active component is used to known when user is clicking (holding) some element.
12 | It's the same as `:active` pseudo selector from css.
13 |
14 | ```js
15 | import { Active } from 'react-powerplug'
16 | ```
17 |
18 | ```jsx
19 |
20 | {({ active, bind }) => (
21 |
22 | You are {active ? 'clicking' : 'not clicking'} this div.
23 |
24 | )}
25 |
26 | ```
27 |
28 | ## Props
29 |
30 |
31 |
32 | The onChange event of the Active is called whenever the `active` state changes.
33 |
34 |
35 | Receive state as function. It can also be `render` prop.
36 |
37 |
38 |
39 | ## Children Props
40 |
41 |
42 |
43 | True if is holding the binded element
44 |
45 |
46 | There are the bind event functions.
47 | Contains `onMouseUp` and `onMouseDown` event listeners.
48 |
49 |
50 |
--------------------------------------------------------------------------------
/docs/components/Compose.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Compose
3 | menu: 4. Utils Containers
4 | ---
5 |
6 | import { Props, Prop } from '../_ui/PropsTable'
7 | import { State } from '../../dist/react-powerplug.esm'
8 |
9 | # Compose
10 |
11 | The Compose component is a special component to you 'merge' two or more components functionalities.
12 | You can, for example, combine Toggle and Counter in a single component and use both power together.
13 |
14 | The components to compose do not necessarily have to be provided by this library, as long as they are render-props components, it works.
15 |
16 | ## Usage
17 |
18 | ```js
19 | import { Compose, Toggle, Counter } from 'react-powerplug'
20 | ```
21 |
22 | ```jsx
23 |
24 | {(counter, toggle) => (
25 |
33 | )}
34 |
35 | ```
36 |
37 | If you need to pass props, especially for `initial`, just pass a created element. Internals this will be cloned.
38 |
39 | ```jsx
40 | ]}>
41 | {(counter, toggle) => (/* ... */)}
42 |
43 | ```
44 |
45 | Also, you can use a built-in Compose component and pass components on `components` prop
46 |
47 | ```jsx
48 |
49 | {(toggle, counter) => (
50 |
51 | )}
52 |
53 | ```
54 |
55 | Behind the scenes, that's what happens:
56 |
57 | ```jsx
58 |
59 | {counter => (
60 |
61 | {toggle => (
62 |
70 | )}
71 |
72 | )}
73 |
74 | ```
75 |
76 | ## Props
77 |
78 |
79 |
80 | Specifies the components to compose
81 |
82 |
83 | Receive state as function. It can also be `render` prop.
84 |
85 |
86 |
87 | ## Children Props
88 |
89 | The render props function provided will receive n arguments, each of them being
90 | the arguments provided by the corresponding component in the list.
91 |
--------------------------------------------------------------------------------
/docs/components/Counter.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Counter
3 | menu: 1. State Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { Counter } from '../../dist/react-powerplug.esm'
8 |
9 | # Counter
10 |
11 | The Counter component is used for when it's necessary to count something.
12 |
13 | ## Usage
14 |
15 | ```js
16 | import { Counter } from 'react-powerplug'
17 | ```
18 |
19 | ```jsx
20 |
21 | {({ count, inc, dec }) => (
22 |
29 | )}
30 |
31 | ```
32 |
33 | ## Props
34 |
35 |
36 |
37 | Specifies the initial `count` state.
38 |
39 |
40 | The onChange event of the Toggle is called whenever the on state changes.
41 |
42 |
43 | Receive state as function. It can also be `render` prop.
44 |
45 |
46 |
47 | ## Children Props
48 |
49 |
50 |
51 | Your `count` state value
52 |
53 |
54 | Increase your count state by 1.
55 |
56 |
57 | Decrease your count state by 1.
58 |
59 |
60 | Arbitrary increase your count state by provided value.
61 |
62 |
63 | Arbitrary decrease your count state by provided value.
64 |
65 |
66 | Arbitrary set a value to `count` state
67 |
68 |
69 | Reset `count` to initial state
70 |
71 |
72 |
--------------------------------------------------------------------------------
/docs/components/Field.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Field
3 | menu: 3. Form Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { State } from '../../dist/react-powerplug.esm'
8 |
9 | # Field
10 |
11 | The Field component is used for form fields like inputs, checkboxes, selects, etc.
12 |
13 | ## Usage
14 |
15 | ```js
16 | import { Field } from 'react-powerplug'
17 | ```
18 |
19 | ```jsx
20 |
21 | {({ value, set }) => (
22 | set(e.target.value)} />
23 | )}
24 |
25 | ```
26 |
27 | ```jsx
28 |
29 | {({ bind }) => }
30 |
31 | ```
32 |
33 | ```jsx
34 |
35 | {({ bind }) => (
36 |
37 | You accept the terms?
38 |
39 | )}
40 |
41 | ```
42 |
43 | ## Props
44 |
45 |
46 |
47 | Specifies the initial `value` state.
48 |
49 |
50 | The onChange event of the Value is called whenever the on state changes.
51 |
52 |
53 | Receive state as function. It can also be `render` prop.
54 |
55 |
56 |
57 | ## Children Props
58 |
59 |
60 |
61 | Your `value` state value
62 |
63 |
64 | Arbitrary set a value to `value` state
65 |
66 |
67 | There are the bind event functions.
68 | Contains `value` prop and `onChange` event listener.
69 |
70 |
71 | Reset `value` to initial state
72 |
73 |
74 |
--------------------------------------------------------------------------------
/docs/components/Focus.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Focus
3 | menu: 2. Feedback Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { Focus } from '../../dist/react-powerplug.esm'
8 |
9 | # Focus
10 |
11 | The Focus component is used to known when user is focusing some element.
12 | It's the same as `:focus` pseudo selector from css.
13 |
14 | ```js
15 | import { Focus } from 'react-powerplug'
16 | ```
17 |
18 | ```jsx
19 |
20 | {({ focused, bind }) => (
21 |
22 |
23 |
You are {focused ? 'focusing' : 'not focusing'} the input.
24 |
25 | )}
26 |
27 | ```
28 |
29 | ## Props
30 |
31 |
32 |
33 | The onChange event of the Focus is called whenever the `focused` state changes.
34 |
35 |
36 | Receive state as function. It can also be `render` prop.
37 |
38 |
39 |
40 | ## Children Props
41 |
42 |
43 |
44 | True if is focusing the binded element
45 |
46 |
47 | There are the bind event functions.
48 | Contains `onFocusIn` and `onFocusOut` event listeners.
49 |
50 |
51 |
--------------------------------------------------------------------------------
/docs/components/Form.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Form
3 | menu: 3. Form Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { Form } from '../../dist/react-powerplug.esm'
8 |
9 | # Form
10 |
11 | The Form component is used for form with multiples fields and a submit handler
12 |
13 | ## Usage
14 |
15 | ```js
16 | import { Form } from 'react-powerplug'
17 | ```
18 |
19 | ```jsx
20 |
40 | )}
41 |
42 | ```
43 |
44 | ## Props
45 |
46 |
47 |
48 | Specifies the initial state for fields in the form (Object with keys is name of fields)
49 |
50 |
51 | The onChange event of the Value is called whenever the on state changes.
52 |
53 |
54 | Receive state as function. It can also be `render` prop.
55 |
56 |
57 |
58 | ## Children Props
59 |
60 |
--------------------------------------------------------------------------------
/docs/components/Hover.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Hover
3 | menu: 2. Feedback Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { Hover } from '../../dist/react-powerplug.esm'
8 |
9 | # Hover
10 |
11 | The Hover component is used to known when user is hovering some element.
12 | It's the same as `:hover` pseudo selector from css.
13 |
14 | ```js
15 | import { Hover } from 'react-powerplug'
16 | ```
17 |
18 | ```jsx
19 |
20 | {({ hovered, bind }) => (
21 |
22 | You are {hovered ? 'hovering' : 'not hovering'} this div.
23 |
24 | )}
25 |
26 | ```
27 |
28 | ## Props
29 |
30 |
31 |
32 | The onChange event of the Hover is called whenever the `hovered` state changes.
33 |
34 |
35 | Receive state as function. It can also be `render` prop.
36 |
37 |
38 |
39 | ## Children Props
40 |
41 |
42 |
43 | True if is hovering the binded element
44 |
45 |
46 | There are the bind event functions.
47 | Contains `onMouseEnter` and `onMouseLeave` event listeners.
48 |
49 |
50 |
--------------------------------------------------------------------------------
/docs/components/Interval.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Interval
3 | menu: 4. Utils Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { Interval } from '../../dist/react-powerplug.esm'
8 |
9 | # Interval
10 |
11 | The Interval component is used for when it's necessary to re-render at regular intervals. Also known as Frame.
12 |
13 | ## Usage
14 |
15 | ```js
16 | import { Interval } from 'react-powerplug'
17 | ```
18 |
19 | ```jsx
20 |
21 | {({ start, stop }) => (
22 | <>
23 | The time is now {new Date().toLocaleTimeString()}
24 | stop()}>Stop interval
25 | resume()}>Start interval
26 | >
27 | )}
28 |
29 | ```
30 |
31 | ## Props
32 |
33 |
34 |
35 | Specifies the delay (for `setInterval`) between re-renders in milliseconds.
36 | The interval will be reset any time this prop changes.
37 | Whenever `delay` is not a finite number, no interval will be set and `Interval` will
38 | not automatically re-render.
39 |
40 |
41 | Receive state as function. It can also be `render` prop.
42 |
43 |
44 |
45 | ## Children Props
46 |
47 |
48 |
49 | Start (or resume) re-renders intervals at defined delay (if not passed delay arg it will be used from props).
50 | Good way to change delay time when needed.
51 |
52 |
53 | Stop (or pause) re-renders intervals
54 |
55 |
56 | Start or Stop re-renders intervals based on current status
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/docs/components/List.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: List
3 | menu: 1. State Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { List } from '../../dist/react-powerplug.esm'
8 |
9 | # List
10 |
11 | The List component is used to work with an array.
12 | *For unique arrays see Set.*
13 |
14 | ## Usage
15 |
16 | ```js
17 | import { List } from 'react-powerplug'
18 | ```
19 |
20 | ```jsx
21 |
22 | {({ list, pull, push }) => (
23 |
24 |
25 | {list.map(({ tag }) => (
26 | pull(value => value === tag)}>
27 | {tag}
28 |
29 | ))}
30 |
31 | )}
32 |
33 | ```
34 |
35 | ## Props
36 |
37 |
38 |
39 | Specifies the initial `list` state.
40 |
41 |
42 | The onChange event of the List is called whenever the `list` state changes.
43 |
44 |
45 | Receive state as function. It can also be `render` prop.
46 |
47 |
48 |
49 | ## Children Props
50 |
51 |
52 |
53 | Your `list` state value
54 |
55 |
56 | Get first element of your `list` array
57 |
58 |
59 | Get last element of your `list` array
60 |
61 |
62 | Add one or more items to your `list` array
63 |
64 |
65 | Remove an item based on a predicate function.
66 | All matched items are removed.
67 |
68 |
69 | Use Array.prototype.sort {' '}
70 | to sort your `list` state.
71 |
72 |
73 | Set a new full `list` array to your state
74 |
75 |
76 | Reset `list` to initial state
77 |
78 |
79 |
--------------------------------------------------------------------------------
/docs/components/Map.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Map
3 | menu: 1. State Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { Map } from '../../dist/react-powerplug.esm'
8 |
9 | # Map
10 |
11 | The Map component is a generic used for a free key-value logics.
12 |
13 | ```js
14 | import { Map } from 'react-powerplug'
15 | ```
16 |
17 | ```jsx
18 |
19 | {({ set, get }) => (
20 |
21 | set('sounds', c)}>
22 | Game Sounds
23 |
24 | set('music', c)}>
25 | Bg Music
26 |
27 | set('graphics', value)}
32 | />
33 |
34 | )}
35 |
36 | ```
37 |
38 | ## Props
39 |
40 |
41 |
42 | Specifies the initial `values` state.
43 |
44 |
45 | The onChange event of the Map is called whenever the `values` state changes.
46 |
47 |
48 | Receive state as function. It can also be `render` prop.
49 |
50 |
51 |
52 | ## Map Children Props
53 |
54 |
55 |
56 | Your `values` state value
57 |
58 |
59 | Assigns `value` to `key`
60 |
61 |
62 | Takes a `key` and `function`, map a function over key's `value`
63 |
64 |
65 | Get assigned `value` from `key`
66 |
67 |
68 | Reset `values` to initial state
69 |
70 |
71 |
--------------------------------------------------------------------------------
/docs/components/Set.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Set
3 | menu: 1. State Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { Set } from '../../dist/react-powerplug.esm'
8 |
9 | # Set
10 |
11 | The Set component is used to work with an array of **unique** values.
12 |
13 | ## Usage
14 |
15 | ```js
16 | import { Set } from 'react-powerplug'
17 | ```
18 |
19 | ```jsx
20 |
21 | {({ values, remove, add }) => (
22 |
23 |
24 | {values.map(tag => (
25 | remove(tag)}>{tag}
26 | ))}
27 |
28 | )}
29 |
30 | ```
31 |
32 | ## Props
33 |
34 |
35 |
36 | Specifies the initial `values` state.
37 |
38 |
39 | The onChange event of the Set is called whenever the `values` state changes.
40 |
41 |
42 | Receive state as function. It can also be `render` prop.
43 |
44 |
45 |
46 | ## Children Props
47 |
48 |
49 |
50 | Your `values` state value
51 |
52 |
53 | Add a unique `value` to your values array.
54 | Does nothing if values array already includes a `value`.
55 |
56 |
57 | Remove a `value` from your `values` array
58 |
59 |
60 | True if `values` array includes a `value`
61 |
62 |
63 | Set `values` state to an empty array
64 |
65 |
66 | Reset `values` to initial state
67 |
68 |
69 |
--------------------------------------------------------------------------------
/docs/components/State.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: State
3 | menu: 1. State Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { State } from '../../dist/react-powerplug.esm'
8 |
9 | # State
10 |
11 | A generic component for storing arbitrary object state.
12 | Equals to React.Component state/setState.
13 |
14 | ## Usage
15 |
16 | ```js
17 | import { State } from 'react-powerplug'
18 | ```
19 |
20 | ```jsx
21 |
22 | {({ state, setState }) => {
23 | const onStart = data => setState({ loading: true })
24 | const onFinish = data => setState({ data, loading: false })
25 |
26 | return (
27 |
28 | )
29 | }}
30 |
31 | ```
32 |
33 | ## Props
34 |
35 |
36 |
37 | Specifies the initial `state`
38 |
39 |
40 | The onChange event of the State is called whenever the state changes
41 |
42 |
43 | Receive state as function. It can also be `render` prop.
44 |
45 |
46 |
47 | ## Children Props
48 |
49 |
50 |
51 | Your `state` value
52 |
53 |
54 | State setter. Acts equals to the{' '}
55 | setState from React.Component.
56 |
57 |
58 | Reset to initial state
59 |
60 |
61 |
62 |
--------------------------------------------------------------------------------
/docs/components/Toggle.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Toggle
3 | menu: 1. State Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { Toggle } from '../../dist/react-powerplug.esm'
8 |
9 | # Toggle
10 |
11 | The Toggle component is used to toggle a boolean. True/false.
12 |
13 |
14 | ## Usage
15 |
16 | ```js
17 | import { Toggle } from 'react-powerplug'
18 | ```
19 |
20 | ```jsx
21 |
22 | {({ on, toggle }) => (
23 |
24 | )}
25 |
26 | ```
27 |
28 | ## Props
29 |
30 |
31 |
32 | Specifies the initial `on` state.
33 |
34 |
35 | The onChange event of the Toggle is called whenever the on state changes.
36 |
37 |
38 | Receive state as function. It can also be `render` prop.
39 |
40 |
41 |
42 | ## Children Props
43 |
44 |
45 |
46 | Your `on` state value
47 |
48 |
49 | Toggle your `on` state value
50 |
51 |
52 | Arbitrary set a value to `on` state
53 |
54 |
55 | Set `on` value to true
56 |
57 |
58 | Set `on` value to false
59 |
60 |
61 | Reset `on` to initial state
62 |
63 |
64 |
--------------------------------------------------------------------------------
/docs/components/Touch.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Touch
3 | menu: 2. Feedback Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { Touch } from '../../dist/react-powerplug.esm'
8 |
9 | # Touch
10 |
11 | The Touch component is used to known when user is touching some element.
12 |
13 | ```js
14 | import { Touch } from 'react-powerplug'
15 | ```
16 |
17 | ```jsx
18 |
19 | {({ touched, bind }) => (
20 |
21 | You are {touched ? 'touching' : 'not touching'} this div.
22 |
23 | )}
24 |
25 | ```
26 |
27 | ## Props
28 |
29 |
30 |
31 | The onChange event of the Touch is called whenever the `touched` state changes.
32 |
33 |
34 | Receive state as function. It can also be `render` prop.
35 |
36 |
37 |
38 | ## Children Props
39 |
40 |
41 |
42 | True if is touching the binded element
43 |
44 |
45 | There are the bind event functions.
46 | Contains `onTouchStart` and `onTouchEnd` event listeners.
47 |
48 |
49 |
--------------------------------------------------------------------------------
/docs/components/Value.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: Value
3 | menu: 1. State Containers
4 | ---
5 |
6 | import { Props, Prop, ChildrenProps, ChildrenProp } from '../_ui/PropsTable'
7 | import { Value } from '../../dist/react-powerplug.esm'
8 |
9 | # Value
10 |
11 | A generic component for storing raw data values.
12 |
13 | ## Usage
14 |
15 | ```js
16 | import { Value } from 'react-powerplug'
17 | ```
18 |
19 | ```jsx
20 |
21 | {({ value, set, reset }) => (
22 | <>
23 |
29 | Reset to initial
30 | >
31 | )}
32 |
33 | ```
34 |
35 | ```jsx
36 |
37 | {({ value, set }) => {
38 | const bindRadio = radioValue => ({
39 | selected: value === radioValue,
40 | onClick: () => set(radioValue),
41 | })
42 |
43 | return (
44 |
49 | )
50 | }}
51 |
52 | ```
53 |
54 | ## Props
55 |
56 |
57 |
58 | Specifies the initial `value` state.
59 |
60 |
61 | The onChange event of the Value is called whenever the on state changes.
62 |
63 |
64 | Receive state as function. It can also be `render` prop.
65 |
66 |
67 |
68 | ## Children Props
69 |
70 |
71 |
72 | Your `value` state value
73 |
74 |
75 | Arbitrary set a value to `value` state
76 |
77 |
78 | Reset `value` to initial state
79 |
80 |
81 |
--------------------------------------------------------------------------------
/docs/utils/compose.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: compose
3 | menu: 5. Utils
4 | ---
5 |
6 | # compose
7 |
8 | The compose utility is a factory version of Compose component.
9 | You can read more about in Compose docs.
10 |
11 | ```js
12 | import { compose, Counter, Toggle } from 'react-powerplug' // note lowercased (c)ompose
13 |
14 | // the order matters
15 | const ToggleCounter = compose(
16 | , // accept a element
17 | Toggle, // or just a component
18 | )
19 |
20 |
21 | {(counter, toggle) => {
22 | // counter.inc, counter.dec, counter.count
23 | // toggle.on, toggle.toggle, etc.
24 | }}
25 |
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/utils/composeEvents.mdx:
--------------------------------------------------------------------------------
1 | ---
2 | name: composeEvents
3 | menu: 5. Utils
4 | ---
5 |
6 | # composeEvents
7 |
8 | The composeEvents utility helps you when you need to pass the same callback more than once.
9 |
10 | ```jsx
11 | import { Hover, composeEvents } from 'react-powerplug'
12 |
13 | const HoveredDiv = ({ children, onMouseEnter, onMouseLeave, ...restProps }) => (
14 |
15 | {({ hovered, bind }) => (
16 |
21 | )}
22 |
23 | )
24 | ```
25 |
26 | It's just merge array of events object into single one.
27 |
28 | ```jsx
29 | const callbacks = composeEvents(
30 | {
31 | onMouseEnter: event => console.log('first call', event),
32 | onMouseLeave: event => console.log('first call', event),
33 | },
34 | {
35 | onMouseEnter: event => console.log('second call', event),
36 | }
37 | )
38 |
39 | /**
40 | * callbacks = {
41 | * onMouseEnter: Function,
42 | * onMouseLeave: Function
43 | * }
44 | */
45 |
46 |
47 |
48 |
49 | ```
50 |
--------------------------------------------------------------------------------
/doczrc.js:
--------------------------------------------------------------------------------
1 | import pkg from './package.json'
2 |
3 | export default {
4 | title: 'React PowerPlug',
5 | description: pkg.description,
6 | base: `/${pkg.name}/`,
7 | version: pkg.version,
8 | propsParser: false,
9 | hashRouter: true,
10 | themeConfig: {
11 | logo: {
12 | src:
13 | 'https://raw.githubusercontent.com/renatorib/react-powerplug/master/logo.png',
14 | width: 231,
15 | },
16 | colors: {
17 | primary: '#3E9DE1',
18 | },
19 | },
20 | }
21 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/renatorib/react-powerplug/8da1bdab0860b958893c7664a4d69616ea04f1d2/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-powerplug",
3 | "version": "1.0.0",
4 | "description": "Give life to your dumb components",
5 | "author": "Renato Ribeiro (http://github.com/renatorib)",
6 | "license": "MIT",
7 | "main": "dist/react-powerplug.cjs.js",
8 | "module": "dist/react-powerplug.esm.js",
9 | "types": "types/index.d.ts",
10 | "files": [
11 | "dist",
12 | "src",
13 | "types"
14 | ],
15 | "scripts": {
16 | "build:flow": "echo \"// @flow\n\nexport * from '../src'\" > dist/react-powerplug.cjs.js.flow",
17 | "build:code": "rollup -c",
18 | "build": "npm run clean && npm run build:code && npm run build:flow",
19 | "clean": "rimraf dist",
20 | "typecheck:flow": "flow check --max-warnings=0",
21 | "typecheck:ts": "dtslint types",
22 | "lint": "eslint src tests",
23 | "test:only": "jest",
24 | "test:umd": "jest --setupTestFrameworkScriptFile ./tests/jestUMDSetup.js",
25 | "test:cjs": "jest --setupTestFrameworkScriptFile ./tests/jestCJSSetup.js",
26 | "test": "npm run build && npm run lint && jest && npm run test:umd && npm run test:cjs",
27 | "precommit": "lint-staged",
28 | "prepublishOnly": "npm run clean && npm run build",
29 | "contributors:add": "all-contributors add",
30 | "contributors:generate": "all-contributors generate",
31 | "docz:dev": "docz dev",
32 | "docz:build": "docz build",
33 | "docz:publish": "npm run docz:build && gh-pages -d .docz/dist"
34 | },
35 | "lint-staged": {
36 | "*.{js,md,ts,tsx}": [
37 | "prettier --write",
38 | "git add"
39 | ]
40 | },
41 | "jest": {
42 | "globalSetup": "jest-environment-puppeteer/setup",
43 | "globalTeardown": "jest-environment-puppeteer/teardown",
44 | "transformIgnorePatterns": [
45 | "/node_modules/",
46 | "/dist/"
47 | ]
48 | },
49 | "repository": {
50 | "type": "git",
51 | "url": "https://github.com/renatorib/react-powerplug.git"
52 | },
53 | "keywords": [
54 | "react",
55 | "reactjs",
56 | "components",
57 | "dumb",
58 | "state"
59 | ],
60 | "bugs": {
61 | "url": "https://github.com/renatorib/react-powerplug/issues"
62 | },
63 | "homepage": "https://github.com/renatorib/react-powerplug",
64 | "dependencies": {
65 | "@babel/runtime": "^7.0.0"
66 | },
67 | "devDependencies": {
68 | "@babel/core": "^7.0.0",
69 | "@babel/plugin-proposal-class-properties": "^7.0.0",
70 | "@babel/plugin-transform-runtime": "^7.0.0",
71 | "@babel/preset-env": "^7.0.0",
72 | "@babel/preset-react": "^7.0.0",
73 | "@types/react": "^16.3.13",
74 | "all-contributors-cli": "^4.11.2",
75 | "babel-core": "^7.0.0-bridge.0",
76 | "babel-eslint": "^9.0.0",
77 | "babel-jest": "^23.0.0",
78 | "cross-env": "^5.0.5",
79 | "docz": "0.11.2",
80 | "docz-core": "0.11.2",
81 | "docz-theme-default": "0.11.2",
82 | "dtslint": "^0.3.0",
83 | "eslint": "^5.3.0",
84 | "eslint-plugin-flowtype": "^2.50.0",
85 | "eslint-plugin-import": "^2.13.0",
86 | "eslint-plugin-react": "^7.10.0",
87 | "flow-bin": "^0.82.0",
88 | "gh-pages": "^2.0.0",
89 | "husky": "^0.14.3",
90 | "jest": "^23.0.0",
91 | "jest-environment-node": "^23.0.0",
92 | "jest-environment-puppeteer": "^2.4.0",
93 | "lint-staged": "^6.0.0",
94 | "prettier": "^1.10.2",
95 | "puppeteer": "^1.4.0",
96 | "react": "^16.4.2",
97 | "react-dom": "^16.4.2",
98 | "react-test-renderer": "^16.2.0",
99 | "rimraf": "^2.6.1",
100 | "rollup": "^0.65.0",
101 | "rollup-plugin-babel": "^4.0.1",
102 | "rollup-plugin-node-resolve": "^3.3.0",
103 | "rollup-plugin-replace": "^2.0.0",
104 | "rollup-plugin-size-snapshot": "^0.6.1",
105 | "rollup-plugin-uglify": "^4.0.0"
106 | },
107 | "peerDependencies": {
108 | "react": "^0.14.0 || ^15.0.0-0 || ^16.0.0-0"
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import nodeResolve from 'rollup-plugin-node-resolve'
2 | import babel from 'rollup-plugin-babel'
3 | import replace from 'rollup-plugin-replace'
4 | import { uglify } from 'rollup-plugin-uglify'
5 | import { sizeSnapshot } from 'rollup-plugin-size-snapshot'
6 | import pkg from './package.json'
7 |
8 | const input = './src/index.js'
9 |
10 | const external = id => !id.startsWith('.') && !id.startsWith('/')
11 |
12 | const name = 'ReactPowerPlug'
13 |
14 | const globals = { react: 'React' }
15 |
16 | const getBabelOptions = ({ useESModules }) => ({
17 | exclude: '**/node_modules/**',
18 | runtimeHelpers: true,
19 | plugins: [['@babel/plugin-transform-runtime', { useESModules }]],
20 | })
21 |
22 | export default [
23 | {
24 | input,
25 | output: {
26 | file: 'dist/react-powerplug.umd.js',
27 | format: 'umd',
28 | name,
29 | globals,
30 | },
31 | external: Object.keys(globals),
32 | plugins: [
33 | nodeResolve(),
34 | babel(getBabelOptions({ useESModules: true })),
35 | replace({ 'process.env.NODE_ENV': JSON.stringify('development') }),
36 | sizeSnapshot(),
37 | ],
38 | },
39 |
40 | {
41 | input,
42 | output: {
43 | file: 'dist/react-powerplug.min.js',
44 | format: 'umd',
45 | name,
46 | globals,
47 | },
48 | external: Object.keys(globals),
49 | plugins: [
50 | nodeResolve(),
51 | babel(getBabelOptions({ useESModules: true })),
52 | replace({ 'process.env.NODE_ENV': JSON.stringify('production') }),
53 | uglify({
54 | compress: {
55 | pure_getters: true,
56 | unsafe: true,
57 | unsafe_comps: true,
58 | warnings: false,
59 | },
60 | }),
61 | ],
62 | },
63 |
64 | {
65 | input,
66 | output: { file: pkg.main, format: 'cjs' },
67 | external,
68 | plugins: [babel(getBabelOptions({ useESModules: false })), sizeSnapshot()],
69 | },
70 |
71 | {
72 | input,
73 | output: { file: pkg.module, format: 'es' },
74 | external,
75 | plugins: [babel(getBabelOptions({ useESModules: true })), sizeSnapshot()],
76 | },
77 | ]
78 |
--------------------------------------------------------------------------------
/src/components/Active.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const Active = ({ onChange, ...props }) => (
6 |
7 | {({ value, set }) =>
8 | renderProps(props, {
9 | active: value,
10 | bind: {
11 | onMouseDown: () => set(true),
12 | onMouseUp: () => set(false),
13 | },
14 | })
15 | }
16 |
17 | )
18 |
19 | export default Active
20 |
--------------------------------------------------------------------------------
/src/components/Compose.js:
--------------------------------------------------------------------------------
1 | import compose from '../utils/compose'
2 |
3 | const Compose = ({ components, ...props }) => compose(...components)(props)
4 |
5 | export default Compose
6 |
--------------------------------------------------------------------------------
/src/components/Counter.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const add = amount => value => value + amount
6 |
7 | const Counter = ({ initial = 0, onChange, ...props }) => (
8 |
9 | {({ value, set, reset }) =>
10 | renderProps(props, {
11 | count: value,
12 | inc: () => set(add(1)),
13 | dec: () => set(add(-1)),
14 | incBy: value => set(add(value)),
15 | decBy: value => set(add(-value)),
16 | set: value => set(value),
17 | reset,
18 | })
19 | }
20 |
21 | )
22 |
23 | export default Counter
24 |
--------------------------------------------------------------------------------
/src/components/Field.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const isObject = value => typeof value === 'object' && value
6 |
7 | const Input = ({ initial = '', onChange, ...props }) => (
8 |
9 | {({ value, set, reset }) =>
10 | renderProps(props, {
11 | value,
12 | reset,
13 | set,
14 | bind: {
15 | value,
16 | onChange: event => {
17 | if (isObject(event) && isObject(event.target)) {
18 | set(event.target.value)
19 | } else {
20 | set(event)
21 | }
22 | },
23 | },
24 | })
25 | }
26 |
27 | )
28 |
29 | export default Input
30 |
--------------------------------------------------------------------------------
/src/components/Focus.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const Focus = ({ onChange, ...props }) => (
6 |
7 | {({ value, set }) =>
8 | renderProps(props, {
9 | focused: value,
10 | bind: {
11 | onFocus: () => set(true),
12 | onBlur: () => set(false),
13 | },
14 | })
15 | }
16 |
17 | )
18 |
19 | export default Focus
20 |
--------------------------------------------------------------------------------
/src/components/FocusManager.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const FocusManager = ({ onChange, ...props }) => {
6 | let canBlur = true
7 | return (
8 |
9 | {({ value, set }) =>
10 | renderProps(props, {
11 | focused: value,
12 | blur: () => {
13 | if (value) {
14 | document.activeElement.blur()
15 | }
16 | },
17 | bind: {
18 | tabIndex: -1,
19 | onBlur: () => {
20 | if (canBlur) {
21 | set(false)
22 | }
23 | },
24 | onFocus: () => {
25 | set(true)
26 | },
27 | onMouseDown: () => {
28 | canBlur = false
29 | },
30 | onMouseUp: () => {
31 | canBlur = true
32 | },
33 | },
34 | })
35 | }
36 |
37 | )
38 | }
39 |
40 | export default FocusManager
41 |
--------------------------------------------------------------------------------
/src/components/Form.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const isObject = value => typeof value === 'object' && value
6 |
7 | const Form = ({ initial = {}, onChange, ...props }) => (
8 |
9 | {({ value: values, set, reset }) =>
10 | renderProps(props, {
11 | values,
12 |
13 | reset,
14 |
15 | setValues: nextValues =>
16 | set(prev => ({
17 | ...prev,
18 | ...(typeof nextValues === 'function'
19 | ? nextValues(prev)
20 | : nextValues),
21 | })),
22 |
23 | field: id => {
24 | const value = values[id]
25 | const setValue = updater =>
26 | typeof updater === 'function'
27 | ? set(prev => ({ ...prev, [id]: updater(prev[id]) }))
28 | : set({ ...values, [id]: updater })
29 |
30 | return {
31 | value,
32 | set: setValue,
33 | bind: {
34 | value,
35 | onChange: event => {
36 | if (isObject(event) && isObject(event.target)) {
37 | setValue(event.target.value)
38 | } else {
39 | setValue(event)
40 | }
41 | },
42 | },
43 | }
44 | },
45 | })
46 | }
47 |
48 | )
49 |
50 | export default Form
51 |
--------------------------------------------------------------------------------
/src/components/Hover.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const Hover = ({ onChange, ...props }) => (
6 |
7 | {({ value, set }) =>
8 | renderProps(props, {
9 | hovered: value,
10 | bind: {
11 | onMouseEnter: () => set(true),
12 | onMouseLeave: () => set(false),
13 | },
14 | })
15 | }
16 |
17 | )
18 |
19 | export default Hover
20 |
--------------------------------------------------------------------------------
/src/components/Interval.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react'
2 | import renderProps from '../utils/renderProps'
3 |
4 | class Interval extends Component {
5 | state = {
6 | times: 0,
7 | }
8 |
9 | intervalId = undefined
10 |
11 | _clearIntervalIfNecessary = () => {
12 | if (this.intervalId) {
13 | this.intervalId = clearInterval(this.intervalId)
14 | }
15 | }
16 |
17 | _setIntervalIfNecessary = delay => {
18 | if (Number.isFinite(delay)) {
19 | this._clearIntervalIfNecessary()
20 | this.intervalId = setInterval(
21 | () => this.setState(s => ({ times: s.times + 1 })),
22 | delay
23 | )
24 | }
25 | }
26 |
27 | stop = () => {
28 | this._clearIntervalIfNecessary()
29 | }
30 |
31 | start = delay => {
32 | const _delay =
33 | typeof delay === 'number'
34 | ? delay
35 | : this.props.delay != null ? this.props.delay : 1000
36 | this._setIntervalIfNecessary(_delay)
37 | }
38 |
39 | toggle = () => {
40 | this.intervalId ? this.stop() : this.start()
41 | }
42 |
43 | componentDidMount() {
44 | this.start()
45 | }
46 |
47 | componentDidUpdate(prevProps) {
48 | if (prevProps.delay !== this.props.delay) {
49 | this.stop()
50 | this.start()
51 | }
52 | }
53 |
54 | componentWillUnmount() {
55 | this.stop()
56 | }
57 |
58 | render() {
59 | return renderProps(this.props, {
60 | start: this.start,
61 | stop: this.stop,
62 | toggle: this.toggle,
63 | })
64 | }
65 | }
66 |
67 | export default Interval
68 |
--------------------------------------------------------------------------------
/src/components/List.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const complement = fn => (...args) => !fn(...args)
6 |
7 | const List = ({ initial = [], onChange, ...props }) => (
8 |
9 | {({ value, set, reset }) =>
10 | renderProps(props, {
11 | list: value,
12 | first: () => value[0],
13 | last: () => value[Math.max(0, value.length - 1)],
14 | set: list => set(list),
15 | push: (...values) => set(list => [...list, ...values]),
16 | pull: predicate => set(list => list.filter(complement(predicate))),
17 | sort: compareFn => set(list => [...list].sort(compareFn)),
18 | reset,
19 | })
20 | }
21 |
22 | )
23 |
24 | export default List
25 |
--------------------------------------------------------------------------------
/src/components/Map.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const Map = ({ initial = {}, onChange, ...props }) => (
6 |
7 | {({ value: values, set, reset }) =>
8 | renderProps(props, {
9 | values,
10 | clear: () => set({}),
11 | reset,
12 | set: (key, updater) =>
13 | set(prev => ({
14 | ...prev,
15 | [key]: typeof updater === 'function' ? updater(prev[key]) : updater,
16 | })),
17 | get: key => values[key],
18 | has: key => values[key] != null,
19 | delete: key => set(({ [key]: deleted, ...prev }) => prev),
20 | })
21 | }
22 |
23 | )
24 |
25 | export default Map
26 |
--------------------------------------------------------------------------------
/src/components/Set.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const unique = arr => arr.filter((d, i) => arr.indexOf(d) === i)
6 | const hasItem = (arr, item) => arr.indexOf(item) !== -1
7 | const removeItem = (arr, item) =>
8 | hasItem(arr, item) ? arr.filter(d => d !== item) : arr
9 | const addUnique = (arr, item) => (hasItem(arr, item) ? arr : [...arr, item])
10 |
11 | const Set = ({ initial = [], onChange, ...props }) => (
12 |
13 | {({ value, set, reset }) =>
14 | renderProps(props, {
15 | values: value,
16 | add: key => set(values => addUnique(values, key)),
17 | clear: () => set([]),
18 | remove: key => set(values => removeItem(values, key)),
19 | has: key => hasItem(value, key),
20 | reset,
21 | })
22 | }
23 |
24 | )
25 |
26 | export default Set
27 |
--------------------------------------------------------------------------------
/src/components/State.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const State = ({ initial = {}, onChange, ...props }) => (
6 |
7 | {({ value, set, reset }) =>
8 | renderProps(props, {
9 | state: value,
10 | setState: (updater, cb) =>
11 | set(
12 | prev => ({
13 | ...prev,
14 | ...(typeof updater === 'function' ? updater(prev) : updater),
15 | }),
16 | cb
17 | ),
18 | resetState: reset,
19 | })
20 | }
21 |
22 | )
23 |
24 | export default State
25 |
--------------------------------------------------------------------------------
/src/components/Toggle.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const Toggle = ({ initial = false, onChange, ...props }) => (
6 |
7 | {({ value, set, reset }) =>
8 | renderProps(props, {
9 | on: value,
10 | set: value => set(value),
11 | reset,
12 | toggle: () => set(on => !on),
13 | setOn: () => set(true),
14 | setOff: () => set(false),
15 | })
16 | }
17 |
18 | )
19 |
20 | export default Toggle
21 |
--------------------------------------------------------------------------------
/src/components/Touch.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import Value from './Value'
3 | import renderProps from '../utils/renderProps'
4 |
5 | const Touch = ({ onChange, ...props }) => (
6 |
7 | {({ value, set }) =>
8 | renderProps(props, {
9 | touched: value,
10 | bind: {
11 | onTouchStart: () => set(true),
12 | onTouchEnd: () => set(false),
13 | },
14 | })
15 | }
16 |
17 | )
18 |
19 | export default Touch
20 |
--------------------------------------------------------------------------------
/src/components/Value.js:
--------------------------------------------------------------------------------
1 | import { Component } from 'react'
2 | import renderProps from '../utils/renderProps'
3 |
4 | const noop = () => {}
5 |
6 | class Value extends Component {
7 | state = {
8 | value: this.props.initial,
9 | }
10 |
11 | _set = (updater, cb = noop) => {
12 | const { onChange = noop } = this.props
13 |
14 | this.setState(
15 | typeof updater === 'function'
16 | ? state => ({ value: updater(state.value) })
17 | : { value: updater },
18 | () => {
19 | onChange(this.state.value)
20 | cb()
21 | }
22 | )
23 | }
24 | _reset = (cb = noop) => {
25 | this._set(this.props.initial, cb)
26 | }
27 |
28 | render() {
29 | return renderProps(this.props, {
30 | value: this.state.value,
31 | set: this._set,
32 | reset: this._reset,
33 | })
34 | }
35 | }
36 |
37 | export default Value
38 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { default as Active } from './components/Active'
2 | export { default as Compose } from './components/Compose'
3 | export { default as Counter } from './components/Counter'
4 | export { default as Field } from './components/Field'
5 | export { default as Focus } from './components/Focus'
6 | export { default as unstable_FocusManager } from './components/FocusManager'
7 | export { default as Form } from './components/Form'
8 | export { default as Hover } from './components/Hover'
9 | export { default as Interval } from './components/Interval'
10 | export { default as List } from './components/List'
11 | export { default as Map } from './components/Map'
12 | export { default as Set } from './components/Set'
13 | export { default as State } from './components/State'
14 | export { default as Toggle } from './components/Toggle'
15 | export { default as Touch } from './components/Touch'
16 | export { default as Value } from './components/Value'
17 |
18 | export { default as compose } from './utils/compose'
19 | export { default as composeEvents } from './utils/composeEvents'
20 | export { default as renderProps } from './utils/renderProps'
21 |
--------------------------------------------------------------------------------
/src/index.js.flow:
--------------------------------------------------------------------------------
1 | // @flow
2 |
3 | import * as React from 'react'
4 |
5 | /* Utils */
6 |
7 | type Updater = (updater: (T => T) | T) => void
8 |
9 | type Reset = (cb?: () => void) => void
10 |
11 | /* Active */
12 |
13 | type ActiveChange = (active: boolean) => void
14 |
15 | type ActiveRender = ({|
16 | active: boolean,
17 | bind: {| onMouseDown: () => void, onMouseUp: () => void |},
18 | |}) => React.Node
19 |
20 | declare export var Active: React.ComponentType<
21 | | {| onChange?: ActiveChange, render: ActiveRender |}
22 | | {| onChange?: ActiveChange, children: ActiveRender |}
23 | >
24 |
25 | /* Compose */
26 |
27 | declare export var Compose: React.ComponentType
28 |
29 | /* Counter */
30 |
31 | type CounterChange = (count: number) => void
32 |
33 | type CounterRender = ({|
34 | count: number,
35 | inc: () => void,
36 | dec: () => void,
37 | incBy: (step: number) => void,
38 | decBy: (step: number) => void,
39 | reset: Reset,
40 | |}) => React.Node
41 |
42 | declare export var Counter: React.ComponentType<
43 | | {| initial?: number, onChange?: CounterChange, render: CounterRender |}
44 | | {| initial?: number, onChange?: CounterChange, children: CounterRender |}
45 | >
46 |
47 | /* Focus */
48 |
49 | type FocusChange = (focused: boolean) => void
50 |
51 | type FocusRender = ({|
52 | focused: boolean,
53 | bind: {| onFocus: () => void, onBlur: () => void |},
54 | |}) => React.Node
55 |
56 | declare export var Focus: React.ComponentType<
57 | | {| onChange?: FocusChange, render: FocusRender |}
58 | | {| onChange?: FocusChange, children: FocusRender |}
59 | >
60 |
61 | /* FocusManager */
62 |
63 | type FocusManagerChange = (focused: boolean) => void
64 |
65 | type FocusManagerRender = ({|
66 | focused: boolean,
67 | blur: () => void,
68 | bind: {|
69 | tabIndex: number,
70 | onFocus: () => void,
71 | onBlur: () => void,
72 | onMouseDown: () => void,
73 | onMouseUp: () => void,
74 | |},
75 | |}) => React.Node
76 |
77 | declare export var unstable_FocusManager: React.ComponentType<
78 | | {| onChange?: FocusManagerChange, render: FocusManagerRender |}
79 | | {| onChange?: FocusManagerChange, children: FocusManagerRender |}
80 | >
81 |
82 | /* Form */
83 |
84 | type FormChange = T => void
85 |
86 | type FormRender = ({|
87 | values: T,
88 | setValues: ((T => $Shape) | $Shape) => void,
89 | reset: Reset,
90 | field: >(
91 | key: K
92 | ) => {|
93 | value: $ElementType,
94 | set: Updater<$ElementType>,
95 | bind: {|
96 | value: $ElementType,
97 | onChange: (
98 | event: { target: { value: $ElementType } } | $ElementType
99 | ) => void,
100 | |},
101 | |},
102 | |}) => React.Node
103 |
104 | type FormProps =
105 | | {| initial: T, onChange?: FormChange, render: FormRender |}
106 | | {| initial: T, onChange?: FormChange, children: FormRender |}
107 |
108 | declare export class Form extends React.Component<
109 | FormProps
110 | > {}
111 |
112 | /* Hover */
113 |
114 | type HoverChange = (hovered: boolean) => void
115 |
116 | type HoverRender = ({|
117 | hovered: boolean,
118 | bind: {| onMouseEnter: () => void, onMouseLeave: () => void |},
119 | |}) => React.Node
120 |
121 | declare export var Hover: React.ComponentType<
122 | | {| onChange?: HoverChange, render: HoverRender |}
123 | | {| onChange?: HoverChange, children: HoverRender |}
124 | >
125 |
126 | /* Field */
127 |
128 | type FieldChange = (value: T) => void
129 |
130 | type FieldRender = ({|
131 | value: T,
132 | set: Updater,
133 | reset: Reset,
134 | bind: {| value: T, onChange: (SyntheticInputEvent<*>) => void |},
135 | |}) => React.Node
136 |
137 | type FieldProps =
138 | | {| initial?: T, onChange?: FieldChange, render: FieldRender |}
139 | | {| initial?: T, onChange?: FieldChange, children: FieldRender |}
140 |
141 | declare export class Field extends React.Component> {}
142 |
143 | /* List */
144 |
145 | type ListValues = $ReadOnlyArray
146 |
147 | type ListChange = (list: ListValues) => void
148 |
149 | type ListRender = ({|
150 | list: ListValues,
151 | first: () => T | void,
152 | last: () => T | void,
153 | set: Updater>,
154 | push: (...values: ListValues) => void,
155 | pull: (predicate: (T) => boolean) => void,
156 | sort: (compare: (a: T, b: T) => -1 | 0 | 1) => void,
157 | reset: Reset,
158 | |}) => React.Node
159 |
160 | type ListProps =
161 | | {|
162 | initial: ListValues,
163 | onChange?: ListChange,
164 | render: ListRender,
165 | |}
166 | | {|
167 | initial: ListValues,
168 | onChange?: ListChange,
169 | children: ListRender,
170 | |}
171 |
172 | declare export class List extends React.Component> {}
173 |
174 | /* Set */
175 |
176 | type SetValues = $ReadOnlyArray
177 |
178 | type SetChange = (values: SetValues) => void
179 |
180 | type SetRender = ({|
181 | values: SetValues,
182 | add: (key: T) => void,
183 | clear: () => void,
184 | remove: (key: T) => void,
185 | has: (key: T) => boolean,
186 | reset: Reset,
187 | |}) => React.Node
188 |
189 | type SetProps =
190 | | {| initial: SetValues, onChange?: SetChange, render: SetRender |}
191 | | {| initial: SetValues, onChange?: SetChange, children: SetRender |}
192 |
193 | declare export class Set extends React.Component> {}
194 |
195 | /* Map */
196 |
197 | type MapValues = { [key: string]: T }
198 |
199 | type MapChange = (MapValues) => void
200 |
201 | type MapRender = ({|
202 | values: MapValues,
203 | clear: () => void,
204 | reset: Reset,
205 | set: (key: string, value: (T => T) | T) => void,
206 | get: (key: string) => T,
207 | has: (key: string) => boolean,
208 | delete: (key: string) => void,
209 | |}) => React.Node
210 |
211 | type MapProps =
212 | | {| initial: MapValues, onChange?: MapChange, render: MapRender |}
213 | | {| initial: MapValues, onChange?: MapChange, children: MapRender |}
214 |
215 | declare export class Map extends React.Component> {}
216 |
217 | /* State */
218 |
219 | type StateChange = T => void
220 |
221 | type StateRender = ({|
222 | state: T,
223 | setState: (updater: (T => $Shape) | $Shape, cb?: () => void) => void,
224 | resetState: Reset,
225 | |}) => React.Node
226 |
227 | type StateProps =
228 | | {| initial: T, onChange?: StateChange, render: StateRender |}
229 | | {| initial: T, onChange?: StateChange, children: StateRender |}
230 |
231 | declare export class State extends React.Component> {}
232 |
233 | /* Interval */
234 |
235 | type IntervalRender = ({|
236 | start: (delay?: number) => void,
237 | stop: () => void,
238 | toggle: () => void,
239 | |}) => React.Node
240 |
241 | type IntervalProps =
242 | | {| delay?: ?number, render: IntervalRender |}
243 | | {| delay?: ?number, children: IntervalRender |}
244 |
245 | declare export class Interval extends React.Component {}
246 |
247 | /* Toggle */
248 |
249 | type ToggleChange = (on: boolean) => void
250 |
251 | type ToggleRender = ({|
252 | on: boolean,
253 | toggle: () => void,
254 | set: Updater,
255 | setOn: () => void,
256 | setOff: () => void,
257 | reset: Reset,
258 | |}) => React.Node
259 |
260 | declare export var Toggle: React.ComponentType<
261 | | {| initial?: boolean, onChange?: ToggleChange, render: ToggleRender |}
262 | | {| initial?: boolean, onChange?: ToggleChange, children: ToggleRender |}
263 | >
264 |
265 | /* Touch */
266 |
267 | type TouchChange = (touched: boolean) => void
268 |
269 | type TouchRender = ({|
270 | touched: boolean,
271 | bind: {| onTouchStart: () => void, onTouchEnd: () => void |},
272 | |}) => React.Node
273 |
274 | declare export var Touch: React.ComponentType<
275 | | {| onChange?: TouchChange, render: TouchRender |}
276 | | {| onChange?: TouchChange, children: TouchRender |}
277 | >
278 |
279 | /* Value */
280 |
281 | type ValueChange = (value: T) => void
282 |
283 | type ValueRender = ({|
284 | value: T,
285 | set: Updater,
286 | reset: Reset,
287 | |}) => React.Node
288 |
289 | type ValueProps =
290 | | {| initial: T, onChange?: ValueChange, render: ValueRender |}
291 | | {| initial: T, onChange?: ValueChange, children: ValueRender |}
292 |
293 | declare export class Value extends React.Component> {}
294 |
295 | /* composeEvents */
296 |
297 | type Events = { [name: string]: Function }
298 |
299 | declare export function composeEvents(...Array): Events
300 |
--------------------------------------------------------------------------------
/src/utils/compose.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import renderProps from './renderProps'
3 |
4 | const isElement = element => typeof element.type === 'function'
5 |
6 | const compose = (...elements) => {
7 | const reversedElements = elements.reverse()
8 |
9 | return composedProps => {
10 | // Stack children arguments recursively and pass
11 | // it down until the last component that render children
12 | // with these stacked arguments
13 | function stackProps(i, elements, propsList = []) {
14 | const element = elements[i]
15 | const isTheLast = i === 0
16 |
17 | // Check if is latest component.
18 | // If is latest then render children,
19 | // Otherwise continue stacking arguments
20 | const renderFn = props =>
21 | isTheLast
22 | ? renderProps(composedProps, ...propsList.concat(props))
23 | : stackProps(i - 1, elements, propsList.concat(props))
24 |
25 | // Clone a element if it's passed created as
26 | // Or create it if passed as just Element
27 | const elementFn = isElement(element)
28 | ? React.cloneElement
29 | : React.createElement
30 |
31 | return elementFn(element, {}, renderFn)
32 | }
33 |
34 | return stackProps(elements.length - 1, reversedElements)
35 | }
36 | }
37 |
38 | export default compose
39 |
--------------------------------------------------------------------------------
/src/utils/composeEvents.js:
--------------------------------------------------------------------------------
1 | const composeEvents = (...objEvents) => {
2 | return objEvents.reverse().reduce((allEvents, events) => {
3 | let append = {}
4 |
5 | for (const key in events) {
6 | append[key] = allEvents[key]
7 | ? // Already have this event: let's merge
8 | (...args) => {
9 | events[key](...args)
10 | allEvents[key](...args)
11 | }
12 | : // Don't have this event yet: just assign the event
13 | events[key]
14 | }
15 |
16 | return { ...allEvents, ...append }
17 | })
18 | }
19 |
20 | export default composeEvents
21 |
--------------------------------------------------------------------------------
/src/utils/renderProps.js:
--------------------------------------------------------------------------------
1 | import warn from './warn'
2 |
3 | const isFn = prop => typeof prop === 'function'
4 |
5 | /**
6 | * renderProps
7 | * is a render/children props interop.
8 | * will pick up the prop that was used,
9 | * or children if both are used
10 | */
11 |
12 | const renderProps = ({ children, render }, ...props) => {
13 | if (process.env.NODE_ENV !== 'production') {
14 | warn(
15 | isFn(children) && isFn(render),
16 | 'You are using the children and render props together.\n' +
17 | 'This is impossible, therefore, only the children will be used.'
18 | )
19 | }
20 |
21 | const fn = isFn(children) ? children : render
22 |
23 | return fn ? fn(...props) : null
24 | }
25 |
26 | export default renderProps
27 |
--------------------------------------------------------------------------------
/src/utils/warn.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | const warn = (condition, message, trace = true) => {
4 | if (condition) {
5 | console.warn(`[react-powerplug]: ${message}`)
6 | console.trace && trace && console.trace('Trace')
7 | }
8 | }
9 |
10 | export default warn
11 |
--------------------------------------------------------------------------------
/tests/components/Active.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Active } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test(' ', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | TestRenderer.create( )
9 |
10 | expect(renderFn).toBeCalledTimes(1)
11 | expect(renderFn).lastCalledWith(expect.objectContaining({ active: false }))
12 |
13 | lastCallArg(renderFn).bind.onMouseDown()
14 | expect(renderFn).toBeCalledTimes(2)
15 | expect(renderFn).lastCalledWith(expect.objectContaining({ active: true }))
16 |
17 | lastCallArg(renderFn).bind.onMouseUp()
18 | expect(renderFn).lastCalledWith(expect.objectContaining({ active: false }))
19 | })
20 |
21 | test(' ', () => {
22 | const renderFn = jest.fn().mockReturnValue(null)
23 | const onChangeFn = jest.fn()
24 | TestRenderer.create( )
25 |
26 | expect(onChangeFn).toBeCalledTimes(0)
27 |
28 | lastCallArg(renderFn).bind.onMouseDown()
29 | expect(onChangeFn).toBeCalledTimes(1)
30 | expect(onChangeFn).lastCalledWith(true)
31 | })
32 |
--------------------------------------------------------------------------------
/tests/components/Compose.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Compose, Counter, Toggle } from '../../src'
4 | import { lastCallArgs } from './utils'
5 |
6 | test(' ', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 |
9 | TestRenderer.create(
10 |
11 | )
12 |
13 | expect(renderFn).toBeCalledTimes(1)
14 | expect(renderFn).lastCalledWith(
15 | expect.objectContaining({ count: 0 }),
16 | expect.objectContaining({ on: false })
17 | )
18 |
19 | lastCallArgs(renderFn)[0].inc()
20 | lastCallArgs(renderFn)[0].incBy(3)
21 | lastCallArgs(renderFn)[1].toggle()
22 |
23 | expect(renderFn).toBeCalledTimes(4)
24 | expect(renderFn).lastCalledWith(
25 | expect.objectContaining({ count: 4 }),
26 | expect.objectContaining({ on: true })
27 | )
28 | })
29 |
--------------------------------------------------------------------------------
/tests/components/Counter.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Counter } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test(' ', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | const testRenderer = TestRenderer.create( )
9 |
10 | expect(renderFn).toBeCalledTimes(1)
11 | expect(renderFn).lastCalledWith(expect.objectContaining({ count: 0 }))
12 |
13 | lastCallArg(renderFn).inc()
14 | expect(renderFn).lastCalledWith(expect.objectContaining({ count: 1 }))
15 |
16 | lastCallArg(renderFn).incBy(5)
17 | expect(renderFn).lastCalledWith(expect.objectContaining({ count: 6 }))
18 |
19 | lastCallArg(renderFn).dec()
20 | expect(renderFn).lastCalledWith(expect.objectContaining({ count: 5 }))
21 |
22 | lastCallArg(renderFn).decBy(3)
23 | expect(renderFn).lastCalledWith(expect.objectContaining({ count: 2 }))
24 |
25 | lastCallArg(renderFn).set(10)
26 | expect(renderFn).lastCalledWith(expect.objectContaining({ count: 10 }))
27 |
28 | lastCallArg(renderFn).set(count => count + 10)
29 | expect(renderFn).lastCalledWith(expect.objectContaining({ count: 20 }))
30 |
31 | testRenderer.update( )
32 | expect(renderFn).lastCalledWith(expect.objectContaining({ count: 20 }))
33 | lastCallArg(renderFn).reset()
34 | expect(renderFn).lastCalledWith(expect.objectContaining({ count: 100 }))
35 | })
36 |
37 | test(' ', () => {
38 | const renderFn = jest.fn().mockReturnValue(null)
39 | const onChangeFn = jest.fn()
40 | TestRenderer.create( )
41 |
42 | expect(onChangeFn).toBeCalledTimes(0)
43 |
44 | lastCallArg(renderFn).inc()
45 | expect(onChangeFn).toBeCalledTimes(1)
46 | expect(onChangeFn).lastCalledWith(1)
47 | })
48 |
--------------------------------------------------------------------------------
/tests/components/Field.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Field } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test(' ', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | const testRenderer = TestRenderer.create(
9 |
10 | )
11 |
12 | expect(renderFn).toBeCalledTimes(1)
13 | expect(renderFn).lastCalledWith(
14 | expect.objectContaining({
15 | value: 'init',
16 | bind: expect.objectContaining({ value: 'init' }),
17 | })
18 | )
19 |
20 | lastCallArg(renderFn).set('value2')
21 | expect(renderFn).toBeCalledTimes(2)
22 | expect(renderFn).lastCalledWith(
23 | expect.objectContaining({
24 | value: 'value2',
25 | bind: expect.objectContaining({ value: 'value2' }),
26 | })
27 | )
28 |
29 | lastCallArg(renderFn).bind.onChange({ target: { value: 'value3' } })
30 | expect(renderFn).toBeCalledTimes(3)
31 | expect(renderFn).lastCalledWith(
32 | expect.objectContaining({
33 | value: 'value3',
34 | bind: expect.objectContaining({ value: 'value3' }),
35 | })
36 | )
37 |
38 | testRenderer.update( )
39 | expect(renderFn).lastCalledWith(
40 | expect.objectContaining({
41 | value: 'value3',
42 | bind: expect.objectContaining({ value: 'value3' }),
43 | })
44 | )
45 | lastCallArg(renderFn).reset()
46 |
47 | expect(renderFn).lastCalledWith(
48 | expect.objectContaining({
49 | value: 'hello',
50 | bind: expect.objectContaining({ value: 'hello' }),
51 | })
52 | )
53 | })
54 |
55 | test(' ', () => {
56 | const renderFn = jest.fn().mockReturnValue(null)
57 | const onChangeFn = jest.fn()
58 | TestRenderer.create( )
59 |
60 | expect(onChangeFn).toBeCalledTimes(0)
61 |
62 | lastCallArg(renderFn).set('value')
63 | expect(onChangeFn).toBeCalledTimes(1)
64 | expect(onChangeFn).lastCalledWith('value')
65 | })
66 |
--------------------------------------------------------------------------------
/tests/components/Focus.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Focus } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test(' ', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | TestRenderer.create( )
9 |
10 | expect(renderFn).toBeCalledTimes(1)
11 | expect(renderFn).lastCalledWith(expect.objectContaining({ focused: false }))
12 |
13 | lastCallArg(renderFn).bind.onFocus()
14 | expect(renderFn).toBeCalledTimes(2)
15 | expect(renderFn).lastCalledWith(expect.objectContaining({ focused: true }))
16 |
17 | lastCallArg(renderFn).bind.onBlur()
18 | expect(renderFn).lastCalledWith(expect.objectContaining({ focused: false }))
19 | })
20 |
21 | test(' ', () => {
22 | const renderFn = jest.fn().mockReturnValue(null)
23 | const onChangeFn = jest.fn()
24 | TestRenderer.create( )
25 |
26 | expect(onChangeFn).toBeCalledTimes(0)
27 |
28 | lastCallArg(renderFn).bind.onFocus()
29 | expect(onChangeFn).toBeCalledTimes(1)
30 | expect(onChangeFn).lastCalledWith(true)
31 |
32 | lastCallArg(renderFn).bind.onBlur()
33 | expect(onChangeFn).toBeCalledTimes(2)
34 | expect(onChangeFn).lastCalledWith(false)
35 | })
36 |
--------------------------------------------------------------------------------
/tests/components/FocusManager.test.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @jest-environment jest-environment-puppeteer
3 | */
4 |
5 | const bootstrap = async () => {
6 | const page = await global.browser.newPage()
7 |
8 | const scripts = [
9 | './node_modules/react/umd/react.development.js',
10 | './node_modules/react-dom/umd/react-dom.development.js',
11 | './dist/react-powerplug.umd.js',
12 | ]
13 |
14 | await page.setViewport({ width: 1000, height: 1000 })
15 |
16 | for (const path of scripts) {
17 | await page.addScriptTag({ path })
18 | }
19 |
20 | await page.evaluate(() => {
21 | const container = document.createElement('div')
22 | if (document.body) {
23 | const body = document.body
24 | body.appendChild(container)
25 | body.style.margin = '0px'
26 | }
27 |
28 | window.render = component => {
29 | window.ReactDOM.render(component, container)
30 | }
31 | })
32 |
33 | return page
34 | }
35 |
36 | const delay = async timeout =>
37 | new Promise(resolve => setTimeout(resolve, timeout))
38 |
39 | test('keep focus when click on menu', async () => {
40 | const page = await bootstrap()
41 | const renderFn = jest.fn()
42 | await page.exposeFunction('renderFn', renderFn)
43 |
44 | await page.evaluate(() => {
45 | const React = window.React
46 | const FocusManager = window.ReactPowerPlug.unstable_FocusManager
47 |
48 | const style = { width: 100, height: 100 }
49 | const App = () => (
50 |
51 | {({ focused, bind }) => {
52 | window.renderFn({ focused })
53 | const props1 = Object.assign({ id: 'rect-1', style }, bind)
54 | const props2 = Object.assign({ id: 'rect-2', style }, bind)
55 | return (
56 | <>
57 |
58 | {focused &&
}
59 | >
60 | )
61 | }}
62 |
63 | )
64 |
65 | window.render( )
66 | })
67 |
68 | expect(renderFn).lastCalledWith({ focused: false })
69 | await page.click('#rect-1')
70 | expect(renderFn).lastCalledWith({ focused: true })
71 | await page.click('#rect-2')
72 | expect(renderFn).lastCalledWith({ focused: true })
73 | // click outside
74 | await page.mouse.click(200, 50)
75 | await delay(100)
76 | expect(renderFn).lastCalledWith({ focused: false })
77 | })
78 |
79 | test('remove focus and state after calling blur', async () => {
80 | const page = await bootstrap()
81 |
82 | await page.evaluate(() => {
83 | const React = window.React
84 | const FocusManager = window.ReactPowerPlug.unstable_FocusManager
85 |
86 | const App = () => (
87 |
88 | {({ focused, blur, bind }) => {
89 | window.blurFocusManager = blur
90 | const style = { width: 100, height: 100 }
91 | const props1 = Object.assign({ id: 'item1', style }, bind)
92 | const props2 = Object.assign(
93 | { id: 'item2', style, onClick: blur },
94 | bind
95 | )
96 | return (
97 | <>
98 | {focused ? 'focused' : 'blured'}
99 |
100 |
101 |
102 | >
103 | )
104 | }}
105 |
106 | )
107 |
108 | window.render( )
109 | })
110 |
111 | const getTextContent = node => node.textContent
112 | const isActiveElement = node => node === document.activeElement
113 |
114 | expect(await page.$eval('#result', getTextContent)).toEqual('blured')
115 |
116 | // focused on click
117 | await page.click('#item1')
118 | expect(await page.$eval('#result', getTextContent)).toEqual('focused')
119 | expect(await page.$eval('#item1', isActiveElement)).toEqual(true)
120 | expect(await page.$eval('#item2', isActiveElement)).toEqual(false)
121 |
122 | // blured on blur()
123 | await page.evaluate(() => window.blurFocusManager())
124 | expect(await page.$eval('#result', getTextContent)).toEqual('blured')
125 | expect(await page.$eval('#item1', isActiveElement)).toEqual(false)
126 | expect(await page.$eval('#item2', isActiveElement)).toEqual(false)
127 |
128 | // focused and immediately blured on click with blur() in it
129 | await page.click('#item2')
130 | expect(await page.$eval('#result', getTextContent)).toEqual('blured')
131 | expect(await page.$eval('#item1', isActiveElement)).toEqual(false)
132 | expect(await page.$eval('#item2', isActiveElement)).toEqual(false)
133 |
134 | // keep focus not registered in manager after blur()
135 | await page.click('#item3')
136 | expect(await page.$eval('#item3', isActiveElement)).toEqual(true)
137 | await page.evaluate(() => window.blurFocusManager())
138 | expect(await page.$eval('#item3', isActiveElement)).toEqual(true)
139 | })
140 |
--------------------------------------------------------------------------------
/tests/components/Form.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Form } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test('', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | const testRenderer = TestRenderer.create(
9 |
10 | )
11 |
12 | expect(renderFn).toBeCalledTimes(1)
13 | expect(renderFn).lastCalledWith(
14 | expect.objectContaining({ values: { prop1: '1', prop2: 2 } })
15 | )
16 |
17 | expect(lastCallArg(renderFn).field('prop1')).toEqual(
18 | expect.objectContaining({
19 | value: '1',
20 | bind: expect.objectContaining({ value: '1' }),
21 | })
22 | )
23 | expect(lastCallArg(renderFn).field('prop2')).toEqual(
24 | expect.objectContaining({
25 | value: 2,
26 | bind: expect.objectContaining({ value: 2 }),
27 | })
28 | )
29 |
30 | lastCallArg(renderFn)
31 | .field('prop1')
32 | .set('10')
33 | lastCallArg(renderFn)
34 | .field('prop2')
35 | .bind.onChange({ target: { value: 20 } })
36 |
37 | expect(lastCallArg(renderFn).field('prop1')).toEqual(
38 | expect.objectContaining({
39 | value: '10',
40 | bind: expect.objectContaining({ value: '10' }),
41 | })
42 | )
43 | expect(lastCallArg(renderFn).field('prop2')).toEqual(
44 | expect.objectContaining({
45 | value: 20,
46 | bind: expect.objectContaining({ value: 20 }),
47 | })
48 | )
49 |
50 | lastCallArg(renderFn)
51 | .field('prop1')
52 | .bind.onChange('100')
53 | lastCallArg(renderFn)
54 | .field('prop2')
55 | .bind.onChange({ target: 200 })
56 |
57 | expect(lastCallArg(renderFn).field('prop1')).toEqual(
58 | expect.objectContaining({
59 | value: '100',
60 | bind: expect.objectContaining({ value: '100' }),
61 | })
62 | )
63 | expect(lastCallArg(renderFn).field('prop2')).toEqual(
64 | expect.objectContaining({
65 | value: { target: 200 },
66 | bind: expect.objectContaining({ value: { target: 200 } }),
67 | })
68 | )
69 |
70 | testRenderer.update()
71 |
72 | expect(lastCallArg(renderFn).field('prop2')).toEqual(
73 | expect.objectContaining({
74 | value: { target: 200 },
75 | bind: expect.objectContaining({ value: { target: 200 } }),
76 | })
77 | )
78 |
79 | lastCallArg(renderFn).reset()
80 |
81 | expect(renderFn).lastCalledWith(
82 | expect.objectContaining({ values: { hello: 'world' } })
83 | )
84 | })
85 |
86 | test('Form setValues', () => {
87 | const renderFn = jest.fn().mockReturnValue(null)
88 | TestRenderer.create(
89 |
90 | )
91 |
92 | expect(lastCallArg(renderFn).values).toEqual({ prop1: 1, prop2: 2 })
93 |
94 | lastCallArg(renderFn).setValues({ prop1: 10 })
95 |
96 | expect(lastCallArg(renderFn).values).toEqual({ prop1: 10, prop2: 2 })
97 |
98 | lastCallArg(renderFn).setValues({ prop1: 100, prop2: 20, prop3: 3 })
99 |
100 | expect(lastCallArg(renderFn).values).toEqual({
101 | prop1: 100,
102 | prop2: 20,
103 | prop3: 3,
104 | })
105 | })
106 |
107 | test('', () => {
108 | const renderFn = jest.fn().mockReturnValue(null)
109 | const onChangeFn = jest.fn()
110 | TestRenderer.create(
111 |
112 | )
113 |
114 | expect(onChangeFn).toBeCalledTimes(0)
115 |
116 | lastCallArg(renderFn)
117 | .field('prop')
118 | .set('10')
119 | expect(onChangeFn).toBeCalledTimes(1)
120 | expect(onChangeFn).lastCalledWith({ prop: '10' })
121 |
122 | lastCallArg(renderFn)
123 | .field('prop')
124 | .bind.onChange({ target: { value: '100' } })
125 | expect(onChangeFn).toBeCalledTimes(2)
126 | expect(onChangeFn).lastCalledWith({ prop: '100' })
127 | })
128 |
--------------------------------------------------------------------------------
/tests/components/Hover.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Hover } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test(' ', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | TestRenderer.create( )
9 |
10 | expect(renderFn).toBeCalledTimes(1)
11 | expect(renderFn).lastCalledWith(expect.objectContaining({ hovered: false }))
12 |
13 | lastCallArg(renderFn).bind.onMouseEnter()
14 | expect(renderFn).toBeCalledTimes(2)
15 | expect(renderFn).lastCalledWith(expect.objectContaining({ hovered: true }))
16 |
17 | lastCallArg(renderFn).bind.onMouseLeave()
18 | expect(renderFn).lastCalledWith(expect.objectContaining({ hovered: false }))
19 | })
20 |
21 | test(' ', () => {
22 | const renderFn = jest.fn().mockReturnValue(null)
23 | const onChangeFn = jest.fn()
24 | TestRenderer.create( )
25 |
26 | expect(onChangeFn).toBeCalledTimes(0)
27 |
28 | lastCallArg(renderFn).bind.onMouseEnter()
29 | expect(onChangeFn).toBeCalledTimes(1)
30 | expect(onChangeFn).lastCalledWith(true)
31 | })
32 |
--------------------------------------------------------------------------------
/tests/components/Interval.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Interval } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | jest.useFakeTimers()
7 |
8 | test(' ', () => {
9 | const renderFn = jest.fn().mockReturnValue(null)
10 | const renderer = TestRenderer.create(
11 | {renderFn}
12 | )
13 |
14 | // Initial call
15 | expect(renderFn).toBeCalledTimes(1)
16 |
17 | jest.advanceTimersByTime(1000)
18 |
19 | renderer.update({renderFn} )
20 | expect(renderFn).toBeCalledTimes(4)
21 |
22 | jest.advanceTimersByTime(2000)
23 |
24 | expect(renderFn).toBeCalledTimes(6)
25 |
26 | lastCallArg(renderFn).stop()
27 | jest.advanceTimersByTime(2000)
28 | expect(renderFn).toBeCalledTimes(6)
29 |
30 | lastCallArg(renderFn).start()
31 | lastCallArg(renderFn).start()
32 | jest.advanceTimersByTime(2000)
33 | expect(renderFn).toBeCalledTimes(8)
34 |
35 | lastCallArg(renderFn).toggle()
36 | jest.advanceTimersByTime(2000)
37 | expect(renderFn).toBeCalledTimes(8)
38 |
39 | lastCallArg(renderFn).toggle()
40 | jest.advanceTimersByTime(2000)
41 | expect(renderFn).toBeCalledTimes(10)
42 | })
43 |
--------------------------------------------------------------------------------
/tests/components/List.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { List } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test('
', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | const testRenderer = TestRenderer.create(
9 |
10 | )
11 |
12 | expect(renderFn).toBeCalledTimes(1)
13 | expect(renderFn).lastCalledWith(expect.objectContaining({ list: [1] }))
14 |
15 | lastCallArg(renderFn).push(8)
16 | expect(renderFn).lastCalledWith(expect.objectContaining({ list: [1, 8] }))
17 |
18 | lastCallArg(renderFn).set([9, 2, 3, 4])
19 | expect(renderFn).lastCalledWith(
20 | expect.objectContaining({ list: [9, 2, 3, 4] })
21 | )
22 |
23 | lastCallArg(renderFn).set(list => [...list, 5])
24 | expect(renderFn).lastCalledWith(
25 | expect.objectContaining({ list: [9, 2, 3, 4, 5] })
26 | )
27 |
28 | const listBeforeSort = lastCallArg(renderFn).list
29 | lastCallArg(renderFn).sort()
30 | expect(renderFn).lastCalledWith(
31 | expect.objectContaining({ list: [2, 3, 4, 5, 9] })
32 | )
33 | expect(listBeforeSort).toEqual([9, 2, 3, 4, 5])
34 |
35 | lastCallArg(renderFn).pull(d => d % 2)
36 | expect(renderFn).lastCalledWith(expect.objectContaining({ list: [2, 4] }))
37 |
38 | expect(lastCallArg(renderFn).first()).toEqual(2)
39 | expect(lastCallArg(renderFn).last()).toEqual(4)
40 |
41 | lastCallArg(renderFn).set([])
42 | expect(lastCallArg(renderFn).first()).toEqual(undefined)
43 | expect(lastCallArg(renderFn).last()).toEqual(undefined)
44 |
45 | // support pushing many array
46 | lastCallArg(renderFn).push(1, 2, 3)
47 | expect(renderFn).lastCalledWith(expect.objectContaining({ list: [1, 2, 3] }))
48 |
49 | testRenderer.update(
)
50 | expect(renderFn).lastCalledWith(expect.objectContaining({ list: [1, 2, 3] }))
51 |
52 | lastCallArg(renderFn).reset()
53 | expect(renderFn).lastCalledWith(expect.objectContaining({ list: [1] }))
54 | })
55 |
56 | test('
', () => {
57 | const renderFn = jest.fn().mockReturnValue(null)
58 | const onChangeFn = jest.fn()
59 | TestRenderer.create(
)
60 |
61 | expect(onChangeFn).toBeCalledTimes(0)
62 |
63 | lastCallArg(renderFn).set([1])
64 | expect(onChangeFn).toBeCalledTimes(1)
65 | expect(onChangeFn).lastCalledWith([1])
66 | })
67 |
--------------------------------------------------------------------------------
/tests/components/Map.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Map } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test(' ', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | const testRenderer = TestRenderer.create(
9 |
10 | )
11 |
12 | expect(renderFn).toBeCalledTimes(1)
13 |
14 | // get
15 | expect(lastCallArg(renderFn).get('a')).toBe(0)
16 |
17 | // set
18 | lastCallArg(renderFn).set('a', 1)
19 | expect(lastCallArg(renderFn).get('a')).toBe(1)
20 |
21 | lastCallArg(renderFn).set('a', d => d + 10)
22 | expect(lastCallArg(renderFn).get('a')).toBe(11)
23 |
24 | // reset
25 | testRenderer.update( )
26 | expect(lastCallArg(renderFn).get('a')).toBe(11)
27 |
28 | lastCallArg(renderFn).reset()
29 | expect(lastCallArg(renderFn).get('a')).toBe(100)
30 |
31 | // has
32 | expect(lastCallArg(renderFn).has('a')).toBe(true)
33 |
34 | lastCallArg(renderFn).set('a', null)
35 | expect(lastCallArg(renderFn).has('a')).toBe(false)
36 | expect(lastCallArg(renderFn).has('b')).toBe(false)
37 |
38 | // clear
39 | lastCallArg(renderFn).set('a', 1)
40 | lastCallArg(renderFn).set('b', 2)
41 | expect(lastCallArg(renderFn).values).toEqual({ a: 1, b: 2 })
42 |
43 | lastCallArg(renderFn).clear()
44 | expect(lastCallArg(renderFn).values).toEqual({})
45 |
46 | // delete
47 | lastCallArg(renderFn).set('a', 1)
48 | lastCallArg(renderFn).set('b', 2)
49 | expect(lastCallArg(renderFn).values).toEqual({ a: 1, b: 2 })
50 |
51 | lastCallArg(renderFn).delete('a')
52 | expect(lastCallArg(renderFn).values).toEqual({ b: 2 })
53 | })
54 |
55 | test(' ', () => {
56 | const renderFn = jest.fn().mockReturnValue(null)
57 | const onChangeFn = jest.fn()
58 | TestRenderer.create( )
59 |
60 | expect(onChangeFn).toBeCalledTimes(0)
61 |
62 | lastCallArg(renderFn).set('a', 1)
63 | expect(onChangeFn).toBeCalledTimes(1)
64 | expect(onChangeFn).lastCalledWith({ a: 1 })
65 | })
66 |
--------------------------------------------------------------------------------
/tests/components/Set.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Set } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test(' ', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | const testRenderer = TestRenderer.create(
9 |
10 | )
11 |
12 | expect(renderFn).toBeCalledTimes(1)
13 |
14 | expect(lastCallArg(renderFn).has(1)).toBe(true)
15 | expect(lastCallArg(renderFn).has(4)).toBe(false)
16 | expect(renderFn).lastCalledWith(
17 | expect.objectContaining({ values: [1, 2, 3] })
18 | )
19 |
20 | lastCallArg(renderFn).add(4)
21 | lastCallArg(renderFn).add(4)
22 | expect(lastCallArg(renderFn).has(4)).toBe(true)
23 | expect(renderFn).lastCalledWith(
24 | expect.objectContaining({ values: [1, 2, 3, 4] })
25 | )
26 |
27 | lastCallArg(renderFn).remove(1)
28 | expect(lastCallArg(renderFn).has(1)).toBe(false)
29 | expect(renderFn).lastCalledWith(
30 | expect.objectContaining({ values: [2, 3, 4] })
31 | )
32 |
33 | lastCallArg(renderFn).clear()
34 | expect(lastCallArg(renderFn).has(2)).toBe(false)
35 | expect(lastCallArg(renderFn).has(3)).toBe(false)
36 | expect(lastCallArg(renderFn).has(4)).toBe(false)
37 | expect(renderFn).lastCalledWith(expect.objectContaining({ values: [] }))
38 |
39 | testRenderer.update( )
40 | expect(renderFn).lastCalledWith(expect.objectContaining({ values: [] }))
41 |
42 | lastCallArg(renderFn).reset()
43 | expect(renderFn).lastCalledWith(expect.objectContaining({ values: [2] }))
44 | })
45 |
46 | test(' ', () => {
47 | const renderFn = jest.fn().mockReturnValue(null)
48 | const onChangeFn = jest.fn()
49 | TestRenderer.create( )
50 |
51 | expect(onChangeFn).toBeCalledTimes(0)
52 |
53 | lastCallArg(renderFn).add(1)
54 | expect(onChangeFn).toBeCalledTimes(1)
55 | expect(onChangeFn).lastCalledWith([1])
56 | })
57 |
--------------------------------------------------------------------------------
/tests/components/State.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { State } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test(' ', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | const callbackFn = jest.fn()
9 | const testRenderer = TestRenderer.create(
10 |
11 | )
12 |
13 | // Initial values
14 | expect(renderFn).lastCalledWith({
15 | state: { myValue: 1 },
16 | setState: expect.any(Function),
17 | resetState: expect.any(Function),
18 | })
19 |
20 | lastCallArg(renderFn).setState({ myValue: 2 })
21 |
22 | // Values after setState
23 | expect(renderFn).lastCalledWith({
24 | state: { myValue: 2 },
25 | setState: expect.any(Function),
26 | resetState: expect.any(Function),
27 | })
28 |
29 | // call callback only once
30 | lastCallArg(renderFn).setState({ myValue: 3 }, callbackFn)
31 | expect(callbackFn).toBeCalledTimes(1)
32 | lastCallArg(renderFn).setState({ myValue: 4 })
33 | expect(callbackFn).toBeCalledTimes(1)
34 |
35 | // Change initial, and update the whole tree
36 | testRenderer.update( )
37 |
38 | // Value hasn't been changed
39 | expect(renderFn).lastCalledWith({
40 | state: { myValue: 4 },
41 | setState: expect.any(Function),
42 | resetState: expect.any(Function),
43 | })
44 |
45 | // Reset state
46 | lastCallArg(renderFn).resetState()
47 |
48 | // Now state value is equal to initial
49 | expect(renderFn).lastCalledWith({
50 | state: { myValue: 3 },
51 | setState: expect.any(Function),
52 | resetState: expect.any(Function),
53 | })
54 | })
55 |
56 | test(' ', () => {
57 | const onChangeFn = jest.fn()
58 | const renderFn = jest.fn().mockReturnValue(null)
59 | TestRenderer.create(
60 |
61 | )
62 |
63 | expect(onChangeFn).toBeCalledTimes(0)
64 |
65 | lastCallArg(renderFn).setState({ myValue: 2 })
66 | expect(onChangeFn).toBeCalledTimes(1)
67 | expect(onChangeFn).toBeCalledWith({ myValue: 2 })
68 | })
69 |
--------------------------------------------------------------------------------
/tests/components/Toggle.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Toggle } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test(' ', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | const testRenderer = TestRenderer.create( )
9 |
10 | expect(renderFn).toBeCalledTimes(1)
11 | expect(renderFn).lastCalledWith(expect.objectContaining({ on: false }))
12 |
13 | lastCallArg(renderFn).toggle()
14 | expect(renderFn).lastCalledWith(expect.objectContaining({ on: true }))
15 |
16 | lastCallArg(renderFn).set(false)
17 | expect(renderFn).lastCalledWith(expect.objectContaining({ on: false }))
18 |
19 | lastCallArg(renderFn).set(on => !on)
20 | expect(renderFn).lastCalledWith(expect.objectContaining({ on: true }))
21 |
22 | testRenderer.update( )
23 | expect(renderFn).lastCalledWith(expect.objectContaining({ on: true }))
24 |
25 | lastCallArg(renderFn).reset()
26 | expect(renderFn).lastCalledWith(expect.objectContaining({ on: false }))
27 |
28 | lastCallArg(renderFn).setOn()
29 | expect(renderFn).lastCalledWith(expect.objectContaining({ on: true }))
30 |
31 | lastCallArg(renderFn).setOff()
32 | expect(renderFn).lastCalledWith(expect.objectContaining({ on: false }))
33 | })
34 |
35 | test(' ', () => {
36 | const renderFn = jest.fn().mockReturnValue(null)
37 | const onChangeFn = jest.fn()
38 | TestRenderer.create(
39 |
40 | )
41 |
42 | expect(onChangeFn).toBeCalledTimes(0)
43 |
44 | lastCallArg(renderFn).set(true)
45 | expect(onChangeFn).toBeCalledTimes(1)
46 | expect(onChangeFn).lastCalledWith(true)
47 | })
48 |
--------------------------------------------------------------------------------
/tests/components/Touch.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Touch } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test(' ', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | TestRenderer.create( )
9 |
10 | expect(renderFn).toBeCalledTimes(1)
11 | expect(renderFn).lastCalledWith(expect.objectContaining({ touched: false }))
12 |
13 | lastCallArg(renderFn).bind.onTouchStart()
14 | expect(renderFn).lastCalledWith(expect.objectContaining({ touched: true }))
15 |
16 | lastCallArg(renderFn).bind.onTouchEnd()
17 | expect(renderFn).lastCalledWith(expect.objectContaining({ touched: false }))
18 | })
19 |
20 | test(' ', () => {
21 | const renderFn = jest.fn().mockReturnValue(null)
22 | const onChangeFn = jest.fn()
23 | TestRenderer.create( )
24 |
25 | expect(onChangeFn).toBeCalledTimes(0)
26 |
27 | lastCallArg(renderFn).bind.onTouchStart()
28 | expect(onChangeFn).toBeCalledTimes(1)
29 | expect(onChangeFn).lastCalledWith(true)
30 | })
31 |
--------------------------------------------------------------------------------
/tests/components/Value.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { Value } from '../../src'
4 | import { lastCallArg } from './utils'
5 |
6 | test(' ', () => {
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | const testRenderer = TestRenderer.create(
9 |
10 | )
11 |
12 | expect(renderFn).toBeCalledTimes(1)
13 |
14 | expect(renderFn).lastCalledWith(expect.objectContaining({ value: { a: 1 } }))
15 |
16 | lastCallArg(renderFn).set({ b: 2 })
17 | expect(renderFn).lastCalledWith(expect.objectContaining({ value: { b: 2 } }))
18 |
19 | lastCallArg(renderFn).set(value => ({ ...value, a: 1 }))
20 | expect(renderFn).lastCalledWith(
21 | expect.objectContaining({ value: { a: 1, b: 2 } })
22 | )
23 |
24 | lastCallArg(renderFn).set(0)
25 | expect(renderFn).lastCalledWith(expect.objectContaining({ value: 0 }))
26 |
27 | // test reset
28 | testRenderer.update( )
29 | expect(renderFn).lastCalledWith(expect.objectContaining({ value: 0 }))
30 |
31 | lastCallArg(renderFn).reset()
32 | expect(renderFn).lastCalledWith(expect.objectContaining({ value: 3 }))
33 | })
34 |
35 | test(' ', () => {
36 | const renderFn = jest.fn().mockReturnValue(null)
37 | const onChangeFn = jest.fn()
38 | TestRenderer.create(
39 |
40 | )
41 |
42 | expect(onChangeFn).toBeCalledTimes(0)
43 |
44 | lastCallArg(renderFn).set(1)
45 | expect(onChangeFn).toBeCalledTimes(1)
46 | expect(onChangeFn).lastCalledWith(1)
47 | })
48 |
--------------------------------------------------------------------------------
/tests/components/utils.js:
--------------------------------------------------------------------------------
1 | const last = arr => arr[Math.max(0, arr.length - 1)]
2 |
3 | export const lastCallArg = mockFn => last(mockFn.mock.calls)[0]
4 | export const lastCallArgs = mockFn => last(mockFn.mock.calls)
5 |
--------------------------------------------------------------------------------
/tests/jestCJSSetup.js:
--------------------------------------------------------------------------------
1 | jest.mock('../src', () => require('../dist/react-powerplug.cjs.js'))
2 |
--------------------------------------------------------------------------------
/tests/jestUMDSetup.js:
--------------------------------------------------------------------------------
1 | jest.mock('../src', () => require('../dist/react-powerplug.umd.js'))
2 |
--------------------------------------------------------------------------------
/tests/test_flow.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | // @flow
3 |
4 | import * as React from 'react'
5 | import {
6 | Active,
7 | Counter,
8 | Focus,
9 | unstable_FocusManager as FocusManager,
10 | Form,
11 | Hover,
12 | Field,
13 | Interval,
14 | List,
15 | Map,
16 | Set,
17 | State,
18 | Toggle,
19 | Touch,
20 | Value,
21 | } from '../src'
22 |
23 | const noop = () => null
24 |
25 | /* Active */
26 | {
27 | const render = ({ active, bind }) => {
28 | ;(active: boolean)
29 | ;(bind.onMouseDown: Function)
30 | ;(bind.onMouseUp: Function)
31 | // $FlowFixMe
32 | ;(active: number)
33 | // $FlowFixMe
34 | ;(bind.onMouseDown: number)
35 | // $FlowFixMe
36 | ;(bind.onMouseUp: number)
37 | return null
38 | }
39 | const onChange = active => {
40 | ;(active: boolean)
41 | // $FlowFixMe
42 | ;(active: number)
43 | }
44 | ;[
45 | ,
46 | {render} ,
47 | ,
48 | {noop} ,
49 | // $FlowFixMe
50 | ,
51 | ]
52 | }
53 |
54 | /* Field */
55 | {
56 | const render = ({ value, set, bind, reset }) => {
57 | ;(value: string)
58 | set('')
59 | ;(bind.value: string)
60 | ;(bind.onChange: Function)
61 | // $FlowFixMe
62 | ;(value: number)
63 | // $FlowFixMe
64 | set(0)
65 | // $FlowFixMe
66 | ;(bind.value: number)
67 | // $FlowFixMe
68 | ;(bind.onChange: number)
69 |
70 | reset()
71 | reset(() => {})
72 | // $FlowFixMe
73 | reset(1)
74 | return null
75 | }
76 | const onChange = value => {
77 | ;(value: string)
78 | // $FlowFixMe
79 | ;(value: number)
80 | }
81 | ;[
82 | ,
83 | {render} ,
84 | ,
85 | {noop} ,
86 | ,
87 | // $FlowFixMe
88 | ,
89 | // $FlowFixMe
90 | ,
91 | ]
92 | }
93 |
94 | {
95 | const render = ({ start, stop, toggle }) => {
96 | start()
97 | start(500)
98 | stop()
99 | toggle()
100 | // $FlowFixMe
101 | start('')
102 | // $FlowFixMe
103 | stop(500)
104 | // $FlowFixMe
105 | toggle(500)
106 | return null
107 | }
108 | ;[
109 | ,
110 | {render} ,
111 | ,
112 | {noop} ,
113 | ,
114 | // $FlowFixMe
115 | ,
116 | // $FlowFixMe
117 | ,
118 | ]
119 | }
120 |
121 | /* Counter */
122 | {
123 | const render = ({ count, inc, dec, incBy, decBy, reset }) => {
124 | ;(count: number)
125 | inc()
126 | dec()
127 | incBy(0)
128 | decBy(0)
129 | // $FlowFixMe
130 | ;(count: string)
131 | // $FlowFixMe
132 | inc('')
133 | // $FlowFixMe
134 | dec('')
135 | // $FlowFixMe
136 | incBy('')
137 | // $FlowFixMe
138 | decBy('')
139 |
140 | reset()
141 | reset(() => {})
142 | // $FlowFixMe
143 | reset(1)
144 |
145 | return null
146 | }
147 | const onChange = count => {
148 | ;(count: number)
149 | // $FlowFixMe
150 | ;(count: string)
151 | }
152 | ;[
153 | ,
154 | {render} ,
155 | ,
156 | {noop} ,
157 | ,
158 | // $FlowFixMe
159 | ,
160 | // $FlowFixMe
161 | ,
162 | ]
163 | }
164 |
165 | /* Focus */
166 | {
167 | const render = ({ focused, bind }) => {
168 | ;(focused: boolean)
169 | ;(bind.onFocus: Function)
170 | ;(bind.onBlur: Function)
171 | // $FlowFixMe
172 | ;(focused: number)
173 | // $FlowFixMe
174 | ;(bind.onFocus: number)
175 | // $FlowFixMe
176 | ;(bind.onBlur: number)
177 | return null
178 | }
179 | const onChange = focused => {
180 | ;(focused: boolean)
181 | // $FlowFixMe
182 | ;(focused: number)
183 | }
184 | ;[
185 | ,
186 | {render} ,
187 | ,
188 | {noop} ,
189 | // $FlowFixMe
190 | ,
191 | ]
192 | }
193 |
194 | /* FocusManager */
195 | {
196 | const render = ({ focused, blur, bind }) => {
197 | ;(focused: boolean)
198 | ;(blur: Function)
199 | ;(bind.onFocus: Function)
200 | ;(bind.onBlur: Function)
201 | ;(bind.onMouseDown: Function)
202 | ;(bind.onMouseUp: Function)
203 | // $FlowFixMe
204 | ;(focused: number)
205 | // $FlowFixMe
206 | ;(bind.onFocus: number)
207 | // $FlowFixMe
208 | ;(bind.onBlur: number)
209 | // $FlowFixMe
210 | ;(bind.onMouseDown: number)
211 | // $FlowFixMe
212 | ;(bind.onMouseUp: number)
213 | return null
214 | }
215 | const onChange = focused => {
216 | ;(focused: boolean)
217 | // $FlowFixMe
218 | ;(focused: number)
219 | }
220 | ;[
221 | ,
222 | {render} ,
223 | ,
224 | {noop} ,
225 | // $FlowFixMe
226 | ,
227 | ]
228 | }
229 |
230 | /* Form */
231 | {
232 | const isNumber = (value: number): number => value
233 |
234 | const render = ({ values, setValues, reset, field }) => {
235 | ;(values.a: string)
236 | const a = field('a')
237 | ;(a.value: string)
238 | ;(a.bind.value: string)
239 | a.set('')
240 | a.bind.onChange('')
241 | a.bind.onChange({ target: { value: '' } })
242 | // $FlowFixMe
243 | ;(a.value: boolean)
244 | // $FlowFixMe
245 | ;(a.bind.value: boolean)
246 | a.set((value: string) => value)
247 | // $FlowFixMe
248 | a.set((value: boolean) => value)
249 | // $FlowFixMe
250 | a.set(true)
251 | // TODO should fail
252 | a.bind.onChange(true)
253 | // TODO should fail
254 | a.bind.onChange({ target: { value: true } })
255 |
256 | const b = field('b')
257 | ;(b.value: number)
258 | // $FlowFixMe
259 | ;(b.value: boolean)
260 |
261 | const c = field('c')
262 | ;(c.value: { value: string })
263 | // $FlowFixMe
264 | ;(c.value: { value: boolean })
265 |
266 | // $FlowFixMe
267 | const d = field('d')
268 |
269 | reset()
270 | reset(() => {})
271 | // $FlowFixMe
272 | reset(1)
273 |
274 | setValues({ a: 'new' })
275 | setValues({ a: 'new', c: { value: 'new' } })
276 | setValues(({ a }) => ({ a }))
277 | // $FlowFixMe
278 | setValues({ wrong: 'value' })
279 | // $FlowFixMe
280 | setValues(({ a }) => ({ d: a }))
281 | // $FlowFixMe
282 | setValues(({ d }) => ({ a: d }))
283 | }
284 | const onChange = data => {
285 | ;(data.a: string)
286 | ;(data.b: number)
287 | ;(data.c: { value: string })
288 | // $FlowFixMe value is string
289 | ;(data.a: boolean)
290 | // $FlowFixMe value is number
291 | ;(data.b: boolean)
292 | // $FlowFixMe value is object
293 | ;(data.c: boolean)
294 | // $FlowFixMe field does not exist
295 | ;(data.d: boolean)
296 | }
297 | ;[
298 | ,
299 | ,
300 | ,
305 | ,
308 | // $FlowFixMe
309 | ,
310 | // $FlowFixMe
311 | ,
312 | // $FlowFixMe
313 | ,
314 | ]
315 | }
316 |
317 | /* Hover */
318 | {
319 | const render = ({ hovered, bind }) => {
320 | ;(hovered: boolean)
321 | ;(bind.onMouseEnter: Function)
322 | ;(bind.onMouseLeave: Function)
323 | // $FlowFixMe
324 | ;(hovered: number)
325 | // $FlowFixMe
326 | ;(bind.onMouseEnter: number)
327 | // $FlowFixMe
328 | ;(bind.onMouseLeave: number)
329 | return null
330 | }
331 | const onChange = hovered => {
332 | ;(hovered: boolean)
333 | // $FlowFixMe
334 | ;(hovered: number)
335 | }
336 | ;[
337 | ,
338 | {render} ,
339 | ,
340 | {noop} ,
341 | // $FlowFixMe
342 | ,
343 | ]
344 | }
345 |
346 | /* List */
347 | {
348 | const render = ({ list, first, last, set, push, pull, sort, reset }) => {
349 | ;(list: $ReadOnlyArray)
350 | ;(first(): string | number | void)
351 | ;(last(): string | number | void)
352 | set([])
353 | set([0])
354 | push(0)
355 | push(0, 1, 2)
356 | pull((d: number) => true)
357 | sort((a: number, b: number) => -1)
358 | // $FlowFixMe
359 | ;(list: $ReadOnlyArray)
360 | //$FlowFixMe
361 | set([''])
362 | //$FlowFixMe
363 | push('')
364 | //$FlowFixMe
365 | pull((d: string) => true)
366 | //$FlowFixMe
367 | sort((a: string, b: string) => -1)
368 |
369 | reset()
370 | reset(() => {})
371 | //$FlowFixMe
372 | reset(1)
373 | }
374 | const onChange = list => {
375 | ;(list: $ReadOnlyArray)
376 | // $FlowFixMe
377 | ;(list: $ReadOnlyArray)
378 | }
379 | ;[
380 | )} render={render} />,
381 | )}>{render}
,
382 | )}
384 | onChange={onChange}
385 | render={noop}
386 | />,
387 | )} onChange={onChange}>
388 | {noop}
389 |
,
390 | // $FlowFixMe
391 |
,
392 | // $FlowFixMe
393 |
,
394 | // $FlowFixMe
395 | {noop}
,
396 | // $FlowFixMe
397 |
,
398 | // $FlowFixMe
399 | {noop}
,
400 | ]
401 | }
402 |
403 | /* Set */
404 | {
405 | const render = ({ values, add, clear, remove, has, reset }) => {
406 | ;(values: $ReadOnlyArray)
407 | add(0)
408 | add('')
409 | remove(0)
410 | remove('')
411 | ;(has(0): boolean)
412 | ;(has(''): boolean)
413 | clear()
414 | // $FlowFixMe
415 | ;(values: $ReadOnlyArray)
416 | // $FlowFixMe
417 | add(true)
418 | // $FlowFixMe
419 | remove(true)
420 | // $FlowFixMe
421 | ;(has(true): boolean)
422 |
423 | reset()
424 | reset(() => {})
425 | // $FlowFixMe
426 | reset(1)
427 | return null
428 | }
429 | const onChange = values => {
430 | ;(values: $ReadOnlyArray)
431 | // $FlowFixMe
432 | ;(values: $ReadOnlyArray)
433 | }
434 | ;[
435 | )} render={render} />,
436 | )}>{render} ,
437 | )}
439 | onChange={onChange}
440 | render={noop}
441 | />,
442 | )} onChange={onChange}>
443 | {noop}
444 | ,
445 | ]
446 | }
447 |
448 | /* Map */
449 | {
450 | const render = ({
451 | values,
452 | clear,
453 | reset,
454 | set,
455 | get,
456 | has,
457 | delete: deleteItem,
458 | }) => {
459 | // unsafe access do not consider keys
460 | ;(values.a: number)
461 | ;(values.b: number)
462 | ;(get('a'): number)
463 | ;(get('b'): number)
464 | // $FlowFixMe
465 | ;(values.a: string)
466 | // $FlowFixMe
467 | ;(get('a'): string)
468 | set('a', 0)
469 | set('a', (value: number) => 0)
470 | // $FlowFixMe
471 | set('a', '')
472 | // $FlowFixMe
473 | set('a', (value: string) => 0)
474 | // $FlowFixMe
475 | set('a', (value: number) => '')
476 | ;(has('a'): boolean)
477 | ;(has('b'): boolean)
478 | // $FlowFixMe
479 | ;(get('a'): string)
480 |
481 | reset()
482 | reset(() => {})
483 | // $FlowFixMe
484 | reset(1)
485 | // $FlowFixMe
486 | ;(has('a'): number)
487 | deleteItem('a')
488 | // $FlowFixMe
489 | deleteItem(0)
490 | return null
491 | }
492 |
493 | const onChange = values => {
494 | ;(values.a: number)
495 | ;(values.b: number)
496 | }
497 | ;[
498 | ,
499 | {render} ,
500 | ,
501 |
502 | {noop}
503 | ,
504 | ]
505 | }
506 |
507 | /* State with inferred generic */
508 | {
509 | const render = ({ state, setState, resetState }) => {
510 | ;(state.v: number)
511 | setState({}, () => {})
512 | setState({ v: 1 })
513 | // $FlowFixMe
514 | ;(state.v: string)
515 | // $FlowFixMe
516 | setState({ v: '' })
517 | // $FlowFixMe
518 | setState({ t: 1 })
519 | // $FlowFixMe
520 | setState({ n: 2 })
521 |
522 | resetState()
523 | resetState(() => {})
524 |
525 | // $FlowFixMe
526 | resetState(1)
527 | }
528 | const onChange = state => {
529 | ;(state.v: number)
530 | // $FlowFixMe
531 | ;(state.v: string)
532 | }
533 | ;[
534 | ,
535 | {render} ,
536 | ,
537 |
538 | {noop}
539 | ,
540 | // $FlowFixMe
541 | ,
542 | // $FlowFixMe
543 | ,
544 | // $FlowFixMe
545 | ,
546 | ]
547 | }
548 |
549 | /* State with specified generic */
550 | {
551 | const render1 = ({ state, setState }) => {
552 | ;(state.n: ?number)
553 | setState({})
554 | setState({ n: 1 })
555 | // $FlowFixMe
556 | ;(state.n: number)
557 | // $FlowFixMe
558 | setState({ n: '' })
559 | }
560 | ;[
561 | ,
562 | {render1} ,
563 | ]
564 | }
565 |
566 | /* Toggle */
567 | {
568 | const render = ({ on, toggle, set, setOn, setOff, reset }) => {
569 | ;(on: boolean)
570 | toggle()
571 | set(true)
572 | set(v => !v)
573 | setOn()
574 | setOff()
575 | // $FlowFixMe
576 | ;(on: number)
577 | // $FlowFixMe
578 | toggle(true)
579 | // $FlowFixMe
580 | set(0)
581 |
582 | reset()
583 | reset(() => {})
584 | // $FlowFixMe
585 | reset(1)
586 | return null
587 | }
588 | const onChange = on => {
589 | ;(on: boolean)
590 | // $FlowFixMe
591 | ;(on: number)
592 | }
593 | ;[
594 | ,
595 | {render} ,
596 | ,
597 | {noop} ,
598 | ,
599 | // $FlowFixMe
600 | ,
601 | // $FlowFixMe
602 | ,
603 | ]
604 | }
605 |
606 | /* Touch */
607 | {
608 | const render = ({ touched, bind }) => {
609 | ;(touched: boolean)
610 | ;(bind.onTouchStart: Function)
611 | ;(bind.onTouchEnd: Function)
612 | // $FlowFixMe
613 | ;(touched: number)
614 | // $FlowFixMe
615 | ;(bind.onTouchStart: number)
616 | // $FlowFixMe
617 | ;(bind.onTouchEnd: number)
618 | return null
619 | }
620 | const onChange = touched => {
621 | ;(touched: boolean)
622 | // $FlowFixMe
623 | ;(touched: number)
624 | }
625 | ;[
626 | ,
627 | {render} ,
628 | ,
629 | {noop} ,
630 | // $FlowFixMe
631 | ,
632 | ]
633 | }
634 |
635 | /* Value with inferred generic */
636 | {
637 | const render = ({ value, set, reset }) => {
638 | ;(value: number | string | boolean)
639 | // $FlowFixMe
640 | ;(value: number)
641 | // $FlowFixMe
642 | ;(value: string)
643 | // $FlowFixMe
644 | ;(value: boolean)
645 | set(true)
646 |
647 | reset()
648 | reset(() => {})
649 |
650 | // $FlowFixMe
651 | reset(1)
652 | }
653 | const onChange = value => {
654 | ;(value: number | string)
655 | // $FlowFixMe
656 | ;(value: number)
657 | // $FlowFixMe
658 | ;(value: string)
659 | }
660 | ;[
661 | ,
662 | {render} ,
663 | ,
664 |
665 | {noop}
666 | ,
667 | // $FlowFixMe
668 | ,
669 | // $FlowFixMe
670 | ,
671 | ]
672 | }
673 |
674 | /* Value with specified generic */
675 | {
676 | const render1 = ({ value, set }) => {
677 | ;(value: number | string)
678 | set('')
679 | // $FlowFixMe
680 | ;(value: number)
681 | }
682 | ;[
683 | ,
684 | {render1} ,
685 | ]
686 | }
687 |
--------------------------------------------------------------------------------
/tests/utils/compose.test.js:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import TestRenderer from 'react-test-renderer'
3 | import { compose, Counter, Toggle } from '../../src'
4 |
5 | test('rerender composed component', () => {
6 | const CounterToggle = compose(Counter, Toggle)
7 | const renderFn = jest.fn().mockReturnValue(null)
8 | let rerender = null
9 |
10 | TestRenderer.create(
11 |
12 | {({ on, toggle }) => {
13 | rerender = toggle
14 |
15 | return (
16 | // hard rerender
17 |
18 | )
19 | }}
20 |
21 | )
22 |
23 | expect(renderFn).toBeCalledTimes(1)
24 | expect(renderFn).lastCalledWith(
25 | expect.objectContaining({ count: 0 }),
26 | expect.objectContaining({ on: false })
27 | )
28 |
29 | rerender()
30 |
31 | expect(renderFn).toBeCalledTimes(2)
32 | expect(renderFn).lastCalledWith(
33 | expect.objectContaining({ count: 0 }),
34 | expect.objectContaining({ on: false })
35 | )
36 | })
37 |
--------------------------------------------------------------------------------
/tests/utils/composeEvents.test.js:
--------------------------------------------------------------------------------
1 | import { composeEvents } from '../../src'
2 |
3 | test('composeEvents should call all events', () => {
4 | const arr = []
5 | const composed = composeEvents(
6 | {
7 | onMouseEnter: () => arr.push(1),
8 | },
9 | {
10 | onMouseEnter: () => arr.push(2),
11 | onMouseLeave: () => arr.push(3),
12 | },
13 | {
14 | onMouseEnter: () => arr.push(4),
15 | onMouseLeave: () => arr.push(5),
16 | },
17 | {
18 | onMouseLeave: () => arr.push(6),
19 | }
20 | )
21 |
22 | composed.onMouseEnter()
23 | composed.onMouseLeave()
24 |
25 | expect(arr).toEqual([1, 2, 4, 3, 5, 6])
26 | })
27 |
--------------------------------------------------------------------------------
/tests/utils/renderProps.test.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 |
3 | import { renderProps } from '../../src'
4 |
5 | console.warn = jest.fn()
6 | console.trace = jest.fn()
7 |
8 | test('renderProps should use children prop when alone', () => {
9 | const props = { children: value => value + 1 }
10 |
11 | expect(renderProps(props, 1)).toBe(2)
12 | })
13 |
14 | test('renderProps should use render prop when alone', () => {
15 | const props = { render: value => value + 1 }
16 |
17 | expect(renderProps(props, 1)).toBe(2)
18 | })
19 |
20 | test('renderProps should use children when together and warns the user', () => {
21 | const props = { children: value => value + 1, render: value => value - 1 }
22 |
23 | expect(renderProps(props, 1)).toBe(2)
24 | expect(console.warn.mock.calls.length).toBe(1)
25 | expect(console.trace.mock.calls.length).toBe(1)
26 | })
27 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | // TypeScript Version: 2.4
2 |
3 | import * as React from 'react'
4 |
5 | /* Utils */
6 |
7 | export type SharedProps = {
8 | reset(cb?: () => void): void
9 | resetState(): void
10 | }
11 |
12 | export type Updater = (value: T | ((updater: T) => T)) => void
13 | export type Callback = (value: T) => void
14 | export type RenderFn = (value: T & SharedProps) => React.ReactNode
15 |
16 | /* Active */
17 |
18 | export type ActiveChange = (active: boolean) => void
19 |
20 | export type ActiveRender = (
21 | argument: {
22 | active: boolean
23 | bind: { onMouseDown: () => void; onMouseUp: () => void }
24 | }
25 | ) => React.ReactNode
26 |
27 | export const Active: React.ComponentType<
28 | | { onChange?: ActiveChange; render: ActiveRender }
29 | | { onChange?: ActiveChange; children: ActiveRender }
30 | >
31 |
32 | /* Compose */
33 |
34 | export const Compose: React.ComponentType
35 |
36 | /* Counter */
37 |
38 | export type CounterChange = Callback
39 |
40 | export type CounterRender = RenderFn<{
41 | count: number
42 | inc: () => void
43 | dec: () => void
44 | incBy: (step: number) => void
45 | decBy: (step: number) => void
46 | }>
47 |
48 | export const Counter: React.ComponentType<
49 | | { initial?: number; onChange?: CounterChange; render: CounterRender }
50 | | { initial?: number; onChange?: CounterChange; children: CounterRender }
51 | >
52 |
53 | /* Focus */
54 |
55 | export type FocusChange = Callback
56 |
57 | export type FocusRender = RenderFn<{
58 | focused: boolean
59 | bind: { onFocus: () => void; onBlur: () => void }
60 | }>
61 |
62 | export const Focus: React.ComponentType<
63 | | { onChange?: FocusChange; render: FocusRender }
64 | | { onChange?: FocusChange; children: FocusRender }
65 | >
66 |
67 | /* FocusManager */
68 |
69 | export type FocusManagerChange = Callback
70 |
71 | export type FocusManagerRender = RenderFn<{
72 | focused: boolean
73 | blur: () => void
74 | bind: {
75 | tabIndex: number
76 | onFocus: () => void
77 | onBlur: () => void
78 | onMouseDown: () => void
79 | onMouseUp: () => void
80 | }
81 | }>
82 |
83 | export const FocusManager: React.ComponentType<
84 | | { onChange?: FocusManagerChange; render: FocusManagerRender }
85 | | { onChange?: FocusManagerChange; children: FocusManagerRender }
86 | >
87 |
88 | /* Form */
89 |
90 | export type FormChange = Callback
91 |
92 | export type FormRender = RenderFn<{
93 | values: T
94 | setValues: (values: T | ((a: T) => T)) => void
95 | field: (
96 | key: K
97 | ) => {
98 | value: T[K]
99 | set: Updater
100 | bind: {
101 | value: T[K]
102 | onChange: (argument: React.ChangeEvent | T[K]) => void
103 | }
104 | }
105 | }>
106 |
107 | export type FormProps =
108 | | { initial: T; onChange?: FormChange; render: FormRender }
109 | | { initial: T; onChange?: FormChange; children: FormRender }
110 |
111 | export interface Hash {
112 | [key: string]: string
113 | }
114 |
115 | export class Form extends React.Component<
116 | FormProps,
117 | any
118 | > {}
119 |
120 | /* Hover */
121 |
122 | export type HoverChange = Callback
123 |
124 | export type HoverRender = RenderFn<{
125 | hovered: boolean
126 | bind: { onMouseEnter: () => void; onMouseLeave: () => void }
127 | }>
128 |
129 | export const Hover: React.ComponentType<
130 | | { onChange?: HoverChange; render: HoverRender }
131 | | { onChange?: HoverChange; children: HoverRender }
132 | >
133 |
134 | /* Field */
135 |
136 | export type FieldChange = Callback
137 |
138 | export type FieldRender = RenderFn<{
139 | value: T
140 | set: Updater
141 | bind: { value: T; onChange: (event: React.ChangeEvent) => void }
142 | }>
143 |
144 | export type FieldProps =
145 | | { initial?: T; onChange?: FieldChange; render: FieldRender }
146 | | { initial?: T; onChange?: FieldChange; children: FieldRender }
147 |
148 | export class Field extends React.Component> {}
149 |
150 | /* List */
151 |
152 | export type ListChange = Callback>
153 |
154 | export type ListRender = RenderFn<{
155 | list: ReadonlyArray
156 | first: () => T | void
157 | last: () => T | void
158 | set: Updater>
159 | push: (value: T) => void
160 | pull: (predicate: (flag: T) => boolean) => void
161 | sort: (compare: (a: T, b: T) => -1 | 0 | 1) => void
162 | }>
163 |
164 | export type ListProps =
165 | | {
166 | initial: ReadonlyArray
167 | onChange?: ListChange
168 | render: ListRender
169 | }
170 | | {
171 | initial: ReadonlyArray
172 | onChange?: ListChange
173 | children: ListRender
174 | }
175 |
176 | export class List extends React.Component, any> {}
177 |
178 | /* Set */
179 |
180 | export type SetChange = Callback>
181 |
182 | export type SetRender = RenderFn<{
183 | values: ReadonlyArray
184 | add: (key: T) => void
185 | clear: () => void
186 | remove: (key: T) => void
187 | has: (key: T) => boolean
188 | }>
189 |
190 | export type SetProps =
191 | | {
192 | initial: ReadonlyArray
193 | onChange?: SetChange
194 | render: SetRender
195 | }
196 | | {
197 | initial: ReadonlyArray
198 | onChange?: SetChange
199 | children: SetRender
200 | }
201 |
202 | export class Set extends React.Component> {}
203 |
204 | /* Map */
205 |
206 | export type MapValues = { [key: string]: T }
207 | export type MapChange = Callback
208 |
209 | export type MapRender = RenderFn<{
210 | values: MapValues
211 | set: (key: K, fn: T | ((value: T) => T)) => void
212 | get: (key: K) => T
213 | has: (key: K) => boolean
214 | delete: (key: K) => void
215 | clear: () => void
216 | }>
217 |
218 | export type MapProps =
219 | | { initial: T; onChange?: MapChange; render: MapRender }
220 | | { initial: T; onChange?: MapChange; children: MapRender }
221 |
222 | export class Map extends React.Component<
223 | MapProps,
224 | any
225 | > {}
226 |
227 | /* State */
228 |
229 | export type StateChange = Callback
230 |
231 | export type StateRender = RenderFn<{
232 | state: T
233 | setState: (
234 | updated: Partial | ((state: T) => Partial),
235 | cb?: () => void
236 | ) => void
237 | }>
238 |
239 | export type StateProps =
240 | | { initial: T; onChange?: StateChange; render: StateRender }
241 | | { initial: T; onChange?: StateChange; children: StateRender }
242 |
243 | export class State extends React.Component> {}
244 |
245 | /* Toggle */
246 |
247 | export type ToggleChange = Callback
248 |
249 | export type ToggleRender = RenderFn<{
250 | on: boolean
251 | toggle: () => void
252 | set: Updater
253 | }>
254 |
255 | export const Toggle: React.ComponentType<
256 | | { initial?: boolean; onChange?: ToggleChange; render: ToggleRender }
257 | | { initial?: boolean; onChange?: ToggleChange; children: ToggleRender }
258 | >
259 |
260 | /* Touch */
261 |
262 | export type TouchChange = Callback
263 |
264 | export type TouchRender = RenderFn<{
265 | touched: boolean
266 | bind: { onTouchStart: () => void; onTouchEnd: () => void }
267 | }>
268 |
269 | export const Touch: React.ComponentType<
270 | | { onChange?: TouchChange; render: TouchRender }
271 | | { onChange?: TouchChange; children: TouchRender }
272 | >
273 |
274 | /* Value */
275 |
276 | export type ValueChange = Callback
277 |
278 | export type ValueRender = RenderFn<{
279 | value: T
280 | set: Updater
281 | }>
282 |
283 | export type ValueProps =
284 | | { initial: T; onChange?: ValueChange; render: ValueRender }
285 | | { initial: T; onChange?: ValueChange; children: ValueRender }
286 |
287 | export class Value extends React.Component> {}
288 |
289 | /* composeEvents */
290 |
291 | export interface Events {
292 | [name: string]: (event: any) => void
293 | }
294 |
295 | export function composeEvents(...arguments: Events[]): Events
296 |
--------------------------------------------------------------------------------
/types/test.tsx:
--------------------------------------------------------------------------------
1 | /* tslint-disable */
2 | // TypeScript Version: 2.7
3 |
4 | import * as React from 'react'
5 | import {
6 | Active,
7 | Input,
8 | Counter,
9 | Focus,
10 | FocusManager,
11 | Form,
12 | Hover,
13 | List,
14 | Map,
15 | Set,
16 | State,
17 | Toggle,
18 | Touch,
19 | Value,
20 | ListRender,
21 | SetRender,
22 | MapRender,
23 | StateRender,
24 | ToggleRender,
25 | TouchRender,
26 | ValueRender,
27 | ActiveRender,
28 | InputRender,
29 | CounterRender,
30 | FocusRender,
31 | FocusManagerRender,
32 | FormRender,
33 | HoverRender,
34 | } from './index'
35 |
36 | const noop = () => null
37 |
38 | /* Active */
39 | {
40 | const render: ActiveRender = ({ active, bind }) => {
41 | return null
42 | }
43 | const onChange = (active: boolean) => {}
44 | ;[
45 | ,
46 | {render} ,
47 | ,
48 | {noop} ,
49 | // $ExpectError
50 | ,
51 | ]
52 | }
53 |
54 | /* Input */
55 | {
56 | const render: InputRender = ({ value, set, bind, reset }) => {
57 | return null
58 | }
59 | const onChange = (value: string) => {}
60 | ;[
61 | ,
62 | {render},
63 | ,
64 | {noop},
65 | ,
66 | // $ExpectError
67 | ,
68 | // $ExpectError
69 | ,
70 | ]
71 | }
72 |
73 | /* Counter */
74 | {
75 | const render: CounterRender = ({
76 | count,
77 | inc,
78 | dec,
79 | incBy,
80 | decBy,
81 | resetState,
82 | }) => {
83 | return null
84 | }
85 | const onChange = (count: number) => {}
86 | ;[
87 | ,
88 | {render} ,
89 | ,
90 | {noop} ,
91 | ,
92 | // $ExpectError
93 | ,
94 | // $ExpectError
95 | ,
96 | ]
97 | }
98 |
99 | /* Focus */
100 | {
101 | const render: FocusRender = ({ focused, bind }) => {
102 | return null
103 | }
104 | const onChange = (focused: boolean) => {}
105 | ;[
106 | ,
107 | {render} ,
108 | ,
109 | {noop} ,
110 | // $ExpectError
111 | ,
112 | ]
113 | }
114 |
115 | /* FocusManager */
116 | {
117 | const render: FocusManagerRender = ({ focused, blur, bind }) => {
118 | return null
119 | }
120 | const onChange = (focused: boolean) => {}
121 | ;[
122 | ,
123 | {render} ,
124 | ,
125 | {noop} ,
126 | // $ExpectError
127 | ,
128 | ]
129 | }
130 |
131 | /* Form */
132 | {
133 | const render: FormRender<{ a: string }, 'a'> = ({ field }) =>
134 | const onChange = (data: {}) => {}
135 | ;[
136 | ,
137 | ,
138 | ,
139 | ,
142 | // $ExpectError
143 | ,
144 | // $ExpectError
145 | ,
146 | // $ExpectError
147 | ,
148 | // $ExpectError
149 | ,
150 | // $ExpectError
151 | ,
152 | ]
153 | }
154 |
155 | /* Hover */
156 | {
157 | const render: HoverRender = ({ hovered, bind }) => {
158 | return null
159 | }
160 | const onChange = (hovered: boolean) => {}
161 | ;[
162 | ,
163 | {render} ,
164 | ,
165 | {noop} ,
166 | // $ExpectError
167 | ,
168 | ]
169 | }
170 |
171 | /* List */
172 | {
173 | const render: ListRender = ({
174 | list,
175 | first,
176 | last,
177 | set,
178 | push,
179 | pull,
180 | sort,
181 | }) =>
182 |
183 | const onChange = (list: number[]) => {}
184 | ;[
185 |
,
186 | {render}
,
187 |
,
188 |
189 | {noop}
190 |
,
191 | // $ExpectError
192 |
,
193 | // $ExpectError
194 |
,
195 | // $ExpectError
196 | {noop}
,
197 | // $ExpectError
198 |
,
199 | // $ExpectError
200 | {noop}
,
201 | ]
202 | }
203 |
204 | /* Set */
205 | {
206 | const render: SetRender = ({ values, add, clear, remove, has }) => {
207 | return null
208 | }
209 | const onChange = (values: number[]) => {}
210 | ;[
211 | ,
212 | {render} ,
213 | ,
214 |
215 | {noop}
216 | ,
217 | ]
218 | }
219 |
220 | /* Map */
221 | {
222 | const render: MapRender<{ a: number }, 'a'> = ({ values, set, get }) => {
223 | return null
224 | }
225 | const onChange = (values: { a: number }) => {}
226 | ;[
227 | ,
228 | {render} ,
229 | ,
230 |
231 | {noop}
232 | ,
233 | // $ExpectError
234 | ,
235 | ]
236 | }
237 |
238 | /* State with inferred generic */
239 | {
240 | const render: StateRender<{ v: number; n?: null }> = ({
241 | state,
242 | setState,
243 | }) =>
244 | const onChange = (state: { v: number; n?: null }) => {}
245 | ;[
246 | ,
247 | {render} ,
248 | ,
249 |
250 | {noop}
251 | ,
252 | ,
253 | // $ExpectError
254 | ,
255 | // $ExpectError
256 | ,
257 | ]
258 | }
259 |
260 | /* Toggle */
261 | {
262 | const render: ToggleRender = ({ on, toggle, set }) => {
263 | return null
264 | }
265 | const onChange = (on: boolean) => {}
266 | ;[
267 | ,
268 | {render} ,
269 | ,
270 | {noop} ,
271 | ,
272 | // $ExpectError
273 | ,
274 | // $ExpectError
275 | ,
276 | ]
277 | }
278 |
279 | /* Touch */
280 | {
281 | const render: TouchRender = ({ touched, bind }) => {
282 | return null
283 | }
284 | const onChange = (touched: boolean) => {}
285 | ;[
286 | ,
287 | {render} ,
288 | ,
289 | {noop} ,
290 | // $ExpectError
291 | ,
292 | ]
293 | }
294 |
295 | /* Value with inferred generic */
296 | {
297 | const renderN: ValueRender = ({ value, set }) =>
298 | const renderS: ValueRender = ({ value, set }) =>
299 | const onChangeN = (value: number) => {}
300 | const onChangeS = (value: string) => {}
301 | ;[
302 | ,
303 | {renderS} ,
304 | ,
305 |
306 | {noop}
307 | ,
308 |
309 | {({ set }) => +{set(42) && set(x => x + 1) && '1'}
}
310 | ,
311 | // $ExpectError
312 |
313 | {({ set }) => +{set('42') && set(x => x + 1) && '1'}
}
314 | ,
315 | // $ExpectError
316 |
317 | {({ set }) => +{set(42) && set(x => x + '1') && '1'}
}
318 | ,
319 | // $ExpectError
320 | ,
321 | // $ExpectError
322 | ,
323 | // $ExpectError
324 | ,
325 | // $ExpectError
326 | {renderN} ,
327 | ]
328 | }
329 |
--------------------------------------------------------------------------------
/types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "lib": ["es5"],
5 | "jsx": "react",
6 | "noImplicitAny": true,
7 | "noImplicitThis": true,
8 | "strictNullChecks": true,
9 | "noEmit": true,
10 | "strictFunctionTypes": false
11 | }
12 | }
--------------------------------------------------------------------------------