Skip to content

Instantly share code, notes, and snippets.

@vovkasm
Last active March 29, 2019 22:31
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vovkasm/f10e1f1c24e355ab242aabc98ba07fe0 to your computer and use it in GitHub Desktop.
Save vovkasm/f10e1f1c24e355ab242aabc98ba07fe0 to your computer and use it in GitHub Desktop.
How to hide keyboard by click outside text field withotu ScrollView
// HACK
import {
blurTextInput,
currentlyFocusedField,
focusTextInput,
isTextInput,
} from 'react-native/Libraries/Components/TextInput/TextInputState'
export { blurTextInput, currentlyFocusedField, focusTextInput, isTextInput }
// tslint:disable:object-literal-sort-keys
import React from 'react'
import { GestureResponderEvent, LayoutRectangle, NativeTouchEvent, UIManager } from 'react-native'
// HACK
import { blurTextInput, currentlyFocusedField, isTextInput } from 'App/lib/extendedTextState'
/**
* Touchable states.
*/
enum States {
NOT_RESPONDER, // Not the responder
PRESS_IN, // Responder, active, in the `PressRect`
PRESS_OUT, // Responder, active, out of `PressRect`
}
const PRESS_EXPAND_PX = 20
interface IProps {
disabled?: boolean
}
export class KeyboardDissmisser extends React.Component<IProps> {
private touchState: States = States.NOT_RESPONDER
private responderID: number | undefined = undefined
private viewLayout: LayoutRectangle | undefined = undefined
render() {
if (!this.props.children) return null
// TODO(vovkasm): fix this shit
const child = React.Children.only(this.props.children) as any
return React.cloneElement(child, {
accessible: false,
children: child.props.children,
onResponderGrant: this.onResponderGrant,
onResponderMove: this.onResponderMove,
onResponderRelease: this.onResponderRelease,
onResponderTerminate: this.onResponderTerminate,
onResponderTerminationRequest: this.onResponderTerminationRequest,
onStartShouldSetResponder: this.onStartShouldSetResponder,
})
}
private onResponderTerminationRequest = () => {
return true
}
/**
* Must return true to start the process of `Touchable`.
*/
private onStartShouldSetResponder = (e: GestureResponderEvent) => {
if (this.props.disabled) return false
const currentlyFocusedTextInput = currentlyFocusedField()
return currentlyFocusedTextInput != null && e.target !== currentlyFocusedTextInput
}
/**
* Place as callback for a DOM element's `onResponderGrant` event.
* @param {SyntheticEvent} e Synthetic event from event system.
*
*/
private onResponderGrant = (e: GestureResponderEvent) => {
this.responderID = e.currentTarget
this.touchState = States.PRESS_IN
this.measureView()
}
/**
* Place as callback for a DOM element's `onResponderRelease` event.
*/
private onResponderRelease = (e: GestureResponderEvent) => {
if (this.touchState === States.PRESS_IN) this.handlePress(e)
this.touchState = States.NOT_RESPONDER
}
/**
* Place as callback for a DOM element's `onResponderTerminate` event.
*/
private onResponderTerminate = (e: GestureResponderEvent) => {
this.touchState = States.NOT_RESPONDER
}
/**
* Place as callback for a DOM element's `onResponderMove` event.
*/
private onResponderMove = (e: GestureResponderEvent) => {
if (this.touchState === States.NOT_RESPONDER) return
// Measurement may not have returned yet.
if (!this.viewLayout) return
const viewLayout = this.viewLayout
const viewX = viewLayout.x
const viewY = viewLayout.y
const viewW = viewLayout.width
const viewH = viewLayout.height
const touch = extractSingleTouch(e.nativeEvent)
const x = touch.pageX
const y = touch.pageY
const isTouchWithinActive =
x > viewX - PRESS_EXPAND_PX &&
y > viewY - PRESS_EXPAND_PX &&
x < viewX + viewW + PRESS_EXPAND_PX &&
y < viewY + viewH + PRESS_EXPAND_PX
this.touchState = isTouchWithinActive ? States.PRESS_IN : States.PRESS_OUT
}
private handlePress(e: GestureResponderEvent) {
const current = currentlyFocusedField()
if (current && !isTextInput(e.target)) {
blurTextInput(current)
}
}
/**
* Measures the `HitRect` node on activation.
*/
private measureView() {
if (!this.responderID) return
UIManager.measure(this.responderID, this.handleMeasure)
}
private handleMeasure = (x: number, y: number, width: number, height: number, pageX: number, pageY: number) => {
// UIManager can't measure
if (!width && !height) return
if (this.viewLayout) {
const viewLayout = this.viewLayout
viewLayout.x = pageX
viewLayout.y = pageY
viewLayout.width = width
viewLayout.height = height
} else {
this.viewLayout = { x: pageX, y: pageY, width, height }
}
}
}
// TODO(vovkasm): Is this needed for RN?
/**
* Utility function for common case of extracting out the primary touch from a
* touch event.
* - `touchEnd` events usually do not have the `touches` property.
* http://stackoverflow.com/questions/3666929/
* mobile-sarai-touchend-event-not-firing-when-last-touch-is-removed
*
* @param nativeEvent Native event that may or may not be a touch.
* @return an object with pageX and pageY or null.
*/
function extractSingleTouch(nativeEvent: NativeTouchEvent): NativeTouchEvent {
const touches = nativeEvent.touches
const changedTouches = nativeEvent.changedTouches
const hasTouches = touches && touches.length > 0
const hasChangedTouches = changedTouches && changedTouches.length > 0
return !hasTouches && hasChangedTouches ? changedTouches[0] : hasTouches ? touches[0] : nativeEvent
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment