Authored by Zhu-Arthur

备份更新 review by 陈林

  1 +/**
  2 + * Copyright (c) 2015-present, Facebook, Inc.
  3 + * All rights reserved.
  4 + *
  5 + * This source code is licensed under the BSD-style license found in the
  6 + * LICENSE file in the root directory of this source tree. An additional grant
  7 + * of patent rights can be found in the PATENTS file in the same directory.
  8 + *
  9 + * @providesModule ScrollResponder
  10 + * @flow
  11 + */
  12 +'use strict';
  13 +
  14 +const Dimensions = require('Dimensions');
  15 +// modify by yoho 2018/6/5
  16 +// const FrameRateLogger = require('FrameRateLogger');
  17 +const Keyboard = require('Keyboard');
  18 +const ReactNative = require('ReactNative');
  19 +const Platform = require('Platform');
  20 +// const ReactNative = require('react/lib/ReactNative');
  21 +// <eof> modify by yoho
  22 +const Subscribable = require('Subscribable');
  23 +const TextInputState = require('TextInputState');
  24 +const UIManager = require('UIManager');
  25 +
  26 +const invariant = require('fbjs/lib/invariant');
  27 +const nullthrows = require('fbjs/lib/nullthrows');
  28 +const performanceNow = require('fbjs/lib/performanceNow');
  29 +const warning = require('fbjs/lib/warning');
  30 +
  31 +const { ScrollViewManager } = require('NativeModules');
  32 +const { getInstanceFromNode } = require('ReactNativeComponentTree');
  33 +
  34 +/**
  35 + * Mixin that can be integrated in order to handle scrolling that plays well
  36 + * with `ResponderEventPlugin`. Integrate with your platform specific scroll
  37 + * views, or even your custom built (every-frame animating) scroll views so that
  38 + * all of these systems play well with the `ResponderEventPlugin`.
  39 + *
  40 + * iOS scroll event timing nuances:
  41 + * ===============================
  42 + *
  43 + *
  44 + * Scrolling without bouncing, if you touch down:
  45 + * -------------------------------
  46 + *
  47 + * 1. `onMomentumScrollBegin` (when animation begins after letting up)
  48 + * ... physical touch starts ...
  49 + * 2. `onTouchStartCapture` (when you press down to stop the scroll)
  50 + * 3. `onTouchStart` (same, but bubble phase)
  51 + * 4. `onResponderRelease` (when lifting up - you could pause forever before * lifting)
  52 + * 5. `onMomentumScrollEnd`
  53 + *
  54 + *
  55 + * Scrolling with bouncing, if you touch down:
  56 + * -------------------------------
  57 + *
  58 + * 1. `onMomentumScrollBegin` (when animation begins after letting up)
  59 + * ... bounce begins ...
  60 + * ... some time elapses ...
  61 + * ... physical touch during bounce ...
  62 + * 2. `onMomentumScrollEnd` (Makes no sense why this occurs first during bounce)
  63 + * 3. `onTouchStartCapture` (immediately after `onMomentumScrollEnd`)
  64 + * 4. `onTouchStart` (same, but bubble phase)
  65 + * 5. `onTouchEnd` (You could hold the touch start for a long time)
  66 + * 6. `onMomentumScrollBegin` (When releasing the view starts bouncing back)
  67 + *
  68 + * So when we receive an `onTouchStart`, how can we tell if we are touching
  69 + * *during* an animation (which then causes the animation to stop)? The only way
  70 + * to tell is if the `touchStart` occurred immediately after the
  71 + * `onMomentumScrollEnd`.
  72 + *
  73 + * This is abstracted out for you, so you can just call this.scrollResponderIsAnimating() if
  74 + * necessary
  75 + *
  76 + * `ScrollResponder` also includes logic for blurring a currently focused input
  77 + * if one is focused while scrolling. The `ScrollResponder` is a natural place
  78 + * to put this logic since it can support not dismissing the keyboard while
  79 + * scrolling, unless a recognized "tap"-like gesture has occurred.
  80 + *
  81 + * The public lifecycle API includes events for keyboard interaction, responder
  82 + * interaction, and scrolling (among others). The keyboard callbacks
  83 + * `onKeyboardWill/Did/*` are *global* events, but are invoked on scroll
  84 + * responder's props so that you can guarantee that the scroll responder's
  85 + * internal state has been updated accordingly (and deterministically) by
  86 + * the time the props callbacks are invoke. Otherwise, you would always wonder
  87 + * if the scroll responder is currently in a state where it recognizes new
  88 + * keyboard positions etc. If coordinating scrolling with keyboard movement,
  89 + * *always* use these hooks instead of listening to your own global keyboard
  90 + * events.
  91 + *
  92 + * Public keyboard lifecycle API: (props callbacks)
  93 + *
  94 + * Standard Keyboard Appearance Sequence:
  95 + *
  96 + * this.props.onKeyboardWillShow
  97 + * this.props.onKeyboardDidShow
  98 + *
  99 + * `onScrollResponderKeyboardDismissed` will be invoked if an appropriate
  100 + * tap inside the scroll responder's scrollable region was responsible
  101 + * for the dismissal of the keyboard. There are other reasons why the
  102 + * keyboard could be dismissed.
  103 + *
  104 + * this.props.onScrollResponderKeyboardDismissed
  105 + *
  106 + * Standard Keyboard Hide Sequence:
  107 + *
  108 + * this.props.onKeyboardWillHide
  109 + * this.props.onKeyboardDidHide
  110 + */
  111 +
  112 +const IS_ANIMATING_TOUCH_START_THRESHOLD_MS = 16;
  113 +
  114 +type State = {
  115 + isTouching: boolean,
  116 + lastMomentumScrollBeginTime: number,
  117 + lastMomentumScrollEndTime: number,
  118 + observedScrollSinceBecomingResponder: boolean,
  119 + becameResponderWhileAnimating: boolean,
  120 +};
  121 +type Event = Object;
  122 +
  123 +// modify by yoho 2018/6/5
  124 +// function isTagInstanceOfTextInput(tag) {
  125 +// const instance = getInstanceFromNode(tag);
  126 +// return instance && instance.viewConfig && (
  127 +// instance.viewConfig.uiViewClassName === 'AndroidTextInput' ||
  128 +// instance.viewConfig.uiViewClassName === 'RCTMultilineTextInputView' ||
  129 +// instance.viewConfig.uiViewClassName === 'RCTSinglelineTextInputView'
  130 +// );
  131 +// }
  132 +// <eof> modify by yoho
  133 +
  134 +const ScrollResponderMixin = {
  135 + mixins: [Subscribable.Mixin],
  136 + scrollResponderMixinGetInitialState: function(): State {
  137 + return {
  138 + isTouching: false,
  139 + lastMomentumScrollBeginTime: 0,
  140 + lastMomentumScrollEndTime: 0,
  141 +
  142 + // Reset to false every time becomes responder. This is used to:
  143 + // - Determine if the scroll view has been scrolled and therefore should
  144 + // refuse to give up its responder lock.
  145 + // - Determine if releasing should dismiss the keyboard when we are in
  146 + // tap-to-dismiss mode (this.props.keyboardShouldPersistTaps !== 'always').
  147 + observedScrollSinceBecomingResponder: false,
  148 + becameResponderWhileAnimating: false,
  149 + };
  150 + },
  151 +
  152 + /**
  153 + * Invoke this from an `onScroll` event.
  154 + */
  155 + scrollResponderHandleScrollShouldSetResponder: function(): boolean {
  156 + return this.state.isTouching;
  157 + },
  158 +
  159 + /**
  160 + * Merely touch starting is not sufficient for a scroll view to become the
  161 + * responder. Being the "responder" means that the very next touch move/end
  162 + * event will result in an action/movement.
  163 + *
  164 + * Invoke this from an `onStartShouldSetResponder` event.
  165 + *
  166 + * `onStartShouldSetResponder` is used when the next move/end will trigger
  167 + * some UI movement/action, but when you want to yield priority to views
  168 + * nested inside of the view.
  169 + *
  170 + * There may be some cases where scroll views actually should return `true`
  171 + * from `onStartShouldSetResponder`: Any time we are detecting a standard tap
  172 + * that gives priority to nested views.
  173 + *
  174 + * - If a single tap on the scroll view triggers an action such as
  175 + * recentering a map style view yet wants to give priority to interaction
  176 + * views inside (such as dropped pins or labels), then we would return true
  177 + * from this method when there is a single touch.
  178 + *
  179 + * - Similar to the previous case, if a two finger "tap" should trigger a
  180 + * zoom, we would check the `touches` count, and if `>= 2`, we would return
  181 + * true.
  182 + *
  183 + */
  184 + scrollResponderHandleStartShouldSetResponder: function(): boolean {
  185 + // modify by yoho 2018/6/5
  186 + // const currentlyFocusedTextInput = TextInputState.currentlyFocusedField();
  187 + //
  188 + // if (this.props.keyboardShouldPersistTaps === 'handled' &&
  189 + // currentlyFocusedTextInput != null &&
  190 + // e.target !== currentlyFocusedTextInput) {
  191 + // return true;
  192 + // }
  193 + // <eof> modify by yoho
  194 + return false;
  195 + },
  196 +
  197 + /**
  198 + * There are times when the scroll view wants to become the responder
  199 + * (meaning respond to the next immediate `touchStart/touchEnd`), in a way
  200 + * that *doesn't* give priority to nested views (hence the capture phase):
  201 + *
  202 + * - Currently animating.
  203 + * - Tapping anywhere that is not a text input, while the keyboard is
  204 + * up (which should dismiss the keyboard).
  205 + *
  206 + * Invoke this from an `onStartShouldSetResponderCapture` event.
  207 + */
  208 + scrollResponderHandleStartShouldSetResponderCapture: function(e: Event): boolean {
  209 + // First see if we want to eat taps while the keyboard is up
  210 + const currentlyFocusedTextInput = TextInputState.currentlyFocusedField();
  211 + // modify by yoho 2018/6/5
  212 + // const {keyboardShouldPersistTaps} = this.props;
  213 + // const keyboardNeverPersistTaps = !keyboardShouldPersistTaps ||
  214 + // keyboardShouldPersistTaps === 'never';
  215 + // if (keyboardNeverPersistTaps &&
  216 + // currentlyFocusedTextInput != null &&
  217 + // !isTagInstanceOfTextInput(e.target)) {
  218 + // return true;
  219 + // }
  220 + if (!this.props.keyboardShouldPersistTaps &&
  221 + currentlyFocusedTextInput != null &&
  222 + e.target !== currentlyFocusedTextInput) {
  223 + return true;
  224 + }
  225 + // <eof> modify by yoho
  226 + return this.scrollResponderIsAnimating();
  227 + },
  228 +
  229 + /**
  230 + * Invoke this from an `onResponderReject` event.
  231 + *
  232 + * Some other element is not yielding its role as responder. Normally, we'd
  233 + * just disable the `UIScrollView`, but a touch has already began on it, the
  234 + * `UIScrollView` will not accept being disabled after that. The easiest
  235 + * solution for now is to accept the limitation of disallowing this
  236 + * altogether. To improve this, find a way to disable the `UIScrollView` after
  237 + * a touch has already started.
  238 + */
  239 + scrollResponderHandleResponderReject: function() {
  240 + },
  241 +
  242 + /**
  243 + * We will allow the scroll view to give up its lock iff it acquired the lock
  244 + * during an animation. This is a very useful default that happens to satisfy
  245 + * many common user experiences.
  246 + *
  247 + * - Stop a scroll on the left edge, then turn that into an outer view's
  248 + * backswipe.
  249 + * - Stop a scroll mid-bounce at the top, continue pulling to have the outer
  250 + * view dismiss.
  251 + * - However, without catching the scroll view mid-bounce (while it is
  252 + * motionless), if you drag far enough for the scroll view to become
  253 + * responder (and therefore drag the scroll view a bit), any backswipe
  254 + * navigation of a swipe gesture higher in the view hierarchy, should be
  255 + * rejected.
  256 + */
  257 + scrollResponderHandleTerminationRequest: function(): boolean {
  258 + return !this.state.observedScrollSinceBecomingResponder;
  259 + },
  260 +
  261 + /**
  262 + * Invoke this from an `onTouchEnd` event.
  263 + *
  264 + * @param {SyntheticEvent} e Event.
  265 + */
  266 + scrollResponderHandleTouchEnd: function(e: Event) {
  267 + const nativeEvent = e.nativeEvent;
  268 + this.state.isTouching = nativeEvent.touches.length !== 0;
  269 + this.props.onTouchEnd && this.props.onTouchEnd(e);
  270 + },
  271 +
  272 + /**
  273 + * Invoke this from an `onTouchCancel` event.
  274 + *
  275 + * @param {SyntheticEvent} e Event.
  276 + */
  277 + scrollResponderHandleTouchCancel: function(e: Event) {
  278 + this.state.isTouching = false;
  279 + this.props.onTouchCancel && this.props.onTouchCancel(e);
  280 + },
  281 +
  282 + /**
  283 + * Invoke this from an `onResponderRelease` event.
  284 + */
  285 + scrollResponderHandleResponderRelease: function(e: Event) {
  286 + this.props.onResponderRelease && this.props.onResponderRelease(e);
  287 +
  288 + // By default scroll views will unfocus a textField
  289 + // if another touch occurs outside of it
  290 + const currentlyFocusedTextInput = TextInputState.currentlyFocusedField();
  291 + // modify by yoho 2018/6/5
  292 + // if (this.props.keyboardShouldPersistTaps !== true &&
  293 + // this.props.keyboardShouldPersistTaps !== 'always' &&
  294 + // currentlyFocusedTextInput != null &&
  295 + // e.target !== currentlyFocusedTextInput &&
  296 + // !this.state.observedScrollSinceBecomingResponder &&
  297 + // !this.state.becameResponderWhileAnimating) {
  298 + // this.props.onScrollResponderKeyboardDismissed &&
  299 + // this.props.onScrollResponderKeyboardDismissed(e);
  300 + // TextInputState.blurTextInput(currentlyFocusedTextInput);
  301 + // }
  302 + if (!this.props.keyboardShouldPersistTaps &&
  303 + currentlyFocusedTextInput != null &&
  304 + e.target !== currentlyFocusedTextInput &&
  305 + !this.state.observedScrollSinceBecomingResponder &&
  306 + !this.state.becameResponderWhileAnimating) {
  307 + this.props.onScrollResponderKeyboardDismissed &&
  308 + this.props.onScrollResponderKeyboardDismissed(e);
  309 + TextInputState.blurTextInput(currentlyFocusedTextInput);
  310 + }
  311 + // <eof> modify by yoho
  312 + },
  313 +
  314 + scrollResponderHandleScroll: function(e: Event) {
  315 + this.state.observedScrollSinceBecomingResponder = true;
  316 + this.props.onScroll && this.props.onScroll(e);
  317 + },
  318 +
  319 + /**
  320 + * Invoke this from an `onResponderGrant` event.
  321 + */
  322 + scrollResponderHandleResponderGrant: function(e: Event) {
  323 + this.state.observedScrollSinceBecomingResponder = false;
  324 + this.props.onResponderGrant && this.props.onResponderGrant(e);
  325 + this.state.becameResponderWhileAnimating = this.scrollResponderIsAnimating();
  326 + },
  327 +
  328 + /**
  329 + * Unfortunately, `onScrollBeginDrag` also fires when *stopping* the scroll
  330 + * animation, and there's not an easy way to distinguish a drag vs. stopping
  331 + * momentum.
  332 + *
  333 + * Invoke this from an `onScrollBeginDrag` event.
  334 + */
  335 + scrollResponderHandleScrollBeginDrag: function(e: Event) {
  336 + // FrameRateLogger.beginScroll(); // TODO: track all scrolls after implementing onScrollEndAnimation // modify by yoho 2018/6/5
  337 + this.props.onScrollBeginDrag && this.props.onScrollBeginDrag(e);
  338 + },
  339 +
  340 + /**
  341 + * Invoke this from an `onScrollEndDrag` event.
  342 + */
  343 + scrollResponderHandleScrollEndDrag: function(e: Event) {
  344 + // modify by yoho 2018/6/5
  345 + // const {velocity} = e.nativeEvent;
  346 + // // - If we are animating, then this is a "drag" that is stopping the scrollview and momentum end
  347 + // // will fire.
  348 + // // - If velocity is non-zero, then the interaction will stop when momentum scroll ends or
  349 + // // another drag starts and ends.
  350 + // // - If we don't get velocity, better to stop the interaction twice than not stop it.
  351 + // if (!this.scrollResponderIsAnimating() &&
  352 + // (!velocity || velocity.x === 0 && velocity.y === 0)) {
  353 + // FrameRateLogger.endScroll();
  354 + // }
  355 + // <eof> modify by yoho
  356 + this.props.onScrollEndDrag && this.props.onScrollEndDrag(e);
  357 + },
  358 +
  359 + /**
  360 + * Invoke this from an `onMomentumScrollBegin` event.
  361 + */
  362 + scrollResponderHandleMomentumScrollBegin: function(e: Event) {
  363 + this.state.lastMomentumScrollBeginTime = performanceNow();
  364 + this.props.onMomentumScrollBegin && this.props.onMomentumScrollBegin(e);
  365 + },
  366 +
  367 + /**
  368 + * Invoke this from an `onMomentumScrollEnd` event.
  369 + */
  370 + scrollResponderHandleMomentumScrollEnd: function(e: Event) {
  371 + // FrameRateLogger.endScroll(); // modify by yoho 2018/6/5
  372 + this.state.lastMomentumScrollEndTime = performanceNow();
  373 + this.props.onMomentumScrollEnd && this.props.onMomentumScrollEnd(e);
  374 + },
  375 +
  376 + /**
  377 + * Invoke this from an `onTouchStart` event.
  378 + *
  379 + * Since we know that the `SimpleEventPlugin` occurs later in the plugin
  380 + * order, after `ResponderEventPlugin`, we can detect that we were *not*
  381 + * permitted to be the responder (presumably because a contained view became
  382 + * responder). The `onResponderReject` won't fire in that case - it only
  383 + * fires when a *current* responder rejects our request.
  384 + *
  385 + * @param {SyntheticEvent} e Touch Start event.
  386 + */
  387 + scrollResponderHandleTouchStart: function(e: Event) {
  388 + this.state.isTouching = true;
  389 + this.props.onTouchStart && this.props.onTouchStart(e);
  390 + },
  391 +
  392 + /**
  393 + * Invoke this from an `onTouchMove` event.
  394 + *
  395 + * Since we know that the `SimpleEventPlugin` occurs later in the plugin
  396 + * order, after `ResponderEventPlugin`, we can detect that we were *not*
  397 + * permitted to be the responder (presumably because a contained view became
  398 + * responder). The `onResponderReject` won't fire in that case - it only
  399 + * fires when a *current* responder rejects our request.
  400 + *
  401 + * @param {SyntheticEvent} e Touch Start event.
  402 + */
  403 + scrollResponderHandleTouchMove: function(e: Event) {
  404 + this.props.onTouchMove && this.props.onTouchMove(e);
  405 + },
  406 +
  407 + /**
  408 + * A helper function for this class that lets us quickly determine if the
  409 + * view is currently animating. This is particularly useful to know when
  410 + * a touch has just started or ended.
  411 + */
  412 + scrollResponderIsAnimating: function(): boolean {
  413 + const now = performanceNow();
  414 + const timeSinceLastMomentumScrollEnd = now - this.state.lastMomentumScrollEndTime;
  415 + const isAnimating = timeSinceLastMomentumScrollEnd < IS_ANIMATING_TOUCH_START_THRESHOLD_MS ||
  416 + this.state.lastMomentumScrollEndTime < this.state.lastMomentumScrollBeginTime;
  417 + return isAnimating;
  418 + },
  419 +
  420 + /**
  421 + * Returns the node that represents native view that can be scrolled.
  422 + * Components can pass what node to use by defining a `getScrollableNode`
  423 + * function otherwise `this` is used.
  424 + */
  425 + scrollResponderGetScrollableNode: function(): any {
  426 + return this.getScrollableNode ?
  427 + this.getScrollableNode() :
  428 + ReactNative.findNodeHandle(this);
  429 + },
  430 +
  431 + /**
  432 + * A helper function to scroll to a specific point in the ScrollView.
  433 + * This is currently used to help focus child TextViews, but can also
  434 + * be used to quickly scroll to any element we want to focus. Syntax:
  435 + *
  436 + * `scrollResponderScrollTo(options: {x: number = 0; y: number = 0; animated: boolean = true})`
  437 + *
  438 + * Note: The weird argument signature is due to the fact that, for historical reasons,
  439 + * the function also accepts separate arguments as as alternative to the options object.
  440 + * This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED.
  441 + */
  442 + scrollResponderScrollTo: function(
  443 + x?: number | { x?: number, y?: number, animated?: boolean },
  444 + y?: number,
  445 + animated?: boolean
  446 + ) {
  447 + if (typeof x === 'number') {
  448 + console.warn('`scrollResponderScrollTo(x, y, animated)` is deprecated. Use `scrollResponderScrollTo({x: 5, y: 5, animated: true})` instead.');
  449 + } else {
  450 + ({x, y, animated} = x || {});
  451 + }
  452 + UIManager.dispatchViewManagerCommand(
  453 + nullthrows(this.scrollResponderGetScrollableNode()),
  454 + UIManager.RCTScrollView.Commands.scrollTo,
  455 + [x || 0, y || 0, animated !== false],
  456 + );
  457 + },
  458 +
  459 + // add by yoho 2018/6/5
  460 + /****
  461 + *设置刷新事件
  462 + *
  463 + *
  464 + **/
  465 + scrollResponderStartPullToRefresh: function() {
  466 + UIManager.dispatchViewManagerCommand(
  467 + this.scrollResponderGetScrollableNode(),
  468 + UIManager.RCTScrollView.Commands.startPullToRefresh,
  469 + [],
  470 + );
  471 + },
  472 +
  473 + /****
  474 + *停止刷新事件
  475 + *
  476 + *
  477 + **/
  478 + scrollResponderStopPullToRefresh: function() {
  479 + UIManager.dispatchViewManagerCommand(
  480 + this.scrollResponderGetScrollableNode(),
  481 + UIManager.RCTScrollView.Commands.stopPullToRefresh,
  482 + [],
  483 + );
  484 + },
  485 + // <eof> add by yoho
  486 +
  487 + /**
  488 + * Scrolls to the end of the ScrollView, either immediately or with a smooth
  489 + * animation.
  490 + *
  491 + * Example:
  492 + *
  493 + * `scrollResponderScrollToEnd({animated: true})`
  494 + */
  495 + scrollResponderScrollToEnd: function(
  496 + options?: { animated?: boolean },
  497 + ) {
  498 + // Default to true
  499 + const animated = (options && options.animated) !== false;
  500 + UIManager.dispatchViewManagerCommand(
  501 + this.scrollResponderGetScrollableNode(),
  502 + UIManager.RCTScrollView.Commands.scrollToEnd,
  503 + [animated],
  504 + );
  505 + },
  506 +
  507 + /**
  508 + * Deprecated, do not use.
  509 + */
  510 + scrollResponderScrollWithoutAnimationTo: function(offsetX: number, offsetY: number) {
  511 + console.warn('`scrollResponderScrollWithoutAnimationTo` is deprecated. Use `scrollResponderScrollTo` instead');
  512 + this.scrollResponderScrollTo({x: offsetX, y: offsetY, animated: false});
  513 + },
  514 +
  515 + /**
  516 + * A helper function to zoom to a specific rect in the scrollview. The argument has the shape
  517 + * {x: number; y: number; width: number; height: number; animated: boolean = true}
  518 + *
  519 + * @platform ios
  520 + */
  521 + scrollResponderZoomTo: function(
  522 + rect: {| x: number, y: number, width: number, height: number, animated?: boolean |},
  523 + animated?: boolean // deprecated, put this inside the rect argument instead
  524 + ) {
  525 + // modify by yoho 2018/6/5
  526 + // invariant(ScrollViewManager && ScrollViewManager.zoomToRect, 'zoomToRect is not implemented');
  527 + // if ('animated' in rect) {
  528 + // animated = rect.animated;
  529 + // delete rect.animated;
  530 + // } else if (typeof animated !== 'undefined') {
  531 + // console.warn('`scrollResponderZoomTo` `animated` argument is deprecated. Use `options.animated` instead');
  532 + // }
  533 + // ScrollViewManager.zoomToRect(this.scrollResponderGetScrollableNode(), rect, animated !== false);
  534 + if (Platform.OS === 'android') {
  535 + invariant('zoomToRect is not implemented');
  536 + } else {
  537 + if ('animated' in rect) {
  538 + var { animated, ...rect } = rect;
  539 + } else if (typeof animated !== 'undefined') {
  540 + console.warn('`scrollResponderZoomTo` `animated` argument is deprecated. Use `options.animated` instead');
  541 + }
  542 + ScrollViewManager.zoomToRect(this.scrollResponderGetScrollableNode(), rect, animated !== false);
  543 + }
  544 + // <eof> modify by yoho
  545 + },
  546 +
  547 + /**
  548 + * Displays the scroll indicators momentarily.
  549 + */
  550 + scrollResponderFlashScrollIndicators: function() {
  551 + UIManager.dispatchViewManagerCommand(
  552 + this.scrollResponderGetScrollableNode(),
  553 + UIManager.RCTScrollView.Commands.flashScrollIndicators,
  554 + []
  555 + );
  556 + },
  557 +
  558 + /**
  559 + * This method should be used as the callback to onFocus in a TextInputs'
  560 + * parent view. Note that any module using this mixin needs to return
  561 + * the parent view's ref in getScrollViewRef() in order to use this method.
  562 + * @param {any} nodeHandle The TextInput node handle
  563 + * @param {number} additionalOffset The scroll view's bottom "contentInset".
  564 + * Default is 0.
  565 + * @param {bool} preventNegativeScrolling Whether to allow pulling the content
  566 + * down to make it meet the keyboard's top. Default is false.
  567 + */
  568 + scrollResponderScrollNativeHandleToKeyboard: function(nodeHandle: any, additionalOffset?: number, preventNegativeScrollOffset?: bool) {
  569 + this.additionalScrollOffset = additionalOffset || 0;
  570 + this.preventNegativeScrollOffset = !!preventNegativeScrollOffset;
  571 + UIManager.measureLayout(
  572 + nodeHandle,
  573 + ReactNative.findNodeHandle(this.getInnerViewNode()),
  574 + this.scrollResponderTextInputFocusError,
  575 + this.scrollResponderInputMeasureAndScrollToKeyboard
  576 + );
  577 + },
  578 +
  579 + /**
  580 + * The calculations performed here assume the scroll view takes up the entire
  581 + * screen - even if has some content inset. We then measure the offsets of the
  582 + * keyboard, and compensate both for the scroll view's "contentInset".
  583 + *
  584 + * @param {number} left Position of input w.r.t. table view.
  585 + * @param {number} top Position of input w.r.t. table view.
  586 + * @param {number} width Width of the text input.
  587 + * @param {number} height Height of the text input.
  588 + */
  589 + scrollResponderInputMeasureAndScrollToKeyboard: function(left: number, top: number, width: number, height: number) {
  590 + let keyboardScreenY = Dimensions.get('window').height;
  591 + if (this.keyboardWillOpenTo) {
  592 + keyboardScreenY = this.keyboardWillOpenTo.endCoordinates.screenY;
  593 + }
  594 + let scrollOffsetY = top - keyboardScreenY + height + this.additionalScrollOffset;
  595 +
  596 + // By default, this can scroll with negative offset, pulling the content
  597 + // down so that the target component's bottom meets the keyboard's top.
  598 + // If requested otherwise, cap the offset at 0 minimum to avoid content
  599 + // shifting down.
  600 + if (this.preventNegativeScrollOffset) {
  601 + scrollOffsetY = Math.max(0, scrollOffsetY);
  602 + }
  603 + this.scrollResponderScrollTo({x: 0, y: scrollOffsetY, animated: true});
  604 +
  605 + this.additionalOffset = 0;
  606 + this.preventNegativeScrollOffset = false;
  607 + },
  608 +
  609 + scrollResponderTextInputFocusError: function(e: Event) {
  610 + console.error('Error measuring text field: ', e);
  611 + },
  612 +
  613 + /**
  614 + * `componentWillMount` is the closest thing to a standard "constructor" for
  615 + * React components.
  616 + *
  617 + * The `keyboardWillShow` is called before input focus.
  618 + */
  619 + UNSAFE_componentWillMount: function() {
  620 + // modify by yoho 2018/6/5
  621 + // const {keyboardShouldPersistTaps} = this.props;
  622 + // warning(
  623 + // typeof keyboardShouldPersistTaps !== 'boolean',
  624 + // `'keyboardShouldPersistTaps={${keyboardShouldPersistTaps}}' is deprecated. `
  625 + // + `Use 'keyboardShouldPersistTaps="${keyboardShouldPersistTaps ? 'always' : 'never'}"' instead`
  626 + // );
  627 +// <eof> modify by yoho
  628 +
  629 + this.keyboardWillOpenTo = null;
  630 + this.additionalScrollOffset = 0;
  631 + this.addListenerOn(Keyboard, 'keyboardWillShow', this.scrollResponderKeyboardWillShow);
  632 + this.addListenerOn(Keyboard, 'keyboardWillHide', this.scrollResponderKeyboardWillHide);
  633 + this.addListenerOn(Keyboard, 'keyboardDidShow', this.scrollResponderKeyboardDidShow);
  634 + this.addListenerOn(Keyboard, 'keyboardDidHide', this.scrollResponderKeyboardDidHide);
  635 + },
  636 +
  637 + /**
  638 + * Warning, this may be called several times for a single keyboard opening.
  639 + * It's best to store the information in this method and then take any action
  640 + * at a later point (either in `keyboardDidShow` or other).
  641 + *
  642 + * Here's the order that events occur in:
  643 + * - focus
  644 + * - willShow {startCoordinates, endCoordinates} several times
  645 + * - didShow several times
  646 + * - blur
  647 + * - willHide {startCoordinates, endCoordinates} several times
  648 + * - didHide several times
  649 + *
  650 + * The `ScrollResponder` providesModule callbacks for each of these events.
  651 + * Even though any user could have easily listened to keyboard events
  652 + * themselves, using these `props` callbacks ensures that ordering of events
  653 + * is consistent - and not dependent on the order that the keyboard events are
  654 + * subscribed to. This matters when telling the scroll view to scroll to where
  655 + * the keyboard is headed - the scroll responder better have been notified of
  656 + * the keyboard destination before being instructed to scroll to where the
  657 + * keyboard will be. Stick to the `ScrollResponder` callbacks, and everything
  658 + * will work.
  659 + *
  660 + * WARNING: These callbacks will fire even if a keyboard is displayed in a
  661 + * different navigation pane. Filter out the events to determine if they are
  662 + * relevant to you. (For example, only if you receive these callbacks after
  663 + * you had explicitly focused a node etc).
  664 + */
  665 + scrollResponderKeyboardWillShow: function(e: Event) {
  666 + this.keyboardWillOpenTo = e;
  667 + this.props.onKeyboardWillShow && this.props.onKeyboardWillShow(e);
  668 + },
  669 +
  670 + scrollResponderKeyboardWillHide: function(e: Event) {
  671 + this.keyboardWillOpenTo = null;
  672 + this.props.onKeyboardWillHide && this.props.onKeyboardWillHide(e);
  673 + },
  674 +
  675 + scrollResponderKeyboardDidShow: function(e: Event) {
  676 + // TODO(7693961): The event for DidShow is not available on iOS yet.
  677 + // Use the one from WillShow and do not assign.
  678 + if (e) {
  679 + this.keyboardWillOpenTo = e;
  680 + }
  681 + this.props.onKeyboardDidShow && this.props.onKeyboardDidShow(e);
  682 + },
  683 +
  684 + scrollResponderKeyboardDidHide: function(e: Event) {
  685 + this.keyboardWillOpenTo = null;
  686 + this.props.onKeyboardDidHide && this.props.onKeyboardDidHide(e);
  687 + }
  688 +
  689 +};
  690 +
  691 +const ScrollResponder = {
  692 + Mixin: ScrollResponderMixin,
  693 +};
  694 +
  695 +module.exports = ScrollResponder;
@@ -193,7 +193,7 @@ export type Props<SectionT> = RequiredProps<SectionT> & @@ -193,7 +193,7 @@ export type Props<SectionT> = RequiredProps<SectionT> &
193 193
194 const defaultProps = { 194 const defaultProps = {
195 ...VirtualizedSectionList.defaultProps, 195 ...VirtualizedSectionList.defaultProps,
196 - stickySectionHeadersEnabled: Platform.OS === 'ios', 196 + stickySectionHeadersEnabled: true,
197 }; 197 };
198 198
199 type DefaultProps = typeof defaultProps; 199 type DefaultProps = typeof defaultProps;
  1 +/**
  2 + * Copyright (c) 2015-present, Facebook, Inc.
  3 + * All rights reserved.
  4 + *
  5 + * This source code is licensed under the BSD-style license found in the
  6 + * LICENSE file in the root directory of this source tree. An additional grant
  7 + * of patent rights can be found in the PATENTS file in the same directory.
  8 + *
  9 + * @providesModule WebView
  10 + */
  11 +'use strict';
  12 +
  13 +var EdgeInsetsPropType = require('EdgeInsetsPropType');
  14 +var ActivityIndicator = require('ActivityIndicator');
  15 +var React = require('React');
  16 +var PropTypes = require('prop-types');
  17 +var ReactNative = require('ReactNative');
  18 +var StyleSheet = require('StyleSheet');
  19 +var UIManager = require('UIManager');
  20 +var View = require('View');
  21 +var ViewPropTypes = require('ViewPropTypes');
  22 +
  23 +var deprecatedPropType = require('deprecatedPropType');
  24 +var keyMirror = require('fbjs/lib/keyMirror');
  25 +var requireNativeComponent = require('requireNativeComponent');
  26 +var resolveAssetSource = require('resolveAssetSource');
  27 +
  28 +var RCT_WEBVIEW_REF = 'webview';
  29 +
  30 +var WebViewState = keyMirror({
  31 + IDLE: null,
  32 + LOADING: null,
  33 + ERROR: null,
  34 +});
  35 +
  36 +//add by yoho
  37 +type Event = Object;
  38 +// <eof> add by yoho
  39 +
  40 +var defaultRenderLoading = () => (
  41 + <View style={styles.loadingView}>
  42 + <ActivityIndicator
  43 + style={styles.loadingProgressBar}
  44 + />
  45 + </View>
  46 +);
  47 +
  48 +/**
  49 + * Renders a native WebView.
  50 + */
  51 +class WebView extends React.Component {
  52 + static get extraNativeComponentConfig() {
  53 + return {
  54 + nativeOnly: {
  55 + messagingEnabled: PropTypes.bool,
  56 + },
  57 + };
  58 + }
  59 +
  60 + static propTypes = {
  61 + ...ViewPropTypes,
  62 + renderError: PropTypes.func,
  63 + renderLoading: PropTypes.func,
  64 + onLoad: PropTypes.func,
  65 + onLoadEnd: PropTypes.func,
  66 + onLoadStart: PropTypes.func,
  67 + onError: PropTypes.func,
  68 + automaticallyAdjustContentInsets: PropTypes.bool,
  69 + contentInset: EdgeInsetsPropType,
  70 + onNavigationStateChange: PropTypes.func,
  71 + onMessage: PropTypes.func,
  72 + onContentSizeChange: PropTypes.func,
  73 + startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
  74 + style: ViewPropTypes.style,
  75 + onShouldStartLoadWithRequest: PropTypes.func, //add by yoho
  76 +
  77 + html: deprecatedPropType(
  78 + PropTypes.string,
  79 + 'Use the `source` prop instead.'
  80 + ),
  81 +
  82 + url: deprecatedPropType(
  83 + PropTypes.string,
  84 + 'Use the `source` prop instead.'
  85 + ),
  86 +
  87 + /**
  88 + * Loads static html or a uri (with optional headers) in the WebView.
  89 + */
  90 + source: PropTypes.oneOfType([
  91 + PropTypes.shape({
  92 + /*
  93 + * The URI to load in the WebView. Can be a local or remote file.
  94 + */
  95 + uri: PropTypes.string,
  96 + /*
  97 + * The HTTP Method to use. Defaults to GET if not specified.
  98 + * NOTE: On Android, only GET and POST are supported.
  99 + */
  100 + method: PropTypes.oneOf(['GET', 'POST']),
  101 + /*
  102 + * Additional HTTP headers to send with the request.
  103 + * NOTE: On Android, this can only be used with GET requests.
  104 + */
  105 + headers: PropTypes.object,
  106 + /*
  107 + * The HTTP body to send with the request. This must be a valid
  108 + * UTF-8 string, and will be sent exactly as specified, with no
  109 + * additional encoding (e.g. URL-escaping or base64) applied.
  110 + * NOTE: On Android, this can only be used with POST requests.
  111 + */
  112 + body: PropTypes.string,
  113 + }),
  114 + PropTypes.shape({
  115 + /*
  116 + * A static HTML page to display in the WebView.
  117 + */
  118 + html: PropTypes.string,
  119 + /*
  120 + * The base URL to be used for any relative links in the HTML.
  121 + */
  122 + baseUrl: PropTypes.string,
  123 + }),
  124 + /*
  125 + * Used internally by packager.
  126 + */
  127 + PropTypes.number,
  128 + ]),
  129 +
  130 + /**
  131 + * Used on Android only, JS is enabled by default for WebView on iOS
  132 + * @platform android
  133 + */
  134 + javaScriptEnabled: PropTypes.bool,
  135 +
  136 + /**
  137 + * Used on Android Lollipop and above only, third party cookies are enabled
  138 + * by default for WebView on Android Kitkat and below and on iOS
  139 + * @platform android
  140 + */
  141 + thirdPartyCookiesEnabled: PropTypes.bool,
  142 +
  143 + /**
  144 + * Used on Android only, controls whether DOM Storage is enabled or not
  145 + * @platform android
  146 + */
  147 + domStorageEnabled: PropTypes.bool,
  148 +
  149 + /**
  150 + * Sets the JS to be injected when the webpage loads.
  151 + */
  152 + injectedJavaScript: PropTypes.string,
  153 +
  154 + /**
  155 + * Sets whether the webpage scales to fit the view and the user can change the scale.
  156 + */
  157 + scalesPageToFit: PropTypes.bool,
  158 +
  159 + /**
  160 + * Sets the user-agent for this WebView. The user-agent can also be set in native using
  161 + * WebViewConfig. This prop will overwrite that config.
  162 + */
  163 + userAgent: PropTypes.string,
  164 +
  165 + /**
  166 + * Used to locate this view in end-to-end tests.
  167 + */
  168 + testID: PropTypes.string,
  169 +
  170 + /**
  171 + * Determines whether HTML5 audio & videos require the user to tap before they can
  172 + * start playing. The default value is `false`.
  173 + */
  174 + mediaPlaybackRequiresUserAction: PropTypes.bool,
  175 +
  176 + /**
  177 + * Boolean that sets whether JavaScript running in the context of a file
  178 + * scheme URL should be allowed to access content from any origin.
  179 + * Including accessing content from other file scheme URLs
  180 + * @platform android
  181 + */
  182 + allowUniversalAccessFromFileURLs: PropTypes.bool,
  183 +
  184 + /**
  185 + * Function that accepts a string that will be passed to the WebView and
  186 + * executed immediately as JavaScript.
  187 + */
  188 + injectJavaScript: PropTypes.func,
  189 +
  190 + /**
  191 + * Specifies the mixed content mode. i.e WebView will allow a secure origin to load content from any other origin.
  192 + *
  193 + * Possible values for `mixedContentMode` are:
  194 + *
  195 + * - `'never'` (default) - WebView will not allow a secure origin to load content from an insecure origin.
  196 + * - `'always'` - WebView will allow a secure origin to load content from any other origin, even if that origin is insecure.
  197 + * - `'compatibility'` - WebView will attempt to be compatible with the approach of a modern web browser with regard to mixed content.
  198 + * @platform android
  199 + */
  200 + mixedContentMode: PropTypes.oneOf([
  201 + 'never',
  202 + 'always',
  203 + 'compatibility'
  204 + ]),
  205 +
  206 + /**
  207 + * Used on Android only, controls whether form autocomplete data should be saved
  208 + * @platform android
  209 + */
  210 + saveFormDataDisabled: PropTypes.bool,
  211 +
  212 + /**
  213 + * Override the native component used to render the WebView. Enables a custom native
  214 + * WebView which uses the same JavaScript as the original WebView.
  215 + */
  216 + nativeConfig: PropTypes.shape({
  217 + /*
  218 + * The native component used to render the WebView.
  219 + */
  220 + component: PropTypes.any,
  221 + /*
  222 + * Set props directly on the native component WebView. Enables custom props which the
  223 + * original WebView doesn't pass through.
  224 + */
  225 + props: PropTypes.object,
  226 + /*
  227 + * Set the ViewManager to use for communcation with the native side.
  228 + * @platform ios
  229 + */
  230 + viewManager: PropTypes.object,
  231 + }),
  232 + /*
  233 + * Used on Android only, controls whether the given list of URL prefixes should
  234 + * make {@link com.facebook.react.views.webview.ReactWebViewClient} to launch a
  235 + * default activity intent for those URL instead of loading it within the webview.
  236 + * Use this to list URLs that WebView cannot handle, e.g. a PDF url.
  237 + * @platform android
  238 + */
  239 + urlPrefixesForDefaultIntent: PropTypes.arrayOf(PropTypes.string),
  240 + };
  241 +
  242 + static defaultProps = {
  243 + javaScriptEnabled : true,
  244 + thirdPartyCookiesEnabled: true,
  245 + scalesPageToFit: true,
  246 + saveFormDataDisabled: false
  247 + };
  248 +
  249 + state = {
  250 + viewState: WebViewState.IDLE,
  251 + lastErrorEvent: null,
  252 + startInLoadingState: true,
  253 + };
  254 +
  255 + UNSAFE_componentWillMount() {
  256 + if (this.props.startInLoadingState) {
  257 + this.setState({viewState: WebViewState.LOADING});
  258 + }
  259 + }
  260 +
  261 + render() {
  262 + var otherView = null;
  263 +
  264 + if (this.state.viewState === WebViewState.LOADING) {
  265 + otherView = (this.props.renderLoading || defaultRenderLoading)();
  266 + } else if (this.state.viewState === WebViewState.ERROR) {
  267 + var errorEvent = this.state.lastErrorEvent;
  268 + otherView = this.props.renderError && this.props.renderError(
  269 + errorEvent.domain,
  270 + errorEvent.code,
  271 + errorEvent.description);
  272 + } else if (this.state.viewState !== WebViewState.IDLE) {
  273 + console.error('RCTWebView invalid state encountered: ' + this.state.loading);
  274 + }
  275 +
  276 + var webViewStyles = [styles.container, this.props.style];
  277 + if (this.state.viewState === WebViewState.LOADING ||
  278 + this.state.viewState === WebViewState.ERROR) {
  279 + // if we're in either LOADING or ERROR states, don't show the webView
  280 + webViewStyles.push(styles.hidden);
  281 + }
  282 +
  283 + var source = this.props.source || {};
  284 + if (this.props.html) {
  285 + source.html = this.props.html;
  286 + } else if (this.props.url) {
  287 + source.uri = this.props.url;
  288 + }
  289 +
  290 + if (source.method === 'POST' && source.headers) {
  291 + console.warn('WebView: `source.headers` is not supported when using POST.');
  292 + } else if (source.method === 'GET' && source.body) {
  293 + console.warn('WebView: `source.body` is not supported when using GET.');
  294 + }
  295 +
  296 + const nativeConfig = this.props.nativeConfig || {};
  297 +
  298 + let NativeWebView = nativeConfig.component || RCTWebView;
  299 +
  300 + // add by yoho 2018/6/5
  301 + var onShouldOverrideUrlLoading = this.props.onShouldStartLoadWithRequest
  302 + && ((event: Event) => {
  303 + var shouldOverride = !this.props.onShouldStartLoadWithRequest(event.nativeEvent);
  304 + UIManager.dispatchViewManagerCommandSync(
  305 + this.getWebViewHandle(),
  306 + UIManager.RCTWebView.Commands.shouldOverrideWithResult,
  307 + [shouldOverride]);
  308 + });
  309 + // <eof> add by yoho
  310 +
  311 + var webView =
  312 + <NativeWebView
  313 + ref={RCT_WEBVIEW_REF}
  314 + key="webViewKey"
  315 + style={webViewStyles}
  316 + source={resolveAssetSource(source)}
  317 + scalesPageToFit={this.props.scalesPageToFit}
  318 + injectedJavaScript={this.props.injectedJavaScript}
  319 + userAgent={this.props.userAgent}
  320 + javaScriptEnabled={this.props.javaScriptEnabled}
  321 + thirdPartyCookiesEnabled={this.props.thirdPartyCookiesEnabled}
  322 + domStorageEnabled={this.props.domStorageEnabled}
  323 + messagingEnabled={typeof this.props.onMessage === 'function'}
  324 + onMessage={this.onMessage}
  325 + contentInset={this.props.contentInset}
  326 + automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
  327 + onContentSizeChange={this.props.onContentSizeChange}
  328 + onLoadingStart={this.onLoadingStart}
  329 + onLoadingFinish={this.onLoadingFinish}
  330 + onLoadingError={this.onLoadingError}
  331 + testID={this.props.testID}
  332 + mediaPlaybackRequiresUserAction={this.props.mediaPlaybackRequiresUserAction}
  333 + allowUniversalAccessFromFileURLs={this.props.allowUniversalAccessFromFileURLs}
  334 + mixedContentMode={this.props.mixedContentMode}
  335 + saveFormDataDisabled={this.props.saveFormDataDisabled}
  336 + onShouldOverrideUrlLoading={onShouldOverrideUrlLoading}
  337 + urlPrefixesForDefaultIntent={this.props.urlPrefixesForDefaultIntent}
  338 + {...nativeConfig.props}
  339 + />;
  340 +
  341 + return (
  342 + <View style={styles.container}>
  343 + {webView}
  344 + {otherView}
  345 + </View>
  346 + );
  347 + }
  348 +
  349 + goForward = () => {
  350 + UIManager.dispatchViewManagerCommand(
  351 + this.getWebViewHandle(),
  352 + UIManager.RCTWebView.Commands.goForward,
  353 + null
  354 + );
  355 + };
  356 +
  357 + goBack = () => {
  358 + UIManager.dispatchViewManagerCommand(
  359 + this.getWebViewHandle(),
  360 + UIManager.RCTWebView.Commands.goBack,
  361 + null
  362 + );
  363 + };
  364 +
  365 + reload = () => {
  366 + this.setState({
  367 + viewState: WebViewState.LOADING
  368 + });
  369 + UIManager.dispatchViewManagerCommand(
  370 + this.getWebViewHandle(),
  371 + UIManager.RCTWebView.Commands.reload,
  372 + null
  373 + );
  374 + };
  375 +
  376 + stopLoading = () => {
  377 + UIManager.dispatchViewManagerCommand(
  378 + this.getWebViewHandle(),
  379 + UIManager.RCTWebView.Commands.stopLoading,
  380 + null
  381 + );
  382 + };
  383 +
  384 + postMessage = (data) => {
  385 + UIManager.dispatchViewManagerCommand(
  386 + this.getWebViewHandle(),
  387 + UIManager.RCTWebView.Commands.postMessage,
  388 + [String(data)]
  389 + );
  390 + };
  391 +
  392 + /**
  393 + * Injects a javascript string into the referenced WebView. Deliberately does not
  394 + * return a response because using eval() to return a response breaks this method
  395 + * on pages with a Content Security Policy that disallows eval(). If you need that
  396 + * functionality, look into postMessage/onMessage.
  397 + */
  398 + injectJavaScript = (data) => {
  399 + UIManager.dispatchViewManagerCommand(
  400 + this.getWebViewHandle(),
  401 + UIManager.RCTWebView.Commands.injectJavaScript,
  402 + [data]
  403 + );
  404 + };
  405 +
  406 + /**
  407 + * We return an event with a bunch of fields including:
  408 + * url, title, loading, canGoBack, canGoForward
  409 + */
  410 + updateNavigationState = (event) => {
  411 + if (this.props.onNavigationStateChange) {
  412 + this.props.onNavigationStateChange(event.nativeEvent);
  413 + }
  414 + };
  415 +
  416 + getWebViewHandle = () => {
  417 + return ReactNative.findNodeHandle(this.refs[RCT_WEBVIEW_REF]);
  418 + };
  419 +
  420 + onLoadingStart = (event) => {
  421 + var onLoadStart = this.props.onLoadStart;
  422 + onLoadStart && onLoadStart(event);
  423 + this.updateNavigationState(event);
  424 + };
  425 +
  426 + onLoadingError = (event) => {
  427 + event.persist(); // persist this event because we need to store it
  428 + var {onError, onLoadEnd} = this.props;
  429 + onError && onError(event);
  430 + onLoadEnd && onLoadEnd(event);
  431 + console.warn('Encountered an error loading page', event.nativeEvent);
  432 +
  433 + this.setState({
  434 + lastErrorEvent: event.nativeEvent,
  435 + viewState: WebViewState.ERROR
  436 + });
  437 + };
  438 +
  439 + onLoadingFinish = (event) => {
  440 + var {onLoad, onLoadEnd} = this.props;
  441 + onLoad && onLoad(event);
  442 + onLoadEnd && onLoadEnd(event);
  443 + this.setState({
  444 + viewState: WebViewState.IDLE,
  445 + });
  446 + this.updateNavigationState(event);
  447 + };
  448 +
  449 + onMessage = (event: Event) => {
  450 + var {onMessage} = this.props;
  451 + onMessage && onMessage(event);
  452 + }
  453 +}
  454 +
  455 +var RCTWebView = requireNativeComponent('RCTWebView', WebView, WebView.extraNativeComponentConfig);
  456 +
  457 +var styles = StyleSheet.create({
  458 + container: {
  459 + flex: 1,
  460 + },
  461 + hidden: {
  462 + height: 0,
  463 + flex: 0, // disable 'flex:1' when hiding a View
  464 + },
  465 + loadingView: {
  466 + flex: 1,
  467 + justifyContent: 'center',
  468 + alignItems: 'center',
  469 + },
  470 + loadingProgressBar: {
  471 + height: 20,
  472 + },
  473 +});
  474 +
  475 +module.exports = WebView;