Compare commits

...

117 Commits

Author SHA1 Message Date
Andrew Clark
8765d60893 Update bundle sizes for 16.4.0 release 2018-05-23 17:35:31 -07:00
Andrew Clark
d31e753f89 Update error codes for 16.4.0 release 2018-05-23 17:35:31 -07:00
Andrew Clark
d427a563d5 Updating package versions for release 16.4.0 2018-05-23 17:30:33 -07:00
Andrew Clark
eca59ec1b3 Updating yarn.lock file for 16.4.0 release 2018-05-23 17:26:46 -07:00
Chang Yan
53852a887b add functional components warning about legacy context api (#12892) 2018-05-23 14:16:39 -07:00
Toru Kobayashi
fe747a51c1 Add React.Timeout to getComponentName (#12890) 2018-05-23 18:39:20 +01:00
Toru Kobayashi
3df157480a Fix a typo (#12889) 2018-05-23 17:47:23 +01:00
Dan Abramov
6f4fb4a059 Tweak the changelog 2018-05-23 17:04:42 +01:00
Dan Abramov
d1be01f079 Add upcoming 16.4.0 changelog 2018-05-23 16:40:35 +01:00
Chang Yan
c601f7a646 add siblings Timeout components test case (#12862) 2018-05-22 15:39:40 -07:00
Chang Yan
7350358374 add legacy context API warning in strict mode (#12849)
* add legacy context APIs warning in strict mode

* refactor if statement and the warning message

* add other flags for type check

* add component stack tree and refactor wording

* fix the nits
2018-05-22 15:38:02 -07:00
Dan Abramov
e885791842 Fix a regression that caused us to listen to extra events at the top (#12878)
* Rewrite to a switch

I find it a bit easier to follow than many comparison conditions.

* Remove unnecessary assignments

They are being assigned below anyway. This is likely a copypasta from the FOCUS/BLUR special case (which *does* need those assignments).

* Unify "cancel" and "close" cases

Their logic is identical.

* Don't listen to media events at the top

* Add a unit test for double-invoking form events

* Remove an unused case and document it in a test

The case I added was wrong (just like including this event in the top level list was always wrong).

In fact it never bubbles, even for <img>. And since we don't special case it in the <img> event
attachment logic when we create it, we never supported <img onLoadStart> at all.

We could fix it. But Chrome doesn't support it either: https://bugs.chromium.org/p/chromium/issues/detail?id=458851.
Nobody asked us for it yet. And supporting it would require attaching an extra listener to every <img>.

So maybe we don't need it? Let's document the existing state of things.

* Add a test verifying we don't attach unnecessary listeners

* Add a comment

* Add a test for submit (bubbles: false)
2018-05-22 19:50:36 +01:00
Brian Vaughn
7c0aca289d Rollup freeze: false (#12879)
* Tell Rollup not to freeze bundles
* Only freeze bundles for DEV builds
2018-05-22 08:16:59 -07:00
Flarnie Marchan
33289b530c Tests and fixes for 'timing out' behavior (#12858)
**what is the change?:**
Test coverage checking that callbacks are called when they time out.

This test surfaced a bug and this commit includes the fix.

I want to refine this approach, but basically we can simulate time outs
by controlling the return value of 'now()' and the argument passed to
the rAF callback.

Next we will write a browser fixture to further test this against real
browser APIs.

**why make this change?:**
Better tests will keep this module working smoothly while we continue
refactoring and improving it.

**test plan:**
Run the new tests, see that it fails without the bug fix.
2018-05-22 08:03:55 -07:00
Sophie Alpert
ad27845ccd Fix double-firing submit events (#12877)
We were adding a listener at the root when we weren't meant to. Blames to e96dc14059.

This now alerts once (at FORM) instead of twice (at FORM, #document):

```
var Hello = class extends React.Component {
  render() {
    return (
      <form onSubmit={(e) => {e.preventDefault(); alert('hi ' + e.nativeEvent.currentTarget.nodeName);}}>
        <button>hi</button>
      </form>
    );
  }
};
```
2018-05-21 17:47:56 -07:00
Dan Abramov
60853f09f3 Try to reenable Flow on Windows CI
We should have more memory now
2018-05-21 18:53:00 +01:00
Dan Abramov
dd5fad2961 Update Flow to 0.70 (#12875)
* Update Flow to 0.70

* Remove unnecessary condition

* Fix wrong assertion

* Strict check
2018-05-21 17:54:48 +01:00
Brian Vaughn
13003654e7 Pass "start time" and "commit time" to Profiler callback (#12852)
* Added start time parameter to Profiler onRender callback
* Profiler also captures commit time
* Only init Profiler stateNode if enableProfilerTimer feature flag enabled
2018-05-21 09:49:22 -07:00
Dan Abramov
12c8a88cd9 Update Jest (#12874) 2018-05-21 16:17:11 +01:00
Dan Abramov
dc3b144f41 Treat Rollup "warnings" as errors (#12868) 2018-05-21 15:38:46 +01:00
Dan Abramov
0442e8275f Add a clear error when renderers clash in tests (#12867) 2018-05-21 15:38:35 +01:00
Kevin Lamping
089d2deb20 add netlify toml file (#12350) 2018-05-20 21:03:51 +01:00
Kevin (Kun) "Kassimo" Qian
d7b9b4921b Fix react native example links in README of 'react-reconciler' (#12871) 2018-05-20 12:01:00 +01:00
Sophie Alpert
9bed4a6aee https in reactProdInvariant text (#12869) 2018-05-19 17:29:41 -07:00
Dan Abramov
397d6115b7 Temporarily disable Flow on AppVeyor
I think it runs out of memory. I’ll reenable if we can bump the limit.
2018-05-19 13:54:44 +01:00
Dan Abramov
47b003a828 Resolve host configs at build time (#12792)
* Extract base Jest config

This makes it easier to change the source config without affecting the build test config.

* Statically import the host config

This changes react-reconciler to import HostConfig instead of getting it through a function argument.

Rather than start with packages like ReactDOM that want to inline it, I started with React Noop and ensured that *custom* renderers using react-reconciler package still work. To do this, I'm making HostConfig module in the reconciler look at a global variable by default (which, in case of the react-reconciler npm package, ends up being the host config argument in the top-level scope).

This is still very broken.

* Add scaffolding for importing an inlined renderer

* Fix the build

* ES exports for renderer methods

* ES modules for host configs

* Remove closures from the reconciler

* Check each renderer's config with Flow

* Fix uncovered Flow issue

We know nextHydratableInstance doesn't get mutated inside this function, but Flow doesn't so it thinks it may be null.
Help Flow.

* Prettier

* Get rid of enable*Reconciler flags

They are not as useful anymore because for almost all cases (except third party renderers) we *know* whether it supports mutation or persistence.

This refactoring means react-reconciler and react-reconciler/persistent third-party packages now ship the same thing.
Not ideal, but this seems worth how simpler the code becomes. We can later look into addressing it by having a single toggle instead.

* Prettier again

* Fix Flow config creation issue

* Fix imprecise Flow typing

* Revert accidental changes
2018-05-19 11:29:11 +01:00
Royi Hagigi
c0fe8d6f69 Adds ReactScheduler red->green unit test for bug fixed in #12834 (#12861)
* Scheduler red->green unit test for bug

* fix lint issue

* ran prettier
2018-05-18 15:05:21 -07:00
Flarnie Marchan
5e80d81f37 High pri - ensure we call timed out callbacks in schedule (#12857)
**what is the change?:**
Fix a typo which caused timed out callbacks to not be called.

**why make this change?:**
This is a bug caught by tests I'm in the process of writing, and we
should fix it asap.

**test plan:**
Tests in a WIP PR - will push and share the WIP test in comments on this
PR.
2018-05-18 10:05:49 -07:00
Brian Vaughn
17908c8ac9 Add test to ensure no duplicate values in ReactSymbols (#12845) 2018-05-18 07:57:25 -07:00
Dan
96992f2a6c Try to fix Windows CI 2018-05-18 09:25:50 +01:00
Pete Nykänen
972d209dcc Fix sample command in scripts/bench/README.md (#12853) 2018-05-18 09:18:35 +01:00
Dan Abramov
40addbd110 Run Flow for each renderer separately (#12846)
* Generate Flow config on install

We'll need to do pre-renderer Flow passes with different configs.
This is the first step to get it working. We only want the original version checked in.

* Create multiple Flow configs from a template

* Run Flow per renderer

* Lint

* Revert the environment consolidation

I thought this would be a bit cleaner at first because we now have non-environment files in this directory.
But Sebastian is changing these files at the same time so I want to avoid conflicts and keep the PR more tightly scoped. Undo.

* Misc
2018-05-18 02:05:19 +01:00
Sebastian Markbåge
40ea053bac Remove incorrect comment
Better to not have it than it being wrong.
2018-05-17 15:47:10 -07:00
Sebastian Markbåge
c5a8dae025 [Fabric] Wire up event emitters (#12847)
I'm exposing a new native method to wire up the event emitter. This will
use a straight fiber pointer instead of react tags to do the dispatching.
2018-05-17 12:38:50 -07:00
Dan Abramov
9d71ef26c3 Run the CI script on Windows 2018-05-17 19:18:47 +01:00
Gary Wang
1a0afed771 getPeerGlobals should check bundleType instead of moduleType (#12839) 2018-05-17 17:16:03 +01:00
Dan Abramov
b245795de3 Re-enable Flow for ReactFiber and fix Flow issues (#12842)
* Lint for untyped imports and enable Flow typing in ReactFiber

* Re-enable Flow for ReactFiber and fix Flow issues

* Avoid an invariant in DEV-only code

I just introduced it, but on a second thought, it's better to keep it as a warning.

* Address review
2018-05-17 17:14:12 +01:00
Flarnie Marchan
7ccb37161f Temporary fix for grabbing wrong rAF polyfill in ReactScheduler (#12837)
* Temporary fix for grabbing wrong rAF polyfill in ReactScheduler

**what is the change?:**
For now...
We need to grab a slightly different implementation of rAF internally at
FB than in Open Source. Making rAF a dependency of the ReactScheduler
module allows us to fork the dependency at FB.

NOTE: After this lands we have an alternative plan to make this module
separate from React and require it before our Facebook timer polyfills
are applied. But want to land this now to keep master in a working state
and fix bugs folks are seeing at Facebook.

Thanks @sebmarkbage @acdlite and @sophiebits for discussing the options
and trade-offs for solving this issue.

**why make this change?:**
This fixes a problem we're running into when experimenting with
ReactScheduler internally at Facebook, **and* it's part of our long term
plan to use dependency injection with the scheduler to make it easier to
test and adjust.

**test plan:**
Ran tests, lint, flow, and will manually test when syncing into
Facebook's codebase.

**issue:**
See internal task T29442940

* ran prettier
2018-05-17 08:57:45 -07:00
Brian Vaughn
4b8510be0f Make REACT_PROFILER_TYPE numeric value unique (#12843) 2018-05-17 08:55:41 -07:00
Dan Abramov
2d20dc47a3 Separate yarn flow and yarn flow-ci (#12841) 2018-05-17 14:29:37 +01:00
Sebastian Markbåge
d4123b4784 Relax current renderer warning (#12838)
If you use an older version of `react` this won't get initialized to null. We don't really need it to be initialized to work.
2018-05-16 17:31:56 -07:00
Brian Vaughn
2ace49362a Removed duplicate feature flag in test (#12836) 2018-05-16 15:39:32 -07:00
Flarnie Marchan
2da155a4c3 Quick fix for minor typo in ReactScheduler (#12834)
**what is the change?:**
We were setting a flag after some early returns, should have set it
right away.

To be fair, it's not clear how you can hit a problem with the current
state of things. Even if a callback is cancelled, it's still in the
'pendingCallbacks' queue until the rAF runs, and we only schedule a rAF
when there are pendingCallbacks in the queue.

But since this is obviously wrong, going to fix it.

We will be adding a regression test in a follow-up PR.

**why make this change?:**
To fix a random bug which was popping up.

**test plan:**
Adding a regression unit test in a follow-up PR.
2018-05-16 14:18:22 -07:00
Brian Vaughn
d6f304e889 Remove Timeout export on React object unless enableSuspense flag (#12833) 2018-05-16 14:02:34 -07:00
Flarnie Marchan
8227e54ccf Quick fix for ReactScheduler type inconsistency (#12828)
**what is the change?:**
In some cases we had defined the 'callback' as taking two arguments,
when really we meant to indicate the second argument passed to
'scheduleWork'.

**why make this change?:**
For correctness and to unblock something @gaearon is working on. A bit
surprised Flow didn't catch this in the first place.

**test plan:**
Ran tests, flow, lint.
2018-05-16 08:07:42 -07:00
Flarnie Marchan
ef294ed6fc Rename Scheduler methods more accurately (#12770)
* Rename Scheduler methods more accurately

**what is the change?:**
```
rIC -> scheduleCallback
```
We will later expose a second method for different priority level, name
TBD. Since we only have one priority right now we can delay the
bikeshedding about the priority names.

cIC -> cancelScheduledCallback
This method can be used to cancel callbacks scheduled at any priority
level, and will remain named this way.

why make this change?:
Originally this module contained a polyfill for requestIdleCallback
and cancelIdleCallback but we are changing the behavior so it's no
longer just a polyfill. The new names are more semantic and distinguish
this from the original polyfill functionality.

**test plan:**
Ran the tests

**why make this change?:**
Getting this out of the way so things are more clear.

**Coming Up Next:**
- Switching from a Map of ids and an array to a linked list for storing
callbacks.
- Error handling

* callback -> work

* update callsites in new places after rebase

* fix typo
2018-05-16 06:36:06 -07:00
Philipp Spieß
49979bbf52 Support Pointer Events (#12507)
* Support Pointer Events

* Add Pointer Events DOM Fixture
2018-05-16 14:34:33 +01:00
Brian Vaughn
de84d5c107 Enable Profiler timing for DOM and RN dev bundles (#12823)
* Enable Profiler timing for DOM and RN dev bundles
* Disable enableProfilerTimer feature flag for ReactIncrementalPerf-test
2018-05-15 15:26:46 -07:00
Sebastian Markbåge
f792275972 Pass instance handle to all Fabric clone methods (#12824)
We might need this in the future if we want to ensure event handler
consistency when an event handler target has been removed before it is
called.
2018-05-15 14:35:13 -07:00
Andrew Clark
a5184b215d Add FB www build of simple-cache-provider (#12822) 2018-05-15 13:21:07 -07:00
Brian Vaughn
103503eb69 Only measure "base" times within ProfileMode (#12821)
* Conditionally start/stop base timer only within Profile mode tree
* Added test to ensure ProfilerTimer not called outside of Profiler root
2018-05-15 12:43:42 -07:00
Dan Abramov
9097f3cdf0 Delete React Call/Return experiment (#12820) 2018-05-15 19:16:29 +01:00
Dan Abramov
d758960116 Tweak comments 2018-05-15 15:42:43 +01:00
Dan Abramov
025d867dce Try another approach at fixing Windows Flow issues 2018-05-15 15:20:15 +01:00
Dan Abramov
fe7890d569 Revert recent Flow changes 2018-05-15 15:03:21 +01:00
Dan Abramov
7ba1abecaa Try to fix Flow issue on Windows (part 5) 2018-05-15 14:55:38 +01:00
Dan Abramov
f2252a2ad4 Try to fix Flow issue on Windows (part 4) 2018-05-15 14:46:58 +01:00
Dan Abramov
b998357f9d Try to fix Flow issue on Windows (part 3) 2018-05-15 14:26:32 +01:00
Dan Abramov
7631024722 Try to fix Flow issue on Windows 2018-05-15 14:07:01 +01:00
Dan Abramov
bb44feb05d Try to fix Flow circular dependency 2018-05-15 13:55:01 +01:00
Dan Abramov
7dc1a176b5 Skip special nodes when reading TestInstance.parent (#12813) 2018-05-15 11:11:19 +01:00
Philipp Spieß
e96dc14059 Use browser event names for top-level event types in React DOM (#12629)
* Add TopLevelEventTypes

* Fix `ReactBrowserEventEmitter`

* Fix EventPluginUtils

* Fix TapEventPlugin

* Fix ResponderEventPlugin

* Update ReactDOMFiberComponent

* Fix BeforeInputEventPlugin

* Fix ChangeEventPlugin

* Fix EnterLeaveEventPlugin

* Add missing non top event type used in ChangeEventPlugin

* Fix SelectEventPlugin

* Fix SimpleEventPlugin

* Fix outstanding Flow issues and move TopLevelEventTypes

* Inline a list of all events in `ReactTestUtils`

* Fix tests

* Make it pretty

* Fix completly unrelated typo

* Don’t use map constructor because of IE11

* Update typings, revert changes to native code

* Make topLevelTypes in ResponderEventPlugin injectable and create DOM and ReactNative variant

* Set proper dependencies for DOMResponderEventPlugin

* Prettify

* Make some react dom tests no longer depend on internal API

* Use factories to create top level speific generic event modules

* Remove unused dependency

* Revert exposed module renaming, hide store creation, and inline dependency decleration

* Add Flow types to createResponderEventPlugin and its consumers

* Remove unused dependency

* Use opaque flow type for TopLevelType

* Add missing semis

* Use raw event names as top level identifer

* Upgrade baylon

This is required for parsing opaque flow types in our CI tests.

* Clean up flow types

* Revert Map changes of ReactBrowserEventEmitter

* Upgrade babel-* packages

Apparently local unit tests also have issues with parsing JavaScript
modules that contain opaque types (not sure why I didn't notice
earlier!?).

* Revert Map changes of SimpleEventPlugin

* Clean up ReactTestUtils

* Add missing semi

* Fix Flow issue

* Make TopLevelType clearer

* Favor for loops

* Explain the new DOMTopLevelEventTypes concept

* Use static injection for Responder plugin types

* Remove null check and rely on flow checks

* Add missing ResponderEventPlugin dependencies
2018-05-15 10:38:50 +01:00
Maxim
1047980dca Remove unused context param from countChildren (#12787) 2018-05-15 10:18:35 +01:00
Timothy Yung
bde4b1659f Delete ReactPerf and ReactDebugTool Stubs (#12809) 2018-05-14 20:28:55 -07:00
Andrew Clark
73f59e6f31 Use global state for hasForceUpdate instead of persisting to queue (#12808)
* Use global state for `hasForceUpdate` instead of persisting to queue

Fixes a bug where `hasForceUpdate` was not reset on commit.

Ideally we'd use a tuple and return `hasForceUpdate` from
`processUpdateQueue`.

* Remove underscore and add comment

* Remove temporary variables
2018-05-14 19:18:47 -07:00
Sophie Alpert
8c747d01cb Use ReactFiberErrorDialog fork for Fabric renderer (#12807) 2018-05-14 18:47:40 -07:00
Timothy Yung
369dd4fb17 Update headers for React Native shims (#12806) 2018-05-15 01:47:47 +01:00
Dan Abramov
45b90d4866 Move renderer host configs into separate modules (#12791)
* Separate test renderer host config

* Separate ART renderer host config

* Separate ReactDOM host config

* Extract RN Fabric host config

* Extract RN host config
2018-05-15 01:12:28 +01:00
Timothy Yung
b2d16047ae Fix Type for ReactNative.NativeComponent (#12805) 2018-05-14 16:36:50 -07:00
Brian Vaughn
c802d29bd1 Use HostContext to warn about invalid View/Text nesting (#12766) 2018-05-14 15:34:01 -07:00
Andrew Clark
c5d3104fc0 Do not fire getDerivedStateFromProps unless props or state have changed (#12802)
Fixes an oversight from #12600. getDerivedStateFromProps should fire
if either props *or* state have changed, but not if *neither* have
changed. This prevents a parent from re-rendering if a deep child
receives an update.
2018-05-14 14:56:48 -07:00
Brian Vaughn
0ba63aa141 Mark React Native and Fabric renderers as @generated (#12801)
Mark React Native and Fabric renderers as @generated
2018-05-14 10:39:30 -07:00
Sophie Alpert
c4abfa4015 Add context provider/consumer to getComponentName (#12778)
RN Inspector uses these.
2018-05-14 10:10:36 -07:00
Sophie Alpert
2a4d2ca7fc Set owner correctly inside forwardRef and context consumer (#12777)
Previously, _owner would be null if you create an element inside forwardRef or inside a context consumer. This is used by ReactNativeFiberInspector when traversing the hierarchy and also to give more info in some warning texts. This also means you'll now correctly get a warning if you call setState inside one of these.

Test Plan: Tim tried it in the RN inspector.
2018-05-14 10:07:31 -07:00
Dan Abramov
72542030cf Use Java version of Google Closure Compiler (#12800)
* makes closure compiler threaded

* Dans PR with a closure compiler java version

* Remove unused dep

* Pin GCC

* Prettier

* Nit rename

* Fix error handling

* Name plugins consistently

* Fix lint

* Maybe this works?

* or this

* AppVeyor

* Fix lint
2018-05-14 17:49:41 +01:00
Dan Abramov
37d12e2916 Update lockfile 2018-05-14 16:20:33 +01:00
Dan Abramov
0470854f55 Split ReactNoop into normal and persistent exports (#12793)
* Copy-paste ReactNoop into ReactNoopPersistent

* Split ReactNoop into normal and persistent exports

* ReactNoopShared -> createReactNoop
2018-05-14 13:57:33 +01:00
Toru Kobayashi
d430e13582 Fix a typo (#12798) 2018-05-14 12:35:20 +01:00
Bartosz Kaszubowski
8506062975 remove unused ES3-specific packages - refs #12716 (#12797) 2018-05-14 11:18:31 +01:00
Dan
7b19f93ab9 Record sizes 2018-05-13 21:12:25 +01:00
Andrew Clark
4b2e65d32e Put recent change to getDerivedStateFromProps behind a feature flag (#12788)
This will allow us to safely ship it at Facebook and get a better idea
for if/how it breaks existing product code.
2018-05-11 18:45:00 -07:00
Filipp Riabchun
4f459bb144 Shallow renderer: pass component instance to setState updater as this (#12784)
* Shallow renderer: pass component instance to setState updater as `this`

* Run prettier
2018-05-11 18:03:08 +01:00
Andrew Clark
b0726e9947 Support sharing context objects between concurrent renderers (#12779)
* Support concurrent primary and secondary renderers.

As a workaround to support multiple concurrent renderers, we categorize
some renderers as primary and others as secondary. We only expect
there to be two concurrent renderers at most: React Native (primary) and
Fabric (secondary); React DOM (primary) and React ART (secondary).
Secondary renderers store their context values on separate fields.

* Add back concurrent renderer warning

Only warn for two concurrent primary or two concurrent secondary renderers.

* Change "_secondary" suffix to "2"

#EveryBitCounts
2018-05-10 18:34:01 -07:00
Andrew Clark
6565795377 Suspense (#12279)
* Timeout component

Adds Timeout component. If a promise is thrown from inside a Timeout component,
React will suspend the in-progress render from committing. When the promise
resolves, React will retry. If the render is suspended for longer than the
maximum threshold, the Timeout switches to a placeholder state.

The timeout threshold is defined as the minimum of:
- The expiration time of the current render
- The `ms` prop given to each Timeout component in the ancestor path of the
thrown promise.

* Add a test for nested fallbacks

Co-authored-by: Andrew Clark <acdlite@fb.com>

* Resume on promise rejection

React should resume rendering regardless of whether it resolves
or rejects.

* Wrap Suspense code in feature flag

* Children of a Timeout must be strict mode compatible

Async is not required for Suspense, but strict mode is.

* Simplify list of pending work

Some of this was added with "soft expiration" in mind, but now with our revised
model for how soft expiration will work, this isn't necessary.

It would be nice to remove more of this, but I think the list itself is inherent
because we need a way to track the start times, for <Timeout ms={ms} />.

* Only use the Timeout update queue to store promises, not for state

It already worked this way in practice.

* Wrap more Suspense-only paths in the feature flag

* Attach promise listener immediately on suspend

Instead of waiting for commit phase.

* Infer approximate start time using expiration time

* Remove list of pending priority levels

We can replicate almost all the functionality by tracking just five
separate levels: the highest/lowest priority pending levels, the
highest/lowest priority suspended levels, and the lowest pinged level.

We lose a bit of granularity, in that if there are multiple levels of
pending updates, only the first and last ones are known. But in practice
this likely isn't a big deal.

These heuristics are almost entirely isolated to a single module and
can be adjusted later, without API changes, if necessary.

Non-IO-bound work is not affected at all.

* ReactFiberPendingWork -> ReactFiberPendingPriority

* Renaming method names from "pending work" to "pending priority"

* Get rid of SuspenseThenable module

Idk why I thought this was neccessary

* Nits based on Sebastian's feedback

* More naming nits + comments

* Add test for hiding a suspended tree to unblock

* Revert change to expiration time rounding

This means you have to account for the start time approximation
heuristic when writing Suspense tests, but that's going to be
true regardless.

When updating the tests, I also made a fix related to offscreen
priority. We should never timeout inside a hidden tree.

* palceholder -> placeholder
2018-05-10 18:09:10 -07:00
Andrew Clark
42a1262375 Update sizes 2018-05-10 18:08:11 -07:00
Brian Vaughn
fc3777b1fe Add Profiler component for collecting new render timing info (#12745)
Add a new component type, Profiler, that can be used to collect new render time metrics. Since this is a new, experimental API, it will be exported as React.unstable_Profiler initially.

Most of the functionality for this component has been added behind a feature flag, enableProfileModeMetrics. When the feature flag is disabled, the component will just render its children with no additional behavior. When the flag is enabled, React will also collect timing information and pass it to the onRender function (as described below).
2018-05-10 15:25:32 -07:00
Flarnie Marchan
a9abd27e4f [schedule] Support multiple callbacks in scheduler (#12746)
* Support using id to cancel scheduled callback

**what is the change?:**
see title

**why make this change?:**
Once we support multiple callbacks you will need to use the id to
specify which callback you mean.

**test plan:**
Added a test, ran all tests, lint, etc.

* ran prettier

* fix lint

* Use object for storing callback info in scheduler

* Wrap initial test in a describe block

* Support multiple callbacks in `ReactScheduler`

**what is the change?:**
We keep a queue of callbacks instead of just one at a time, and call
them in order first by their timeoutTime and then by the order which
they were scheduled in.

**why make this change?:**
We plan on using this module to coordinate JS outside of React, so we
will need to schedule more than one callback at a time.

**test plan:**
Added a boatload of shiny new tests. :)

Plus ran all the old ones.

NOTE: The tests do not yet cover the vital logic of callbacks timing
out, and later commits will add the missing test coverage.

* Heuristic to avoid looking for timed out callbacks when none timed out

**what is the change?:**
Tracks the current soonest timeOut time for all scheduled callbacks.

**why make this change?:**
We were checking every scheduled callback to see if it timed out on
every tick. It's more efficient to skip that O(n) check if we know that
none have timed out.

**test plan:**
Ran existing tests.

Will write new tests to cover timeout behavior in more detail soon.

* Put multiple callback support under a disabled feature flag

**what is the change?:**
See title

**why make this change?:**
We don't have error handling in place yet, so should maintain the old
behavior until that is in place.

But want to get this far to continue making incremental changes.

**test plan:**
Updated and ran tests.

* Hide support for multiple callbacks under a feature flag

**what is the change?:**
see title

**why make this change?:**
We haven't added error handling yet, so should not expose this feature.

**test plan:**
Ran all tests, temporarily split out the tests for multiple callbacks
into separate file. Will recombine once we remove the flag.

* Fix nits from code review

See comments on https://github.com/facebook/react/pull/12743

* update checklist in comments

* Remove nested loop which calls additional timed out callbacks

**what is the change?:**
We used to re-run any callbacks which time out whilst other callbacks
are running, but now we will only check once for timed out callbacks
then then run them.

**why make this change?:**
To simplify the code and the behavior of this module.

**test plan:**
Ran all existing tests.

* Remove feature flag

**what is the change?:**
see title

**why make this change?:**
Because only React is using this, and it sounds like async. rendering
won't hit any different behavior due to these changes.

**test plan:**
Existing tests pass, and this allowed us to recombine all tests to run
in both 'test' and 'test-build' modes.

* remove outdated file

* fix typo
2018-05-09 15:28:13 -07:00
bee0060
3fb8be5c30 Minor fix params description for addPercent function (#12669) 2018-05-07 17:46:42 -07:00
Toru Kobayashi
0bf24cc83e setState returning null and undefined is no-op on the ShallowRenderer (#12756) 2018-05-07 17:31:33 -07:00
Dan Abramov
25dda90c1e Mark context consumers with PerformedWork effect on render (#12729)
* Mark new component types with PerformedWork effect

* Don't do it for ForwardRef

Since this has some overhead and ForwardRef is likely going to be used around context, let's skip it.
We don't highlight ForwardRef alone in DevTools anyway.
2018-05-02 16:35:16 +01:00
Dan Abramov
ad7cd68667 Rename internal property to fix React DevTools (#12727) 2018-05-01 21:04:20 +01:00
Sophie Alpert
200357596a Add error when running jest directly (#12726)
```
$ jest
 FAIL  scripts/jest/dont-run-jest-directly.js
  ● Test suite failed to run

    Don't run `jest` directly. Run `yarn test` instead.

    > 1 | throw new Error("Don't run `jest` directly. Run `yarn test` instead.");
      2 |

      at Object.<anonymous> (scripts/jest/dont-run-jest-directly.js:1:96)

Test Suites: 1 failed, 1 total
Tests:       0 total
Snapshots:   0 total
Time:        0.866s
Ran all test suites.
```
2018-05-01 12:46:17 -07:00
Dan Abramov
e0ca51a85d Make React.forwardRef() components discoverable by TestRenderer traversal (#12725) 2018-05-01 19:55:06 +01:00
Toru Kobayashi
7dd4ca2911 Call getDerivedStateFromProps even for setState of ShallowRenderer (#12676) 2018-04-30 17:04:40 +01:00
Dan Abramov
9a9f54720f Remove ES3-specific transforms (#12716) 2018-04-30 14:30:37 +01:00
Airam
dcc854bcc3 prevent removing attributes on custom component tags (#12702) 2018-04-28 20:52:30 +01:00
Dan Abramov
045d4f166d Fix a context propagation bug (#12708)
* Fix a context propagation bug

* Add a regression test
2018-04-28 01:52:48 +01:00
Dan Abramov
7c39328571 Don't bail on new context Provider if a legacy provider rendered above (#12586)
* Don't bail on new context Provider if a legacy provider rendered above

* Avoid an extra variable
2018-04-26 20:59:17 +01:00
Dan Abramov
d883d59863 forwardRef() components should not re-render on deep setState() (#12690)
* Add a failing test for forwardRef memoization

* Memoize forwardRef props and bail out on strict equality

* Bail out only when ref matches the current ref
2018-04-26 19:47:34 +01:00
Heaven
ec57d29941 Remove redundant feature flag in the test due to https://github.com/facebook/react/pull/12117 (#12696) 2018-04-26 18:39:11 +01:00
Flarnie Marchan
9c77ffb444 Dedup conditional in ReactScheduler (#12680)
**what is the change?:**
We had a condition to set either 'performance.now' or 'Date.now' as the
'now' function.

Then later we had another conditional checking again if
'performance.now' was supported, and using it if so, otherwise falling
back to 'Date.now'.

More efficient to just use the 'now' shortcut defined above.

**why make this change?:**
Fewer lines, clearer code.

**test plan:**
Now that we have tests we can run them :)
2018-04-24 08:54:31 -07:00
Andrew Clark
09a14eacd4 Update bundle sizes 2018-04-23 19:38:07 -07:00
Andrew Clark
1673485720 Revert stray console.log 2018-04-23 18:44:14 -07:00
Flarnie Marchan
1e3cd332a0 Remove the 'alwaysUseRequestIdleCallbackPolyfill' feature flag (#12648)
* Remove the 'alwaysUseRequestIdleCallbackPolyfill' feature flag

**what is the change?:**
Removes the feature flag 'alwaysUseRequestIdleCallbackPolyfill', such
that we **always** use the polyfill for requestIdleCallback.

**why make this change?:**
We have been testing this feature flag at 100% for some time internally,
and determined it works better for React than the native implementation.
Looks like RN was overriding the flag to use the native when possible,
but since no RN products are using 'async' mode it should be safe to
switch this flag over for RN as well.

**test plan:**
We have already been testing this internally for some time.

**issue:**
internal task t28128480

* fix mistaken conditional

* Add mocking of rAF, postMessage, and initial test for ReactScheduler

**what is the change?:**
- In all tests where we previously mocked rIC or relied on native
mocking which no longer works, we are now mocking rAF and postMessage.
- Also adds a basic initial test for ReactScheduler.
NOTE -> we do plan to write headless browser tests for ReactScheduler!
This is just an initial test, to verify that it works with the mocked
out browser APIs as expected.

**why make this change?:**
We need to mock out the browser APIs more completely for the new
'ReactScheduler' to work in our tests. Many tests are depending on it,
since it's used at a low level.

By mocking the browser APIs rather than the 'react-scheduler' module, we
enable testing the production bundles. This approach is trading
isolation for accuracy. These tests will be closer to a real use.

**test plan:**
run the tests :)

**issue:**
internal task T28128480
2018-04-23 15:25:46 -07:00
Brian Vaughn
149a34f735 Exposed flushSync on the test renderer (#12672) 2018-04-23 10:27:39 -07:00
Andrew Clark
b548b3cd64 Decouple update queue from Fiber type (#12600)
* Decouple update queue from Fiber type

The update queue is in need of a refactor. Recent bugfixes (#12528) have
exposed some flaws in how it's modeled. Upcoming features like Suspense
and [redacted] also rely on the update queue in ways that weren't
anticipated in the original design.

Major changes:

- Instead of boolean flags for `isReplace` and `isForceUpdate`, updates
have a `tag` field (like Fiber). This lowers the cost for adding new
types of updates.
- Render phase updates are special cased. Updates scheduled during
the render phase are dropped if the work-in-progress does not commit.
This is used for `getDerivedStateFrom{Props,Catch}`.
- `callbackList` has been replaced with a generic effect list. Aside
from callbacks, this is also used for `componentDidCatch`.

* Remove first class UpdateQueue types and use closures instead

I tried to avoid this at first, since we avoid it everywhere else in the Fiber
codebase, but since updates are not in a hot path, the trade off with file size
seems worth it.

* Store captured errors on a separate part of the update queue

This way they can be reused independently of updates like
getDerivedStateFromProps. This will be important for resuming.

* Revert back to storing hasForceUpdate on the update queue

Instead of using the effect tag. Ideally, this would be part of the
return type of processUpdateQueue.

* Rename UpdateQueue effect type back to Callback

I don't love this name either, but it's less confusing than UpdateQueue
I suppose. Conceptually, this is usually a callback: setState callbacks,
componentDidCatch. The only case that feels a bit weird is Timeouts,
which use this effect to attach a promise listener. I guess that kinda
fits, too.

* Call getDerivedStateFromProps every render, even if props did not change

Rather than enqueue a new setState updater for every props change, we
can skip the update queue entirely and merge the result into state at
the end. This makes more sense, since "receiving props" is not an event
that should be observed. It's still a bit weird, since eventually we do
persist the derived state (in other words, it accumulates).

* Store captured effects on separate list from "own" effects (callbacks)

For resuming, we need the ability to discard the "own" effects while
reusing the captured effects.

* Optimize for class components

Change `process` and `callback` to match the expected payload types
for class components. I had intended for the update queue to be reusable
for both class components and a future React API, but we'll likely have
to fork anyway.

* Only double-invoke render phase lifecycles functions in DEV

* Use global state to track currently processing queue in DEV
2018-04-22 23:05:28 -07:00
Nicole Levy
5dcf93d146 Validate props on context providers (#12658)
* checkPropTypes in updateContextProvider

* invalid “prop”

* `type not `types` .. :l

* test

* don’t need extra check with no spelling mistake (:

* change error message to specifically address provider

* don’t need class, add extra render to make sure good props go through

* nitpicky rename

* prettier

* switch to `Context.Provider`

* add stack to warning, add extra undefined check

* separate dev check

* add stack to test

* more efficient

* remove unused function

* prettier

* const to top
2018-04-22 13:39:38 +01:00
Dan Abramov
c040bcbea8 Add server integration tests for new context (#12654)
* Add server integration tests for new context

* Pretty please

* Remove unused
2018-04-21 21:21:05 +01:00
Flarnie Marchan
999b656ed1 Initial commit (#12624)
This is the first step - pulling the ReactDOMFrameScheduling module out
into a separate package.

Co-authored-by: Brandon Dail <aweary@users.noreply.github.com>
2018-04-19 09:29:08 -07:00
Brian Vaughn
f80bbf88e5 StrictMode should not warn about polyfilled getSnapshotBeforeUpdate (#12647)
* Installed 3.x release of react-lifecycles-compat
* Updated ReactComponentLifeCycle-test and ReactDOMServerLifecycles-test to cover both polyfilled lifecycles in StrictMode
* Updated StrictMode warnings to not warn about polyfilled getSnapshotBeforeUpdate
2018-04-19 09:08:44 -07:00
Brian Vaughn
920f30ef77 Add forwardRef DEV warning for prop-types on render function (#12644) 2018-04-18 16:36:31 -07:00
Brian Vaughn
0887c7d56c Fork React Native renderer into FB and OSS bundles (#12625)
* Added new "native-fb" and "native-fabric-fb" bundles.
* Split RN_DEV and RN_PROD bundle types into RN_OSS_DEV, RN_OSS_PROD, RN_FB_DEV, and RN_FB_PROD. (This is a bit redundant but it seemed the least intrusive way of supporting a forked feature flags file for these bundles.)
* Renamed FB_DEV and FB_PROD bundle types to be more explicitly for www (FB_WWW_DEV and FB_WWW_PROD)
* Removed Haste @providesModule headers from the RB-specific RN renderer bundles to avoid a duplicate name conflicts.
* Remove dynamic values from OSS RN feature flags. (Leave them in FB RN feature flags.)
* Updated the sync script(s) to account for new renderer type.
* Move ReactFeatureFlags.js shim to FB bundle only (since OSS bundle no longer needs dynamic values).
2018-04-18 13:16:50 -07:00
Sebastian Markbåge
039695cc01 [RN] Update Secret Types (#12635) 2018-04-17 19:21:46 -07:00
Dan Abramov
b05e67e36a Bump Prettier (#12622) 2018-04-17 01:43:55 +01:00
Brian Vaughn
77ebeb1b09 Don't git commit noop-renderer unless package deps change (#12623) 2018-04-16 09:46:39 -07:00
Heaven
b85c5cd188 remove duplicate code in test (#12620) 2018-04-16 16:36:49 +01:00
Dan Abramov
01402f4ad9 Add 16.3.2 changelog (#12621) 2018-04-16 16:31:48 +01:00
250 changed files with 19341 additions and 12493 deletions

View File

@@ -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 }]
]
}

View File

@@ -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

View File

@@ -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
View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)) {

View File

@@ -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">

View File

@@ -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>;
}

View File

@@ -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>

View File

@@ -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;

View 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;

View File

@@ -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;

View 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;

View 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
View 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

View File

@@ -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"

View File

@@ -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",

View File

@@ -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,

View File

@@ -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,

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;
},
},
};

View 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];

View File

@@ -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

View 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;

View 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];

View File

@@ -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": [

View File

@@ -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;

View 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);
}

View 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 '';
}
}

View File

@@ -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 **)',

View File

@@ -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.

View File

@@ -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');
}

View File

@@ -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"
}
}

View File

@@ -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;

View File

@@ -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']);
});
});

View File

@@ -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",

View File

@@ -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');
});
});
});

View File

@@ -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,
);
});

View File

@@ -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;
}
});
});

View File

@@ -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);
}
});
});

View File

@@ -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);

View File

@@ -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');
});

View File

@@ -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);
});

View File

@@ -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');
});

View File

@@ -42,7 +42,7 @@ describe('ReactDOMServerIntegration', () => {
resetModules();
});
describe('context', function() {
describe('legacy context', function() {
let PurpleContext, RedContext;
beforeEach(() => {
class Parent extends React.Component {

View 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');
});
});
});

View File

@@ -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,
);
});
});
});

View File

@@ -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() {

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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');

View 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);
}
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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>;

View File

@@ -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);
}
},

View 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);
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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++) {

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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) {

View 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;

View File

@@ -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,

View File

@@ -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');
});

View File

@@ -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');
});

View File

@@ -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.';

View File

@@ -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;

View File

@@ -157,6 +157,9 @@ export function shouldRemoveAttribute(
) {
return true;
}
if (isCustomComponentTag) {
return false;
}
if (propertyInfo !== null) {
switch (propertyInfo.type) {
case BOOLEAN:

View File

@@ -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,
);
});

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -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);
});
});

View File

@@ -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,

View 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.
}

View 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 {}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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>,
) {

View File

@@ -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';

View File

@@ -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;

View File

@@ -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};

View 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
}

View File

@@ -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,

View File

@@ -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,
};

View File

@@ -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;

View File

@@ -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,
);
});
});

View File

@@ -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();

View File

@@ -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};

View File

@@ -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,
);
});
});

View File

@@ -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\\"}"
`;

View File

@@ -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\\"}"
`;

View 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');
}

View File

@@ -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