Compare commits
117 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
0887c7d56c | ||
|
|
039695cc01 | ||
|
|
b05e67e36a | ||
|
|
77ebeb1b09 | ||
|
|
b85c5cd188 | ||
|
|
01402f4ad9 |
4
.babelrc
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
|
||||
|
||||
42
.flowconfig
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
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
|
||||
|
||||
56
CHANGELOG.md
56
CHANGELOG.md
@@ -4,8 +4,64 @@
|
||||
Changes that have landed in master but are not yet released.
|
||||
Click to see more.
|
||||
</summary>
|
||||
|
||||
### 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))
|
||||
|
||||
</details>
|
||||
|
||||
## 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
|
||||
|
||||
@@ -5,8 +5,8 @@ init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: 8
|
||||
nodejs_version: 8
|
||||
JAVA_HOME: C:\Program Files\Java\jdk1.8.0
|
||||
|
||||
# Finish on first failed build
|
||||
matrix:
|
||||
@@ -30,7 +30,7 @@ install:
|
||||
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)) {
|
||||
|
||||
@@ -64,6 +64,7 @@ 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>
|
||||
</select>
|
||||
</label>
|
||||
<label htmlFor="react_version">
|
||||
|
||||
@@ -11,6 +11,7 @@ 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';
|
||||
|
||||
const React = window.React;
|
||||
|
||||
@@ -46,6 +47,8 @@ function FixturesPage() {
|
||||
return <CustomElementFixtures />;
|
||||
case '/media-events':
|
||||
return <MediaEventsFixtures />;
|
||||
case '/pointer-events':
|
||||
return <PointerEventsFixtures />;
|
||||
default:
|
||||
return <p>Please select a test fixture.</p>;
|
||||
}
|
||||
|
||||
@@ -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
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
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
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;
|
||||
9
netlify.toml
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
|
||||
26
package.json
26
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "16.3.2",
|
||||
"version": "16.4.0",
|
||||
"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": "^22.4.4",
|
||||
"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",
|
||||
@@ -62,16 +60,17 @@
|
||||
"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": "^22.4.4",
|
||||
"jest-diff": "^22.4.3",
|
||||
"merge-stream": "^1.0.0",
|
||||
"minimatch": "^3.0.4",
|
||||
"minimist": "^1.2.0",
|
||||
@@ -79,14 +78,13 @@
|
||||
"ncp": "^2.0.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"platform": "^1.1.0",
|
||||
"prettier": "1.8.1",
|
||||
"prettier": "1.11.1",
|
||||
"prop-types": "^15.6.0",
|
||||
"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",
|
||||
@@ -103,13 +101,16 @@
|
||||
"devEngines": {
|
||||
"node": "8.x || 9.x"
|
||||
},
|
||||
"jest": {
|
||||
"testRegex": "/scripts/jest/dont-run-jest-directly\\.js$"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "npm run version-check && node ./scripts/rollup/build.js",
|
||||
"flow-coverage": "flow-coverage-report --config ./.flowcoverage",
|
||||
"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 +118,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"
|
||||
|
||||
@@ -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.0",
|
||||
"repository": "facebook/react",
|
||||
"files": [
|
||||
"LICENSE",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,7 +166,7 @@ export function getListener(inst: Fiber, registrationName: string) {
|
||||
* @internal
|
||||
*/
|
||||
function extractEvents(
|
||||
topLevelType: string,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
@@ -227,7 +228,7 @@ export function runEventsInBatch(
|
||||
}
|
||||
|
||||
export function runExtractedEventsInBatch(
|
||||
topLevelType: string,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: Fiber,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -12,6 +12,7 @@ import type {
|
||||
DispatchConfig,
|
||||
ReactSyntheticEvent,
|
||||
} from './ReactSyntheticEventType';
|
||||
import type {TopLevelType} from './TopLevelEventTypes';
|
||||
|
||||
export type EventTypes = {[key: string]: DispatchConfig};
|
||||
|
||||
@@ -22,7 +23,7 @@ export type PluginName = string;
|
||||
export type PluginModule<NativeEvent> = {
|
||||
eventTypes: EventTypes,
|
||||
extractEvents: (
|
||||
topLevelType: string,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: Fiber,
|
||||
nativeTarget: NativeEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
|
||||
@@ -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))
|
||||
);
|
||||
@@ -534,7 +563,7 @@ const ResponderEventPlugin = {
|
||||
}
|
||||
|
||||
const isResponderTerminate =
|
||||
responderInst && topLevelType === 'topTouchCancel';
|
||||
responderInst && topLevelType === TOP_TOUCH_CANCEL;
|
||||
const isResponderRelease =
|
||||
responderInst &&
|
||||
!isResponderTerminate &&
|
||||
@@ -556,23 +585,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 +596,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
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];
|
||||
@@ -10,7 +10,7 @@
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
|
||||
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
|
||||
|
||||
39
packages/events/TopLevelEventTypes.js
Normal file
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;
|
||||
41
packages/events/forks/ResponderTopLevelEventTypes.dom.js
Normal file
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.0",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
"keywords": [
|
||||
|
||||
410
packages/react-art/src/ReactART.js
vendored
410
packages/react-art/src/ReactART.js
vendored
@@ -6,268 +6,18 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactFiberReconciler from 'react-reconciler';
|
||||
import * as ReactDOMFrameScheduling from 'shared/ReactDOMFrameScheduling';
|
||||
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,158 +131,6 @@ 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);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/** API */
|
||||
|
||||
export const ClippingRectangle = TYPES.CLIPPING_RECTANGLE;
|
||||
|
||||
400
packages/react-art/src/ReactARTHostConfig.js
vendored
Normal file
400
packages/react-art/src/ReactARTHostConfig.js
vendored
Normal file
@@ -0,0 +1,400 @@
|
||||
/**
|
||||
* 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 'react-scheduler';
|
||||
import Transform from 'art/core/transform';
|
||||
import Mode from 'art/modes/current';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
import emptyObject from 'fbjs/lib/emptyObject';
|
||||
|
||||
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
|
||||
|
||||
const pooledTransform = new Transform();
|
||||
|
||||
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 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 emptyObject;
|
||||
}
|
||||
|
||||
export function getChildHostContext() {
|
||||
return emptyObject;
|
||||
}
|
||||
|
||||
export const scheduleDeferredCallback = ReactScheduler.scheduleWork;
|
||||
export const cancelDeferredCallback = ReactScheduler.cancelScheduledWork;
|
||||
|
||||
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
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 '';
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,19 @@ 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');
|
||||
|
||||
let Group;
|
||||
let Shape;
|
||||
let Surface;
|
||||
@@ -22,15 +35,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);
|
||||
@@ -339,11 +343,65 @@ 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) {
|
||||
testRenderer.unstable_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.
|
||||
const testRenderer = ReactTestRenderer.create(
|
||||
<CurrentRendererContext.Provider value="Test">
|
||||
<Yield value="A" />
|
||||
<Yield value="B" />
|
||||
<LogCurrentRenderer />
|
||||
<Yield value="C" />
|
||||
</CurrentRendererContext.Provider>,
|
||||
{
|
||||
unstable_isAsync: true,
|
||||
},
|
||||
);
|
||||
|
||||
testRenderer.unstable_flushThrough(['A']);
|
||||
|
||||
ReactDOM.render(
|
||||
<Surface>
|
||||
<LogCurrentRenderer />
|
||||
<CurrentRendererContext.Provider value="ART">
|
||||
<LogCurrentRenderer />
|
||||
</CurrentRendererContext.Provider>
|
||||
</Surface>,
|
||||
container,
|
||||
);
|
||||
|
||||
expect(ops).toEqual([null, 'ART']);
|
||||
|
||||
ops = [];
|
||||
expect(testRenderer.unstable_flushAll()).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 +409,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 +420,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 +428,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 +440,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
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.0",
|
||||
"description": "React package for working with the DOM.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -63,28 +63,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 +80,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,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -309,14 +309,9 @@ describe('ReactDOM', () => {
|
||||
const actual = [];
|
||||
|
||||
function click(node) {
|
||||
const fakeNativeEvent = function() {};
|
||||
fakeNativeEvent.target = node;
|
||||
fakeNativeEvent.path = [node, container];
|
||||
ReactTestUtils.simulateNativeEventOnNode(
|
||||
'topClick',
|
||||
node,
|
||||
fakeNativeEvent,
|
||||
);
|
||||
ReactTestUtils.Simulate.click(node, {
|
||||
path: [node, container],
|
||||
});
|
||||
}
|
||||
|
||||
class Wrapper extends React.Component {
|
||||
@@ -439,4 +434,39 @@ describe('ReactDOM', () => {
|
||||
Object.defineProperty(global, 'document', documentDescriptor);
|
||||
}
|
||||
});
|
||||
|
||||
it('warns when requestAnimationFrame is not polyfilled in the browser', () => {
|
||||
const previousRAF = global.requestAnimationFrame;
|
||||
try {
|
||||
global.requestAnimationFrame = undefined;
|
||||
jest.resetModules();
|
||||
expect(() => require('react-dom')).toWarnDev(
|
||||
'React depends on requestAnimationFrame.',
|
||||
);
|
||||
} finally {
|
||||
global.requestAnimationFrame = previousRAF;
|
||||
}
|
||||
});
|
||||
|
||||
// We're just testing importing, not using it.
|
||||
// It is important because even isomorphic components may import it.
|
||||
it('can import findDOMNode in Node environment', () => {
|
||||
const previousRAF = global.requestAnimationFrame;
|
||||
const previousRIC = global.requestIdleCallback;
|
||||
const prevWindow = global.window;
|
||||
try {
|
||||
global.requestAnimationFrame = undefined;
|
||||
global.requestIdleCallback = undefined;
|
||||
// Simulate the Node environment:
|
||||
delete global.window;
|
||||
jest.resetModules();
|
||||
expect(() => {
|
||||
require('react-dom');
|
||||
}).not.toThrow();
|
||||
} finally {
|
||||
global.requestAnimationFrame = previousRAF;
|
||||
global.requestIdleCallback = previousRIC;
|
||||
global.window = prevWindow;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -217,4 +217,165 @@ describe('ReactDOMEventListener', () => {
|
||||
expect(mouseOut.mock.calls[0][0]).toEqual(instance.getInner());
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/facebook/react/pull/12877
|
||||
it('should not fire form events twice', () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
const formRef = React.createRef();
|
||||
const inputRef = React.createRef();
|
||||
|
||||
const handleInvalid = jest.fn();
|
||||
const handleReset = jest.fn();
|
||||
const handleSubmit = jest.fn();
|
||||
ReactDOM.render(
|
||||
<form ref={formRef} onReset={handleReset} onSubmit={handleSubmit}>
|
||||
<input ref={inputRef} onInvalid={handleInvalid} />
|
||||
</form>,
|
||||
container,
|
||||
);
|
||||
|
||||
inputRef.current.dispatchEvent(
|
||||
new Event('invalid', {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Events/invalid
|
||||
bubbles: false,
|
||||
}),
|
||||
);
|
||||
expect(handleInvalid).toHaveBeenCalledTimes(1);
|
||||
|
||||
formRef.current.dispatchEvent(
|
||||
new Event('reset', {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Events/reset
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
expect(handleReset).toHaveBeenCalledTimes(1);
|
||||
|
||||
formRef.current.dispatchEvent(
|
||||
new Event('submit', {
|
||||
// https://developer.mozilla.org/en-US/docs/Web/Events/submit
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
expect(handleSubmit).toHaveBeenCalledTimes(1);
|
||||
|
||||
formRef.current.dispatchEvent(
|
||||
new Event('submit', {
|
||||
// Might happen on older browsers.
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
expect(handleSubmit).toHaveBeenCalledTimes(2); // It already fired in this test.
|
||||
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('should dispatch loadstart only for media elements', () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
const imgRef = React.createRef();
|
||||
const videoRef = React.createRef();
|
||||
|
||||
const handleImgLoadStart = jest.fn();
|
||||
const handleVideoLoadStart = jest.fn();
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<img ref={imgRef} onLoadStart={handleImgLoadStart} />
|
||||
<video ref={videoRef} onLoadStart={handleVideoLoadStart} />
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
|
||||
// Note for debugging: loadstart currently doesn't fire in Chrome.
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=458851
|
||||
imgRef.current.dispatchEvent(
|
||||
new ProgressEvent('loadstart', {
|
||||
bubbles: false,
|
||||
}),
|
||||
);
|
||||
// Historically, we happened to not support onLoadStart
|
||||
// on <img>, and this test documents that lack of support.
|
||||
// If we decide to support it in the future, we should change
|
||||
// this line to expect 1 call. Note that fixing this would
|
||||
// be simple but would require attaching a handler to each
|
||||
// <img>. So far nobody asked us for it.
|
||||
expect(handleImgLoadStart).toHaveBeenCalledTimes(0);
|
||||
|
||||
videoRef.current.dispatchEvent(
|
||||
new ProgressEvent('loadstart', {
|
||||
bubbles: false,
|
||||
}),
|
||||
);
|
||||
expect(handleVideoLoadStart).toHaveBeenCalledTimes(1);
|
||||
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('should not attempt to listen to unnecessary events on the top level', () => {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
const videoRef = React.createRef();
|
||||
const handleVideoPlay = jest.fn(); // We'll test this one.
|
||||
const mediaEvents = {
|
||||
onAbort() {},
|
||||
onCanPlay() {},
|
||||
onCanPlayThrough() {},
|
||||
onDurationChange() {},
|
||||
onEmptied() {},
|
||||
onEncrypted() {},
|
||||
onEnded() {},
|
||||
onError() {},
|
||||
onLoadedData() {},
|
||||
onLoadedMetadata() {},
|
||||
onLoadStart() {},
|
||||
onPause() {},
|
||||
onPlay() {},
|
||||
onPlaying() {},
|
||||
onProgress() {},
|
||||
onRateChange() {},
|
||||
onSeeked() {},
|
||||
onSeeking() {},
|
||||
onStalled() {},
|
||||
onSuspend() {},
|
||||
onTimeUpdate() {},
|
||||
onVolumeChange() {},
|
||||
onWaiting() {},
|
||||
};
|
||||
|
||||
const originalAddEventListener = document.addEventListener;
|
||||
document.addEventListener = function(type) {
|
||||
throw new Error(
|
||||
`Did not expect to add a top-level listener for the "${type}" event.`,
|
||||
);
|
||||
};
|
||||
|
||||
try {
|
||||
// We expect that mounting this tree will
|
||||
// *not* attach handlers for any top-level events.
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
<video ref={videoRef} {...mediaEvents} onPlay={handleVideoPlay} />
|
||||
<audio {...mediaEvents}>
|
||||
<source {...mediaEvents} />
|
||||
</audio>
|
||||
<form onReset={() => {}} onSubmit={() => {}} />
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
|
||||
// Also verify dispatching one of them works
|
||||
videoRef.current.dispatchEvent(
|
||||
new Event('play', {
|
||||
bubbles: false,
|
||||
}),
|
||||
);
|
||||
expect(handleVideoPlay).toHaveBeenCalledTimes(1);
|
||||
} finally {
|
||||
document.addEventListener = originalAddEventListener;
|
||||
document.body.removeChild(container);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -838,12 +838,7 @@ describe('ReactDOMFiber', () => {
|
||||
|
||||
expect(portal.tagName).toBe('DIV');
|
||||
|
||||
const fakeNativeEvent = {};
|
||||
ReactTestUtils.simulateNativeEventOnNode(
|
||||
'topClick',
|
||||
portal,
|
||||
fakeNativeEvent,
|
||||
);
|
||||
ReactTestUtils.Simulate.click(portal);
|
||||
|
||||
expect(ops).toEqual(['portal clicked', 'parent clicked']);
|
||||
});
|
||||
@@ -858,14 +853,12 @@ describe('ReactDOMFiber', () => {
|
||||
|
||||
function simulateMouseMove(from, to) {
|
||||
if (from) {
|
||||
ReactTestUtils.simulateNativeEventOnNode('topMouseOut', from, {
|
||||
target: from,
|
||||
ReactTestUtils.SimulateNative.mouseOut(from, {
|
||||
relatedTarget: to,
|
||||
});
|
||||
}
|
||||
if (to) {
|
||||
ReactTestUtils.simulateNativeEventOnNode('topMouseOver', to, {
|
||||
target: to,
|
||||
ReactTestUtils.SimulateNative.mouseOver(to, {
|
||||
relatedTarget: from,
|
||||
});
|
||||
}
|
||||
@@ -983,12 +976,7 @@ describe('ReactDOMFiber', () => {
|
||||
expect(node.tagName).toEqual('DIV');
|
||||
|
||||
function click(target) {
|
||||
const fakeNativeEvent = {};
|
||||
ReactTestUtils.simulateNativeEventOnNode(
|
||||
'topClick',
|
||||
target,
|
||||
fakeNativeEvent,
|
||||
);
|
||||
ReactTestUtils.Simulate.click(target);
|
||||
}
|
||||
|
||||
click(node);
|
||||
|
||||
@@ -20,6 +20,31 @@ describe('ReactDOMFiberAsync', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
// TODO pull this into helper method, reduce repetition.
|
||||
// mock the browser APIs which are used in react-scheduler:
|
||||
// - requestAnimationFrame should pass the DOMHighResTimeStamp argument
|
||||
// - calling 'window.postMessage' should actually fire postmessage handlers
|
||||
global.requestAnimationFrame = function(cb) {
|
||||
return setTimeout(() => {
|
||||
cb(Date.now());
|
||||
});
|
||||
};
|
||||
const originalAddEventListener = global.addEventListener;
|
||||
let postMessageCallback;
|
||||
global.addEventListener = function(eventName, callback, useCapture) {
|
||||
if (eventName === 'message') {
|
||||
postMessageCallback = callback;
|
||||
} else {
|
||||
originalAddEventListener(eventName, callback, useCapture);
|
||||
}
|
||||
};
|
||||
global.postMessage = function(messageKey, targetOrigin) {
|
||||
const postMessageEvent = {source: window, data: messageKey};
|
||||
if (postMessageCallback) {
|
||||
postMessageCallback(postMessageEvent);
|
||||
}
|
||||
};
|
||||
jest.resetModules();
|
||||
container = document.createElement('div');
|
||||
ReactDOM = require('react-dom');
|
||||
});
|
||||
|
||||
@@ -694,10 +694,9 @@ describe('ReactDOMInput', () => {
|
||||
|
||||
setUntrackedValue.call(node, 'giraffe');
|
||||
|
||||
const fakeNativeEvent = function() {};
|
||||
fakeNativeEvent.target = node;
|
||||
fakeNativeEvent.path = [node, container];
|
||||
ReactTestUtils.simulateNativeEventOnNode('topInput', node, fakeNativeEvent);
|
||||
ReactTestUtils.SimulateNative.input(node, {
|
||||
path: [node, container],
|
||||
});
|
||||
|
||||
expect(handled).toBe(true);
|
||||
});
|
||||
|
||||
@@ -17,52 +17,46 @@ let AsyncMode = React.unstable_AsyncMode;
|
||||
describe('ReactDOMRoot', () => {
|
||||
let container;
|
||||
|
||||
let scheduledCallback;
|
||||
let flush;
|
||||
let now;
|
||||
let expire;
|
||||
let advanceCurrentTime;
|
||||
|
||||
beforeEach(() => {
|
||||
container = document.createElement('div');
|
||||
|
||||
// Override requestIdleCallback
|
||||
scheduledCallback = null;
|
||||
flush = function(units = Infinity) {
|
||||
if (scheduledCallback !== null) {
|
||||
let didStop = false;
|
||||
while (scheduledCallback !== null && !didStop) {
|
||||
const cb = scheduledCallback;
|
||||
scheduledCallback = null;
|
||||
cb({
|
||||
timeRemaining() {
|
||||
if (units > 0) {
|
||||
return 999;
|
||||
}
|
||||
didStop = true;
|
||||
return 0;
|
||||
},
|
||||
});
|
||||
units--;
|
||||
}
|
||||
// TODO pull this into helper method, reduce repetition.
|
||||
// mock the browser APIs which are used in react-scheduler:
|
||||
// - requestAnimationFrame should pass the DOMHighResTimeStamp argument
|
||||
// - calling 'window.postMessage' should actually fire postmessage handlers
|
||||
// - must allow artificially changing time returned by Date.now
|
||||
// Performance.now is not supported in the test environment
|
||||
const originalDateNow = Date.now;
|
||||
let advancedTime = null;
|
||||
global.Date.now = function() {
|
||||
if (advancedTime) {
|
||||
return originalDateNow() + advancedTime;
|
||||
}
|
||||
return originalDateNow();
|
||||
};
|
||||
advanceCurrentTime = function(amount) {
|
||||
advancedTime = amount;
|
||||
};
|
||||
global.requestAnimationFrame = function(cb) {
|
||||
return setTimeout(() => {
|
||||
cb(Date.now());
|
||||
});
|
||||
};
|
||||
const originalAddEventListener = global.addEventListener;
|
||||
let postMessageCallback;
|
||||
global.addEventListener = function(eventName, callback, useCapture) {
|
||||
if (eventName === 'message') {
|
||||
postMessageCallback = callback;
|
||||
} else {
|
||||
originalAddEventListener(eventName, callback, useCapture);
|
||||
}
|
||||
};
|
||||
global.performance = {
|
||||
now() {
|
||||
return now;
|
||||
},
|
||||
};
|
||||
global.requestIdleCallback = function(cb) {
|
||||
scheduledCallback = cb;
|
||||
};
|
||||
|
||||
now = 0;
|
||||
expire = function(ms) {
|
||||
now += ms;
|
||||
};
|
||||
global.performance = {
|
||||
now() {
|
||||
return now;
|
||||
},
|
||||
global.postMessage = function(messageKey, targetOrigin) {
|
||||
const postMessageEvent = {source: window, data: messageKey};
|
||||
if (postMessageCallback) {
|
||||
postMessageCallback(postMessageEvent);
|
||||
}
|
||||
};
|
||||
|
||||
jest.resetModules();
|
||||
@@ -75,17 +69,17 @@ describe('ReactDOMRoot', () => {
|
||||
it('renders children', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
});
|
||||
|
||||
it('unmounts children', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
root.unmount();
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('');
|
||||
});
|
||||
|
||||
@@ -97,7 +91,7 @@ describe('ReactDOMRoot', () => {
|
||||
ops.push('inside callback: ' + container.textContent);
|
||||
});
|
||||
ops.push('before committing: ' + container.textContent);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
ops.push('after committing: ' + container.textContent);
|
||||
expect(ops).toEqual([
|
||||
'before committing: ',
|
||||
@@ -110,7 +104,7 @@ describe('ReactDOMRoot', () => {
|
||||
it('resolves `work.then` callback synchronously if the work already committed', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
const work = root.render(<AsyncMode>Hi</AsyncMode>);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
let ops = [];
|
||||
work.then(() => {
|
||||
ops.push('inside callback');
|
||||
@@ -138,7 +132,7 @@ describe('ReactDOMRoot', () => {
|
||||
<span />
|
||||
</div>,
|
||||
);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
|
||||
// Accepts `hydrate` option
|
||||
const container2 = document.createElement('div');
|
||||
@@ -149,7 +143,7 @@ describe('ReactDOMRoot', () => {
|
||||
<span />
|
||||
</div>,
|
||||
);
|
||||
expect(flush).toWarnDev('Extra attributes');
|
||||
expect(jest.runAllTimers).toWarnDev('Extra attributes');
|
||||
});
|
||||
|
||||
it('does not clear existing children', async () => {
|
||||
@@ -161,7 +155,7 @@ describe('ReactDOMRoot', () => {
|
||||
<span>d</span>
|
||||
</div>,
|
||||
);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('abcd');
|
||||
root.render(
|
||||
<div>
|
||||
@@ -169,7 +163,7 @@ describe('ReactDOMRoot', () => {
|
||||
<span>c</span>
|
||||
</div>,
|
||||
);
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('abdc');
|
||||
});
|
||||
|
||||
@@ -205,7 +199,7 @@ describe('ReactDOMRoot', () => {
|
||||
</AsyncMode>,
|
||||
);
|
||||
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
|
||||
// Hasn't updated yet
|
||||
expect(container.textContent).toEqual('');
|
||||
@@ -234,7 +228,7 @@ describe('ReactDOMRoot', () => {
|
||||
const batch = root.createBatch();
|
||||
batch.render(<Foo>Hi</Foo>);
|
||||
// Flush all async work.
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
// Root should complete without committing.
|
||||
expect(ops).toEqual(['Foo']);
|
||||
expect(container.textContent).toEqual('');
|
||||
@@ -252,7 +246,7 @@ describe('ReactDOMRoot', () => {
|
||||
const batch = root.createBatch();
|
||||
batch.render(<AsyncMode>Foo</AsyncMode>);
|
||||
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
|
||||
// Hasn't updated yet
|
||||
expect(container.textContent).toEqual('');
|
||||
@@ -292,7 +286,7 @@ describe('ReactDOMRoot', () => {
|
||||
const root = ReactDOM.unstable_createRoot(container);
|
||||
root.render(<AsyncMode>1</AsyncMode>);
|
||||
|
||||
expire(2000);
|
||||
advanceCurrentTime(2000);
|
||||
// This batch has a later expiration time than the earlier update.
|
||||
const batch = root.createBatch();
|
||||
|
||||
@@ -300,7 +294,7 @@ describe('ReactDOMRoot', () => {
|
||||
batch.commit();
|
||||
expect(container.textContent).toEqual('');
|
||||
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('1');
|
||||
});
|
||||
|
||||
@@ -327,7 +321,7 @@ describe('ReactDOMRoot', () => {
|
||||
batch1.render(1);
|
||||
|
||||
// This batch has a later expiration time
|
||||
expire(2000);
|
||||
advanceCurrentTime(2000);
|
||||
const batch2 = root.createBatch();
|
||||
batch2.render(2);
|
||||
|
||||
@@ -346,7 +340,7 @@ describe('ReactDOMRoot', () => {
|
||||
batch1.render(1);
|
||||
|
||||
// This batch has a later expiration time
|
||||
expire(2000);
|
||||
advanceCurrentTime(2000);
|
||||
const batch2 = root.createBatch();
|
||||
batch2.render(2);
|
||||
|
||||
@@ -356,7 +350,7 @@ describe('ReactDOMRoot', () => {
|
||||
expect(container.textContent).toEqual('2');
|
||||
|
||||
batch1.commit();
|
||||
flush();
|
||||
jest.runAllTimers();
|
||||
expect(container.textContent).toEqual('1');
|
||||
});
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ describe('ReactDOMServerIntegration', () => {
|
||||
resetModules();
|
||||
});
|
||||
|
||||
describe('context', function() {
|
||||
describe('legacy context', function() {
|
||||
let PurpleContext, RedContext;
|
||||
beforeEach(() => {
|
||||
class Parent extends React.Component {
|
||||
195
packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js
vendored
Normal file
195
packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* 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';
|
||||
|
||||
const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegrationTestUtils');
|
||||
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactDOMServer;
|
||||
|
||||
function initModules() {
|
||||
// Reset warning cache.
|
||||
jest.resetModuleRegistry();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
|
||||
// Make them available to the helpers.
|
||||
return {
|
||||
ReactDOM,
|
||||
ReactDOMServer,
|
||||
};
|
||||
}
|
||||
|
||||
const {resetModules, itRenders} = ReactDOMServerIntegrationUtils(initModules);
|
||||
|
||||
describe('ReactDOMServerIntegration', () => {
|
||||
beforeEach(() => {
|
||||
resetModules();
|
||||
});
|
||||
|
||||
describe('context', function() {
|
||||
let PurpleContext, RedContext, Consumer;
|
||||
beforeEach(() => {
|
||||
let Context = React.createContext('none');
|
||||
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Context.Provider value={this.props.text}>
|
||||
{this.props.children}
|
||||
</Context.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
Consumer = Context.Consumer;
|
||||
PurpleContext = props => <Parent text="purple">{props.children}</Parent>;
|
||||
RedContext = props => <Parent text="red">{props.children}</Parent>;
|
||||
});
|
||||
|
||||
itRenders('class child with context', async render => {
|
||||
class ClassChildWithContext extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Consumer>{text => text}</Consumer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const e = await render(
|
||||
<PurpleContext>
|
||||
<ClassChildWithContext />
|
||||
</PurpleContext>,
|
||||
);
|
||||
expect(e.textContent).toBe('purple');
|
||||
});
|
||||
|
||||
itRenders('stateless child with context', async render => {
|
||||
function StatelessChildWithContext(props) {
|
||||
return <Consumer>{text => text}</Consumer>;
|
||||
}
|
||||
|
||||
const e = await render(
|
||||
<PurpleContext>
|
||||
<StatelessChildWithContext />
|
||||
</PurpleContext>,
|
||||
);
|
||||
expect(e.textContent).toBe('purple');
|
||||
});
|
||||
|
||||
itRenders('class child with default context', async render => {
|
||||
class ClassChildWithWrongContext extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div id="classWrongChild">
|
||||
<Consumer>{text => text}</Consumer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const e = await render(<ClassChildWithWrongContext />);
|
||||
expect(e.textContent).toBe('none');
|
||||
});
|
||||
|
||||
itRenders('stateless child with wrong context', async render => {
|
||||
function StatelessChildWithWrongContext(props) {
|
||||
return (
|
||||
<div id="statelessWrongChild">
|
||||
<Consumer>{text => text}</Consumer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const e = await render(<StatelessChildWithWrongContext />);
|
||||
expect(e.textContent).toBe('none');
|
||||
});
|
||||
|
||||
itRenders('with context passed through to a grandchild', async render => {
|
||||
function Grandchild(props) {
|
||||
return (
|
||||
<div>
|
||||
<Consumer>{text => text}</Consumer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const Child = props => <Grandchild />;
|
||||
|
||||
const e = await render(
|
||||
<PurpleContext>
|
||||
<Child />
|
||||
</PurpleContext>,
|
||||
);
|
||||
expect(e.textContent).toBe('purple');
|
||||
});
|
||||
|
||||
itRenders('a child context overriding a parent context', async render => {
|
||||
const Grandchild = props => {
|
||||
return (
|
||||
<div>
|
||||
<Consumer>{text => text}</Consumer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const e = await render(
|
||||
<PurpleContext>
|
||||
<RedContext>
|
||||
<Grandchild />
|
||||
</RedContext>
|
||||
</PurpleContext>,
|
||||
);
|
||||
expect(e.textContent).toBe('red');
|
||||
});
|
||||
|
||||
itRenders('multiple contexts', async render => {
|
||||
const Theme = React.createContext('dark');
|
||||
const Language = React.createContext('french');
|
||||
class Parent extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<Theme.Provider value="light">
|
||||
<Child />
|
||||
</Theme.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Child() {
|
||||
return (
|
||||
<Language.Provider value="english">
|
||||
<Grandchild />
|
||||
</Language.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
const Grandchild = props => {
|
||||
return (
|
||||
<div>
|
||||
<Theme.Consumer>
|
||||
{theme => <div id="theme">{theme}</div>}
|
||||
</Theme.Consumer>
|
||||
<Language.Consumer>
|
||||
{language => <div id="language">{language}</div>}
|
||||
</Language.Consumer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const e = await render(<Parent />);
|
||||
expect(e.querySelector('#theme').textContent).toBe('light');
|
||||
expect(e.querySelector('#language').textContent).toBe('english');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -69,9 +69,9 @@ describe('ReactDOMServerLifecycles', () => {
|
||||
});
|
||||
|
||||
describe('react-lifecycles-compat', () => {
|
||||
const polyfill = require('react-lifecycles-compat');
|
||||
const {polyfill} = require('react-lifecycles-compat');
|
||||
|
||||
it('should not warn about deprecated cWM/cWRP for polyfilled components', () => {
|
||||
it('should not warn for components with polyfilled getDerivedStateFromProps', () => {
|
||||
class PolyfilledComponent extends React.Component {
|
||||
state = {};
|
||||
static getDerivedStateFromProps() {
|
||||
@@ -84,7 +84,35 @@ describe('ReactDOMServerLifecycles', () => {
|
||||
|
||||
polyfill(PolyfilledComponent);
|
||||
|
||||
ReactDOMServer.renderToString(<PolyfilledComponent />);
|
||||
const container = document.createElement('div');
|
||||
ReactDOMServer.renderToString(
|
||||
<React.StrictMode>
|
||||
<PolyfilledComponent />
|
||||
</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');
|
||||
ReactDOMServer.renderToString(
|
||||
<React.StrictMode>
|
||||
<PolyfilledComponent />
|
||||
</React.StrictMode>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
'use strict';
|
||||
|
||||
let React;
|
||||
let ReactCallReturn;
|
||||
let ReactDOMServer;
|
||||
let PropTypes;
|
||||
|
||||
@@ -23,7 +22,6 @@ describe('ReactDOMServer', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactCallReturn = require('react-call-return');
|
||||
PropTypes = require('prop-types');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
});
|
||||
@@ -623,25 +621,6 @@ describe('ReactDOMServer', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw rendering call/return on the server', () => {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(
|
||||
<div>{ReactCallReturn.unstable_createReturn(42)}</div>,
|
||||
);
|
||||
}).toThrow(
|
||||
'The experimental Call and Return types are not currently supported by the server renderer.',
|
||||
);
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(
|
||||
<div>
|
||||
{ReactCallReturn.unstable_createCall(null, function() {}, {})}
|
||||
</div>,
|
||||
);
|
||||
}).toThrow(
|
||||
'The experimental Call and Return types are not currently supported by the server renderer.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn when server rendering a class with a render method that does not extend React.Component', () => {
|
||||
class ClassWithRenderNotExtended {
|
||||
render() {
|
||||
|
||||
@@ -35,6 +35,7 @@ Array [
|
||||
"ended",
|
||||
"error",
|
||||
"focus",
|
||||
"gotPointerCapture",
|
||||
"input",
|
||||
"invalid",
|
||||
"keyDown",
|
||||
@@ -44,6 +45,7 @@ Array [
|
||||
"loadStart",
|
||||
"loadedData",
|
||||
"loadedMetadata",
|
||||
"lostPointerCapture",
|
||||
"mouseDown",
|
||||
"mouseEnter",
|
||||
"mouseLeave",
|
||||
@@ -55,6 +57,14 @@ Array [
|
||||
"pause",
|
||||
"play",
|
||||
"playing",
|
||||
"pointerCancel",
|
||||
"pointerDown",
|
||||
"pointerEnter",
|
||||
"pointerLeave",
|
||||
"pointerMove",
|
||||
"pointerOut",
|
||||
"pointerOver",
|
||||
"pointerUp",
|
||||
"progress",
|
||||
"rateChange",
|
||||
"reset",
|
||||
|
||||
@@ -53,9 +53,7 @@ module.exports = function(initModules) {
|
||||
console.error.calls.count() !== 0
|
||||
) {
|
||||
console.log(
|
||||
`We expected ${
|
||||
count
|
||||
} warning(s), but saw ${console.error.calls.count()} warning(s).`,
|
||||
`We expected ${count} warning(s), but saw ${console.error.calls.count()} warning(s).`,
|
||||
);
|
||||
if (console.error.calls.count() > 0) {
|
||||
console.log(`We saw these warnings:`);
|
||||
@@ -237,9 +235,7 @@ module.exports = function(initModules) {
|
||||
testFn(clientCleanRender));
|
||||
it(`renders ${desc} with client render on top of good server markup`, () =>
|
||||
testFn(clientRenderOnServerString));
|
||||
it(`renders ${
|
||||
desc
|
||||
} with client render on top of bad server markup`, async () => {
|
||||
it(`renders ${desc} with client render on top of bad server markup`, async () => {
|
||||
try {
|
||||
await testFn(clientRenderOnBadMarkup);
|
||||
} catch (x) {
|
||||
|
||||
548
packages/react-dom/src/client/ReactDOM.js
vendored
548
packages/react-dom/src/client/ReactDOM.js
vendored
@@ -14,12 +14,12 @@ import type {
|
||||
FiberRoot,
|
||||
Batch as FiberRootBatch,
|
||||
} from 'react-reconciler/src/ReactFiberRoot';
|
||||
import type {Container} from './ReactDOMHostConfig';
|
||||
|
||||
import '../shared/checkReact';
|
||||
import './ReactDOMClientInjection';
|
||||
|
||||
import ReactFiberReconciler from 'react-reconciler';
|
||||
// TODO: direct imports like some-package/src/* are bad. Fix me.
|
||||
import * as DOMRenderer from 'react-reconciler/inline.dom';
|
||||
import * as ReactPortal from 'shared/ReactPortal';
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
import * as ReactGenericBatching from 'events/ReactGenericBatching';
|
||||
@@ -29,7 +29,6 @@ import * as EventPluginRegistry from 'events/EventPluginRegistry';
|
||||
import * as EventPropagators from 'events/EventPropagators';
|
||||
import * as ReactInstanceMap from 'shared/ReactInstanceMap';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
import * as ReactDOMFrameScheduling from 'shared/ReactDOMFrameScheduling';
|
||||
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
|
||||
import getComponentName from 'shared/getComponentName';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
@@ -38,49 +37,27 @@ import warning from 'fbjs/lib/warning';
|
||||
|
||||
import * as ReactDOMComponentTree from './ReactDOMComponentTree';
|
||||
import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
|
||||
import * as ReactInputSelection from './ReactInputSelection';
|
||||
import setTextContent from './setTextContent';
|
||||
import validateDOMNesting from './validateDOMNesting';
|
||||
import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
|
||||
import * as ReactDOMEventListener from '../events/ReactDOMEventListener';
|
||||
import {getChildNamespace} from '../shared/DOMNamespaces';
|
||||
import {
|
||||
ELEMENT_NODE,
|
||||
TEXT_NODE,
|
||||
COMMENT_NODE,
|
||||
DOCUMENT_NODE,
|
||||
DOCUMENT_FRAGMENT_NODE,
|
||||
} from '../shared/HTMLNodeType';
|
||||
import {ROOT_ATTRIBUTE_NAME} from '../shared/DOMProperty';
|
||||
const {
|
||||
createElement,
|
||||
createTextNode,
|
||||
setInitialProperties,
|
||||
diffProperties,
|
||||
updateProperties,
|
||||
diffHydratedProperties,
|
||||
diffHydratedText,
|
||||
warnForUnmatchedText,
|
||||
warnForDeletedHydratableElement,
|
||||
warnForDeletedHydratableText,
|
||||
warnForInsertedHydratedElement,
|
||||
warnForInsertedHydratedText,
|
||||
} = ReactDOMFiberComponent;
|
||||
const {updatedAncestorInfo} = validateDOMNesting;
|
||||
const {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
|
||||
|
||||
let SUPPRESS_HYDRATION_WARNING;
|
||||
let topLevelUpdateWarnings;
|
||||
let warnOnInvalidCallback;
|
||||
let didWarnAboutUnstableCreatePortal = false;
|
||||
|
||||
if (__DEV__) {
|
||||
SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
|
||||
if (
|
||||
typeof Map !== 'function' ||
|
||||
// $FlowIssue Flow incorrectly thinks Map has no prototype
|
||||
Map.prototype == null ||
|
||||
typeof Map.prototype.forEach !== 'function' ||
|
||||
typeof Set !== 'function' ||
|
||||
// $FlowIssue Flow incorrectly thinks Set has no prototype
|
||||
Set.prototype == null ||
|
||||
typeof Set.prototype.clear !== 'function' ||
|
||||
typeof Set.prototype.forEach !== 'function'
|
||||
@@ -157,26 +134,6 @@ type DOMContainer =
|
||||
_reactRootContainer: ?Root,
|
||||
});
|
||||
|
||||
type Container = Element | Document;
|
||||
type Props = {
|
||||
autoFocus?: boolean,
|
||||
children?: mixed,
|
||||
hidden?: boolean,
|
||||
suppressHydrationWarning?: boolean,
|
||||
};
|
||||
type Instance = Element;
|
||||
type TextInstance = Text;
|
||||
|
||||
type HostContextDev = {
|
||||
namespace: string,
|
||||
ancestorInfo: mixed,
|
||||
};
|
||||
type HostContextProd = string;
|
||||
type HostContext = HostContextDev | HostContextProd;
|
||||
|
||||
let eventsEnabled: ?boolean = null;
|
||||
let selectionInformation: ?mixed = null;
|
||||
|
||||
type Batch = FiberRootBatch & {
|
||||
render(children: ReactNodeList): Work,
|
||||
then(onComplete: () => mixed): void,
|
||||
@@ -491,503 +448,6 @@ function shouldHydrateDueToLegacyHeuristic(container) {
|
||||
);
|
||||
}
|
||||
|
||||
function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
|
||||
switch (type) {
|
||||
case 'button':
|
||||
case 'input':
|
||||
case 'select':
|
||||
case 'textarea':
|
||||
return !!props.autoFocus;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const DOMRenderer = ReactFiberReconciler({
|
||||
getRootHostContext(rootContainerInstance: Container): HostContext {
|
||||
let type;
|
||||
let namespace;
|
||||
const nodeType = rootContainerInstance.nodeType;
|
||||
switch (nodeType) {
|
||||
case DOCUMENT_NODE:
|
||||
case DOCUMENT_FRAGMENT_NODE: {
|
||||
type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
|
||||
let root = (rootContainerInstance: any).documentElement;
|
||||
namespace = root ? root.namespaceURI : getChildNamespace(null, '');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const container: any =
|
||||
nodeType === COMMENT_NODE
|
||||
? rootContainerInstance.parentNode
|
||||
: rootContainerInstance;
|
||||
const ownNamespace = container.namespaceURI || null;
|
||||
type = container.tagName;
|
||||
namespace = getChildNamespace(ownNamespace, type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
const validatedTag = type.toLowerCase();
|
||||
const ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
|
||||
return {namespace, ancestorInfo};
|
||||
}
|
||||
return namespace;
|
||||
},
|
||||
|
||||
getChildHostContext(
|
||||
parentHostContext: HostContext,
|
||||
type: string,
|
||||
): HostContext {
|
||||
if (__DEV__) {
|
||||
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
|
||||
const namespace = getChildNamespace(parentHostContextDev.namespace, type);
|
||||
const ancestorInfo = updatedAncestorInfo(
|
||||
parentHostContextDev.ancestorInfo,
|
||||
type,
|
||||
null,
|
||||
);
|
||||
return {namespace, ancestorInfo};
|
||||
}
|
||||
const parentNamespace = ((parentHostContext: any): HostContextProd);
|
||||
return getChildNamespace(parentNamespace, type);
|
||||
},
|
||||
|
||||
getPublicInstance(instance) {
|
||||
return instance;
|
||||
},
|
||||
|
||||
prepareForCommit(): void {
|
||||
eventsEnabled = ReactBrowserEventEmitter.isEnabled();
|
||||
selectionInformation = ReactInputSelection.getSelectionInformation();
|
||||
ReactBrowserEventEmitter.setEnabled(false);
|
||||
},
|
||||
|
||||
resetAfterCommit(): void {
|
||||
ReactInputSelection.restoreSelection(selectionInformation);
|
||||
selectionInformation = null;
|
||||
ReactBrowserEventEmitter.setEnabled(eventsEnabled);
|
||||
eventsEnabled = null;
|
||||
},
|
||||
|
||||
createInstance(
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): Instance {
|
||||
let parentNamespace: string;
|
||||
if (__DEV__) {
|
||||
// TODO: take namespace into account when validating.
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
|
||||
if (
|
||||
typeof props.children === 'string' ||
|
||||
typeof props.children === 'number'
|
||||
) {
|
||||
const string = '' + props.children;
|
||||
const ownAncestorInfo = updatedAncestorInfo(
|
||||
hostContextDev.ancestorInfo,
|
||||
type,
|
||||
null,
|
||||
);
|
||||
validateDOMNesting(null, string, ownAncestorInfo);
|
||||
}
|
||||
parentNamespace = hostContextDev.namespace;
|
||||
} else {
|
||||
parentNamespace = ((hostContext: any): HostContextProd);
|
||||
}
|
||||
const domElement: Instance = createElement(
|
||||
type,
|
||||
props,
|
||||
rootContainerInstance,
|
||||
parentNamespace,
|
||||
);
|
||||
precacheFiberNode(internalInstanceHandle, domElement);
|
||||
updateFiberProps(domElement, props);
|
||||
return domElement;
|
||||
},
|
||||
|
||||
appendInitialChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.appendChild(child);
|
||||
},
|
||||
|
||||
finalizeInitialChildren(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
): boolean {
|
||||
setInitialProperties(domElement, type, props, rootContainerInstance);
|
||||
return shouldAutoFocusHostComponent(type, props);
|
||||
},
|
||||
|
||||
prepareUpdate(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
): null | Array<mixed> {
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
if (
|
||||
typeof newProps.children !== typeof oldProps.children &&
|
||||
(typeof newProps.children === 'string' ||
|
||||
typeof newProps.children === 'number')
|
||||
) {
|
||||
const string = '' + newProps.children;
|
||||
const ownAncestorInfo = updatedAncestorInfo(
|
||||
hostContextDev.ancestorInfo,
|
||||
type,
|
||||
null,
|
||||
);
|
||||
validateDOMNesting(null, string, ownAncestorInfo);
|
||||
}
|
||||
}
|
||||
return diffProperties(
|
||||
domElement,
|
||||
type,
|
||||
oldProps,
|
||||
newProps,
|
||||
rootContainerInstance,
|
||||
);
|
||||
},
|
||||
|
||||
shouldSetTextContent(type: string, props: Props): boolean {
|
||||
return (
|
||||
type === 'textarea' ||
|
||||
typeof props.children === 'string' ||
|
||||
typeof props.children === 'number' ||
|
||||
(typeof props.dangerouslySetInnerHTML === 'object' &&
|
||||
props.dangerouslySetInnerHTML !== null &&
|
||||
typeof props.dangerouslySetInnerHTML.__html === 'string')
|
||||
);
|
||||
},
|
||||
|
||||
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
||||
return !!props.hidden;
|
||||
},
|
||||
|
||||
createTextInstance(
|
||||
text: string,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): TextInstance {
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
validateDOMNesting(null, text, hostContextDev.ancestorInfo);
|
||||
}
|
||||
const textNode: TextInstance = createTextNode(text, rootContainerInstance);
|
||||
precacheFiberNode(internalInstanceHandle, textNode);
|
||||
return textNode;
|
||||
},
|
||||
|
||||
now: ReactDOMFrameScheduling.now,
|
||||
|
||||
mutation: {
|
||||
commitMount(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
): void {
|
||||
// Despite the naming that might imply otherwise, this method only
|
||||
// fires if there is an `Update` effect scheduled during mounting.
|
||||
// This happens if `finalizeInitialChildren` returns `true` (which it
|
||||
// does to implement the `autoFocus` attribute on the client). But
|
||||
// there are also other cases when this might happen (such as patching
|
||||
// up text content during hydration mismatch). So we'll check this again.
|
||||
if (shouldAutoFocusHostComponent(type, newProps)) {
|
||||
((domElement: any):
|
||||
| HTMLButtonElement
|
||||
| HTMLInputElement
|
||||
| HTMLSelectElement
|
||||
| HTMLTextAreaElement).focus();
|
||||
}
|
||||
},
|
||||
|
||||
commitUpdate(
|
||||
domElement: Instance,
|
||||
updatePayload: Array<mixed>,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
): void {
|
||||
// Update the props handle so that we know which props are the ones with
|
||||
// with current event handlers.
|
||||
updateFiberProps(domElement, newProps);
|
||||
// Apply the diff to the DOM node.
|
||||
updateProperties(domElement, updatePayload, type, oldProps, newProps);
|
||||
},
|
||||
|
||||
resetTextContent(domElement: Instance): void {
|
||||
setTextContent(domElement, '');
|
||||
},
|
||||
|
||||
commitTextUpdate(
|
||||
textInstance: TextInstance,
|
||||
oldText: string,
|
||||
newText: string,
|
||||
): void {
|
||||
textInstance.nodeValue = newText;
|
||||
},
|
||||
|
||||
appendChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.appendChild(child);
|
||||
},
|
||||
|
||||
appendChildToContainer(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).insertBefore(child, container);
|
||||
} else {
|
||||
container.appendChild(child);
|
||||
}
|
||||
},
|
||||
|
||||
insertBefore(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.insertBefore(child, beforeChild);
|
||||
},
|
||||
|
||||
insertInContainerBefore(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).insertBefore(child, beforeChild);
|
||||
} else {
|
||||
container.insertBefore(child, beforeChild);
|
||||
}
|
||||
},
|
||||
|
||||
removeChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.removeChild(child);
|
||||
},
|
||||
|
||||
removeChildFromContainer(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).removeChild(child);
|
||||
} else {
|
||||
container.removeChild(child);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
hydration: {
|
||||
canHydrateInstance(
|
||||
instance: Instance | TextInstance,
|
||||
type: string,
|
||||
props: Props,
|
||||
): null | Instance {
|
||||
if (
|
||||
instance.nodeType !== ELEMENT_NODE ||
|
||||
type.toLowerCase() !== instance.nodeName.toLowerCase()
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
// This has now been refined to an element node.
|
||||
return ((instance: any): Instance);
|
||||
},
|
||||
|
||||
canHydrateTextInstance(
|
||||
instance: Instance | TextInstance,
|
||||
text: string,
|
||||
): null | TextInstance {
|
||||
if (text === '' || instance.nodeType !== TEXT_NODE) {
|
||||
// Empty strings are not parsed by HTML so there won't be a correct match here.
|
||||
return null;
|
||||
}
|
||||
// This has now been refined to a text node.
|
||||
return ((instance: any): TextInstance);
|
||||
},
|
||||
|
||||
getNextHydratableSibling(
|
||||
instance: Instance | TextInstance,
|
||||
): null | Instance | TextInstance {
|
||||
let node = instance.nextSibling;
|
||||
// Skip non-hydratable nodes.
|
||||
while (
|
||||
node &&
|
||||
node.nodeType !== ELEMENT_NODE &&
|
||||
node.nodeType !== TEXT_NODE
|
||||
) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
return (node: any);
|
||||
},
|
||||
|
||||
getFirstHydratableChild(
|
||||
parentInstance: Container | Instance,
|
||||
): null | Instance | TextInstance {
|
||||
let next = parentInstance.firstChild;
|
||||
// Skip non-hydratable nodes.
|
||||
while (
|
||||
next &&
|
||||
next.nodeType !== ELEMENT_NODE &&
|
||||
next.nodeType !== TEXT_NODE
|
||||
) {
|
||||
next = next.nextSibling;
|
||||
}
|
||||
return (next: any);
|
||||
},
|
||||
|
||||
hydrateInstance(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): null | Array<mixed> {
|
||||
precacheFiberNode(internalInstanceHandle, instance);
|
||||
// TODO: Possibly defer this until the commit phase where all the events
|
||||
// get attached.
|
||||
updateFiberProps(instance, props);
|
||||
let parentNamespace: string;
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
parentNamespace = hostContextDev.namespace;
|
||||
} else {
|
||||
parentNamespace = ((hostContext: any): HostContextProd);
|
||||
}
|
||||
return diffHydratedProperties(
|
||||
instance,
|
||||
type,
|
||||
props,
|
||||
parentNamespace,
|
||||
rootContainerInstance,
|
||||
);
|
||||
},
|
||||
|
||||
hydrateTextInstance(
|
||||
textInstance: TextInstance,
|
||||
text: string,
|
||||
internalInstanceHandle: Object,
|
||||
): boolean {
|
||||
precacheFiberNode(internalInstanceHandle, textInstance);
|
||||
return diffHydratedText(textInstance, text);
|
||||
},
|
||||
|
||||
didNotMatchHydratedContainerTextInstance(
|
||||
parentContainer: Container,
|
||||
textInstance: TextInstance,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
warnForUnmatchedText(textInstance, text);
|
||||
}
|
||||
},
|
||||
|
||||
didNotMatchHydratedTextInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
textInstance: TextInstance,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
warnForUnmatchedText(textInstance, text);
|
||||
}
|
||||
},
|
||||
|
||||
didNotHydrateContainerInstance(
|
||||
parentContainer: Container,
|
||||
instance: Instance | TextInstance,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
if (instance.nodeType === 1) {
|
||||
warnForDeletedHydratableElement(parentContainer, (instance: any));
|
||||
} else {
|
||||
warnForDeletedHydratableText(parentContainer, (instance: any));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
didNotHydrateInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
instance: Instance | TextInstance,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
if (instance.nodeType === 1) {
|
||||
warnForDeletedHydratableElement(parentInstance, (instance: any));
|
||||
} else {
|
||||
warnForDeletedHydratableText(parentInstance, (instance: any));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
didNotFindHydratableContainerInstance(
|
||||
parentContainer: Container,
|
||||
type: string,
|
||||
props: Props,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
warnForInsertedHydratedElement(parentContainer, type, props);
|
||||
}
|
||||
},
|
||||
|
||||
didNotFindHydratableContainerTextInstance(
|
||||
parentContainer: Container,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
warnForInsertedHydratedText(parentContainer, text);
|
||||
}
|
||||
},
|
||||
|
||||
didNotFindHydratableInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
warnForInsertedHydratedElement(parentInstance, type, props);
|
||||
}
|
||||
},
|
||||
|
||||
didNotFindHydratableTextInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
warnForInsertedHydratedText(parentInstance, text);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
scheduleDeferredCallback: ReactDOMFrameScheduling.rIC,
|
||||
cancelDeferredCallback: ReactDOMFrameScheduling.cIC,
|
||||
});
|
||||
|
||||
ReactGenericBatching.injection.injectRenderer(DOMRenderer);
|
||||
|
||||
let warnedAboutHydrateAPI = false;
|
||||
|
||||
@@ -21,8 +21,16 @@ import * as ReactDOMFiberTextarea from './ReactDOMFiberTextarea';
|
||||
import * as inputValueTracking from './inputValueTracking';
|
||||
import setInnerHTML from './setInnerHTML';
|
||||
import setTextContent from './setTextContent';
|
||||
import {
|
||||
TOP_ERROR,
|
||||
TOP_INVALID,
|
||||
TOP_LOAD,
|
||||
TOP_RESET,
|
||||
TOP_SUBMIT,
|
||||
TOP_TOGGLE,
|
||||
} from '../events/DOMTopLevelEventTypes';
|
||||
import {listenTo, trapBubbledEvent} from '../events/ReactBrowserEventEmitter';
|
||||
import {mediaEventTypes} from '../events/BrowserEventConstants';
|
||||
import {mediaEventTypes} from '../events/DOMTopLevelEventTypes';
|
||||
import * as CSSPropertyOperations from '../shared/CSSPropertyOperations';
|
||||
import {Namespaces, getIntrinsicNamespace} from '../shared/DOMNamespaces';
|
||||
import {
|
||||
@@ -424,7 +432,11 @@ export function setInitialProperties(
|
||||
const isCustomComponentTag = isCustomComponent(tag, rawProps);
|
||||
if (__DEV__) {
|
||||
validatePropertiesInDevelopment(tag, rawProps);
|
||||
if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
|
||||
if (
|
||||
isCustomComponentTag &&
|
||||
!didWarnShadyDOM &&
|
||||
(domElement: any).shadyRoot
|
||||
) {
|
||||
warning(
|
||||
false,
|
||||
'%s is using shady DOM. Using shady DOM with React can ' +
|
||||
@@ -440,43 +452,41 @@ export function setInitialProperties(
|
||||
switch (tag) {
|
||||
case 'iframe':
|
||||
case 'object':
|
||||
trapBubbledEvent('topLoad', 'load', domElement);
|
||||
trapBubbledEvent(TOP_LOAD, domElement);
|
||||
props = rawProps;
|
||||
break;
|
||||
case 'video':
|
||||
case 'audio':
|
||||
// Create listener for each media event
|
||||
for (const event in mediaEventTypes) {
|
||||
if (mediaEventTypes.hasOwnProperty(event)) {
|
||||
trapBubbledEvent(event, mediaEventTypes[event], domElement);
|
||||
}
|
||||
for (let i = 0; i < mediaEventTypes.length; i++) {
|
||||
trapBubbledEvent(mediaEventTypes[i], domElement);
|
||||
}
|
||||
props = rawProps;
|
||||
break;
|
||||
case 'source':
|
||||
trapBubbledEvent('topError', 'error', domElement);
|
||||
trapBubbledEvent(TOP_ERROR, domElement);
|
||||
props = rawProps;
|
||||
break;
|
||||
case 'img':
|
||||
case 'image':
|
||||
case 'link':
|
||||
trapBubbledEvent('topError', 'error', domElement);
|
||||
trapBubbledEvent('topLoad', 'load', domElement);
|
||||
trapBubbledEvent(TOP_ERROR, domElement);
|
||||
trapBubbledEvent(TOP_LOAD, domElement);
|
||||
props = rawProps;
|
||||
break;
|
||||
case 'form':
|
||||
trapBubbledEvent('topReset', 'reset', domElement);
|
||||
trapBubbledEvent('topSubmit', 'submit', domElement);
|
||||
trapBubbledEvent(TOP_RESET, domElement);
|
||||
trapBubbledEvent(TOP_SUBMIT, domElement);
|
||||
props = rawProps;
|
||||
break;
|
||||
case 'details':
|
||||
trapBubbledEvent('topToggle', 'toggle', domElement);
|
||||
trapBubbledEvent(TOP_TOGGLE, domElement);
|
||||
props = rawProps;
|
||||
break;
|
||||
case 'input':
|
||||
ReactDOMFiberInput.initWrapperState(domElement, rawProps);
|
||||
props = ReactDOMFiberInput.getHostProps(domElement, rawProps);
|
||||
trapBubbledEvent('topInvalid', 'invalid', domElement);
|
||||
trapBubbledEvent(TOP_INVALID, domElement);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
@@ -488,7 +498,7 @@ export function setInitialProperties(
|
||||
case 'select':
|
||||
ReactDOMFiberSelect.initWrapperState(domElement, rawProps);
|
||||
props = ReactDOMFiberSelect.getHostProps(domElement, rawProps);
|
||||
trapBubbledEvent('topInvalid', 'invalid', domElement);
|
||||
trapBubbledEvent(TOP_INVALID, domElement);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
@@ -496,7 +506,7 @@ export function setInitialProperties(
|
||||
case 'textarea':
|
||||
ReactDOMFiberTextarea.initWrapperState(domElement, rawProps);
|
||||
props = ReactDOMFiberTextarea.getHostProps(domElement, rawProps);
|
||||
trapBubbledEvent('topInvalid', 'invalid', domElement);
|
||||
trapBubbledEvent(TOP_INVALID, domElement);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
@@ -814,7 +824,11 @@ export function diffHydratedProperties(
|
||||
suppressHydrationWarning = rawProps[SUPPRESS_HYDRATION_WARNING] === true;
|
||||
isCustomComponentTag = isCustomComponent(tag, rawProps);
|
||||
validatePropertiesInDevelopment(tag, rawProps);
|
||||
if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
|
||||
if (
|
||||
isCustomComponentTag &&
|
||||
!didWarnShadyDOM &&
|
||||
(domElement: any).shadyRoot
|
||||
) {
|
||||
warning(
|
||||
false,
|
||||
'%s is using shady DOM. Using shady DOM with React can ' +
|
||||
@@ -829,36 +843,34 @@ export function diffHydratedProperties(
|
||||
switch (tag) {
|
||||
case 'iframe':
|
||||
case 'object':
|
||||
trapBubbledEvent('topLoad', 'load', domElement);
|
||||
trapBubbledEvent(TOP_LOAD, domElement);
|
||||
break;
|
||||
case 'video':
|
||||
case 'audio':
|
||||
// Create listener for each media event
|
||||
for (const event in mediaEventTypes) {
|
||||
if (mediaEventTypes.hasOwnProperty(event)) {
|
||||
trapBubbledEvent(event, mediaEventTypes[event], domElement);
|
||||
}
|
||||
for (let i = 0; i < mediaEventTypes.length; i++) {
|
||||
trapBubbledEvent(mediaEventTypes[i], domElement);
|
||||
}
|
||||
break;
|
||||
case 'source':
|
||||
trapBubbledEvent('topError', 'error', domElement);
|
||||
trapBubbledEvent(TOP_ERROR, domElement);
|
||||
break;
|
||||
case 'img':
|
||||
case 'image':
|
||||
case 'link':
|
||||
trapBubbledEvent('topError', 'error', domElement);
|
||||
trapBubbledEvent('topLoad', 'load', domElement);
|
||||
trapBubbledEvent(TOP_ERROR, domElement);
|
||||
trapBubbledEvent(TOP_LOAD, domElement);
|
||||
break;
|
||||
case 'form':
|
||||
trapBubbledEvent('topReset', 'reset', domElement);
|
||||
trapBubbledEvent('topSubmit', 'submit', domElement);
|
||||
trapBubbledEvent(TOP_RESET, domElement);
|
||||
trapBubbledEvent(TOP_SUBMIT, domElement);
|
||||
break;
|
||||
case 'details':
|
||||
trapBubbledEvent('topToggle', 'toggle', domElement);
|
||||
trapBubbledEvent(TOP_TOGGLE, domElement);
|
||||
break;
|
||||
case 'input':
|
||||
ReactDOMFiberInput.initWrapperState(domElement, rawProps);
|
||||
trapBubbledEvent('topInvalid', 'invalid', domElement);
|
||||
trapBubbledEvent(TOP_INVALID, domElement);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
@@ -868,14 +880,14 @@ export function diffHydratedProperties(
|
||||
break;
|
||||
case 'select':
|
||||
ReactDOMFiberSelect.initWrapperState(domElement, rawProps);
|
||||
trapBubbledEvent('topInvalid', 'invalid', domElement);
|
||||
trapBubbledEvent(TOP_INVALID, domElement);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
break;
|
||||
case 'textarea':
|
||||
ReactDOMFiberTextarea.initWrapperState(domElement, rawProps);
|
||||
trapBubbledEvent('topInvalid', 'invalid', domElement);
|
||||
trapBubbledEvent(TOP_INVALID, domElement);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
|
||||
581
packages/react-dom/src/client/ReactDOMHostConfig.js
vendored
Normal file
581
packages/react-dom/src/client/ReactDOMHostConfig.js
vendored
Normal file
@@ -0,0 +1,581 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
import * as ReactScheduler from 'react-scheduler';
|
||||
|
||||
import * as ReactDOMComponentTree from './ReactDOMComponentTree';
|
||||
import * as ReactDOMFiberComponent from './ReactDOMFiberComponent';
|
||||
import * as ReactInputSelection from './ReactInputSelection';
|
||||
import setTextContent from './setTextContent';
|
||||
import validateDOMNesting from './validateDOMNesting';
|
||||
import * as ReactBrowserEventEmitter from '../events/ReactBrowserEventEmitter';
|
||||
import {getChildNamespace} from '../shared/DOMNamespaces';
|
||||
import {
|
||||
ELEMENT_NODE,
|
||||
TEXT_NODE,
|
||||
COMMENT_NODE,
|
||||
DOCUMENT_NODE,
|
||||
DOCUMENT_FRAGMENT_NODE,
|
||||
} from '../shared/HTMLNodeType';
|
||||
|
||||
export type Type = string;
|
||||
export type Props = {
|
||||
autoFocus?: boolean,
|
||||
children?: mixed,
|
||||
hidden?: boolean,
|
||||
suppressHydrationWarning?: boolean,
|
||||
dangerouslySetInnerHTML?: mixed,
|
||||
};
|
||||
export type Container = Element | Document;
|
||||
export type Instance = Element;
|
||||
export type TextInstance = Text;
|
||||
export type HydratableInstance = Element | Text;
|
||||
export type PublicInstance = Element | Text;
|
||||
type HostContextDev = {
|
||||
namespace: string,
|
||||
ancestorInfo: mixed,
|
||||
};
|
||||
type HostContextProd = string;
|
||||
export type HostContext = HostContextDev | HostContextProd;
|
||||
export type UpdatePayload = Array<mixed>;
|
||||
export type ChildSet = void; // Unused
|
||||
|
||||
const {
|
||||
createElement,
|
||||
createTextNode,
|
||||
setInitialProperties,
|
||||
diffProperties,
|
||||
updateProperties,
|
||||
diffHydratedProperties,
|
||||
diffHydratedText,
|
||||
warnForUnmatchedText,
|
||||
warnForDeletedHydratableElement,
|
||||
warnForDeletedHydratableText,
|
||||
warnForInsertedHydratedElement,
|
||||
warnForInsertedHydratedText,
|
||||
} = ReactDOMFiberComponent;
|
||||
const {updatedAncestorInfo} = validateDOMNesting;
|
||||
const {precacheFiberNode, updateFiberProps} = ReactDOMComponentTree;
|
||||
|
||||
let SUPPRESS_HYDRATION_WARNING;
|
||||
if (__DEV__) {
|
||||
SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';
|
||||
}
|
||||
|
||||
let eventsEnabled: ?boolean = null;
|
||||
let selectionInformation: ?mixed = null;
|
||||
|
||||
function shouldAutoFocusHostComponent(type: string, props: Props): boolean {
|
||||
switch (type) {
|
||||
case 'button':
|
||||
case 'input':
|
||||
case 'select':
|
||||
case 'textarea':
|
||||
return !!props.autoFocus;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export * from 'shared/HostConfigWithNoPersistence';
|
||||
|
||||
export function getRootHostContext(
|
||||
rootContainerInstance: Container,
|
||||
): HostContext {
|
||||
let type;
|
||||
let namespace;
|
||||
const nodeType = rootContainerInstance.nodeType;
|
||||
switch (nodeType) {
|
||||
case DOCUMENT_NODE:
|
||||
case DOCUMENT_FRAGMENT_NODE: {
|
||||
type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
|
||||
let root = (rootContainerInstance: any).documentElement;
|
||||
namespace = root ? root.namespaceURI : getChildNamespace(null, '');
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
const container: any =
|
||||
nodeType === COMMENT_NODE
|
||||
? rootContainerInstance.parentNode
|
||||
: rootContainerInstance;
|
||||
const ownNamespace = container.namespaceURI || null;
|
||||
type = container.tagName;
|
||||
namespace = getChildNamespace(ownNamespace, type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (__DEV__) {
|
||||
const validatedTag = type.toLowerCase();
|
||||
const ancestorInfo = updatedAncestorInfo(null, validatedTag, null);
|
||||
return {namespace, ancestorInfo};
|
||||
}
|
||||
return namespace;
|
||||
}
|
||||
|
||||
export function getChildHostContext(
|
||||
parentHostContext: HostContext,
|
||||
type: string,
|
||||
rootContainerInstance: Container,
|
||||
): HostContext {
|
||||
if (__DEV__) {
|
||||
const parentHostContextDev = ((parentHostContext: any): HostContextDev);
|
||||
const namespace = getChildNamespace(parentHostContextDev.namespace, type);
|
||||
const ancestorInfo = updatedAncestorInfo(
|
||||
parentHostContextDev.ancestorInfo,
|
||||
type,
|
||||
null,
|
||||
);
|
||||
return {namespace, ancestorInfo};
|
||||
}
|
||||
const parentNamespace = ((parentHostContext: any): HostContextProd);
|
||||
return getChildNamespace(parentNamespace, type);
|
||||
}
|
||||
|
||||
export function getPublicInstance(instance: Instance): * {
|
||||
return instance;
|
||||
}
|
||||
|
||||
export function prepareForCommit(containerInfo: Container): void {
|
||||
eventsEnabled = ReactBrowserEventEmitter.isEnabled();
|
||||
selectionInformation = ReactInputSelection.getSelectionInformation();
|
||||
ReactBrowserEventEmitter.setEnabled(false);
|
||||
}
|
||||
|
||||
export function resetAfterCommit(containerInfo: Container): void {
|
||||
ReactInputSelection.restoreSelection(selectionInformation);
|
||||
selectionInformation = null;
|
||||
ReactBrowserEventEmitter.setEnabled(eventsEnabled);
|
||||
eventsEnabled = null;
|
||||
}
|
||||
|
||||
export function createInstance(
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): Instance {
|
||||
let parentNamespace: string;
|
||||
if (__DEV__) {
|
||||
// TODO: take namespace into account when validating.
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
|
||||
if (
|
||||
typeof props.children === 'string' ||
|
||||
typeof props.children === 'number'
|
||||
) {
|
||||
const string = '' + props.children;
|
||||
const ownAncestorInfo = updatedAncestorInfo(
|
||||
hostContextDev.ancestorInfo,
|
||||
type,
|
||||
null,
|
||||
);
|
||||
validateDOMNesting(null, string, ownAncestorInfo);
|
||||
}
|
||||
parentNamespace = hostContextDev.namespace;
|
||||
} else {
|
||||
parentNamespace = ((hostContext: any): HostContextProd);
|
||||
}
|
||||
const domElement: Instance = createElement(
|
||||
type,
|
||||
props,
|
||||
rootContainerInstance,
|
||||
parentNamespace,
|
||||
);
|
||||
precacheFiberNode(internalInstanceHandle, domElement);
|
||||
updateFiberProps(domElement, props);
|
||||
return domElement;
|
||||
}
|
||||
|
||||
export function appendInitialChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.appendChild(child);
|
||||
}
|
||||
|
||||
export function finalizeInitialChildren(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
): boolean {
|
||||
setInitialProperties(domElement, type, props, rootContainerInstance);
|
||||
return shouldAutoFocusHostComponent(type, props);
|
||||
}
|
||||
|
||||
export function prepareUpdate(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
): null | Array<mixed> {
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
if (
|
||||
typeof newProps.children !== typeof oldProps.children &&
|
||||
(typeof newProps.children === 'string' ||
|
||||
typeof newProps.children === 'number')
|
||||
) {
|
||||
const string = '' + newProps.children;
|
||||
const ownAncestorInfo = updatedAncestorInfo(
|
||||
hostContextDev.ancestorInfo,
|
||||
type,
|
||||
null,
|
||||
);
|
||||
validateDOMNesting(null, string, ownAncestorInfo);
|
||||
}
|
||||
}
|
||||
return diffProperties(
|
||||
domElement,
|
||||
type,
|
||||
oldProps,
|
||||
newProps,
|
||||
rootContainerInstance,
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldSetTextContent(type: string, props: Props): boolean {
|
||||
return (
|
||||
type === 'textarea' ||
|
||||
typeof props.children === 'string' ||
|
||||
typeof props.children === 'number' ||
|
||||
(typeof props.dangerouslySetInnerHTML === 'object' &&
|
||||
props.dangerouslySetInnerHTML !== null &&
|
||||
typeof props.dangerouslySetInnerHTML.__html === 'string')
|
||||
);
|
||||
}
|
||||
|
||||
export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
||||
return !!props.hidden;
|
||||
}
|
||||
|
||||
export function createTextInstance(
|
||||
text: string,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): TextInstance {
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
validateDOMNesting(null, text, hostContextDev.ancestorInfo);
|
||||
}
|
||||
const textNode: TextInstance = createTextNode(text, rootContainerInstance);
|
||||
precacheFiberNode(internalInstanceHandle, textNode);
|
||||
return textNode;
|
||||
}
|
||||
|
||||
export const now = ReactScheduler.now;
|
||||
export const isPrimaryRenderer = true;
|
||||
export const scheduleDeferredCallback = ReactScheduler.scheduleWork;
|
||||
export const cancelDeferredCallback = ReactScheduler.cancelScheduledWork;
|
||||
|
||||
// -------------------
|
||||
// Mutation
|
||||
// -------------------
|
||||
|
||||
export const supportsMutation = true;
|
||||
|
||||
export function commitMount(
|
||||
domElement: Instance,
|
||||
type: string,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
): void {
|
||||
// Despite the naming that might imply otherwise, this method only
|
||||
// fires if there is an `Update` effect scheduled during mounting.
|
||||
// This happens if `finalizeInitialChildren` returns `true` (which it
|
||||
// does to implement the `autoFocus` attribute on the client). But
|
||||
// there are also other cases when this might happen (such as patching
|
||||
// up text content during hydration mismatch). So we'll check this again.
|
||||
if (shouldAutoFocusHostComponent(type, newProps)) {
|
||||
((domElement: any):
|
||||
| HTMLButtonElement
|
||||
| HTMLInputElement
|
||||
| HTMLSelectElement
|
||||
| HTMLTextAreaElement).focus();
|
||||
}
|
||||
}
|
||||
|
||||
export function commitUpdate(
|
||||
domElement: Instance,
|
||||
updatePayload: Array<mixed>,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
): void {
|
||||
// Update the props handle so that we know which props are the ones with
|
||||
// with current event handlers.
|
||||
updateFiberProps(domElement, newProps);
|
||||
// Apply the diff to the DOM node.
|
||||
updateProperties(domElement, updatePayload, type, oldProps, newProps);
|
||||
}
|
||||
|
||||
export function resetTextContent(domElement: Instance): void {
|
||||
setTextContent(domElement, '');
|
||||
}
|
||||
|
||||
export function commitTextUpdate(
|
||||
textInstance: TextInstance,
|
||||
oldText: string,
|
||||
newText: string,
|
||||
): void {
|
||||
textInstance.nodeValue = newText;
|
||||
}
|
||||
|
||||
export function appendChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.appendChild(child);
|
||||
}
|
||||
|
||||
export function appendChildToContainer(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).insertBefore(child, container);
|
||||
} else {
|
||||
container.appendChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
export function insertBefore(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.insertBefore(child, beforeChild);
|
||||
}
|
||||
|
||||
export function insertInContainerBefore(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).insertBefore(child, beforeChild);
|
||||
} else {
|
||||
container.insertBefore(child, beforeChild);
|
||||
}
|
||||
}
|
||||
|
||||
export function removeChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance.removeChild(child);
|
||||
}
|
||||
|
||||
export function removeChildFromContainer(
|
||||
container: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
if (container.nodeType === COMMENT_NODE) {
|
||||
(container.parentNode: any).removeChild(child);
|
||||
} else {
|
||||
container.removeChild(child);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------
|
||||
// Hydration
|
||||
// -------------------
|
||||
|
||||
export const supportsHydration = true;
|
||||
|
||||
export function canHydrateInstance(
|
||||
instance: Instance | TextInstance,
|
||||
type: string,
|
||||
props: Props,
|
||||
): null | Instance {
|
||||
if (
|
||||
instance.nodeType !== ELEMENT_NODE ||
|
||||
type.toLowerCase() !== instance.nodeName.toLowerCase()
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
// This has now been refined to an element node.
|
||||
return ((instance: any): Instance);
|
||||
}
|
||||
|
||||
export function canHydrateTextInstance(
|
||||
instance: Instance | TextInstance,
|
||||
text: string,
|
||||
): null | TextInstance {
|
||||
if (text === '' || instance.nodeType !== TEXT_NODE) {
|
||||
// Empty strings are not parsed by HTML so there won't be a correct match here.
|
||||
return null;
|
||||
}
|
||||
// This has now been refined to a text node.
|
||||
return ((instance: any): TextInstance);
|
||||
}
|
||||
|
||||
export function getNextHydratableSibling(
|
||||
instance: Instance | TextInstance,
|
||||
): null | Instance | TextInstance {
|
||||
let node = instance.nextSibling;
|
||||
// Skip non-hydratable nodes.
|
||||
while (
|
||||
node &&
|
||||
node.nodeType !== ELEMENT_NODE &&
|
||||
node.nodeType !== TEXT_NODE
|
||||
) {
|
||||
node = node.nextSibling;
|
||||
}
|
||||
return (node: any);
|
||||
}
|
||||
|
||||
export function getFirstHydratableChild(
|
||||
parentInstance: Container | Instance,
|
||||
): null | Instance | TextInstance {
|
||||
let next = parentInstance.firstChild;
|
||||
// Skip non-hydratable nodes.
|
||||
while (
|
||||
next &&
|
||||
next.nodeType !== ELEMENT_NODE &&
|
||||
next.nodeType !== TEXT_NODE
|
||||
) {
|
||||
next = next.nextSibling;
|
||||
}
|
||||
return (next: any);
|
||||
}
|
||||
|
||||
export function hydrateInstance(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): null | Array<mixed> {
|
||||
precacheFiberNode(internalInstanceHandle, instance);
|
||||
// TODO: Possibly defer this until the commit phase where all the events
|
||||
// get attached.
|
||||
updateFiberProps(instance, props);
|
||||
let parentNamespace: string;
|
||||
if (__DEV__) {
|
||||
const hostContextDev = ((hostContext: any): HostContextDev);
|
||||
parentNamespace = hostContextDev.namespace;
|
||||
} else {
|
||||
parentNamespace = ((hostContext: any): HostContextProd);
|
||||
}
|
||||
return diffHydratedProperties(
|
||||
instance,
|
||||
type,
|
||||
props,
|
||||
parentNamespace,
|
||||
rootContainerInstance,
|
||||
);
|
||||
}
|
||||
|
||||
export function hydrateTextInstance(
|
||||
textInstance: TextInstance,
|
||||
text: string,
|
||||
internalInstanceHandle: Object,
|
||||
): boolean {
|
||||
precacheFiberNode(internalInstanceHandle, textInstance);
|
||||
return diffHydratedText(textInstance, text);
|
||||
}
|
||||
|
||||
export function didNotMatchHydratedContainerTextInstance(
|
||||
parentContainer: Container,
|
||||
textInstance: TextInstance,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
warnForUnmatchedText(textInstance, text);
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotMatchHydratedTextInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
textInstance: TextInstance,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
warnForUnmatchedText(textInstance, text);
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotHydrateContainerInstance(
|
||||
parentContainer: Container,
|
||||
instance: Instance | TextInstance,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
if (instance.nodeType === 1) {
|
||||
warnForDeletedHydratableElement(parentContainer, (instance: any));
|
||||
} else {
|
||||
warnForDeletedHydratableText(parentContainer, (instance: any));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotHydrateInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
instance: Instance | TextInstance,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
if (instance.nodeType === 1) {
|
||||
warnForDeletedHydratableElement(parentInstance, (instance: any));
|
||||
} else {
|
||||
warnForDeletedHydratableText(parentInstance, (instance: any));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotFindHydratableContainerInstance(
|
||||
parentContainer: Container,
|
||||
type: string,
|
||||
props: Props,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
warnForInsertedHydratedElement(parentContainer, type, props);
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotFindHydratableContainerTextInstance(
|
||||
parentContainer: Container,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__) {
|
||||
warnForInsertedHydratedText(parentContainer, text);
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotFindHydratableInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
warnForInsertedHydratedElement(parentInstance, type, props);
|
||||
}
|
||||
}
|
||||
|
||||
export function didNotFindHydratableTextInstance(
|
||||
parentType: string,
|
||||
parentProps: Props,
|
||||
parentInstance: Instance,
|
||||
text: string,
|
||||
) {
|
||||
if (__DEV__ && parentProps[SUPPRESS_HYDRATION_WARNING] !== true) {
|
||||
warnForInsertedHydratedText(parentInstance, text);
|
||||
}
|
||||
}
|
||||
@@ -63,20 +63,21 @@ function trackValueOnNode(node: any): ?ValueTracker {
|
||||
// (needed for certain tests that spyOn input values and Safari)
|
||||
if (
|
||||
node.hasOwnProperty(valueField) ||
|
||||
typeof descriptor === 'undefined' ||
|
||||
typeof descriptor.get !== 'function' ||
|
||||
typeof descriptor.set !== 'function'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {get, set} = descriptor;
|
||||
Object.defineProperty(node, valueField, {
|
||||
configurable: true,
|
||||
get: function() {
|
||||
return descriptor.get.call(this);
|
||||
return get.call(this);
|
||||
},
|
||||
set: function(value) {
|
||||
currentValue = '' + value;
|
||||
descriptor.set.call(this, value);
|
||||
set.call(this, value);
|
||||
},
|
||||
});
|
||||
// We could've passed this the first time
|
||||
|
||||
@@ -5,11 +5,23 @@
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import type {TopLevelTypes} from './BrowserEventConstants';
|
||||
import type {TopLevelType} from 'events/TopLevelEventTypes';
|
||||
|
||||
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
|
||||
import {
|
||||
TOP_BLUR,
|
||||
TOP_COMPOSITION_START,
|
||||
TOP_COMPOSITION_END,
|
||||
TOP_COMPOSITION_UPDATE,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_PRESS,
|
||||
TOP_KEY_UP,
|
||||
TOP_MOUSE_DOWN,
|
||||
TOP_TEXT_INPUT,
|
||||
TOP_PASTE,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import * as FallbackCompositionState from './FallbackCompositionState';
|
||||
import SyntheticCompositionEvent from './SyntheticCompositionEvent';
|
||||
import SyntheticInputEvent from './SyntheticInputEvent';
|
||||
@@ -50,10 +62,10 @@ const eventTypes = {
|
||||
captured: 'onBeforeInputCapture',
|
||||
},
|
||||
dependencies: [
|
||||
'topCompositionEnd',
|
||||
'topKeyPress',
|
||||
'topTextInput',
|
||||
'topPaste',
|
||||
TOP_COMPOSITION_END,
|
||||
TOP_KEY_PRESS,
|
||||
TOP_TEXT_INPUT,
|
||||
TOP_PASTE,
|
||||
],
|
||||
},
|
||||
compositionEnd: {
|
||||
@@ -62,12 +74,12 @@ const eventTypes = {
|
||||
captured: 'onCompositionEndCapture',
|
||||
},
|
||||
dependencies: [
|
||||
'topBlur',
|
||||
'topCompositionEnd',
|
||||
'topKeyDown',
|
||||
'topKeyPress',
|
||||
'topKeyUp',
|
||||
'topMouseDown',
|
||||
TOP_BLUR,
|
||||
TOP_COMPOSITION_END,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_PRESS,
|
||||
TOP_KEY_UP,
|
||||
TOP_MOUSE_DOWN,
|
||||
],
|
||||
},
|
||||
compositionStart: {
|
||||
@@ -76,12 +88,12 @@ const eventTypes = {
|
||||
captured: 'onCompositionStartCapture',
|
||||
},
|
||||
dependencies: [
|
||||
'topBlur',
|
||||
'topCompositionStart',
|
||||
'topKeyDown',
|
||||
'topKeyPress',
|
||||
'topKeyUp',
|
||||
'topMouseDown',
|
||||
TOP_BLUR,
|
||||
TOP_COMPOSITION_START,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_PRESS,
|
||||
TOP_KEY_UP,
|
||||
TOP_MOUSE_DOWN,
|
||||
],
|
||||
},
|
||||
compositionUpdate: {
|
||||
@@ -90,12 +102,12 @@ const eventTypes = {
|
||||
captured: 'onCompositionUpdateCapture',
|
||||
},
|
||||
dependencies: [
|
||||
'topBlur',
|
||||
'topCompositionUpdate',
|
||||
'topKeyDown',
|
||||
'topKeyPress',
|
||||
'topKeyUp',
|
||||
'topMouseDown',
|
||||
TOP_BLUR,
|
||||
TOP_COMPOSITION_UPDATE,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_PRESS,
|
||||
TOP_KEY_UP,
|
||||
TOP_MOUSE_DOWN,
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -124,11 +136,11 @@ function isKeypressCommand(nativeEvent) {
|
||||
*/
|
||||
function getCompositionEventType(topLevelType) {
|
||||
switch (topLevelType) {
|
||||
case 'topCompositionStart':
|
||||
case TOP_COMPOSITION_START:
|
||||
return eventTypes.compositionStart;
|
||||
case 'topCompositionEnd':
|
||||
case TOP_COMPOSITION_END:
|
||||
return eventTypes.compositionEnd;
|
||||
case 'topCompositionUpdate':
|
||||
case TOP_COMPOSITION_UPDATE:
|
||||
return eventTypes.compositionUpdate;
|
||||
}
|
||||
}
|
||||
@@ -142,7 +154,7 @@ function getCompositionEventType(topLevelType) {
|
||||
* @return {boolean}
|
||||
*/
|
||||
function isFallbackCompositionStart(topLevelType, nativeEvent) {
|
||||
return topLevelType === 'topKeyDown' && nativeEvent.keyCode === START_KEYCODE;
|
||||
return topLevelType === TOP_KEY_DOWN && nativeEvent.keyCode === START_KEYCODE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,16 +166,16 @@ function isFallbackCompositionStart(topLevelType, nativeEvent) {
|
||||
*/
|
||||
function isFallbackCompositionEnd(topLevelType, nativeEvent) {
|
||||
switch (topLevelType) {
|
||||
case 'topKeyUp':
|
||||
case TOP_KEY_UP:
|
||||
// Command keys insert or clear IME input.
|
||||
return END_KEYCODES.indexOf(nativeEvent.keyCode) !== -1;
|
||||
case 'topKeyDown':
|
||||
case TOP_KEY_DOWN:
|
||||
// Expect IME keyCode on each keydown. If we get any other
|
||||
// code we must have exited earlier.
|
||||
return nativeEvent.keyCode !== START_KEYCODE;
|
||||
case 'topKeyPress':
|
||||
case 'topMouseDown':
|
||||
case 'topBlur':
|
||||
case TOP_KEY_PRESS:
|
||||
case TOP_MOUSE_DOWN:
|
||||
case TOP_BLUR:
|
||||
// Events are not possible without cancelling IME.
|
||||
return true;
|
||||
default:
|
||||
@@ -252,15 +264,15 @@ function extractCompositionEvent(
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {TopLevelTypes} topLevelType Record from `BrowserEventConstants`.
|
||||
* @param {TopLevelType} topLevelType Number from `TopLevelType`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {?string} The string corresponding to this `beforeInput` event.
|
||||
*/
|
||||
function getNativeBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
|
||||
function getNativeBeforeInputChars(topLevelType: TopLevelType, nativeEvent) {
|
||||
switch (topLevelType) {
|
||||
case 'topCompositionEnd':
|
||||
case TOP_COMPOSITION_END:
|
||||
return getDataFromCustomEvent(nativeEvent);
|
||||
case 'topKeyPress':
|
||||
case TOP_KEY_PRESS:
|
||||
/**
|
||||
* If native `textInput` events are available, our goal is to make
|
||||
* use of them. However, there is a special case: the spacebar key.
|
||||
@@ -283,7 +295,7 @@ function getNativeBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
|
||||
hasSpaceKeypress = true;
|
||||
return SPACEBAR_CHAR;
|
||||
|
||||
case 'topTextInput':
|
||||
case TOP_TEXT_INPUT:
|
||||
// Record the characters to be added to the DOM.
|
||||
const chars = nativeEvent.data;
|
||||
|
||||
@@ -306,18 +318,18 @@ function getNativeBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
|
||||
* For browsers that do not provide the `textInput` event, extract the
|
||||
* appropriate string to use for SyntheticInputEvent.
|
||||
*
|
||||
* @param {string} topLevelType Record from `BrowserEventConstants`.
|
||||
* @param {number} topLevelType Number from `TopLevelEventTypes`.
|
||||
* @param {object} nativeEvent Native browser event.
|
||||
* @return {?string} The fallback string for this `beforeInput` event.
|
||||
*/
|
||||
function getFallbackBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
|
||||
function getFallbackBeforeInputChars(topLevelType: TopLevelType, nativeEvent) {
|
||||
// If we are currently composing (IME) and using a fallback to do so,
|
||||
// try to extract the composed characters from the fallback object.
|
||||
// If composition event is available, we extract a string only at
|
||||
// compositionevent, otherwise extract it at fallback events.
|
||||
if (isComposing) {
|
||||
if (
|
||||
topLevelType === 'topCompositionEnd' ||
|
||||
topLevelType === TOP_COMPOSITION_END ||
|
||||
(!canUseCompositionEvent &&
|
||||
isFallbackCompositionEnd(topLevelType, nativeEvent))
|
||||
) {
|
||||
@@ -330,11 +342,11 @@ function getFallbackBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
|
||||
}
|
||||
|
||||
switch (topLevelType) {
|
||||
case 'topPaste':
|
||||
case TOP_PASTE:
|
||||
// If a paste event occurs after a keypress, throw out the input
|
||||
// chars. Paste events should not lead to BeforeInput events.
|
||||
return null;
|
||||
case 'topKeyPress':
|
||||
case TOP_KEY_PRESS:
|
||||
/**
|
||||
* As of v27, Firefox may fire keypress events even when no character
|
||||
* will be inserted. A few possibilities:
|
||||
@@ -365,7 +377,7 @@ function getFallbackBeforeInputChars(topLevelType: TopLevelTypes, nativeEvent) {
|
||||
}
|
||||
}
|
||||
return null;
|
||||
case 'topCompositionEnd':
|
||||
case TOP_COMPOSITION_END:
|
||||
return useFallbackCompositionData ? null : nativeEvent.data;
|
||||
default:
|
||||
return null;
|
||||
|
||||
@@ -1,97 +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.
|
||||
*/
|
||||
|
||||
import getVendorPrefixedEventName from './getVendorPrefixedEventName';
|
||||
|
||||
/**
|
||||
* Types of raw signals from the browser caught at the top level.
|
||||
*
|
||||
* For events like 'submit' or audio/video events which don't consistently
|
||||
* bubble (which we trap at a lower node than `document`), binding
|
||||
* at `document` would cause duplicate events so we don't include them here.
|
||||
*/
|
||||
export const topLevelTypes = {
|
||||
topAnimationEnd: getVendorPrefixedEventName('animationend'),
|
||||
topAnimationIteration: getVendorPrefixedEventName('animationiteration'),
|
||||
topAnimationStart: getVendorPrefixedEventName('animationstart'),
|
||||
topBlur: 'blur',
|
||||
topCancel: 'cancel',
|
||||
topChange: 'change',
|
||||
topClick: 'click',
|
||||
topClose: 'close',
|
||||
topCompositionEnd: 'compositionend',
|
||||
topCompositionStart: 'compositionstart',
|
||||
topCompositionUpdate: 'compositionupdate',
|
||||
topContextMenu: 'contextmenu',
|
||||
topCopy: 'copy',
|
||||
topCut: 'cut',
|
||||
topDoubleClick: 'dblclick',
|
||||
topDrag: 'drag',
|
||||
topDragEnd: 'dragend',
|
||||
topDragEnter: 'dragenter',
|
||||
topDragExit: 'dragexit',
|
||||
topDragLeave: 'dragleave',
|
||||
topDragOver: 'dragover',
|
||||
topDragStart: 'dragstart',
|
||||
topDrop: 'drop',
|
||||
topFocus: 'focus',
|
||||
topInput: 'input',
|
||||
topKeyDown: 'keydown',
|
||||
topKeyPress: 'keypress',
|
||||
topKeyUp: 'keyup',
|
||||
topLoad: 'load',
|
||||
topLoadStart: 'loadstart',
|
||||
topMouseDown: 'mousedown',
|
||||
topMouseMove: 'mousemove',
|
||||
topMouseOut: 'mouseout',
|
||||
topMouseOver: 'mouseover',
|
||||
topMouseUp: 'mouseup',
|
||||
topPaste: 'paste',
|
||||
topScroll: 'scroll',
|
||||
topSelectionChange: 'selectionchange',
|
||||
topTextInput: 'textInput',
|
||||
topToggle: 'toggle',
|
||||
topTouchCancel: 'touchcancel',
|
||||
topTouchEnd: 'touchend',
|
||||
topTouchMove: 'touchmove',
|
||||
topTouchStart: 'touchstart',
|
||||
topTransitionEnd: getVendorPrefixedEventName('transitionend'),
|
||||
topWheel: 'wheel',
|
||||
};
|
||||
|
||||
// There are so many media events, it makes sense to just
|
||||
// maintain a list of them. Note these aren't technically
|
||||
// "top-level" since they don't bubble. We should come up
|
||||
// with a better naming convention if we come to refactoring
|
||||
// the event system.
|
||||
export const mediaEventTypes = {
|
||||
topAbort: 'abort',
|
||||
topCanPlay: 'canplay',
|
||||
topCanPlayThrough: 'canplaythrough',
|
||||
topDurationChange: 'durationchange',
|
||||
topEmptied: 'emptied',
|
||||
topEncrypted: 'encrypted',
|
||||
topEnded: 'ended',
|
||||
topError: 'error',
|
||||
topLoadedData: 'loadeddata',
|
||||
topLoadedMetadata: 'loadedmetadata',
|
||||
topLoadStart: 'loadstart',
|
||||
topPause: 'pause',
|
||||
topPlay: 'play',
|
||||
topPlaying: 'playing',
|
||||
topProgress: 'progress',
|
||||
topRateChange: 'ratechange',
|
||||
topSeeked: 'seeked',
|
||||
topSeeking: 'seeking',
|
||||
topStalled: 'stalled',
|
||||
topSuspend: 'suspend',
|
||||
topTimeUpdate: 'timeupdate',
|
||||
topVolumeChange: 'volumechange',
|
||||
topWaiting: 'waiting',
|
||||
};
|
||||
|
||||
export type TopLevelTypes = $Enum<typeof topLevelTypes>;
|
||||
@@ -13,6 +13,16 @@ import SyntheticEvent from 'events/SyntheticEvent';
|
||||
import isTextInputElement from 'shared/isTextInputElement';
|
||||
import ExecutionEnvironment from 'fbjs/lib/ExecutionEnvironment';
|
||||
|
||||
import {
|
||||
TOP_BLUR,
|
||||
TOP_CHANGE,
|
||||
TOP_CLICK,
|
||||
TOP_FOCUS,
|
||||
TOP_INPUT,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_UP,
|
||||
TOP_SELECTION_CHANGE,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import getEventTarget from './getEventTarget';
|
||||
import isEventSupported from './isEventSupported';
|
||||
import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
|
||||
@@ -26,14 +36,14 @@ const eventTypes = {
|
||||
captured: 'onChangeCapture',
|
||||
},
|
||||
dependencies: [
|
||||
'topBlur',
|
||||
'topChange',
|
||||
'topClick',
|
||||
'topFocus',
|
||||
'topInput',
|
||||
'topKeyDown',
|
||||
'topKeyUp',
|
||||
'topSelectionChange',
|
||||
TOP_BLUR,
|
||||
TOP_CHANGE,
|
||||
TOP_CLICK,
|
||||
TOP_FOCUS,
|
||||
TOP_INPUT,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_UP,
|
||||
TOP_SELECTION_CHANGE,
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -100,7 +110,7 @@ function getInstIfValueChanged(targetInst) {
|
||||
}
|
||||
|
||||
function getTargetInstForChangeEvent(topLevelType, targetInst) {
|
||||
if (topLevelType === 'topChange') {
|
||||
if (topLevelType === TOP_CHANGE) {
|
||||
return targetInst;
|
||||
}
|
||||
}
|
||||
@@ -155,7 +165,7 @@ function handlePropertyChange(nativeEvent) {
|
||||
}
|
||||
|
||||
function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
|
||||
if (topLevelType === 'topFocus') {
|
||||
if (topLevelType === TOP_FOCUS) {
|
||||
// In IE9, propertychange fires for most input events but is buggy and
|
||||
// doesn't fire when text is deleted, but conveniently, selectionchange
|
||||
// appears to fire in all of the remaining cases so we catch those and
|
||||
@@ -168,7 +178,7 @@ function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
|
||||
// missed a blur event somehow.
|
||||
stopWatchingForValueChange();
|
||||
startWatchingForValueChange(target, targetInst);
|
||||
} else if (topLevelType === 'topBlur') {
|
||||
} else if (topLevelType === TOP_BLUR) {
|
||||
stopWatchingForValueChange();
|
||||
}
|
||||
}
|
||||
@@ -176,9 +186,9 @@ function handleEventsForInputEventPolyfill(topLevelType, target, targetInst) {
|
||||
// For IE8 and IE9.
|
||||
function getTargetInstForInputEventPolyfill(topLevelType, targetInst) {
|
||||
if (
|
||||
topLevelType === 'topSelectionChange' ||
|
||||
topLevelType === 'topKeyUp' ||
|
||||
topLevelType === 'topKeyDown'
|
||||
topLevelType === TOP_SELECTION_CHANGE ||
|
||||
topLevelType === TOP_KEY_UP ||
|
||||
topLevelType === TOP_KEY_DOWN
|
||||
) {
|
||||
// On the selectionchange event, the target is just document which isn't
|
||||
// helpful for us so just check activeElement instead.
|
||||
@@ -210,13 +220,13 @@ function shouldUseClickEvent(elem) {
|
||||
}
|
||||
|
||||
function getTargetInstForClickEvent(topLevelType, targetInst) {
|
||||
if (topLevelType === 'topClick') {
|
||||
if (topLevelType === TOP_CLICK) {
|
||||
return getInstIfValueChanged(targetInst);
|
||||
}
|
||||
}
|
||||
|
||||
function getTargetInstForInputOrChangeEvent(topLevelType, targetInst) {
|
||||
if (topLevelType === 'topInput' || topLevelType === 'topChange') {
|
||||
if (topLevelType === TOP_INPUT || topLevelType === TOP_CHANGE) {
|
||||
return getInstIfValueChanged(targetInst);
|
||||
}
|
||||
}
|
||||
@@ -292,7 +302,7 @@ const ChangeEventPlugin = {
|
||||
}
|
||||
|
||||
// When blurring, set the value attribute for number inputs
|
||||
if (topLevelType === 'topBlur') {
|
||||
if (topLevelType === TOP_BLUR) {
|
||||
handleControlledInputBlur(targetInst, targetNode);
|
||||
}
|
||||
},
|
||||
|
||||
182
packages/react-dom/src/events/DOMTopLevelEventTypes.js
vendored
Normal file
182
packages/react-dom/src/events/DOMTopLevelEventTypes.js
vendored
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
|
||||
|
||||
import {
|
||||
unsafeCastStringToDOMTopLevelType,
|
||||
unsafeCastDOMTopLevelTypeToString,
|
||||
} from 'events/TopLevelEventTypes';
|
||||
import getVendorPrefixedEventName from './getVendorPrefixedEventName';
|
||||
|
||||
/**
|
||||
* To identify top level events in ReactDOM, we use constants defined by this
|
||||
* module. This is the only module that uses the unsafe* methods to express
|
||||
* that the constants actually correspond to the browser event names. This lets
|
||||
* us save some bundle size by avoiding a top level type -> event name map.
|
||||
* The rest of ReactDOM code should import top level types from this file.
|
||||
*/
|
||||
export const TOP_ABORT = unsafeCastStringToDOMTopLevelType('abort');
|
||||
export const TOP_ANIMATION_END = unsafeCastStringToDOMTopLevelType(
|
||||
getVendorPrefixedEventName('animationend'),
|
||||
);
|
||||
export const TOP_ANIMATION_ITERATION = unsafeCastStringToDOMTopLevelType(
|
||||
getVendorPrefixedEventName('animationiteration'),
|
||||
);
|
||||
export const TOP_ANIMATION_START = unsafeCastStringToDOMTopLevelType(
|
||||
getVendorPrefixedEventName('animationstart'),
|
||||
);
|
||||
export const TOP_BLUR = unsafeCastStringToDOMTopLevelType('blur');
|
||||
export const TOP_CAN_PLAY = unsafeCastStringToDOMTopLevelType('canplay');
|
||||
export const TOP_CAN_PLAY_THROUGH = unsafeCastStringToDOMTopLevelType(
|
||||
'canplaythrough',
|
||||
);
|
||||
export const TOP_CANCEL = unsafeCastStringToDOMTopLevelType('cancel');
|
||||
export const TOP_CHANGE = unsafeCastStringToDOMTopLevelType('change');
|
||||
export const TOP_CLICK = unsafeCastStringToDOMTopLevelType('click');
|
||||
export const TOP_CLOSE = unsafeCastStringToDOMTopLevelType('close');
|
||||
export const TOP_COMPOSITION_END = unsafeCastStringToDOMTopLevelType(
|
||||
'compositionend',
|
||||
);
|
||||
export const TOP_COMPOSITION_START = unsafeCastStringToDOMTopLevelType(
|
||||
'compositionstart',
|
||||
);
|
||||
export const TOP_COMPOSITION_UPDATE = unsafeCastStringToDOMTopLevelType(
|
||||
'compositionupdate',
|
||||
);
|
||||
export const TOP_CONTEXT_MENU = unsafeCastStringToDOMTopLevelType(
|
||||
'contextmenu',
|
||||
);
|
||||
export const TOP_COPY = unsafeCastStringToDOMTopLevelType('copy');
|
||||
export const TOP_CUT = unsafeCastStringToDOMTopLevelType('cut');
|
||||
export const TOP_DOUBLE_CLICK = unsafeCastStringToDOMTopLevelType('dblclick');
|
||||
export const TOP_DRAG = unsafeCastStringToDOMTopLevelType('drag');
|
||||
export const TOP_DRAG_END = unsafeCastStringToDOMTopLevelType('dragend');
|
||||
export const TOP_DRAG_ENTER = unsafeCastStringToDOMTopLevelType('dragenter');
|
||||
export const TOP_DRAG_EXIT = unsafeCastStringToDOMTopLevelType('dragexit');
|
||||
export const TOP_DRAG_LEAVE = unsafeCastStringToDOMTopLevelType('dragleave');
|
||||
export const TOP_DRAG_OVER = unsafeCastStringToDOMTopLevelType('dragover');
|
||||
export const TOP_DRAG_START = unsafeCastStringToDOMTopLevelType('dragstart');
|
||||
export const TOP_DROP = unsafeCastStringToDOMTopLevelType('drop');
|
||||
export const TOP_DURATION_CHANGE = unsafeCastStringToDOMTopLevelType(
|
||||
'durationchange',
|
||||
);
|
||||
export const TOP_EMPTIED = unsafeCastStringToDOMTopLevelType('emptied');
|
||||
export const TOP_ENCRYPTED = unsafeCastStringToDOMTopLevelType('encrypted');
|
||||
export const TOP_ENDED = unsafeCastStringToDOMTopLevelType('ended');
|
||||
export const TOP_ERROR = unsafeCastStringToDOMTopLevelType('error');
|
||||
export const TOP_FOCUS = unsafeCastStringToDOMTopLevelType('focus');
|
||||
export const TOP_GOT_POINTER_CAPTURE = unsafeCastStringToDOMTopLevelType(
|
||||
'gotpointercapture',
|
||||
);
|
||||
export const TOP_INPUT = unsafeCastStringToDOMTopLevelType('input');
|
||||
export const TOP_INVALID = unsafeCastStringToDOMTopLevelType('invalid');
|
||||
export const TOP_KEY_DOWN = unsafeCastStringToDOMTopLevelType('keydown');
|
||||
export const TOP_KEY_PRESS = unsafeCastStringToDOMTopLevelType('keypress');
|
||||
export const TOP_KEY_UP = unsafeCastStringToDOMTopLevelType('keyup');
|
||||
export const TOP_LOAD = unsafeCastStringToDOMTopLevelType('load');
|
||||
export const TOP_LOAD_START = unsafeCastStringToDOMTopLevelType('loadstart');
|
||||
export const TOP_LOADED_DATA = unsafeCastStringToDOMTopLevelType('loadeddata');
|
||||
export const TOP_LOADED_METADATA = unsafeCastStringToDOMTopLevelType(
|
||||
'loadedmetadata',
|
||||
);
|
||||
export const TOP_LOST_POINTER_CAPTURE = unsafeCastStringToDOMTopLevelType(
|
||||
'lostpointercapture',
|
||||
);
|
||||
export const TOP_MOUSE_DOWN = unsafeCastStringToDOMTopLevelType('mousedown');
|
||||
export const TOP_MOUSE_MOVE = unsafeCastStringToDOMTopLevelType('mousemove');
|
||||
export const TOP_MOUSE_OUT = unsafeCastStringToDOMTopLevelType('mouseout');
|
||||
export const TOP_MOUSE_OVER = unsafeCastStringToDOMTopLevelType('mouseover');
|
||||
export const TOP_MOUSE_UP = unsafeCastStringToDOMTopLevelType('mouseup');
|
||||
export const TOP_PASTE = unsafeCastStringToDOMTopLevelType('paste');
|
||||
export const TOP_PAUSE = unsafeCastStringToDOMTopLevelType('pause');
|
||||
export const TOP_PLAY = unsafeCastStringToDOMTopLevelType('play');
|
||||
export const TOP_PLAYING = unsafeCastStringToDOMTopLevelType('playing');
|
||||
export const TOP_POINTER_CANCEL = unsafeCastStringToDOMTopLevelType(
|
||||
'pointercancel',
|
||||
);
|
||||
export const TOP_POINTER_DOWN = unsafeCastStringToDOMTopLevelType(
|
||||
'pointerdown',
|
||||
);
|
||||
export const TOP_POINTER_ENTER = unsafeCastStringToDOMTopLevelType(
|
||||
'pointerenter',
|
||||
);
|
||||
export const TOP_POINTER_LEAVE = unsafeCastStringToDOMTopLevelType(
|
||||
'pointerleave',
|
||||
);
|
||||
export const TOP_POINTER_MOVE = unsafeCastStringToDOMTopLevelType(
|
||||
'pointermove',
|
||||
);
|
||||
export const TOP_POINTER_OUT = unsafeCastStringToDOMTopLevelType('pointerout');
|
||||
export const TOP_POINTER_OVER = unsafeCastStringToDOMTopLevelType(
|
||||
'pointerover',
|
||||
);
|
||||
export const TOP_POINTER_UP = unsafeCastStringToDOMTopLevelType('pointerup');
|
||||
export const TOP_PROGRESS = unsafeCastStringToDOMTopLevelType('progress');
|
||||
export const TOP_RATE_CHANGE = unsafeCastStringToDOMTopLevelType('ratechange');
|
||||
export const TOP_RESET = unsafeCastStringToDOMTopLevelType('reset');
|
||||
export const TOP_SCROLL = unsafeCastStringToDOMTopLevelType('scroll');
|
||||
export const TOP_SEEKED = unsafeCastStringToDOMTopLevelType('seeked');
|
||||
export const TOP_SEEKING = unsafeCastStringToDOMTopLevelType('seeking');
|
||||
export const TOP_SELECTION_CHANGE = unsafeCastStringToDOMTopLevelType(
|
||||
'selectionchange',
|
||||
);
|
||||
export const TOP_STALLED = unsafeCastStringToDOMTopLevelType('stalled');
|
||||
export const TOP_SUBMIT = unsafeCastStringToDOMTopLevelType('submit');
|
||||
export const TOP_SUSPEND = unsafeCastStringToDOMTopLevelType('suspend');
|
||||
export const TOP_TEXT_INPUT = unsafeCastStringToDOMTopLevelType('textInput');
|
||||
export const TOP_TIME_UPDATE = unsafeCastStringToDOMTopLevelType('timeupdate');
|
||||
export const TOP_TOGGLE = unsafeCastStringToDOMTopLevelType('toggle');
|
||||
export const TOP_TOUCH_CANCEL = unsafeCastStringToDOMTopLevelType(
|
||||
'touchcancel',
|
||||
);
|
||||
export const TOP_TOUCH_END = unsafeCastStringToDOMTopLevelType('touchend');
|
||||
export const TOP_TOUCH_MOVE = unsafeCastStringToDOMTopLevelType('touchmove');
|
||||
export const TOP_TOUCH_START = unsafeCastStringToDOMTopLevelType('touchstart');
|
||||
export const TOP_TRANSITION_END = unsafeCastStringToDOMTopLevelType(
|
||||
getVendorPrefixedEventName('transitionend'),
|
||||
);
|
||||
export const TOP_VOLUME_CHANGE = unsafeCastStringToDOMTopLevelType(
|
||||
'volumechange',
|
||||
);
|
||||
export const TOP_WAITING = unsafeCastStringToDOMTopLevelType('waiting');
|
||||
export const TOP_WHEEL = unsafeCastStringToDOMTopLevelType('wheel');
|
||||
|
||||
// List of events that need to be individually attached to media elements.
|
||||
// Note that events in this list will *not* be listened to at the top level
|
||||
// unless they're explicitly whitelisted in `ReactBrowserEventEmitter.listenTo`.
|
||||
export const mediaEventTypes = [
|
||||
TOP_ABORT,
|
||||
TOP_CAN_PLAY,
|
||||
TOP_CAN_PLAY_THROUGH,
|
||||
TOP_DURATION_CHANGE,
|
||||
TOP_EMPTIED,
|
||||
TOP_ENCRYPTED,
|
||||
TOP_ENDED,
|
||||
TOP_ERROR,
|
||||
TOP_LOADED_DATA,
|
||||
TOP_LOADED_METADATA,
|
||||
TOP_LOAD_START,
|
||||
TOP_PAUSE,
|
||||
TOP_PLAY,
|
||||
TOP_PLAYING,
|
||||
TOP_PROGRESS,
|
||||
TOP_RATE_CHANGE,
|
||||
TOP_SEEKED,
|
||||
TOP_SEEKING,
|
||||
TOP_STALLED,
|
||||
TOP_SUSPEND,
|
||||
TOP_TIME_UPDATE,
|
||||
TOP_VOLUME_CHANGE,
|
||||
TOP_WAITING,
|
||||
];
|
||||
|
||||
export function getRawEventName(topLevelType: DOMTopLevelEventType): string {
|
||||
return unsafeCastDOMTopLevelTypeToString(topLevelType);
|
||||
}
|
||||
@@ -7,7 +7,14 @@
|
||||
|
||||
import {accumulateEnterLeaveDispatches} from 'events/EventPropagators';
|
||||
|
||||
import {
|
||||
TOP_MOUSE_OUT,
|
||||
TOP_MOUSE_OVER,
|
||||
TOP_POINTER_OUT,
|
||||
TOP_POINTER_OVER,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import SyntheticMouseEvent from './SyntheticMouseEvent';
|
||||
import SyntheticPointerEvent from './SyntheticPointerEvent';
|
||||
import {
|
||||
getClosestInstanceFromNode,
|
||||
getNodeFromInstance,
|
||||
@@ -16,11 +23,19 @@ import {
|
||||
const eventTypes = {
|
||||
mouseEnter: {
|
||||
registrationName: 'onMouseEnter',
|
||||
dependencies: ['topMouseOut', 'topMouseOver'],
|
||||
dependencies: [TOP_MOUSE_OUT, TOP_MOUSE_OVER],
|
||||
},
|
||||
mouseLeave: {
|
||||
registrationName: 'onMouseLeave',
|
||||
dependencies: ['topMouseOut', 'topMouseOver'],
|
||||
dependencies: [TOP_MOUSE_OUT, TOP_MOUSE_OVER],
|
||||
},
|
||||
pointerEnter: {
|
||||
registrationName: 'onPointerEnter',
|
||||
dependencies: [TOP_POINTER_OUT, TOP_POINTER_OVER],
|
||||
},
|
||||
pointerLeave: {
|
||||
registrationName: 'onPointerLeave',
|
||||
dependencies: [TOP_POINTER_OUT, TOP_POINTER_OVER],
|
||||
},
|
||||
};
|
||||
|
||||
@@ -40,14 +55,17 @@ const EnterLeaveEventPlugin = {
|
||||
nativeEvent,
|
||||
nativeEventTarget,
|
||||
) {
|
||||
if (
|
||||
topLevelType === 'topMouseOver' &&
|
||||
(nativeEvent.relatedTarget || nativeEvent.fromElement)
|
||||
) {
|
||||
const isOverEvent =
|
||||
topLevelType === TOP_MOUSE_OVER || topLevelType === TOP_POINTER_OVER;
|
||||
const isOutEvent =
|
||||
topLevelType === TOP_MOUSE_OUT || topLevelType === TOP_POINTER_OUT;
|
||||
|
||||
if (isOverEvent && (nativeEvent.relatedTarget || nativeEvent.fromElement)) {
|
||||
return null;
|
||||
}
|
||||
if (topLevelType !== 'topMouseOut' && topLevelType !== 'topMouseOver') {
|
||||
// Must not be a mouse in or mouse out - ignoring.
|
||||
|
||||
if (!isOutEvent && !isOverEvent) {
|
||||
// Must not be a mouse or pointer in or out - ignoring.
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -67,7 +85,7 @@ const EnterLeaveEventPlugin = {
|
||||
|
||||
let from;
|
||||
let to;
|
||||
if (topLevelType === 'topMouseOut') {
|
||||
if (isOutEvent) {
|
||||
from = targetInst;
|
||||
const related = nativeEvent.relatedTarget || nativeEvent.toElement;
|
||||
to = related ? getClosestInstanceFromNode(related) : null;
|
||||
@@ -82,26 +100,43 @@ const EnterLeaveEventPlugin = {
|
||||
return null;
|
||||
}
|
||||
|
||||
let eventInterface, leaveEventType, enterEventType, eventTypePrefix;
|
||||
|
||||
if (topLevelType === TOP_MOUSE_OUT || topLevelType === TOP_MOUSE_OVER) {
|
||||
eventInterface = SyntheticMouseEvent;
|
||||
leaveEventType = eventTypes.mouseLeave;
|
||||
enterEventType = eventTypes.mouseEnter;
|
||||
eventTypePrefix = 'mouse';
|
||||
} else if (
|
||||
topLevelType === TOP_POINTER_OUT ||
|
||||
topLevelType === TOP_POINTER_OVER
|
||||
) {
|
||||
eventInterface = SyntheticPointerEvent;
|
||||
leaveEventType = eventTypes.pointerLeave;
|
||||
enterEventType = eventTypes.pointerEnter;
|
||||
eventTypePrefix = 'pointer';
|
||||
}
|
||||
|
||||
const fromNode = from == null ? win : getNodeFromInstance(from);
|
||||
const toNode = to == null ? win : getNodeFromInstance(to);
|
||||
|
||||
const leave = SyntheticMouseEvent.getPooled(
|
||||
eventTypes.mouseLeave,
|
||||
const leave = eventInterface.getPooled(
|
||||
leaveEventType,
|
||||
from,
|
||||
nativeEvent,
|
||||
nativeEventTarget,
|
||||
);
|
||||
leave.type = 'mouseleave';
|
||||
leave.type = eventTypePrefix + 'leave';
|
||||
leave.target = fromNode;
|
||||
leave.relatedTarget = toNode;
|
||||
|
||||
const enter = SyntheticMouseEvent.getPooled(
|
||||
eventTypes.mouseEnter,
|
||||
const enter = eventInterface.getPooled(
|
||||
enterEventType,
|
||||
to,
|
||||
nativeEvent,
|
||||
nativeEventTarget,
|
||||
);
|
||||
enter.type = 'mouseenter';
|
||||
enter.type = eventTypePrefix + 'enter';
|
||||
enter.target = toNode;
|
||||
enter.relatedTarget = fromNode;
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
export function addEventBubbleListener(
|
||||
element: Element,
|
||||
element: Document | Element,
|
||||
eventType: string,
|
||||
listener: Function,
|
||||
): void {
|
||||
@@ -16,7 +16,7 @@ export function addEventBubbleListener(
|
||||
}
|
||||
|
||||
export function addEventCaptureListener(
|
||||
element: Element,
|
||||
element: Document | Element,
|
||||
eventType: string,
|
||||
listener: Function,
|
||||
): void {
|
||||
|
||||
@@ -3,9 +3,23 @@
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {registrationNameDependencies} from 'events/EventPluginRegistry';
|
||||
import {
|
||||
TOP_BLUR,
|
||||
TOP_CANCEL,
|
||||
TOP_CLOSE,
|
||||
TOP_FOCUS,
|
||||
TOP_INVALID,
|
||||
TOP_RESET,
|
||||
TOP_SCROLL,
|
||||
TOP_SUBMIT,
|
||||
getRawEventName,
|
||||
mediaEventTypes,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import {
|
||||
setEnabled,
|
||||
isEnabled,
|
||||
@@ -13,7 +27,6 @@ import {
|
||||
trapCapturedEvent,
|
||||
} from './ReactDOMEventListener';
|
||||
import isEventSupported from './isEventSupported';
|
||||
import {topLevelTypes} from './BrowserEventConstants';
|
||||
|
||||
/**
|
||||
* Summary of `ReactBrowserEventEmitter` event handling:
|
||||
@@ -79,7 +92,7 @@ let reactTopListenersCounter = 0;
|
||||
*/
|
||||
const topListenersIDKey = '_reactListenersID' + ('' + Math.random()).slice(2);
|
||||
|
||||
function getListeningForDocument(mountAt) {
|
||||
function getListeningForDocument(mountAt: any) {
|
||||
// In IE8, `mountAt` is a host object and doesn't have `hasOwnProperty`
|
||||
// directly.
|
||||
if (!Object.prototype.hasOwnProperty.call(mountAt, topListenersIDKey)) {
|
||||
@@ -108,45 +121,61 @@ function getListeningForDocument(mountAt) {
|
||||
* they bubble to document.
|
||||
*
|
||||
* @param {string} registrationName Name of listener (e.g. `onClick`).
|
||||
* @param {object} contentDocumentHandle Document which owns the container
|
||||
* @param {object} mountAt Container where to mount the listener
|
||||
*/
|
||||
export function listenTo(registrationName, contentDocumentHandle) {
|
||||
const mountAt = contentDocumentHandle;
|
||||
export function listenTo(
|
||||
registrationName: string,
|
||||
mountAt: Document | Element,
|
||||
) {
|
||||
const isListening = getListeningForDocument(mountAt);
|
||||
const dependencies = registrationNameDependencies[registrationName];
|
||||
|
||||
for (let i = 0; i < dependencies.length; i++) {
|
||||
const dependency = dependencies[i];
|
||||
if (!(isListening.hasOwnProperty(dependency) && isListening[dependency])) {
|
||||
if (dependency === 'topScroll') {
|
||||
trapCapturedEvent('topScroll', 'scroll', mountAt);
|
||||
} else if (dependency === 'topFocus' || dependency === 'topBlur') {
|
||||
trapCapturedEvent('topFocus', 'focus', mountAt);
|
||||
trapCapturedEvent('topBlur', 'blur', mountAt);
|
||||
|
||||
// to make sure blur and focus event listeners are only attached once
|
||||
isListening.topBlur = true;
|
||||
isListening.topFocus = true;
|
||||
} else if (dependency === 'topCancel') {
|
||||
if (isEventSupported('cancel', true)) {
|
||||
trapCapturedEvent('topCancel', 'cancel', mountAt);
|
||||
}
|
||||
isListening.topCancel = true;
|
||||
} else if (dependency === 'topClose') {
|
||||
if (isEventSupported('close', true)) {
|
||||
trapCapturedEvent('topClose', 'close', mountAt);
|
||||
}
|
||||
isListening.topClose = true;
|
||||
} else if (topLevelTypes.hasOwnProperty(dependency)) {
|
||||
trapBubbledEvent(dependency, topLevelTypes[dependency], mountAt);
|
||||
switch (dependency) {
|
||||
case TOP_SCROLL:
|
||||
trapCapturedEvent(TOP_SCROLL, mountAt);
|
||||
break;
|
||||
case TOP_FOCUS:
|
||||
case TOP_BLUR:
|
||||
trapCapturedEvent(TOP_FOCUS, mountAt);
|
||||
trapCapturedEvent(TOP_BLUR, mountAt);
|
||||
// We set the flag for a single dependency later in this function,
|
||||
// but this ensures we mark both as attached rather than just one.
|
||||
isListening[TOP_BLUR] = true;
|
||||
isListening[TOP_FOCUS] = true;
|
||||
break;
|
||||
case TOP_CANCEL:
|
||||
case TOP_CLOSE:
|
||||
if (isEventSupported(getRawEventName(dependency), true)) {
|
||||
trapCapturedEvent(dependency, mountAt);
|
||||
}
|
||||
break;
|
||||
case TOP_INVALID:
|
||||
case TOP_SUBMIT:
|
||||
case TOP_RESET:
|
||||
// We listen to them on the target DOM elements.
|
||||
// Some of them bubble so we don't want them to fire twice.
|
||||
break;
|
||||
default:
|
||||
// By default, listen on the top level to all non-media events.
|
||||
// Media events don't bubble so adding the listener wouldn't do anything.
|
||||
const isMediaEvent = mediaEventTypes.indexOf(dependency) !== -1;
|
||||
if (!isMediaEvent) {
|
||||
trapBubbledEvent(dependency, mountAt);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
isListening[dependency] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function isListeningToAllDependencies(registrationName, mountAt) {
|
||||
export function isListeningToAllDependencies(
|
||||
registrationName: string,
|
||||
mountAt: Document | Element,
|
||||
) {
|
||||
const isListening = getListeningForDocument(mountAt);
|
||||
const dependencies = registrationNameDependencies[registrationName];
|
||||
for (let i = 0; i < dependencies.length; i++) {
|
||||
|
||||
@@ -3,8 +3,14 @@
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {AnyNativeEvent} from 'events/PluginModuleType';
|
||||
import type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
import type {DOMTopLevelEventType} from 'events/TopLevelEventTypes';
|
||||
|
||||
import {batchedUpdates, interactiveUpdates} from 'events/ReactGenericBatching';
|
||||
import {runExtractedEventsInBatch} from 'events/EventPluginHub';
|
||||
import {isFiberMounted} from 'react-reconciler/reflection';
|
||||
@@ -14,6 +20,7 @@ import {addEventBubbleListener, addEventCaptureListener} from './EventListener';
|
||||
import getEventTarget from './getEventTarget';
|
||||
import {getClosestInstanceFromNode} from '../client/ReactDOMComponentTree';
|
||||
import SimpleEventPlugin from './SimpleEventPlugin';
|
||||
import {getRawEventName} from './DOMTopLevelEventTypes';
|
||||
|
||||
const {isInteractiveTopLevelEventType} = SimpleEventPlugin;
|
||||
|
||||
@@ -40,7 +47,16 @@ function findRootContainerNode(inst) {
|
||||
}
|
||||
|
||||
// Used to store ancestor hierarchy in top level callback
|
||||
function getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst) {
|
||||
function getTopLevelCallbackBookKeeping(
|
||||
topLevelType,
|
||||
nativeEvent,
|
||||
targetInst,
|
||||
): {
|
||||
topLevelType: ?DOMTopLevelEventType,
|
||||
nativeEvent: ?AnyNativeEvent,
|
||||
targetInst: Fiber | null,
|
||||
ancestors: Array<Fiber>,
|
||||
} {
|
||||
if (callbackBookkeepingPool.length) {
|
||||
const instance = callbackBookkeepingPool.pop();
|
||||
instance.topLevelType = topLevelType;
|
||||
@@ -101,7 +117,7 @@ function handleTopLevel(bookKeeping) {
|
||||
// TODO: can we stop exporting these?
|
||||
export let _enabled = true;
|
||||
|
||||
export function setEnabled(enabled) {
|
||||
export function setEnabled(enabled: ?boolean) {
|
||||
_enabled = !!enabled;
|
||||
}
|
||||
|
||||
@@ -112,14 +128,16 @@ export function isEnabled() {
|
||||
/**
|
||||
* Traps top-level events by using event bubbling.
|
||||
*
|
||||
* @param {string} topLevelType Record from `BrowserEventConstants`.
|
||||
* @param {string} handlerBaseName Event name (e.g. "click").
|
||||
* @param {number} topLevelType Number from `TopLevelEventTypes`.
|
||||
* @param {object} element Element on which to attach listener.
|
||||
* @return {?object} An object with a remove function which will forcefully
|
||||
* remove the listener.
|
||||
* @internal
|
||||
*/
|
||||
export function trapBubbledEvent(topLevelType, handlerBaseName, element) {
|
||||
export function trapBubbledEvent(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
element: Document | Element,
|
||||
) {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
@@ -129,7 +147,7 @@ export function trapBubbledEvent(topLevelType, handlerBaseName, element) {
|
||||
|
||||
addEventBubbleListener(
|
||||
element,
|
||||
handlerBaseName,
|
||||
getRawEventName(topLevelType),
|
||||
// Check if interactive and wrap in interactiveUpdates
|
||||
dispatch.bind(null, topLevelType),
|
||||
);
|
||||
@@ -138,14 +156,16 @@ export function trapBubbledEvent(topLevelType, handlerBaseName, element) {
|
||||
/**
|
||||
* Traps a top-level event by using event capturing.
|
||||
*
|
||||
* @param {string} topLevelType Record from `BrowserEventConstants`.
|
||||
* @param {string} handlerBaseName Event name (e.g. "click").
|
||||
* @param {number} topLevelType Number from `TopLevelEventTypes`.
|
||||
* @param {object} element Element on which to attach listener.
|
||||
* @return {?object} An object with a remove function which will forcefully
|
||||
* remove the listener.
|
||||
* @internal
|
||||
*/
|
||||
export function trapCapturedEvent(topLevelType, handlerBaseName, element) {
|
||||
export function trapCapturedEvent(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
element: Document | Element,
|
||||
) {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
@@ -155,7 +175,7 @@ export function trapCapturedEvent(topLevelType, handlerBaseName, element) {
|
||||
|
||||
addEventCaptureListener(
|
||||
element,
|
||||
handlerBaseName,
|
||||
getRawEventName(topLevelType),
|
||||
// Check if interactive and wrap in interactiveUpdates
|
||||
dispatch.bind(null, topLevelType),
|
||||
);
|
||||
@@ -165,7 +185,10 @@ function dispatchInteractiveEvent(topLevelType, nativeEvent) {
|
||||
interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
|
||||
}
|
||||
|
||||
export function dispatchEvent(topLevelType, nativeEvent) {
|
||||
export function dispatchEvent(
|
||||
topLevelType: DOMTopLevelEventType,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
) {
|
||||
if (!_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,16 @@ import isTextInputElement from 'shared/isTextInputElement';
|
||||
import getActiveElement from 'fbjs/lib/getActiveElement';
|
||||
import shallowEqual from 'fbjs/lib/shallowEqual';
|
||||
|
||||
import {
|
||||
TOP_BLUR,
|
||||
TOP_CONTEXT_MENU,
|
||||
TOP_FOCUS,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_UP,
|
||||
TOP_MOUSE_DOWN,
|
||||
TOP_MOUSE_UP,
|
||||
TOP_SELECTION_CHANGE,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import {isListeningToAllDependencies} from './ReactBrowserEventEmitter';
|
||||
import {getNodeFromInstance} from '../client/ReactDOMComponentTree';
|
||||
import * as ReactInputSelection from '../client/ReactInputSelection';
|
||||
@@ -29,14 +39,14 @@ const eventTypes = {
|
||||
captured: 'onSelectCapture',
|
||||
},
|
||||
dependencies: [
|
||||
'topBlur',
|
||||
'topContextMenu',
|
||||
'topFocus',
|
||||
'topKeyDown',
|
||||
'topKeyUp',
|
||||
'topMouseDown',
|
||||
'topMouseUp',
|
||||
'topSelectionChange',
|
||||
TOP_BLUR,
|
||||
TOP_CONTEXT_MENU,
|
||||
TOP_FOCUS,
|
||||
TOP_KEY_DOWN,
|
||||
TOP_KEY_UP,
|
||||
TOP_MOUSE_DOWN,
|
||||
TOP_MOUSE_UP,
|
||||
TOP_SELECTION_CHANGE,
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -156,7 +166,7 @@ const SelectEventPlugin = {
|
||||
|
||||
switch (topLevelType) {
|
||||
// Track the input node that has focus.
|
||||
case 'topFocus':
|
||||
case TOP_FOCUS:
|
||||
if (
|
||||
isTextInputElement(targetNode) ||
|
||||
targetNode.contentEditable === 'true'
|
||||
@@ -166,18 +176,18 @@ const SelectEventPlugin = {
|
||||
lastSelection = null;
|
||||
}
|
||||
break;
|
||||
case 'topBlur':
|
||||
case TOP_BLUR:
|
||||
activeElement = null;
|
||||
activeElementInst = null;
|
||||
lastSelection = null;
|
||||
break;
|
||||
// Don't fire the event while the user is dragging. This matches the
|
||||
// semantics of the native select event.
|
||||
case 'topMouseDown':
|
||||
case TOP_MOUSE_DOWN:
|
||||
mouseDown = true;
|
||||
break;
|
||||
case 'topContextMenu':
|
||||
case 'topMouseUp':
|
||||
case TOP_CONTEXT_MENU:
|
||||
case TOP_MOUSE_UP:
|
||||
mouseDown = false;
|
||||
return constructSelectEvent(nativeEvent, nativeEventTarget);
|
||||
// Chrome and IE fire non-standard event when selection is changed (and
|
||||
@@ -189,13 +199,13 @@ const SelectEventPlugin = {
|
||||
// keyup, but we check on keydown as well in the case of holding down a
|
||||
// key, when multiple keydown events are fired but only one keyup is.
|
||||
// This is also our approach for IE handling, for the reason above.
|
||||
case 'topSelectionChange':
|
||||
case TOP_SELECTION_CHANGE:
|
||||
if (skipSelectionChangeEvent) {
|
||||
break;
|
||||
}
|
||||
// falls through
|
||||
case 'topKeyDown':
|
||||
case 'topKeyUp':
|
||||
case TOP_KEY_DOWN:
|
||||
case TOP_KEY_UP:
|
||||
return constructSelectEvent(nativeEvent, nativeEventTarget);
|
||||
}
|
||||
|
||||
|
||||
325
packages/react-dom/src/events/SimpleEventPlugin.js
vendored
325
packages/react-dom/src/events/SimpleEventPlugin.js
vendored
@@ -7,7 +7,10 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {TopLevelTypes} from './BrowserEventConstants';
|
||||
import type {
|
||||
TopLevelType,
|
||||
DOMTopLevelEventType,
|
||||
} from 'events/TopLevelEventTypes';
|
||||
import type {
|
||||
DispatchConfig,
|
||||
ReactSyntheticEvent,
|
||||
@@ -17,6 +20,8 @@ import type {EventTypes, PluginModule} from 'events/PluginModuleType';
|
||||
|
||||
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
|
||||
import SyntheticEvent from 'events/SyntheticEvent';
|
||||
|
||||
import * as DOMTopLevelEventTypes from './DOMTopLevelEventTypes';
|
||||
import warning from 'fbjs/lib/warning';
|
||||
|
||||
import SyntheticAnimationEvent from './SyntheticAnimationEvent';
|
||||
@@ -24,6 +29,7 @@ import SyntheticClipboardEvent from './SyntheticClipboardEvent';
|
||||
import SyntheticFocusEvent from './SyntheticFocusEvent';
|
||||
import SyntheticKeyboardEvent from './SyntheticKeyboardEvent';
|
||||
import SyntheticMouseEvent from './SyntheticMouseEvent';
|
||||
import SyntheticPointerEvent from './SyntheticPointerEvent';
|
||||
import SyntheticDragEvent from './SyntheticDragEvent';
|
||||
import SyntheticTouchEvent from './SyntheticTouchEvent';
|
||||
import SyntheticTransitionEvent from './SyntheticTransitionEvent';
|
||||
@@ -41,93 +47,104 @@ import getEventCharCode from './getEventCharCode';
|
||||
* bubbled: 'onAbort',
|
||||
* captured: 'onAbortCapture',
|
||||
* },
|
||||
* dependencies: ['topAbort'],
|
||||
* dependencies: [TOP_ABORT],
|
||||
* },
|
||||
* ...
|
||||
* };
|
||||
* topLevelEventsToDispatchConfig = {
|
||||
* 'topAbort': { sameConfig }
|
||||
* };
|
||||
* topLevelEventsToDispatchConfig = new Map([
|
||||
* [TOP_ABORT, { sameConfig }],
|
||||
* ]);
|
||||
*/
|
||||
const interactiveEventTypeNames: Array<string> = [
|
||||
'blur',
|
||||
'cancel',
|
||||
'click',
|
||||
'close',
|
||||
'contextMenu',
|
||||
'copy',
|
||||
'cut',
|
||||
'doubleClick',
|
||||
'dragEnd',
|
||||
'dragStart',
|
||||
'drop',
|
||||
'focus',
|
||||
'input',
|
||||
'invalid',
|
||||
'keyDown',
|
||||
'keyPress',
|
||||
'keyUp',
|
||||
'mouseDown',
|
||||
'mouseUp',
|
||||
'paste',
|
||||
'pause',
|
||||
'play',
|
||||
'rateChange',
|
||||
'reset',
|
||||
'seeked',
|
||||
'submit',
|
||||
'touchCancel',
|
||||
'touchEnd',
|
||||
'touchStart',
|
||||
'volumeChange',
|
||||
type EventTuple = [DOMTopLevelEventType, string];
|
||||
const interactiveEventTypeNames: Array<EventTuple> = [
|
||||
[DOMTopLevelEventTypes.TOP_BLUR, 'blur'],
|
||||
[DOMTopLevelEventTypes.TOP_CANCEL, 'cancel'],
|
||||
[DOMTopLevelEventTypes.TOP_CLICK, 'click'],
|
||||
[DOMTopLevelEventTypes.TOP_CLOSE, 'close'],
|
||||
[DOMTopLevelEventTypes.TOP_CONTEXT_MENU, 'contextMenu'],
|
||||
[DOMTopLevelEventTypes.TOP_COPY, 'copy'],
|
||||
[DOMTopLevelEventTypes.TOP_CUT, 'cut'],
|
||||
[DOMTopLevelEventTypes.TOP_DOUBLE_CLICK, 'doubleClick'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_END, 'dragEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_START, 'dragStart'],
|
||||
[DOMTopLevelEventTypes.TOP_DROP, 'drop'],
|
||||
[DOMTopLevelEventTypes.TOP_FOCUS, 'focus'],
|
||||
[DOMTopLevelEventTypes.TOP_INPUT, 'input'],
|
||||
[DOMTopLevelEventTypes.TOP_INVALID, 'invalid'],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_DOWN, 'keyDown'],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_PRESS, 'keyPress'],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_UP, 'keyUp'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_DOWN, 'mouseDown'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_UP, 'mouseUp'],
|
||||
[DOMTopLevelEventTypes.TOP_PASTE, 'paste'],
|
||||
[DOMTopLevelEventTypes.TOP_PAUSE, 'pause'],
|
||||
[DOMTopLevelEventTypes.TOP_PLAY, 'play'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_CANCEL, 'pointerCancel'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_DOWN, 'pointerDown'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_UP, 'pointerUp'],
|
||||
[DOMTopLevelEventTypes.TOP_RATE_CHANGE, 'rateChange'],
|
||||
[DOMTopLevelEventTypes.TOP_RESET, 'reset'],
|
||||
[DOMTopLevelEventTypes.TOP_SEEKED, 'seeked'],
|
||||
[DOMTopLevelEventTypes.TOP_SUBMIT, 'submit'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_CANCEL, 'touchCancel'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_END, 'touchEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_START, 'touchStart'],
|
||||
[DOMTopLevelEventTypes.TOP_VOLUME_CHANGE, 'volumeChange'],
|
||||
];
|
||||
const nonInteractiveEventTypeNames: Array<string> = [
|
||||
'abort',
|
||||
'animationEnd',
|
||||
'animationIteration',
|
||||
'animationStart',
|
||||
'canPlay',
|
||||
'canPlayThrough',
|
||||
'drag',
|
||||
'dragEnter',
|
||||
'dragExit',
|
||||
'dragLeave',
|
||||
'dragOver',
|
||||
'durationChange',
|
||||
'emptied',
|
||||
'encrypted',
|
||||
'ended',
|
||||
'error',
|
||||
'load',
|
||||
'loadedData',
|
||||
'loadedMetadata',
|
||||
'loadStart',
|
||||
'mouseMove',
|
||||
'mouseOut',
|
||||
'mouseOver',
|
||||
'playing',
|
||||
'progress',
|
||||
'scroll',
|
||||
'seeking',
|
||||
'stalled',
|
||||
'suspend',
|
||||
'timeUpdate',
|
||||
'toggle',
|
||||
'touchMove',
|
||||
'transitionEnd',
|
||||
'waiting',
|
||||
'wheel',
|
||||
const nonInteractiveEventTypeNames: Array<EventTuple> = [
|
||||
[DOMTopLevelEventTypes.TOP_ABORT, 'abort'],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_END, 'animationEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_ITERATION, 'animationIteration'],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_START, 'animationStart'],
|
||||
[DOMTopLevelEventTypes.TOP_CAN_PLAY, 'canPlay'],
|
||||
[DOMTopLevelEventTypes.TOP_CAN_PLAY_THROUGH, 'canPlayThrough'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG, 'drag'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_ENTER, 'dragEnter'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_EXIT, 'dragExit'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_LEAVE, 'dragLeave'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_OVER, 'dragOver'],
|
||||
[DOMTopLevelEventTypes.TOP_DURATION_CHANGE, 'durationChange'],
|
||||
[DOMTopLevelEventTypes.TOP_EMPTIED, 'emptied'],
|
||||
[DOMTopLevelEventTypes.TOP_ENCRYPTED, 'encrypted'],
|
||||
[DOMTopLevelEventTypes.TOP_ENDED, 'ended'],
|
||||
[DOMTopLevelEventTypes.TOP_ERROR, 'error'],
|
||||
[DOMTopLevelEventTypes.TOP_GOT_POINTER_CAPTURE, 'gotPointerCapture'],
|
||||
[DOMTopLevelEventTypes.TOP_LOAD, 'load'],
|
||||
[DOMTopLevelEventTypes.TOP_LOADED_DATA, 'loadedData'],
|
||||
[DOMTopLevelEventTypes.TOP_LOADED_METADATA, 'loadedMetadata'],
|
||||
[DOMTopLevelEventTypes.TOP_LOAD_START, 'loadStart'],
|
||||
[DOMTopLevelEventTypes.TOP_LOST_POINTER_CAPTURE, 'lostPointerCapture'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_MOVE, 'mouseMove'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_OUT, 'mouseOut'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_OVER, 'mouseOver'],
|
||||
[DOMTopLevelEventTypes.TOP_PLAYING, 'playing'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_MOVE, 'pointerMove'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_OUT, 'pointerOut'],
|
||||
[DOMTopLevelEventTypes.TOP_POINTER_OVER, 'pointerOver'],
|
||||
[DOMTopLevelEventTypes.TOP_PROGRESS, 'progress'],
|
||||
[DOMTopLevelEventTypes.TOP_SCROLL, 'scroll'],
|
||||
[DOMTopLevelEventTypes.TOP_SEEKING, 'seeking'],
|
||||
[DOMTopLevelEventTypes.TOP_STALLED, 'stalled'],
|
||||
[DOMTopLevelEventTypes.TOP_SUSPEND, 'suspend'],
|
||||
[DOMTopLevelEventTypes.TOP_TIME_UPDATE, 'timeUpdate'],
|
||||
[DOMTopLevelEventTypes.TOP_TOGGLE, 'toggle'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_MOVE, 'touchMove'],
|
||||
[DOMTopLevelEventTypes.TOP_TRANSITION_END, 'transitionEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_WAITING, 'waiting'],
|
||||
[DOMTopLevelEventTypes.TOP_WHEEL, 'wheel'],
|
||||
];
|
||||
|
||||
const eventTypes: EventTypes = {};
|
||||
const topLevelEventsToDispatchConfig: {
|
||||
[key: TopLevelTypes]: DispatchConfig,
|
||||
[key: TopLevelType]: DispatchConfig,
|
||||
} = {};
|
||||
|
||||
function addEventTypeNameToConfig(event: string, isInteractive: boolean) {
|
||||
function addEventTypeNameToConfig(
|
||||
[topEvent, event]: EventTuple,
|
||||
isInteractive: boolean,
|
||||
) {
|
||||
const capitalizedEvent = event[0].toUpperCase() + event.slice(1);
|
||||
const onEvent = 'on' + capitalizedEvent;
|
||||
const topEvent = 'top' + capitalizedEvent;
|
||||
|
||||
const type = {
|
||||
phasedRegistrationNames: {
|
||||
@@ -141,58 +158,60 @@ function addEventTypeNameToConfig(event: string, isInteractive: boolean) {
|
||||
topLevelEventsToDispatchConfig[topEvent] = type;
|
||||
}
|
||||
|
||||
interactiveEventTypeNames.forEach(eventTypeName => {
|
||||
addEventTypeNameToConfig(eventTypeName, true);
|
||||
interactiveEventTypeNames.forEach(eventTuple => {
|
||||
addEventTypeNameToConfig(eventTuple, true);
|
||||
});
|
||||
nonInteractiveEventTypeNames.forEach(eventTypeName => {
|
||||
addEventTypeNameToConfig(eventTypeName, false);
|
||||
nonInteractiveEventTypeNames.forEach(eventTuple => {
|
||||
addEventTypeNameToConfig(eventTuple, false);
|
||||
});
|
||||
|
||||
// Only used in DEV for exhaustiveness validation.
|
||||
const knownHTMLTopLevelTypes = [
|
||||
'topAbort',
|
||||
'topCancel',
|
||||
'topCanPlay',
|
||||
'topCanPlayThrough',
|
||||
'topClose',
|
||||
'topDurationChange',
|
||||
'topEmptied',
|
||||
'topEncrypted',
|
||||
'topEnded',
|
||||
'topError',
|
||||
'topInput',
|
||||
'topInvalid',
|
||||
'topLoad',
|
||||
'topLoadedData',
|
||||
'topLoadedMetadata',
|
||||
'topLoadStart',
|
||||
'topPause',
|
||||
'topPlay',
|
||||
'topPlaying',
|
||||
'topProgress',
|
||||
'topRateChange',
|
||||
'topReset',
|
||||
'topSeeked',
|
||||
'topSeeking',
|
||||
'topStalled',
|
||||
'topSubmit',
|
||||
'topSuspend',
|
||||
'topTimeUpdate',
|
||||
'topToggle',
|
||||
'topVolumeChange',
|
||||
'topWaiting',
|
||||
const knownHTMLTopLevelTypes: Array<DOMTopLevelEventType> = [
|
||||
DOMTopLevelEventTypes.TOP_ABORT,
|
||||
DOMTopLevelEventTypes.TOP_CANCEL,
|
||||
DOMTopLevelEventTypes.TOP_CAN_PLAY,
|
||||
DOMTopLevelEventTypes.TOP_CAN_PLAY_THROUGH,
|
||||
DOMTopLevelEventTypes.TOP_CLOSE,
|
||||
DOMTopLevelEventTypes.TOP_DURATION_CHANGE,
|
||||
DOMTopLevelEventTypes.TOP_EMPTIED,
|
||||
DOMTopLevelEventTypes.TOP_ENCRYPTED,
|
||||
DOMTopLevelEventTypes.TOP_ENDED,
|
||||
DOMTopLevelEventTypes.TOP_ERROR,
|
||||
DOMTopLevelEventTypes.TOP_INPUT,
|
||||
DOMTopLevelEventTypes.TOP_INVALID,
|
||||
DOMTopLevelEventTypes.TOP_LOAD,
|
||||
DOMTopLevelEventTypes.TOP_LOADED_DATA,
|
||||
DOMTopLevelEventTypes.TOP_LOADED_METADATA,
|
||||
DOMTopLevelEventTypes.TOP_LOAD_START,
|
||||
DOMTopLevelEventTypes.TOP_PAUSE,
|
||||
DOMTopLevelEventTypes.TOP_PLAY,
|
||||
DOMTopLevelEventTypes.TOP_PLAYING,
|
||||
DOMTopLevelEventTypes.TOP_PROGRESS,
|
||||
DOMTopLevelEventTypes.TOP_RATE_CHANGE,
|
||||
DOMTopLevelEventTypes.TOP_RESET,
|
||||
DOMTopLevelEventTypes.TOP_SEEKED,
|
||||
DOMTopLevelEventTypes.TOP_SEEKING,
|
||||
DOMTopLevelEventTypes.TOP_STALLED,
|
||||
DOMTopLevelEventTypes.TOP_SUBMIT,
|
||||
DOMTopLevelEventTypes.TOP_SUSPEND,
|
||||
DOMTopLevelEventTypes.TOP_TIME_UPDATE,
|
||||
DOMTopLevelEventTypes.TOP_TOGGLE,
|
||||
DOMTopLevelEventTypes.TOP_VOLUME_CHANGE,
|
||||
DOMTopLevelEventTypes.TOP_WAITING,
|
||||
];
|
||||
|
||||
const SimpleEventPlugin: PluginModule<MouseEvent> = {
|
||||
const SimpleEventPlugin: PluginModule<MouseEvent> & {
|
||||
isInteractiveTopLevelEventType: (topLevelType: TopLevelType) => boolean,
|
||||
} = {
|
||||
eventTypes: eventTypes,
|
||||
|
||||
isInteractiveTopLevelEventType(topLevelType: TopLevelTypes): boolean {
|
||||
isInteractiveTopLevelEventType(topLevelType: TopLevelType): boolean {
|
||||
const config = topLevelEventsToDispatchConfig[topLevelType];
|
||||
return config !== undefined && config.isInteractive === true;
|
||||
},
|
||||
|
||||
extractEvents: function(
|
||||
topLevelType: TopLevelTypes,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: Fiber,
|
||||
nativeEvent: MouseEvent,
|
||||
nativeEventTarget: EventTarget,
|
||||
@@ -203,7 +222,7 @@ const SimpleEventPlugin: PluginModule<MouseEvent> = {
|
||||
}
|
||||
let EventConstructor;
|
||||
switch (topLevelType) {
|
||||
case 'topKeyPress':
|
||||
case DOMTopLevelEventTypes.TOP_KEY_PRESS:
|
||||
// Firefox creates a keypress event for function keys too. This removes
|
||||
// the unwanted keypress events. Enter is however both printable and
|
||||
// non-printable. One would expect Tab to be as well (but it isn't).
|
||||
@@ -211,67 +230,77 @@ const SimpleEventPlugin: PluginModule<MouseEvent> = {
|
||||
return null;
|
||||
}
|
||||
/* falls through */
|
||||
case 'topKeyDown':
|
||||
case 'topKeyUp':
|
||||
case DOMTopLevelEventTypes.TOP_KEY_DOWN:
|
||||
case DOMTopLevelEventTypes.TOP_KEY_UP:
|
||||
EventConstructor = SyntheticKeyboardEvent;
|
||||
break;
|
||||
case 'topBlur':
|
||||
case 'topFocus':
|
||||
case DOMTopLevelEventTypes.TOP_BLUR:
|
||||
case DOMTopLevelEventTypes.TOP_FOCUS:
|
||||
EventConstructor = SyntheticFocusEvent;
|
||||
break;
|
||||
case 'topClick':
|
||||
case DOMTopLevelEventTypes.TOP_CLICK:
|
||||
// Firefox creates a click event on right mouse clicks. This removes the
|
||||
// unwanted click events.
|
||||
if (nativeEvent.button === 2) {
|
||||
return null;
|
||||
}
|
||||
/* falls through */
|
||||
case 'topDoubleClick':
|
||||
case 'topMouseDown':
|
||||
case 'topMouseMove':
|
||||
case 'topMouseUp':
|
||||
case DOMTopLevelEventTypes.TOP_DOUBLE_CLICK:
|
||||
case DOMTopLevelEventTypes.TOP_MOUSE_DOWN:
|
||||
case DOMTopLevelEventTypes.TOP_MOUSE_MOVE:
|
||||
case DOMTopLevelEventTypes.TOP_MOUSE_UP:
|
||||
// TODO: Disabled elements should not respond to mouse events
|
||||
/* falls through */
|
||||
case 'topMouseOut':
|
||||
case 'topMouseOver':
|
||||
case 'topContextMenu':
|
||||
case DOMTopLevelEventTypes.TOP_MOUSE_OUT:
|
||||
case DOMTopLevelEventTypes.TOP_MOUSE_OVER:
|
||||
case DOMTopLevelEventTypes.TOP_CONTEXT_MENU:
|
||||
EventConstructor = SyntheticMouseEvent;
|
||||
break;
|
||||
case 'topDrag':
|
||||
case 'topDragEnd':
|
||||
case 'topDragEnter':
|
||||
case 'topDragExit':
|
||||
case 'topDragLeave':
|
||||
case 'topDragOver':
|
||||
case 'topDragStart':
|
||||
case 'topDrop':
|
||||
case DOMTopLevelEventTypes.TOP_DRAG:
|
||||
case DOMTopLevelEventTypes.TOP_DRAG_END:
|
||||
case DOMTopLevelEventTypes.TOP_DRAG_ENTER:
|
||||
case DOMTopLevelEventTypes.TOP_DRAG_EXIT:
|
||||
case DOMTopLevelEventTypes.TOP_DRAG_LEAVE:
|
||||
case DOMTopLevelEventTypes.TOP_DRAG_OVER:
|
||||
case DOMTopLevelEventTypes.TOP_DRAG_START:
|
||||
case DOMTopLevelEventTypes.TOP_DROP:
|
||||
EventConstructor = SyntheticDragEvent;
|
||||
break;
|
||||
case 'topTouchCancel':
|
||||
case 'topTouchEnd':
|
||||
case 'topTouchMove':
|
||||
case 'topTouchStart':
|
||||
case DOMTopLevelEventTypes.TOP_TOUCH_CANCEL:
|
||||
case DOMTopLevelEventTypes.TOP_TOUCH_END:
|
||||
case DOMTopLevelEventTypes.TOP_TOUCH_MOVE:
|
||||
case DOMTopLevelEventTypes.TOP_TOUCH_START:
|
||||
EventConstructor = SyntheticTouchEvent;
|
||||
break;
|
||||
case 'topAnimationEnd':
|
||||
case 'topAnimationIteration':
|
||||
case 'topAnimationStart':
|
||||
case DOMTopLevelEventTypes.TOP_ANIMATION_END:
|
||||
case DOMTopLevelEventTypes.TOP_ANIMATION_ITERATION:
|
||||
case DOMTopLevelEventTypes.TOP_ANIMATION_START:
|
||||
EventConstructor = SyntheticAnimationEvent;
|
||||
break;
|
||||
case 'topTransitionEnd':
|
||||
case DOMTopLevelEventTypes.TOP_TRANSITION_END:
|
||||
EventConstructor = SyntheticTransitionEvent;
|
||||
break;
|
||||
case 'topScroll':
|
||||
case DOMTopLevelEventTypes.TOP_SCROLL:
|
||||
EventConstructor = SyntheticUIEvent;
|
||||
break;
|
||||
case 'topWheel':
|
||||
case DOMTopLevelEventTypes.TOP_WHEEL:
|
||||
EventConstructor = SyntheticWheelEvent;
|
||||
break;
|
||||
case 'topCopy':
|
||||
case 'topCut':
|
||||
case 'topPaste':
|
||||
case DOMTopLevelEventTypes.TOP_COPY:
|
||||
case DOMTopLevelEventTypes.TOP_CUT:
|
||||
case DOMTopLevelEventTypes.TOP_PASTE:
|
||||
EventConstructor = SyntheticClipboardEvent;
|
||||
break;
|
||||
case DOMTopLevelEventTypes.TOP_GOT_POINTER_CAPTURE:
|
||||
case DOMTopLevelEventTypes.TOP_LOST_POINTER_CAPTURE:
|
||||
case DOMTopLevelEventTypes.TOP_POINTER_CANCEL:
|
||||
case DOMTopLevelEventTypes.TOP_POINTER_DOWN:
|
||||
case DOMTopLevelEventTypes.TOP_POINTER_MOVE:
|
||||
case DOMTopLevelEventTypes.TOP_POINTER_OUT:
|
||||
case DOMTopLevelEventTypes.TOP_POINTER_OVER:
|
||||
case DOMTopLevelEventTypes.TOP_POINTER_UP:
|
||||
EventConstructor = SyntheticPointerEvent;
|
||||
break;
|
||||
default:
|
||||
if (__DEV__) {
|
||||
if (knownHTMLTopLevelTypes.indexOf(topLevelType) === -1) {
|
||||
|
||||
25
packages/react-dom/src/events/SyntheticPointerEvent.js
vendored
Normal file
25
packages/react-dom/src/events/SyntheticPointerEvent.js
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 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 SyntheticMouseEvent from './SyntheticMouseEvent';
|
||||
|
||||
/**
|
||||
* @interface PointerEvent
|
||||
* @see http://www.w3.org/TR/pointerevents/
|
||||
*/
|
||||
const SyntheticPointerEvent = SyntheticMouseEvent.extend({
|
||||
pointerId: null,
|
||||
width: null,
|
||||
height: null,
|
||||
pressure: null,
|
||||
tiltX: null,
|
||||
tiltY: null,
|
||||
pointerType: null,
|
||||
isPrimary: null,
|
||||
});
|
||||
|
||||
export default SyntheticPointerEvent;
|
||||
58
packages/react-dom/src/events/TapEventPlugin.js
vendored
58
packages/react-dom/src/events/TapEventPlugin.js
vendored
@@ -7,12 +7,43 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {isStartish, isEndish} from 'events/EventPluginUtils';
|
||||
import {accumulateTwoPhaseDispatches} from 'events/EventPropagators';
|
||||
import TouchEventUtils from 'fbjs/lib/TouchEventUtils';
|
||||
import type {TopLevelType} from 'events/TopLevelEventTypes';
|
||||
|
||||
import {
|
||||
TOP_MOUSE_DOWN,
|
||||
TOP_MOUSE_MOVE,
|
||||
TOP_MOUSE_UP,
|
||||
TOP_POINTER_CANCEL,
|
||||
TOP_POINTER_DOWN,
|
||||
TOP_POINTER_UP,
|
||||
TOP_POINTER_MOVE,
|
||||
TOP_TOUCH_CANCEL,
|
||||
TOP_TOUCH_END,
|
||||
TOP_TOUCH_MOVE,
|
||||
TOP_TOUCH_START,
|
||||
} from './DOMTopLevelEventTypes';
|
||||
import SyntheticUIEvent from './SyntheticUIEvent';
|
||||
|
||||
function isStartish(topLevelType) {
|
||||
return (
|
||||
topLevelType === TOP_MOUSE_DOWN ||
|
||||
topLevelType === TOP_TOUCH_START ||
|
||||
topLevelType === TOP_POINTER_DOWN
|
||||
);
|
||||
}
|
||||
|
||||
function isEndish(topLevelType) {
|
||||
return (
|
||||
topLevelType === TOP_MOUSE_UP ||
|
||||
topLevelType === TOP_POINTER_CANCEL ||
|
||||
topLevelType === TOP_POINTER_UP ||
|
||||
topLevelType === TOP_TOUCH_CANCEL ||
|
||||
topLevelType === TOP_TOUCH_END
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* We are extending the Flow 'Touch' declaration to enable using bracket
|
||||
* notation to access properties.
|
||||
@@ -75,15 +106,24 @@ function getDistance(coords: CoordinatesType, nativeEvent: _Touch): number {
|
||||
}
|
||||
|
||||
const touchEvents = [
|
||||
'topTouchStart',
|
||||
'topTouchCancel',
|
||||
'topTouchEnd',
|
||||
'topTouchMove',
|
||||
TOP_TOUCH_START,
|
||||
TOP_TOUCH_CANCEL,
|
||||
TOP_TOUCH_END,
|
||||
TOP_TOUCH_MOVE,
|
||||
];
|
||||
|
||||
const dependencies = ['topMouseDown', 'topMouseMove', 'topMouseUp'].concat(
|
||||
touchEvents,
|
||||
);
|
||||
const pointerEvents = [
|
||||
TOP_POINTER_CANCEL,
|
||||
TOP_POINTER_DOWN,
|
||||
TOP_POINTER_MOVE,
|
||||
TOP_POINTER_UP,
|
||||
];
|
||||
|
||||
const dependencies: Array<TopLevelType> = [
|
||||
TOP_MOUSE_DOWN,
|
||||
TOP_MOUSE_MOVE,
|
||||
TOP_MOUSE_UP,
|
||||
].concat(touchEvents, pointerEvents);
|
||||
|
||||
const eventTypes = {
|
||||
touchTap: {
|
||||
@@ -105,7 +145,7 @@ const TapEventPlugin = {
|
||||
eventTypes: eventTypes,
|
||||
|
||||
extractEvents: function(
|
||||
topLevelType: mixed,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: mixed,
|
||||
nativeEvent: _Touch,
|
||||
nativeEventTarget: EventTarget,
|
||||
|
||||
@@ -32,6 +32,31 @@ describe('ChangeEventPlugin', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
// TODO pull this into helper method, reduce repetition.
|
||||
// mock the browser APIs which are used in react-scheduler:
|
||||
// - requestAnimationFrame should pass the DOMHighResTimeStamp argument
|
||||
// - calling 'window.postMessage' should actually fire postmessage handlers
|
||||
global.requestAnimationFrame = function(cb) {
|
||||
return setTimeout(() => {
|
||||
cb(Date.now());
|
||||
});
|
||||
};
|
||||
const originalAddEventListener = global.addEventListener;
|
||||
let postMessageCallback;
|
||||
global.addEventListener = function(eventName, callback, useCapture) {
|
||||
if (eventName === 'message') {
|
||||
postMessageCallback = callback;
|
||||
} else {
|
||||
originalAddEventListener(eventName, callback, useCapture);
|
||||
}
|
||||
};
|
||||
global.postMessage = function(messageKey, targetOrigin) {
|
||||
const postMessageEvent = {source: window, data: messageKey};
|
||||
if (postMessageCallback) {
|
||||
postMessageCallback(postMessageEvent);
|
||||
}
|
||||
};
|
||||
jest.resetModules();
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
@@ -450,8 +475,6 @@ describe('ChangeEventPlugin', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableAsyncSubtreeAPI = true;
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactDOM = require('react-dom');
|
||||
});
|
||||
|
||||
@@ -33,6 +33,31 @@ describe('SimpleEventPlugin', function() {
|
||||
}
|
||||
|
||||
beforeEach(function() {
|
||||
// TODO pull this into helper method, reduce repetition.
|
||||
// mock the browser APIs which are used in react-scheduler:
|
||||
// - requestAnimationFrame should pass the DOMHighResTimeStamp argument
|
||||
// - calling 'window.postMessage' should actually fire postmessage handlers
|
||||
global.requestAnimationFrame = function(cb) {
|
||||
return setTimeout(() => {
|
||||
cb(Date.now());
|
||||
});
|
||||
};
|
||||
const originalAddEventListener = global.addEventListener;
|
||||
let postMessageCallback;
|
||||
global.addEventListener = function(eventName, callback, useCapture) {
|
||||
if (eventName === 'message') {
|
||||
postMessageCallback = callback;
|
||||
} else {
|
||||
originalAddEventListener(eventName, callback, useCapture);
|
||||
}
|
||||
};
|
||||
global.postMessage = function(messageKey, targetOrigin) {
|
||||
const postMessageEvent = {source: window, data: messageKey};
|
||||
if (postMessageCallback) {
|
||||
postMessageCallback(postMessageEvent);
|
||||
}
|
||||
};
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactTestUtils = require('react-dom/test-utils');
|
||||
@@ -221,7 +246,6 @@ describe('SimpleEventPlugin', function() {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.enableAsyncSubtreeAPI = true;
|
||||
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
|
||||
ReactDOM = require('react-dom');
|
||||
});
|
||||
|
||||
@@ -147,9 +147,7 @@ describe('SyntheticEvent', () => {
|
||||
|
||||
const getExpectedWarning = property =>
|
||||
'Warning: This synthetic event is reused for performance reasons. If ' +
|
||||
`you're seeing this, you're accessing the property \`${
|
||||
property
|
||||
}\` on a ` +
|
||||
`you're seeing this, you're accessing the property \`${property}\` on a ` +
|
||||
'released/nullified synthetic event. This is set to null. If you must ' +
|
||||
'keep the original synthetic event around, use event.persist(). ' +
|
||||
'See https://fb.me/react-event-pooling for more information.';
|
||||
|
||||
@@ -31,9 +31,8 @@ import {
|
||||
REACT_FRAGMENT_TYPE,
|
||||
REACT_STRICT_MODE_TYPE,
|
||||
REACT_ASYNC_MODE_TYPE,
|
||||
REACT_CALL_TYPE,
|
||||
REACT_RETURN_TYPE,
|
||||
REACT_PORTAL_TYPE,
|
||||
REACT_PROFILER_TYPE,
|
||||
REACT_PROVIDER_TYPE,
|
||||
REACT_CONTEXT_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
@@ -811,6 +810,7 @@ class ReactDOMServerRenderer {
|
||||
switch (elementType) {
|
||||
case REACT_STRICT_MODE_TYPE:
|
||||
case REACT_ASYNC_MODE_TYPE:
|
||||
case REACT_PROFILER_TYPE:
|
||||
case REACT_FRAGMENT_TYPE: {
|
||||
const nextChildren = toArray(
|
||||
((nextChild: any): ReactElement).props.children,
|
||||
@@ -829,13 +829,6 @@ class ReactDOMServerRenderer {
|
||||
this.stack.push(frame);
|
||||
return '';
|
||||
}
|
||||
case REACT_CALL_TYPE:
|
||||
case REACT_RETURN_TYPE:
|
||||
invariant(
|
||||
false,
|
||||
'The experimental Call and Return types are not currently ' +
|
||||
'supported by the server renderer.',
|
||||
);
|
||||
// eslint-disable-next-line-no-fallthrough
|
||||
default:
|
||||
break;
|
||||
|
||||
3
packages/react-dom/src/shared/DOMProperty.js
vendored
3
packages/react-dom/src/shared/DOMProperty.js
vendored
@@ -157,6 +157,9 @@ export function shouldRemoveAttribute(
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (isCustomComponentTag) {
|
||||
return false;
|
||||
}
|
||||
if (propertyInfo !== null) {
|
||||
switch (propertyInfo.type) {
|
||||
case BOOLEAN:
|
||||
|
||||
155
packages/react-dom/src/test-utils/ReactTestUtils.js
vendored
155
packages/react-dom/src/test-utils/ReactTestUtils.js
vendored
@@ -18,7 +18,7 @@ import {
|
||||
import SyntheticEvent from 'events/SyntheticEvent';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
import {topLevelTypes, mediaEventTypes} from '../events/BrowserEventConstants';
|
||||
import * as DOMTopLevelEventTypes from '../events/DOMTopLevelEventTypes';
|
||||
|
||||
const {findDOMNode} = ReactDOM;
|
||||
const {
|
||||
@@ -36,6 +36,33 @@ function Event(suffix) {}
|
||||
* @class ReactTestUtils
|
||||
*/
|
||||
|
||||
/**
|
||||
* Simulates a top level event being dispatched from a raw event that occurred
|
||||
* on an `Element` node.
|
||||
* @param {number} topLevelType A number from `TopLevelEventTypes`
|
||||
* @param {!Element} node The dom to simulate an event occurring on.
|
||||
* @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
|
||||
*/
|
||||
function simulateNativeEventOnNode(topLevelType, node, fakeNativeEvent) {
|
||||
fakeNativeEvent.target = node;
|
||||
ReactDOMEventListener.dispatchEvent(topLevelType, fakeNativeEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates a top level event being dispatched from a raw event that occurred
|
||||
* on the `ReactDOMComponent` `comp`.
|
||||
* @param {Object} topLevelType A type from `BrowserEventConstants.topLevelTypes`.
|
||||
* @param {!ReactDOMComponent} comp
|
||||
* @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
|
||||
*/
|
||||
function simulateNativeEventOnDOMComponent(
|
||||
topLevelType,
|
||||
comp,
|
||||
fakeNativeEvent,
|
||||
) {
|
||||
simulateNativeEventOnNode(topLevelType, findDOMNode(comp), fakeNativeEvent);
|
||||
}
|
||||
|
||||
function findAllInRenderedFiberTreeInternal(fiber, test) {
|
||||
if (!fiber) {
|
||||
return [];
|
||||
@@ -291,37 +318,6 @@ const ReactTestUtils = {
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Simulates a top level event being dispatched from a raw event that occurred
|
||||
* on an `Element` node.
|
||||
* @param {Object} topLevelType A type from `BrowserEventConstants.topLevelTypes`
|
||||
* @param {!Element} node The dom to simulate an event occurring on.
|
||||
* @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
|
||||
*/
|
||||
simulateNativeEventOnNode: function(topLevelType, node, fakeNativeEvent) {
|
||||
fakeNativeEvent.target = node;
|
||||
ReactDOMEventListener.dispatchEvent(topLevelType, fakeNativeEvent);
|
||||
},
|
||||
|
||||
/**
|
||||
* Simulates a top level event being dispatched from a raw event that occurred
|
||||
* on the `ReactDOMComponent` `comp`.
|
||||
* @param {Object} topLevelType A type from `BrowserEventConstants.topLevelTypes`.
|
||||
* @param {!ReactDOMComponent} comp
|
||||
* @param {?Event} fakeNativeEvent Fake native event to use in SyntheticEvent.
|
||||
*/
|
||||
simulateNativeEventOnDOMComponent: function(
|
||||
topLevelType,
|
||||
comp,
|
||||
fakeNativeEvent,
|
||||
) {
|
||||
ReactTestUtils.simulateNativeEventOnNode(
|
||||
topLevelType,
|
||||
findDOMNode(comp),
|
||||
fakeNativeEvent,
|
||||
);
|
||||
},
|
||||
|
||||
nativeTouchData: function(x, y) {
|
||||
return {
|
||||
touches: [{pageX: x, pageY: y}],
|
||||
@@ -436,20 +432,20 @@ buildSimulators();
|
||||
* to dispatch synthetic events.
|
||||
*/
|
||||
|
||||
function makeNativeSimulator(eventType) {
|
||||
function makeNativeSimulator(eventType, topLevelType) {
|
||||
return function(domComponentOrNode, nativeEventData) {
|
||||
const fakeNativeEvent = new Event(eventType);
|
||||
Object.assign(fakeNativeEvent, nativeEventData);
|
||||
if (ReactTestUtils.isDOMComponent(domComponentOrNode)) {
|
||||
ReactTestUtils.simulateNativeEventOnDOMComponent(
|
||||
eventType,
|
||||
simulateNativeEventOnDOMComponent(
|
||||
topLevelType,
|
||||
domComponentOrNode,
|
||||
fakeNativeEvent,
|
||||
);
|
||||
} else if (domComponentOrNode.tagName) {
|
||||
// Will allow on actual dom nodes.
|
||||
ReactTestUtils.simulateNativeEventOnNode(
|
||||
eventType,
|
||||
simulateNativeEventOnNode(
|
||||
topLevelType,
|
||||
domComponentOrNode,
|
||||
fakeNativeEvent,
|
||||
);
|
||||
@@ -457,23 +453,84 @@ function makeNativeSimulator(eventType) {
|
||||
};
|
||||
}
|
||||
|
||||
const eventKeys = [].concat(
|
||||
Object.keys(topLevelTypes),
|
||||
Object.keys(mediaEventTypes),
|
||||
);
|
||||
|
||||
eventKeys.forEach(function(eventType) {
|
||||
// Event type is stored as 'topClick' - we transform that to 'click'
|
||||
const convenienceName =
|
||||
eventType.indexOf('top') === 0
|
||||
? eventType.charAt(3).toLowerCase() + eventType.substr(4)
|
||||
: eventType;
|
||||
[
|
||||
[DOMTopLevelEventTypes.TOP_ABORT, 'abort'],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_END, 'animationEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_ITERATION, 'animationIteration'],
|
||||
[DOMTopLevelEventTypes.TOP_ANIMATION_START, 'animationStart'],
|
||||
[DOMTopLevelEventTypes.TOP_BLUR, 'blur'],
|
||||
[DOMTopLevelEventTypes.TOP_CAN_PLAY_THROUGH, 'canPlayThrough'],
|
||||
[DOMTopLevelEventTypes.TOP_CAN_PLAY, 'canPlay'],
|
||||
[DOMTopLevelEventTypes.TOP_CANCEL, 'cancel'],
|
||||
[DOMTopLevelEventTypes.TOP_CHANGE, 'change'],
|
||||
[DOMTopLevelEventTypes.TOP_CLICK, 'click'],
|
||||
[DOMTopLevelEventTypes.TOP_CLOSE, 'close'],
|
||||
[DOMTopLevelEventTypes.TOP_COMPOSITION_END, 'compositionEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_COMPOSITION_START, 'compositionStart'],
|
||||
[DOMTopLevelEventTypes.TOP_COMPOSITION_UPDATE, 'compositionUpdate'],
|
||||
[DOMTopLevelEventTypes.TOP_CONTEXT_MENU, 'contextMenu'],
|
||||
[DOMTopLevelEventTypes.TOP_COPY, 'copy'],
|
||||
[DOMTopLevelEventTypes.TOP_CUT, 'cut'],
|
||||
[DOMTopLevelEventTypes.TOP_DOUBLE_CLICK, 'doubleClick'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_END, 'dragEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_ENTER, 'dragEnter'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_EXIT, 'dragExit'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_LEAVE, 'dragLeave'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_OVER, 'dragOver'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG_START, 'dragStart'],
|
||||
[DOMTopLevelEventTypes.TOP_DRAG, 'drag'],
|
||||
[DOMTopLevelEventTypes.TOP_DROP, 'drop'],
|
||||
[DOMTopLevelEventTypes.TOP_DURATION_CHANGE, 'durationChange'],
|
||||
[DOMTopLevelEventTypes.TOP_EMPTIED, 'emptied'],
|
||||
[DOMTopLevelEventTypes.TOP_ENCRYPTED, 'encrypted'],
|
||||
[DOMTopLevelEventTypes.TOP_ENDED, 'ended'],
|
||||
[DOMTopLevelEventTypes.TOP_ERROR, 'error'],
|
||||
[DOMTopLevelEventTypes.TOP_FOCUS, 'focus'],
|
||||
[DOMTopLevelEventTypes.TOP_INPUT, 'input'],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_DOWN, 'keyDown'],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_PRESS, 'keyPress'],
|
||||
[DOMTopLevelEventTypes.TOP_KEY_UP, 'keyUp'],
|
||||
[DOMTopLevelEventTypes.TOP_LOAD_START, 'loadStart'],
|
||||
[DOMTopLevelEventTypes.TOP_LOAD_START, 'loadStart'],
|
||||
[DOMTopLevelEventTypes.TOP_LOAD, 'load'],
|
||||
[DOMTopLevelEventTypes.TOP_LOADED_DATA, 'loadedData'],
|
||||
[DOMTopLevelEventTypes.TOP_LOADED_METADATA, 'loadedMetadata'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_DOWN, 'mouseDown'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_MOVE, 'mouseMove'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_OUT, 'mouseOut'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_OVER, 'mouseOver'],
|
||||
[DOMTopLevelEventTypes.TOP_MOUSE_UP, 'mouseUp'],
|
||||
[DOMTopLevelEventTypes.TOP_PASTE, 'paste'],
|
||||
[DOMTopLevelEventTypes.TOP_PAUSE, 'pause'],
|
||||
[DOMTopLevelEventTypes.TOP_PLAY, 'play'],
|
||||
[DOMTopLevelEventTypes.TOP_PLAYING, 'playing'],
|
||||
[DOMTopLevelEventTypes.TOP_PROGRESS, 'progress'],
|
||||
[DOMTopLevelEventTypes.TOP_RATE_CHANGE, 'rateChange'],
|
||||
[DOMTopLevelEventTypes.TOP_SCROLL, 'scroll'],
|
||||
[DOMTopLevelEventTypes.TOP_SEEKED, 'seeked'],
|
||||
[DOMTopLevelEventTypes.TOP_SEEKING, 'seeking'],
|
||||
[DOMTopLevelEventTypes.TOP_SELECTION_CHANGE, 'selectionChange'],
|
||||
[DOMTopLevelEventTypes.TOP_STALLED, 'stalled'],
|
||||
[DOMTopLevelEventTypes.TOP_SUSPEND, 'suspend'],
|
||||
[DOMTopLevelEventTypes.TOP_TEXT_INPUT, 'textInput'],
|
||||
[DOMTopLevelEventTypes.TOP_TIME_UPDATE, 'timeUpdate'],
|
||||
[DOMTopLevelEventTypes.TOP_TOGGLE, 'toggle'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_CANCEL, 'touchCancel'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_END, 'touchEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_MOVE, 'touchMove'],
|
||||
[DOMTopLevelEventTypes.TOP_TOUCH_START, 'touchStart'],
|
||||
[DOMTopLevelEventTypes.TOP_TRANSITION_END, 'transitionEnd'],
|
||||
[DOMTopLevelEventTypes.TOP_VOLUME_CHANGE, 'volumeChange'],
|
||||
[DOMTopLevelEventTypes.TOP_WAITING, 'waiting'],
|
||||
[DOMTopLevelEventTypes.TOP_WHEEL, 'wheel'],
|
||||
].forEach(([topLevelType, eventType]) => {
|
||||
/**
|
||||
* @param {!Element|ReactDOMComponent} domComponentOrNode
|
||||
* @param {?Event} nativeEventData Fake native event to use in SyntheticEvent.
|
||||
*/
|
||||
ReactTestUtils.SimulateNative[convenienceName] = makeNativeSimulator(
|
||||
ReactTestUtils.SimulateNative[eventType] = makeNativeSimulator(
|
||||
eventType,
|
||||
topLevelType,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-is",
|
||||
"version": "16.3.2",
|
||||
"version": "16.4.0",
|
||||
"description": "Brand checking of React Elements.",
|
||||
"main": "index.js",
|
||||
"repository": "facebook/react",
|
||||
|
||||
6
packages/react-is/src/ReactIs.js
vendored
6
packages/react-is/src/ReactIs.js
vendored
@@ -16,6 +16,7 @@ import {
|
||||
REACT_FORWARD_REF_TYPE,
|
||||
REACT_FRAGMENT_TYPE,
|
||||
REACT_PORTAL_TYPE,
|
||||
REACT_PROFILER_TYPE,
|
||||
REACT_PROVIDER_TYPE,
|
||||
REACT_STRICT_MODE_TYPE,
|
||||
} from 'shared/ReactSymbols';
|
||||
@@ -32,6 +33,7 @@ export function typeOf(object: any) {
|
||||
switch (type) {
|
||||
case REACT_ASYNC_MODE_TYPE:
|
||||
case REACT_FRAGMENT_TYPE:
|
||||
case REACT_PROFILER_TYPE:
|
||||
case REACT_STRICT_MODE_TYPE:
|
||||
return type;
|
||||
default:
|
||||
@@ -60,6 +62,7 @@ export const ContextProvider = REACT_PROVIDER_TYPE;
|
||||
export const Element = REACT_ELEMENT_TYPE;
|
||||
export const ForwardRef = REACT_FORWARD_REF_TYPE;
|
||||
export const Fragment = REACT_FRAGMENT_TYPE;
|
||||
export const Profiler = REACT_PROFILER_TYPE;
|
||||
export const Portal = REACT_PORTAL_TYPE;
|
||||
export const StrictMode = REACT_STRICT_MODE_TYPE;
|
||||
|
||||
@@ -87,6 +90,9 @@ export function isForwardRef(object: any) {
|
||||
export function isFragment(object: any) {
|
||||
return typeOf(object) === REACT_FRAGMENT_TYPE;
|
||||
}
|
||||
export function isProfiler(object: any) {
|
||||
return typeOf(object) === REACT_PROFILER_TYPE;
|
||||
}
|
||||
export function isPortal(object: any) {
|
||||
return typeOf(object) === REACT_PORTAL_TYPE;
|
||||
}
|
||||
|
||||
14
packages/react-is/src/__tests__/ReactIs-test.js
vendored
14
packages/react-is/src/__tests__/ReactIs-test.js
vendored
@@ -145,4 +145,18 @@ describe('ReactIs', () => {
|
||||
expect(ReactIs.isStrictMode(<React.unstable_AsyncMode />)).toBe(false);
|
||||
expect(ReactIs.isStrictMode(<div />)).toBe(false);
|
||||
});
|
||||
|
||||
it('should identify profile root', () => {
|
||||
expect(
|
||||
ReactIs.typeOf(<React.unstable_Profiler id="foo" onRender={jest.fn()} />),
|
||||
).toBe(ReactIs.Profiler);
|
||||
expect(
|
||||
ReactIs.isProfiler(
|
||||
<React.unstable_Profiler id="foo" onRender={jest.fn()} />,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(ReactIs.isProfiler({type: ReactIs.unstable_Profiler})).toBe(false);
|
||||
expect(ReactIs.isProfiler(<React.unstable_AsyncMode />)).toBe(false);
|
||||
expect(ReactIs.isProfiler(<div />)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,8 @@ import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
|
||||
import './ReactFabricInjection';
|
||||
|
||||
import * as ReactFabricRenderer from 'react-reconciler/inline.fabric';
|
||||
|
||||
import * as ReactPortal from 'shared/ReactPortal';
|
||||
import * as ReactGenericBatching from 'events/ReactGenericBatching';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
@@ -19,7 +21,6 @@ import ReactVersion from 'shared/ReactVersion';
|
||||
import NativeMethodsMixin from './NativeMethodsMixin';
|
||||
import ReactNativeComponent from './ReactNativeComponent';
|
||||
import * as ReactNativeComponentTree from './ReactNativeComponentTree';
|
||||
import ReactFabricRenderer from './ReactFabricRenderer';
|
||||
import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
|
||||
|
||||
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
|
||||
@@ -62,9 +63,11 @@ function findNodeHandle(componentOrHandle: any): ?number {
|
||||
if (hostInstance == null) {
|
||||
return hostInstance;
|
||||
}
|
||||
if (hostInstance.canonical) {
|
||||
// TODO: the code is right but the types here are wrong.
|
||||
// https://github.com/facebook/react/pull/12863
|
||||
if ((hostInstance: any).canonical) {
|
||||
// Fabric
|
||||
return hostInstance.canonical._nativeTag;
|
||||
return (hostInstance: any).canonical._nativeTag;
|
||||
}
|
||||
return hostInstance._nativeTag;
|
||||
}
|
||||
@@ -118,28 +121,6 @@ const ReactFabric: ReactFabricType = {
|
||||
},
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
// $FlowFixMe
|
||||
Object.assign(
|
||||
ReactFabric.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||
{
|
||||
// TODO: none of these work since Fiber. Remove these dependencies.
|
||||
// Used by RCTRenderingPerf, Systrace:
|
||||
ReactDebugTool: {
|
||||
addHook() {},
|
||||
removeHook() {},
|
||||
},
|
||||
// Used by ReactPerfStallHandler, RCTRenderingPerf:
|
||||
ReactPerf: {
|
||||
start() {},
|
||||
stop() {},
|
||||
printInclusive() {},
|
||||
printWasted() {},
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
ReactFabricRenderer.injectIntoDevTools({
|
||||
findFiberByHostInstance: ReactNativeComponentTree.getClosestInstanceFromNode,
|
||||
getInspectorDataForViewTag: getInspectorDataForViewTag,
|
||||
|
||||
37
packages/react-native-renderer/src/ReactFabricEventEmitter.js
vendored
Normal file
37
packages/react-native-renderer/src/ReactFabricEventEmitter.js
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2015-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 type {Fiber} from 'react-reconciler/src/ReactFiber';
|
||||
|
||||
import {getListener, runExtractedEventsInBatch} from 'events/EventPluginHub';
|
||||
import {registrationNameModules} from 'events/EventPluginRegistry';
|
||||
import {batchedUpdates} from 'events/ReactGenericBatching';
|
||||
|
||||
import type {AnyNativeEvent} from 'events/PluginModuleType';
|
||||
import type {TopLevelType} from 'events/TopLevelEventTypes';
|
||||
|
||||
export {getListener, registrationNameModules as registrationNames};
|
||||
|
||||
export function dispatchEvent(
|
||||
target: Object,
|
||||
topLevelType: TopLevelType,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
) {
|
||||
const targetFiber = (target: Fiber);
|
||||
batchedUpdates(function() {
|
||||
runExtractedEventsInBatch(
|
||||
topLevelType,
|
||||
targetFiber,
|
||||
nativeEvent,
|
||||
nativeEvent.target,
|
||||
);
|
||||
});
|
||||
// React Native doesn't use ReactControlledComponent but if it did, here's
|
||||
// where it would do it.
|
||||
}
|
||||
391
packages/react-native-renderer/src/ReactFabricHostConfig.js
vendored
Normal file
391
packages/react-native-renderer/src/ReactFabricHostConfig.js
vendored
Normal file
@@ -0,0 +1,391 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
import type {
|
||||
MeasureInWindowOnSuccessCallback,
|
||||
MeasureLayoutOnSuccessCallback,
|
||||
MeasureOnSuccessCallback,
|
||||
NativeMethodsMixinType,
|
||||
ReactNativeBaseComponentViewConfig,
|
||||
} from './ReactNativeTypes';
|
||||
|
||||
import {mountSafeCallback, warnForStyleProps} from './NativeMethodsMixinUtils';
|
||||
import * as ReactNativeAttributePayload from './ReactNativeAttributePayload';
|
||||
import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling';
|
||||
import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
|
||||
|
||||
import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
import {dispatchEvent} from './ReactFabricEventEmitter';
|
||||
|
||||
// Modules provided by RN:
|
||||
import TextInputState from 'TextInputState';
|
||||
import {
|
||||
createNode,
|
||||
cloneNode,
|
||||
cloneNodeWithNewChildren,
|
||||
cloneNodeWithNewChildrenAndProps,
|
||||
cloneNodeWithNewProps,
|
||||
createChildSet as createChildNodeSet,
|
||||
appendChild as appendChildNode,
|
||||
appendChildToSet as appendChildNodeToSet,
|
||||
completeRoot,
|
||||
registerEventHandler,
|
||||
} from 'FabricUIManager';
|
||||
import UIManager from 'UIManager';
|
||||
|
||||
// Counter for uniquely identifying views.
|
||||
// % 10 === 1 means it is a rootTag.
|
||||
// % 2 === 0 means it is a Fabric tag.
|
||||
// This means that they never overlap.
|
||||
let nextReactTag = 2;
|
||||
|
||||
type Node = Object;
|
||||
export type Type = string;
|
||||
export type Props = Object;
|
||||
export type Instance = {
|
||||
node: Node,
|
||||
canonical: ReactFabricHostComponent,
|
||||
};
|
||||
export type TextInstance = {
|
||||
node: Node,
|
||||
};
|
||||
export type HydratableInstance = Instance | TextInstance;
|
||||
export type PublicInstance = ReactFabricHostComponent;
|
||||
export type Container = number;
|
||||
export type ChildSet = Object;
|
||||
export type HostContext = $ReadOnly<{|
|
||||
isInAParentText: boolean,
|
||||
|}>;
|
||||
export type UpdatePayload = Object;
|
||||
|
||||
// TODO: Remove this conditional once all changes have propagated.
|
||||
if (registerEventHandler) {
|
||||
/**
|
||||
* Register the event emitter with the native bridge
|
||||
*/
|
||||
registerEventHandler(dispatchEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is used for refs on host components.
|
||||
*/
|
||||
class ReactFabricHostComponent {
|
||||
_nativeTag: number;
|
||||
viewConfig: ReactNativeBaseComponentViewConfig;
|
||||
currentProps: Props;
|
||||
|
||||
constructor(
|
||||
tag: number,
|
||||
viewConfig: ReactNativeBaseComponentViewConfig,
|
||||
props: Props,
|
||||
) {
|
||||
this._nativeTag = tag;
|
||||
this.viewConfig = viewConfig;
|
||||
this.currentProps = props;
|
||||
}
|
||||
|
||||
blur() {
|
||||
TextInputState.blurTextInput(this._nativeTag);
|
||||
}
|
||||
|
||||
focus() {
|
||||
TextInputState.focusTextInput(this._nativeTag);
|
||||
}
|
||||
|
||||
measure(callback: MeasureOnSuccessCallback) {
|
||||
UIManager.measure(this._nativeTag, mountSafeCallback(this, callback));
|
||||
}
|
||||
|
||||
measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
|
||||
UIManager.measureInWindow(
|
||||
this._nativeTag,
|
||||
mountSafeCallback(this, callback),
|
||||
);
|
||||
}
|
||||
|
||||
measureLayout(
|
||||
relativeToNativeNode: number,
|
||||
onSuccess: MeasureLayoutOnSuccessCallback,
|
||||
onFail: () => void /* currently unused */,
|
||||
) {
|
||||
UIManager.measureLayout(
|
||||
this._nativeTag,
|
||||
relativeToNativeNode,
|
||||
mountSafeCallback(this, onFail),
|
||||
mountSafeCallback(this, onSuccess),
|
||||
);
|
||||
}
|
||||
|
||||
setNativeProps(nativeProps: Object) {
|
||||
if (__DEV__) {
|
||||
warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
|
||||
}
|
||||
|
||||
const updatePayload = ReactNativeAttributePayload.create(
|
||||
nativeProps,
|
||||
this.viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
// Avoid the overhead of bridge calls if there's no update.
|
||||
// This is an expensive no-op for Android, and causes an unnecessary
|
||||
// view invalidation for certain components (eg RCTTextInput) on iOS.
|
||||
if (updatePayload != null) {
|
||||
UIManager.updateView(
|
||||
this._nativeTag,
|
||||
this.viewConfig.uiViewClassName,
|
||||
updatePayload,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
(ReactFabricHostComponent.prototype: NativeMethodsMixinType);
|
||||
|
||||
export * from 'shared/HostConfigWithNoMutation';
|
||||
export * from 'shared/HostConfigWithNoHydration';
|
||||
|
||||
export function appendInitialChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
appendChildNode(parentInstance.node, child.node);
|
||||
}
|
||||
|
||||
export function createInstance(
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): Instance {
|
||||
const tag = nextReactTag;
|
||||
nextReactTag += 2;
|
||||
|
||||
const viewConfig = ReactNativeViewConfigRegistry.get(type);
|
||||
|
||||
if (__DEV__) {
|
||||
for (const key in viewConfig.validAttributes) {
|
||||
if (props.hasOwnProperty(key)) {
|
||||
deepFreezeAndThrowOnMutationInDev(props[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invariant(
|
||||
type !== 'RCTView' || !hostContext.isInAParentText,
|
||||
'Nesting of <View> within <Text> is not currently supported.',
|
||||
);
|
||||
|
||||
const updatePayload = ReactNativeAttributePayload.create(
|
||||
props,
|
||||
viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
const node = createNode(
|
||||
tag, // reactTag
|
||||
viewConfig.uiViewClassName, // viewName
|
||||
rootContainerInstance, // rootTag
|
||||
updatePayload, // props
|
||||
internalInstanceHandle, // internalInstanceHandle
|
||||
);
|
||||
|
||||
const component = new ReactFabricHostComponent(tag, viewConfig, props);
|
||||
|
||||
return {
|
||||
node: node,
|
||||
canonical: component,
|
||||
};
|
||||
}
|
||||
|
||||
export function createTextInstance(
|
||||
text: string,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): TextInstance {
|
||||
invariant(
|
||||
hostContext.isInAParentText,
|
||||
'Text strings must be rendered within a <Text> component.',
|
||||
);
|
||||
|
||||
const tag = nextReactTag;
|
||||
nextReactTag += 2;
|
||||
|
||||
const node = createNode(
|
||||
tag, // reactTag
|
||||
'RCTRawText', // viewName
|
||||
rootContainerInstance, // rootTag
|
||||
{text: text}, // props
|
||||
internalInstanceHandle, // instance handle
|
||||
);
|
||||
|
||||
return {
|
||||
node: node,
|
||||
};
|
||||
}
|
||||
|
||||
export function finalizeInitialChildren(
|
||||
parentInstance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getRootHostContext(
|
||||
rootContainerInstance: Container,
|
||||
): HostContext {
|
||||
return {isInAParentText: false};
|
||||
}
|
||||
|
||||
export function getChildHostContext(
|
||||
parentHostContext: HostContext,
|
||||
type: string,
|
||||
rootContainerInstance: Container,
|
||||
): HostContext {
|
||||
const prevIsInAParentText = parentHostContext.isInAParentText;
|
||||
const isInAParentText =
|
||||
type === 'AndroidTextInput' || // Android
|
||||
type === 'RCTMultilineTextInputView' || // iOS
|
||||
type === 'RCTSinglelineTextInputView' || // iOS
|
||||
type === 'RCTText' ||
|
||||
type === 'RCTVirtualText';
|
||||
|
||||
if (prevIsInAParentText !== isInAParentText) {
|
||||
return {isInAParentText};
|
||||
} else {
|
||||
return parentHostContext;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPublicInstance(instance: Instance): * {
|
||||
return instance.canonical;
|
||||
}
|
||||
|
||||
export function prepareForCommit(containerInfo: Container): void {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function prepareUpdate(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
): null | Object {
|
||||
const viewConfig = instance.canonical.viewConfig;
|
||||
const updatePayload = ReactNativeAttributePayload.diff(
|
||||
oldProps,
|
||||
newProps,
|
||||
viewConfig.validAttributes,
|
||||
);
|
||||
// TODO: If the event handlers have changed, we need to update the current props
|
||||
// in the commit phase but there is no host config hook to do it yet.
|
||||
return updatePayload;
|
||||
}
|
||||
|
||||
export function resetAfterCommit(containerInfo: Container): void {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function shouldSetTextContent(type: string, props: Props): boolean {
|
||||
// TODO (bvaughn) Revisit this decision.
|
||||
// Always returning false simplifies the createInstance() implementation,
|
||||
// But creates an additional child Fiber for raw text children.
|
||||
// No additional native views are created though.
|
||||
// It's not clear to me which is better so I'm deferring for now.
|
||||
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
|
||||
return false;
|
||||
}
|
||||
|
||||
// The Fabric renderer is secondary to the existing React Native renderer.
|
||||
export const isPrimaryRenderer = false;
|
||||
export const now = ReactNativeFrameScheduling.now;
|
||||
export const scheduleDeferredCallback =
|
||||
ReactNativeFrameScheduling.scheduleDeferredCallback;
|
||||
export const cancelDeferredCallback =
|
||||
ReactNativeFrameScheduling.cancelDeferredCallback;
|
||||
|
||||
// -------------------
|
||||
// Persistence
|
||||
// -------------------
|
||||
|
||||
export const supportsPersistence = true;
|
||||
|
||||
export function cloneInstance(
|
||||
instance: Instance,
|
||||
updatePayload: null | Object,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
keepChildren: boolean,
|
||||
recyclableInstance: null | Instance,
|
||||
): Instance {
|
||||
const node = instance.node;
|
||||
let clone;
|
||||
if (keepChildren) {
|
||||
if (updatePayload !== null) {
|
||||
clone = cloneNodeWithNewProps(
|
||||
node,
|
||||
updatePayload,
|
||||
internalInstanceHandle,
|
||||
);
|
||||
} else {
|
||||
clone = cloneNode(node, internalInstanceHandle);
|
||||
}
|
||||
} else {
|
||||
if (updatePayload !== null) {
|
||||
clone = cloneNodeWithNewChildrenAndProps(
|
||||
node,
|
||||
updatePayload,
|
||||
internalInstanceHandle,
|
||||
);
|
||||
} else {
|
||||
clone = cloneNodeWithNewChildren(node, internalInstanceHandle);
|
||||
}
|
||||
}
|
||||
return {
|
||||
node: clone,
|
||||
canonical: instance.canonical,
|
||||
};
|
||||
}
|
||||
|
||||
export function createContainerChildSet(container: Container): ChildSet {
|
||||
return createChildNodeSet(container);
|
||||
}
|
||||
|
||||
export function appendChildToContainerChildSet(
|
||||
childSet: ChildSet,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
appendChildNodeToSet(childSet, child.node);
|
||||
}
|
||||
|
||||
export function finalizeContainerChildren(
|
||||
container: Container,
|
||||
newChildren: ChildSet,
|
||||
): void {
|
||||
completeRoot(container, newChildren);
|
||||
}
|
||||
|
||||
export function replaceContainerChildren(
|
||||
container: Container,
|
||||
newChildren: ChildSet,
|
||||
): void {}
|
||||
@@ -1,324 +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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {
|
||||
MeasureInWindowOnSuccessCallback,
|
||||
MeasureLayoutOnSuccessCallback,
|
||||
MeasureOnSuccessCallback,
|
||||
NativeMethodsMixinType,
|
||||
ReactNativeBaseComponentViewConfig,
|
||||
} from './ReactNativeTypes';
|
||||
|
||||
import {mountSafeCallback, warnForStyleProps} from './NativeMethodsMixinUtils';
|
||||
import * as ReactNativeAttributePayload from './ReactNativeAttributePayload';
|
||||
import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling';
|
||||
import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
|
||||
import ReactFiberReconciler from 'react-reconciler';
|
||||
|
||||
import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
|
||||
import emptyObject from 'fbjs/lib/emptyObject';
|
||||
|
||||
// Modules provided by RN:
|
||||
import TextInputState from 'TextInputState';
|
||||
import FabricUIManager from 'FabricUIManager';
|
||||
import UIManager from 'UIManager';
|
||||
|
||||
// Counter for uniquely identifying views.
|
||||
// % 10 === 1 means it is a rootTag.
|
||||
// % 2 === 0 means it is a Fabric tag.
|
||||
// This means that they never overlap.
|
||||
let nextReactTag = 2;
|
||||
|
||||
/**
|
||||
* This is used for refs on host components.
|
||||
*/
|
||||
class ReactFabricHostComponent {
|
||||
_nativeTag: number;
|
||||
viewConfig: ReactNativeBaseComponentViewConfig;
|
||||
currentProps: Props;
|
||||
|
||||
constructor(
|
||||
tag: number,
|
||||
viewConfig: ReactNativeBaseComponentViewConfig,
|
||||
props: Props,
|
||||
) {
|
||||
this._nativeTag = tag;
|
||||
this.viewConfig = viewConfig;
|
||||
this.currentProps = props;
|
||||
}
|
||||
|
||||
blur() {
|
||||
TextInputState.blurTextInput(this._nativeTag);
|
||||
}
|
||||
|
||||
focus() {
|
||||
TextInputState.focusTextInput(this._nativeTag);
|
||||
}
|
||||
|
||||
measure(callback: MeasureOnSuccessCallback) {
|
||||
UIManager.measure(this._nativeTag, mountSafeCallback(this, callback));
|
||||
}
|
||||
|
||||
measureInWindow(callback: MeasureInWindowOnSuccessCallback) {
|
||||
UIManager.measureInWindow(
|
||||
this._nativeTag,
|
||||
mountSafeCallback(this, callback),
|
||||
);
|
||||
}
|
||||
|
||||
measureLayout(
|
||||
relativeToNativeNode: number,
|
||||
onSuccess: MeasureLayoutOnSuccessCallback,
|
||||
onFail: () => void /* currently unused */,
|
||||
) {
|
||||
UIManager.measureLayout(
|
||||
this._nativeTag,
|
||||
relativeToNativeNode,
|
||||
mountSafeCallback(this, onFail),
|
||||
mountSafeCallback(this, onSuccess),
|
||||
);
|
||||
}
|
||||
|
||||
setNativeProps(nativeProps: Object) {
|
||||
if (__DEV__) {
|
||||
warnForStyleProps(nativeProps, this.viewConfig.validAttributes);
|
||||
}
|
||||
|
||||
const updatePayload = ReactNativeAttributePayload.create(
|
||||
nativeProps,
|
||||
this.viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
// Avoid the overhead of bridge calls if there's no update.
|
||||
// This is an expensive no-op for Android, and causes an unnecessary
|
||||
// view invalidation for certain components (eg RCTTextInput) on iOS.
|
||||
if (updatePayload != null) {
|
||||
UIManager.updateView(
|
||||
this._nativeTag,
|
||||
this.viewConfig.uiViewClassName,
|
||||
updatePayload,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-expressions
|
||||
(ReactFabricHostComponent.prototype: NativeMethodsMixinType);
|
||||
|
||||
type Node = Object;
|
||||
type ChildSet = Object;
|
||||
type Container = number;
|
||||
type Instance = {
|
||||
node: Node,
|
||||
canonical: ReactFabricHostComponent,
|
||||
};
|
||||
type Props = Object;
|
||||
type TextInstance = {
|
||||
node: Node,
|
||||
};
|
||||
|
||||
const ReactFabricRenderer = ReactFiberReconciler({
|
||||
appendInitialChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
FabricUIManager.appendChild(parentInstance.node, child.node);
|
||||
},
|
||||
|
||||
createInstance(
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: {},
|
||||
internalInstanceHandle: Object,
|
||||
): Instance {
|
||||
const tag = nextReactTag;
|
||||
nextReactTag += 2;
|
||||
|
||||
const viewConfig = ReactNativeViewConfigRegistry.get(type);
|
||||
|
||||
if (__DEV__) {
|
||||
for (const key in viewConfig.validAttributes) {
|
||||
if (props.hasOwnProperty(key)) {
|
||||
deepFreezeAndThrowOnMutationInDev(props[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updatePayload = ReactNativeAttributePayload.create(
|
||||
props,
|
||||
viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
const node = FabricUIManager.createNode(
|
||||
tag, // reactTag
|
||||
viewConfig.uiViewClassName, // viewName
|
||||
rootContainerInstance, // rootTag
|
||||
updatePayload, // props
|
||||
internalInstanceHandle, // internalInstanceHandle
|
||||
);
|
||||
|
||||
const component = new ReactFabricHostComponent(tag, viewConfig, props);
|
||||
|
||||
return {
|
||||
node: node,
|
||||
canonical: component,
|
||||
};
|
||||
},
|
||||
|
||||
createTextInstance(
|
||||
text: string,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: {},
|
||||
internalInstanceHandle: Object,
|
||||
): TextInstance {
|
||||
const tag = nextReactTag;
|
||||
nextReactTag += 2;
|
||||
|
||||
const node = FabricUIManager.createNode(
|
||||
tag, // reactTag
|
||||
'RCTRawText', // viewName
|
||||
rootContainerInstance, // rootTag
|
||||
{text: text}, // props
|
||||
internalInstanceHandle, // instance handle
|
||||
);
|
||||
|
||||
return {
|
||||
node: node,
|
||||
};
|
||||
},
|
||||
|
||||
finalizeInitialChildren(
|
||||
parentInstance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
): boolean {
|
||||
return false;
|
||||
},
|
||||
|
||||
getRootHostContext(): {} {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getChildHostContext(): {} {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getPublicInstance(instance) {
|
||||
return instance.canonical;
|
||||
},
|
||||
|
||||
now: ReactNativeFrameScheduling.now,
|
||||
|
||||
prepareForCommit(): void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
prepareUpdate(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: {},
|
||||
): null | Object {
|
||||
const viewConfig = instance.canonical.viewConfig;
|
||||
const updatePayload = ReactNativeAttributePayload.diff(
|
||||
oldProps,
|
||||
newProps,
|
||||
viewConfig.validAttributes,
|
||||
);
|
||||
// TODO: If the event handlers have changed, we need to update the current props
|
||||
// in the commit phase but there is no host config hook to do it yet.
|
||||
return updatePayload;
|
||||
},
|
||||
|
||||
resetAfterCommit(): void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
scheduleDeferredCallback: ReactNativeFrameScheduling.scheduleDeferredCallback,
|
||||
cancelDeferredCallback: ReactNativeFrameScheduling.cancelDeferredCallback,
|
||||
|
||||
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
||||
return false;
|
||||
},
|
||||
|
||||
shouldSetTextContent(type: string, props: Props): boolean {
|
||||
// TODO (bvaughn) Revisit this decision.
|
||||
// Always returning false simplifies the createInstance() implementation,
|
||||
// But creates an additional child Fiber for raw text children.
|
||||
// No additional native views are created though.
|
||||
// It's not clear to me which is better so I'm deferring for now.
|
||||
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
|
||||
return false;
|
||||
},
|
||||
|
||||
persistence: {
|
||||
cloneInstance(
|
||||
instance: Instance,
|
||||
updatePayload: null | Object,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
keepChildren: boolean,
|
||||
recyclableInstance: null | Instance,
|
||||
): Instance {
|
||||
const node = instance.node;
|
||||
let clone;
|
||||
if (keepChildren) {
|
||||
if (updatePayload !== null) {
|
||||
clone = FabricUIManager.cloneNodeWithNewProps(node, updatePayload);
|
||||
} else {
|
||||
clone = FabricUIManager.cloneNode(node);
|
||||
}
|
||||
} else {
|
||||
if (updatePayload !== null) {
|
||||
clone = FabricUIManager.cloneNodeWithNewChildrenAndProps(
|
||||
node,
|
||||
updatePayload,
|
||||
);
|
||||
} else {
|
||||
clone = FabricUIManager.cloneNodeWithNewChildren(node);
|
||||
}
|
||||
}
|
||||
return {
|
||||
node: clone,
|
||||
canonical: instance.canonical,
|
||||
};
|
||||
},
|
||||
|
||||
createContainerChildSet(container: Container): ChildSet {
|
||||
return FabricUIManager.createChildSet(container);
|
||||
},
|
||||
|
||||
appendChildToContainerChildSet(
|
||||
childSet: ChildSet,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
FabricUIManager.appendChildToSet(childSet, child.node);
|
||||
},
|
||||
|
||||
finalizeContainerChildren(
|
||||
container: Container,
|
||||
newChildren: ChildSet,
|
||||
): void {
|
||||
FabricUIManager.completeRoot(container, newChildren);
|
||||
},
|
||||
|
||||
replaceContainerChildren(
|
||||
container: Container,
|
||||
newChildren: ChildSet,
|
||||
): void {},
|
||||
},
|
||||
});
|
||||
|
||||
export default ReactFabricRenderer;
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
accumulateTwoPhaseDispatches,
|
||||
accumulateDirectDispatches,
|
||||
} from 'events/EventPropagators';
|
||||
import type {TopLevelType} from 'events/TopLevelEventTypes';
|
||||
import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
|
||||
import SyntheticEvent from 'events/SyntheticEvent';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
@@ -29,7 +30,7 @@ const ReactNativeBridgeEventPlugin = {
|
||||
* @see {EventPluginHub.extractEvents}
|
||||
*/
|
||||
extractEvents: function(
|
||||
topLevelType: string,
|
||||
topLevelType: TopLevelType,
|
||||
targetInst: Object,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
nativeEventTarget: Object,
|
||||
|
||||
@@ -15,6 +15,7 @@ import warning from 'fbjs/lib/warning';
|
||||
import {getInstanceFromNode} from './ReactNativeComponentTree';
|
||||
|
||||
import type {AnyNativeEvent} from 'events/PluginModuleType';
|
||||
import type {TopLevelType} from 'events/TopLevelEventTypes';
|
||||
|
||||
export {getListener, registrationNameModules as registrationNames};
|
||||
|
||||
@@ -88,7 +89,7 @@ const removeTouchesAtIndices = function(
|
||||
*/
|
||||
export function _receiveRootNodeIDEvent(
|
||||
rootNodeID: number,
|
||||
topLevelType: string,
|
||||
topLevelType: TopLevelType,
|
||||
nativeEventParam: ?AnyNativeEvent,
|
||||
) {
|
||||
const nativeEvent = nativeEventParam || EMPTY_NATIVE_EVENT;
|
||||
@@ -114,7 +115,7 @@ export function _receiveRootNodeIDEvent(
|
||||
*/
|
||||
export function receiveEvent(
|
||||
rootNodeID: number,
|
||||
topLevelType: string,
|
||||
topLevelType: TopLevelType,
|
||||
nativeEventParam: AnyNativeEvent,
|
||||
) {
|
||||
_receiveRootNodeIDEvent(rootNodeID, topLevelType, nativeEventParam);
|
||||
@@ -145,7 +146,7 @@ export function receiveEvent(
|
||||
* identifier 0, also abandoning traditional click handlers.
|
||||
*/
|
||||
export function receiveTouches(
|
||||
eventTopLevelType: string,
|
||||
eventTopLevelType: TopLevelType,
|
||||
touches: Array<Object>,
|
||||
changedIndices: Array<number>,
|
||||
) {
|
||||
|
||||
@@ -14,7 +14,7 @@ import type {
|
||||
NativeMethodsMixinType,
|
||||
ReactNativeBaseComponentViewConfig,
|
||||
} from './ReactNativeTypes';
|
||||
import type {Instance} from './ReactNativeFiberRenderer';
|
||||
import type {Instance} from './ReactNativeHostConfig';
|
||||
|
||||
// Modules provided by RN:
|
||||
import TextInputState from 'TextInputState';
|
||||
|
||||
@@ -1,400 +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.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ReactNativeBaseComponentViewConfig} from './ReactNativeTypes';
|
||||
|
||||
import ReactFiberReconciler from 'react-reconciler';
|
||||
import emptyObject from 'fbjs/lib/emptyObject';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
// Modules provided by RN:
|
||||
import UIManager from 'UIManager';
|
||||
import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
|
||||
|
||||
import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
|
||||
import * as ReactNativeAttributePayload from './ReactNativeAttributePayload';
|
||||
import {
|
||||
precacheFiberNode,
|
||||
uncacheFiberNode,
|
||||
updateFiberProps,
|
||||
} from './ReactNativeComponentTree';
|
||||
import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
|
||||
import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling';
|
||||
|
||||
type Container = number;
|
||||
export type Instance = {
|
||||
_children: Array<Instance | number>,
|
||||
_nativeTag: number,
|
||||
viewConfig: ReactNativeBaseComponentViewConfig,
|
||||
};
|
||||
type Props = Object;
|
||||
type TextInstance = number;
|
||||
|
||||
// Counter for uniquely identifying views.
|
||||
// % 10 === 1 means it is a rootTag.
|
||||
// % 2 === 0 means it is a Fabric tag.
|
||||
let nextReactTag = 3;
|
||||
function allocateTag() {
|
||||
let tag = nextReactTag;
|
||||
if (tag % 10 === 1) {
|
||||
tag += 2;
|
||||
}
|
||||
nextReactTag = tag + 2;
|
||||
return tag;
|
||||
}
|
||||
|
||||
function recursivelyUncacheFiberNode(node: Instance | TextInstance) {
|
||||
if (typeof node === 'number') {
|
||||
// Leaf node (eg text)
|
||||
uncacheFiberNode(node);
|
||||
} else {
|
||||
uncacheFiberNode((node: any)._nativeTag);
|
||||
|
||||
(node: any)._children.forEach(recursivelyUncacheFiberNode);
|
||||
}
|
||||
}
|
||||
|
||||
const NativeRenderer = ReactFiberReconciler({
|
||||
appendInitialChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance._children.push(child);
|
||||
},
|
||||
|
||||
createInstance(
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: {},
|
||||
internalInstanceHandle: Object,
|
||||
): Instance {
|
||||
const tag = allocateTag();
|
||||
const viewConfig = ReactNativeViewConfigRegistry.get(type);
|
||||
|
||||
if (__DEV__) {
|
||||
for (const key in viewConfig.validAttributes) {
|
||||
if (props.hasOwnProperty(key)) {
|
||||
deepFreezeAndThrowOnMutationInDev(props[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const updatePayload = ReactNativeAttributePayload.create(
|
||||
props,
|
||||
viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
UIManager.createView(
|
||||
tag, // reactTag
|
||||
viewConfig.uiViewClassName, // viewName
|
||||
rootContainerInstance, // rootTag
|
||||
updatePayload, // props
|
||||
);
|
||||
|
||||
const component = new ReactNativeFiberHostComponent(tag, viewConfig);
|
||||
|
||||
precacheFiberNode(internalInstanceHandle, tag);
|
||||
updateFiberProps(tag, props);
|
||||
|
||||
// Not sure how to avoid this cast. Flow is okay if the component is defined
|
||||
// in the same file but if it's external it can't see the types.
|
||||
return ((component: any): Instance);
|
||||
},
|
||||
|
||||
createTextInstance(
|
||||
text: string,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: {},
|
||||
internalInstanceHandle: Object,
|
||||
): TextInstance {
|
||||
const tag = allocateTag();
|
||||
|
||||
UIManager.createView(
|
||||
tag, // reactTag
|
||||
'RCTRawText', // viewName
|
||||
rootContainerInstance, // rootTag
|
||||
{text: text}, // props
|
||||
);
|
||||
|
||||
precacheFiberNode(internalInstanceHandle, tag);
|
||||
|
||||
return tag;
|
||||
},
|
||||
|
||||
finalizeInitialChildren(
|
||||
parentInstance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
): boolean {
|
||||
// Don't send a no-op message over the bridge.
|
||||
if (parentInstance._children.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Map from child objects to native tags.
|
||||
// Either way we need to pass a copy of the Array to prevent it from being frozen.
|
||||
const nativeTags = parentInstance._children.map(
|
||||
child =>
|
||||
typeof child === 'number'
|
||||
? child // Leaf node (eg text)
|
||||
: child._nativeTag,
|
||||
);
|
||||
|
||||
UIManager.setChildren(
|
||||
parentInstance._nativeTag, // containerTag
|
||||
nativeTags, // reactTags
|
||||
);
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
getRootHostContext(): {} {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getChildHostContext(): {} {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
getPublicInstance(instance) {
|
||||
return instance;
|
||||
},
|
||||
|
||||
now: ReactNativeFrameScheduling.now,
|
||||
|
||||
prepareForCommit(): void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
prepareUpdate(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: {},
|
||||
): null | Object {
|
||||
return emptyObject;
|
||||
},
|
||||
|
||||
resetAfterCommit(): void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
scheduleDeferredCallback: ReactNativeFrameScheduling.scheduleDeferredCallback,
|
||||
cancelDeferredCallback: ReactNativeFrameScheduling.cancelDeferredCallback,
|
||||
|
||||
shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
||||
return false;
|
||||
},
|
||||
|
||||
shouldSetTextContent(type: string, props: Props): boolean {
|
||||
// TODO (bvaughn) Revisit this decision.
|
||||
// Always returning false simplifies the createInstance() implementation,
|
||||
// But creates an additional child Fiber for raw text children.
|
||||
// No additional native views are created though.
|
||||
// It's not clear to me which is better so I'm deferring for now.
|
||||
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
|
||||
return false;
|
||||
},
|
||||
|
||||
mutation: {
|
||||
appendChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
const childTag = typeof child === 'number' ? child : child._nativeTag;
|
||||
const children = parentInstance._children;
|
||||
const index = children.indexOf(child);
|
||||
|
||||
if (index >= 0) {
|
||||
children.splice(index, 1);
|
||||
children.push(child);
|
||||
|
||||
UIManager.manageChildren(
|
||||
parentInstance._nativeTag, // containerTag
|
||||
[index], // moveFromIndices
|
||||
[children.length - 1], // moveToIndices
|
||||
[], // addChildReactTags
|
||||
[], // addAtIndices
|
||||
[], // removeAtIndices
|
||||
);
|
||||
} else {
|
||||
children.push(child);
|
||||
|
||||
UIManager.manageChildren(
|
||||
parentInstance._nativeTag, // containerTag
|
||||
[], // moveFromIndices
|
||||
[], // moveToIndices
|
||||
[childTag], // addChildReactTags
|
||||
[children.length - 1], // addAtIndices
|
||||
[], // removeAtIndices
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
appendChildToContainer(
|
||||
parentInstance: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
const childTag = typeof child === 'number' ? child : child._nativeTag;
|
||||
UIManager.setChildren(
|
||||
parentInstance, // containerTag
|
||||
[childTag], // reactTags
|
||||
);
|
||||
},
|
||||
|
||||
commitTextUpdate(
|
||||
textInstance: TextInstance,
|
||||
oldText: string,
|
||||
newText: string,
|
||||
): void {
|
||||
UIManager.updateView(
|
||||
textInstance, // reactTag
|
||||
'RCTRawText', // viewName
|
||||
{text: newText}, // props
|
||||
);
|
||||
},
|
||||
|
||||
commitMount(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
): void {
|
||||
// Noop
|
||||
},
|
||||
|
||||
commitUpdate(
|
||||
instance: Instance,
|
||||
updatePayloadTODO: Object,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
): void {
|
||||
const viewConfig = instance.viewConfig;
|
||||
|
||||
updateFiberProps(instance._nativeTag, newProps);
|
||||
|
||||
const updatePayload = ReactNativeAttributePayload.diff(
|
||||
oldProps,
|
||||
newProps,
|
||||
viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
// Avoid the overhead of bridge calls if there's no update.
|
||||
// This is an expensive no-op for Android, and causes an unnecessary
|
||||
// view invalidation for certain components (eg RCTTextInput) on iOS.
|
||||
if (updatePayload != null) {
|
||||
UIManager.updateView(
|
||||
instance._nativeTag, // reactTag
|
||||
viewConfig.uiViewClassName, // viewName
|
||||
updatePayload, // props
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
insertBefore(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
const children = (parentInstance: any)._children;
|
||||
const index = children.indexOf(child);
|
||||
|
||||
// Move existing child or add new child?
|
||||
if (index >= 0) {
|
||||
children.splice(index, 1);
|
||||
const beforeChildIndex = children.indexOf(beforeChild);
|
||||
children.splice(beforeChildIndex, 0, child);
|
||||
|
||||
UIManager.manageChildren(
|
||||
(parentInstance: any)._nativeTag, // containerID
|
||||
[index], // moveFromIndices
|
||||
[beforeChildIndex], // moveToIndices
|
||||
[], // addChildReactTags
|
||||
[], // addAtIndices
|
||||
[], // removeAtIndices
|
||||
);
|
||||
} else {
|
||||
const beforeChildIndex = children.indexOf(beforeChild);
|
||||
children.splice(beforeChildIndex, 0, child);
|
||||
|
||||
const childTag = typeof child === 'number' ? child : child._nativeTag;
|
||||
|
||||
UIManager.manageChildren(
|
||||
(parentInstance: any)._nativeTag, // containerID
|
||||
[], // moveFromIndices
|
||||
[], // moveToIndices
|
||||
[childTag], // addChildReactTags
|
||||
[beforeChildIndex], // addAtIndices
|
||||
[], // removeAtIndices
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
insertInContainerBefore(
|
||||
parentInstance: Container,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
// TODO (bvaughn): Remove this check when...
|
||||
// We create a wrapper object for the container in ReactNative render()
|
||||
// Or we refactor to remove wrapper objects entirely.
|
||||
// For more info on pros/cons see PR #8560 description.
|
||||
invariant(
|
||||
typeof parentInstance !== 'number',
|
||||
'Container does not support insertBefore operation',
|
||||
);
|
||||
},
|
||||
|
||||
removeChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
recursivelyUncacheFiberNode(child);
|
||||
const children = parentInstance._children;
|
||||
const index = children.indexOf(child);
|
||||
|
||||
children.splice(index, 1);
|
||||
|
||||
UIManager.manageChildren(
|
||||
parentInstance._nativeTag, // containerID
|
||||
[], // moveFromIndices
|
||||
[], // moveToIndices
|
||||
[], // addChildReactTags
|
||||
[], // addAtIndices
|
||||
[index], // removeAtIndices
|
||||
);
|
||||
},
|
||||
|
||||
removeChildFromContainer(
|
||||
parentInstance: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
recursivelyUncacheFiberNode(child);
|
||||
UIManager.manageChildren(
|
||||
parentInstance, // containerID
|
||||
[], // moveFromIndices
|
||||
[], // moveToIndices
|
||||
[], // addChildReactTags
|
||||
[], // addAtIndices
|
||||
[0], // removeAtIndices
|
||||
);
|
||||
},
|
||||
|
||||
resetTextContent(instance: Instance): void {
|
||||
// Noop
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export default NativeRenderer;
|
||||
@@ -7,7 +7,7 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {Deadline} from 'react-reconciler';
|
||||
import type {Deadline} from 'react-reconciler/src/ReactFiberScheduler';
|
||||
|
||||
const hasNativePerformanceNow =
|
||||
typeof performance === 'object' && typeof performance.now === 'function';
|
||||
@@ -43,15 +43,19 @@ function setTimeoutCallback() {
|
||||
// RN has a poor polyfill for requestIdleCallback so we aren't using it.
|
||||
// This implementation is only intended for short-term use anyway.
|
||||
// We also don't implement cancel functionality b'c Fiber doesn't currently need it.
|
||||
function scheduleDeferredCallback(callback: Callback): number {
|
||||
function scheduleDeferredCallback(
|
||||
callback: Callback,
|
||||
options?: {timeout: number},
|
||||
): number {
|
||||
// We assume only one callback is scheduled at a time b'c that's how Fiber works.
|
||||
scheduledCallback = callback;
|
||||
return setTimeout(setTimeoutCallback, 1);
|
||||
const timeoutId = setTimeout(setTimeoutCallback, 1);
|
||||
return (timeoutId: any); // Timeouts are always numbers on RN
|
||||
}
|
||||
|
||||
function cancelDeferredCallback(callbackID: number) {
|
||||
scheduledCallback = null;
|
||||
clearTimeout(callbackID);
|
||||
clearTimeout((callbackID: any)); // Timeouts are always numbers on RN
|
||||
}
|
||||
|
||||
export {now, scheduleDeferredCallback, cancelDeferredCallback};
|
||||
|
||||
442
packages/react-native-renderer/src/ReactNativeHostConfig.js
vendored
Normal file
442
packages/react-native-renderer/src/ReactNativeHostConfig.js
vendored
Normal file
@@ -0,0 +1,442 @@
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
|
||||
import type {ReactNativeBaseComponentViewConfig} from './ReactNativeTypes';
|
||||
|
||||
import emptyObject from 'fbjs/lib/emptyObject';
|
||||
import invariant from 'fbjs/lib/invariant';
|
||||
|
||||
// Modules provided by RN:
|
||||
import UIManager from 'UIManager';
|
||||
import deepFreezeAndThrowOnMutationInDev from 'deepFreezeAndThrowOnMutationInDev';
|
||||
|
||||
import * as ReactNativeViewConfigRegistry from 'ReactNativeViewConfigRegistry';
|
||||
import * as ReactNativeAttributePayload from './ReactNativeAttributePayload';
|
||||
import {
|
||||
precacheFiberNode,
|
||||
uncacheFiberNode,
|
||||
updateFiberProps,
|
||||
} from './ReactNativeComponentTree';
|
||||
import ReactNativeFiberHostComponent from './ReactNativeFiberHostComponent';
|
||||
import * as ReactNativeFrameScheduling from './ReactNativeFrameScheduling';
|
||||
|
||||
export type Type = string;
|
||||
export type Props = Object;
|
||||
export type Container = number;
|
||||
export type Instance = {
|
||||
_children: Array<Instance | number>,
|
||||
_nativeTag: number,
|
||||
viewConfig: ReactNativeBaseComponentViewConfig,
|
||||
};
|
||||
export type TextInstance = number;
|
||||
export type HydratableInstance = Instance | TextInstance;
|
||||
export type PublicInstance = Instance;
|
||||
export type HostContext = $ReadOnly<{|
|
||||
isInAParentText: boolean,
|
||||
|}>;
|
||||
export type UpdatePayload = Object; // Unused
|
||||
export type ChildSet = void; // Unused
|
||||
|
||||
// Counter for uniquely identifying views.
|
||||
// % 10 === 1 means it is a rootTag.
|
||||
// % 2 === 0 means it is a Fabric tag.
|
||||
let nextReactTag = 3;
|
||||
function allocateTag() {
|
||||
let tag = nextReactTag;
|
||||
if (tag % 10 === 1) {
|
||||
tag += 2;
|
||||
}
|
||||
nextReactTag = tag + 2;
|
||||
return tag;
|
||||
}
|
||||
|
||||
function recursivelyUncacheFiberNode(node: Instance | TextInstance) {
|
||||
if (typeof node === 'number') {
|
||||
// Leaf node (eg text)
|
||||
uncacheFiberNode(node);
|
||||
} else {
|
||||
uncacheFiberNode((node: any)._nativeTag);
|
||||
|
||||
(node: any)._children.forEach(recursivelyUncacheFiberNode);
|
||||
}
|
||||
}
|
||||
|
||||
export * from 'shared/HostConfigWithNoPersistence';
|
||||
export * from 'shared/HostConfigWithNoHydration';
|
||||
|
||||
export function appendInitialChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
parentInstance._children.push(child);
|
||||
}
|
||||
|
||||
export function createInstance(
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): Instance {
|
||||
const tag = allocateTag();
|
||||
const viewConfig = ReactNativeViewConfigRegistry.get(type);
|
||||
|
||||
if (__DEV__) {
|
||||
for (const key in viewConfig.validAttributes) {
|
||||
if (props.hasOwnProperty(key)) {
|
||||
deepFreezeAndThrowOnMutationInDev(props[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
invariant(
|
||||
type !== 'RCTView' || !hostContext.isInAParentText,
|
||||
'Nesting of <View> within <Text> is not currently supported.',
|
||||
);
|
||||
|
||||
const updatePayload = ReactNativeAttributePayload.create(
|
||||
props,
|
||||
viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
UIManager.createView(
|
||||
tag, // reactTag
|
||||
viewConfig.uiViewClassName, // viewName
|
||||
rootContainerInstance, // rootTag
|
||||
updatePayload, // props
|
||||
);
|
||||
|
||||
const component = new ReactNativeFiberHostComponent(tag, viewConfig);
|
||||
|
||||
precacheFiberNode(internalInstanceHandle, tag);
|
||||
updateFiberProps(tag, props);
|
||||
|
||||
// Not sure how to avoid this cast. Flow is okay if the component is defined
|
||||
// in the same file but if it's external it can't see the types.
|
||||
return ((component: any): Instance);
|
||||
}
|
||||
|
||||
export function createTextInstance(
|
||||
text: string,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
internalInstanceHandle: Object,
|
||||
): TextInstance {
|
||||
invariant(
|
||||
hostContext.isInAParentText,
|
||||
'Text strings must be rendered within a <Text> component.',
|
||||
);
|
||||
|
||||
const tag = allocateTag();
|
||||
|
||||
UIManager.createView(
|
||||
tag, // reactTag
|
||||
'RCTRawText', // viewName
|
||||
rootContainerInstance, // rootTag
|
||||
{text: text}, // props
|
||||
);
|
||||
|
||||
precacheFiberNode(internalInstanceHandle, tag);
|
||||
|
||||
return tag;
|
||||
}
|
||||
|
||||
export function finalizeInitialChildren(
|
||||
parentInstance: Instance,
|
||||
type: string,
|
||||
props: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
): boolean {
|
||||
// Don't send a no-op message over the bridge.
|
||||
if (parentInstance._children.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Map from child objects to native tags.
|
||||
// Either way we need to pass a copy of the Array to prevent it from being frozen.
|
||||
const nativeTags = parentInstance._children.map(
|
||||
child =>
|
||||
typeof child === 'number'
|
||||
? child // Leaf node (eg text)
|
||||
: child._nativeTag,
|
||||
);
|
||||
|
||||
UIManager.setChildren(
|
||||
parentInstance._nativeTag, // containerTag
|
||||
nativeTags, // reactTags
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function getRootHostContext(
|
||||
rootContainerInstance: Container,
|
||||
): HostContext {
|
||||
return {isInAParentText: false};
|
||||
}
|
||||
|
||||
export function getChildHostContext(
|
||||
parentHostContext: HostContext,
|
||||
type: string,
|
||||
rootContainerInstance: Container,
|
||||
): HostContext {
|
||||
const prevIsInAParentText = parentHostContext.isInAParentText;
|
||||
const isInAParentText =
|
||||
type === 'AndroidTextInput' || // Android
|
||||
type === 'RCTMultilineTextInputView' || // iOS
|
||||
type === 'RCTSinglelineTextInputView' || // iOS
|
||||
type === 'RCTText' ||
|
||||
type === 'RCTVirtualText';
|
||||
|
||||
if (prevIsInAParentText !== isInAParentText) {
|
||||
return {isInAParentText};
|
||||
} else {
|
||||
return parentHostContext;
|
||||
}
|
||||
}
|
||||
|
||||
export function getPublicInstance(instance: Instance): * {
|
||||
return instance;
|
||||
}
|
||||
|
||||
export function prepareForCommit(containerInfo: Container): void {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function prepareUpdate(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
rootContainerInstance: Container,
|
||||
hostContext: HostContext,
|
||||
): null | Object {
|
||||
return emptyObject;
|
||||
}
|
||||
|
||||
export function resetAfterCommit(containerInfo: Container): void {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export const now = ReactNativeFrameScheduling.now;
|
||||
export const isPrimaryRenderer = true;
|
||||
export const scheduleDeferredCallback =
|
||||
ReactNativeFrameScheduling.scheduleDeferredCallback;
|
||||
export const cancelDeferredCallback =
|
||||
ReactNativeFrameScheduling.cancelDeferredCallback;
|
||||
|
||||
export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function shouldSetTextContent(type: string, props: Props): boolean {
|
||||
// TODO (bvaughn) Revisit this decision.
|
||||
// Always returning false simplifies the createInstance() implementation,
|
||||
// But creates an additional child Fiber for raw text children.
|
||||
// No additional native views are created though.
|
||||
// It's not clear to me which is better so I'm deferring for now.
|
||||
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
|
||||
return false;
|
||||
}
|
||||
|
||||
// -------------------
|
||||
// Mutation
|
||||
// -------------------
|
||||
|
||||
export const supportsMutation = true;
|
||||
|
||||
export function appendChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
const childTag = typeof child === 'number' ? child : child._nativeTag;
|
||||
const children = parentInstance._children;
|
||||
const index = children.indexOf(child);
|
||||
|
||||
if (index >= 0) {
|
||||
children.splice(index, 1);
|
||||
children.push(child);
|
||||
|
||||
UIManager.manageChildren(
|
||||
parentInstance._nativeTag, // containerTag
|
||||
[index], // moveFromIndices
|
||||
[children.length - 1], // moveToIndices
|
||||
[], // addChildReactTags
|
||||
[], // addAtIndices
|
||||
[], // removeAtIndices
|
||||
);
|
||||
} else {
|
||||
children.push(child);
|
||||
|
||||
UIManager.manageChildren(
|
||||
parentInstance._nativeTag, // containerTag
|
||||
[], // moveFromIndices
|
||||
[], // moveToIndices
|
||||
[childTag], // addChildReactTags
|
||||
[children.length - 1], // addAtIndices
|
||||
[], // removeAtIndices
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function appendChildToContainer(
|
||||
parentInstance: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
const childTag = typeof child === 'number' ? child : child._nativeTag;
|
||||
UIManager.setChildren(
|
||||
parentInstance, // containerTag
|
||||
[childTag], // reactTags
|
||||
);
|
||||
}
|
||||
|
||||
export function commitTextUpdate(
|
||||
textInstance: TextInstance,
|
||||
oldText: string,
|
||||
newText: string,
|
||||
): void {
|
||||
UIManager.updateView(
|
||||
textInstance, // reactTag
|
||||
'RCTRawText', // viewName
|
||||
{text: newText}, // props
|
||||
);
|
||||
}
|
||||
|
||||
export function commitMount(
|
||||
instance: Instance,
|
||||
type: string,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
): void {
|
||||
// Noop
|
||||
}
|
||||
|
||||
export function commitUpdate(
|
||||
instance: Instance,
|
||||
updatePayloadTODO: Object,
|
||||
type: string,
|
||||
oldProps: Props,
|
||||
newProps: Props,
|
||||
internalInstanceHandle: Object,
|
||||
): void {
|
||||
const viewConfig = instance.viewConfig;
|
||||
|
||||
updateFiberProps(instance._nativeTag, newProps);
|
||||
|
||||
const updatePayload = ReactNativeAttributePayload.diff(
|
||||
oldProps,
|
||||
newProps,
|
||||
viewConfig.validAttributes,
|
||||
);
|
||||
|
||||
// Avoid the overhead of bridge calls if there's no update.
|
||||
// This is an expensive no-op for Android, and causes an unnecessary
|
||||
// view invalidation for certain components (eg RCTTextInput) on iOS.
|
||||
if (updatePayload != null) {
|
||||
UIManager.updateView(
|
||||
instance._nativeTag, // reactTag
|
||||
viewConfig.uiViewClassName, // viewName
|
||||
updatePayload, // props
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function insertBefore(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
const children = (parentInstance: any)._children;
|
||||
const index = children.indexOf(child);
|
||||
|
||||
// Move existing child or add new child?
|
||||
if (index >= 0) {
|
||||
children.splice(index, 1);
|
||||
const beforeChildIndex = children.indexOf(beforeChild);
|
||||
children.splice(beforeChildIndex, 0, child);
|
||||
|
||||
UIManager.manageChildren(
|
||||
(parentInstance: any)._nativeTag, // containerID
|
||||
[index], // moveFromIndices
|
||||
[beforeChildIndex], // moveToIndices
|
||||
[], // addChildReactTags
|
||||
[], // addAtIndices
|
||||
[], // removeAtIndices
|
||||
);
|
||||
} else {
|
||||
const beforeChildIndex = children.indexOf(beforeChild);
|
||||
children.splice(beforeChildIndex, 0, child);
|
||||
|
||||
const childTag = typeof child === 'number' ? child : child._nativeTag;
|
||||
|
||||
UIManager.manageChildren(
|
||||
(parentInstance: any)._nativeTag, // containerID
|
||||
[], // moveFromIndices
|
||||
[], // moveToIndices
|
||||
[childTag], // addChildReactTags
|
||||
[beforeChildIndex], // addAtIndices
|
||||
[], // removeAtIndices
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function insertInContainerBefore(
|
||||
parentInstance: Container,
|
||||
child: Instance | TextInstance,
|
||||
beforeChild: Instance | TextInstance,
|
||||
): void {
|
||||
// TODO (bvaughn): Remove this check when...
|
||||
// We create a wrapper object for the container in ReactNative render()
|
||||
// Or we refactor to remove wrapper objects entirely.
|
||||
// For more info on pros/cons see PR #8560 description.
|
||||
invariant(
|
||||
typeof parentInstance !== 'number',
|
||||
'Container does not support insertBefore operation',
|
||||
);
|
||||
}
|
||||
|
||||
export function removeChild(
|
||||
parentInstance: Instance,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
recursivelyUncacheFiberNode(child);
|
||||
const children = parentInstance._children;
|
||||
const index = children.indexOf(child);
|
||||
|
||||
children.splice(index, 1);
|
||||
|
||||
UIManager.manageChildren(
|
||||
parentInstance._nativeTag, // containerID
|
||||
[], // moveFromIndices
|
||||
[], // moveToIndices
|
||||
[], // addChildReactTags
|
||||
[], // addAtIndices
|
||||
[index], // removeAtIndices
|
||||
);
|
||||
}
|
||||
|
||||
export function removeChildFromContainer(
|
||||
parentInstance: Container,
|
||||
child: Instance | TextInstance,
|
||||
): void {
|
||||
recursivelyUncacheFiberNode(child);
|
||||
UIManager.manageChildren(
|
||||
parentInstance, // containerID
|
||||
[], // moveFromIndices
|
||||
[], // moveToIndices
|
||||
[], // addChildReactTags
|
||||
[], // addAtIndices
|
||||
[0], // removeAtIndices
|
||||
);
|
||||
}
|
||||
|
||||
export function resetTextContent(instance: Instance): void {
|
||||
// Noop
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import type {ReactNodeList} from 'shared/ReactTypes';
|
||||
|
||||
import './ReactNativeInjection';
|
||||
|
||||
import * as ReactNativeFiberRenderer from 'react-reconciler/inline.native';
|
||||
import * as ReactPortal from 'shared/ReactPortal';
|
||||
import * as ReactGenericBatching from 'events/ReactGenericBatching';
|
||||
import ReactVersion from 'shared/ReactVersion';
|
||||
@@ -23,7 +24,6 @@ import {getStackAddendumByWorkInProgressFiber} from 'shared/ReactFiberComponentT
|
||||
import NativeMethodsMixin from './NativeMethodsMixin';
|
||||
import ReactNativeComponent from './ReactNativeComponent';
|
||||
import * as ReactNativeComponentTree from './ReactNativeComponentTree';
|
||||
import ReactNativeFiberRenderer from './ReactNativeFiberRenderer';
|
||||
import {getInspectorDataForViewTag} from './ReactNativeFiberInspector';
|
||||
|
||||
import {ReactCurrentOwner} from 'shared/ReactGlobalSharedState';
|
||||
@@ -66,9 +66,9 @@ function findNodeHandle(componentOrHandle: any): ?number {
|
||||
if (hostInstance == null) {
|
||||
return hostInstance;
|
||||
}
|
||||
if (hostInstance.canonical) {
|
||||
if ((hostInstance: any).canonical) {
|
||||
// Fabric
|
||||
return hostInstance.canonical._nativeTag;
|
||||
return (hostInstance: any).canonical._nativeTag;
|
||||
}
|
||||
return hostInstance._nativeTag;
|
||||
}
|
||||
@@ -144,28 +144,6 @@ const ReactNativeRenderer: ReactNativeType = {
|
||||
},
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
// $FlowFixMe
|
||||
Object.assign(
|
||||
ReactNativeRenderer.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED,
|
||||
{
|
||||
// TODO: none of these work since Fiber. Remove these dependencies.
|
||||
// Used by RCTRenderingPerf, Systrace:
|
||||
ReactDebugTool: {
|
||||
addHook() {},
|
||||
removeHook() {},
|
||||
},
|
||||
// Used by ReactPerfStallHandler, RCTRenderingPerf:
|
||||
ReactPerf: {
|
||||
start() {},
|
||||
stop() {},
|
||||
printInclusive() {},
|
||||
printWasted() {},
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
ReactNativeFiberRenderer.injectIntoDevTools({
|
||||
findFiberByHostInstance: ReactNativeComponentTree.getClosestInstanceFromNode,
|
||||
getInspectorDataForViewTag: getInspectorDataForViewTag,
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @format
|
||||
* @flow
|
||||
* @providesModule ReactNativeTypes
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export type MeasureOnSuccessCallback = (
|
||||
x: number,
|
||||
y: number,
|
||||
@@ -52,6 +54,22 @@ export type ReactNativeBaseComponentViewConfig = {
|
||||
|
||||
export type ViewConfigGetter = () => ReactNativeBaseComponentViewConfig;
|
||||
|
||||
/**
|
||||
* Class only exists for its Flow type.
|
||||
*/
|
||||
class ReactNativeComponent<Props> extends React.Component<Props> {
|
||||
blur(): void {}
|
||||
focus(): void {}
|
||||
measure(callback: MeasureOnSuccessCallback): void {}
|
||||
measureInWindow(callback: MeasureInWindowOnSuccessCallback): void {}
|
||||
measureLayout(
|
||||
relativeToNativeNode: number,
|
||||
onSuccess: MeasureLayoutOnSuccessCallback,
|
||||
onFail?: () => void,
|
||||
): void {}
|
||||
setNativeProps(nativeProps: Object): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* This type keeps ReactNativeFiberHostComponent and NativeMethodsMixin in sync.
|
||||
* It can also provide types for ReactNative applications that use NMM or refs.
|
||||
@@ -72,16 +90,22 @@ export type NativeMethodsMixinType = {
|
||||
type SecretInternalsType = {
|
||||
NativeMethodsMixin: NativeMethodsMixinType,
|
||||
ReactNativeComponentTree: any,
|
||||
computeComponentStackForErrorReporting(tag: number): string,
|
||||
// TODO (bvaughn) Decide which additional types to expose here?
|
||||
// And how much information to fill in for the above types.
|
||||
};
|
||||
|
||||
type SecretInternalsFabricType = {
|
||||
NativeMethodsMixin: NativeMethodsMixinType,
|
||||
ReactNativeComponentTree: any,
|
||||
};
|
||||
|
||||
/**
|
||||
* Flat ReactNative renderer bundles are too big for Flow to parse efficiently.
|
||||
* Provide minimal Flow typing for the high-level RN API and call it a day.
|
||||
*/
|
||||
export type ReactNativeType = {
|
||||
NativeComponent: any,
|
||||
NativeComponent: typeof ReactNativeComponent,
|
||||
findNodeHandle(componentOrHandle: any): ?number,
|
||||
render(
|
||||
element: React$Element<any>,
|
||||
@@ -96,7 +120,7 @@ export type ReactNativeType = {
|
||||
};
|
||||
|
||||
export type ReactFabricType = {
|
||||
NativeComponent: any,
|
||||
NativeComponent: typeof ReactNativeComponent,
|
||||
findNodeHandle(componentOrHandle: any): ?number,
|
||||
render(
|
||||
element: React$Element<any>,
|
||||
@@ -105,5 +129,5 @@ export type ReactFabricType = {
|
||||
): any,
|
||||
unmountComponentAtNode(containerTag: number): any,
|
||||
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: SecretInternalsType,
|
||||
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: SecretInternalsFabricType,
|
||||
};
|
||||
|
||||
@@ -65,7 +65,7 @@ const RCTFabricUIManager = {
|
||||
children: [],
|
||||
};
|
||||
}),
|
||||
cloneNode: jest.fn(function cloneNode(node) {
|
||||
cloneNode: jest.fn(function cloneNode(node, instanceHandle) {
|
||||
return {
|
||||
reactTag: node.reactTag,
|
||||
viewName: node.viewName,
|
||||
@@ -73,7 +73,10 @@ const RCTFabricUIManager = {
|
||||
children: node.children,
|
||||
};
|
||||
}),
|
||||
cloneNodeWithNewChildren: jest.fn(function cloneNodeWithNewChildren(node) {
|
||||
cloneNodeWithNewChildren: jest.fn(function cloneNodeWithNewChildren(
|
||||
node,
|
||||
instanceHandle,
|
||||
) {
|
||||
return {
|
||||
reactTag: node.reactTag,
|
||||
viewName: node.viewName,
|
||||
@@ -84,6 +87,7 @@ const RCTFabricUIManager = {
|
||||
cloneNodeWithNewProps: jest.fn(function cloneNodeWithNewProps(
|
||||
node,
|
||||
newPropsDiff,
|
||||
instanceHandle,
|
||||
) {
|
||||
return {
|
||||
reactTag: node.reactTag,
|
||||
@@ -93,7 +97,11 @@ const RCTFabricUIManager = {
|
||||
};
|
||||
}),
|
||||
cloneNodeWithNewChildrenAndProps: jest.fn(
|
||||
function cloneNodeWithNewChildrenAndProps(node, newPropsDiff) {
|
||||
function cloneNodeWithNewChildrenAndProps(
|
||||
node,
|
||||
newPropsDiff,
|
||||
instanceHandle,
|
||||
) {
|
||||
return {
|
||||
reactTag: node.reactTag,
|
||||
viewName: node.viewName,
|
||||
@@ -117,6 +125,8 @@ const RCTFabricUIManager = {
|
||||
completeRoot: jest.fn(function completeRoot(rootTag, newChildSet) {
|
||||
roots.set(rootTag, newChildSet);
|
||||
}),
|
||||
|
||||
registerEventHandler: jest.fn(function registerEventHandler(callback) {}),
|
||||
};
|
||||
|
||||
module.exports = RCTFabricUIManager;
|
||||
|
||||
@@ -17,7 +17,7 @@ let UIManager;
|
||||
let FabricUIManager;
|
||||
|
||||
jest.mock('shared/ReactFeatureFlags', () =>
|
||||
require('shared/forks/ReactFeatureFlags.native-fabric'),
|
||||
require('shared/forks/ReactFeatureFlags.native-fabric-oss'),
|
||||
);
|
||||
|
||||
describe('ReactFabric', () => {
|
||||
@@ -33,9 +33,9 @@ describe('ReactFabric', () => {
|
||||
});
|
||||
|
||||
it('should be able to create and render a native component', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
ReactFabric.render(<View foo="test" />, 1);
|
||||
@@ -45,9 +45,9 @@ describe('ReactFabric', () => {
|
||||
});
|
||||
|
||||
it('should be able to create and update a native component', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
const firstNode = {};
|
||||
@@ -61,15 +61,19 @@ describe('ReactFabric', () => {
|
||||
ReactFabric.render(<View foo="bar" />, 11);
|
||||
|
||||
expect(FabricUIManager.createNode.mock.calls.length).toBe(1);
|
||||
expect(FabricUIManager.cloneNodeWithNewProps).toBeCalledWith(firstNode, {
|
||||
expect(FabricUIManager.cloneNodeWithNewProps.mock.calls.length).toBe(1);
|
||||
expect(FabricUIManager.cloneNodeWithNewProps.mock.calls[0][0]).toBe(
|
||||
firstNode,
|
||||
);
|
||||
expect(FabricUIManager.cloneNodeWithNewProps.mock.calls[0][1]).toEqual({
|
||||
foo: 'bar',
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call FabricUIManager.cloneNode after render for properties that have not changed', () => {
|
||||
const Text = createReactNativeComponentClass('Text', () => ({
|
||||
const Text = createReactNativeComponentClass('RCTText', () => ({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'Text',
|
||||
uiViewClassName: 'RCTText',
|
||||
}));
|
||||
|
||||
ReactFabric.render(<Text foo="a">1</Text>, 11);
|
||||
@@ -110,15 +114,15 @@ describe('ReactFabric', () => {
|
||||
});
|
||||
|
||||
it('should only pass props diffs to FabricUIManager.cloneNode', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const Text = createReactNativeComponentClass('RCTText', () => ({
|
||||
validAttributes: {foo: true, bar: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTText',
|
||||
}));
|
||||
|
||||
ReactFabric.render(
|
||||
<View foo="a" bar="a">
|
||||
<Text foo="a" bar="a">
|
||||
1
|
||||
</View>,
|
||||
</Text>,
|
||||
11,
|
||||
);
|
||||
expect(FabricUIManager.cloneNode).not.toBeCalled();
|
||||
@@ -127,9 +131,9 @@ describe('ReactFabric', () => {
|
||||
expect(FabricUIManager.cloneNodeWithNewChildrenAndProps).not.toBeCalled();
|
||||
|
||||
ReactFabric.render(
|
||||
<View foo="a" bar="b">
|
||||
<Text foo="a" bar="b">
|
||||
1
|
||||
</View>,
|
||||
</Text>,
|
||||
11,
|
||||
);
|
||||
expect(FabricUIManager.cloneNodeWithNewProps.mock.calls[0][1]).toEqual({
|
||||
@@ -138,9 +142,9 @@ describe('ReactFabric', () => {
|
||||
expect(FabricUIManager.__dumpHierarchyForJestTestsOnly()).toMatchSnapshot();
|
||||
|
||||
ReactFabric.render(
|
||||
<View foo="b" bar="b">
|
||||
<Text foo="b" bar="b">
|
||||
2
|
||||
</View>,
|
||||
</Text>,
|
||||
11,
|
||||
);
|
||||
expect(
|
||||
@@ -152,9 +156,9 @@ describe('ReactFabric', () => {
|
||||
});
|
||||
|
||||
it('should not call UIManager.updateView from setNativeProps for properties that have not changed', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
class Subclass extends ReactFabric.NativeComponent {
|
||||
@@ -187,9 +191,9 @@ describe('ReactFabric', () => {
|
||||
});
|
||||
|
||||
it('returns the correct instance and calls it in the callback', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
let a;
|
||||
@@ -208,9 +212,9 @@ describe('ReactFabric', () => {
|
||||
});
|
||||
|
||||
it('renders and reorders children', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {title: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
class Component extends React.Component {
|
||||
@@ -249,9 +253,9 @@ describe('ReactFabric', () => {
|
||||
});
|
||||
|
||||
it('should call complete after inserting children', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
const snapshots = [];
|
||||
@@ -272,4 +276,80 @@ describe('ReactFabric', () => {
|
||||
);
|
||||
expect(snapshots).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('should throw when <View> is used inside of a <Text> ancestor', () => {
|
||||
const Image = createReactNativeComponentClass('RCTImage', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTImage',
|
||||
}));
|
||||
const Text = createReactNativeComponentClass('RCTText', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTText',
|
||||
}));
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
expect(() =>
|
||||
ReactFabric.render(
|
||||
<Text>
|
||||
<View />
|
||||
</Text>,
|
||||
11,
|
||||
),
|
||||
).toThrow('Nesting of <View> within <Text> is not currently supported.');
|
||||
|
||||
// Non-View things (e.g. Image) are fine
|
||||
ReactFabric.render(
|
||||
<Text>
|
||||
<Image />
|
||||
</Text>,
|
||||
11,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw for text not inside of a <Text> ancestor', () => {
|
||||
const ScrollView = createReactNativeComponentClass('RCTScrollView', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTScrollView',
|
||||
}));
|
||||
const Text = createReactNativeComponentClass('RCTText', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTText',
|
||||
}));
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
expect(() => ReactFabric.render(<View>this should warn</View>, 11)).toThrow(
|
||||
'Text strings must be rendered within a <Text> component.',
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
ReactFabric.render(
|
||||
<Text>
|
||||
<ScrollView>hi hello hi</ScrollView>
|
||||
</Text>,
|
||||
11,
|
||||
),
|
||||
).toThrow('Text strings must be rendered within a <Text> component.');
|
||||
});
|
||||
|
||||
it('should not throw for text inside of an indirect <Text> ancestor', () => {
|
||||
const Text = createReactNativeComponentClass('RCTText', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTText',
|
||||
}));
|
||||
|
||||
const Indirection = () => 'Hi';
|
||||
|
||||
ReactFabric.render(
|
||||
<Text>
|
||||
<Indirection />
|
||||
</Text>,
|
||||
11,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ describe('ReactFabric', () => {
|
||||
ReactNative = require('react-native-renderer');
|
||||
jest.resetModules();
|
||||
jest.mock('shared/ReactFeatureFlags', () =>
|
||||
require('shared/forks/ReactFeatureFlags.native-fabric'),
|
||||
require('shared/forks/ReactFeatureFlags.native-fabric-oss'),
|
||||
);
|
||||
|
||||
React = require('react');
|
||||
@@ -31,9 +31,9 @@ describe('ReactFabric', () => {
|
||||
});
|
||||
|
||||
it('find Fabric nodes with the RN renderer', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {title: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
let ref = React.createRef();
|
||||
|
||||
@@ -153,7 +153,7 @@ it('handles events', () => {
|
||||
it('handles events on text nodes', () => {
|
||||
expect(RCTEventEmitter.register.mock.calls.length).toBe(1);
|
||||
const EventEmitter = RCTEventEmitter.register.mock.calls[0][0];
|
||||
const Text = fakeRequireNativeComponent('Text', {});
|
||||
const Text = fakeRequireNativeComponent('RCTText', {});
|
||||
|
||||
class ContextHack extends React.Component {
|
||||
static childContextTypes = {isInAParentText: PropTypes.bool};
|
||||
|
||||
@@ -27,9 +27,9 @@ describe('ReactNative', () => {
|
||||
});
|
||||
|
||||
it('should be able to create and render a native component', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
ReactNative.render(<View foo="test" />, 1);
|
||||
@@ -40,9 +40,9 @@ describe('ReactNative', () => {
|
||||
});
|
||||
|
||||
it('should be able to create and update a native component', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
ReactNative.render(<View foo="foo" />, 11);
|
||||
@@ -57,13 +57,13 @@ describe('ReactNative', () => {
|
||||
expect(UIManager.createView.mock.calls.length).toBe(1);
|
||||
expect(UIManager.setChildren.mock.calls.length).toBe(1);
|
||||
expect(UIManager.manageChildren).not.toBeCalled();
|
||||
expect(UIManager.updateView).toBeCalledWith(3, 'View', {foo: 'bar'});
|
||||
expect(UIManager.updateView).toBeCalledWith(3, 'RCTView', {foo: 'bar'});
|
||||
});
|
||||
|
||||
it('should not call UIManager.updateView after render for properties that have not changed', () => {
|
||||
const Text = createReactNativeComponentClass('Text', () => ({
|
||||
const Text = createReactNativeComponentClass('RCTText', () => ({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'Text',
|
||||
uiViewClassName: 'RCTText',
|
||||
}));
|
||||
|
||||
ReactNative.render(<Text foo="a">1</Text>, 11);
|
||||
@@ -87,9 +87,9 @@ describe('ReactNative', () => {
|
||||
});
|
||||
|
||||
it('should not call UIManager.updateView from setNativeProps for properties that have not changed', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
class Subclass extends ReactNative.NativeComponent {
|
||||
@@ -122,9 +122,9 @@ describe('ReactNative', () => {
|
||||
});
|
||||
|
||||
it('returns the correct instance and calls it in the callback', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {foo: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
let a;
|
||||
@@ -143,9 +143,9 @@ describe('ReactNative', () => {
|
||||
});
|
||||
|
||||
it('renders and reorders children', () => {
|
||||
const View = createReactNativeComponentClass('View', () => ({
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {title: true},
|
||||
uiViewClassName: 'View',
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
class Component extends React.Component {
|
||||
@@ -182,4 +182,80 @@ describe('ReactNative', () => {
|
||||
ReactNative.render(<Component />, 11);
|
||||
expect(mockArgs.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should throw when <View> is used inside of a <Text> ancestor', () => {
|
||||
const Image = createReactNativeComponentClass('RCTImage', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTImage',
|
||||
}));
|
||||
const Text = createReactNativeComponentClass('RCTText', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTText',
|
||||
}));
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
expect(() =>
|
||||
ReactNative.render(
|
||||
<Text>
|
||||
<View />
|
||||
</Text>,
|
||||
11,
|
||||
),
|
||||
).toThrow('Nesting of <View> within <Text> is not currently supported.');
|
||||
|
||||
// Non-View things (e.g. Image) are fine
|
||||
ReactNative.render(
|
||||
<Text>
|
||||
<Image />
|
||||
</Text>,
|
||||
11,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw for text not inside of a <Text> ancestor', () => {
|
||||
const ScrollView = createReactNativeComponentClass('RCTScrollView', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTScrollView',
|
||||
}));
|
||||
const Text = createReactNativeComponentClass('RCTText', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTText',
|
||||
}));
|
||||
const View = createReactNativeComponentClass('RCTView', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTView',
|
||||
}));
|
||||
|
||||
expect(() => ReactNative.render(<View>this should warn</View>, 11)).toThrow(
|
||||
'Text strings must be rendered within a <Text> component.',
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
ReactNative.render(
|
||||
<Text>
|
||||
<ScrollView>hi hello hi</ScrollView>
|
||||
</Text>,
|
||||
11,
|
||||
),
|
||||
).toThrow('Text strings must be rendered within a <Text> component.');
|
||||
});
|
||||
|
||||
it('should not throw for text inside of an indirect <Text> ancestor', () => {
|
||||
const Text = createReactNativeComponentClass('RCTText', () => ({
|
||||
validAttributes: {},
|
||||
uiViewClassName: 'RCTText',
|
||||
}));
|
||||
|
||||
const Indirection = () => 'Hi';
|
||||
|
||||
ReactNative.render(
|
||||
<Text>
|
||||
<Indirection />
|
||||
</Text>,
|
||||
11,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,69 +2,69 @@
|
||||
|
||||
exports[`ReactFabric renders and reorders children 1`] = `
|
||||
"11
|
||||
View null
|
||||
View {\\"title\\":\\"a\\"}
|
||||
View {\\"title\\":\\"b\\"}
|
||||
View {\\"title\\":\\"c\\"}
|
||||
View {\\"title\\":\\"d\\"}
|
||||
View {\\"title\\":\\"e\\"}
|
||||
View {\\"title\\":\\"f\\"}
|
||||
View {\\"title\\":\\"g\\"}
|
||||
View {\\"title\\":\\"h\\"}
|
||||
View {\\"title\\":\\"i\\"}
|
||||
View {\\"title\\":\\"j\\"}
|
||||
View {\\"title\\":\\"k\\"}
|
||||
View {\\"title\\":\\"l\\"}
|
||||
View {\\"title\\":\\"m\\"}
|
||||
View {\\"title\\":\\"n\\"}
|
||||
View {\\"title\\":\\"o\\"}
|
||||
View {\\"title\\":\\"p\\"}
|
||||
View {\\"title\\":\\"q\\"}
|
||||
View {\\"title\\":\\"r\\"}
|
||||
View {\\"title\\":\\"s\\"}
|
||||
View {\\"title\\":\\"t\\"}"
|
||||
RCTView null
|
||||
RCTView {\\"title\\":\\"a\\"}
|
||||
RCTView {\\"title\\":\\"b\\"}
|
||||
RCTView {\\"title\\":\\"c\\"}
|
||||
RCTView {\\"title\\":\\"d\\"}
|
||||
RCTView {\\"title\\":\\"e\\"}
|
||||
RCTView {\\"title\\":\\"f\\"}
|
||||
RCTView {\\"title\\":\\"g\\"}
|
||||
RCTView {\\"title\\":\\"h\\"}
|
||||
RCTView {\\"title\\":\\"i\\"}
|
||||
RCTView {\\"title\\":\\"j\\"}
|
||||
RCTView {\\"title\\":\\"k\\"}
|
||||
RCTView {\\"title\\":\\"l\\"}
|
||||
RCTView {\\"title\\":\\"m\\"}
|
||||
RCTView {\\"title\\":\\"n\\"}
|
||||
RCTView {\\"title\\":\\"o\\"}
|
||||
RCTView {\\"title\\":\\"p\\"}
|
||||
RCTView {\\"title\\":\\"q\\"}
|
||||
RCTView {\\"title\\":\\"r\\"}
|
||||
RCTView {\\"title\\":\\"s\\"}
|
||||
RCTView {\\"title\\":\\"t\\"}"
|
||||
`;
|
||||
|
||||
exports[`ReactFabric renders and reorders children 2`] = `
|
||||
"11
|
||||
View null
|
||||
View {\\"title\\":\\"m\\"}
|
||||
View {\\"title\\":\\"x\\"}
|
||||
View {\\"title\\":\\"h\\"}
|
||||
View {\\"title\\":\\"p\\"}
|
||||
View {\\"title\\":\\"g\\"}
|
||||
View {\\"title\\":\\"w\\"}
|
||||
View {\\"title\\":\\"f\\"}
|
||||
View {\\"title\\":\\"r\\"}
|
||||
View {\\"title\\":\\"a\\"}
|
||||
View {\\"title\\":\\"l\\"}
|
||||
View {\\"title\\":\\"k\\"}
|
||||
View {\\"title\\":\\"e\\"}
|
||||
View {\\"title\\":\\"o\\"}
|
||||
View {\\"title\\":\\"i\\"}
|
||||
View {\\"title\\":\\"v\\"}
|
||||
View {\\"title\\":\\"c\\"}
|
||||
View {\\"title\\":\\"s\\"}
|
||||
View {\\"title\\":\\"t\\"}
|
||||
View {\\"title\\":\\"z\\"}
|
||||
View {\\"title\\":\\"y\\"}"
|
||||
RCTView null
|
||||
RCTView {\\"title\\":\\"m\\"}
|
||||
RCTView {\\"title\\":\\"x\\"}
|
||||
RCTView {\\"title\\":\\"h\\"}
|
||||
RCTView {\\"title\\":\\"p\\"}
|
||||
RCTView {\\"title\\":\\"g\\"}
|
||||
RCTView {\\"title\\":\\"w\\"}
|
||||
RCTView {\\"title\\":\\"f\\"}
|
||||
RCTView {\\"title\\":\\"r\\"}
|
||||
RCTView {\\"title\\":\\"a\\"}
|
||||
RCTView {\\"title\\":\\"l\\"}
|
||||
RCTView {\\"title\\":\\"k\\"}
|
||||
RCTView {\\"title\\":\\"e\\"}
|
||||
RCTView {\\"title\\":\\"o\\"}
|
||||
RCTView {\\"title\\":\\"i\\"}
|
||||
RCTView {\\"title\\":\\"v\\"}
|
||||
RCTView {\\"title\\":\\"c\\"}
|
||||
RCTView {\\"title\\":\\"s\\"}
|
||||
RCTView {\\"title\\":\\"t\\"}
|
||||
RCTView {\\"title\\":\\"z\\"}
|
||||
RCTView {\\"title\\":\\"y\\"}"
|
||||
`;
|
||||
|
||||
exports[`ReactFabric should call complete after inserting children 1`] = `
|
||||
Array [
|
||||
"View {\\"foo\\":\\"a\\"}
|
||||
View {\\"foo\\":\\"b\\"}",
|
||||
"RCTView {\\"foo\\":\\"a\\"}
|
||||
RCTView {\\"foo\\":\\"b\\"}",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`ReactFabric should only pass props diffs to FabricUIManager.cloneNode 1`] = `
|
||||
"11
|
||||
View {\\"foo\\":\\"a\\",\\"bar\\":\\"b\\"}
|
||||
RCTText {\\"foo\\":\\"a\\",\\"bar\\":\\"b\\"}
|
||||
RCTRawText {\\"text\\":\\"1\\"}"
|
||||
`;
|
||||
|
||||
exports[`ReactFabric should only pass props diffs to FabricUIManager.cloneNode 2`] = `
|
||||
"11
|
||||
View {\\"foo\\":\\"b\\",\\"bar\\":\\"b\\"}
|
||||
RCTText {\\"foo\\":\\"b\\",\\"bar\\":\\"b\\"}
|
||||
RCTRawText {\\"text\\":\\"2\\"}"
|
||||
`;
|
||||
|
||||
@@ -2,50 +2,50 @@
|
||||
|
||||
exports[`ReactNative renders and reorders children 1`] = `
|
||||
"<native root> {}
|
||||
View null
|
||||
View {\\"title\\":\\"a\\"}
|
||||
View {\\"title\\":\\"b\\"}
|
||||
View {\\"title\\":\\"c\\"}
|
||||
View {\\"title\\":\\"d\\"}
|
||||
View {\\"title\\":\\"e\\"}
|
||||
View {\\"title\\":\\"f\\"}
|
||||
View {\\"title\\":\\"g\\"}
|
||||
View {\\"title\\":\\"h\\"}
|
||||
View {\\"title\\":\\"i\\"}
|
||||
View {\\"title\\":\\"j\\"}
|
||||
View {\\"title\\":\\"k\\"}
|
||||
View {\\"title\\":\\"l\\"}
|
||||
View {\\"title\\":\\"m\\"}
|
||||
View {\\"title\\":\\"n\\"}
|
||||
View {\\"title\\":\\"o\\"}
|
||||
View {\\"title\\":\\"p\\"}
|
||||
View {\\"title\\":\\"q\\"}
|
||||
View {\\"title\\":\\"r\\"}
|
||||
View {\\"title\\":\\"s\\"}
|
||||
View {\\"title\\":\\"t\\"}"
|
||||
RCTView null
|
||||
RCTView {\\"title\\":\\"a\\"}
|
||||
RCTView {\\"title\\":\\"b\\"}
|
||||
RCTView {\\"title\\":\\"c\\"}
|
||||
RCTView {\\"title\\":\\"d\\"}
|
||||
RCTView {\\"title\\":\\"e\\"}
|
||||
RCTView {\\"title\\":\\"f\\"}
|
||||
RCTView {\\"title\\":\\"g\\"}
|
||||
RCTView {\\"title\\":\\"h\\"}
|
||||
RCTView {\\"title\\":\\"i\\"}
|
||||
RCTView {\\"title\\":\\"j\\"}
|
||||
RCTView {\\"title\\":\\"k\\"}
|
||||
RCTView {\\"title\\":\\"l\\"}
|
||||
RCTView {\\"title\\":\\"m\\"}
|
||||
RCTView {\\"title\\":\\"n\\"}
|
||||
RCTView {\\"title\\":\\"o\\"}
|
||||
RCTView {\\"title\\":\\"p\\"}
|
||||
RCTView {\\"title\\":\\"q\\"}
|
||||
RCTView {\\"title\\":\\"r\\"}
|
||||
RCTView {\\"title\\":\\"s\\"}
|
||||
RCTView {\\"title\\":\\"t\\"}"
|
||||
`;
|
||||
|
||||
exports[`ReactNative renders and reorders children 2`] = `
|
||||
"<native root> {}
|
||||
View null
|
||||
View {\\"title\\":\\"m\\"}
|
||||
View {\\"title\\":\\"x\\"}
|
||||
View {\\"title\\":\\"h\\"}
|
||||
View {\\"title\\":\\"p\\"}
|
||||
View {\\"title\\":\\"g\\"}
|
||||
View {\\"title\\":\\"w\\"}
|
||||
View {\\"title\\":\\"f\\"}
|
||||
View {\\"title\\":\\"r\\"}
|
||||
View {\\"title\\":\\"a\\"}
|
||||
View {\\"title\\":\\"l\\"}
|
||||
View {\\"title\\":\\"k\\"}
|
||||
View {\\"title\\":\\"e\\"}
|
||||
View {\\"title\\":\\"o\\"}
|
||||
View {\\"title\\":\\"i\\"}
|
||||
View {\\"title\\":\\"v\\"}
|
||||
View {\\"title\\":\\"c\\"}
|
||||
View {\\"title\\":\\"s\\"}
|
||||
View {\\"title\\":\\"t\\"}
|
||||
View {\\"title\\":\\"z\\"}
|
||||
View {\\"title\\":\\"y\\"}"
|
||||
RCTView null
|
||||
RCTView {\\"title\\":\\"m\\"}
|
||||
RCTView {\\"title\\":\\"x\\"}
|
||||
RCTView {\\"title\\":\\"h\\"}
|
||||
RCTView {\\"title\\":\\"p\\"}
|
||||
RCTView {\\"title\\":\\"g\\"}
|
||||
RCTView {\\"title\\":\\"w\\"}
|
||||
RCTView {\\"title\\":\\"f\\"}
|
||||
RCTView {\\"title\\":\\"r\\"}
|
||||
RCTView {\\"title\\":\\"a\\"}
|
||||
RCTView {\\"title\\":\\"l\\"}
|
||||
RCTView {\\"title\\":\\"k\\"}
|
||||
RCTView {\\"title\\":\\"e\\"}
|
||||
RCTView {\\"title\\":\\"o\\"}
|
||||
RCTView {\\"title\\":\\"i\\"}
|
||||
RCTView {\\"title\\":\\"v\\"}
|
||||
RCTView {\\"title\\":\\"c\\"}
|
||||
RCTView {\\"title\\":\\"s\\"}
|
||||
RCTView {\\"title\\":\\"t\\"}
|
||||
RCTView {\\"title\\":\\"z\\"}
|
||||
RCTView {\\"title\\":\\"y\\"}"
|
||||
`;
|
||||
|
||||
7
packages/react-noop-renderer/npm/persistent.js
vendored
Normal file
7
packages/react-noop-renderer/npm/persistent.js
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./cjs/react-noop-renderer-persistent.production.min.js');
|
||||
} else {
|
||||
module.exports = require('./cjs/react-noop-renderer-persistent.development.js');
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
"LICENSE",
|
||||
"README.md",
|
||||
"index.js",
|
||||
"persistent.js",
|
||||
"cjs/"
|
||||
]
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user