docs: function (config) {
      const interactionState = {
        handle: (null: ?number),
      };
      const gestureState = {
        // Useful for debugging
        stateID: Math.random(),
      };
      PanResponder._initializeGestureState(gestureState);
      const panHandlers = {
        onStartShouldSetResponder: function (e) {
          return config.onStartShouldSetPanResponder === undefined ?
            false :
            config.onStartShouldSetPanResponder(e, gestureState);
        },
        onMoveShouldSetResponder: function (e) {
          return config.onMoveShouldSetPanResponder === undefined ?
            false :
            config.onMoveShouldSetPanResponder(e, gestureState);
        },
        onStartShouldSetResponderCapture: function (e) {
          // TODO: Actually, we should reinitialize the state any time
          // touches.length increases from 0 active to > 0 active.
          if (e.nativeEvent.touches.length === 1) {
            PanResponder._initializeGestureState(gestureState);
          }
          gestureState.numberActiveTouches = e.touchHistory.numberActiveTouches;
          return config.onStartShouldSetPanResponderCapture !== undefined ?
            config.onStartShouldSetPanResponderCapture(e, gestureState) :
            false;
        },
  
        onMoveShouldSetResponderCapture: function (e) {
          const touchHistory = e.touchHistory;
          // Responder system incorrectly dispatches should* to current responder
          // Filter out any touch moves past the first one - we would have
          // already processed multi-touch geometry during the first event.
          if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
            return false;
          }
          PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
          return config.onMoveShouldSetPanResponderCapture ?
            config.onMoveShouldSetPanResponderCapture(e, gestureState) :
            false;
        },
  
        onResponderGrant: function (e) {
          if (!interactionState.handle) {
            interactionState.handle = InteractionManager.createInteractionHandle();
          }
          gestureState.x0 = currentCentroidX(e.touchHistory);
          gestureState.y0 = currentCentroidY(e.touchHistory);
          gestureState.dx = 0;
          gestureState.dy = 0;
          if (config.onPanResponderGrant) {
            config.onPanResponderGrant(e, gestureState);
          }
          // TODO: t7467124 investigate if this can be removed
          return config.onShouldBlockNativeResponder === undefined ?
            true :
            config.onShouldBlockNativeResponder();
        },
  
        onResponderReject: function (e) {
          clearInteractionHandle(interactionState, config.onPanResponderReject, e, gestureState);
        },
  
        onResponderRelease: function (e) {
          clearInteractionHandle(interactionState, config.onPanResponderRelease, e, gestureState);
          PanResponder._initializeGestureState(gestureState);
        },
  
        onResponderStart: function (e) {
          const touchHistory = e.touchHistory;
          gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
          if (config.onPanResponderStart) {
            config.onPanResponderStart(e, gestureState);
          }
        },
  
        onResponderMove: function (e) {
          const touchHistory = e.touchHistory;
          // Guard against the dispatch of two touch moves when there are two
          // simultaneously changed touches.
          if (gestureState._accountsForMovesUpTo === touchHistory.mostRecentTimeStamp) {
            return;
          }
          // Filter out any touch moves past the first one - we would have
          // already processed multi-touch geometry during the first event.
          PanResponder._updateGestureStateOnMove(gestureState, touchHistory);
          if (config.onPanResponderMove) {
            config.onPanResponderMove(e, gestureState);
          }
        },
  
        onResponderEnd: function (e) {
          const touchHistory = e.touchHistory;
          gestureState.numberActiveTouches = touchHistory.numberActiveTouches;
          clearInteractionHandle(interactionState, config.onPanResponderEnd, e, gestureState);
        },
  
        onResponderTerminate: function (e) {
          clearInteractionHandle(interactionState, config.onPanResponderTerminate, e, gestureState);
          PanResponder._initializeGestureState(gestureState);
        },
  
        onResponderTerminationRequest: function (e) {
          return config.onPanResponderTerminationRequest === undefined ?
            true :
            config.onPanResponderTerminationRequest(e, gestureState);
        }
      };
      return {
        panHandlers,
        getInteractionHandle(): ?number {
          return interactionState.handle;
        },
      };
    }