Compare commits
285 Commits
v16.3.2
...
radio-clic
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0664d552c | ||
|
|
1609cf3432 | ||
|
|
46d5afc54d | ||
|
|
b3b80a4835 | ||
|
|
5e8beec84b | ||
|
|
470377bbdb | ||
|
|
ad17ca639b | ||
|
|
6db080154b | ||
|
|
fddb23601f | ||
|
|
95738e5cfd | ||
|
|
d79238f1ee | ||
|
|
ae63110335 | ||
|
|
e341e503b2 | ||
|
|
00cd4444e2 | ||
|
|
41f6d8cc7a | ||
|
|
0624c719f4 | ||
|
|
f60a7f722c | ||
|
|
ff41519ec2 | ||
|
|
c44c2a2161 | ||
|
|
28cd494bdf | ||
|
|
b381f41411 | ||
|
|
0182a74632 | ||
|
|
2a2ef7e0fd | ||
|
|
840cb1a268 | ||
|
|
bc1ea9cd96 | ||
|
|
0154a79fed | ||
|
|
dbd16c8a96 | ||
|
|
ca0941fce3 | ||
|
|
a32c727f2e | ||
|
|
2b509e2c8c | ||
|
|
5776fa3fcf | ||
|
|
3d3506d37d | ||
|
|
71b4e99901 | ||
|
|
8121212f0d | ||
|
|
2a1bc3f74c | ||
|
|
2c560cb995 | ||
|
|
ead08827d0 | ||
|
|
e4e58343e4 | ||
|
|
0e235bb8f7 | ||
|
|
236f608723 | ||
|
|
acbb4f93f0 | ||
|
|
171e0b7d44 | ||
|
|
606c30aa5f | ||
|
|
9f78913b20 | ||
|
|
6d3e262880 | ||
|
|
82c7ca4cca | ||
|
|
21ac62c77a | ||
|
|
d6a0626b38 | ||
|
|
fd410f43fc | ||
|
|
f9358c51c8 | ||
|
|
854c953905 | ||
|
|
467d139101 | ||
|
|
43ffae2d17 | ||
|
|
659a29cecf | ||
|
|
58f3b29d91 | ||
|
|
1c89cb62fd | ||
|
|
e6076ecf48 | ||
|
|
32f6f258ba | ||
|
|
9ca37f8431 | ||
|
|
f89f25f471 | ||
|
|
7b99ceabec | ||
|
|
6ebc8f3c07 | ||
|
|
d64d1ddb57 | ||
|
|
afd46490d0 | ||
|
|
e79366d549 | ||
|
|
1f32d3c6dc | ||
|
|
377e1a049e | ||
|
|
96d38d178a | ||
|
|
095dd5049c | ||
|
|
5662595677 | ||
|
|
ebbd221432 | ||
|
|
ddc91af795 | ||
|
|
3596e40b39 | ||
|
|
449f6ddd5c | ||
|
|
f762b3abb1 | ||
|
|
6f6b560a64 | ||
|
|
1386ccddd8 | ||
|
|
f5779bbc10 | ||
|
|
9faf389e79 | ||
|
|
85fe4ddce7 | ||
|
|
07fefe3331 | ||
|
|
88d7ed8bfb | ||
|
|
f128fdea48 | ||
|
|
aa8266c4f7 | ||
|
|
c039c16f21 | ||
|
|
6731bfbed7 | ||
|
|
64e1921aab | ||
|
|
bf32a3d195 | ||
|
|
183aefa51f | ||
|
|
3297102de6 | ||
|
|
f4b6a9f8ee | ||
|
|
3eedcb1fda | ||
|
|
6d6de6011c | ||
|
|
71a60ddb16 | ||
|
|
9e6c99ca2e | ||
|
|
baff5cc2f6 | ||
|
|
6a530e3baa | ||
|
|
c35a1e7483 | ||
|
|
076bbeace7 | ||
|
|
da5c87bdfa | ||
|
|
a960d18bc7 | ||
|
|
5b3d17a5f7 | ||
|
|
b0f60895f7 | ||
|
|
ae8c6dd534 | ||
|
|
0fcf92d06d | ||
|
|
97af3e1f3a | ||
|
|
4fe6eec15b | ||
|
|
8e87c139b4 | ||
|
|
aeda7b745d | ||
|
|
b1b3acbd6b | ||
|
|
ae14317d68 | ||
|
|
72434a7686 | ||
|
|
64c54edea4 | ||
|
|
9bd4d1fae2 | ||
|
|
9bda7b28f3 | ||
|
|
2e75779075 | ||
|
|
bc963f353d | ||
|
|
051637da61 | ||
|
|
2a8085980f | ||
|
|
e0c78344e2 | ||
|
|
74b1723df1 | ||
|
|
9725065eb4 | ||
|
|
a5957bf296 | ||
|
|
0b87b27906 | ||
|
|
65eb6b94ac | ||
|
|
c469e3b422 | ||
|
|
036ae3c6e2 | ||
|
|
945fc1bfce | ||
|
|
392530104c | ||
|
|
1594409fab | ||
|
|
d5c11193e2 | ||
|
|
ec60457bcd | ||
|
|
30bc8ef792 | ||
|
|
d480782c41 | ||
|
|
4ac6f133af | ||
|
|
23be4102df | ||
|
|
d0d4280640 | ||
|
|
c78957eac8 | ||
|
|
bfb12ebb52 | ||
|
|
394b17eede | ||
|
|
188c4252a2 | ||
|
|
d3e0a3aaf3 | ||
|
|
9cf3733a9a | ||
|
|
52fbe7612e | ||
|
|
c5a733e1e3 | ||
|
|
36546b5137 | ||
|
|
65ab53694f | ||
|
|
15767a8f8f | ||
|
|
3118ed9d64 | ||
|
|
524a743313 | ||
|
|
ae57b125c7 | ||
|
|
e0a03c1b4d | ||
|
|
79a740c6e3 | ||
|
|
ff724d3c28 | ||
|
|
001f9ef471 | ||
|
|
83f76e4db9 | ||
|
|
4f1f909b5b | ||
|
|
8aeea5afa2 | ||
|
|
aa85b0fd5f | ||
|
|
a32f857ac7 | ||
|
|
61777a78f6 | ||
|
|
e7bd3d59a9 | ||
|
|
f35d989bea | ||
|
|
5578700671 | ||
|
|
76e07071a1 | ||
|
|
345e0a71ac | ||
|
|
fa7fa812c7 | ||
|
|
8765d60893 | ||
|
|
d31e753f89 | ||
|
|
d427a563d5 | ||
|
|
eca59ec1b3 | ||
|
|
53852a887b | ||
|
|
fe747a51c1 | ||
|
|
3df157480a | ||
|
|
6f4fb4a059 | ||
|
|
d1be01f079 | ||
|
|
c601f7a646 | ||
|
|
7350358374 | ||
|
|
e885791842 | ||
|
|
7c0aca289d | ||
|
|
33289b530c | ||
|
|
ad27845ccd | ||
|
|
60853f09f3 | ||
|
|
dd5fad2961 | ||
|
|
13003654e7 | ||
|
|
12c8a88cd9 | ||
|
|
dc3b144f41 | ||
|
|
0442e8275f | ||
|
|
089d2deb20 | ||
|
|
d7b9b4921b | ||
|
|
9bed4a6aee | ||
|
|
397d6115b7 | ||
|
|
47b003a828 | ||
|
|
c0fe8d6f69 | ||
|
|
5e80d81f37 | ||
|
|
17908c8ac9 | ||
|
|
96992f2a6c | ||
|
|
972d209dcc | ||
|
|
40addbd110 | ||
|
|
40ea053bac | ||
|
|
c5a8dae025 | ||
|
|
9d71ef26c3 | ||
|
|
1a0afed771 | ||
|
|
b245795de3 | ||
|
|
7ccb37161f | ||
|
|
4b8510be0f | ||
|
|
2d20dc47a3 | ||
|
|
d4123b4784 | ||
|
|
2ace49362a | ||
|
|
2da155a4c3 | ||
|
|
d6f304e889 | ||
|
|
8227e54ccf | ||
|
|
ef294ed6fc | ||
|
|
49979bbf52 | ||
|
|
de84d5c107 | ||
|
|
f792275972 | ||
|
|
a5184b215d | ||
|
|
103503eb69 | ||
|
|
9097f3cdf0 | ||
|
|
d758960116 | ||
|
|
025d867dce | ||
|
|
fe7890d569 | ||
|
|
7ba1abecaa | ||
|
|
f2252a2ad4 | ||
|
|
b998357f9d | ||
|
|
7631024722 | ||
|
|
bb44feb05d | ||
|
|
7dc1a176b5 | ||
|
|
e96dc14059 | ||
|
|
1047980dca | ||
|
|
bde4b1659f | ||
|
|
73f59e6f31 | ||
|
|
8c747d01cb | ||
|
|
369dd4fb17 | ||
|
|
45b90d4866 | ||
|
|
b2d16047ae | ||
|
|
c802d29bd1 | ||
|
|
c5d3104fc0 | ||
|
|
0ba63aa141 | ||
|
|
c4abfa4015 | ||
|
|
2a4d2ca7fc | ||
|
|
72542030cf | ||
|
|
37d12e2916 | ||
|
|
0470854f55 | ||
|
|
d430e13582 | ||
|
|
8506062975 | ||
|
|
7b19f93ab9 | ||
|
|
4b2e65d32e | ||
|
|
4f459bb144 | ||
|
|
b0726e9947 | ||
|
|
6565795377 | ||
|
|
42a1262375 | ||
|
|
fc3777b1fe | ||
|
|
a9abd27e4f | ||
|
|
3fb8be5c30 | ||
|
|
0bf24cc83e | ||
|
|
25dda90c1e | ||
|
|
ad7cd68667 | ||
|
|
200357596a | ||
|
|
e0ca51a85d | ||
|
|
7dd4ca2911 | ||
|
|
9a9f54720f | ||
|
|
dcc854bcc3 | ||
|
|
045d4f166d | ||
|
|
7c39328571 | ||
|
|
d883d59863 | ||
|
|
ec57d29941 | ||
|
|
9c77ffb444 | ||
|
|
09a14eacd4 | ||
|
|
1673485720 | ||
|
|
1e3cd332a0 | ||
|
|
149a34f735 | ||
|
|
b548b3cd64 | ||
|
|
5dcf93d146 | ||
|
|
c040bcbea8 | ||
|
|
999b656ed1 | ||
|
|
f80bbf88e5 | ||
|
|
920f30ef77 | ||
|
|
7b1bbe6bef | ||
|
|
0887c7d56c | ||
|
|
039695cc01 | ||
|
|
b05e67e36a | ||
|
|
77ebeb1b09 | ||
|
|
b85c5cd188 | ||
|
|
01402f4ad9 |
4
.babelrc
@@ -18,8 +18,6 @@
|
||||
["transform-es2015-spread", { "loose": true }],
|
||||
"transform-es2015-parameters",
|
||||
["transform-es2015-destructuring", { "loose": true }],
|
||||
["transform-es2015-block-scoping", { "throwIfClosureRequired": true }],
|
||||
"transform-es3-member-expression-literals",
|
||||
"transform-es3-property-literals"
|
||||
["transform-es2015-block-scoping", { "throwIfClosureRequired": true }]
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ jobs:
|
||||
build:
|
||||
|
||||
docker:
|
||||
- image: circleci/node:8
|
||||
- image: circleci/openjdk:8-jdk-node-browsers
|
||||
|
||||
environment:
|
||||
TZ: /usr/share/zoneinfo/America/Los_Angeles
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
|
||||
- run:
|
||||
name: Install Packages
|
||||
command: yarn install
|
||||
command: yarn install --frozen-lockfile
|
||||
|
||||
- run:
|
||||
name: Test Packages
|
||||
|
||||
@@ -81,5 +81,6 @@ module.exports = {
|
||||
spyOnDev: true,
|
||||
spyOnDevAndProd: true,
|
||||
spyOnProd: true,
|
||||
__PROFILE__: true,
|
||||
},
|
||||
};
|
||||
|
||||
42
.flowconfig
@@ -1,42 +0,0 @@
|
||||
[ignore]
|
||||
|
||||
<PROJECT_ROOT>/fixtures/.*
|
||||
<PROJECT_ROOT>/build/.*
|
||||
<PROJECT_ROOT>/scripts/bench/.*
|
||||
|
||||
# These shims are copied into external projects:
|
||||
<PROJECT_ROOT>/scripts/rollup/shims/facebook-www/.*
|
||||
<PROJECT_ROOT>/scripts/rollup/shims/react-native/.*
|
||||
|
||||
<PROJECT_ROOT>/.*/node_modules/y18n/.*
|
||||
<PROJECT_ROOT>/node_modules/chrome-devtools-frontend/.*
|
||||
<PROJECT_ROOT>/node_modules/devtools-timeline-model/.*
|
||||
<PROJECT_ROOT>/node_modules/create-react-class/.*
|
||||
<PROJECT_ROOT>/.*/__mocks__/.*
|
||||
<PROJECT_ROOT>/.*/__tests__/.*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
./node_modules/fbjs/flow/lib/dev.js
|
||||
./scripts/flow
|
||||
|
||||
[options]
|
||||
esproposal.class_static_fields=enable
|
||||
esproposal.class_instance_fields=enable
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
munge_underscores=false
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FixMe
|
||||
suppress_type=$FlowExpectedError
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
[version]
|
||||
^0.61.0
|
||||
3
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
.DS_STORE
|
||||
node_modules
|
||||
scripts/flow/*/.flowconfig
|
||||
*~
|
||||
*.pyc
|
||||
.grunt
|
||||
@@ -21,4 +22,4 @@ chrome-user-data
|
||||
*.iml
|
||||
.vscode
|
||||
*.swp
|
||||
*.swo
|
||||
*.swo
|
||||
|
||||
@@ -6,6 +6,7 @@ module.exports = {
|
||||
jsxBracketSameLine: true,
|
||||
trailingComma: 'es5',
|
||||
printWidth: 80,
|
||||
parser: 'babylon',
|
||||
|
||||
overrides: [
|
||||
{
|
||||
|
||||
114
CHANGELOG.md
@@ -4,8 +4,104 @@
|
||||
Changes that have landed in master but are not yet released.
|
||||
Click to see more.
|
||||
</summary>
|
||||
|
||||
</details>
|
||||
|
||||
## 16.4.2 (August 1, 2018)
|
||||
|
||||
### React DOM Server
|
||||
|
||||
* Fix a [potential XSS vulnerability when the attacker controls an attribute name](https://reactjs.org/blog/2018/08/01/react-v-16-4-2.html) (`CVE-2018-6341`). This fix is available in the latest `react-dom@16.4.2`, as well as in previous affected minor versions: `react-dom@16.0.1`, `react-dom@16.1.2`, `react-dom@16.2.1`, and `react-dom@16.3.3`. ([@gaearon](https://github.com/gaearon) in [#13302](https://github.com/facebook/react/pull/13302))
|
||||
|
||||
* Fix a crash in the server renderer when an attribute is called `hasOwnProperty`. This fix is only available in `react-dom@16.4.2`. ([@gaearon](https://github.com/gaearon) in [#13303](https://github.com/facebook/react/pull/13303))
|
||||
|
||||
## 16.4.1 (June 13, 2018)
|
||||
|
||||
### React
|
||||
|
||||
* You can now assign `propTypes` to components returned by `React.ForwardRef`. ([@bvaughn](https://github.com/bvaughn) in [#12911](https://github.com/facebook/react/pull/12911))
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix a crash when the input `type` changes from some other types to `text`. ([@spirosikmd](https://github.com/spirosikmd) in [#12135](https://github.com/facebook/react/pull/12135))
|
||||
* Fix a crash in IE11 when restoring focus to an SVG element. ([@ThaddeusJiang](https://github.com/ThaddeusJiang) in [#12996](https://github.com/facebook/react/pull/12996))
|
||||
* Fix a range input not updating in some cases. ([@Illu](https://github.com/Illu) in [#12939](https://github.com/facebook/react/pull/12939))
|
||||
* Fix input validation triggering unnecessarily in Firefox. ([@nhunzaker](https://github.com/nhunzaker) in [#12925](https://github.com/facebook/react/pull/12925))
|
||||
* Fix an incorrect `event.target` value for the `onChange` event in IE9. ([@nhunzaker](https://github.com/nhunzaker) in [#12976](https://github.com/facebook/react/pull/12976))
|
||||
* Fix a false positive error when returning an empty `<React.Fragment />` from a component. ([@philipp-spiess](https://github.com/philipp-spiess) in [#12966](https://github.com/facebook/react/pull/12966))
|
||||
|
||||
### React DOM Server
|
||||
|
||||
* Fix an incorrect value being provided by new context API. ([@ericsoderberghp](https://github.com/ericsoderberghp) in [#12985](https://github.com/facebook/react/pull/12985), [@gaearon](https://github.com/gaearon) in [#13019](https://github.com/facebook/react/pull/13019))
|
||||
|
||||
### React Test Renderer
|
||||
|
||||
* Allow multiple root children in test renderer traversal API. ([@gaearon](https://github.com/gaearon) in [#13017](https://github.com/facebook/react/pull/13017))
|
||||
* Fix `getDerivedStateFromProps()` in the shallow renderer to not discard the pending state. ([@fatfisz](https://github.com/fatfisz) in [#13030](https://github.com/facebook/react/pull/13030))
|
||||
|
||||
## 16.4.0 (May 23, 2018)
|
||||
|
||||
### React
|
||||
|
||||
* Add a new [experimental](https://github.com/reactjs/rfcs/pull/51) `React.unstable_Profiler` component for measuring performance. ([@bvaughn](https://github.com/bvaughn) in [#12745](https://github.com/facebook/react/pull/12745))
|
||||
|
||||
### React DOM
|
||||
|
||||
* Add support for the Pointer Events specification. ([@philipp-spiess](https://github.com/philipp-spiess) in [#12507](https://github.com/facebook/react/pull/12507))
|
||||
* Properly call `getDerivedStateFromProps()` regardless of the reason for re-rendering. ([@acdlite](https://github.com/acdlite) in [#12600](https://github.com/facebook/react/pull/12600) and [#12802](https://github.com/facebook/react/pull/12802))
|
||||
* Fix a bug that prevented context propagation in some cases. ([@gaearon](https://github.com/gaearon) in [#12708](https://github.com/facebook/react/pull/12708))
|
||||
* Fix re-rendering of components using `forwardRef()` on a deeper `setState()`. ([@gaearon](https://github.com/gaearon) in [#12690](https://github.com/facebook/react/pull/12690))
|
||||
* Fix some attributes incorrectly getting removed from custom element nodes. ([@airamrguez](https://github.com/airamrguez) in [#12702](https://github.com/facebook/react/pull/12702))
|
||||
* Fix context providers to not bail out on children if there's a legacy context provider above. ([@gaearon](https://github.com/gaearon) in [#12586](https://github.com/facebook/react/pull/12586))
|
||||
* Add the ability to specify `propTypes` on a context provider component. ([@nicolevy](https://github.com/nicolevy) in [#12658](https://github.com/facebook/react/pull/12658))
|
||||
* Fix a false positive warning when using `react-lifecycles-compat` in `<StrictMode>`. ([@bvaughn](https://github.com/bvaughn) in [#12644](https://github.com/facebook/react/pull/12644))
|
||||
* Warn when the `forwardRef()` render function has `propTypes` or `defaultProps`. ([@bvaughn](https://github.com/bvaughn) in [#12644](https://github.com/facebook/react/pull/12644))
|
||||
* Improve how `forwardRef()` and context consumers are displayed in the component stack. ([@sophiebits](https://github.com/sophiebits) in [#12777](https://github.com/facebook/react/pull/12777))
|
||||
* Change internal event names. This can break third-party packages that rely on React internals in unsupported ways. ([@philipp-spiess](https://github.com/philipp-spiess) in [#12629](https://github.com/facebook/react/pull/12629))
|
||||
|
||||
### React Test Renderer
|
||||
|
||||
* Fix the `getDerivedStateFromProps()` support to match the new React DOM behavior. ([@koba04](https://github.com/koba04) in [#12676](https://github.com/facebook/react/pull/12676))
|
||||
* Fix a `testInstance.parent` crash when the parent is a fragment or another special node. ([@gaearon](https://github.com/gaearon) in [#12813](https://github.com/facebook/react/pull/12813))
|
||||
* `forwardRef()` components are now discoverable by the test renderer traversal methods. ([@gaearon](https://github.com/gaearon) in [#12725](https://github.com/facebook/react/pull/12725))
|
||||
* Shallow renderer now ignores `setState()` updaters that return `null` or `undefined`. ([@koba04](https://github.com/koba04) in [#12756](https://github.com/facebook/react/pull/12756))
|
||||
|
||||
### React ART
|
||||
|
||||
* Fix reading context provided from the tree managed by React DOM. ([@acdlite](https://github.com/acdlite) in [#12779](https://github.com/facebook/react/pull/12779))
|
||||
|
||||
### React Call Return (Experimental)
|
||||
|
||||
* This experiment was deleted because it was affecting the bundle size and the API wasn't good enough. It's likely to come back in the future in some other form. ([@gaearon](https://github.com/gaearon) in [#12820](https://github.com/facebook/react/pull/12820))
|
||||
|
||||
### React Reconciler (Experimental)
|
||||
|
||||
* The [new host config shape](https://github.com/facebook/react/blob/c601f7a64640290af85c9f0e33c78480656b46bc/packages/react-noop-renderer/src/createReactNoop.js#L82-L285) is flat and doesn't use nested objects. ([@gaearon](https://github.com/gaearon) in [#12792](https://github.com/facebook/react/pull/12792))
|
||||
|
||||
## 16.3.3 (August 1, 2018)
|
||||
|
||||
### React DOM Server
|
||||
|
||||
* Fix a [potential XSS vulnerability when the attacker controls an attribute name](https://reactjs.org/blog/2018/08/01/react-v-16-4-2.html) (`CVE-2018-6341`). This fix is available in the latest `react-dom@16.4.2`, as well as in previous affected minor versions: `react-dom@16.0.1`, `react-dom@16.1.2`, `react-dom@16.2.1`, and `react-dom@16.3.3`. ([@gaearon](https://github.com/gaearon) in [#13302](https://github.com/facebook/react/pull/13302))
|
||||
|
||||
## 16.3.2 (April 16, 2018)
|
||||
|
||||
### React
|
||||
|
||||
* Improve the error message when passing `null` or `undefined` to `React.cloneElement`. ([@nicolevy](https://github.com/nicolevy) in [#12534](https://github.com/facebook/react/pull/12534))
|
||||
|
||||
### React DOM
|
||||
|
||||
* Fix an IE crash in development when using `<StrictMode>`. ([@bvaughn](https://github.com/bvaughn) in [#12546](https://github.com/facebook/react/pull/12546))
|
||||
* Fix labels in User Timing measurements for new component types. ([@bvaughn](https://github.com/bvaughn) in [#12609](https://github.com/facebook/react/pull/12609))
|
||||
* Improve the warning about wrong component type casing. ([@nicolevy](https://github.com/nicolevy) in [#12533](https://github.com/facebook/react/pull/12533))
|
||||
* Improve general performance in development mode. ([@gaearon](https://github.com/gaearon) in [#12537](https://github.com/facebook/react/pull/12537))
|
||||
* Improve performance of the experimental `unstable_observedBits` API with nesting. ([@gaearon](https://github.com/gaearon) in [#12543](https://github.com/facebook/react/pull/12543))
|
||||
|
||||
### React Test Renderer
|
||||
|
||||
* Add a UMD build. ([@bvaughn](https://github.com/bvaughn) in [#12594](https://github.com/facebook/react/pull/12594))
|
||||
|
||||
## 16.3.1 (April 3, 2018)
|
||||
|
||||
### React
|
||||
@@ -97,6 +193,12 @@
|
||||
|
||||
* Fix a crash on updates. ([@rmhartog](https://github.com/rmhartog) in [#11955](https://github.com/facebook/react/pull/11955))
|
||||
|
||||
## 16.2.1 (August 1, 2018)
|
||||
|
||||
### React DOM Server
|
||||
|
||||
* Fix a [potential XSS vulnerability when the attacker controls an attribute name](https://reactjs.org/blog/2018/08/01/react-v-16-4-2.html) (`CVE-2018-6341`). This fix is available in the latest `react-dom@16.4.2`, as well as in previous affected minor versions: `react-dom@16.0.1`, `react-dom@16.1.2`, `react-dom@16.2.1`, and `react-dom@16.3.3`. ([@gaearon](https://github.com/gaearon) in [#13302](https://github.com/facebook/react/pull/13302))
|
||||
|
||||
## 16.2.0 (November 28, 2017)
|
||||
|
||||
### React
|
||||
@@ -121,6 +223,12 @@
|
||||
|
||||
* Many tests were rewritten against the public API. Big thanks to [everyone who contributed](https://github.com/facebook/react/issues/11299)!
|
||||
|
||||
## 16.1.2 (August 1, 2018)
|
||||
|
||||
### React DOM Server
|
||||
|
||||
* Fix a [potential XSS vulnerability when the attacker controls an attribute name](https://reactjs.org/blog/2018/08/01/react-v-16-4-2.html) (`CVE-2018-6341`). This fix is available in the latest `react-dom@16.4.2`, as well as in previous affected minor versions: `react-dom@16.0.1`, `react-dom@16.1.2`, `react-dom@16.2.1`, and `react-dom@16.3.3`. ([@gaearon](https://github.com/gaearon) in [#13302](https://github.com/facebook/react/pull/13302))
|
||||
|
||||
## 16.1.1 (November 13, 2017)
|
||||
|
||||
### React
|
||||
@@ -211,6 +319,12 @@ Starting with 16.1.0, we will no longer be publishing new releases on Bower. You
|
||||
|
||||
* First release of the [new experimental package](https://github.com/facebook/react/tree/master/packages/react-call-return) for parent-child communication. ([@gaearon](https://github.com/gaearon) in [#11364](https://github.com/facebook/react/pull/11364))
|
||||
|
||||
## 16.0.1 (August 1, 2018)
|
||||
|
||||
### React DOM Server
|
||||
|
||||
* Fix a [potential XSS vulnerability when the attacker controls an attribute name](https://reactjs.org/blog/2018/08/01/react-v-16-4-2.html) (`CVE-2018-6341`). This fix is available in the latest `react-dom@16.4.2`, as well as in previous affected minor versions: `react-dom@16.0.1`, `react-dom@16.1.2`, `react-dom@16.2.1`, and `react-dom@16.3.3`. ([@gaearon](https://github.com/gaearon) in [#13302](https://github.com/facebook/react/pull/13302))
|
||||
|
||||
## 16.0.0 (September 26, 2017)
|
||||
|
||||
### New JS Environment Requirements
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
# Code of Conduct
|
||||
|
||||
Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.facebook.com/pages/876921332402685/open-source-code-of-conduct) so that you can understand what actions will and will not be tolerated.
|
||||
Facebook has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](https://code.fb.com/codeofconduct/) so that you can understand what actions will and will not be tolerated.
|
||||
|
||||
37
README.md
@@ -8,15 +8,28 @@ React is a JavaScript library for building user interfaces.
|
||||
|
||||
[Learn how to use React in your own project](https://reactjs.org/docs/getting-started.html).
|
||||
|
||||
## Installation
|
||||
|
||||
React has been designed for gradual adoption from the start, and **you can use as little or as much React as you need**:
|
||||
|
||||
* Use [Online Playgrounds](https://reactjs.org/docs/getting-started.html#online-playgrounds) to get a taste of React.
|
||||
* [Add React to a Website](https://reactjs.org/docs/add-react-to-a-website.html) as a `<script>` tag in one minute.
|
||||
* [Create a New React App](https://reactjs.org/docs/create-a-new-react-app.html) if you're looking for a powerful JavaScript toolchain.
|
||||
|
||||
You can use React as a `<script>` tag from a [CDN](https://reactjs.org/docs/cdn-links.html), or as a `react` package on [npm](https://www.npmjs.com/).
|
||||
|
||||
## Documentation
|
||||
|
||||
You can find the React documentation [on the website](https://reactjs.org/docs).
|
||||
It is divided into several sections:
|
||||
|
||||
* [Quick Start](https://reactjs.org/docs/hello-world.html)
|
||||
Check out the [Getting Started](https://reactjs.org/docs/getting-started.html) page for a quick overview.
|
||||
|
||||
The documentation is divided into several sections:
|
||||
|
||||
* [Tutorial](https://reactjs.org/tutorial/tutorial.html)
|
||||
* [Main Concepts](https://reactjs.org/docs/hello-world.html)
|
||||
* [Advanced Guides](https://reactjs.org/docs/jsx-in-depth.html)
|
||||
* [API Reference](https://reactjs.org/docs/react-api.html)
|
||||
* [Tutorial](https://reactjs.org/tutorial/tutorial.html)
|
||||
* [Where to Get Support](https://reactjs.org/community/support.html)
|
||||
* [Contributing Guide](https://reactjs.org/docs/how-to-contribute.html)
|
||||
|
||||
@@ -34,26 +47,14 @@ class HelloMessage extends React.Component {
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
<HelloMessage name="John" />,
|
||||
<HelloMessage name="Taylor" />,
|
||||
document.getElementById('container')
|
||||
);
|
||||
```
|
||||
|
||||
This example will render "Hello John" into a container on the page.
|
||||
This example will render "Hello Taylor" into a container on the page.
|
||||
|
||||
You'll notice that we used an HTML-like syntax; [we call it JSX](https://reactjs.org/docs/introducing-jsx.html). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML. We recommend using [Babel](https://babeljs.io/) with a [React preset](https://babeljs.io/docs/plugins/preset-react/) to convert JSX into native JavaScript for browsers to digest.
|
||||
|
||||
## Installation
|
||||
|
||||
React is available as the `react` package on [npm](https://www.npmjs.com/). It is also available on a [CDN](https://reactjs.org/docs/cdn-links.html).
|
||||
|
||||
React is flexible and can be used in a variety of projects. You can create new apps with it, but you can also gradually introduce it into an existing codebase without doing a rewrite.
|
||||
|
||||
The recommended way to install React depends on your project. Here you can find short guides for the most common scenarios:
|
||||
|
||||
* [Trying Out React](https://reactjs.org/docs/try-react.html)
|
||||
* [Creating a New Application](https://reactjs.org/docs/add-react-to-a-new-app.html)
|
||||
* [Adding React to an Existing Application](https://reactjs.org/docs/add-react-to-an-existing-app.html)
|
||||
You'll notice that we used an HTML-like syntax; [we call it JSX](https://reactjs.org/docs/introducing-jsx.html). JSX is not required to use React, but it makes code more readable, and writing it feels like writing HTML. If you're using React as a `<script>` tag, read [this section](https://reactjs.org/docs/add-react-to-a-website.html#optional-try-react-with-jsx) on integrating JSX; otherwise, the [recommended JavaScript toolchains](https://reactjs.org/docs/create-a-new-react-app.html) handle it automatically.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -5,8 +5,9 @@ init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
environment:
|
||||
JAVA_HOME: C:\Program Files\Java\jdk1.8.0
|
||||
matrix:
|
||||
- nodejs_version: 8
|
||||
- nodejs_version: 10
|
||||
|
||||
# Finish on first failed build
|
||||
matrix:
|
||||
@@ -25,12 +26,12 @@ deploy: off
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version $env:platform
|
||||
- yarn install
|
||||
- yarn install --frozen-lockfile
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- yarn lint
|
||||
- yarn flow
|
||||
# - yarn flow-ci
|
||||
- yarn build
|
||||
- yarn test
|
||||
- yarn prettier
|
||||
|
||||
@@ -53,7 +53,8 @@ function generateMDTable(headers, body) {
|
||||
|
||||
/**
|
||||
* Generates a user-readable string from a percentage change
|
||||
* @param {string[]} headers
|
||||
* @param {number} change
|
||||
* @param {boolean} includeEmoji
|
||||
*/
|
||||
function addPercent(change, includeEmoji) {
|
||||
if (!isFinite(change)) {
|
||||
|
||||
@@ -20,7 +20,7 @@ The left box shows the property (or attribute) assigned by React 15.\*, and the
|
||||
right box shows the property (or attribute) assigned by the latest version of
|
||||
React 16.
|
||||
|
||||
Right now we use a purple outline to call out cases where the assigned property
|
||||
Right now, we use a purple outline to call out cases where the assigned property
|
||||
(or attribute) has changed between React 15 and 16.
|
||||
|
||||
---
|
||||
|
||||
@@ -64,6 +64,8 @@ class Header extends React.Component {
|
||||
<option value="/event-pooling">Event Pooling</option>
|
||||
<option value="/custom-elements">Custom Elements</option>
|
||||
<option value="/media-events">Media Events</option>
|
||||
<option value="/pointer-events">Pointer Events</option>
|
||||
<option value="/mouse-events">Mouse Events</option>
|
||||
</select>
|
||||
</label>
|
||||
<label htmlFor="react_version">
|
||||
|
||||
@@ -11,6 +11,8 @@ import ErrorHandling from './error-handling';
|
||||
import EventPooling from './event-pooling';
|
||||
import CustomElementFixtures from './custom-elements';
|
||||
import MediaEventsFixtures from './media-events';
|
||||
import PointerEventsFixtures from './pointer-events';
|
||||
import MouseEventsFixtures from './mouse-events';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
@@ -46,6 +48,10 @@ function FixturesPage() {
|
||||
return <CustomElementFixtures />;
|
||||
case '/media-events':
|
||||
return <MediaEventsFixtures />;
|
||||
case '/pointer-events':
|
||||
return <PointerEventsFixtures />;
|
||||
case '/mouse-events':
|
||||
return <MouseEventsFixtures />;
|
||||
default:
|
||||
return <p>Please select a test fixture.</p>;
|
||||
}
|
||||
|
||||
16
fixtures/dom/src/components/fixtures/mouse-events/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import FixtureSet from '../../FixtureSet';
|
||||
import MouseMovement from './mouse-movement';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class MouseEvents extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<FixtureSet title="Mouse Events" description="">
|
||||
<MouseMovement />
|
||||
</FixtureSet>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MouseEvents;
|
||||
@@ -0,0 +1,48 @@
|
||||
import TestCase from '../../TestCase';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class MouseMovement extends React.Component {
|
||||
state = {
|
||||
movement: {x: 0, y: 0},
|
||||
};
|
||||
|
||||
onMove = event => {
|
||||
this.setState({x: event.movementX, y: event.movementY});
|
||||
};
|
||||
|
||||
render() {
|
||||
const {x, y} = this.state;
|
||||
|
||||
const boxStyle = {
|
||||
padding: '10px 20px',
|
||||
border: '1px solid #d9d9d9',
|
||||
margin: '10px 0 20px',
|
||||
};
|
||||
|
||||
return (
|
||||
<TestCase
|
||||
title="Mouse Movement"
|
||||
description="We polyfill the movementX and movementY fields."
|
||||
affectedBrowsers="IE, Safari">
|
||||
<TestCase.Steps>
|
||||
<li>Mouse over the box below</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
The reported values should equal the pixel (integer) difference
|
||||
between mouse movements positions.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<div style={boxStyle} onMouseMove={this.onMove}>
|
||||
<p>Trace your mouse over this box.</p>
|
||||
<p>
|
||||
Last movement: {x},{y}
|
||||
</p>
|
||||
</div>
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MouseMovement;
|
||||
@@ -15,7 +15,7 @@ function NumberInputs() {
|
||||
`}
|
||||
affectedBrowsers="IE Edge, IE 11">
|
||||
<TestCase.Steps>
|
||||
<li>Type any string (not an actual password</li>
|
||||
<li>Type any string (not an actual password)</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
const React = window.React;
|
||||
|
||||
const CIRCLE_SIZE = 80;
|
||||
|
||||
class DragBox extends React.Component {
|
||||
state = {
|
||||
hasCapture: false,
|
||||
circleLeft: 80,
|
||||
circleTop: 80,
|
||||
};
|
||||
isDragging = false;
|
||||
previousLeft = 0;
|
||||
previousTop = 0;
|
||||
|
||||
onDown = event => {
|
||||
this.isDragging = true;
|
||||
event.target.setPointerCapture(event.pointerId);
|
||||
|
||||
// We store the initial coordinates to be able to calculate the changes
|
||||
// later on.
|
||||
this.extractPositionDelta(event);
|
||||
};
|
||||
|
||||
onMove = event => {
|
||||
if (!this.isDragging) {
|
||||
return;
|
||||
}
|
||||
const {left, top} = this.extractPositionDelta(event);
|
||||
|
||||
this.setState(({circleLeft, circleTop}) => ({
|
||||
circleLeft: circleLeft + left,
|
||||
circleTop: circleTop + top,
|
||||
}));
|
||||
};
|
||||
|
||||
onUp = event => (this.isDragging = false);
|
||||
onGotCapture = event => this.setState({hasCapture: true});
|
||||
onLostCapture = event => this.setState({hasCapture: false});
|
||||
|
||||
extractPositionDelta = event => {
|
||||
const left = event.pageX;
|
||||
const top = event.pageY;
|
||||
const delta = {
|
||||
left: left - this.previousLeft,
|
||||
top: top - this.previousTop,
|
||||
};
|
||||
this.previousLeft = left;
|
||||
this.previousTop = top;
|
||||
return delta;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {hasCapture, circleLeft, circleTop} = this.state;
|
||||
|
||||
const boxStyle = {
|
||||
border: '1px solid #d9d9d9',
|
||||
margin: '10px 0 20px',
|
||||
minHeight: 400,
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
};
|
||||
|
||||
const circleStyle = {
|
||||
width: CIRCLE_SIZE,
|
||||
height: CIRCLE_SIZE,
|
||||
borderRadius: CIRCLE_SIZE / 2,
|
||||
position: 'absolute',
|
||||
left: circleLeft,
|
||||
top: circleTop,
|
||||
backgroundColor: hasCapture ? 'blue' : 'green',
|
||||
touchAction: 'none',
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={boxStyle}>
|
||||
<div
|
||||
style={circleStyle}
|
||||
onPointerDown={this.onDown}
|
||||
onPointerMove={this.onMove}
|
||||
onPointerUp={this.onUp}
|
||||
onPointerCancel={this.onUp}
|
||||
onGotPointerCapture={this.onGotCapture}
|
||||
onLostPointerCapture={this.onLostCapture}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DragBox;
|
||||
25
fixtures/dom/src/components/fixtures/pointer-events/drag.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import TestCase from '../../TestCase';
|
||||
import DragBox from './drag-box';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class Drag extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<TestCase title="Drag" description="">
|
||||
<TestCase.Steps>
|
||||
<li>Drag the circle below with any pointer tool</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
While dragging, the circle must have turn blue to indicate that a
|
||||
pointer capture was received.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<DragBox />
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Drag;
|
||||
@@ -0,0 +1,34 @@
|
||||
const React = window.React;
|
||||
|
||||
class DrawBox extends React.Component {
|
||||
render() {
|
||||
const boxStyle = {
|
||||
border: '1px solid #d9d9d9',
|
||||
margin: '10px 0 20px',
|
||||
padding: '20px 20px',
|
||||
touchAction: 'none',
|
||||
};
|
||||
|
||||
const obstacleStyle = {
|
||||
border: '1px solid #d9d9d9',
|
||||
width: '25%',
|
||||
height: '200px',
|
||||
margin: '12.5%',
|
||||
display: 'inline-block',
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={boxStyle}
|
||||
onPointerOver={this.props.onOver}
|
||||
onPointerOut={this.props.onOut}
|
||||
onPointerEnter={this.props.onEnter}
|
||||
onPointerLeave={this.props.onLeave}>
|
||||
<div style={obstacleStyle} />
|
||||
<div style={obstacleStyle} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DrawBox;
|
||||
51
fixtures/dom/src/components/fixtures/pointer-events/hover.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import TestCase from '../../TestCase';
|
||||
import HoverBox from './hover-box';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class Hover extends React.Component {
|
||||
state = {
|
||||
overs: 0,
|
||||
outs: 0,
|
||||
enters: 0,
|
||||
leaves: 0,
|
||||
};
|
||||
|
||||
onOver = () => this.setState({overs: this.state.overs + 1});
|
||||
onOut = () => this.setState({outs: this.state.outs + 1});
|
||||
onEnter = () => this.setState({enters: this.state.enters + 1});
|
||||
onLeave = () => this.setState({leaves: this.state.leaves + 1});
|
||||
|
||||
render() {
|
||||
const {overs, outs, enters, leaves} = this.state;
|
||||
|
||||
return (
|
||||
<TestCase title="Hover" description="">
|
||||
<TestCase.Steps>
|
||||
<li>Hover over the above box and the obstacles</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
Overs and outs should increase when moving over the obstacles but
|
||||
enters and leaves should not.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<HoverBox
|
||||
onOver={this.onOver}
|
||||
onOut={this.onOut}
|
||||
onEnter={this.onEnter}
|
||||
onLeave={this.onLeave}
|
||||
/>
|
||||
|
||||
<p>
|
||||
Pointer Overs: <b>{overs}</b> <br />
|
||||
Pointer Outs: <b>{outs}</b> <br />
|
||||
Pointer Enters: <b>{enters}</b> <br />
|
||||
Pointer Leaves: <b>{leaves}</b> <br />
|
||||
</p>
|
||||
</TestCase>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Hover;
|
||||
20
fixtures/dom/src/components/fixtures/pointer-events/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import FixtureSet from '../../FixtureSet';
|
||||
import Drag from './drag';
|
||||
import Hover from './hover';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class PointerEvents extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<FixtureSet
|
||||
title="Pointer Events"
|
||||
description="Pointer Events are not supported in every browser. The examples below might not work in every browser. To test pointer events, make sure to use Google Chrome, Firefox, Internet Explorer, or Edge.">
|
||||
<Drag />
|
||||
<Hover />
|
||||
</FixtureSet>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PointerEvents;
|
||||
@@ -158,6 +158,31 @@ class SelectFixture extends React.Component {
|
||||
</form>
|
||||
</div>
|
||||
</TestCase>
|
||||
|
||||
<TestCase
|
||||
title="An option which contains conditional render fails"
|
||||
relatedIssues="11911">
|
||||
<TestCase.Steps>
|
||||
<li>Select any option</li>
|
||||
</TestCase.Steps>
|
||||
<TestCase.ExpectedResult>
|
||||
Option should be set
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<div className="test-fixture">
|
||||
<select value={this.state.value} onChange={this.onChange}>
|
||||
<option value="red">
|
||||
red {this.state.value === 'red' && 'is chosen '} TextNode
|
||||
</option>
|
||||
<option value="blue">
|
||||
blue {this.state.value === 'blue' && 'is chosen '} TextNode
|
||||
</option>
|
||||
<option value="green">
|
||||
green {this.state.value === 'green' && 'is chosen '} TextNode
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</TestCase>
|
||||
</FixtureSet>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import Fixture from '../../Fixture';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
class ReplaceEmailInput extends React.Component {
|
||||
state = {
|
||||
formSubmitted: false,
|
||||
};
|
||||
|
||||
onReset = () => {
|
||||
this.setState({formSubmitted: false});
|
||||
};
|
||||
|
||||
onSubmit = event => {
|
||||
event.preventDefault();
|
||||
this.setState({formSubmitted: true});
|
||||
};
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fixture>
|
||||
<form className="control-box" onSubmit={this.onSubmit}>
|
||||
<fieldset>
|
||||
<legend>Email</legend>
|
||||
{!this.state.formSubmitted ? (
|
||||
<input type="email" />
|
||||
) : (
|
||||
<input type="text" disabled={true} />
|
||||
)}
|
||||
</fieldset>
|
||||
</form>
|
||||
<button type="button" onClick={this.onReset}>
|
||||
Reset
|
||||
</button>
|
||||
</Fixture>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ReplaceEmailInput;
|
||||
@@ -2,6 +2,7 @@ import Fixture from '../../Fixture';
|
||||
import FixtureSet from '../../FixtureSet';
|
||||
import TestCase from '../../TestCase';
|
||||
import InputTestCase from './InputTestCase';
|
||||
import ReplaceEmailInput from './ReplaceEmailInput';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
@@ -64,21 +65,36 @@ class TextInputFixtures extends React.Component {
|
||||
<Fixture>
|
||||
<form className="control-box">
|
||||
<fieldset>
|
||||
<legend>Text</legend>
|
||||
<legend>Empty value prop string</legend>
|
||||
<input value="" required={true} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>No value prop</legend>
|
||||
<input required={true} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Date</legend>
|
||||
<legend>Empty defaultValue prop string</legend>
|
||||
<input required={true} defaultValue="" />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>No value prop date input</legend>
|
||||
<input type="date" required={true} />
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Empty value prop date input</legend>
|
||||
<input type="date" value="" required={true} />
|
||||
</fieldset>
|
||||
</form>
|
||||
</Fixture>
|
||||
|
||||
<p className="footnote">
|
||||
Checking the date type is also important because of a prior fix for
|
||||
iOS Safari that involved assigning over value/defaultValue
|
||||
properties of the input to prevent a display bug. This also
|
||||
triggered input validation.
|
||||
properties of the input to prevent a display bug. This also triggers
|
||||
input validation.
|
||||
</p>
|
||||
<p className="footnote">
|
||||
The date inputs should be blank in iOS. This is not a bug.
|
||||
</p>
|
||||
</TestCase>
|
||||
|
||||
@@ -110,6 +126,21 @@ class TextInputFixtures extends React.Component {
|
||||
<InputTestCase type="url" defaultValue="" />
|
||||
</TestCase>
|
||||
|
||||
<TestCase
|
||||
title="Replacing email input with text disabled input"
|
||||
relatedIssues="12062">
|
||||
<TestCase.Steps>
|
||||
<li>Type "test@test.com"</li>
|
||||
<li>Press enter</li>
|
||||
</TestCase.Steps>
|
||||
|
||||
<TestCase.ExpectedResult>
|
||||
There should be no selection-related error in the console.
|
||||
</TestCase.ExpectedResult>
|
||||
|
||||
<ReplaceEmailInput />
|
||||
</TestCase>
|
||||
|
||||
<TestCase title="All inputs" description="General test of all inputs">
|
||||
<InputTestCase type="text" defaultValue="Text" />
|
||||
<InputTestCase type="email" defaultValue="user@example.com" />
|
||||
|
||||
494
fixtures/schedule/index.html
Normal file
@@ -0,0 +1,494 @@
|
||||
<!DOCTYPE html>
|
||||
<html style="width: 100%; height: 100%;">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Schedule Test Page</title>
|
||||
<style>
|
||||
.correct {
|
||||
border: solid green 2px;
|
||||
}
|
||||
.incorrect {
|
||||
border: dashed red 2px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Schedule Fixture</h1>
|
||||
<p>
|
||||
This fixture is for manual testing purposes, and the patterns used in
|
||||
implementing it should not be used as a model. This is mainly for anyone
|
||||
working on making changes to the `schedule` module.
|
||||
</p>
|
||||
<h2>Tests:</h2>
|
||||
<ol>
|
||||
<li>
|
||||
<button onClick="runTestOne()">Run Test 1</button>
|
||||
<p>Calls the callback within the frame when not blocked:</p>
|
||||
<div><b>Expected:</b></div>
|
||||
<div id="test-1-expected">
|
||||
</div>
|
||||
<div> -------------------------------------------------</div>
|
||||
<div> If you see the same above and below it's correct.
|
||||
<div> -------------------------------------------------</div>
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-1"></div>
|
||||
</li>
|
||||
<li>
|
||||
<p>Accepts multiple callbacks and calls within frame when not blocked</p>
|
||||
<button onClick="runTestTwo()">Run Test 2</button>
|
||||
<div><b>Expected:</b></div>
|
||||
<div id="test-2-expected">
|
||||
</div>
|
||||
<div> -------------------------------------------------</div>
|
||||
<div> If you see the same above and below it's correct.
|
||||
<div> -------------------------------------------------</div>
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-2"></div>
|
||||
</li>
|
||||
<li>
|
||||
<p>Schedules callbacks in correct order when they use scheduleWork to schedule themselves</p>
|
||||
<button onClick="runTestThree()">Run Test 3</button>
|
||||
<div><b>Expected:</b></div>
|
||||
<div id="test-3-expected">
|
||||
</div>
|
||||
<div> -------------------------------------------------</div>
|
||||
<div> If you see the same above and below it's correct.
|
||||
<div> -------------------------------------------------</div>
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-3"></div>
|
||||
</li>
|
||||
<li>
|
||||
<p>Calls timed out callbacks and then any more pending callbacks, defers others if time runs out</p>
|
||||
<button onClick="runTestFour()">Run Test 4</button>
|
||||
<div><b>Expected:</b></div>
|
||||
<div id="test-4-expected">
|
||||
</div>
|
||||
<div> -------------------------------------------------</div>
|
||||
<div> If you see the same above and below it's correct.
|
||||
<div> -------------------------------------------------</div>
|
||||
<div><b>Actual:</b></div>
|
||||
<div id="test-4"></div>
|
||||
</li>
|
||||
<li>
|
||||
<p>When some callbacks throw errors, still calls them all within the same frame</p>
|
||||
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
|
||||
<button onClick="runTestFive()">Run Test 5</button>
|
||||
</li>
|
||||
<li>
|
||||
<p>When some callbacks throw errors <b> and some also time out</b>, still calls them all within the same frame</p>
|
||||
<p><b>IMPORTANT:</b> Open the console when you run this! Inspect the logs there!</p>
|
||||
<button onClick="runTestSix()">Run Test 6</button>
|
||||
</li>
|
||||
<li>
|
||||
<p>Continues calling callbacks even when user switches away from this tab</p>
|
||||
<button onClick="runTestSeven()">Run Test 7</button>
|
||||
<div><b>Click the button above, observe the counter, then switch to
|
||||
another tab and switch back:</b></div>
|
||||
<div id="test-7">
|
||||
</div>
|
||||
<div> If the counter advanced while you were away from this tab, it's correct.</div>
|
||||
</li>
|
||||
</ol>
|
||||
<script src="../../build/dist/react-scheduler.development.js"></script>
|
||||
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
|
||||
<script type="text/babel">
|
||||
const {scheduleWork, cancelWork, now} = ReactScheduler;
|
||||
function displayTestResult(testNumber) {
|
||||
const expectationNode = document.getElementById('test-' + testNumber + '-expected');
|
||||
const resultNode = document.getElementById('test-' + testNumber);
|
||||
resultNode.innerHTML = latestResults[testNumber - 1].join('<br />');
|
||||
expectationNode.innerHTML = expectedResults[testNumber - 1].join('<br />');
|
||||
}
|
||||
function clearTestResult(testNumber) {
|
||||
const resultNode = document.getElementById('test-' + testNumber);
|
||||
resultNode.innerHTML = '';
|
||||
latestResults[testNumber - 1] = [];
|
||||
}
|
||||
function updateTestResult(testNumber, textToAddToResult) {
|
||||
latestResults[testNumber - 1].push(textToAddToResult);
|
||||
};
|
||||
function checkTestResult(testNumber) {
|
||||
|
||||
let correct = true;
|
||||
const expected = expectedResults[testNumber - 1]; // zero indexing
|
||||
const result = latestResults[testNumber - 1]; // zero indexing
|
||||
if (expected.length !== result.length) {
|
||||
correct = false;
|
||||
} else {
|
||||
for (let i = 0, len = expected.length; i < len; i++) {
|
||||
if (expected[i] !== result[i]) {
|
||||
correct = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const currentClass = correct ? 'correct' : 'incorrect';
|
||||
const previousClass = correct ? 'incorrect' : 'correct';
|
||||
document.getElementById('test-' + testNumber).classList.remove(previousClass);
|
||||
document.getElementById('test-' + testNumber).classList.add(currentClass);
|
||||
}
|
||||
function logWhenFramesStart(testNumber, cb) {
|
||||
requestAnimationFrame(() => {
|
||||
updateTestResult(testNumber, 'frame 1 started');
|
||||
requestAnimationFrame(() => {
|
||||
updateTestResult(testNumber, 'frame 2 started');
|
||||
requestAnimationFrame(() => {
|
||||
updateTestResult(testNumber, 'frame 3 started... we stop counting now.');
|
||||
cb();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
// push in results when we run the test
|
||||
const latestResults = [
|
||||
// test 1
|
||||
[
|
||||
],
|
||||
// test 2
|
||||
[
|
||||
],
|
||||
// test 3
|
||||
[
|
||||
],
|
||||
// test 4
|
||||
[
|
||||
],
|
||||
// test 5
|
||||
[
|
||||
],
|
||||
];
|
||||
|
||||
const expectedResults = [
|
||||
// test 1
|
||||
[
|
||||
'scheduled Cb1',
|
||||
'frame 1 started',
|
||||
'cb1 called with argument of {"didTimeout":false}',
|
||||
'frame 2 started',
|
||||
'frame 3 started... we stop counting now.',
|
||||
],
|
||||
// test 2
|
||||
[
|
||||
'scheduled CbA',
|
||||
'scheduled CbB',
|
||||
'frame 1 started',
|
||||
'cbA called with argument of {"didTimeout":false}',
|
||||
'cbB called with argument of {"didTimeout":false}',
|
||||
'frame 2 started',
|
||||
'frame 3 started... we stop counting now.',
|
||||
],
|
||||
// test 3
|
||||
[
|
||||
'scheduled CbA',
|
||||
'scheduled CbB',
|
||||
'frame 1 started',
|
||||
'scheduled CbA again',
|
||||
'cbA0 called with argument of {"didTimeout":false}',
|
||||
'cbB called with argument of {"didTimeout":false}',
|
||||
'cbA1 called with argument of {"didTimeout":false}',
|
||||
'frame 2 started',
|
||||
'frame 3 started... we stop counting now.',
|
||||
],
|
||||
// test 4
|
||||
[
|
||||
'scheduled cbA',
|
||||
'scheduled cbB',
|
||||
'scheduled cbC',
|
||||
'scheduled cbD',
|
||||
'frame 1 started',
|
||||
'cbC called with argument of {"didTimeout":true}',
|
||||
'cbA called with argument of {"didTimeout":false}',
|
||||
'cbA running and taking some time',
|
||||
'frame 2 started',
|
||||
'cbB called with argument of {"didTimeout":false}',
|
||||
'cbD called with argument of {"didTimeout":false}',
|
||||
'frame 3 started... we stop counting now.',
|
||||
],
|
||||
// test 5
|
||||
[
|
||||
// ... TODO
|
||||
],
|
||||
];
|
||||
function runTestOne() {
|
||||
// Test 1
|
||||
// Calls the callback with the frame when not blocked
|
||||
clearTestResult(1);
|
||||
const test1Log = [];
|
||||
const cb1Arguments = [];
|
||||
const cb1 = (x) => {
|
||||
updateTestResult(1, 'cb1 called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
scheduleWork(cb1);
|
||||
updateTestResult(1, 'scheduled Cb1');
|
||||
logWhenFramesStart(1, () => {
|
||||
displayTestResult(1);
|
||||
checkTestResult(1);
|
||||
});
|
||||
};
|
||||
|
||||
function runTestTwo() {
|
||||
// Test 2
|
||||
// accepts multiple callbacks and calls within frame when not blocked
|
||||
clearTestResult(2);
|
||||
const cbA = (x) => {
|
||||
updateTestResult(2, 'cbA called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbB = (x) => {
|
||||
updateTestResult(2, 'cbB called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
scheduleWork(cbA);
|
||||
updateTestResult(2, 'scheduled CbA');
|
||||
scheduleWork(cbB);
|
||||
updateTestResult(2, 'scheduled CbB');
|
||||
logWhenFramesStart(2, () => {
|
||||
displayTestResult(2);
|
||||
checkTestResult(2);
|
||||
});
|
||||
}
|
||||
|
||||
function runTestThree() {
|
||||
// Test 3
|
||||
// Schedules callbacks in correct order when they use scheduleWork to schedule themselves
|
||||
clearTestResult(3);
|
||||
let callbackAIterations = 0;
|
||||
const cbA = (x) => {
|
||||
if (callbackAIterations < 1) {
|
||||
scheduleWork(cbA);
|
||||
updateTestResult(3, 'scheduled CbA again');
|
||||
}
|
||||
updateTestResult(3, 'cbA' + callbackAIterations + ' called with argument of ' + JSON.stringify(x));
|
||||
callbackAIterations++;
|
||||
}
|
||||
const cbB = (x) => {
|
||||
updateTestResult(3, 'cbB called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
scheduleWork(cbA);
|
||||
updateTestResult(3, 'scheduled CbA');
|
||||
scheduleWork(cbB);
|
||||
updateTestResult(3, 'scheduled CbB');
|
||||
logWhenFramesStart(3, () => {
|
||||
displayTestResult(3);
|
||||
checkTestResult(3);
|
||||
});
|
||||
}
|
||||
|
||||
function waitForTimeToPass(timeInMs) {
|
||||
const startTime = Date.now();
|
||||
const endTime = startTime + timeInMs;
|
||||
while (Date.now() < endTime) {
|
||||
// wait...
|
||||
}
|
||||
}
|
||||
|
||||
function runTestFour() {
|
||||
// Test 4
|
||||
// Calls timed out callbacks and then any more pending callbacks, defers others if time runs out
|
||||
clearTestResult(4);
|
||||
const cbA = (x) => {
|
||||
updateTestResult(4, 'cbA called with argument of ' + JSON.stringify(x));
|
||||
updateTestResult(4, 'cbA running and taking some time');
|
||||
waitForTimeToPass(35);
|
||||
}
|
||||
const cbB = (x) => {
|
||||
updateTestResult(4, 'cbB called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbC = (x) => {
|
||||
updateTestResult(4, 'cbC called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbD = (x) => {
|
||||
updateTestResult(4, 'cbD called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
scheduleWork(cbA); // won't time out
|
||||
updateTestResult(4, 'scheduled cbA');
|
||||
scheduleWork(cbB, {timeout: 100}); // times out later
|
||||
updateTestResult(4, 'scheduled cbB');
|
||||
scheduleWork(cbC, {timeout: 1}); // will time out fast
|
||||
updateTestResult(4, 'scheduled cbC');
|
||||
scheduleWork(cbD); // won't time out
|
||||
updateTestResult(4, 'scheduled cbD');
|
||||
|
||||
// should have run in order of C, A, B, D
|
||||
|
||||
logWhenFramesStart(4, () => {
|
||||
displayTestResult(4);
|
||||
checkTestResult(4);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Error handling
|
||||
|
||||
function runTestFive() {
|
||||
// Test 5
|
||||
// When some callbacks throw errors, still calls them all within the same frame
|
||||
const cbA = (x) => {
|
||||
console.log('cbA called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbB = (x) => {
|
||||
console.log('cbB called with argument of ' + JSON.stringify(x));
|
||||
console.log('cbB is about to throw an error!');
|
||||
throw new Error('error B');
|
||||
}
|
||||
const cbC = (x) => {
|
||||
console.log('cbC called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbD = (x) => {
|
||||
console.log('cbD called with argument of ' + JSON.stringify(x));
|
||||
console.log('cbD is about to throw an error!');
|
||||
throw new Error('error D');
|
||||
}
|
||||
const cbE = (x) => {
|
||||
console.log('cbE called with argument of ' + JSON.stringify(x));
|
||||
console.log('This was the last callback! ------------------');
|
||||
}
|
||||
|
||||
console.log('We are aiming to roughly emulate the way ' +
|
||||
'`requestAnimationFrame` handles errors from callbacks.');
|
||||
|
||||
console.log('about to run the simulation of what it should look like...:');
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 1 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 2 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 3 started... we stop counting now.');
|
||||
console.log('about to wait a moment and start this again but ' +
|
||||
'with the scheduler instead of requestAnimationFrame');
|
||||
setTimeout(runSchedulerCode, 1000);
|
||||
});
|
||||
});
|
||||
});
|
||||
requestAnimationFrame(cbA);
|
||||
console.log('scheduled cbA');
|
||||
requestAnimationFrame(cbB); // will throw error
|
||||
console.log('scheduled cbB');
|
||||
requestAnimationFrame(cbC);
|
||||
console.log('scheduled cbC');
|
||||
requestAnimationFrame(cbD); // will throw error
|
||||
console.log('scheduled cbD');
|
||||
requestAnimationFrame(cbE);
|
||||
console.log('scheduled cbE');
|
||||
|
||||
|
||||
function runSchedulerCode() {
|
||||
console.log('-------------------------------------------------------------');
|
||||
console.log('now lets see what it looks like using the scheduler...:');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 1 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 2 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 3 started... we stop counting now.');
|
||||
});
|
||||
});
|
||||
});
|
||||
scheduleWork(cbA);
|
||||
console.log('scheduled cbA');
|
||||
scheduleWork(cbB); // will throw error
|
||||
console.log('scheduled cbB');
|
||||
scheduleWork(cbC);
|
||||
console.log('scheduled cbC');
|
||||
scheduleWork(cbD); // will throw error
|
||||
console.log('scheduled cbD');
|
||||
scheduleWork(cbE);
|
||||
console.log('scheduled cbE');
|
||||
};
|
||||
}
|
||||
|
||||
function runTestSix() {
|
||||
// Test 6
|
||||
// When some callbacks throw errors, still calls them all within the same frame
|
||||
const cbA = (x) => {
|
||||
console.log('cbA called with argument of ' + JSON.stringify(x));
|
||||
console.log('cbA is about to throw an error!');
|
||||
throw new Error('error A');
|
||||
}
|
||||
const cbB = (x) => {
|
||||
console.log('cbB called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbC = (x) => {
|
||||
console.log('cbC called with argument of ' + JSON.stringify(x));
|
||||
}
|
||||
const cbD = (x) => {
|
||||
console.log('cbD called with argument of ' + JSON.stringify(x));
|
||||
console.log('cbD is about to throw an error!');
|
||||
throw new Error('error D');
|
||||
}
|
||||
const cbE = (x) => {
|
||||
console.log('cbE called with argument of ' + JSON.stringify(x));
|
||||
console.log('This was the last callback! ------------------');
|
||||
}
|
||||
|
||||
console.log('We are aiming to roughly emulate the way ' +
|
||||
'`requestAnimationFrame` handles errors from callbacks.');
|
||||
|
||||
console.log('about to run the simulation of what it should look like...:');
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 1 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 2 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 3 started... we stop counting now.');
|
||||
console.log('about to wait a moment and start this again but ' +
|
||||
'with the scheduler instead of requestAnimationFrame');
|
||||
setTimeout(runSchedulerCode, 1000);
|
||||
});
|
||||
});
|
||||
});
|
||||
requestAnimationFrame(cbC);
|
||||
console.log('scheduled cbC first; simulating timing out');
|
||||
requestAnimationFrame(cbD); // will throw error
|
||||
console.log('scheduled cbD first; simulating timing out');
|
||||
requestAnimationFrame(cbE);
|
||||
console.log('scheduled cbE first; simulating timing out');
|
||||
requestAnimationFrame(cbA);
|
||||
console.log('scheduled cbA'); // will throw error
|
||||
requestAnimationFrame(cbB);
|
||||
console.log('scheduled cbB');
|
||||
|
||||
|
||||
function runSchedulerCode() {
|
||||
console.log('-------------------------------------------------------------');
|
||||
console.log('now lets see what it looks like using the scheduler...:');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 1 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 2 started');
|
||||
requestAnimationFrame(() => {
|
||||
console.log('frame 3 started... we stop counting now.');
|
||||
});
|
||||
});
|
||||
});
|
||||
scheduleWork(cbA);
|
||||
console.log('scheduled cbA');
|
||||
scheduleWork(cbB); // will throw error
|
||||
console.log('scheduled cbB');
|
||||
scheduleWork(cbC, {timeout: 1});
|
||||
console.log('scheduled cbC');
|
||||
scheduleWork(cbD, {timeout: 1}); // will throw error
|
||||
console.log('scheduled cbD');
|
||||
scheduleWork(cbE, {timeout: 1});
|
||||
console.log('scheduled cbE');
|
||||
};
|
||||
}
|
||||
|
||||
function runTestSeven() {
|
||||
// Test 7
|
||||
// Calls callbacks, continues calling them even when this tab is in the
|
||||
// background
|
||||
clearTestResult(7);
|
||||
let counter = -1;
|
||||
function incrementCounterAndScheduleNextCallback() {
|
||||
const counterNode = document.getElementById('test-7');
|
||||
counter++;
|
||||
counterNode.innerHTML = counter;
|
||||
waitForTimeToPass(100);
|
||||
scheduleWork(incrementCounterAndScheduleNextCallback);
|
||||
}
|
||||
scheduleWork(incrementCounterAndScheduleNextCallback);
|
||||
}
|
||||
</script type="text/babel">
|
||||
</body>
|
||||
</html>
|
||||
14
fixtures/unstable-async/suspense/.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
||||
# testing
|
||||
coverage
|
||||
|
||||
# production
|
||||
build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
npm-debug.log
|
||||
33
fixtures/unstable-async/suspense/README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# IO "suspense" demo
|
||||
|
||||
## What is this fixture?
|
||||
|
||||
This is a demo application based on [Dan Abramov's](https://github.com/gaearon) recent [JSConf Iceland talk](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html) about React.
|
||||
|
||||
It depends on a local build of React and enables us to easily test async and "suspense" APIs in a more "real world app" like context.
|
||||
|
||||
## Can I use this code in production?
|
||||
|
||||
No. The APIs being tested here are unstable and some of them have still not been released to NPM. For now, this fixture is only a test harness.
|
||||
|
||||
## How do I run this fixture?
|
||||
|
||||
Clone the React repository.
|
||||
|
||||
Open `packages/shared/ReactFeatureFlags.js` locally (make sure you didn't open a similarly named file!) and set [the `enableSuspense` flag](https://github.com/facebook/react/blob/d79238f1eeb6634ba7a3df23c3b2709b56cbb8b2/packages/shared/ReactFeatureFlags.js#L19) to `true`.
|
||||
|
||||
Then follow these steps:
|
||||
|
||||
```shell
|
||||
# 1: Build react from source
|
||||
cd /path/to/react
|
||||
yarn
|
||||
yarn build dom,core,interaction,simple-cache-provider --type=NODE
|
||||
|
||||
# 2: Install fixture dependencies
|
||||
cd fixtures/unstable-async/suspense/
|
||||
yarn
|
||||
|
||||
# 3: Run the app
|
||||
yarn start
|
||||
```
|
||||
41
fixtures/unstable-async/suspense/package.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "io-demo",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"homepage": ".",
|
||||
"devDependencies": {
|
||||
"gh-pages": "^1.1.0",
|
||||
"react-scripts": "^1.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"clipboard": "^1.7.1",
|
||||
"github-fork-ribbon-css": "^0.2.1",
|
||||
"react": "../../../build/node_modules/react",
|
||||
"react-dom": "../../../build/node_modules/react-dom",
|
||||
"react-draggable": "^3.0.5",
|
||||
"simple-cache-provider": "../../../build/node_modules/simple-cache-provider"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject",
|
||||
"deploy": "gh-pages -d build"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "./node_modules/react-scripts/config/eslint.js"
|
||||
},
|
||||
"browserslist": {
|
||||
"development": [
|
||||
"last 2 chrome versions",
|
||||
"last 2 firefox versions",
|
||||
"last 2 edge versions"
|
||||
],
|
||||
"production": [
|
||||
">1%",
|
||||
"last 4 versions",
|
||||
"Firefox ESR",
|
||||
"not ie < 11"
|
||||
]
|
||||
}
|
||||
}
|
||||
BIN
fixtures/unstable-async/suspense/public/favicon.ico
Executable file
|
After Width: | Height: | Size: 24 KiB |
BIN
fixtures/unstable-async/suspense/public/img/acdlite.jpeg
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
fixtures/unstable-async/suspense/public/img/bvaughn.jpeg
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
fixtures/unstable-async/suspense/public/img/flarnie.jpeg
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
fixtures/unstable-async/suspense/public/img/gaearon.jpeg
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
fixtures/unstable-async/suspense/public/img/sebmarkbage.jpeg
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
fixtures/unstable-async/suspense/public/img/sophiebits.jpeg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
fixtures/unstable-async/suspense/public/img/trueadm.jpeg
Normal file
|
After Width: | Height: | Size: 26 KiB |
13
fixtures/unstable-async/suspense/public/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="./src/favicon.ico">
|
||||
<title>Movie List</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<div id="debugger"></div>
|
||||
</body>
|
||||
</html>
|
||||
15
fixtures/unstable-async/suspense/public/manifest.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "Emoji Search",
|
||||
"name": "Emoji Search Example App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
334
fixtures/unstable-async/suspense/src/api/data.js
Normal file
@@ -0,0 +1,334 @@
|
||||
export const coreContributorListJSON = [
|
||||
{
|
||||
id: 'acdlite',
|
||||
name: 'Andrew Clark',
|
||||
},
|
||||
{
|
||||
id: 'bvaughn',
|
||||
name: 'Brian Vaughn',
|
||||
},
|
||||
{
|
||||
id: 'gaearon',
|
||||
name: 'Dan Abramov',
|
||||
},
|
||||
{
|
||||
id: 'trueadm',
|
||||
name: 'Dominic Gannaway',
|
||||
},
|
||||
{
|
||||
id: 'flarnie',
|
||||
name: 'Flarnie Marchan',
|
||||
},
|
||||
{
|
||||
id: 'sebmarkbage',
|
||||
name: 'Sebastian Markbåge',
|
||||
},
|
||||
{
|
||||
id: 'sophiebits',
|
||||
name: 'Sophie Alpert',
|
||||
},
|
||||
];
|
||||
|
||||
export const userProfileJSON = {
|
||||
acdlite: {
|
||||
id: 'acdlite',
|
||||
name: 'Andrew Clark',
|
||||
image: '/img/acdlite.jpeg',
|
||||
location: 'Redwood City, CA',
|
||||
email: 'acdlite@me.com',
|
||||
tagline: 'React core at Facebook. Hi!',
|
||||
},
|
||||
bvaughn: {
|
||||
id: 'bvaughn',
|
||||
name: 'Brian Vaughn',
|
||||
image: '/img/bvaughn.jpeg',
|
||||
location: 'Mountain View, CA',
|
||||
email: 'brian.david.vaughn@gmail.com',
|
||||
tagline:
|
||||
'React JS core team at @facebook; formerly at @treasure-data and @google.',
|
||||
},
|
||||
gaearon: {
|
||||
id: 'gaearon',
|
||||
name: 'Dan Abramov',
|
||||
image: '/img/gaearon.jpeg',
|
||||
location: 'London, UK',
|
||||
email: 'dan.abramov@me.com',
|
||||
tagline:
|
||||
'Working on @reactjs. Co-author of Redux and Create React App. Building tools for humans.',
|
||||
},
|
||||
flarnie: {
|
||||
id: 'flarnie',
|
||||
name: 'Flarnie Marchan',
|
||||
image: '/img/flarnie.jpeg',
|
||||
location: 'Oakland, CA',
|
||||
email: null,
|
||||
tagline:
|
||||
'Software Engineer at Facebook React Core Team & Co-maintainer of Draft.js',
|
||||
},
|
||||
sebmarkbage: {
|
||||
id: 'sebmarkbage',
|
||||
name: 'Sebastian Markbåge',
|
||||
image: '/img/sebmarkbage.jpeg',
|
||||
location: 'San Francisco',
|
||||
email: 'sebastian@calyptus.eu',
|
||||
tagline: null,
|
||||
},
|
||||
sophiebits: {
|
||||
id: 'sophiebits',
|
||||
name: 'Sophie Alpert',
|
||||
image: '/img/sophiebits.jpeg',
|
||||
location: 'California',
|
||||
email: 'hi@sophiebits.com',
|
||||
tagline:
|
||||
'I like fixing things. eng manager of @reactjs at Facebook. ex-@khanacademy. 💎🌸 she/her. kindness, intersectional feminism, music.',
|
||||
},
|
||||
trueadm: {
|
||||
id: 'trueadm',
|
||||
name: 'Dominic Gannaway',
|
||||
image: '/img/trueadm.jpeg',
|
||||
location: 'London, United Kingdom',
|
||||
email: null,
|
||||
tagline:
|
||||
'Currently an engineer on the React core team at @facebook. Author of @infernojs and t7. Enjoys coding + being a Dad.',
|
||||
},
|
||||
};
|
||||
export const userRepositoriesListJSON = {
|
||||
acdlite: [
|
||||
{
|
||||
name: 'recompose',
|
||||
url: 'https://github.com/acdlite/recompose',
|
||||
description:
|
||||
'A React utility belt for function components and higher-order components.',
|
||||
},
|
||||
{
|
||||
name: 'react-fiber-architecture',
|
||||
url: 'https://github.com/acdlite/react-fiber-architecture',
|
||||
description: "A description of React's new core algorithm, React Fiber",
|
||||
},
|
||||
{
|
||||
name: 'redux-router',
|
||||
url: 'https://github.com/acdlite/redux-router',
|
||||
description:
|
||||
'Redux bindings for React Router – keep your router state inside your Redux store',
|
||||
},
|
||||
{
|
||||
name: 'flummox',
|
||||
url: 'https://github.com/acdlite/flummox',
|
||||
description: 'Minimal, isomorphic Flux.',
|
||||
},
|
||||
{
|
||||
name: 'redux-rx',
|
||||
url: 'https://github.com/acdlite/redux-rx',
|
||||
description: 'RxJS utilities for Redux.',
|
||||
},
|
||||
{
|
||||
name: 'react-remarkable',
|
||||
url: 'https://github.com/acdlite/react-remarkable',
|
||||
description: 'A React component for rendering Markdown with remarkable',
|
||||
},
|
||||
],
|
||||
bvaughn: [
|
||||
{
|
||||
name: 'react-virtualized',
|
||||
url: 'https://github.com/bvaughn/react-virtualized',
|
||||
description:
|
||||
'React components for efficiently rendering large lists and tabular data',
|
||||
},
|
||||
{
|
||||
name: 'redux-search',
|
||||
url: 'https://github.com/bvaughn/redux-search',
|
||||
description: 'Redux bindings for client-side search',
|
||||
},
|
||||
{
|
||||
name: 'react-window',
|
||||
url: 'https://github.com/bvaughn/react-window',
|
||||
description:
|
||||
'React components for efficiently rendering large lists and tabular data',
|
||||
},
|
||||
{
|
||||
name: 'react-virtualized-select',
|
||||
url: 'https://github.com/bvaughn/react-virtualized-select',
|
||||
description:
|
||||
'HOC that uses react-virtualized and react-select to display large lists of options in a drop-down',
|
||||
},
|
||||
{
|
||||
name: 'js-search',
|
||||
url: 'https://github.com/bvaughn/js-search',
|
||||
description:
|
||||
'JS Search is an efficient, client-side search library for JavaScript and JSON objects',
|
||||
},
|
||||
{
|
||||
name: 'react-highlight-words',
|
||||
url: 'https://github.com/bvaughn/react-highlight-words',
|
||||
description:
|
||||
'React component to highlight words within a larger body of text',
|
||||
},
|
||||
],
|
||||
gaearon: [
|
||||
{
|
||||
name: 'facebook/react',
|
||||
url: 'https://github.com/facebook/react',
|
||||
description:
|
||||
'A declarative, efficient, and flexible JavaScript library for building user interfaces.',
|
||||
},
|
||||
{
|
||||
name: 'reduxjs/redux',
|
||||
url: 'https://github.com/reduxjs/redux',
|
||||
description: 'Predictable state container for JavaScript apps',
|
||||
},
|
||||
{
|
||||
name: 'facebook/create-react-app',
|
||||
url: 'https://github.com/facebook/create-react-app',
|
||||
description: 'Create React apps with no build configuration.',
|
||||
},
|
||||
{
|
||||
name: 'reduxjs/redux-devtools',
|
||||
url: 'https://github.com/reduxjs/redux-devtools',
|
||||
description:
|
||||
'DevTools for Redux with hot reloading, action replay, and customizable UI',
|
||||
},
|
||||
{
|
||||
name: 'react-dnd/react-dnd',
|
||||
url: 'https://github.com/react-dnd/react-dnd',
|
||||
description: 'Drag and Drop for React',
|
||||
},
|
||||
{
|
||||
name: 'paularmstrong/normalizr',
|
||||
url: 'https://github.com/paularmstrong/normalizr',
|
||||
description: 'Normalizes nested JSON according to a schema',
|
||||
},
|
||||
],
|
||||
flarnie: [
|
||||
{
|
||||
name: 'diffux/diffux',
|
||||
url: 'https://github.com/diffux/diffux',
|
||||
description: 'Perceptual diffs of responsive screenshots made simple.',
|
||||
},
|
||||
{
|
||||
name: 'facebook/draft-js',
|
||||
url: 'https://github.com/facebook/draft-js',
|
||||
description: 'A React framework for building text editors.',
|
||||
},
|
||||
{
|
||||
name: 'facebook/react',
|
||||
url: 'https://github.com/facebook/react',
|
||||
description:
|
||||
'A declarative, efficient, and flexible JavaScript library for building user interfaces.',
|
||||
},
|
||||
{
|
||||
name: 'facebook/jest',
|
||||
url: 'https://github.com/facebook/jest',
|
||||
description: '🃏 Delightful JavaScript Testing.',
|
||||
},
|
||||
{
|
||||
name: 'Galooshi/import-js',
|
||||
url: 'https://github.com/Galooshi/import-js',
|
||||
description: 'A tool to simplify importing JS modules',
|
||||
},
|
||||
{
|
||||
name: 'webpack_rails_demo',
|
||||
url: 'https://github.com/flarnie/webpack_rails_demo',
|
||||
description: 'Setting up webpack with Ruby on Rails: a basic demo',
|
||||
},
|
||||
],
|
||||
sebmarkbage: [
|
||||
{
|
||||
name: 'art',
|
||||
url: 'https://github.com/sebmarkbage/art',
|
||||
description:
|
||||
"Retained mode vector drawing API designed for multiple output modes. There's also a built-in SVG parser.",
|
||||
},
|
||||
{
|
||||
name: 'ecmascript-immutable-data-structures',
|
||||
url:
|
||||
'https://github.com/sebmarkbage/ecmascript-immutable-data-structures',
|
||||
description: null,
|
||||
},
|
||||
{
|
||||
name: 'ocamlrun-wasm',
|
||||
url: 'https://github.com/sebmarkbage/ocamlrun-wasm',
|
||||
description: 'OCamlrun WebAssembly - OCaml Bytecode Interpreter in WASM',
|
||||
},
|
||||
{
|
||||
name: 'ecmascript-generator-expression',
|
||||
url: 'https://github.com/sebmarkbage/ecmascript-generator-expression',
|
||||
description:
|
||||
'Proposal for do Generator Expressions in ECMAScript. Work in progress. Edit Add topics',
|
||||
},
|
||||
{
|
||||
name: 'ecmascript-undefined-propagation',
|
||||
url: 'https://github.com/sebmarkbage/ecmascript-undefined-propagation',
|
||||
description:
|
||||
'ECMAScript proposal to relax the rules to return `undefined` for property access on `null` or `undefined` instead of throwing.',
|
||||
},
|
||||
{
|
||||
name: 'ecmascript-shallow-equal',
|
||||
url: 'https://github.com/sebmarkbage/ecmascript-shallow-equal',
|
||||
description: 'A proposal for ECMAScript for Object.shallowEqual',
|
||||
},
|
||||
],
|
||||
sophiebits: [
|
||||
{
|
||||
name: 'facebook/react',
|
||||
url: 'https://github.com/facebook/react',
|
||||
description:
|
||||
'A declarative, efficient, and flexible JavaScript library for building user interfaces.',
|
||||
},
|
||||
{
|
||||
name: 'Khan/KaTeX',
|
||||
url: 'https://github.com/Khan/KaTeX',
|
||||
description: 'Fast math typesetting for the web.',
|
||||
},
|
||||
{
|
||||
name: 'facebook/react-devtools',
|
||||
url: 'https://github.com/facebook/react-devtools',
|
||||
description:
|
||||
'An extension that allows inspection of React component hierarchy in the Chrome and Firefox Developer Tools.',
|
||||
},
|
||||
{
|
||||
name: 'vim-awesome/vim-awesome',
|
||||
url: 'https://github.com/vim-awesome/vim-awesome',
|
||||
description: 'Awesome Vim plugins from across the universe',
|
||||
},
|
||||
{
|
||||
name: 'facebook/draft-js',
|
||||
url: 'https://github.com/facebook/draft-js',
|
||||
description: 'A React framework for building text editors.',
|
||||
},
|
||||
{
|
||||
name: 'es3ify',
|
||||
url: 'https://github.com/sophiebits/es3ify',
|
||||
description:
|
||||
'Browserify transform to convert ES5 syntax to be ES3-compatible.',
|
||||
},
|
||||
],
|
||||
trueadm: [
|
||||
{
|
||||
name: 'facebook/react',
|
||||
url: 'https://github.com/facebook/react',
|
||||
description:
|
||||
'A declarative, efficient, and flexible JavaScript library for building user interfaces.',
|
||||
},
|
||||
{
|
||||
name: 'infernojs/inferno',
|
||||
url: 'https://github.com/infernojs/inferno',
|
||||
description:
|
||||
'An extremely fast, React-like JavaScript library for building modern user interfaces',
|
||||
},
|
||||
{
|
||||
name: 'facebook/prepack',
|
||||
url: 'https://github.com/facebook/prepack',
|
||||
description: 'A JavaScript bundle optimizer.',
|
||||
},
|
||||
{
|
||||
name: 't7',
|
||||
url: 'https://github.com/trueadm/t7',
|
||||
description: 'Lightweight virtual DOM templating library',
|
||||
},
|
||||
{
|
||||
name: 'infernojs/babel-plugin-inferno',
|
||||
url: 'https://github.com/infernojs/babel-plugin-inferno',
|
||||
description: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
67
fixtures/unstable-async/suspense/src/api/index.js
Normal file
@@ -0,0 +1,67 @@
|
||||
import {
|
||||
coreContributorListJSON,
|
||||
userProfileJSON,
|
||||
userRepositoriesListJSON,
|
||||
} from './data';
|
||||
|
||||
export function fetchCoreContributorListJSON() {
|
||||
return makeFakeAPICall('/react/contributors', coreContributorListJSON);
|
||||
}
|
||||
|
||||
export function fetchUserProfileJSON(id) {
|
||||
return makeFakeAPICall(`/${id}/details`, userProfileJSON[id]);
|
||||
}
|
||||
|
||||
export function fetchUserRepositoriesListJSON(id) {
|
||||
return makeFakeAPICall(`/${id}/repositories`, userRepositoriesListJSON[id]);
|
||||
}
|
||||
|
||||
let fakeRequestTime = 1000;
|
||||
let onProgress = () => true;
|
||||
|
||||
export function setFakeRequestTime(val) {
|
||||
fakeRequestTime = val;
|
||||
}
|
||||
|
||||
export function setProgressHandler(handler) {
|
||||
onProgress = handler;
|
||||
}
|
||||
|
||||
export function setPauseNewRequests(value) {
|
||||
shouldPauseNewRequests = value;
|
||||
}
|
||||
|
||||
let shouldPauseNewRequests = false;
|
||||
let notifiers = {};
|
||||
let isPausedUrl = {};
|
||||
|
||||
export function setPaused(url, isPaused) {
|
||||
const wasPaused = isPausedUrl[url];
|
||||
isPausedUrl[url] = isPaused;
|
||||
if (isPaused !== wasPaused) {
|
||||
notifiers[url]();
|
||||
}
|
||||
}
|
||||
|
||||
function makeFakeAPICall(url, result) {
|
||||
let i = 1;
|
||||
return new Promise(resolve => {
|
||||
isPausedUrl[url] = shouldPauseNewRequests;
|
||||
function notify() {
|
||||
if (!isPausedUrl[url]) {
|
||||
i++;
|
||||
}
|
||||
onProgress(url, i, isPausedUrl[url]);
|
||||
if (isPausedUrl[url]) {
|
||||
return;
|
||||
}
|
||||
if (i === 100) {
|
||||
resolve(result);
|
||||
} else {
|
||||
setTimeout(notify, fakeRequestTime / 100);
|
||||
}
|
||||
}
|
||||
notifiers[url] = notify;
|
||||
notify();
|
||||
});
|
||||
}
|
||||
7
fixtures/unstable-async/suspense/src/cache.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import {createCache} from 'simple-cache-provider';
|
||||
|
||||
export let cache;
|
||||
function initCache() {
|
||||
cache = createCache(initCache);
|
||||
}
|
||||
initCache();
|
||||
82
fixtures/unstable-async/suspense/src/components/App.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import React, {Fragment, Placeholder, PureComponent} from 'react';
|
||||
import {unstable_deferredUpdates} from 'react-dom';
|
||||
import {createResource} from 'simple-cache-provider';
|
||||
import {cache} from '../cache';
|
||||
import Spinner from './Spinner';
|
||||
import ContributorListPage from './ContributorListPage';
|
||||
|
||||
const UserPageResource = createResource(() => import('./UserPage'));
|
||||
|
||||
function UserPageLoader(props) {
|
||||
const UserPage = UserPageResource.read(cache).default;
|
||||
return <UserPage {...props} />;
|
||||
}
|
||||
|
||||
export default class App extends PureComponent {
|
||||
state = {
|
||||
currentId: null,
|
||||
showDetail: false,
|
||||
};
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (
|
||||
prevState.showDetail !== this.state.showDetail ||
|
||||
prevState.currentId !== this.state.currentId
|
||||
) {
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
handleUserClick = id => {
|
||||
this.setState({
|
||||
currentId: id,
|
||||
});
|
||||
unstable_deferredUpdates(() => {
|
||||
this.setState({
|
||||
showDetail: true,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
handleBackClick = () =>
|
||||
this.setState({
|
||||
currentId: null,
|
||||
showDetail: false,
|
||||
});
|
||||
|
||||
render() {
|
||||
const {currentId, showDetail} = this.state;
|
||||
return showDetail
|
||||
? this.renderDetail(currentId)
|
||||
: this.renderList(currentId);
|
||||
}
|
||||
|
||||
renderDetail(id) {
|
||||
return (
|
||||
<div>
|
||||
<button
|
||||
onClick={this.handleBackClick}
|
||||
style={{
|
||||
display: 'block',
|
||||
marginBottom: '1rem',
|
||||
}}>
|
||||
Return to list
|
||||
</button>
|
||||
<Placeholder delayMs={2000} fallback={<Spinner size="large" />}>
|
||||
<UserPageLoader id={id} />
|
||||
</Placeholder>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderList(loadingId) {
|
||||
return (
|
||||
<Placeholder delayMs={1500} fallback={<Spinner size="large" />}>
|
||||
<ContributorListPage
|
||||
loadingId={loadingId}
|
||||
onUserClick={this.handleUserClick}
|
||||
/>
|
||||
</Placeholder>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
import React, {Fragment} from 'react';
|
||||
import {createResource} from 'simple-cache-provider';
|
||||
import {cache} from '../cache';
|
||||
import Spinner from './Spinner';
|
||||
import {fetchCoreContributorListJSON} from '../api';
|
||||
|
||||
const ContributorListResource = createResource(fetchCoreContributorListJSON);
|
||||
|
||||
const ContributorListPage = ({loadingId, onUserClick}) => (
|
||||
<Fragment>
|
||||
<h1>React Core Team</h1>
|
||||
<ul
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridGap: '0.5rem',
|
||||
gridTemplateColumns: 'repeat(auto-fill, 20rem)',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
}}>
|
||||
{ContributorListResource.read(cache).map(user => (
|
||||
<ContributorListItem
|
||||
key={user.id}
|
||||
onClick={() => onUserClick(user.id)}
|
||||
isLoading={loadingId && user.id === loadingId}
|
||||
user={user}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const ContributorListItem = ({isLoading, onClick, user}) => (
|
||||
<li
|
||||
onClick={onClick}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'var(--color-buttonBg)',
|
||||
border: '1px solid var(--color-buttonBorder)',
|
||||
borderRadius: '1rem',
|
||||
opacity: isLoading === false ? 0.5 : 1,
|
||||
cursor: isLoading ? 'default' : 'pointer',
|
||||
}}
|
||||
tabIndex="0">
|
||||
<div>
|
||||
<strong>{user.name}</strong>
|
||||
<div style={{marginTop: '0.5rem'}}>{user.id}</div>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<Spinner size="small" />
|
||||
) : (
|
||||
<svg width="24" height="24" viewBox="0 0 24 24">
|
||||
<path fill="none" d="M0 0h24v24H0z" />
|
||||
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
|
||||
</svg>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
|
||||
export default ContributorListPage;
|
||||
68
fixtures/unstable-async/suspense/src/components/Spinner.css
Normal file
@@ -0,0 +1,68 @@
|
||||
.Spinner {
|
||||
animation: rotate 1.3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(270deg); }
|
||||
}
|
||||
|
||||
.SmallSpinnerPath {
|
||||
stroke-dasharray: 100;
|
||||
stroke-dashoffset: 0;
|
||||
transform-origin: center;
|
||||
animation:
|
||||
SmallDash 1.3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes SmallDash {
|
||||
0% { stroke-dashoffset: 100; }
|
||||
50% {
|
||||
stroke-dashoffset: 50;
|
||||
transform:rotate(135deg);
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 100;
|
||||
transform:rotate(450deg);
|
||||
}
|
||||
}
|
||||
|
||||
.MediumSpinnerPath {
|
||||
stroke-dasharray: 150;
|
||||
stroke-dashoffset: 0;
|
||||
transform-origin: center;
|
||||
animation:
|
||||
MediumDash 1.3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes MediumDash {
|
||||
0% { stroke-dashoffset: 150; }
|
||||
50% {
|
||||
stroke-dashoffset: 50;
|
||||
transform:rotate(135deg);
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 150;
|
||||
transform:rotate(450deg);
|
||||
}
|
||||
}
|
||||
|
||||
.LargeSpinnerPath {
|
||||
stroke-dasharray: 200;
|
||||
stroke-dashoffset: 0;
|
||||
transform-origin: center;
|
||||
animation:
|
||||
LargeDash 1.3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes LargeDash {
|
||||
0% { stroke-dashoffset: 200; }
|
||||
50% {
|
||||
stroke-dashoffset: 50;
|
||||
transform:rotate(135deg);
|
||||
}
|
||||
100% {
|
||||
stroke-dashoffset: 200;
|
||||
transform:rotate(450deg);
|
||||
}
|
||||
}
|
||||
48
fixtures/unstable-async/suspense/src/components/Spinner.js
Normal file
@@ -0,0 +1,48 @@
|
||||
import React from 'react';
|
||||
import './Spinner.css';
|
||||
|
||||
const SPINNER_SIZES = {
|
||||
small: 30,
|
||||
medium: 50,
|
||||
large: 70,
|
||||
};
|
||||
|
||||
const STROKE_WIDTHS = {
|
||||
small: 4,
|
||||
medium: 5,
|
||||
large: 6,
|
||||
};
|
||||
|
||||
const PATH_CLASS_NAMES = {
|
||||
small: 'SmallSpinnerPath',
|
||||
medium: 'MediumSpinnerPath',
|
||||
large: 'LargeSpinnerPath',
|
||||
};
|
||||
|
||||
// Heavily inspired by https://codepen.io/mrrocks/pen/EiplA
|
||||
export default function Spinner({size = 'small'}) {
|
||||
const baseSize = SPINNER_SIZES[size];
|
||||
const pathSize = baseSize / 2;
|
||||
const strokeWidth = STROKE_WIDTHS[size];
|
||||
const pathRadius = `${baseSize / 2 - strokeWidth}px`;
|
||||
const className = PATH_CLASS_NAMES[size];
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={className}
|
||||
width={baseSize}
|
||||
height={baseSize}
|
||||
viewBox={`0 0 ${baseSize} ${baseSize}`}>
|
||||
<circle
|
||||
className="SpinnerPath"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={strokeWidth}
|
||||
strokeLinecap="round"
|
||||
cx={pathSize}
|
||||
cy={pathSize}
|
||||
r={pathRadius}
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
169
fixtures/unstable-async/suspense/src/components/UserPage.js
Normal file
@@ -0,0 +1,169 @@
|
||||
import React, {Fragment, Placeholder} from 'react';
|
||||
import {createResource} from 'simple-cache-provider';
|
||||
import Spinner from './Spinner';
|
||||
import {cache} from '../cache';
|
||||
import {fetchUserProfileJSON, fetchUserRepositoriesListJSON} from '../api';
|
||||
|
||||
export default function UserPage({id}) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fill, 20rem)',
|
||||
gridGap: '1rem',
|
||||
alignItems: 'start',
|
||||
}}>
|
||||
<UserDetails id={id} />
|
||||
<Placeholder delayMs={1000} fallback={<Spinner size="medium" />}>
|
||||
<Repositories id={id} />
|
||||
</Placeholder>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const UserDetailsResource = createResource(fetchUserProfileJSON);
|
||||
|
||||
function UserDetails({id}) {
|
||||
const user = UserDetailsResource.read(cache, id);
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridGap: '0.5rem',
|
||||
width: '20rem',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'var(--color-buttonBg)',
|
||||
border: '1px solid var(--color-buttonBorder)',
|
||||
borderRadius: '1rem',
|
||||
}}>
|
||||
<UserPicture source={user.image} />
|
||||
<div
|
||||
style={{
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 'bold',
|
||||
color: 'var(--color-pageTextDark)',
|
||||
}}>
|
||||
{user.name}
|
||||
</div>
|
||||
<div style={{fontSize: '1.25rem'}}>{user.id}</div>
|
||||
{user.tagline !== null && <div>{user.tagline}</div>}
|
||||
<hr
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '1px',
|
||||
border: 'none',
|
||||
backgroundColor: '#ddd',
|
||||
}}
|
||||
/>
|
||||
{user.location && <Location location={user.location} />}
|
||||
{user.email && <Email email={user.email} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Location = ({location}) => (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
style={{
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
marginRight: '0.5rem',
|
||||
fill: 'currentColor',
|
||||
}}>
|
||||
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z" />
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
</svg>
|
||||
{location}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Email = ({email}) => (
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
}}>
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
style={{
|
||||
width: '24px',
|
||||
height: '24px',
|
||||
marginRight: '0.5rem',
|
||||
fill: 'currentColor',
|
||||
}}>
|
||||
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" />
|
||||
<path d="M0 0h24v24H0z" fill="none" />
|
||||
</svg>
|
||||
<a href={`mailto:${email}`}>{email}</a>
|
||||
</div>
|
||||
);
|
||||
|
||||
const ImageResource = createResource(
|
||||
src =>
|
||||
new Promise(resolve => {
|
||||
const img = new Image();
|
||||
img.onload = () => resolve(src);
|
||||
img.src = src;
|
||||
})
|
||||
);
|
||||
|
||||
function Img({src, ...rest}) {
|
||||
return <img src={ImageResource.read(cache, src)} {...rest} />;
|
||||
}
|
||||
|
||||
function UserPicture({source}) {
|
||||
return (
|
||||
<Placeholder delayMs={1500} fallback={<img src={source} alt="poster" />}>
|
||||
<Img
|
||||
src={source}
|
||||
alt="profile picture"
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
borderRadius: '0.5rem',
|
||||
}}
|
||||
/>
|
||||
</Placeholder>
|
||||
);
|
||||
}
|
||||
|
||||
const UserRepositoriesResource = createResource(fetchUserRepositoriesListJSON);
|
||||
|
||||
function Repositories({id}) {
|
||||
const reviews = UserRepositoriesResource.read(cache, id);
|
||||
return (
|
||||
<ul
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridGap: '1rem',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
}}>
|
||||
{reviews.map(review => <Repository key={review.id} {...review} />)}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
function Repository({description, name, url}) {
|
||||
return (
|
||||
<li
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridGap: '0.5rem',
|
||||
padding: '1rem',
|
||||
backgroundColor: 'var(--color-buttonBg)',
|
||||
border: '1px solid var(--color-buttonBorder)',
|
||||
borderRadius: '1rem',
|
||||
}}>
|
||||
<strong>
|
||||
<a href={url}>{name}</a>
|
||||
</strong>
|
||||
<div>{description}</div>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
90
fixtures/unstable-async/suspense/src/index.css
Normal file
@@ -0,0 +1,90 @@
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
:root {
|
||||
--color-debuggerBg: #f7f7f7;
|
||||
--color-debuggerText: #333;
|
||||
--color-debuggerBorder: #e7e7e7;
|
||||
|
||||
--color-panelBg: #f7f7f7;
|
||||
--color-panelText: #333;
|
||||
|
||||
--color-pageTextDark: #000;
|
||||
--color-pageText: #333;
|
||||
--color-pageBg: #fff;
|
||||
|
||||
--color-buttonBg: #f7f7f7;
|
||||
--color-buttonBorder: #e7e7e7;
|
||||
|
||||
--pt: 8px;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: calc(var(--pt)*4);
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||
color: var(--color-pageText);
|
||||
background-color: var(--color-pageBg);
|
||||
}
|
||||
|
||||
/* -------------------------------- */
|
||||
/* Debugger */
|
||||
/* -------------------------------- */
|
||||
|
||||
#debugger {
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
/* width: 100vw; */
|
||||
/* height: 100vh; */
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.🎛 {
|
||||
position: fixed;
|
||||
max-width: calc(var(--pt)*28);
|
||||
border-radius: var(--pt);
|
||||
padding: calc(var(--pt)*2);
|
||||
background-color: var(--color-debuggerBg);
|
||||
border: 1px solid var(--color-debuggerBorder);
|
||||
border-radius: 0.5rem;
|
||||
color: var(--color-debuggerText);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.🕹 {
|
||||
background-color: var(--color-buttonBg);
|
||||
border: 1px solid var(--color-buttonBorder);
|
||||
border-radius: var(--pt);
|
||||
padding: 0;
|
||||
width: calc(var(--pt)*5);
|
||||
height: calc(var(--pt)*5);
|
||||
font-size: calc(var(--pt)*3);
|
||||
line-height: 1;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.🕹.👶 {
|
||||
width: calc(var(--pt)*3);
|
||||
height: calc(var(--pt)*3);
|
||||
font-size: calc(var(--pt)*2);
|
||||
}
|
||||
|
||||
.🕹.🐘 {
|
||||
width: auto;
|
||||
height: calc(var(--pt)*7);
|
||||
padding: var(--pt) calc(var(--pt)*2);
|
||||
}
|
||||
|
||||
.🕹:hover {
|
||||
background-color: white;
|
||||
top: -4px;
|
||||
left: -4px;
|
||||
box-shadow: 4px 4px 0 var(--color-buttonBorder);
|
||||
}
|
||||
|
||||
.🕹:active {
|
||||
box-shadow: none;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
272
fixtures/unstable-async/suspense/src/index.js
Normal file
@@ -0,0 +1,272 @@
|
||||
import React, {Fragment, PureComponent} from 'react';
|
||||
import {unstable_createRoot, render} from 'react-dom';
|
||||
import {cache} from './cache';
|
||||
import {
|
||||
setFakeRequestTime,
|
||||
setPaused,
|
||||
setPauseNewRequests,
|
||||
setProgressHandler,
|
||||
} from './api';
|
||||
import App from './components/App';
|
||||
import Draggable from 'react-draggable';
|
||||
import './index.css';
|
||||
|
||||
let handleReset;
|
||||
|
||||
class Shell extends PureComponent {
|
||||
state = {
|
||||
iteration: 0,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
handleReset = this.handleReset;
|
||||
}
|
||||
|
||||
handleReset = () =>
|
||||
this.setState(prevState => ({
|
||||
iteration: prevState.iteration + 1,
|
||||
}));
|
||||
|
||||
render() {
|
||||
return <App key={this.state.iteration} />;
|
||||
}
|
||||
}
|
||||
|
||||
class Debugger extends PureComponent {
|
||||
state = {
|
||||
iteration: 0,
|
||||
strategy: 'async',
|
||||
requestTime: 1,
|
||||
showDebugger: false,
|
||||
pauseNewRequests: false,
|
||||
waitTime: 0,
|
||||
requests: {},
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
setFakeRequestTime(this.state.requestTime * 1000);
|
||||
setProgressHandler(this.handleProgress);
|
||||
window.addEventListener('keydown', e => {
|
||||
if (e.key.toLowerCase() === '/') {
|
||||
this.setState(state => ({
|
||||
showDebugger: !state.showDebugger,
|
||||
}));
|
||||
} else if (e.key.toLowerCase() === 'p') {
|
||||
this.togglePauseRequests();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (prevState.requestTime !== this.state.requestTime) {
|
||||
setFakeRequestTime(this.state.requestTime * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
handleReset = () => {
|
||||
cache.invalidate();
|
||||
this.setState(state => ({
|
||||
requests: {},
|
||||
}));
|
||||
handleReset();
|
||||
};
|
||||
|
||||
handleProgress = (url, progress, isPaused) => {
|
||||
this.setState(state => ({
|
||||
requests: {
|
||||
...state.requests,
|
||||
[url]: {
|
||||
url,
|
||||
progress,
|
||||
isPaused,
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
togglePauseRequests = () => {
|
||||
this.setState(
|
||||
prevState => {
|
||||
return {pauseNewRequests: !prevState.pauseNewRequests};
|
||||
},
|
||||
() => {
|
||||
setPauseNewRequests(this.state.pauseNewRequests);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
if (!this.state.showDebugger) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Draggable cancel="input">
|
||||
<div
|
||||
className="🎛"
|
||||
style={{
|
||||
bottom: 20,
|
||||
right: 20,
|
||||
}}>
|
||||
<div>
|
||||
Latency: {this.state.requestTime} second{this.state.requestTime !==
|
||||
1
|
||||
? 's'
|
||||
: ''}{' '}
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="3"
|
||||
step="0.5"
|
||||
style={{width: '100%'}}
|
||||
value={this.state.requestTime}
|
||||
onChange={e => {
|
||||
e.stopPropagation();
|
||||
this.setState({requestTime: parseFloat(e.target.value)});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={this.state.pauseNewRequests}
|
||||
onChange={this.togglePauseRequests}
|
||||
/>
|
||||
Pause new requests
|
||||
</label>
|
||||
<br />
|
||||
<br />
|
||||
{Object.values(this.state.requests).filter(x => x.progress !== 100)
|
||||
.length > 0 ? (
|
||||
<Fragment>
|
||||
<div style={{marginBottom: 10}}>
|
||||
<b>Loading</b>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<div style={{marginBottom: 10}}>
|
||||
<b>Loading</b>
|
||||
</div>
|
||||
<small style={{height: 20, display: 'block'}}>(None)</small>
|
||||
</Fragment>
|
||||
)}
|
||||
{Object.keys(this.state.requests)
|
||||
.reverse()
|
||||
.map(url => {
|
||||
const {progress, isPaused} = this.state.requests[url];
|
||||
if (progress === 100) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
key={url}
|
||||
style={{
|
||||
height: 20,
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
cursor: 'pointer',
|
||||
title: isPaused ? 'Resume' : 'Pause',
|
||||
}}
|
||||
onClick={e => {
|
||||
setPaused(url, !isPaused);
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: progress + '%',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
backgroundColor: isPaused ? '#fbfb0e' : '#61dafb',
|
||||
zIndex: -1,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 'bold',
|
||||
color: 'black',
|
||||
}}>
|
||||
{url}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
{Object.values(this.state.requests).filter(x => x.progress === 100)
|
||||
.length > 0 ? (
|
||||
<Fragment>
|
||||
<br />
|
||||
<div style={{marginBottom: 10}}>
|
||||
<b>Cached</b>{' '}
|
||||
<button
|
||||
style={{
|
||||
height: 16,
|
||||
outline: 'none',
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
onClick={this.handleReset}>
|
||||
🗑
|
||||
</button>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<br />
|
||||
<div style={{marginBottom: 10}}>
|
||||
<b>Cached</b>
|
||||
</div>
|
||||
<small style={{height: 20, display: 'block'}}>(None)</small>
|
||||
</Fragment>
|
||||
)}
|
||||
{Object.keys(this.state.requests)
|
||||
.reverse()
|
||||
.map(url => {
|
||||
const {progress} = this.state.requests[url];
|
||||
if (progress !== 100) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={url}
|
||||
style={{
|
||||
height: 20,
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
height: '100%',
|
||||
width: progress + '%',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
backgroundColor:
|
||||
progress !== 100 ? '#61dafb' : 'lightgreen',
|
||||
zIndex: -1,
|
||||
opacity: 0.8,
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
fontFamily: 'monospace',
|
||||
fontWeight: 'bold',
|
||||
color: 'black',
|
||||
}}>
|
||||
{url}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Draggable>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unstable_createRoot(document.getElementById('root')).render(<Shell />);
|
||||
|
||||
render(<Debugger />, document.getElementById('debugger'));
|
||||
7435
fixtures/unstable-async/suspense/yarn.lock
Normal file
21
fixtures/unstable-async/time-slicing/.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
29
fixtures/unstable-async/time-slicing/README.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# CPU async rendering demo
|
||||
|
||||
## What is this fixture?
|
||||
|
||||
This is a demo application based on [Dan Abramov's](https://github.com/gaearon) recent [JSConf Iceland talk](https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html) about React.
|
||||
|
||||
It depends on a local build of React and enables us to easily test async "time slicing" APIs in a more "real world app" like context.
|
||||
|
||||
## Can I use this code in production?
|
||||
|
||||
No. The APIs being tested here are unstable and some of them have still not been released to NPM. For now, this fixture is only a test harness.
|
||||
|
||||
There are also known bugs and inefficiencies in master so **don't use this fixture for demonstration purposes either yet**. Until they are fixed, this fixture is **not** indicative of React async rendering performance.
|
||||
|
||||
## How do I run this fixture?
|
||||
|
||||
```shell
|
||||
# 1: Build react from source
|
||||
cd /path/to/react
|
||||
yarn
|
||||
yarn build dom,core,interaction,simple-cache-provider --type=NODE
|
||||
|
||||
# 2: Install fixture dependencies
|
||||
cd fixtures/unstable-async/time-slicing/
|
||||
yarn
|
||||
|
||||
# 3: Run the app
|
||||
yarn start
|
||||
```
|
||||
20
fixtures/unstable-async/time-slicing/package.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "cpu-demo",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"glamor": "^2.20.40",
|
||||
"react": "../../../build/node_modules/react",
|
||||
"react-dom": "../../../build/node_modules/react-dom",
|
||||
"react-markdown": "^3.2.0",
|
||||
"react-scripts": "^1.1.4",
|
||||
"simple-cache-provider": "../../../build/node_modules/simple-cache-provider",
|
||||
"victory": "^0.25.6"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
}
|
||||
}
|
||||
BIN
fixtures/unstable-async/time-slicing/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
40
fixtures/unstable-async/time-slicing/public/index.html
Normal file
@@ -0,0 +1,40 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
15
fixtures/unstable-async/time-slicing/public/manifest.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
}
|
||||
],
|
||||
"start_url": "./index.html",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
126
fixtures/unstable-async/time-slicing/src/Charts.js
Normal file
@@ -0,0 +1,126 @@
|
||||
import React, {PureComponent} from 'react';
|
||||
import {
|
||||
VictoryArea,
|
||||
VictoryAxis,
|
||||
VictoryChart,
|
||||
VictoryBar,
|
||||
VictoryTheme,
|
||||
VictoryScatter,
|
||||
VictoryStack,
|
||||
} from 'victory';
|
||||
|
||||
const colors = ['#fff489', '#fa57c1', '#b166cc', '#7572ff', '#69a6f9'];
|
||||
|
||||
export default class Charts extends PureComponent {
|
||||
render() {
|
||||
const streamData = this.props.data;
|
||||
return (
|
||||
<div>
|
||||
<div style={{display: 'flex'}}>
|
||||
<VictoryChart
|
||||
theme={VictoryTheme.material}
|
||||
width={400}
|
||||
height={400}
|
||||
style={{
|
||||
parent: {
|
||||
backgroundColor: '#222',
|
||||
},
|
||||
}}>
|
||||
<VictoryAxis
|
||||
style={{
|
||||
axis: {stroke: 'white'},
|
||||
tickLabels: {fill: 'white'},
|
||||
}}
|
||||
/>
|
||||
<VictoryAxis
|
||||
style={{
|
||||
axis: {stroke: 'white'},
|
||||
tickLabels: {fill: 'white'},
|
||||
}}
|
||||
dependentAxis
|
||||
/>
|
||||
<VictoryScatter
|
||||
data={streamData[0]}
|
||||
size={6}
|
||||
style={{
|
||||
data: {
|
||||
fill: d => colors[d.x % 5],
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</VictoryChart>
|
||||
|
||||
<VictoryChart
|
||||
theme={VictoryTheme.material}
|
||||
width={400}
|
||||
height={400}
|
||||
style={{
|
||||
parent: {
|
||||
backgroundColor: '#222',
|
||||
},
|
||||
}}
|
||||
domainPadding={[20, 20]}>
|
||||
<VictoryAxis
|
||||
style={{
|
||||
axis: {stroke: 'white'},
|
||||
tickLabels: {fill: 'white'},
|
||||
}}
|
||||
/>
|
||||
<VictoryAxis
|
||||
style={{
|
||||
axis: {stroke: 'white'},
|
||||
tickLabels: {fill: 'white'},
|
||||
}}
|
||||
dependentAxis
|
||||
/>
|
||||
<VictoryBar
|
||||
data={streamData[0]}
|
||||
style={{
|
||||
data: {
|
||||
fill: d => colors[d.x % 5],
|
||||
stroke: 'none',
|
||||
padding: 5,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</VictoryChart>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
top: '-50px',
|
||||
}}>
|
||||
<VictoryChart
|
||||
theme={VictoryTheme.material}
|
||||
width={800}
|
||||
height={350}
|
||||
style={{
|
||||
parent: {
|
||||
backgroundColor: '#222',
|
||||
},
|
||||
}}>
|
||||
<VictoryAxis
|
||||
style={{
|
||||
axis: {stroke: 'white'},
|
||||
tickLabels: {fill: 'white'},
|
||||
}}
|
||||
/>
|
||||
<VictoryAxis
|
||||
style={{
|
||||
axis: {stroke: 'white'},
|
||||
tickLabels: {fill: 'white'},
|
||||
}}
|
||||
dependentAxis
|
||||
/>
|
||||
<VictoryStack>
|
||||
{streamData.map((data, i) => (
|
||||
<VictoryArea key={i} data={data} colorScale={colors} />
|
||||
))}
|
||||
</VictoryStack>
|
||||
</VictoryChart>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
105
fixtures/unstable-async/time-slicing/src/Clock.js
Normal file
@@ -0,0 +1,105 @@
|
||||
import React, {createRef, PureComponent} from 'react';
|
||||
|
||||
const SPEED = 0.003 / Math.PI;
|
||||
const FRAMES = 10;
|
||||
|
||||
export default class Clock extends PureComponent {
|
||||
faceRef = createRef();
|
||||
arcGroupRef = createRef();
|
||||
clockHandRef = createRef();
|
||||
frame = null;
|
||||
hitCounter = 0;
|
||||
rotation = 0;
|
||||
t0 = Date.now();
|
||||
arcs = [];
|
||||
|
||||
animate = () => {
|
||||
const now = Date.now();
|
||||
const td = now - this.t0;
|
||||
this.rotation = (this.rotation + SPEED * td) % (2 * Math.PI);
|
||||
this.t0 = now;
|
||||
|
||||
this.arcs.push({rotation: this.rotation, td});
|
||||
|
||||
let lx, ly, tx, ty;
|
||||
if (this.arcs.length > FRAMES) {
|
||||
this.arcs.forEach(({rotation, td}, i) => {
|
||||
lx = tx;
|
||||
ly = ty;
|
||||
const r = 145;
|
||||
tx = 155 + r * Math.cos(rotation);
|
||||
ty = 155 + r * Math.sin(rotation);
|
||||
const bigArc = SPEED * td < Math.PI ? '0' : '1';
|
||||
const path = `M${tx} ${ty}A${r} ${r} 0 ${bigArc} 0 ${lx} ${ly}L155 155`;
|
||||
const hue = 120 - Math.min(120, td / 4);
|
||||
const colour = `hsl(${hue}, 100%, ${60 - i * (30 / FRAMES)}%)`;
|
||||
if (i !== 0) {
|
||||
const arcEl = this.arcGroupRef.current.children[i - 1];
|
||||
arcEl.setAttribute('d', path);
|
||||
arcEl.setAttribute('fill', colour);
|
||||
}
|
||||
});
|
||||
this.clockHandRef.current.setAttribute('d', `M155 155L${tx} ${ty}`);
|
||||
this.arcs.shift();
|
||||
}
|
||||
|
||||
if (this.hitCounter > 0) {
|
||||
this.faceRef.current.setAttribute(
|
||||
'fill',
|
||||
`hsla(0, 0%, ${this.hitCounter}%, 0.95)`
|
||||
);
|
||||
this.hitCounter -= 1;
|
||||
} else {
|
||||
this.hitCounter = 0;
|
||||
this.faceRef.current.setAttribute('fill', 'hsla(0, 0%, 5%, 0.95)');
|
||||
}
|
||||
|
||||
this.frame = requestAnimationFrame(this.animate);
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.frame = requestAnimationFrame(this.animate);
|
||||
if (this.faceRef.current) {
|
||||
this.faceRef.current.addEventListener('click', this.handleClick);
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
console.log('componentDidUpdate()', this.faceRef.current);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.faceRef.current.removeEventListener('click', this.handleClick);
|
||||
if (this.frame) {
|
||||
cancelAnimationFrame(this.frame);
|
||||
}
|
||||
}
|
||||
|
||||
handleClick = e => {
|
||||
e.stopPropagation();
|
||||
this.hitCounter = 50;
|
||||
};
|
||||
|
||||
render() {
|
||||
const paths = new Array(FRAMES);
|
||||
for (let i = 0; i < FRAMES; i++) {
|
||||
paths.push(<path className="arcHand" key={i} />);
|
||||
}
|
||||
return (
|
||||
<div className="stutterer">
|
||||
<svg height="310" width="310">
|
||||
<circle
|
||||
className="clockFace"
|
||||
onClick={this.handleClick}
|
||||
cx={155}
|
||||
cy={155}
|
||||
r={150}
|
||||
ref={this.faceRef}
|
||||
/>
|
||||
<g ref={this.arcGroupRef}>{paths}</g>
|
||||
<path className="clockHand" ref={this.clockHandRef} />
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
143
fixtures/unstable-async/time-slicing/src/index.css
Normal file
@@ -0,0 +1,143 @@
|
||||
html,
|
||||
body {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
user-select: none;
|
||||
font-family: Karla, Helvetica Neue, Helvetica, sans-serif;
|
||||
background: rgb(34, 34, 34);
|
||||
color: white;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.VictoryContainer {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
max-width: 960px;
|
||||
margin: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.rendering {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
zoom: 1.8;
|
||||
}
|
||||
|
||||
label {
|
||||
zoom: 1;
|
||||
margin-right: 50px;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
label.selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
label:nth-child(1).selected {
|
||||
color: rgb(253, 25, 153);
|
||||
}
|
||||
|
||||
label:nth-child(2).selected {
|
||||
color: rgb(255, 240, 1);
|
||||
}
|
||||
|
||||
label:nth-child(3).selected {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.input {
|
||||
padding: 16px;
|
||||
font-size: 30px;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
.input.sync {
|
||||
outline-color: rgba(253, 25, 153, 0.1);
|
||||
}
|
||||
.input.debounced {
|
||||
outline-color: rgba(255, 240, 1, 0.1);
|
||||
}
|
||||
.input.async {
|
||||
outline-color: rgba(97, 218, 251, 0.1);
|
||||
}
|
||||
|
||||
|
||||
label {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
label label {
|
||||
display: 'inline-block';
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.row {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.column {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.demo {
|
||||
position: relative;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.stutterer {
|
||||
transform: scale(1.5);
|
||||
height: 310px;
|
||||
width: 310px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: -256px;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
box-shadow: 0 0 10px 10px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 200px;
|
||||
}
|
||||
|
||||
.clockHand {
|
||||
stroke: white;
|
||||
stroke-width: 10px;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.clockFace {
|
||||
stroke: white;
|
||||
stroke-width: 10px;
|
||||
}
|
||||
|
||||
.arcHand {
|
||||
}
|
||||
|
||||
.innerLine {
|
||||
border-radius: 6px;
|
||||
position: absolute;
|
||||
height: 149px;
|
||||
left: 47.5%;
|
||||
top: 0%;
|
||||
width: 5%;
|
||||
background-color: red;
|
||||
transform-origin: bottom center;
|
||||
}
|
||||
156
fixtures/unstable-async/time-slicing/src/index.js
Normal file
@@ -0,0 +1,156 @@
|
||||
import React, {PureComponent, unstable_AsyncMode} from 'react';
|
||||
import {flushSync, render, unstable_deferredUpdates} from 'react-dom';
|
||||
import _ from 'lodash';
|
||||
import Charts from './Charts';
|
||||
import Clock from './Clock';
|
||||
import './index.css';
|
||||
|
||||
let cachedData = new Map();
|
||||
|
||||
class App extends PureComponent {
|
||||
state = {
|
||||
value: '',
|
||||
strategy: 'sync',
|
||||
showDemo: true,
|
||||
showClock: false,
|
||||
};
|
||||
|
||||
// Random data for the chart
|
||||
getStreamData(input) {
|
||||
if (cachedData.has(input)) {
|
||||
return cachedData.get(input);
|
||||
}
|
||||
const multiplier = input.length !== 0 ? input.length : 1;
|
||||
const complexity =
|
||||
(parseInt(window.location.search.substring(1), 10) / 100) * 25 || 25;
|
||||
const data = _.range(5).map(t =>
|
||||
_.range(complexity * multiplier).map((j, i) => {
|
||||
return {
|
||||
x: j,
|
||||
y: (t + 1) * _.random(0, 255),
|
||||
};
|
||||
})
|
||||
);
|
||||
cachedData.set(input, data);
|
||||
return data;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
window.addEventListener('keydown', e => {
|
||||
if (e.key.toLowerCase() === '?') {
|
||||
e.preventDefault();
|
||||
this.setState(state => ({
|
||||
showClock: !state.showClock,
|
||||
}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
handleChartClick = e => {
|
||||
if (this.state.showDemo) {
|
||||
if (e.shiftKey) {
|
||||
this.setState({showDemo: false});
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.state.strategy !== 'async') {
|
||||
this.setState(state => ({
|
||||
showDemo: !state.showDemo,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
if (this._ignoreClick) {
|
||||
return;
|
||||
}
|
||||
this._ignoreClick = true;
|
||||
|
||||
// TODO: needing setTimeout here seems like a React bug.
|
||||
setTimeout(() => {
|
||||
unstable_deferredUpdates(() => {
|
||||
this.setState({showDemo: true}, () => {
|
||||
this._ignoreClick = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
debouncedHandleChange = _.debounce(value => {
|
||||
if (this.state.strategy === 'debounced') {
|
||||
flushSync(() => {
|
||||
this.setState({value: value});
|
||||
});
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
renderOption(strategy, label) {
|
||||
const {strategy: currentStrategy} = this.state;
|
||||
return (
|
||||
<label className={strategy === currentStrategy ? 'selected' : null}>
|
||||
<input
|
||||
type="radio"
|
||||
checked={strategy === currentStrategy}
|
||||
onChange={() => this.setState({strategy})}
|
||||
/>
|
||||
{label}
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
handleChange = e => {
|
||||
const value = e.target.value;
|
||||
const {strategy} = this.state;
|
||||
switch (strategy) {
|
||||
case 'sync':
|
||||
this.setState({value});
|
||||
break;
|
||||
case 'debounced':
|
||||
this.debouncedHandleChange(value);
|
||||
break;
|
||||
case 'async':
|
||||
// TODO: needing setTimeout here seems like a React bug.
|
||||
setTimeout(() => {
|
||||
unstable_deferredUpdates(() => {
|
||||
this.setState({value});
|
||||
});
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const Wrapper =
|
||||
this.state.strategy === 'async' ? unstable_AsyncMode : 'div';
|
||||
const {showClock} = this.state;
|
||||
const data = this.getStreamData(this.state.value);
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="rendering">
|
||||
{this.renderOption('sync', 'Synchronous')}
|
||||
{this.renderOption('debounced', 'Debounced')}
|
||||
{this.renderOption('async', 'Asynchronous')}
|
||||
</div>
|
||||
<input
|
||||
className={'input ' + this.state.strategy}
|
||||
placeholder="longer input → more components and DOM nodes"
|
||||
defaultValue={this.state.input}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<Wrapper>
|
||||
<div className="demo" onClick={this.handleChartClick}>
|
||||
{this.state.showDemo && (
|
||||
<Charts data={data} onClick={this.handleChartClick} />
|
||||
)}
|
||||
<div style={{display: showClock ? 'block' : 'none'}}>
|
||||
<Clock />
|
||||
</div>
|
||||
</div>
|
||||
</Wrapper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.getElementById('root');
|
||||
render(<App />, container);
|
||||
7078
fixtures/unstable-async/time-slicing/yarn.lock
Normal file
9
netlify.toml
Normal file
@@ -0,0 +1,9 @@
|
||||
[build]
|
||||
base = ""
|
||||
publish = "fixtures/dom/build"
|
||||
command = "yarn build --type=UMD_DEV && cd fixtures/dom/ && yarn && yarn prestart && yarn build"
|
||||
|
||||
[[redirects]]
|
||||
from = "/*"
|
||||
to = "/index.html"
|
||||
status = 200
|
||||
31
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "16.3.2",
|
||||
"version": "16.4.1",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
@@ -11,7 +11,7 @@
|
||||
"babel-code-frame": "^6.26.0",
|
||||
"babel-core": "^6.0.0",
|
||||
"babel-eslint": "^8.0.0",
|
||||
"babel-jest": "^22.0.6",
|
||||
"babel-jest": "^23.0.1",
|
||||
"babel-plugin-check-es2015-constants": "^6.5.0",
|
||||
"babel-plugin-external-helpers": "^6.22.0",
|
||||
"babel-plugin-syntax-trailing-function-commas": "^6.5.0",
|
||||
@@ -31,14 +31,12 @@
|
||||
"babel-plugin-transform-es2015-shorthand-properties": "^6.5.0",
|
||||
"babel-plugin-transform-es2015-spread": "^6.5.2",
|
||||
"babel-plugin-transform-es2015-template-literals": "^6.5.2",
|
||||
"babel-plugin-transform-es3-member-expression-literals": "^6.5.0",
|
||||
"babel-plugin-transform-es3-property-literals": "^6.5.0",
|
||||
"babel-plugin-transform-object-rest-spread": "^6.6.5",
|
||||
"babel-plugin-transform-react-jsx-source": "^6.8.0",
|
||||
"babel-plugin-transform-regenerator": "^6.26.0",
|
||||
"babel-preset-react": "^6.5.0",
|
||||
"babel-traverse": "^6.9.0",
|
||||
"babylon": "6.15.0",
|
||||
"babylon": "6.18.0",
|
||||
"bundle-collapser": "^1.1.1",
|
||||
"chalk": "^1.1.3",
|
||||
"cli-table": "^0.3.1",
|
||||
@@ -59,19 +57,19 @@
|
||||
"eslint-plugin-no-for-of-loops": "^1.0.0",
|
||||
"eslint-plugin-react": "^6.7.1",
|
||||
"eslint-plugin-react-internal": "link:./scripts/eslint-rules/",
|
||||
"fbjs": "^0.8.16",
|
||||
"fbjs-scripts": "^0.6.0",
|
||||
"filesize": "^3.5.6",
|
||||
"flow-bin": "^0.61.0",
|
||||
"flow-bin": "^0.72.0",
|
||||
"flow-coverage-report": "^0.4.0",
|
||||
"git-branch": "^0.3.0",
|
||||
"glob": "^6.0.4",
|
||||
"glob-stream": "^6.1.0",
|
||||
"google-closure-compiler": "20180506.0.0",
|
||||
"gzip-js": "~0.3.2",
|
||||
"gzip-size": "^3.0.0",
|
||||
"jasmine-check": "^1.0.0-rc.0",
|
||||
"jest": "^22.0.6",
|
||||
"jest-diff": "^22.1.0",
|
||||
"jest": "^23.1.0",
|
||||
"jest-diff": "^23.0.1",
|
||||
"merge-stream": "^1.0.0",
|
||||
"minimatch": "^3.0.4",
|
||||
"minimist": "^1.2.0",
|
||||
@@ -79,14 +77,13 @@
|
||||
"ncp": "^2.0.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"platform": "^1.1.0",
|
||||
"prettier": "1.8.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"prettier": "1.13.7",
|
||||
"prop-types": "^15.6.2",
|
||||
"random-seed": "^0.3.0",
|
||||
"react-lifecycles-compat": "^1.0.2",
|
||||
"react-lifecycles-compat": "^3.0.2",
|
||||
"rimraf": "^2.6.1",
|
||||
"rollup": "^0.52.1",
|
||||
"rollup-plugin-babel": "^3.0.1",
|
||||
"rollup-plugin-closure-compiler-js": "^1.0.6",
|
||||
"rollup-plugin-commonjs": "^8.2.6",
|
||||
"rollup-plugin-node-resolve": "^2.1.1",
|
||||
"rollup-plugin-prettier": "^0.3.0",
|
||||
@@ -101,7 +98,10 @@
|
||||
"yargs": "^6.3.0"
|
||||
},
|
||||
"devEngines": {
|
||||
"node": "8.x || 9.x"
|
||||
"node": "8.x || 9.x || 10.x"
|
||||
},
|
||||
"jest": {
|
||||
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run version-check && node ./scripts/rollup/build.js",
|
||||
@@ -109,7 +109,7 @@
|
||||
"linc": "node ./scripts/tasks/linc.js",
|
||||
"lint": "node ./scripts/tasks/eslint.js",
|
||||
"lint-build": "node ./scripts/rollup/validate/index.js",
|
||||
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json",
|
||||
"postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json && node ./scripts/flow/createFlowConfigs.js",
|
||||
"debug-test": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.source.js --runInBand",
|
||||
"test": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source.js",
|
||||
"test-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source.js",
|
||||
@@ -117,6 +117,7 @@
|
||||
"test-build": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.build.js",
|
||||
"test-build-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.build.js",
|
||||
"flow": "node ./scripts/tasks/flow.js",
|
||||
"flow-ci": "node ./scripts/tasks/flow-ci.js",
|
||||
"prettier": "node ./scripts/prettier/index.js write-changed",
|
||||
"prettier-all": "node ./scripts/prettier/index.js write",
|
||||
"version-check": "node ./scripts/tasks/version-check.js"
|
||||
|
||||
@@ -15,7 +15,7 @@ Other cases have **better long-term solutions**:
|
||||
|
||||
The main motivation for `create-subscription` is to provide a way for library authors to ensure compatibility with React's upcoming asynchronous rendering mode. `create-subscription` guarantees correctness in async mode, accounting for the subtle bugs and edge cases that a library author might otherwise miss.
|
||||
|
||||
However, it achieves correctness by sometimes de-opting to synchronous mode, obviating the benefits of async rendering. This is an inherent limitation of storing state outside of React's managed state queue and rendering in response to a change event.
|
||||
However, [it achieves correctness by sometimes de-opting to synchronous mode](https://github.com/facebook/react/issues/13186#issuecomment-403959161), obviating the benefits of async rendering. This is an inherent limitation of storing state outside of React's managed state queue and rendering in response to a change event.
|
||||
|
||||
The effect of de-opting to sync mode is that the main thread may periodically be blocked (in the case of CPU-bound work), and placeholders may appear earlier than desired (in the case of IO-bound work).
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "create-subscription",
|
||||
"description": "utility for subscribing to external data sources inside React components",
|
||||
"version": "16.3.2",
|
||||
"version": "16.4.1",
|
||||
"repository": "facebook/react",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
@@ -9,9 +9,6 @@
|
||||
"index.js",
|
||||
"cjs/"
|
||||
],
|
||||
"dependencies": {
|
||||
"fbjs": "^0.8.16"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.3.0"
|
||||
},
|
||||
|
||||
@@ -264,7 +264,6 @@ describe('createSubscription', () => {
|
||||
|
||||
it('should ignore values emitted by a new subscribable until the commit phase', () => {
|
||||
const log = [];
|
||||
let parentInstance;
|
||||
|
||||
function Child({value}) {
|
||||
ReactNoop.yield('Child: ' + value);
|
||||
@@ -301,8 +300,6 @@ describe('createSubscription', () => {
|
||||
}
|
||||
|
||||
render() {
|
||||
parentInstance = this;
|
||||
|
||||
return (
|
||||
<Subscription source={this.state.observed}>
|
||||
{(value = 'default') => {
|
||||
@@ -331,8 +328,8 @@ describe('createSubscription', () => {
|
||||
observableB.next('b-2');
|
||||
observableB.next('b-3');
|
||||
|
||||
// Mimic a higher-priority interruption
|
||||
parentInstance.setState({observed: observableA});
|
||||
// Update again
|
||||
ReactNoop.render(<Parent observed={observableA} />);
|
||||
|
||||
// Flush everything and ensure that the correct subscribable is used
|
||||
// We expect the last emitted update to be rendered (because of the commit phase value check)
|
||||
@@ -354,7 +351,6 @@ describe('createSubscription', () => {
|
||||
|
||||
it('should not drop values emitted between updates', () => {
|
||||
const log = [];
|
||||
let parentInstance;
|
||||
|
||||
function Child({value}) {
|
||||
ReactNoop.yield('Child: ' + value);
|
||||
@@ -391,8 +387,6 @@ describe('createSubscription', () => {
|
||||
}
|
||||
|
||||
render() {
|
||||
parentInstance = this;
|
||||
|
||||
return (
|
||||
<Subscription source={this.state.observed}>
|
||||
{(value = 'default') => {
|
||||
@@ -420,8 +414,8 @@ describe('createSubscription', () => {
|
||||
observableA.next('a-1');
|
||||
observableA.next('a-2');
|
||||
|
||||
// Mimic a higher-priority interruption
|
||||
parentInstance.setState({observed: observableA});
|
||||
// Update again
|
||||
ReactNoop.render(<Parent observed={observableA} />);
|
||||
|
||||
// Flush everything and ensure that the correct subscribable is used
|
||||
// We expect the new subscribable to finish rendering,
|
||||
@@ -456,7 +450,9 @@ describe('createSubscription', () => {
|
||||
},
|
||||
() => null,
|
||||
);
|
||||
}).toWarnDev('Subscription must specify a getCurrentValue function');
|
||||
}).toWarnDev('Subscription must specify a getCurrentValue function', {
|
||||
withoutStack: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should warn for invalid missing subscribe', () => {
|
||||
@@ -467,7 +463,9 @@ describe('createSubscription', () => {
|
||||
},
|
||||
() => null,
|
||||
);
|
||||
}).toWarnDev('Subscription must specify a subscribe function');
|
||||
}).toWarnDev('Subscription must specify a subscribe function', {
|
||||
withoutStack: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should warn if subscribe does not return an unsubscribe method', () => {
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import invariant from 'shared/invariant';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
||||
type Unsubscribe = () => void;
|
||||
|
||||
@@ -21,7 +21,7 @@ export function createSubscription<Property, Value>(
|
||||
getCurrentValue: (source: Property) => Value | void,
|
||||
|
||||
// Setup a subscription for the subscribable value in props, and return an unsubscribe function.
|
||||
// Return false to indicate the property cannot be unsubscribed from (e.g. native Promises).
|
||||
// Return empty function if the property cannot be unsubscribed from (e.g. native Promises).
|
||||
// Due to the variety of change event types, subscribers should provide their own handlers.
|
||||
// Those handlers should not attempt to update state though;
|
||||
// They should call the callback() instead when a subscription changes.
|
||||
@@ -36,11 +36,11 @@ export function createSubscription<Property, Value>(
|
||||
}> {
|
||||
const {getCurrentValue, subscribe} = config;
|
||||
|
||||
warning(
|
||||
warningWithoutStack(
|
||||
typeof getCurrentValue === 'function',
|
||||
'Subscription must specify a getCurrentValue function',
|
||||
);
|
||||
warning(
|
||||
warningWithoutStack(
|
||||
typeof subscribe === 'function',
|
||||
'Subscription must specify a subscribe function',
|
||||
);
|
||||
@@ -87,13 +87,13 @@ export function createSubscription<Property, Value>(
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (this.state.source !== prevState.source) {
|
||||
this.unsubscribe(prevState);
|
||||
this.unsubscribe();
|
||||
this.subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribe(this.state);
|
||||
this.unsubscribe();
|
||||
|
||||
// Track mounted to avoid calling setState after unmounting
|
||||
// For source like Promises that can't be unsubscribed from.
|
||||
@@ -147,7 +147,7 @@ export function createSubscription<Property, Value>(
|
||||
}
|
||||
}
|
||||
|
||||
unsubscribe(state: State) {
|
||||
unsubscribe() {
|
||||
if (typeof this._unsubscribe === 'function') {
|
||||
this._unsubscribe();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import ReactErrorUtils from 'shared/ReactErrorUtils';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import {
|
||||
injectEventPluginOrder,
|
||||
@@ -25,6 +25,7 @@ import type {PluginModule} from './PluginModuleType';
|
||||
import type {ReactSyntheticEvent} from './ReactSyntheticEventType';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {AnyNativeEvent} from './PluginModuleType';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
|
||||
/**
|
||||
* Internal queue of events that have accumulated their dispatches and are
|
||||
@@ -165,8 +166,8 @@ export function getListener(inst: Fiber, registrationName: string) {
|
||||
* @internal
|
||||
*/
|
||||
function extractEvents(
|
||||
topLevelType: string,
|
||||
targetInst: Fiber,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: null | Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {
|
||||
@@ -227,8 +228,8 @@ export function runEventsInBatch(
|
||||
}
|
||||
|
||||
export function runExtractedEventsInBatch(
|
||||
topLevelType: string,
|
||||
targetInst: Fiber,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: null | Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
) {
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
PluginModule,
|
||||
} from './PluginModuleType';
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
type NamesToPlugins = {[key: PluginName]: PluginModule<AnyNativeEvent>};
|
||||
type EventPluginOrder = null | Array<PluginName>;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
*/
|
||||
|
||||
import ReactErrorUtils from 'shared/ReactErrorUtils';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import invariant from 'shared/invariant';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
||||
export let getFiberCurrentPropsFromNode = null;
|
||||
export let getInstanceFromNode = null;
|
||||
@@ -21,7 +21,7 @@ export const injection = {
|
||||
getNodeFromInstance,
|
||||
} = Injected);
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
warningWithoutStack(
|
||||
getNodeFromInstance && getInstanceFromNode,
|
||||
'EventPluginUtils.injection.injectComponentTree(...): Injected ' +
|
||||
'module is missing getNodeFromInstance or getInstanceFromNode.',
|
||||
@@ -30,21 +30,6 @@ export const injection = {
|
||||
},
|
||||
};
|
||||
|
||||
export function isEndish(topLevelType) {
|
||||
return (
|
||||
topLevelType === 'topMouseUp' ||
|
||||
topLevelType === 'topTouchEnd' ||
|
||||
topLevelType === 'topTouchCancel'
|
||||
);
|
||||
}
|
||||
|
||||
export function isMoveish(topLevelType) {
|
||||
return topLevelType === 'topMouseMove' || topLevelType === 'topTouchMove';
|
||||
}
|
||||
export function isStartish(topLevelType) {
|
||||
return topLevelType === 'topMouseDown' || topLevelType === 'topTouchStart';
|
||||
}
|
||||
|
||||
let validateEventDispatches;
|
||||
if (__DEV__) {
|
||||
validateEventDispatches = function(event) {
|
||||
@@ -54,14 +39,18 @@ if (__DEV__) {
|
||||
const listenersIsArr = Array.isArray(dispatchListeners);
|
||||
const listenersLen = listenersIsArr
|
||||
? dispatchListeners.length
|
||||
: dispatchListeners ? 1 : 0;
|
||||
: dispatchListeners
|
||||
? 1
|
||||
: 0;
|
||||
|
||||
const instancesIsArr = Array.isArray(dispatchInstances);
|
||||
const instancesLen = instancesIsArr
|
||||
? dispatchInstances.length
|
||||
: dispatchInstances ? 1 : 0;
|
||||
: dispatchInstances
|
||||
? 1
|
||||
: 0;
|
||||
|
||||
warning(
|
||||
warningWithoutStack(
|
||||
instancesIsArr === listenersIsArr && instancesLen === listenersLen,
|
||||
'EventPluginUtils: Invalid `event`.',
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
traverseTwoPhase,
|
||||
traverseEnterLeave,
|
||||
} from 'shared/ReactTreeTraversal';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
||||
import {getListener} from './EventPluginHub';
|
||||
import accumulateInto from './accumulateInto';
|
||||
@@ -46,7 +46,7 @@ function listenerAtPhase(inst, event, propagationPhase: PropagationPhases) {
|
||||
*/
|
||||
function accumulateDirectionalDispatches(inst, phase, event) {
|
||||
if (__DEV__) {
|
||||
warning(inst, 'Dispatching inst must not be null');
|
||||
warningWithoutStack(inst, 'Dispatching inst must not be null');
|
||||
}
|
||||
const listener = listenerAtPhase(inst, event, phase);
|
||||
if (listener) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
DispatchConfig,
|
||||
ReactSyntheticEvent,
|
||||
} from './ReactSyntheticEventType';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
|
||||
export type EventTypes = {[key: string]: DispatchConfig};
|
||||
|
||||
@@ -22,8 +23,8 @@ export type PluginName = string;
|
||||
export type PluginModule<NativeEvent> = {
|
||||
eventTypes: EventTypes,
|
||||
extractEvents: (
|
||||
topLevelType: string,
|
||||
targetInst: Fiber,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: null | Fiber,
|
||||
nativeTarget: NativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
) => ?ReactSyntheticEvent,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import {
|
||||
getInstanceFromNode,
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
*/
|
||||
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
|
||||
export type DispatchConfig = {
|
||||
dependencies: Array<string>,
|
||||
dependencies: Array<TopLevelType>,
|
||||
phasedRegistrationNames?: {
|
||||
bubbled: string,
|
||||
captured: string,
|
||||
|
||||
@@ -8,9 +8,6 @@
|
||||
import {getLowestCommonAncestor, isAncestor} from 'shared/ReactTreeTraversal';
|
||||
|
||||
import {
|
||||
isStartish,
|
||||
isMoveish,
|
||||
isEndish,
|
||||
executeDirectDispatch,
|
||||
hasDispatches,
|
||||
executeDispatchesInOrderStopAtTrue,
|
||||
@@ -24,6 +21,17 @@ import {
|
||||
import ResponderSyntheticEvent from './ResponderSyntheticEvent';
|
||||
import ResponderTouchHistoryStore from './ResponderTouchHistoryStore';
|
||||
import accumulate from './accumulate';
|
||||
import {
|
||||
TOP_SCROLL,
|
||||
TOP_SELECTION_CHANGE,
|
||||
TOP_TOUCH_CANCEL,
|
||||
isStartish,
|
||||
isMoveish,
|
||||
isEndish,
|
||||
startDependencies,
|
||||
moveDependencies,
|
||||
endDependencies,
|
||||
} from './ResponderTopLevelEventTypes';
|
||||
|
||||
/**
|
||||
* Instance of element that should respond to touch/move types of interactions,
|
||||
@@ -37,11 +45,6 @@ let responderInst = null;
|
||||
*/
|
||||
let trackedTouchCount = 0;
|
||||
|
||||
/**
|
||||
* Last reported number of active touches.
|
||||
*/
|
||||
let previousActiveTouches = 0;
|
||||
|
||||
const changeResponder = function(nextResponderInst, blockHostResponder) {
|
||||
const oldResponderInst = responderInst;
|
||||
responderInst = nextResponderInst;
|
||||
@@ -64,6 +67,7 @@ const eventTypes = {
|
||||
bubbled: 'onStartShouldSetResponder',
|
||||
captured: 'onStartShouldSetResponderCapture',
|
||||
},
|
||||
dependencies: startDependencies,
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -80,6 +84,7 @@ const eventTypes = {
|
||||
bubbled: 'onScrollShouldSetResponder',
|
||||
captured: 'onScrollShouldSetResponderCapture',
|
||||
},
|
||||
dependencies: [TOP_SCROLL],
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -94,6 +99,7 @@ const eventTypes = {
|
||||
bubbled: 'onSelectionChangeShouldSetResponder',
|
||||
captured: 'onSelectionChangeShouldSetResponderCapture',
|
||||
},
|
||||
dependencies: [TOP_SELECTION_CHANGE],
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -105,21 +111,44 @@ const eventTypes = {
|
||||
bubbled: 'onMoveShouldSetResponder',
|
||||
captured: 'onMoveShouldSetResponderCapture',
|
||||
},
|
||||
dependencies: moveDependencies,
|
||||
},
|
||||
|
||||
/**
|
||||
* Direct responder events dispatched directly to responder. Do not bubble.
|
||||
*/
|
||||
responderStart: {registrationName: 'onResponderStart'},
|
||||
responderMove: {registrationName: 'onResponderMove'},
|
||||
responderEnd: {registrationName: 'onResponderEnd'},
|
||||
responderRelease: {registrationName: 'onResponderRelease'},
|
||||
responderStart: {
|
||||
registrationName: 'onResponderStart',
|
||||
dependencies: startDependencies,
|
||||
},
|
||||
responderMove: {
|
||||
registrationName: 'onResponderMove',
|
||||
dependencies: moveDependencies,
|
||||
},
|
||||
responderEnd: {
|
||||
registrationName: 'onResponderEnd',
|
||||
dependencies: endDependencies,
|
||||
},
|
||||
responderRelease: {
|
||||
registrationName: 'onResponderRelease',
|
||||
dependencies: endDependencies,
|
||||
},
|
||||
responderTerminationRequest: {
|
||||
registrationName: 'onResponderTerminationRequest',
|
||||
dependencies: [],
|
||||
},
|
||||
responderGrant: {
|
||||
registrationName: 'onResponderGrant',
|
||||
dependencies: [],
|
||||
},
|
||||
responderReject: {
|
||||
registrationName: 'onResponderReject',
|
||||
dependencies: [],
|
||||
},
|
||||
responderTerminate: {
|
||||
registrationName: 'onResponderTerminate',
|
||||
dependencies: [],
|
||||
},
|
||||
responderGrant: {registrationName: 'onResponderGrant'},
|
||||
responderReject: {registrationName: 'onResponderReject'},
|
||||
responderTerminate: {registrationName: 'onResponderTerminate'},
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -322,7 +351,7 @@ function setResponderAndExtractTransfer(
|
||||
? eventTypes.startShouldSetResponder
|
||||
: isMoveish(topLevelType)
|
||||
? eventTypes.moveShouldSetResponder
|
||||
: topLevelType === 'topSelectionChange'
|
||||
: topLevelType === TOP_SELECTION_CHANGE
|
||||
? eventTypes.selectionChangeShouldSetResponder
|
||||
: eventTypes.scrollShouldSetResponder;
|
||||
|
||||
@@ -427,8 +456,8 @@ function canTriggerTransfer(topLevelType, topLevelInst, nativeEvent) {
|
||||
// responderIgnoreScroll: We are trying to migrate away from specifically
|
||||
// tracking native scroll events here and responderIgnoreScroll indicates we
|
||||
// will send topTouchCancel to handle canceling touch events instead
|
||||
((topLevelType === 'topScroll' && !nativeEvent.responderIgnoreScroll) ||
|
||||
(trackedTouchCount > 0 && topLevelType === 'topSelectionChange') ||
|
||||
((topLevelType === TOP_SCROLL && !nativeEvent.responderIgnoreScroll) ||
|
||||
(trackedTouchCount > 0 && topLevelType === TOP_SELECTION_CHANGE) ||
|
||||
isStartish(topLevelType) ||
|
||||
isMoveish(topLevelType))
|
||||
);
|
||||
@@ -519,7 +548,9 @@ const ResponderEventPlugin = {
|
||||
? eventTypes.responderStart
|
||||
: isResponderTouchMove
|
||||
? eventTypes.responderMove
|
||||
: isResponderTouchEnd ? eventTypes.responderEnd : null;
|
||||
: isResponderTouchEnd
|
||||
? eventTypes.responderEnd
|
||||
: null;
|
||||
|
||||
if (incrementalTouch) {
|
||||
const gesture = ResponderSyntheticEvent.getPooled(
|
||||
@@ -534,7 +565,7 @@ const ResponderEventPlugin = {
|
||||
}
|
||||
|
||||
const isResponderTerminate =
|
||||
responderInst && topLevelType === 'topTouchCancel';
|
||||
responderInst && topLevelType === TOP_TOUCH_CANCEL;
|
||||
const isResponderRelease =
|
||||
responderInst &&
|
||||
!isResponderTerminate &&
|
||||
@@ -542,7 +573,9 @@ const ResponderEventPlugin = {
|
||||
noResponderTouches(nativeEvent);
|
||||
const finalTouch = isResponderTerminate
|
||||
? eventTypes.responderTerminate
|
||||
: isResponderRelease ? eventTypes.responderRelease : null;
|
||||
: isResponderRelease
|
||||
? eventTypes.responderRelease
|
||||
: null;
|
||||
if (finalTouch) {
|
||||
const finalEvent = ResponderSyntheticEvent.getPooled(
|
||||
finalTouch,
|
||||
@@ -556,23 +589,10 @@ const ResponderEventPlugin = {
|
||||
changeResponder(null);
|
||||
}
|
||||
|
||||
const numberActiveTouches =
|
||||
ResponderTouchHistoryStore.touchHistory.numberActiveTouches;
|
||||
if (
|
||||
ResponderEventPlugin.GlobalInteractionHandler &&
|
||||
numberActiveTouches !== previousActiveTouches
|
||||
) {
|
||||
ResponderEventPlugin.GlobalInteractionHandler.onChange(
|
||||
numberActiveTouches,
|
||||
);
|
||||
}
|
||||
previousActiveTouches = numberActiveTouches;
|
||||
|
||||
return extracted;
|
||||
},
|
||||
|
||||
GlobalResponderHandler: null,
|
||||
GlobalInteractionHandler: null,
|
||||
|
||||
injection: {
|
||||
/**
|
||||
@@ -580,17 +600,9 @@ const ResponderEventPlugin = {
|
||||
* Object that handles any change in responder. Use this to inject
|
||||
* integration with an existing touch handling system etc.
|
||||
*/
|
||||
injectGlobalResponderHandler: function(GlobalResponderHandler) {
|
||||
injectGlobalResponderHandler(GlobalResponderHandler) {
|
||||
ResponderEventPlugin.GlobalResponderHandler = GlobalResponderHandler;
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {{onChange: (numberActiveTouches) => void} GlobalInteractionHandler
|
||||
* Object that handles any change in the number of active touches.
|
||||
*/
|
||||
injectGlobalInteractionHandler: function(GlobalInteractionHandler) {
|
||||
ResponderEventPlugin.GlobalInteractionHandler = GlobalInteractionHandler;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
31
packages/events/ResponderTopLevelEventTypes.js
Normal file
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export const TOP_TOUCH_START = 'topTouchStart';
|
||||
export const TOP_TOUCH_MOVE = 'topTouchMove';
|
||||
export const TOP_TOUCH_END = 'topTouchEnd';
|
||||
export const TOP_TOUCH_CANCEL = 'topTouchCancel';
|
||||
export const TOP_SCROLL = 'topScroll';
|
||||
export const TOP_SELECTION_CHANGE = 'topSelectionChange';
|
||||
|
||||
export function isStartish(topLevelType: mixed): boolean {
|
||||
return topLevelType === TOP_TOUCH_START;
|
||||
}
|
||||
|
||||
export function isMoveish(topLevelType: mixed): boolean {
|
||||
return topLevelType === TOP_TOUCH_MOVE;
|
||||
}
|
||||
|
||||
export function isEndish(topLevelType: mixed): boolean {
|
||||
return topLevelType === TOP_TOUCH_END || topLevelType === TOP_TOUCH_CANCEL;
|
||||
}
|
||||
|
||||
export const startDependencies = [TOP_TOUCH_START];
|
||||
export const moveDependencies = [TOP_TOUCH_MOVE];
|
||||
export const endDependencies = [TOP_TOUCH_CANCEL, TOP_TOUCH_END];
|
||||
@@ -7,10 +7,10 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import invariant from 'shared/invariant';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
||||
import {isEndish, isMoveish, isStartish} from './EventPluginUtils';
|
||||
import {isStartish, isMoveish, isEndish} from './ResponderTopLevelEventTypes';
|
||||
|
||||
/**
|
||||
* Tracks the position and time of each active touch by `touch.identifier`. We
|
||||
@@ -95,7 +95,7 @@ function resetTouchRecord(touchRecord: TouchRecord, touch: Touch): void {
|
||||
function getTouchIdentifier({identifier}: Touch): number {
|
||||
invariant(identifier != null, 'Touch object is missing identifier.');
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
warningWithoutStack(
|
||||
identifier <= MAX_TOUCH_BANK,
|
||||
'Touch identifier %s is greater than maximum supported %s which causes ' +
|
||||
'performance issues backfilling array locations for all of the indices.',
|
||||
@@ -200,7 +200,7 @@ const ResponderTouchHistoryStore = {
|
||||
}
|
||||
if (__DEV__) {
|
||||
const activeRecord = touchBank[touchHistory.indexOfSingleActiveTouch];
|
||||
warning(
|
||||
warningWithoutStack(
|
||||
activeRecord != null && activeRecord.touchActive,
|
||||
'Cannot find single active touch.',
|
||||
);
|
||||
|
||||
@@ -7,23 +7,11 @@
|
||||
|
||||
/* eslint valid-typeof: 0 */
|
||||
|
||||
import emptyFunction from 'fbjs/lib/emptyFunction';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
import invariant from 'shared/invariant';
|
||||
import warningWithoutStack from 'shared/warningWithoutStack';
|
||||
|
||||
let didWarnForAddedNewProperty = false;
|
||||
const EVENT_POOL_SIZE = 10;
|
||||
|
||||
const shouldBeReleasedProperties = [
|
||||
'dispatchConfig',
|
||||
'_targetInst',
|
||||
'nativeEvent',
|
||||
'isDefaultPrevented',
|
||||
'isPropagationStopped',
|
||||
'_dispatchListeners',
|
||||
'_dispatchInstances',
|
||||
];
|
||||
|
||||
/**
|
||||
* @interface Event
|
||||
* @see http://www.w3.org/TR/DOM-Level-3-Events/
|
||||
@@ -32,7 +20,9 @@ const EventInterface = {
|
||||
type: null,
|
||||
target: null,
|
||||
// currentTarget is set when dispatching; no use in copying it here
|
||||
currentTarget: emptyFunction.thatReturnsNull,
|
||||
currentTarget: function() {
|
||||
return null;
|
||||
},
|
||||
eventPhase: null,
|
||||
bubbles: null,
|
||||
cancelable: null,
|
||||
@@ -43,6 +33,14 @@ const EventInterface = {
|
||||
isTrusted: null,
|
||||
};
|
||||
|
||||
function functionThatReturnsTrue() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function functionThatReturnsFalse() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synthetic events are dispatched by event plugins, typically in response to a
|
||||
* top-level event delegation handler.
|
||||
@@ -72,6 +70,8 @@ function SyntheticEvent(
|
||||
delete this.nativeEvent;
|
||||
delete this.preventDefault;
|
||||
delete this.stopPropagation;
|
||||
delete this.isDefaultPrevented;
|
||||
delete this.isPropagationStopped;
|
||||
}
|
||||
|
||||
this.dispatchConfig = dispatchConfig;
|
||||
@@ -103,11 +103,11 @@ function SyntheticEvent(
|
||||
? nativeEvent.defaultPrevented
|
||||
: nativeEvent.returnValue === false;
|
||||
if (defaultPrevented) {
|
||||
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
|
||||
this.isDefaultPrevented = functionThatReturnsTrue;
|
||||
} else {
|
||||
this.isDefaultPrevented = emptyFunction.thatReturnsFalse;
|
||||
this.isDefaultPrevented = functionThatReturnsFalse;
|
||||
}
|
||||
this.isPropagationStopped = emptyFunction.thatReturnsFalse;
|
||||
this.isPropagationStopped = functionThatReturnsFalse;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ Object.assign(SyntheticEvent.prototype, {
|
||||
} else if (typeof event.returnValue !== 'unknown') {
|
||||
event.returnValue = false;
|
||||
}
|
||||
this.isDefaultPrevented = emptyFunction.thatReturnsTrue;
|
||||
this.isDefaultPrevented = functionThatReturnsTrue;
|
||||
},
|
||||
|
||||
stopPropagation: function() {
|
||||
@@ -144,7 +144,7 @@ Object.assign(SyntheticEvent.prototype, {
|
||||
event.cancelBubble = true;
|
||||
}
|
||||
|
||||
this.isPropagationStopped = emptyFunction.thatReturnsTrue;
|
||||
this.isPropagationStopped = functionThatReturnsTrue;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -153,7 +153,7 @@ Object.assign(SyntheticEvent.prototype, {
|
||||
* won't be added back into the pool.
|
||||
*/
|
||||
persist: function() {
|
||||
this.isPersistent = emptyFunction.thatReturnsTrue;
|
||||
this.isPersistent = functionThatReturnsTrue;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -161,7 +161,7 @@ Object.assign(SyntheticEvent.prototype, {
|
||||
*
|
||||
* @return {boolean} True if this should not be released, false otherwise.
|
||||
*/
|
||||
isPersistent: emptyFunction.thatReturnsFalse,
|
||||
isPersistent: functionThatReturnsFalse,
|
||||
|
||||
/**
|
||||
* `PooledClass` looks for `destructor` on each instance it releases.
|
||||
@@ -179,24 +179,44 @@ Object.assign(SyntheticEvent.prototype, {
|
||||
this[propName] = null;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < shouldBeReleasedProperties.length; i++) {
|
||||
this[shouldBeReleasedProperties[i]] = null;
|
||||
}
|
||||
this.dispatchConfig = null;
|
||||
this._targetInst = null;
|
||||
this.nativeEvent = null;
|
||||
this.isDefaultPrevented = functionThatReturnsFalse;
|
||||
this.isPropagationStopped = functionThatReturnsFalse;
|
||||
this._dispatchListeners = null;
|
||||
this._dispatchInstances = null;
|
||||
if (__DEV__) {
|
||||
Object.defineProperty(
|
||||
this,
|
||||
'nativeEvent',
|
||||
getPooledWarningPropertyDefinition('nativeEvent', null),
|
||||
);
|
||||
Object.defineProperty(
|
||||
this,
|
||||
'isDefaultPrevented',
|
||||
getPooledWarningPropertyDefinition(
|
||||
'isDefaultPrevented',
|
||||
functionThatReturnsFalse,
|
||||
),
|
||||
);
|
||||
Object.defineProperty(
|
||||
this,
|
||||
'isPropagationStopped',
|
||||
getPooledWarningPropertyDefinition(
|
||||
'isPropagationStopped',
|
||||
functionThatReturnsFalse,
|
||||
),
|
||||
);
|
||||
Object.defineProperty(
|
||||
this,
|
||||
'preventDefault',
|
||||
getPooledWarningPropertyDefinition('preventDefault', emptyFunction),
|
||||
getPooledWarningPropertyDefinition('preventDefault', () => {}),
|
||||
);
|
||||
Object.defineProperty(
|
||||
this,
|
||||
'stopPropagation',
|
||||
getPooledWarningPropertyDefinition('stopPropagation', emptyFunction),
|
||||
getPooledWarningPropertyDefinition('stopPropagation', () => {}),
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -228,49 +248,6 @@ SyntheticEvent.extend = function(Interface) {
|
||||
return Class;
|
||||
};
|
||||
|
||||
/** Proxying after everything set on SyntheticEvent
|
||||
* to resolve Proxy issue on some WebKit browsers
|
||||
* in which some Event properties are set to undefined (GH#10010)
|
||||
*/
|
||||
if (__DEV__) {
|
||||
const isProxySupported =
|
||||
typeof Proxy === 'function' &&
|
||||
// https://github.com/facebook/react/issues/12011
|
||||
!Object.isSealed(new Proxy({}, {}));
|
||||
|
||||
if (isProxySupported) {
|
||||
/*eslint-disable no-func-assign */
|
||||
SyntheticEvent = new Proxy(SyntheticEvent, {
|
||||
construct: function(target, args) {
|
||||
return this.apply(target, Object.create(target.prototype), args);
|
||||
},
|
||||
apply: function(constructor, that, args) {
|
||||
return new Proxy(constructor.apply(that, args), {
|
||||
set: function(target, prop, value) {
|
||||
if (
|
||||
prop !== 'isPersistent' &&
|
||||
!target.constructor.Interface.hasOwnProperty(prop) &&
|
||||
shouldBeReleasedProperties.indexOf(prop) === -1
|
||||
) {
|
||||
warning(
|
||||
didWarnForAddedNewProperty || target.isPersistent(),
|
||||
"This synthetic event is reused for performance reasons. If you're " +
|
||||
"seeing this, you're adding a new property in the synthetic event object. " +
|
||||
'The property is never released. See ' +
|
||||
'https://fb.me/react-event-pooling for more information.',
|
||||
);
|
||||
didWarnForAddedNewProperty = true;
|
||||
}
|
||||
target[prop] = value;
|
||||
return true;
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
/*eslint-enable no-func-assign */
|
||||
}
|
||||
}
|
||||
|
||||
addEventPoolingTo(SyntheticEvent);
|
||||
|
||||
/**
|
||||
@@ -307,7 +284,7 @@ function getPooledWarningPropertyDefinition(propName, getVal) {
|
||||
|
||||
function warn(action, result) {
|
||||
const warningCondition = false;
|
||||
warning(
|
||||
warningWithoutStack(
|
||||
warningCondition,
|
||||
"This synthetic event is reused for performance reasons. If you're seeing this, " +
|
||||
"you're %s `%s` on a released/nullified synthetic event. %s. " +
|
||||
@@ -345,7 +322,7 @@ function releasePooledEvent(event) {
|
||||
const EventConstructor = this;
|
||||
invariant(
|
||||
event instanceof EventConstructor,
|
||||
'Trying to release an event instance into a pool of a different type.',
|
||||
'Trying to release an event instance into a pool of a different type.',
|
||||
);
|
||||
event.destructor();
|
||||
if (EventConstructor.eventPool.length < EVENT_POOL_SIZE) {
|
||||
|
||||
39
packages/events/TopLevelEventTypes.js
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
export type RNTopLevelEventType =
|
||||
| 'topMouseDown'
|
||||
| 'topMouseMove'
|
||||
| 'topMouseUp'
|
||||
| 'topScroll'
|
||||
| 'topSelectionChange'
|
||||
| 'topTouchCancel'
|
||||
| 'topTouchEnd'
|
||||
| 'topTouchMove'
|
||||
| 'topTouchStart';
|
||||
|
||||
export opaque type DOMTopLevelEventType = string;
|
||||
|
||||
// Do not uses the below two methods directly!
|
||||
// Instead use constants exported from DOMTopLevelEventTypes in ReactDOM.
|
||||
// (It is the only module that is allowed to access these methods.)
|
||||
|
||||
export function unsafeCastStringToDOMTopLevelType(
|
||||
topLevelType: string,
|
||||
): DOMTopLevelEventType {
|
||||
return topLevelType;
|
||||
}
|
||||
|
||||
export function unsafeCastDOMTopLevelTypeToString(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
): string {
|
||||
return topLevelType;
|
||||
}
|
||||
|
||||
export type TopLevelType = DOMTopLevelEventType | RNTopLevelEventType;
|
||||
@@ -7,7 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
/**
|
||||
* Accumulates items that must not be null or undefined.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
/**
|
||||
* Accumulates items that must not be null or undefined into the first one. This
|
||||
|
||||
41
packages/events/forks/ResponderTopLevelEventTypes.dom.js
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
// Note: ideally these would be imported from DOMTopLevelEventTypes,
|
||||
// but our build system currently doesn't let us do that from a fork.
|
||||
|
||||
export const TOP_TOUCH_START = 'touchstart';
|
||||
export const TOP_TOUCH_MOVE = 'touchmove';
|
||||
export const TOP_TOUCH_END = 'touchend';
|
||||
export const TOP_TOUCH_CANCEL = 'touchcancel';
|
||||
export const TOP_SCROLL = 'scroll';
|
||||
export const TOP_SELECTION_CHANGE = 'selectionchange';
|
||||
export const TOP_MOUSE_DOWN = 'mousedown';
|
||||
export const TOP_MOUSE_MOVE = 'mousemove';
|
||||
export const TOP_MOUSE_UP = 'mouseup';
|
||||
|
||||
export function isStartish(topLevelType: mixed): boolean {
|
||||
return topLevelType === TOP_TOUCH_START || topLevelType === TOP_MOUSE_DOWN;
|
||||
}
|
||||
|
||||
export function isMoveish(topLevelType: mixed): boolean {
|
||||
return topLevelType === TOP_TOUCH_MOVE || topLevelType === TOP_MOUSE_MOVE;
|
||||
}
|
||||
|
||||
export function isEndish(topLevelType: mixed): boolean {
|
||||
return (
|
||||
topLevelType === TOP_TOUCH_END ||
|
||||
topLevelType === TOP_TOUCH_CANCEL ||
|
||||
topLevelType === TOP_MOUSE_UP
|
||||
);
|
||||
}
|
||||
|
||||
export const startDependencies = [TOP_TOUCH_START, TOP_MOUSE_DOWN];
|
||||
export const moveDependencies = [TOP_TOUCH_MOVE, TOP_MOUSE_MOVE];
|
||||
export const endDependencies = [TOP_TOUCH_CANCEL, TOP_TOUCH_END, TOP_MOUSE_UP];
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "react-art",
|
||||
"description": "React ART is a JavaScript library for drawing vector graphics using React. It provides declarative and reactive bindings to the ART library. Using the same declarative API you can render the output to either Canvas, SVG or VML (IE8).",
|
||||
"version": "16.3.2",
|
||||
"version": "16.4.1",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
"keywords": [
|
||||
@@ -20,10 +20,9 @@
|
||||
"dependencies": {
|
||||
"art": "^0.10.1",
|
||||
"create-react-class": "^15.6.2",
|
||||
"fbjs": "^0.8.16",
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.0"
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
414
packages/react-art/src/ReactART.js
vendored
@@ -6,268 +6,19 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactFiberReconciler from 'react-reconciler';
|
||||
import * as ReactDOMFrameScheduling from 'shared/ReactDOMFrameScheduling';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
import * as ARTRenderer from 'react-reconciler/inline.art';
|
||||
import Transform from 'art/core/transform';
|
||||
import Mode from 'art/modes/current';
|
||||
import FastNoSideEffects from 'art/modes/fast-noSideEffects';
|
||||
import Transform from 'art/core/transform';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import emptyObject from 'fbjs/lib/emptyObject';
|
||||
|
||||
import {TYPES, childrenAsString} from './ReactARTInternals';
|
||||
|
||||
Mode.setCurrent(
|
||||
// Change to 'art/modes/dom' for easier debugging via SVG
|
||||
FastNoSideEffects,
|
||||
);
|
||||
|
||||
const pooledTransform = new Transform();
|
||||
|
||||
const EVENT_TYPES = {
|
||||
onClick: 'click',
|
||||
onMouseMove: 'mousemove',
|
||||
onMouseOver: 'mouseover',
|
||||
onMouseOut: 'mouseout',
|
||||
onMouseUp: 'mouseup',
|
||||
onMouseDown: 'mousedown',
|
||||
};
|
||||
|
||||
const TYPES = {
|
||||
CLIPPING_RECTANGLE: 'ClippingRectangle',
|
||||
GROUP: 'Group',
|
||||
SHAPE: 'Shape',
|
||||
TEXT: 'Text',
|
||||
};
|
||||
|
||||
const UPDATE_SIGNAL = {};
|
||||
|
||||
/** Helper Methods */
|
||||
|
||||
function addEventListeners(instance, type, listener) {
|
||||
// We need to explicitly unregister before unmount.
|
||||
// For this reason we need to track subscriptions.
|
||||
if (!instance._listeners) {
|
||||
instance._listeners = {};
|
||||
instance._subscriptions = {};
|
||||
}
|
||||
|
||||
instance._listeners[type] = listener;
|
||||
|
||||
if (listener) {
|
||||
if (!instance._subscriptions[type]) {
|
||||
instance._subscriptions[type] = instance.subscribe(
|
||||
type,
|
||||
createEventHandler(instance),
|
||||
instance,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (instance._subscriptions[type]) {
|
||||
instance._subscriptions[type]();
|
||||
delete instance._subscriptions[type];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function childrenAsString(children) {
|
||||
if (!children) {
|
||||
return '';
|
||||
} else if (typeof children === 'string') {
|
||||
return children;
|
||||
} else if (children.length) {
|
||||
return children.join('');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function createEventHandler(instance) {
|
||||
return function handleEvent(event) {
|
||||
const listener = instance._listeners[event.type];
|
||||
|
||||
if (!listener) {
|
||||
// Noop
|
||||
} else if (typeof listener === 'function') {
|
||||
listener.call(instance, event);
|
||||
} else if (listener.handleEvent) {
|
||||
listener.handleEvent(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function destroyEventListeners(instance) {
|
||||
if (instance._subscriptions) {
|
||||
for (let type in instance._subscriptions) {
|
||||
instance._subscriptions[type]();
|
||||
}
|
||||
}
|
||||
|
||||
instance._subscriptions = null;
|
||||
instance._listeners = null;
|
||||
}
|
||||
|
||||
function getScaleX(props) {
|
||||
if (props.scaleX != null) {
|
||||
return props.scaleX;
|
||||
} else if (props.scale != null) {
|
||||
return props.scale;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function getScaleY(props) {
|
||||
if (props.scaleY != null) {
|
||||
return props.scaleY;
|
||||
} else if (props.scale != null) {
|
||||
return props.scale;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function isSameFont(oldFont, newFont) {
|
||||
if (oldFont === newFont) {
|
||||
return true;
|
||||
} else if (typeof newFont === 'string' || typeof oldFont === 'string') {
|
||||
return false;
|
||||
} else {
|
||||
return (
|
||||
newFont.fontSize === oldFont.fontSize &&
|
||||
newFont.fontStyle === oldFont.fontStyle &&
|
||||
newFont.fontVariant === oldFont.fontVariant &&
|
||||
newFont.fontWeight === oldFont.fontWeight &&
|
||||
newFont.fontFamily === oldFont.fontFamily
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Render Methods */
|
||||
|
||||
function applyClippingRectangleProps(instance, props, prevProps = {}) {
|
||||
applyNodeProps(instance, props, prevProps);
|
||||
|
||||
instance.width = props.width;
|
||||
instance.height = props.height;
|
||||
}
|
||||
|
||||
function applyGroupProps(instance, props, prevProps = {}) {
|
||||
applyNodeProps(instance, props, prevProps);
|
||||
|
||||
instance.width = props.width;
|
||||
instance.height = props.height;
|
||||
}
|
||||
|
||||
function applyNodeProps(instance, props, prevProps = {}) {
|
||||
const scaleX = getScaleX(props);
|
||||
const scaleY = getScaleY(props);
|
||||
|
||||
pooledTransform
|
||||
.transformTo(1, 0, 0, 1, 0, 0)
|
||||
.move(props.x || 0, props.y || 0)
|
||||
.rotate(props.rotation || 0, props.originX, props.originY)
|
||||
.scale(scaleX, scaleY, props.originX, props.originY);
|
||||
|
||||
if (props.transform != null) {
|
||||
pooledTransform.transform(props.transform);
|
||||
}
|
||||
|
||||
if (
|
||||
instance.xx !== pooledTransform.xx ||
|
||||
instance.yx !== pooledTransform.yx ||
|
||||
instance.xy !== pooledTransform.xy ||
|
||||
instance.yy !== pooledTransform.yy ||
|
||||
instance.x !== pooledTransform.x ||
|
||||
instance.y !== pooledTransform.y
|
||||
) {
|
||||
instance.transformTo(pooledTransform);
|
||||
}
|
||||
|
||||
if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
|
||||
instance.indicate(props.cursor, props.title);
|
||||
}
|
||||
|
||||
if (instance.blend && props.opacity !== prevProps.opacity) {
|
||||
instance.blend(props.opacity == null ? 1 : props.opacity);
|
||||
}
|
||||
|
||||
if (props.visible !== prevProps.visible) {
|
||||
if (props.visible == null || props.visible) {
|
||||
instance.show();
|
||||
} else {
|
||||
instance.hide();
|
||||
}
|
||||
}
|
||||
|
||||
for (let type in EVENT_TYPES) {
|
||||
addEventListeners(instance, EVENT_TYPES[type], props[type]);
|
||||
}
|
||||
}
|
||||
|
||||
function applyRenderableNodeProps(instance, props, prevProps = {}) {
|
||||
applyNodeProps(instance, props, prevProps);
|
||||
|
||||
if (prevProps.fill !== props.fill) {
|
||||
if (props.fill && props.fill.applyFill) {
|
||||
props.fill.applyFill(instance);
|
||||
} else {
|
||||
instance.fill(props.fill);
|
||||
}
|
||||
}
|
||||
if (
|
||||
prevProps.stroke !== props.stroke ||
|
||||
prevProps.strokeWidth !== props.strokeWidth ||
|
||||
prevProps.strokeCap !== props.strokeCap ||
|
||||
prevProps.strokeJoin !== props.strokeJoin ||
|
||||
// TODO: Consider deep check of stokeDash; may benefit VML in IE.
|
||||
prevProps.strokeDash !== props.strokeDash
|
||||
) {
|
||||
instance.stroke(
|
||||
props.stroke,
|
||||
props.strokeWidth,
|
||||
props.strokeCap,
|
||||
props.strokeJoin,
|
||||
props.strokeDash,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function applyShapeProps(instance, props, prevProps = {}) {
|
||||
applyRenderableNodeProps(instance, props, prevProps);
|
||||
|
||||
const path = props.d || childrenAsString(props.children);
|
||||
|
||||
const prevDelta = instance._prevDelta;
|
||||
const prevPath = instance._prevPath;
|
||||
|
||||
if (
|
||||
path !== prevPath ||
|
||||
path.delta !== prevDelta ||
|
||||
prevProps.height !== props.height ||
|
||||
prevProps.width !== props.width
|
||||
) {
|
||||
instance.draw(path, props.width, props.height);
|
||||
|
||||
instance._prevDelta = path.delta;
|
||||
instance._prevPath = path;
|
||||
}
|
||||
}
|
||||
|
||||
function applyTextProps(instance, props, prevProps = {}) {
|
||||
applyRenderableNodeProps(instance, props, prevProps);
|
||||
|
||||
const string = props.children;
|
||||
|
||||
if (
|
||||
instance._currentString !== string ||
|
||||
!isSameFont(props.font, prevProps.font) ||
|
||||
props.alignment !== prevProps.alignment ||
|
||||
props.path !== prevProps.path
|
||||
) {
|
||||
instance.draw(string, props.font, props.alignment, props.path);
|
||||
|
||||
instance._currentString = string;
|
||||
}
|
||||
}
|
||||
|
||||
/** Declarative fill-type objects; API design not finalized */
|
||||
|
||||
const slice = Array.prototype.slice;
|
||||
@@ -381,156 +132,11 @@ class Text extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
/** ART Renderer */
|
||||
|
||||
const ARTRenderer = ReactFiberReconciler({
|
||||
appendInitialChild(parentInstance, child) {
|
||||
if (typeof child === 'string') {
|
||||
// Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
|
||||
invariant(false, 'Text children should already be flattened.');
|
||||
return;
|
||||
}
|
||||
|
||||
child.inject(parentInstance);
|
||||
},
|
||||
|
||||
createInstance(type, props, internalInstanceHandle) {
|
||||
let instance;
|
||||
|
||||
switch (type) {
|
||||
case TYPES.CLIPPING_RECTANGLE:
|
||||
instance = Mode.ClippingRectangle();
|
||||
instance._applyProps = applyClippingRectangleProps;
|
||||
break;
|
||||
case TYPES.GROUP:
|
||||
instance = Mode.Group();
|
||||
instance._applyProps = applyGroupProps;
|
||||
break;
|
||||
case TYPES.SHAPE:
|
||||
instance = Mode.Shape();
|
||||
instance._applyProps = applyShapeProps;
|
||||
break;
|
||||
case TYPES.TEXT:
|
||||
instance = Mode.Text(
|
||||
props.children,
|
||||
props.font,
|
||||
props.alignment,
|
||||
props.path,
|
||||
);
|
||||
instance._applyProps = applyTextProps;
|
||||
break;
|
||||
}
|
||||
|
||||
invariant(instance, 'ReactART does not support the type "%s"', type);
|
||||
|
||||
instance._applyProps(instance, props);
|
||||
|
||||
return instance;
|
||||
},
|
||||
|
||||
createTextInstance(text, rootContainerInstance, internalInstanceHandle) {
|
||||
return text;
|
||||
},
|
||||
|
||||
finalizeInitialChildren(domElement, type, props) {
|
||||
return false;
|
||||
},
|
||||
|
||||
getPublicInstance(instance) {
|
||||
return instance;
|
||||
},
|
||||
|
||||
prepareForCommit() {
|
||||
// Noop
|
||||
},
|
||||
|
||||
prepareUpdate(domElement, type, oldProps, newProps) {
|
||||
return UPDATE_SIGNAL;
|
||||
},
|
||||
|
||||
resetAfterCommit() {
|
||||
// Noop
|
||||
},
|
||||
|
||||
resetTextContent(domElement) {
|
||||
// Noop
|
||||
},
|
||||
|
||||
shouldDeprioritizeSubtree(type, props) {
|
||||
return false;
|
||||
},
|
||||
|
||||
getRootHostContext() {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getChildHostContext() {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
|
||||
|
||||
shouldSetTextContent(type, props) {
|
||||
return (
|
||||
typeof props.children === 'string' || typeof props.children === 'number'
|
||||
);
|
||||
},
|
||||
|
||||
now: ReactDOMFrameScheduling.now,
|
||||
|
||||
mutation: {
|
||||
appendChild(parentInstance, child) {
|
||||
if (child.parentNode === parentInstance) {
|
||||
child.eject();
|
||||
}
|
||||
child.inject(parentInstance);
|
||||
},
|
||||
|
||||
appendChildToContainer(parentInstance, child) {
|
||||
if (child.parentNode === parentInstance) {
|
||||
child.eject();
|
||||
}
|
||||
child.inject(parentInstance);
|
||||
},
|
||||
|
||||
insertBefore(parentInstance, child, beforeChild) {
|
||||
invariant(
|
||||
child !== beforeChild,
|
||||
'ReactART: Can not insert node before itself',
|
||||
);
|
||||
child.injectBefore(beforeChild);
|
||||
},
|
||||
|
||||
insertInContainerBefore(parentInstance, child, beforeChild) {
|
||||
invariant(
|
||||
child !== beforeChild,
|
||||
'ReactART: Can not insert node before itself',
|
||||
);
|
||||
child.injectBefore(beforeChild);
|
||||
},
|
||||
|
||||
removeChild(parentInstance, child) {
|
||||
destroyEventListeners(child);
|
||||
child.eject();
|
||||
},
|
||||
|
||||
removeChildFromContainer(parentInstance, child) {
|
||||
destroyEventListeners(child);
|
||||
child.eject();
|
||||
},
|
||||
|
||||
commitTextUpdate(textInstance, oldText, newText) {
|
||||
// Noop
|
||||
},
|
||||
|
||||
commitMount(instance, type, newProps) {
|
||||
// Noop
|
||||
},
|
||||
|
||||
commitUpdate(instance, updatePayload, type, oldProps, newProps) {
|
||||
instance._applyProps(instance, newProps, oldProps);
|
||||
},
|
||||
},
|
||||
ARTRenderer.injectIntoDevTools({
|
||||
findFiberByHostInstance: () => null,
|
||||
bundleType: __DEV__ ? 1 : 0,
|
||||
version: ReactVersion,
|
||||
rendererPackageName: 'react-art',
|
||||
});
|
||||
|
||||
/** API */
|
||||
|
||||
408
packages/react-art/src/ReactARTHostConfig.js
vendored
Normal file
@@ -0,0 +1,408 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as ReactScheduler from 'shared/ReactScheduler';
|
||||
import Transform from 'art/core/transform';
|
||||
import Mode from 'art/modes/current';
|
||||
import invariant from 'shared/invariant';
|
||||
|
||||
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
|
||||
|
||||
const pooledTransform = new Transform();
|
||||
|
||||
const NO_CONTEXT = {};
|
||||
const UPDATE_SIGNAL = {};
|
||||
if (__DEV__) {
|
||||
Object.freeze(NO_CONTEXT);
|
||||
Object.freeze(UPDATE_SIGNAL);
|
||||
}
|
||||
|
||||
/** Helper Methods */
|
||||
|
||||
function addEventListeners(instance, type, listener) {
|
||||
// We need to explicitly unregister before unmount.
|
||||
// For this reason we need to track subscriptions.
|
||||
if (!instance._listeners) {
|
||||
instance._listeners = {};
|
||||
instance._subscriptions = {};
|
||||
}
|
||||
|
||||
instance._listeners[type] = listener;
|
||||
|
||||
if (listener) {
|
||||
if (!instance._subscriptions[type]) {
|
||||
instance._subscriptions[type] = instance.subscribe(
|
||||
type,
|
||||
createEventHandler(instance),
|
||||
instance,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (instance._subscriptions[type]) {
|
||||
instance._subscriptions[type]();
|
||||
delete instance._subscriptions[type];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function createEventHandler(instance) {
|
||||
return function handleEvent(event) {
|
||||
const listener = instance._listeners[event.type];
|
||||
|
||||
if (!listener) {
|
||||
// Noop
|
||||
} else if (typeof listener === 'function') {
|
||||
listener.call(instance, event);
|
||||
} else if (listener.handleEvent) {
|
||||
listener.handleEvent(event);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function destroyEventListeners(instance) {
|
||||
if (instance._subscriptions) {
|
||||
for (let type in instance._subscriptions) {
|
||||
instance._subscriptions[type]();
|
||||
}
|
||||
}
|
||||
|
||||
instance._subscriptions = null;
|
||||
instance._listeners = null;
|
||||
}
|
||||
|
||||
function getScaleX(props) {
|
||||
if (props.scaleX != null) {
|
||||
return props.scaleX;
|
||||
} else if (props.scale != null) {
|
||||
return props.scale;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function getScaleY(props) {
|
||||
if (props.scaleY != null) {
|
||||
return props.scaleY;
|
||||
} else if (props.scale != null) {
|
||||
return props.scale;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
function isSameFont(oldFont, newFont) {
|
||||
if (oldFont === newFont) {
|
||||
return true;
|
||||
} else if (typeof newFont === 'string' || typeof oldFont === 'string') {
|
||||
return false;
|
||||
} else {
|
||||
return (
|
||||
newFont.fontSize === oldFont.fontSize &&
|
||||
newFont.fontStyle === oldFont.fontStyle &&
|
||||
newFont.fontVariant === oldFont.fontVariant &&
|
||||
newFont.fontWeight === oldFont.fontWeight &&
|
||||
newFont.fontFamily === oldFont.fontFamily
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/** Render Methods */
|
||||
|
||||
function applyClippingRectangleProps(instance, props, prevProps = {}) {
|
||||
applyNodeProps(instance, props, prevProps);
|
||||
|
||||
instance.width = props.width;
|
||||
instance.height = props.height;
|
||||
}
|
||||
|
||||
function applyGroupProps(instance, props, prevProps = {}) {
|
||||
applyNodeProps(instance, props, prevProps);
|
||||
|
||||
instance.width = props.width;
|
||||
instance.height = props.height;
|
||||
}
|
||||
|
||||
function applyNodeProps(instance, props, prevProps = {}) {
|
||||
const scaleX = getScaleX(props);
|
||||
const scaleY = getScaleY(props);
|
||||
|
||||
pooledTransform
|
||||
.transformTo(1, 0, 0, 1, 0, 0)
|
||||
.move(props.x || 0, props.y || 0)
|
||||
.rotate(props.rotation || 0, props.originX, props.originY)
|
||||
.scale(scaleX, scaleY, props.originX, props.originY);
|
||||
|
||||
if (props.transform != null) {
|
||||
pooledTransform.transform(props.transform);
|
||||
}
|
||||
|
||||
if (
|
||||
instance.xx !== pooledTransform.xx ||
|
||||
instance.yx !== pooledTransform.yx ||
|
||||
instance.xy !== pooledTransform.xy ||
|
||||
instance.yy !== pooledTransform.yy ||
|
||||
instance.x !== pooledTransform.x ||
|
||||
instance.y !== pooledTransform.y
|
||||
) {
|
||||
instance.transformTo(pooledTransform);
|
||||
}
|
||||
|
||||
if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
|
||||
instance.indicate(props.cursor, props.title);
|
||||
}
|
||||
|
||||
if (instance.blend && props.opacity !== prevProps.opacity) {
|
||||
instance.blend(props.opacity == null ? 1 : props.opacity);
|
||||
}
|
||||
|
||||
if (props.visible !== prevProps.visible) {
|
||||
if (props.visible == null || props.visible) {
|
||||
instance.show();
|
||||
} else {
|
||||
instance.hide();
|
||||
}
|
||||
}
|
||||
|
||||
for (let type in EVENT_TYPES) {
|
||||
addEventListeners(instance, EVENT_TYPES[type], props[type]);
|
||||
}
|
||||
}
|
||||
|
||||
function applyRenderableNodeProps(instance, props, prevProps = {}) {
|
||||
applyNodeProps(instance, props, prevProps);
|
||||
|
||||
if (prevProps.fill !== props.fill) {
|
||||
if (props.fill && props.fill.applyFill) {
|
||||
props.fill.applyFill(instance);
|
||||
} else {
|
||||
instance.fill(props.fill);
|
||||
}
|
||||
}
|
||||
if (
|
||||
prevProps.stroke !== props.stroke ||
|
||||
prevProps.strokeWidth !== props.strokeWidth ||
|
||||
prevProps.strokeCap !== props.strokeCap ||
|
||||
prevProps.strokeJoin !== props.strokeJoin ||
|
||||
// TODO: Consider deep check of stokeDash; may benefit VML in IE.
|
||||
prevProps.strokeDash !== props.strokeDash
|
||||
) {
|
||||
instance.stroke(
|
||||
props.stroke,
|
||||
props.strokeWidth,
|
||||
props.strokeCap,
|
||||
props.strokeJoin,
|
||||
props.strokeDash,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function applyShapeProps(instance, props, prevProps = {}) {
|
||||
applyRenderableNodeProps(instance, props, prevProps);
|
||||
|
||||
const path = props.d || childrenAsString(props.children);
|
||||
|
||||
const prevDelta = instance._prevDelta;
|
||||
const prevPath = instance._prevPath;
|
||||
|
||||
if (
|
||||
path !== prevPath ||
|
||||
path.delta !== prevDelta ||
|
||||
prevProps.height !== props.height ||
|
||||
prevProps.width !== props.width
|
||||
) {
|
||||
instance.draw(path, props.width, props.height);
|
||||
|
||||
instance._prevDelta = path.delta;
|
||||
instance._prevPath = path;
|
||||
}
|
||||
}
|
||||
|
||||
function applyTextProps(instance, props, prevProps = {}) {
|
||||
applyRenderableNodeProps(instance, props, prevProps);
|
||||
|
||||
const string = props.children;
|
||||
|
||||
if (
|
||||
instance._currentString !== string ||
|
||||
!isSameFont(props.font, prevProps.font) ||
|
||||
props.alignment !== prevProps.alignment ||
|
||||
props.path !== prevProps.path
|
||||
) {
|
||||
instance.draw(string, props.font, props.alignment, props.path);
|
||||
|
||||
instance._currentString = string;
|
||||
}
|
||||
}
|
||||
|
||||
export * from 'shared/HostConfigWithNoPersistence';
|
||||
export * from 'shared/HostConfigWithNoHydration';
|
||||
|
||||
export function appendInitialChild(parentInstance, child) {
|
||||
if (typeof child === 'string') {
|
||||
// Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
|
||||
invariant(false, 'Text children should already be flattened.');
|
||||
return;
|
||||
}
|
||||
|
||||
child.inject(parentInstance);
|
||||
}
|
||||
|
||||
export function createInstance(type, props, internalInstanceHandle) {
|
||||
let instance;
|
||||
|
||||
switch (type) {
|
||||
case TYPES.CLIPPING_RECTANGLE:
|
||||
instance = Mode.ClippingRectangle();
|
||||
instance._applyProps = applyClippingRectangleProps;
|
||||
break;
|
||||
case TYPES.GROUP:
|
||||
instance = Mode.Group();
|
||||
instance._applyProps = applyGroupProps;
|
||||
break;
|
||||
case TYPES.SHAPE:
|
||||
instance = Mode.Shape();
|
||||
instance._applyProps = applyShapeProps;
|
||||
break;
|
||||
case TYPES.TEXT:
|
||||
instance = Mode.Text(
|
||||
props.children,
|
||||
props.font,
|
||||
props.alignment,
|
||||
props.path,
|
||||
);
|
||||
instance._applyProps = applyTextProps;
|
||||
break;
|
||||
}
|
||||
|
||||
invariant(instance, 'ReactART does not support the type "%s"', type);
|
||||
|
||||
instance._applyProps(instance, props);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
export function createTextInstance(
|
||||
text,
|
||||
rootContainerInstance,
|
||||
internalInstanceHandle,
|
||||
) {
|
||||
return text;
|
||||
}
|
||||
|
||||
export function finalizeInitialChildren(domElement, type, props) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getPublicInstance(instance) {
|
||||
return instance;
|
||||
}
|
||||
|
||||
export function prepareForCommit() {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function prepareUpdate(domElement, type, oldProps, newProps) {
|
||||
return UPDATE_SIGNAL;
|
||||
}
|
||||
|
||||
export function resetAfterCommit() {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function resetTextContent(domElement) {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function shouldDeprioritizeSubtree(type, props) {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getRootHostContext() {
|
||||
return NO_CONTEXT;
|
||||
}
|
||||
|
||||
export function getChildHostContext() {
|
||||
return NO_CONTEXT;
|
||||
}
|
||||
|
||||
export const scheduleDeferredCallback = ReactScheduler.scheduleWork;
|
||||
export const cancelDeferredCallback = ReactScheduler.cancelScheduledWork;
|
||||
|
||||
export const scheduleTimeout = setTimeout;
|
||||
export const cancelTimeout = clearTimeout;
|
||||
export const noTimeout = -1;
|
||||
|
||||
export function shouldSetTextContent(type, props) {
|
||||
return (
|
||||
typeof props.children === 'string' || typeof props.children === 'number'
|
||||
);
|
||||
}
|
||||
|
||||
export const now = ReactScheduler.now;
|
||||
|
||||
// The ART renderer is secondary to the React DOM renderer.
|
||||
export const isPrimaryRenderer = false;
|
||||
|
||||
export const supportsMutation = true;
|
||||
|
||||
export function appendChild(parentInstance, child) {
|
||||
if (child.parentNode === parentInstance) {
|
||||
child.eject();
|
||||
}
|
||||
child.inject(parentInstance);
|
||||
}
|
||||
|
||||
export function appendChildToContainer(parentInstance, child) {
|
||||
if (child.parentNode === parentInstance) {
|
||||
child.eject();
|
||||
}
|
||||
child.inject(parentInstance);
|
||||
}
|
||||
|
||||
export function insertBefore(parentInstance, child, beforeChild) {
|
||||
invariant(
|
||||
child !== beforeChild,
|
||||
'ReactART: Can not insert node before itself',
|
||||
);
|
||||
child.injectBefore(beforeChild);
|
||||
}
|
||||
|
||||
export function insertInContainerBefore(parentInstance, child, beforeChild) {
|
||||
invariant(
|
||||
child !== beforeChild,
|
||||
'ReactART: Can not insert node before itself',
|
||||
);
|
||||
child.injectBefore(beforeChild);
|
||||
}
|
||||
|
||||
export function removeChild(parentInstance, child) {
|
||||
destroyEventListeners(child);
|
||||
child.eject();
|
||||
}
|
||||
|
||||
export function removeChildFromContainer(parentInstance, child) {
|
||||
destroyEventListeners(child);
|
||||
child.eject();
|
||||
}
|
||||
|
||||
export function commitTextUpdate(textInstance, oldText, newText) {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function commitMount(instance, type, newProps) {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function commitUpdate(
|
||||
instance,
|
||||
updatePayload,
|
||||
type,
|
||||
oldProps,
|
||||
newProps,
|
||||
) {
|
||||
instance._applyProps(instance, newProps, oldProps);
|
||||
}
|
||||
34
packages/react-art/src/ReactARTInternals.js
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export const TYPES = {
|
||||
CLIPPING_RECTANGLE: 'ClippingRectangle',
|
||||
GROUP: 'Group',
|
||||
SHAPE: 'Shape',
|
||||
TEXT: 'Text',
|
||||
};
|
||||
|
||||
export const EVENT_TYPES = {
|
||||
onClick: 'click',
|
||||
onMouseMove: 'mousemove',
|
||||
onMouseOver: 'mouseover',
|
||||
onMouseOut: 'mouseout',
|
||||
onMouseUp: 'mouseup',
|
||||
onMouseDown: 'mousedown',
|
||||
};
|
||||
|
||||
export function childrenAsString(children) {
|
||||
if (!children) {
|
||||
return '';
|
||||
} else if (typeof children === 'string') {
|
||||
return children;
|
||||
} else if (children.length) {
|
||||
return children.join('');
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
115
packages/react-art/src/__tests__/ReactART-test.js
vendored
@@ -15,6 +15,23 @@ const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const ReactTestUtils = require('react-dom/test-utils');
|
||||
|
||||
// Isolate test renderer.
|
||||
jest.resetModules();
|
||||
const ReactTestRenderer = require('react-test-renderer');
|
||||
|
||||
// Isolate ART renderer.
|
||||
jest.resetModules();
|
||||
const ReactART = require('react-art');
|
||||
const ARTSVGMode = require('art/modes/svg');
|
||||
const ARTCurrentMode = require('art/modes/current');
|
||||
const Circle = require('react-art/Circle');
|
||||
const Rectangle = require('react-art/Rectangle');
|
||||
const Wedge = require('react-art/Wedge');
|
||||
|
||||
// Isolate the noop renderer
|
||||
jest.resetModules();
|
||||
const ReactNoop = require('react-noop-renderer');
|
||||
|
||||
let Group;
|
||||
let Shape;
|
||||
let Surface;
|
||||
@@ -22,15 +39,6 @@ let TestComponent;
|
||||
|
||||
const Missing = {};
|
||||
|
||||
const ReactART = require('react-art');
|
||||
const ARTSVGMode = require('art/modes/svg');
|
||||
const ARTCurrentMode = require('art/modes/current');
|
||||
|
||||
const renderer = require('react-test-renderer');
|
||||
const Circle = require('react-art/Circle');
|
||||
const Rectangle = require('react-art/Rectangle');
|
||||
const Wedge = require('react-art/Wedge');
|
||||
|
||||
function testDOMNodeStructure(domNode, expectedStructure) {
|
||||
expect(domNode).toBeDefined();
|
||||
expect(domNode.nodeName).toBe(expectedStructure.nodeName);
|
||||
@@ -67,6 +75,8 @@ describe('ReactART', () => {
|
||||
Surface = ReactART.Surface;
|
||||
|
||||
TestComponent = class extends React.Component {
|
||||
group = React.createRef();
|
||||
|
||||
render() {
|
||||
const a = (
|
||||
<Shape
|
||||
@@ -100,7 +110,7 @@ describe('ReactART', () => {
|
||||
|
||||
return (
|
||||
<Surface width={150} height={200}>
|
||||
<Group ref="group">
|
||||
<Group ref={this.group}>
|
||||
{this.props.flipped ? [b, a, c] : [a, b, c]}
|
||||
</Group>
|
||||
</Surface>
|
||||
@@ -117,7 +127,7 @@ describe('ReactART', () => {
|
||||
it('should have the correct lifecycle state', () => {
|
||||
let instance = <TestComponent />;
|
||||
instance = ReactTestUtils.renderIntoDocument(instance);
|
||||
const group = instance.refs.group;
|
||||
const group = instance.group.current;
|
||||
// Duck type test for an ART group
|
||||
expect(typeof group.indicate).toBe('function');
|
||||
});
|
||||
@@ -256,15 +266,17 @@ describe('ReactART', () => {
|
||||
let ref = null;
|
||||
|
||||
class Outer extends React.Component {
|
||||
test = React.createRef();
|
||||
|
||||
componentDidMount() {
|
||||
ref = this.refs.test;
|
||||
ref = this.test.current;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Surface>
|
||||
<Group>
|
||||
<CustomShape ref="test" />
|
||||
<CustomShape ref={this.test} />
|
||||
</Group>
|
||||
</Surface>
|
||||
);
|
||||
@@ -285,26 +297,28 @@ describe('ReactART', () => {
|
||||
let ref = {};
|
||||
|
||||
class Outer extends React.Component {
|
||||
test = React.createRef();
|
||||
|
||||
componentDidMount() {
|
||||
ref = this.refs.test;
|
||||
ref = this.test.current;
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
ref = this.refs.test;
|
||||
ref = this.test.current;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Surface>
|
||||
<Group>
|
||||
{this.props.mountCustomShape && <CustomShape ref="test" />}
|
||||
{this.props.mountCustomShape && <CustomShape ref={this.test} />}
|
||||
</Group>
|
||||
</Surface>
|
||||
);
|
||||
}
|
||||
}
|
||||
ReactDOM.render(<Outer />, container);
|
||||
expect(ref).not.toBeDefined();
|
||||
expect(ref).toBe(null);
|
||||
ReactDOM.render(<Outer mountCustomShape={true} />, container);
|
||||
expect(ref.constructor).toBe(CustomShape);
|
||||
});
|
||||
@@ -339,11 +353,62 @@ describe('ReactART', () => {
|
||||
doClick(instance);
|
||||
expect(onClick2).toBeCalled();
|
||||
});
|
||||
|
||||
it('can concurrently render with a "primary" renderer while sharing context', () => {
|
||||
const CurrentRendererContext = React.createContext(null);
|
||||
|
||||
function Yield(props) {
|
||||
ReactNoop.yield(props.value);
|
||||
return null;
|
||||
}
|
||||
|
||||
let ops = [];
|
||||
function LogCurrentRenderer() {
|
||||
return (
|
||||
<CurrentRendererContext.Consumer>
|
||||
{currentRenderer => {
|
||||
ops.push(currentRenderer);
|
||||
return null;
|
||||
}}
|
||||
</CurrentRendererContext.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
// Using test renderer instead of the DOM renderer here because async
|
||||
// testing APIs for the DOM renderer don't exist.
|
||||
ReactNoop.render(
|
||||
<CurrentRendererContext.Provider value="Test">
|
||||
<Yield value="A" />
|
||||
<Yield value="B" />
|
||||
<LogCurrentRenderer />
|
||||
<Yield value="C" />
|
||||
</CurrentRendererContext.Provider>,
|
||||
);
|
||||
|
||||
ReactNoop.flushThrough(['A']);
|
||||
|
||||
ReactDOM.render(
|
||||
<Surface>
|
||||
<LogCurrentRenderer />
|
||||
<CurrentRendererContext.Provider value="ART">
|
||||
<LogCurrentRenderer />
|
||||
</CurrentRendererContext.Provider>
|
||||
</Surface>,
|
||||
container,
|
||||
);
|
||||
|
||||
expect(ops).toEqual([null, 'ART']);
|
||||
|
||||
ops = [];
|
||||
expect(ReactNoop.flush()).toEqual(['B', 'C']);
|
||||
|
||||
expect(ops).toEqual(['Test']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ReactARTComponents', () => {
|
||||
it('should generate a <Shape> with props for drawing the Circle', () => {
|
||||
const circle = renderer.create(
|
||||
const circle = ReactTestRenderer.create(
|
||||
<Circle radius={10} stroke="green" strokeWidth={3} fill="blue" />,
|
||||
);
|
||||
expect(circle.toJSON()).toMatchSnapshot();
|
||||
@@ -351,7 +416,9 @@ describe('ReactARTComponents', () => {
|
||||
|
||||
it('should warn if radius is missing on a Circle component', () => {
|
||||
expect(() =>
|
||||
renderer.create(<Circle stroke="green" strokeWidth={3} fill="blue" />),
|
||||
ReactTestRenderer.create(
|
||||
<Circle stroke="green" strokeWidth={3} fill="blue" />,
|
||||
),
|
||||
).toWarnDev(
|
||||
'Warning: Failed prop type: The prop `radius` is marked as required in `Circle`, ' +
|
||||
'but its value is `undefined`.' +
|
||||
@@ -360,7 +427,7 @@ describe('ReactARTComponents', () => {
|
||||
});
|
||||
|
||||
it('should generate a <Shape> with props for drawing the Rectangle', () => {
|
||||
const rectangle = renderer.create(
|
||||
const rectangle = ReactTestRenderer.create(
|
||||
<Rectangle width={50} height={50} stroke="green" fill="blue" />,
|
||||
);
|
||||
expect(rectangle.toJSON()).toMatchSnapshot();
|
||||
@@ -368,7 +435,7 @@ describe('ReactARTComponents', () => {
|
||||
|
||||
it('should warn if width/height is missing on a Rectangle component', () => {
|
||||
expect(() =>
|
||||
renderer.create(<Rectangle stroke="green" fill="blue" />),
|
||||
ReactTestRenderer.create(<Rectangle stroke="green" fill="blue" />),
|
||||
).toWarnDev([
|
||||
'Warning: Failed prop type: The prop `width` is marked as required in `Rectangle`, ' +
|
||||
'but its value is `undefined`.' +
|
||||
@@ -380,21 +447,21 @@ describe('ReactARTComponents', () => {
|
||||
});
|
||||
|
||||
it('should generate a <Shape> with props for drawing the Wedge', () => {
|
||||
const wedge = renderer.create(
|
||||
const wedge = ReactTestRenderer.create(
|
||||
<Wedge outerRadius={50} startAngle={0} endAngle={360} fill="blue" />,
|
||||
);
|
||||
expect(wedge.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should return null if startAngle equals to endAngle on Wedge', () => {
|
||||
const wedge = renderer.create(
|
||||
const wedge = ReactTestRenderer.create(
|
||||
<Wedge outerRadius={50} startAngle={0} endAngle={0} fill="blue" />,
|
||||
);
|
||||
expect(wedge.toJSON()).toBeNull();
|
||||
});
|
||||
|
||||
it('should warn if outerRadius/startAngle/endAngle is missing on a Wedge component', () => {
|
||||
expect(() => renderer.create(<Wedge fill="blue" />)).toWarnDev([
|
||||
expect(() => ReactTestRenderer.create(<Wedge fill="blue" />)).toWarnDev([
|
||||
'Warning: Failed prop type: The prop `outerRadius` is marked as required in `Wedge`, ' +
|
||||
'but its value is `undefined`.' +
|
||||
'\n in Wedge (at **)',
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# react-call-return
|
||||
|
||||
This is an experimental package for multi-pass rendering in React.
|
||||
|
||||
**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**
|
||||
|
||||
**Use it at your own risk.**
|
||||
|
||||
# No, Really, It Is Unstable
|
||||
|
||||
This is **an experiment**.
|
||||
|
||||
We **will** replace this with a different API in the future.
|
||||
It can break between patch versions of React.
|
||||
|
||||
We also know that **it has bugs**.
|
||||
|
||||
Don't rely on this for anything except experiments.
|
||||
Even in experiments, make sure to lock the versions so that an update doesn't break your app.
|
||||
|
||||
Don't publish third party components relying on this unless you clearly mark them as experimental too.
|
||||
They will break.
|
||||
|
||||
Have fun! Let us know if you find interesting use cases for it.
|
||||
|
||||
# API
|
||||
|
||||
See the test case in `src/__tests__/ReactCallReturn.js` for an example.
|
||||
|
||||
# What and Why
|
||||
|
||||
The API is not very intuitive right now, but [this is a good overview](https://cdb.reacttraining.com/react-call-return-what-and-why-7e7761f81843) of why it might be useful in some cases. We are very open to better API ideas for this concept.
|
||||
7
packages/react-call-return/npm/index.js
vendored
@@ -1,7 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-call-return.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-call-return.development.js');
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
"name": "react-call-return",
|
||||
"description": "Experimental APIs for multi-pass rendering in React.",
|
||||
"version": "0.8.0",
|
||||
"repository": "facebook/react",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"index.js",
|
||||
"cjs/"
|
||||
],
|
||||
"dependencies": {
|
||||
"fbjs": "^0.8.16",
|
||||
"object-assign": "^4.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2014-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {
|
||||
REACT_CALL_TYPE,
|
||||
REACT_RETURN_TYPE,
|
||||
REACT_ELEMENT_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
|
||||
import type {ReactCall, ReactNodeList, ReactReturn} from 'shared/ReactTypes';
|
||||
|
||||
type CallHandler<T, V> = (props: T, returns: Array<V>) => ReactNodeList;
|
||||
|
||||
export function unstable_createCall<T, V>(
|
||||
children: ReactNodeList,
|
||||
handler: CallHandler<T, V>,
|
||||
props: T,
|
||||
key: ?string = null,
|
||||
): ReactCall<V> {
|
||||
const call = {
|
||||
// This tag allow us to uniquely identify this as a React Call
|
||||
$$typeof: REACT_ELEMENT_TYPE,
|
||||
type: REACT_CALL_TYPE,
|
||||
key: key == null ? null : '' + key,
|
||||
ref: null,
|
||||
props: {
|
||||
props,
|
||||
handler,
|
||||
children: children,
|
||||
},
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
// TODO: Add _store property for marking this as validated.
|
||||
if (Object.freeze) {
|
||||
Object.freeze(call.props);
|
||||
Object.freeze(call);
|
||||
}
|
||||
}
|
||||
|
||||
return call;
|
||||
}
|
||||
|
||||
export function unstable_createReturn<V>(value: V): ReactReturn<V> {
|
||||
const returnNode = {
|
||||
// This tag allow us to uniquely identify this as a React Call
|
||||
$$typeof: REACT_ELEMENT_TYPE,
|
||||
type: REACT_RETURN_TYPE,
|
||||
key: null,
|
||||
ref: null,
|
||||
props: {
|
||||
value,
|
||||
},
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
// TODO: Add _store property for marking this as validated.
|
||||
if (Object.freeze) {
|
||||
Object.freeze(returnNode);
|
||||
}
|
||||
}
|
||||
|
||||
return returnNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the object is a call object.
|
||||
*/
|
||||
export function unstable_isCall(object: mixed): boolean {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
object !== null &&
|
||||
object.type === REACT_CALL_TYPE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the object is a return object.
|
||||
*/
|
||||
export function unstable_isReturn(object: mixed): boolean {
|
||||
return (
|
||||
typeof object === 'object' &&
|
||||
object !== null &&
|
||||
object.type === REACT_RETURN_TYPE
|
||||
);
|
||||
}
|
||||
|
||||
export const unstable_REACT_RETURN_TYPE = REACT_RETURN_TYPE;
|
||||
export const unstable_REACT_CALL_TYPE = REACT_CALL_TYPE;
|
||||
@@ -1,326 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2013-present, Facebook, Inc.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactFeatureFlags;
|
||||
let ReactNoop;
|
||||
let ReactCallReturn;
|
||||
|
||||
describe('ReactCallReturn', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
ReactCallReturn = require('react-call-return');
|
||||
});
|
||||
|
||||
function div(...children) {
|
||||
children = children.map(c => (typeof c === 'string' ? {text: c} : c));
|
||||
return {type: 'div', children, prop: undefined};
|
||||
}
|
||||
|
||||
function span(prop) {
|
||||
return {type: 'span', children: [], prop};
|
||||
}
|
||||
|
||||
it('should render a call', () => {
|
||||
const ops = [];
|
||||
|
||||
function Continuation({isSame}) {
|
||||
ops.push(['Continuation', isSame]);
|
||||
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
|
||||
}
|
||||
|
||||
// An alternative API could mark Continuation as something that needs
|
||||
// returning. E.g. Continuation.returnType = 123;
|
||||
function Child({bar}) {
|
||||
ops.push(['Child', bar]);
|
||||
return ReactCallReturn.unstable_createReturn({
|
||||
props: {
|
||||
bar: bar,
|
||||
},
|
||||
continuation: Continuation,
|
||||
});
|
||||
}
|
||||
|
||||
function Indirection() {
|
||||
ops.push('Indirection');
|
||||
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
|
||||
}
|
||||
|
||||
function HandleReturns(props, returns) {
|
||||
ops.push('HandleReturns');
|
||||
return returns.map((y, i) => (
|
||||
<y.continuation key={i} isSame={props.foo === y.props.bar} />
|
||||
));
|
||||
}
|
||||
|
||||
// An alternative API could mark Parent as something that needs
|
||||
// returning. E.g. Parent.handler = HandleReturns;
|
||||
function Parent(props) {
|
||||
ops.push('Parent');
|
||||
return ReactCallReturn.unstable_createCall(
|
||||
props.children,
|
||||
HandleReturns,
|
||||
props,
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div>
|
||||
<Parent foo={true}>
|
||||
<Indirection />
|
||||
</Parent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'Parent',
|
||||
'Indirection',
|
||||
['Child', true],
|
||||
// Return
|
||||
['Child', false],
|
||||
// Return
|
||||
'HandleReturns',
|
||||
// Call continuations
|
||||
['Continuation', true],
|
||||
['Continuation', false],
|
||||
]);
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
div(span('foo==bar'), span('foo!=bar')),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update a call', () => {
|
||||
function Continuation({isSame}) {
|
||||
return <span prop={isSame ? 'foo==bar' : 'foo!=bar'} />;
|
||||
}
|
||||
|
||||
function Child({bar}) {
|
||||
return ReactCallReturn.unstable_createReturn({
|
||||
props: {
|
||||
bar: bar,
|
||||
},
|
||||
continuation: Continuation,
|
||||
});
|
||||
}
|
||||
|
||||
function Indirection() {
|
||||
return [<Child key="a" bar={true} />, <Child key="b" bar={false} />];
|
||||
}
|
||||
|
||||
function HandleReturns(props, returns) {
|
||||
return returns.map((y, i) => (
|
||||
<y.continuation key={i} isSame={props.foo === y.props.bar} />
|
||||
));
|
||||
}
|
||||
|
||||
function Parent(props) {
|
||||
return ReactCallReturn.unstable_createCall(
|
||||
props.children,
|
||||
HandleReturns,
|
||||
props,
|
||||
);
|
||||
}
|
||||
|
||||
function App(props) {
|
||||
return (
|
||||
<div>
|
||||
<Parent foo={props.foo}>
|
||||
<Indirection />
|
||||
</Parent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<App foo={true} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
div(span('foo==bar'), span('foo!=bar')),
|
||||
]);
|
||||
|
||||
ReactNoop.render(<App foo={false} />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
div(span('foo!=bar'), span('foo==bar')),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should unmount a composite in a call', () => {
|
||||
let ops = [];
|
||||
|
||||
class Continuation extends React.Component {
|
||||
render() {
|
||||
ops.push('Continuation');
|
||||
return <div />;
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ops.push('Unmount Continuation');
|
||||
}
|
||||
}
|
||||
|
||||
class Child extends React.Component {
|
||||
render() {
|
||||
ops.push('Child');
|
||||
return ReactCallReturn.unstable_createReturn(Continuation);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ops.push('Unmount Child');
|
||||
}
|
||||
}
|
||||
|
||||
function HandleReturns(props, returns) {
|
||||
ops.push('HandleReturns');
|
||||
return returns.map((ContinuationComponent, i) => (
|
||||
<ContinuationComponent key={i} />
|
||||
));
|
||||
}
|
||||
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
ops.push('Parent');
|
||||
return ReactCallReturn.unstable_createCall(
|
||||
this.props.children,
|
||||
HandleReturns,
|
||||
this.props,
|
||||
);
|
||||
}
|
||||
componentWillUnmount() {
|
||||
ops.push('Unmount Parent');
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(
|
||||
<Parent>
|
||||
<Child />
|
||||
</Parent>,
|
||||
);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['Parent', 'Child', 'HandleReturns', 'Continuation']);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(<div />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual([
|
||||
'Unmount Parent',
|
||||
'Unmount Child',
|
||||
'Unmount Continuation',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle deep updates in call', () => {
|
||||
let instances = {};
|
||||
|
||||
class Counter extends React.Component {
|
||||
state = {value: 5};
|
||||
render() {
|
||||
instances[this.props.id] = this;
|
||||
return ReactCallReturn.unstable_createReturn(this.state.value);
|
||||
}
|
||||
}
|
||||
|
||||
function App(props) {
|
||||
return ReactCallReturn.unstable_createCall(
|
||||
[
|
||||
<Counter key="a" id="a" />,
|
||||
<Counter key="b" id="b" />,
|
||||
<Counter key="c" id="c" />,
|
||||
],
|
||||
(p, returns) => returns.map((y, i) => <span key={i} prop={y * 100} />),
|
||||
{},
|
||||
);
|
||||
}
|
||||
|
||||
ReactNoop.render(<App />);
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(500), span(500), span(500)]);
|
||||
|
||||
instances.a.setState({value: 1});
|
||||
instances.b.setState({value: 2});
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(100), span(200), span(500)]);
|
||||
});
|
||||
|
||||
it('should unmount and remount children', () => {
|
||||
let ops = [];
|
||||
|
||||
class Call extends React.Component {
|
||||
render() {
|
||||
return ReactCallReturn.unstable_createCall(
|
||||
this.props.children,
|
||||
(p, returns) => returns,
|
||||
{},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Return extends React.Component {
|
||||
render() {
|
||||
ops.push(`Return ${this.props.value}`);
|
||||
return ReactCallReturn.unstable_createReturn(this.props.children);
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
ops.push(`Mount Return ${this.props.value}`);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ops.push(`Unmount Return ${this.props.value}`);
|
||||
}
|
||||
}
|
||||
|
||||
ReactNoop.render(
|
||||
<Call>
|
||||
<Return value={1} />
|
||||
<Return value={2} />
|
||||
</Call>,
|
||||
);
|
||||
expect(ReactNoop.flush).toWarnDev(
|
||||
'componentWillMount: Please update the following components ' +
|
||||
'to use componentDidMount instead: Return',
|
||||
);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'Mount Return 1',
|
||||
'Return 1',
|
||||
'Mount Return 2',
|
||||
'Return 2',
|
||||
]);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(<Call />);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['Unmount Return 1', 'Unmount Return 2']);
|
||||
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(
|
||||
<Call>
|
||||
<Return value={3} />
|
||||
</Call>,
|
||||
);
|
||||
ReactNoop.flush();
|
||||
|
||||
expect(ops).toEqual(['Mount Return 3', 'Return 3']);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-dom",
|
||||
"version": "16.3.2",
|
||||
"version": "16.4.1",
|
||||
"description": "React package for working with the DOM.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
@@ -13,10 +13,9 @@
|
||||
},
|
||||
"homepage": "https://reactjs.org/",
|
||||
"dependencies": {
|
||||
"fbjs": "^0.8.16",
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.0"
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.0.0"
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('DOMPropertyOperations', () => {
|
||||
|
||||
it('should not remove empty attributes for special properties', () => {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<input value="" />, container);
|
||||
ReactDOM.render(<input value="" onChange={() => {}} />, container);
|
||||
expect(container.firstChild.getAttribute('value')).toBe('');
|
||||
expect(container.firstChild.value).toBe('');
|
||||
});
|
||||
@@ -123,7 +123,7 @@ describe('DOMPropertyOperations', () => {
|
||||
spyOnDevAndProd(container.firstChild, 'setAttribute');
|
||||
ReactDOM.render(<progress value={30} />, container);
|
||||
ReactDOM.render(<progress value="30" />, container);
|
||||
expect(container.firstChild.setAttribute.calls.count()).toBe(2);
|
||||
expect(container.firstChild.setAttribute).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -155,5 +155,11 @@ describe('DOMPropertyOperations', () => {
|
||||
expect(container.firstChild.getAttribute('value')).toBe('foo');
|
||||
expect(container.firstChild.value).toBe('foo');
|
||||
});
|
||||
|
||||
it('should not remove attributes for custom component tag', () => {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<my-icon size="5px" />, container);
|
||||
expect(container.firstChild.getAttribute('size')).toBe('5px');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -42,6 +42,8 @@ let getListener;
|
||||
let putListener;
|
||||
let deleteAllListeners;
|
||||
|
||||
let container;
|
||||
|
||||
function registerSimpleTestHandler() {
|
||||
putListener(CHILD, ON_CLICK_KEY, LISTENER);
|
||||
const listener = getListener(CHILD, ON_CLICK_KEY);
|
||||
@@ -63,7 +65,8 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
ReactBrowserEventEmitter = require('../events/ReactBrowserEventEmitter');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
|
||||
const container = document.createElement('div');
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
let GRANDPARENT_PROPS = {};
|
||||
let PARENT_PROPS = {};
|
||||
@@ -129,6 +132,11 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
idCallOrder = [];
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
container = null;
|
||||
});
|
||||
|
||||
it('should store a listener correctly', () => {
|
||||
registerSimpleTestHandler();
|
||||
const listener = getListener(CHILD, ON_CLICK_KEY);
|
||||
@@ -150,25 +158,25 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
|
||||
it('should invoke a simple handler registered on a node', () => {
|
||||
registerSimpleTestHandler();
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
expect(LISTENER.mock.calls.length).toBe(1);
|
||||
CHILD.click();
|
||||
expect(LISTENER).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not invoke handlers if ReactBrowserEventEmitter is disabled', () => {
|
||||
registerSimpleTestHandler();
|
||||
ReactBrowserEventEmitter.setEnabled(false);
|
||||
ReactTestUtils.SimulateNative.click(CHILD);
|
||||
expect(LISTENER.mock.calls.length).toBe(0);
|
||||
CHILD.click();
|
||||
expect(LISTENER).toHaveBeenCalledTimes(0);
|
||||
ReactBrowserEventEmitter.setEnabled(true);
|
||||
ReactTestUtils.SimulateNative.click(CHILD);
|
||||
expect(LISTENER.mock.calls.length).toBe(1);
|
||||
CHILD.click();
|
||||
expect(LISTENER).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should bubble simply', () => {
|
||||
putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, CHILD));
|
||||
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder.length).toBe(3);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
expect(idCallOrder[1]).toBe(PARENT);
|
||||
@@ -179,7 +187,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, 'GRANDPARENT'));
|
||||
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, 'PARENT'));
|
||||
putListener(CHILD, ON_CLICK_KEY, recordID.bind(null, 'CHILD'));
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'GRANDPARENT']);
|
||||
|
||||
idCallOrder = [];
|
||||
@@ -191,7 +199,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
recordID.bind(null, 'UPDATED_GRANDPARENT'),
|
||||
);
|
||||
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder).toEqual(['CHILD', 'PARENT', 'UPDATED_GRANDPARENT']);
|
||||
});
|
||||
|
||||
@@ -224,7 +232,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
recordID(GRANDPARENT);
|
||||
expect(event.currentTarget).toBe(GRANDPARENT);
|
||||
});
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder.length).toBe(3);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
expect(idCallOrder[1]).toBe(PARENT);
|
||||
@@ -239,7 +247,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
recordIDAndStopPropagation.bind(null, PARENT),
|
||||
);
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder.length).toBe(2);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
expect(idCallOrder[1]).toBe(PARENT);
|
||||
@@ -254,7 +262,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
e.isPropagationStopped = () => true;
|
||||
});
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder.length).toBe(2);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
expect(idCallOrder[1]).toBe(PARENT);
|
||||
@@ -268,7 +276,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
);
|
||||
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder.length).toBe(1);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
});
|
||||
@@ -277,7 +285,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
putListener(CHILD, ON_CLICK_KEY, recordIDAndReturnFalse.bind(null, CHILD));
|
||||
putListener(PARENT, ON_CLICK_KEY, recordID.bind(null, PARENT));
|
||||
putListener(GRANDPARENT, ON_CLICK_KEY, recordID.bind(null, GRANDPARENT));
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
CHILD.click();
|
||||
expect(idCallOrder.length).toBe(3);
|
||||
expect(idCallOrder[0]).toBe(CHILD);
|
||||
expect(idCallOrder[1]).toBe(PARENT);
|
||||
@@ -300,8 +308,8 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
};
|
||||
putListener(CHILD, ON_CLICK_KEY, handleChildClick);
|
||||
putListener(PARENT, ON_CLICK_KEY, handleParentClick);
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
expect(handleParentClick.mock.calls.length).toBe(1);
|
||||
CHILD.click();
|
||||
expect(handleParentClick).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should not invoke newly inserted handlers while bubbling', () => {
|
||||
@@ -310,8 +318,8 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
putListener(PARENT, ON_CLICK_KEY, handleParentClick);
|
||||
};
|
||||
putListener(CHILD, ON_CLICK_KEY, handleChildClick);
|
||||
ReactTestUtils.Simulate.click(CHILD);
|
||||
expect(handleParentClick.mock.calls.length).toBe(0);
|
||||
CHILD.click();
|
||||
expect(handleParentClick).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
it('should have mouse enter simulated by test utils', () => {
|
||||
@@ -325,7 +333,7 @@ describe('ReactBrowserEventEmitter', () => {
|
||||
spyOnDevAndProd(EventTarget.prototype, 'addEventListener');
|
||||
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
|
||||
ReactBrowserEventEmitter.listenTo(ON_CLICK_KEY, document);
|
||||
expect(EventTarget.prototype.addEventListener.calls.count()).toBe(1);
|
||||
expect(EventTarget.prototype.addEventListener).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should work with event plugins without dependencies', () => {
|
||||
|
||||
@@ -80,8 +80,8 @@ describe('ReactChildReconciler', () => {
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
' in div (at **)\n' +
|
||||
'could change in a future version.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in Parent (at **)\n' +
|
||||
' in GrandParent (at **)',
|
||||
@@ -127,8 +127,8 @@ describe('ReactChildReconciler', () => {
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
'duplicated and/or omitted — the behavior is unsupported and ' +
|
||||
'could change in a future version.',
|
||||
' in div (at **)\n' +
|
||||
'could change in a future version.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Component (at **)\n' +
|
||||
' in Parent (at **)\n' +
|
||||
' in GrandParent (at **)',
|
||||
|
||||
@@ -278,7 +278,7 @@ describe('ReactComponent', () => {
|
||||
componentDidMount() {
|
||||
// Check .props.title to make sure we got the right elements back
|
||||
expect(this.wrapperRef.getTitle()).toBe('wrapper');
|
||||
expect(ReactDOM.findDOMNode(this.innerRef).className).toBe('inner');
|
||||
expect(this.innerRef.className).toBe('inner');
|
||||
mounted = true;
|
||||
}
|
||||
}
|
||||
@@ -387,11 +387,11 @@ describe('ReactComponent', () => {
|
||||
const callback = jest.fn();
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<div />, container, callback);
|
||||
expect(callback.mock.calls.length).toBe(1);
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
ReactDOM.render(<div className="foo" />, container, callback);
|
||||
expect(callback.mock.calls.length).toBe(2);
|
||||
expect(callback).toHaveBeenCalledTimes(2);
|
||||
ReactDOM.render(<span />, container, callback);
|
||||
expect(callback.mock.calls.length).toBe(3);
|
||||
expect(callback).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('throws usefully when rendering badly-typed elements', () => {
|
||||
|
||||
@@ -43,19 +43,22 @@ describe('ReactComponentLifeCycle', () => {
|
||||
const container = document.createElement('div');
|
||||
expect(() =>
|
||||
ReactDOM.render(<MyComponent x={1} />, container),
|
||||
).toLowPriorityWarnDev([
|
||||
'componentWillMount is deprecated and will be removed in the next major version. ' +
|
||||
'Use componentDidMount instead. As a temporary workaround, ' +
|
||||
'you can rename to UNSAFE_componentWillMount.' +
|
||||
'\n\nPlease update the following components: MyComponent',
|
||||
'componentWillReceiveProps is deprecated and will be removed in the next major version. ' +
|
||||
'Use static getDerivedStateFromProps instead.' +
|
||||
'\n\nPlease update the following components: MyComponent',
|
||||
'componentWillUpdate is deprecated and will be removed in the next major version. ' +
|
||||
'Use componentDidUpdate instead. As a temporary workaround, ' +
|
||||
'you can rename to UNSAFE_componentWillUpdate.' +
|
||||
'\n\nPlease update the following components: MyComponent',
|
||||
]);
|
||||
).toLowPriorityWarnDev(
|
||||
[
|
||||
'componentWillMount is deprecated and will be removed in the next major version. ' +
|
||||
'Use componentDidMount instead. As a temporary workaround, ' +
|
||||
'you can rename to UNSAFE_componentWillMount.' +
|
||||
'\n\nPlease update the following components: MyComponent',
|
||||
'componentWillReceiveProps is deprecated and will be removed in the next major version. ' +
|
||||
'Use static getDerivedStateFromProps instead.' +
|
||||
'\n\nPlease update the following components: MyComponent',
|
||||
'componentWillUpdate is deprecated and will be removed in the next major version. ' +
|
||||
'Use componentDidUpdate instead. As a temporary workaround, ' +
|
||||
'you can rename to UNSAFE_componentWillUpdate.' +
|
||||
'\n\nPlease update the following components: MyComponent',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// Dedupe check (update and instantiate new
|
||||
ReactDOM.render(<MyComponent x={2} />, container);
|
||||
@@ -63,28 +66,9 @@ describe('ReactComponentLifeCycle', () => {
|
||||
});
|
||||
|
||||
describe('react-lifecycles-compat', () => {
|
||||
const polyfill = require('react-lifecycles-compat');
|
||||
|
||||
it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
return null;
|
||||
}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
polyfill(PolyfilledComponent);
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<PolyfilledComponent />, container);
|
||||
});
|
||||
|
||||
it('should not warn about unsafe lifecycles within "strict" tree for polyfilled components', () => {
|
||||
const {StrictMode} = React;
|
||||
const {polyfill} = require('react-lifecycles-compat');
|
||||
|
||||
it('should not warn for components with polyfilled getDerivedStateFromProps', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
@@ -99,9 +83,31 @@ describe('ReactComponentLifeCycle', () => {
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<StrictMode>
|
||||
<React.StrictMode>
|
||||
<PolyfilledComponent />
|
||||
</StrictMode>,
|
||||
</React.StrictMode>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
|
||||
it('should not warn for components with polyfilled getSnapshotBeforeUpdate', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
getSnapshotBeforeUpdate() {
|
||||
return null;
|
||||
}
|
||||
componentDidUpdate() {}
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
polyfill(PolyfilledComponent);
|
||||
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<PolyfilledComponent />
|
||||
</React.StrictMode>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -68,7 +68,8 @@ type ComponentLifeCycle =
|
||||
* Mounted components have a DOM node representation and are capable of
|
||||
* receiving new props.
|
||||
*/
|
||||
| 'MOUNTED' /**
|
||||
| 'MOUNTED'
|
||||
/**
|
||||
* Unmounted components are inactive and cannot receive new props.
|
||||
*/
|
||||
| 'UNMOUNTED';
|
||||
@@ -218,6 +219,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
'This is a no-op, but it might indicate a bug in your application. ' +
|
||||
'Instead, assign to `this.state` directly or define a `state = {};` ' +
|
||||
'class property with the desired state in the StatefulComponent component.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// Check deduplication; (no extra warnings should be logged).
|
||||
@@ -248,7 +250,9 @@ describe('ReactComponentLifeCycle', () => {
|
||||
expect(() => {
|
||||
const instance = ReactTestUtils.renderIntoDocument(element);
|
||||
expect(instance._isMounted()).toBeTruthy();
|
||||
}).toWarnDev('Component is accessing isMounted inside its render()');
|
||||
}).toWarnDev('Component is accessing isMounted inside its render()', {
|
||||
withoutStack: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should correctly determine if a null component is mounted', () => {
|
||||
@@ -275,7 +279,9 @@ describe('ReactComponentLifeCycle', () => {
|
||||
expect(() => {
|
||||
const instance = ReactTestUtils.renderIntoDocument(element);
|
||||
expect(instance._isMounted()).toBeTruthy();
|
||||
}).toWarnDev('Component is accessing isMounted inside its render()');
|
||||
}).toWarnDev('Component is accessing isMounted inside its render()', {
|
||||
withoutStack: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('isMounted should return false when unmounted', () => {
|
||||
@@ -313,7 +319,9 @@ describe('ReactComponentLifeCycle', () => {
|
||||
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(<Component />);
|
||||
}).toWarnDev('Component is accessing findDOMNode inside its render()');
|
||||
}).toWarnDev('Component is accessing findDOMNode inside its render()', {
|
||||
withoutStack: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('should carry through each of the phases of setup', () => {
|
||||
@@ -379,6 +387,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
instance = ReactDOM.render(<LifeCycleComponent />, container);
|
||||
}).toWarnDev(
|
||||
'LifeCycleComponent is accessing isMounted inside its render() function',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// getInitialState
|
||||
@@ -674,6 +683,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
const container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Component />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -701,6 +711,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
const container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Component value={1} />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
ReactDOM.render(<Component value={2} />, container);
|
||||
});
|
||||
@@ -728,6 +739,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
const container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Component value={1} />, container)).toWarnDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
ReactDOM.render(<Component value={2} />, container);
|
||||
});
|
||||
@@ -756,6 +768,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
' componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
class WillMount extends React.Component {
|
||||
@@ -775,6 +788,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
' UNSAFE_componentWillMount\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
class WillMountAndUpdate extends React.Component {
|
||||
@@ -796,6 +810,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
' UNSAFE_componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
class WillReceiveProps extends React.Component {
|
||||
@@ -815,6 +830,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
' componentWillReceiveProps\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -841,6 +857,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
' componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
class WillMount extends React.Component {
|
||||
@@ -859,6 +876,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
' UNSAFE_componentWillMount\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
class WillMountAndUpdate extends React.Component {
|
||||
@@ -879,6 +897,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
' UNSAFE_componentWillUpdate\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
class WillReceiveProps extends React.Component {
|
||||
@@ -897,6 +916,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
' componentWillReceiveProps\n\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://fb.me/react-async-component-lifecycle-hooks',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -964,6 +984,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
expect(() => ReactDOM.render(<MyComponent />, div)).toWarnDev(
|
||||
'MyComponent.getDerivedStateFromProps(): A valid state object (or null) must ' +
|
||||
'be returned. You have returned undefined.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// De-duped
|
||||
@@ -984,6 +1005,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
expect(() => ReactDOM.render(<MyComponent />, div)).toWarnDev(
|
||||
'MyComponent: Did not properly initialize state during construction. ' +
|
||||
'Expected state to be an object, but it was undefined.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// De-duped
|
||||
@@ -1065,16 +1087,22 @@ describe('ReactComponentLifeCycle', () => {
|
||||
}
|
||||
}
|
||||
|
||||
ReactTestUtils.renderIntoDocument(<Parent />);
|
||||
expect(divRef.current.textContent).toBe('remote:0, local:0');
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
try {
|
||||
ReactDOM.render(<Parent />, container);
|
||||
expect(divRef.current.textContent).toBe('remote:0, local:0');
|
||||
|
||||
// Trigger setState() calls
|
||||
childInstance.updateState();
|
||||
expect(divRef.current.textContent).toBe('remote:1, local:1');
|
||||
// Trigger setState() calls
|
||||
childInstance.updateState();
|
||||
expect(divRef.current.textContent).toBe('remote:1, local:1');
|
||||
|
||||
// Trigger batched setState() calls
|
||||
ReactTestUtils.Simulate.click(divRef.current);
|
||||
expect(divRef.current.textContent).toBe('remote:2, local:2');
|
||||
// Trigger batched setState() calls
|
||||
divRef.current.click();
|
||||
expect(divRef.current.textContent).toBe('remote:2, local:2');
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
|
||||
it('should pass the return value from getSnapshotBeforeUpdate to componentDidUpdate', () => {
|
||||
@@ -1203,6 +1231,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
expect(() => ReactDOM.render(<MyComponent value="bar" />, div)).toWarnDev(
|
||||
'MyComponent.getSnapshotBeforeUpdate(): A snapshot value (or null) must ' +
|
||||
'be returned. You have returned undefined.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// De-duped
|
||||
@@ -1223,6 +1252,7 @@ describe('ReactComponentLifeCycle', () => {
|
||||
expect(() => ReactDOM.render(<MyComponent />, div)).toWarnDev(
|
||||
'MyComponent: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
|
||||
'This component defines getSnapshotBeforeUpdate() only.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// De-duped
|
||||
|
||||
@@ -17,10 +17,50 @@ let ReactDOMServer;
|
||||
let ReactCurrentOwner;
|
||||
let ReactTestUtils;
|
||||
let PropTypes;
|
||||
let shallowEqual;
|
||||
let shallowCompare;
|
||||
|
||||
describe('ReactCompositeComponent', () => {
|
||||
const hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
|
||||
/**
|
||||
* Performs equality by iterating through keys on an object and returning false
|
||||
* when any key has values which are not strictly equal between the arguments.
|
||||
* Returns true when the values of all keys are strictly equal.
|
||||
*/
|
||||
function shallowEqual(objA: mixed, objB: mixed): boolean {
|
||||
if (Object.is(objA, objB)) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
typeof objA !== 'object' ||
|
||||
objA === null ||
|
||||
typeof objB !== 'object' ||
|
||||
objB === null
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const keysA = Object.keys(objA);
|
||||
const keysB = Object.keys(objB);
|
||||
if (keysA.length !== keysB.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < keysA.length; i++) {
|
||||
if (
|
||||
!hasOwnProperty.call(objB, keysA[i]) ||
|
||||
!Object.is(objA[keysA[i]], objB[keysA[i]])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function shallowCompare(instance, nextProps, nextState) {
|
||||
return (
|
||||
!shallowEqual(instance.props, nextProps) ||
|
||||
!shallowEqual(instance.state, nextState)
|
||||
);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
@@ -30,14 +70,6 @@ describe('ReactCompositeComponent', () => {
|
||||
.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner;
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
PropTypes = require('prop-types');
|
||||
shallowEqual = require('fbjs/lib/shallowEqual');
|
||||
|
||||
shallowCompare = function(instance, nextProps, nextState) {
|
||||
return (
|
||||
!shallowEqual(instance.props, nextProps) ||
|
||||
!shallowEqual(instance.state, nextState)
|
||||
);
|
||||
};
|
||||
|
||||
MorphingComponent = class extends React.Component {
|
||||
state = {activated: false};
|
||||
@@ -131,6 +163,7 @@ describe('ReactCompositeComponent', () => {
|
||||
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
|
||||
'will stop working in React v17. Replace the ReactDOM.render() call ' +
|
||||
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// New explicit API
|
||||
@@ -140,23 +173,28 @@ describe('ReactCompositeComponent', () => {
|
||||
});
|
||||
|
||||
it('should react to state changes from callbacks', () => {
|
||||
const instance = ReactTestUtils.renderIntoDocument(<MorphingComponent />);
|
||||
let el = ReactDOM.findDOMNode(instance);
|
||||
expect(el.tagName).toBe('A');
|
||||
|
||||
ReactTestUtils.Simulate.click(el);
|
||||
el = ReactDOM.findDOMNode(instance);
|
||||
expect(el.tagName).toBe('B');
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
try {
|
||||
const instance = ReactDOM.render(<MorphingComponent />, container);
|
||||
let el = ReactDOM.findDOMNode(instance);
|
||||
expect(el.tagName).toBe('A');
|
||||
el.click();
|
||||
el = ReactDOM.findDOMNode(instance);
|
||||
expect(el.tagName).toBe('B');
|
||||
} finally {
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
|
||||
it('should rewire refs when rendering to different child types', () => {
|
||||
const instance = ReactTestUtils.renderIntoDocument(<MorphingComponent />);
|
||||
|
||||
expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe('A');
|
||||
expect(instance.refs.x.tagName).toBe('A');
|
||||
instance._toggleActivatedState();
|
||||
expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe('B');
|
||||
expect(instance.refs.x.tagName).toBe('B');
|
||||
instance._toggleActivatedState();
|
||||
expect(ReactDOM.findDOMNode(instance.refs.x).tagName).toBe('A');
|
||||
expect(instance.refs.x.tagName).toBe('A');
|
||||
});
|
||||
|
||||
it('should not cache old DOM nodes when switching constructors', () => {
|
||||
@@ -242,6 +280,7 @@ describe('ReactCompositeComponent', () => {
|
||||
'This is a no-op, but it might indicate a bug in your application. ' +
|
||||
'Instead, assign to `this.state` directly or define a `state = {};` ' +
|
||||
'class property with the desired state in the MyComponent component.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// No additional warning should be recorded
|
||||
@@ -266,6 +305,7 @@ describe('ReactCompositeComponent', () => {
|
||||
'This is a no-op, but it might indicate a bug in your application. ' +
|
||||
'Instead, assign to `this.state` directly or define a `state = {};` ' +
|
||||
'class property with the desired state in the MyComponent component.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// No additional warning should be recorded
|
||||
@@ -426,9 +466,8 @@ describe('ReactCompositeComponent', () => {
|
||||
instance = ReactDOM.render(<Component />, container);
|
||||
}).toWarnDev(
|
||||
'Cannot update during an existing state transition (such as within ' +
|
||||
"`render` or another component's constructor). Render methods should " +
|
||||
'be a pure function of props and state; constructor side-effects are ' +
|
||||
'an anti-pattern, but can be moved to `componentWillMount`.',
|
||||
'`render`). Render methods should be a pure function of props and state.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
// The setState call is queued and then executed as a second pass. This
|
||||
@@ -476,6 +515,7 @@ describe('ReactCompositeComponent', () => {
|
||||
instance = ReactDOM.render(<Component />, container);
|
||||
}).toWarnDev(
|
||||
'Warning: setState(...): Cannot call setState() inside getChildContext()',
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
expect(renderPasses).toBe(2);
|
||||
@@ -552,6 +592,7 @@ describe('ReactCompositeComponent', () => {
|
||||
expect(() => instance.setState({bogus: true})).toWarnDev(
|
||||
'Warning: Component.shouldComponentUpdate(): Returned undefined instead of a ' +
|
||||
'boolean value. Make sure to return true or false.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -568,6 +609,7 @@ describe('ReactCompositeComponent', () => {
|
||||
'Warning: Component has a method called ' +
|
||||
'componentDidUnmount(). But there is no such lifecycle method. ' +
|
||||
'Did you mean componentWillUnmount()?',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -586,6 +628,7 @@ describe('ReactCompositeComponent', () => {
|
||||
'If you meant to update the state in response to changing props, ' +
|
||||
'use componentWillReceiveProps(). If you meant to fetch data or ' +
|
||||
'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -604,6 +647,7 @@ describe('ReactCompositeComponent', () => {
|
||||
expect(() => ReactTestUtils.renderIntoDocument(<Component />)).toWarnDev(
|
||||
'Warning: Setting defaultProps as an instance property on Component is not supported ' +
|
||||
'and will be ignored. Instead, define defaultProps as a static property on Component.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1094,6 +1138,7 @@ describe('ReactCompositeComponent', () => {
|
||||
'triggering nested component updates from render is not allowed. If ' +
|
||||
'necessary, trigger nested updates in componentDidUpdate.\n\nCheck the ' +
|
||||
'render method of Outer.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1385,6 +1430,7 @@ describe('ReactCompositeComponent', () => {
|
||||
expect(() => ReactDOM.render(<Foo idx="qwe" />, container)).toWarnDev(
|
||||
'Foo(...): When calling super() in `Foo`, make sure to pass ' +
|
||||
"up the same props that your component's constructor was passed.",
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
|
||||